File indexing completed on 2025-01-19 03:59:40

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2008-01-20
0007  * Description : User interface for searches
0008  *
0009  * SPDX-FileCopyrightText: 2008-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "searchview.h"
0016 
0017 // Qt includes
0018 
0019 #include <QGradient>
0020 #include <QHBoxLayout>
0021 #include <QPaintEvent>
0022 #include <QPainter>
0023 #include <QTimeLine>
0024 #include <QVBoxLayout>
0025 #include <QPushButton>
0026 #include <QApplication>
0027 #include <QDialogButtonBox>
0028 #include <QIcon>
0029 
0030 // KDE includes
0031 
0032 #include <klocalizedstring.h>
0033 
0034 // Local includes
0035 
0036 #include "digikam_debug.h"
0037 #include "digikam_globals.h"
0038 #include "searchgroup.h"
0039 #include "searchutilities.h"
0040 #include "searchwindow.h"
0041 #include "coredbsearchxml.h"
0042 #include "thememanager.h"
0043 
0044 namespace Digikam
0045 {
0046 
0047 AbstractSearchGroupContainer::AbstractSearchGroupContainer(QWidget* const parent)
0048     : QWidget     (parent),
0049       m_groupIndex(0)
0050 {
0051 }
0052 
0053 SearchGroup* AbstractSearchGroupContainer::addSearchGroup()
0054 {
0055     SearchGroup* const group = createSearchGroup();
0056     m_groups << group;
0057     addGroupToLayout(group);
0058 
0059     connect(group, SIGNAL(removeRequested()),
0060             this, SLOT(removeSendingSearchGroup()));
0061 
0062     return group;
0063 }
0064 
0065 void AbstractSearchGroupContainer::removeSearchGroup(SearchGroup* group)
0066 {
0067     if (group->groupType() == SearchGroup::FirstGroup)
0068     {
0069         qCWarning(DIGIKAM_GENERAL_LOG) << "Attempt to delete the primary search group";
0070         return;
0071     }
0072 
0073     m_groups.removeAll(group);
0074 
0075     // This method call may arise from an event handler of a widget within group. Defer deletion.
0076 
0077     group->deleteLater();
0078 }
0079 
0080 void AbstractSearchGroupContainer::startReadingGroups(SearchXmlCachingReader&)
0081 {
0082     m_groupIndex = 0;
0083 }
0084 
0085 void AbstractSearchGroupContainer::readGroup(SearchXmlCachingReader& reader)
0086 {
0087     SearchGroup* group = nullptr;
0088 
0089     if (m_groupIndex >= m_groups.size())
0090     {
0091         group = addSearchGroup();
0092     }
0093     else
0094     {
0095         group = m_groups.at(m_groupIndex);
0096     }
0097 
0098     group->read(reader);
0099 
0100     ++m_groupIndex;
0101 }
0102 
0103 void AbstractSearchGroupContainer::finishReadingGroups()
0104 {
0105     // remove superfluous groups
0106 
0107     while (m_groups.size() > (m_groupIndex + 1))
0108     {
0109         delete m_groups.takeLast();
0110     }
0111 
0112     // for empty searches, and we have an initial search group, reset the remaining search group
0113 
0114     if (!m_groupIndex && !m_groups.isEmpty())
0115     {
0116         m_groups.first()->reset();
0117     }
0118 }
0119 
0120 void AbstractSearchGroupContainer::writeGroups(SearchXmlWriter& writer) const
0121 {
0122     Q_FOREACH (SearchGroup* const group, m_groups)
0123     {
0124         group->write(writer);
0125     }
0126 }
0127 
0128 void AbstractSearchGroupContainer::removeSendingSearchGroup()
0129 {
0130     removeSearchGroup(static_cast<SearchGroup*>(sender()));
0131 }
0132 
0133 QList<QRect> AbstractSearchGroupContainer::startupAnimationAreaOfGroups() const
0134 {
0135     QList<QRect> list;
0136 
0137     Q_FOREACH (SearchGroup* const group, m_groups)
0138     {
0139         // cppcheck-suppress useStlAlgorithm
0140         list += group->startupAnimationArea();
0141     }
0142 
0143     return list;
0144 }
0145 
0146 // -------------------------------------------------------------------------
0147 
0148 class Q_DECL_HIDDEN SearchView::Private
0149 {
0150 public:
0151 
0152     explicit Private()
0153       : needAnimationForReadIn(false),
0154         layout                (nullptr),
0155         timeline              (nullptr),
0156         bar                   (nullptr)
0157     {
0158     }
0159 
0160     bool                     needAnimationForReadIn;
0161 
0162     QVBoxLayout*             layout;
0163     QCache<QString, QPixmap> pixmapCache;
0164     QTimeLine*               timeline;
0165 
0166     SearchViewBottomBar*     bar;
0167 };
0168 
0169 SearchView::SearchView()
0170     : d(new Private)
0171 {
0172     d->pixmapCache.setMaxCost(4);
0173 }
0174 
0175 SearchView::~SearchView()
0176 {
0177     delete d;
0178 }
0179 
0180 void SearchView::setup()
0181 {
0182     connect(ThemeManager::instance(), SIGNAL(signalThemeChanged()),
0183             this, SLOT(setTheme()));
0184 
0185     setTheme();
0186 
0187     d->layout = new QVBoxLayout;
0188     d->layout->setContentsMargins(QMargins());
0189     d->layout->setSpacing(0);
0190 
0191     // add stretch at bottom
0192 
0193     d->layout->addStretch(1);
0194 
0195     // create initial group
0196 
0197     addSearchGroup();
0198 
0199     setLayout(d->layout);
0200 
0201     // prepare animation
0202 
0203     d->timeline = new QTimeLine(500, this);
0204     d->timeline->setFrameRange(0, 100);
0205 
0206     connect(d->timeline, SIGNAL(finished()),
0207             this, SLOT(timeLineFinished()));
0208 
0209     connect(d->timeline, SIGNAL(frameChanged(int)),
0210             this, SLOT(animationFrame(int)));
0211 }
0212 
0213 void SearchView::setBottomBar(SearchViewBottomBar* const bar)
0214 {
0215     d->bar = bar;
0216 
0217     connect(d->bar, SIGNAL(okPressed()),
0218             this, SIGNAL(searchOk()));
0219 
0220     connect(d->bar, SIGNAL(cancelPressed()),
0221             this, SIGNAL(searchCancel()));
0222 
0223     connect(d->bar, SIGNAL(tryoutPressed()),
0224             this, SIGNAL(searchTryout()));
0225 
0226     connect(d->bar, SIGNAL(addGroupPressed()),
0227             this, SLOT(slotAddGroupButton()));
0228 
0229     connect(d->bar, SIGNAL(resetPressed()),
0230             this, SLOT(slotResetButton()));
0231 }
0232 
0233 void SearchView::read(const QString& xml)
0234 {
0235     SearchXmlCachingReader reader(xml);
0236 
0237     startReadingGroups(reader);
0238     SearchXml::Element element;
0239 
0240     while (!reader.atEnd())
0241     {
0242         element = reader.readNext();
0243 
0244         if (element == SearchXml::Group)
0245         {
0246             readGroup(reader);
0247         }
0248     }
0249 
0250     finishReadingGroups();
0251 
0252     if (isVisible())
0253     {
0254         startAnimation();
0255     }
0256     else
0257     {
0258         d->needAnimationForReadIn = true;
0259     }
0260 }
0261 
0262 void SearchView::addGroupToLayout(SearchGroup* group)
0263 {
0264     // insert at last-but-one position; leave stretch at the bottom
0265 
0266     d->layout->insertWidget(d->layout->count() - 1, group);
0267 }
0268 
0269 SearchGroup* SearchView::createSearchGroup()
0270 {
0271     SearchGroup* const group = new SearchGroup(this);
0272     group->setup(m_groups.isEmpty() ? SearchGroup::FirstGroup : SearchGroup::ChainGroup);
0273 
0274     return group;
0275 }
0276 
0277 void SearchView::slotAddGroupButton()
0278 {
0279     addSearchGroup();
0280 }
0281 
0282 void SearchView::slotResetButton()
0283 {
0284     while (m_groups.size() > 1)
0285     {
0286         delete m_groups.takeLast();
0287     }
0288 
0289     if (!m_groups.isEmpty())
0290     {
0291         if (m_groups.first())
0292         {
0293             m_groups.first()->reset();
0294         }
0295     }
0296 }
0297 
0298 QString SearchView::write() const
0299 {
0300     SearchXmlWriter writer;
0301     writeGroups(writer);
0302     writer.finish();
0303     qCDebug(DIGIKAM_GENERAL_LOG) << writer.xml();
0304 
0305     return writer.xml();
0306 }
0307 
0308 void SearchView::startAnimation()
0309 {
0310     d->timeline->setEasingCurve(QEasingCurve(QEasingCurve::InCurve));
0311     d->timeline->setDuration(500);
0312     d->timeline->setDirection(QTimeLine::Forward);
0313     d->timeline->start();
0314 }
0315 
0316 void SearchView::animationFrame(int)
0317 {
0318     update();
0319 }
0320 
0321 void SearchView::timeLineFinished()
0322 {
0323     if (d->timeline->direction() == QTimeLine::Forward)
0324     {
0325         d->timeline->setDirection(QTimeLine::Backward);
0326         d->timeline->start();
0327     }
0328     else
0329     {
0330         update();
0331     }
0332 }
0333 
0334 void SearchView::showEvent(QShowEvent*)
0335 {
0336     if (d->needAnimationForReadIn)
0337     {
0338         d->needAnimationForReadIn = false;
0339         startAnimation();
0340     }
0341 }
0342 
0343 void SearchView::paintEvent(QPaintEvent*)
0344 {
0345     if (d->timeline->state() == QTimeLine::Running)
0346     {
0347         QList<QRect> rects = startupAnimationAreaOfGroups();
0348 
0349         if (rects.isEmpty())
0350         {
0351             return;
0352         }
0353 
0354         int animationStep = d->timeline->currentFrame();
0355         const int margin  = 2;
0356 
0357         QRadialGradient grad(0.5, 0.5, 1, 0.5, 0.3);
0358         grad.setCoordinateMode(QGradient::ObjectBoundingMode);
0359         QColor color = qApp->palette().color(QPalette::Link);
0360         QColor colorStart(color), colorEnd(color);
0361         colorStart.setAlphaF(0);
0362         colorEnd.setAlphaF(color.alphaF()  * animationStep / 100.0);
0363         grad.setColorAt(0, colorEnd);
0364         grad.setColorAt(1, colorStart);
0365 
0366         QPainter p(this);
0367         p.setRenderHint(QPainter::Antialiasing, true);
0368         p.setPen(QPen(Qt::NoPen));
0369         p.setBrush(grad);
0370 
0371         Q_FOREACH (QRect rect, rects) // krazy:exclude=foreach
0372         {
0373             rect.adjust(-margin, -margin, margin, margin);
0374             p.drawRoundedRect(rect, 4, 4);
0375         }
0376     }
0377 }
0378 
0379 void SearchView::setTheme()
0380 {
0381     // settings with style sheet results in extremely slow painting
0382 
0383     setBackgroundRole(QPalette::Base);
0384 
0385     QFont f = font();
0386     QString fontSizeLarger;
0387     QString fontSizeSmaller;
0388 
0389     if (f.pointSizeF() == -1)
0390     {
0391         // set pixel size
0392 
0393         fontSizeLarger  = QString::number(f.pixelSize() + 2) + QLatin1String("px");
0394         fontSizeSmaller = QString::number(f.pixelSize())     + QLatin1String("px");
0395     }
0396     else
0397     {
0398         fontSizeLarger  = QString::number(f.pointSizeF() + 2) + QLatin1String("pt");
0399         fontSizeSmaller = QString::number(f.pointSizeF())     + QLatin1String("pt");
0400     }
0401 
0402     QString sheet =
0403         QLatin1String("#SearchGroupLabel_MainLabel "
0404                       " { font-weight: bold; font-size: ")
0405         + fontSizeLarger + QLatin1Char(';') +
0406         QLatin1String("   color: ")
0407         + qApp->palette().color(QPalette::HighlightedText).name() + QLatin1Char(';') +
0408         QLatin1String(" } "
0409                       "#SearchGroupLabel_SimpleLabel "
0410                       " { font-size: ")
0411         + fontSizeLarger + QLatin1Char(';') +
0412         QLatin1String("   color: ")
0413         + qApp->palette().color(QPalette::HighlightedText).name() + QLatin1Char(';') +
0414         QLatin1String(" } "
0415                       "#SearchGroupLabel_GroupOpLabel "
0416                       " { font-weight: bold; font-size: ")
0417         + fontSizeLarger + QLatin1Char(';') +
0418         QLatin1String("   color: ")
0419         + qApp->palette().color(QPalette::HighlightedText).name() + QLatin1Char(';') +
0420         QLatin1String("   text-decoration: underline; "
0421                       " } "
0422                       "#SearchGroupLabel_CheckBox "
0423                       " { color: ")
0424         + qApp->palette().color(QPalette::HighlightedText).name() + QLatin1Char(';') +
0425         QLatin1String(" } "
0426                       "#SearchGroupLabel_RemoveLabel "
0427                       " { color: ")
0428         + qApp->palette().color(QPalette::HighlightedText).name() + QLatin1Char(';') +
0429         QLatin1String("   font-style: italic; "
0430                       "   text-decoration: underline; "
0431                       " } "
0432                       "#SearchGroupLabel_OptionsLabel "
0433                       " { color: ")
0434         + qApp->palette().color(QPalette::HighlightedText).name() + QLatin1Char(';') +
0435         QLatin1String("   font-style: italic; "
0436                       "   text-decoration: underline; font-size: ")
0437         + fontSizeSmaller + QLatin1Char(';') +
0438         QLatin1String(" } "
0439                       "#SearchFieldGroupLabel_Label "
0440                       " { color: ")
0441         + qApp->palette().color(QPalette::Link).name() + QLatin1Char(';') +
0442         QLatin1String("   font-weight: bold; "
0443                       " } "
0444                       "#SearchField_MainLabel "
0445                       " { font-weight: bold; } "
0446                       "#SearchFieldChoice_ClickLabel "
0447                       " { color: ")
0448         + qApp->palette().color(QPalette::Link).name() + QLatin1Char(';') +
0449         QLatin1String("   font-style: italic; "
0450                       "   text-decoration: underline; "
0451                       " } "
0452                       "QComboBox#SearchFieldChoice_ComboBox"
0453                       " {  border-width: 0px; border-style: solid; padding-left: 5px; "
0454                       " } "
0455                       "QComboBox::drop-down#SearchFieldChoice_ComboBox"
0456                       " {  subcontrol-origin: padding; subcontrol-position: right top; "
0457                       "    border: 0px; background: rgba(0,0,0,0); width: 0px; height: 0px; "
0458                       " } ");
0459 
0460     QWidget::setStyleSheet(sheet);
0461 
0462     d->pixmapCache.clear();
0463 }
0464 
0465 QPixmap SearchView::cachedBannerPixmap(int w, int h) const
0466 {
0467     QString key  = QLatin1String("BannerPixmap-") + QString::number(w) + QLatin1Char('-') + QString::number(h);
0468     QPixmap* pix = d->pixmapCache.object(key);
0469 
0470     if (!pix)
0471     {
0472         QPixmap pixmap(w, h);
0473         pixmap.fill(qApp->palette().color(QPalette::Highlight));
0474         d->pixmapCache.insert(key, new QPixmap(pixmap));
0475 
0476         return pixmap;
0477     }
0478     else
0479     {
0480         return *pix;
0481     }
0482 }
0483 
0484 QPixmap SearchView::groupLabelPixmap(int w, int h)
0485 {
0486     return cachedBannerPixmap(w, h);
0487 }
0488 
0489 QPixmap SearchView::bottomBarPixmap(int w, int h)
0490 {
0491     return cachedBannerPixmap(w, h);
0492 }
0493 
0494 // -------------------------------------------------------------------------
0495 
0496 SearchViewBottomBar::SearchViewBottomBar(SearchViewThemedPartsCache* const cache, QWidget* const parent)
0497     : QWidget     (parent),
0498       m_themeCache(cache)
0499 {
0500     m_mainLayout      = new QHBoxLayout;
0501 
0502     m_addGroupsButton = new QPushButton(i18n("Add Search Group"));
0503     m_addGroupsButton->setIcon(QIcon::fromTheme(QLatin1String("list-add")));
0504 
0505     connect(m_addGroupsButton, SIGNAL(clicked()),
0506             this, SIGNAL(addGroupPressed()));
0507 
0508     m_mainLayout->addWidget(m_addGroupsButton);
0509 
0510     m_resetButton = new QPushButton(i18n("Reset"));
0511     m_resetButton->setIcon(QIcon::fromTheme(QLatin1String("edit-undo")));
0512 
0513     connect(m_resetButton, SIGNAL(clicked()),
0514             this, SIGNAL(resetPressed()));
0515 
0516     m_mainLayout->addWidget(m_resetButton);
0517     m_mainLayout->addStretch(1);
0518 
0519     m_buttonBox = new QDialogButtonBox(this);
0520 
0521     QPushButton* const ok = m_buttonBox->addButton(QDialogButtonBox::Ok);
0522 
0523     connect(ok, SIGNAL(clicked()),
0524             this, SIGNAL(okPressed()));
0525 
0526     QPushButton* const cancel = m_buttonBox->addButton(QDialogButtonBox::Cancel);
0527 
0528     connect(cancel, SIGNAL(clicked()),
0529             this, SIGNAL(cancelPressed()));
0530 
0531     QPushButton* const aBtn = m_buttonBox->addButton(i18n("Try"), QDialogButtonBox::ApplyRole);
0532 
0533     connect(aBtn, SIGNAL(clicked()),
0534             this, SIGNAL(tryoutPressed()));
0535 
0536     m_mainLayout->addWidget(m_buttonBox);
0537 
0538     QPushButton* const help = m_buttonBox->addButton(QDialogButtonBox::Help);
0539 
0540     connect(help, SIGNAL(clicked()),
0541             this, SLOT(slotHelp()));
0542 
0543     setLayout(m_mainLayout);
0544 }
0545 
0546 void SearchViewBottomBar::paintEvent(QPaintEvent*)
0547 {
0548     // paint themed background
0549 
0550     QPainter p(this);
0551     p.drawPixmap(0, 0, m_themeCache->bottomBarPixmap(width(), height()));
0552 }
0553 
0554 void SearchViewBottomBar::slotHelp()
0555 {
0556     openOnlineDocumentation(QLatin1String("main_window"), QLatin1String("search_view"));
0557 }
0558 
0559 } // namespace Digikam
0560 
0561 #include "moc_searchview.cpp"