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"