File indexing completed on 2024-05-05 04:19:15

0001 // vim: set tabstop=4 shiftwidth=4 expandtab:
0002 /*
0003 Gwenview: an image viewer
0004 Copyright 2008 Aurélien Gâteau <agateau@kde.org>
0005 
0006 This program is free software; you can redistribute it and/or
0007 modify it under the terms of the GNU General Public License
0008 as published by the Free Software Foundation; either version 2
0009 of the License, or (at your option) any later version.
0010 
0011 This program is distributed in the hope that it will be useful,
0012 but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 GNU General Public License for more details.
0015 
0016 You should have received a copy of the GNU General Public License
0017 along with this program; if not, write to the Free Software
0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.
0019 
0020 */
0021 // Self
0022 #include "filtercontroller.h"
0023 
0024 // Qt
0025 #include <QAction>
0026 #include <QCompleter>
0027 #include <QIcon>
0028 #include <QLineEdit>
0029 #include <QPainter>
0030 #include <QPainterPath>
0031 #include <QTimer>
0032 #include <QToolButton>
0033 
0034 // KF
0035 #include <KComboBox>
0036 #include <KIconLoader>
0037 #include <KLocalizedString>
0038 
0039 // Local
0040 #include "gwenview_app_debug.h"
0041 #include <chrono>
0042 #include <lib/flowlayout.h>
0043 #include <lib/paintutils.h>
0044 
0045 using namespace std::chrono_literals;
0046 
0047 #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
0048 // KF
0049 
0050 // Local
0051 #endif
0052 
0053 namespace Gwenview
0054 {
0055 NameFilterWidget::NameFilterWidget(SortedDirModel *model)
0056     : mFilter(new NameFilter(model))
0057 {
0058     mModeComboBox = new KComboBox;
0059     mModeComboBox->addItem(i18n("Name contains"), QVariant(NameFilter::Contains));
0060     mModeComboBox->addItem(i18n("Name does not contain"), QVariant(NameFilter::DoesNotContain));
0061 
0062     mLineEdit = new QLineEdit;
0063 
0064     auto layout = new QHBoxLayout(this);
0065     layout->setContentsMargins(0, 0, 0, 0);
0066     layout->setSpacing(2);
0067     layout->addWidget(mModeComboBox);
0068     layout->addWidget(mLineEdit);
0069 
0070     auto timer = new QTimer(this);
0071     timer->setInterval(350ms);
0072     timer->setSingleShot(true);
0073     connect(timer, &QTimer::timeout, this, &NameFilterWidget::applyNameFilter);
0074 
0075     connect(mLineEdit, SIGNAL(textChanged(QString)), timer, SLOT(start()));
0076 
0077     connect(mModeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(applyNameFilter()));
0078 
0079     QTimer::singleShot(0, mLineEdit, SLOT(setFocus()));
0080 }
0081 
0082 NameFilterWidget::~NameFilterWidget()
0083 {
0084     delete mFilter;
0085 }
0086 
0087 void NameFilterWidget::applyNameFilter()
0088 {
0089     const QVariant data = mModeComboBox->itemData(mModeComboBox->currentIndex());
0090     mFilter->setMode(NameFilter::Mode(data.toInt()));
0091     mFilter->setText(mLineEdit->text());
0092 }
0093 
0094 DateFilterWidget::DateFilterWidget(SortedDirModel *model)
0095     : mFilter(new DateFilter(model))
0096 {
0097     mModeComboBox = new KComboBox;
0098     mModeComboBox->addItem(i18n("Date >="), DateFilter::GreaterOrEqual);
0099     mModeComboBox->addItem(i18n("Date ="), DateFilter::Equal);
0100     mModeComboBox->addItem(i18n("Date <="), DateFilter::LessOrEqual);
0101 
0102     mDateWidget = new DateWidget;
0103 
0104     auto layout = new QHBoxLayout(this);
0105     layout->setContentsMargins(0, 0, 0, 0);
0106     layout->addWidget(mModeComboBox);
0107     layout->addWidget(mDateWidget);
0108 
0109     connect(mDateWidget, &DateWidget::dateChanged, this, &DateFilterWidget::applyDateFilter);
0110     connect(mModeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(applyDateFilter()));
0111 
0112     applyDateFilter();
0113 }
0114 
0115 DateFilterWidget::~DateFilterWidget()
0116 {
0117     delete mFilter;
0118 }
0119 
0120 void DateFilterWidget::applyDateFilter()
0121 {
0122     const QVariant data = mModeComboBox->itemData(mModeComboBox->currentIndex());
0123     mFilter->setMode(DateFilter::Mode(data.toInt()));
0124     mFilter->setDate(mDateWidget->date());
0125 }
0126 
0127 #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
0128 RatingFilterWidget::RatingFilterWidget(SortedDirModel *model)
0129 {
0130     mModeComboBox = new KComboBox;
0131     mModeComboBox->addItem(i18n("Rating >="), RatingFilter::GreaterOrEqual);
0132     mModeComboBox->addItem(i18n("Rating ="), RatingFilter::Equal);
0133     mModeComboBox->addItem(i18n("Rating <="), RatingFilter::LessOrEqual);
0134 
0135     mRatingWidget = new KRatingWidget;
0136     mRatingWidget->setHalfStepsEnabled(true);
0137     mRatingWidget->setMaxRating(10);
0138 
0139     auto layout = new QHBoxLayout(this);
0140     layout->setContentsMargins(0, 0, 0, 0);
0141     layout->addWidget(mModeComboBox);
0142     layout->addWidget(mRatingWidget);
0143 
0144     mFilter = new RatingFilter(model);
0145 
0146     QObject::connect(mModeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(updateFilterMode()));
0147 
0148     QObject::connect(mRatingWidget, SIGNAL(ratingChanged(int)), SLOT(slotRatingChanged(int)));
0149 
0150     updateFilterMode();
0151 }
0152 
0153 RatingFilterWidget::~RatingFilterWidget()
0154 {
0155     delete mFilter;
0156 }
0157 
0158 void RatingFilterWidget::slotRatingChanged(int value)
0159 {
0160     mFilter->setRating(value);
0161 }
0162 
0163 void RatingFilterWidget::updateFilterMode()
0164 {
0165     const QVariant data = mModeComboBox->itemData(mModeComboBox->currentIndex());
0166     mFilter->setMode(RatingFilter::Mode(data.toInt()));
0167 }
0168 
0169 TagFilterWidget::TagFilterWidget(SortedDirModel *model)
0170     : mFilter(new TagFilter(model))
0171 {
0172     mModeComboBox = new KComboBox;
0173     mModeComboBox->addItem(i18n("Tagged"), QVariant(true));
0174     mModeComboBox->addItem(i18n("Not Tagged"), QVariant(false));
0175 
0176     mTagComboBox = new QComboBox;
0177 
0178     auto layout = new QHBoxLayout(this);
0179     layout->setContentsMargins(0, 0, 0, 0);
0180     layout->addWidget(mModeComboBox);
0181     layout->addWidget(mTagComboBox);
0182 
0183     AbstractSemanticInfoBackEnd *backEnd = model->semanticInfoBackEnd();
0184     backEnd->refreshAllTags();
0185     TagModel *tagModel = TagModel::createAllTagsModel(this, backEnd);
0186 
0187     auto completer = new QCompleter(mTagComboBox);
0188     completer->setCaseSensitivity(Qt::CaseInsensitive);
0189     completer->setModel(tagModel);
0190     mTagComboBox->setCompleter(completer);
0191     mTagComboBox->setInsertPolicy(QComboBox::NoInsert);
0192     mTagComboBox->setEditable(true);
0193     mTagComboBox->setModel(tagModel);
0194     mTagComboBox->setCurrentIndex(-1);
0195 
0196     connect(mTagComboBox, SIGNAL(currentIndexChanged(int)), SLOT(updateTagSetFilter()));
0197 
0198     connect(mModeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(updateTagSetFilter()));
0199 
0200     QTimer::singleShot(0, mTagComboBox, SLOT(setFocus()));
0201 }
0202 
0203 TagFilterWidget::~TagFilterWidget()
0204 {
0205     delete mFilter;
0206 }
0207 
0208 void TagFilterWidget::updateTagSetFilter()
0209 {
0210     QModelIndex index = mTagComboBox->model()->index(mTagComboBox->currentIndex(), 0);
0211     if (!index.isValid()) {
0212         qCWarning(GWENVIEW_APP_LOG) << "Invalid index";
0213         return;
0214     }
0215     SemanticInfoTag tag = index.data(TagModel::TagRole).toString();
0216     mFilter->setTag(tag);
0217 
0218     bool wantMatchingTag = mModeComboBox->itemData(mModeComboBox->currentIndex()).toBool();
0219     mFilter->setWantMatchingTag(wantMatchingTag);
0220 }
0221 #endif
0222 
0223 /**
0224  * A container for all filter widgets. It features a close button on the right.
0225  */
0226 class FilterWidgetContainer : public QFrame
0227 {
0228 public:
0229     FilterWidgetContainer()
0230     {
0231         QPalette pal = palette();
0232         pal.setColor(QPalette::Window, pal.color(QPalette::Highlight));
0233         setPalette(pal);
0234     }
0235 
0236     void setFilterWidget(QWidget *widget)
0237     {
0238         auto closeButton = new QToolButton;
0239         closeButton->setIcon(QIcon::fromTheme(QStringLiteral("window-close")));
0240         closeButton->setAutoRaise(true);
0241         closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
0242         int size = style()->pixelMetric(QStyle::PM_SmallIconSize);
0243         closeButton->setIconSize(QSize(size, size));
0244         connect(closeButton, &QAbstractButton::clicked, this, &QObject::deleteLater);
0245         auto layout = new QHBoxLayout(this);
0246         layout->setContentsMargins(2, 2, 2, 2);
0247         layout->setSpacing(2);
0248         layout->addWidget(widget);
0249         layout->addWidget(closeButton);
0250     }
0251 
0252 protected:
0253     void paintEvent(QPaintEvent *) override
0254     {
0255         QPainter painter(this);
0256         painter.setRenderHint(QPainter::Antialiasing);
0257         const QPainterPath path = PaintUtils::roundedRectangle(QRectF(rect()).adjusted(0.5, 0.5, -0.5, -0.5), 6);
0258 
0259         const QColor color = palette().color(QPalette::Highlight);
0260         painter.fillPath(path, PaintUtils::alphaAdjustedF(color, 0.5));
0261         painter.setPen(color);
0262         painter.drawPath(path);
0263     }
0264 };
0265 
0266 FilterController::FilterController(QFrame *frame, SortedDirModel *dirModel)
0267     : QObject(frame)
0268 {
0269     q = this;
0270     mFrame = frame;
0271     mDirModel = dirModel;
0272     mFilterWidgetCount = 0;
0273 
0274     mFrame->hide();
0275     auto layout = new FlowLayout(mFrame);
0276     layout->setSpacing(2);
0277 
0278     addAction(i18nc("@action:inmenu", "Filter by Name"), SLOT(addFilterByName()), QKeySequence(Qt::CTRL | Qt::Key_I));
0279 
0280     addAction(i18nc("@action:inmenu", "Filter by Date"), SLOT(addFilterByDate()), QKeySequence());
0281 
0282 #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
0283     addAction(i18nc("@action:inmenu", "Filter by Rating"), SLOT(addFilterByRating()), QKeySequence());
0284 
0285     addAction(i18nc("@action:inmenu", "Filter by Tag"), SLOT(addFilterByTag()), QKeySequence());
0286 #endif
0287 }
0288 
0289 QList<QAction *> FilterController::actionList() const
0290 {
0291     return mActionList;
0292 }
0293 
0294 void FilterController::addFilterByName()
0295 {
0296     addFilter(new NameFilterWidget(mDirModel));
0297 }
0298 
0299 void FilterController::addFilterByDate()
0300 {
0301     addFilter(new DateFilterWidget(mDirModel));
0302 }
0303 
0304 #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
0305 void FilterController::addFilterByRating()
0306 {
0307     addFilter(new RatingFilterWidget(mDirModel));
0308 }
0309 
0310 void FilterController::addFilterByTag()
0311 {
0312     addFilter(new TagFilterWidget(mDirModel));
0313 }
0314 #endif
0315 
0316 void FilterController::slotFilterWidgetClosed()
0317 {
0318     mFilterWidgetCount--;
0319     if (mFilterWidgetCount == 0) {
0320         mFrame->hide();
0321     }
0322 }
0323 
0324 void FilterController::addAction(const QString &text, const char *slot, const QKeySequence &shortcut)
0325 {
0326     auto action = new QAction(text, q);
0327     action->setShortcut(shortcut);
0328     QObject::connect(action, SIGNAL(triggered()), q, slot);
0329     mActionList << action;
0330 }
0331 
0332 void FilterController::addFilter(QWidget *widget)
0333 {
0334     if (mFrame->isHidden()) {
0335         mFrame->show();
0336     }
0337     auto container = new FilterWidgetContainer;
0338     container->setFilterWidget(widget);
0339     mFrame->layout()->addWidget(container);
0340 
0341     mFilterWidgetCount++;
0342     QObject::connect(container, &QObject::destroyed, q, &FilterController::slotFilterWidgetClosed);
0343 }
0344 
0345 } // namespace
0346 
0347 #include "moc_filtercontroller.cpp"