File indexing completed on 2025-04-27 03:58:40

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2007-11-25
0007  * Description : a bar used to search a string - version not based on database models
0008  *
0009  * SPDX-FileCopyrightText: 2007-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2009-2010 by Johannes Wienke <languitar at semipol dot de>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "searchtextbar.h"
0017 
0018 // Qt includes
0019 
0020 #include <QContextMenuEvent>
0021 #include <QMenu>
0022 #include <QColor>
0023 #include <QPalette>
0024 #include <QString>
0025 #include <QMap>
0026 #include <QTimer>
0027 
0028 // KDE includes
0029 
0030 #include <klocalizedstring.h>
0031 #include <kconfiggroup.h>
0032 
0033 // Local includes
0034 
0035 #include "digikam_debug.h"
0036 
0037 namespace Digikam
0038 {
0039 
0040 bool operator==(const SearchTextSettings& a, const SearchTextSettings& b)
0041 {
0042     return ((a.caseSensitive == b.caseSensitive) && (a.text == b.text));
0043 }
0044 
0045 class Q_DECL_HIDDEN SearchTextBar::Private
0046 {
0047 public:
0048 
0049     explicit Private()
0050       : optionAutoCompletionModeEntry(QLatin1String("AutoCompletionMode")),
0051         optionCaseSensitiveEntry     (QLatin1String("CaseSensitive")),
0052         textQueryCompletion          (false),
0053         hasCaseSensitive             (true),
0054         highlightOnResult            (true),
0055         hasResultColor               (140, 220, 140),
0056         hasNoResultColor             (220, 140, 140),
0057         completer                    (nullptr),
0058         searchTimer                  (nullptr)
0059     {
0060     }
0061 
0062     QString            optionAutoCompletionModeEntry;
0063     QString            optionCaseSensitiveEntry;
0064 
0065     bool               textQueryCompletion;
0066     bool               hasCaseSensitive;
0067     bool               highlightOnResult;
0068 
0069     QColor             hasResultColor;
0070     QColor             hasNoResultColor;
0071 
0072     ModelCompleter*    completer;
0073 
0074     SearchTextSettings settings;
0075     QTimer*            searchTimer;   ///< Timer to delay search processing and not signals bombarding at each key pressed.
0076 };
0077 
0078 SearchTextBar::SearchTextBar(QWidget* const parent, const QString& name, const QString& msg)
0079     : QLineEdit        (parent),
0080       StateSavingObject(this),
0081       d                (new Private)
0082 {
0083     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
0084     setObjectName(name + QLatin1String(" Search Text Tool"));
0085     setClearButtonEnabled(true);
0086     setPlaceholderText(msg.isNull() ? i18nc("@info: search text bar", "Search...") : msg);
0087 
0088     d->completer   = new ModelCompleter(this);
0089     setCompleter(d->completer);
0090 
0091     d->searchTimer = new QTimer(this);
0092     d->searchTimer->setInterval(500);
0093     d->searchTimer->setSingleShot(true);
0094 
0095     connect(d->searchTimer, SIGNAL(timeout()),
0096             this, SLOT(slotTextChanged()));
0097 
0098     connect(this, SIGNAL(textChanged(QString)),
0099             d->searchTimer, SLOT(start()));
0100 
0101     connect(d->completer, QOverload<>::of(&ModelCompleter::signalActivated),
0102             this, [=]()
0103         {
0104             Q_EMIT completerActivated();
0105         }
0106     );
0107 
0108     connect(d->completer, QOverload<const int>::of(&ModelCompleter::signalHighlighted),
0109             this, [=](const int albumId)
0110         {
0111             Q_EMIT completerHighlighted(albumId);
0112         }
0113     );
0114 
0115     loadState();
0116 }
0117 
0118 SearchTextBar::~SearchTextBar()
0119 {
0120     saveState();
0121     delete d;
0122 }
0123 
0124 void SearchTextBar::doLoadState()
0125 {
0126     KConfigGroup group        = getConfigGroup();
0127     completer()->setCompletionMode((QCompleter::CompletionMode)group.readEntry(entryName(d->optionAutoCompletionModeEntry),
0128                                                                                (int)QCompleter::PopupCompletion));
0129     d->settings.caseSensitive = (Qt::CaseSensitivity)group.readEntry(entryName(d->optionCaseSensitiveEntry),
0130                                                                      (int)Qt::CaseInsensitive);
0131     setIgnoreCase(d->settings.caseSensitive == Qt::CaseInsensitive);
0132 }
0133 
0134 void SearchTextBar::doSaveState()
0135 {
0136     KConfigGroup group = getConfigGroup();
0137 
0138     if (completer()->completionMode() != QCompleter::PopupCompletion)
0139     {
0140         group.writeEntry(entryName(d->optionAutoCompletionModeEntry), (int)completer()->completionMode());
0141     }
0142     else
0143     {
0144         group.deleteEntry(entryName(d->optionAutoCompletionModeEntry));
0145     }
0146 
0147     group.writeEntry(entryName(d->optionCaseSensitiveEntry), (int)d->settings.caseSensitive);
0148     group.sync();
0149 }
0150 
0151 void SearchTextBar::setTextQueryCompletion(bool b)
0152 {
0153     d->textQueryCompletion = b;
0154 }
0155 
0156 bool SearchTextBar::hasTextQueryCompletion() const
0157 {
0158     return d->textQueryCompletion;
0159 }
0160 
0161 void SearchTextBar::setHighlightOnResult(bool highlight)
0162 {
0163     d->highlightOnResult = highlight;
0164 
0165     if (!highlight)
0166     {
0167         setPalette(QPalette());
0168     }
0169 }
0170 
0171 SearchTextBar::HighlightState SearchTextBar::getCurrentHighlightState() const
0172 {
0173     if      (palette() == QPalette())
0174     {
0175         return NEUTRAL;
0176     }
0177     else if (palette().color(QPalette::Active, QPalette::Base) == d->hasResultColor)
0178     {
0179         return HAS_RESULT;
0180     }
0181     else if (palette().color(QPalette::Active, QPalette::Base) == d->hasNoResultColor)
0182     {
0183         return NO_RESULT;
0184     }
0185 
0186     qCDebug(DIGIKAM_WIDGETS_LOG) << "Impossible highlighting state";
0187 
0188     return NEUTRAL;
0189 }
0190 
0191 void SearchTextBar::setCaseSensitive(bool b)
0192 {
0193     d->hasCaseSensitive = b;
0194 
0195     // Reset settings if selecting case sensitivity is not allowed
0196 
0197     if (!b)
0198     {
0199         d->settings.caseSensitive = Qt::CaseInsensitive;
0200     }
0201 
0202     // Re-Q_EMIT signal with changed settings
0203 
0204     if (!text().isEmpty())
0205     {
0206         Q_EMIT signalSearchTextSettings(d->settings);
0207     }
0208 }
0209 
0210 bool SearchTextBar::hasCaseSensitive() const
0211 {
0212     return d->hasCaseSensitive;
0213 }
0214 
0215 void SearchTextBar::setSearchTextSettings(const SearchTextSettings& settings)
0216 {
0217     d->settings = settings;
0218 }
0219 
0220 SearchTextSettings SearchTextBar::searchTextSettings() const
0221 {
0222     return d->settings;
0223 }
0224 
0225 ModelCompleter* SearchTextBar::completerModel() const
0226 {
0227     return d->completer;
0228 }
0229 
0230 void SearchTextBar::slotTextChanged()
0231 {
0232     QString txt = text();
0233 
0234     if (txt.isEmpty())
0235     {
0236         setPalette(QPalette());
0237     }
0238 
0239     d->settings.text = txt;
0240 
0241     Q_EMIT signalSearchTextSettings(d->settings);
0242 }
0243 
0244 void SearchTextBar::slotSearchResult(bool match)
0245 {
0246     // only highlight if text is not empty or highlighting is disabled.
0247 
0248     if (text().isEmpty() || !d->highlightOnResult)
0249     {
0250         setPalette(QPalette());
0251         return;
0252     }
0253 
0254     QPalette pal = palette();
0255     pal.setColor(QPalette::Active, QPalette::Base,
0256                  match ? d->hasResultColor
0257                        : d->hasNoResultColor);
0258     pal.setColor(QPalette::Active, QPalette::Text, Qt::black);
0259     setPalette(pal);
0260 }
0261 
0262 void SearchTextBar::contextMenuEvent(QContextMenuEvent* e)
0263 {
0264     QAction* cs       = nullptr;
0265     QMenu* const menu = createStandardContextMenu();
0266 
0267     if (d->hasCaseSensitive)
0268     {
0269         cs = menu->addAction(i18nc("@info: search text bar", "Case sensitive"));
0270         cs->setCheckable(true);
0271         cs->setChecked(d->settings.caseSensitive == Qt::CaseInsensitive ? false : true);
0272     }
0273 
0274     menu->exec(e->globalPos());
0275 
0276     if (d->hasCaseSensitive)
0277     {
0278         setIgnoreCase(!cs->isChecked());
0279     }
0280 
0281     delete menu;
0282 }
0283 
0284 void SearchTextBar::setIgnoreCase(bool ignore)
0285 {
0286     if (hasCaseSensitive())
0287     {
0288         if (ignore)
0289         {
0290             completer()->setCaseSensitivity(Qt::CaseInsensitive);
0291             d->settings.caseSensitive = Qt::CaseInsensitive;
0292         }
0293         else
0294         {
0295             completer()->setCaseSensitivity(Qt::CaseSensitive);
0296             d->settings.caseSensitive = Qt::CaseSensitive;
0297         }
0298     }
0299     else
0300     {
0301         completer()->setCaseSensitivity(Qt::CaseInsensitive);
0302         d->settings.caseSensitive = Qt::CaseInsensitive;
0303     }
0304 
0305     // Re-Q_EMIT signal with changed settings
0306 
0307     if (!text().isEmpty())
0308     {
0309         Q_EMIT signalSearchTextSettings(d->settings);
0310     }
0311 }
0312 
0313 } // namespace Digikam
0314 
0315 #include "moc_searchtextbar.cpp"