File indexing completed on 2024-05-12 04:19:48

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 "tagwidget.h"
0023 
0024 // Qt
0025 #include <QComboBox>
0026 #include <QCompleter>
0027 #include <QHBoxLayout>
0028 #include <QKeyEvent>
0029 #include <QLineEdit>
0030 #include <QListView>
0031 #include <QPushButton>
0032 #include <QSortFilterProxyModel>
0033 #include <QTimer>
0034 #include <QVBoxLayout>
0035 
0036 // KF
0037 #include <KLocalizedString>
0038 
0039 // Local
0040 #include <lib/semanticinfo/tagitemdelegate.h>
0041 #include <lib/semanticinfo/tagmodel.h>
0042 
0043 namespace Gwenview
0044 {
0045 class TagCompleterModel : public QSortFilterProxyModel
0046 {
0047 public:
0048     TagCompleterModel(QObject *parent)
0049         : QSortFilterProxyModel(parent)
0050     {
0051     }
0052 
0053     void setTagInfo(const TagInfo &tagInfo)
0054     {
0055         mExcludedTagSet.clear();
0056         TagInfo::ConstIterator it = tagInfo.begin(), end = tagInfo.end();
0057         for (; it != end; ++it) {
0058             if (it.value()) {
0059                 mExcludedTagSet << it.key();
0060             }
0061         }
0062         invalidate();
0063     }
0064 
0065     void setSemanticInfoBackEnd(AbstractSemanticInfoBackEnd *backEnd)
0066     {
0067         setSourceModel(TagModel::createAllTagsModel(this, backEnd));
0068     }
0069 
0070 protected:
0071     bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
0072     {
0073         QModelIndex sourceIndex = sourceModel()->index(sourceRow, 0, sourceParent);
0074         SemanticInfoTag tag = sourceIndex.data(TagModel::TagRole).toString();
0075         return !mExcludedTagSet.contains(tag);
0076     }
0077 
0078 private:
0079     TagSet mExcludedTagSet;
0080 };
0081 
0082 /**
0083  * A simple class to eat return keys. We use it to avoid propagating the return
0084  * key from our KLineEdit to a dialog using TagWidget.
0085  * We can't use KLineEdit::setTrapReturnKey() because it does not play well
0086  * with QCompleter, it only deals with KCompletion.
0087  */
0088 class ReturnKeyEater : public QObject
0089 {
0090 public:
0091     explicit ReturnKeyEater(QObject *parent = nullptr)
0092         : QObject(parent)
0093     {
0094     }
0095 
0096 protected:
0097     bool eventFilter(QObject *, QEvent *event) override
0098     {
0099         if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
0100             auto keyEvent = static_cast<QKeyEvent *>(event);
0101             switch (keyEvent->key()) {
0102             case Qt::Key_Return:
0103             case Qt::Key_Enter:
0104                 return true;
0105             default:
0106                 return false;
0107             }
0108         }
0109         return false;
0110     }
0111 };
0112 
0113 struct TagWidgetPrivate {
0114     TagWidget *q = nullptr;
0115     TagInfo mTagInfo;
0116     QListView *mListView = nullptr;
0117     QComboBox *mComboBox = nullptr;
0118     QPushButton *mAddButton = nullptr;
0119     AbstractSemanticInfoBackEnd *mBackEnd = nullptr;
0120     TagCompleterModel *mTagCompleterModel = nullptr;
0121     TagModel *mAssignedTagModel = nullptr;
0122 
0123     void setupWidgets()
0124     {
0125         mListView = new QListView;
0126         auto delegate = new TagItemDelegate(mListView);
0127         QObject::connect(delegate, SIGNAL(removeTagRequested(SemanticInfoTag)), q, SLOT(removeTag(SemanticInfoTag)));
0128         QObject::connect(delegate, SIGNAL(assignTagToAllRequested(SemanticInfoTag)), q, SLOT(assignTag(SemanticInfoTag)));
0129         mListView->setItemDelegate(delegate);
0130         mListView->setModel(mAssignedTagModel);
0131 
0132         mComboBox = new QComboBox;
0133         mComboBox->setEditable(true);
0134         mComboBox->setInsertPolicy(QComboBox::NoInsert);
0135 
0136         mTagCompleterModel = new TagCompleterModel(q);
0137         auto completer = new QCompleter(q);
0138         completer->setCaseSensitivity(Qt::CaseInsensitive);
0139         completer->setModel(mTagCompleterModel);
0140         mComboBox->setCompleter(completer);
0141 
0142         mComboBox->setModel(mTagCompleterModel);
0143 
0144         mAddButton = new QPushButton;
0145         mAddButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0146         mAddButton->setToolTip(i18n("Add tag"));
0147         mAddButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0148         QObject::connect(mAddButton, SIGNAL(clicked()), q, SLOT(addTagFromComboBox()));
0149 
0150         auto layout = new QVBoxLayout(q);
0151         layout->setContentsMargins(0, 0, 0, 0);
0152         layout->addWidget(mListView);
0153 
0154         auto hLayout = new QHBoxLayout;
0155         hLayout->addWidget(mComboBox);
0156         hLayout->addWidget(mAddButton);
0157         layout->addLayout(hLayout);
0158 
0159         q->setTabOrder(mComboBox, mListView);
0160     }
0161 
0162     void fillTagModel()
0163     {
0164         Q_ASSERT(mBackEnd);
0165 
0166         mAssignedTagModel->clear();
0167         TagInfo::ConstIterator it = mTagInfo.constBegin(), end = mTagInfo.constEnd();
0168         for (; it != end; ++it) {
0169             mAssignedTagModel->addTag(it.key(), QString(), it.value() ? TagModel::FullyAssigned : TagModel::PartiallyAssigned);
0170         }
0171     }
0172 
0173     void updateCompleterModel()
0174     {
0175         mTagCompleterModel->setTagInfo(mTagInfo);
0176         mComboBox->setCurrentIndex(-1);
0177     }
0178 };
0179 
0180 TagWidget::TagWidget(QWidget *parent)
0181     : QWidget(parent)
0182     , d(new TagWidgetPrivate)
0183 {
0184     d->q = this;
0185     d->mBackEnd = nullptr;
0186     d->mAssignedTagModel = new TagModel(this);
0187     d->setupWidgets();
0188     installEventFilter(new ReturnKeyEater(this));
0189 
0190     connect(d->mComboBox->lineEdit(), &QLineEdit::returnPressed, this, &TagWidget::addTagFromComboBox);
0191 }
0192 
0193 TagWidget::~TagWidget()
0194 {
0195     delete d;
0196 }
0197 
0198 void TagWidget::setSemanticInfoBackEnd(AbstractSemanticInfoBackEnd *backEnd)
0199 {
0200     d->mBackEnd = backEnd;
0201     d->mAssignedTagModel->setSemanticInfoBackEnd(backEnd);
0202     d->mTagCompleterModel->setSemanticInfoBackEnd(backEnd);
0203 }
0204 
0205 void TagWidget::setTagInfo(const TagInfo &tagInfo)
0206 {
0207     d->mTagInfo = tagInfo;
0208     d->fillTagModel();
0209     d->updateCompleterModel();
0210 }
0211 
0212 void TagWidget::addTagFromComboBox()
0213 {
0214     Q_ASSERT(d->mBackEnd);
0215     QString label = d->mComboBox->currentText();
0216     if (label.isEmpty()) {
0217         return;
0218     }
0219     assignTag(d->mBackEnd->tagForLabel(label.trimmed()));
0220 
0221     // Use a QTimer because if the tag is new, it will be inserted in the model
0222     // and QComboBox will sometimes select it. At least it does so when the
0223     // model is empty.
0224     QTimer::singleShot(0, d->mComboBox, &QComboBox::clearEditText);
0225 }
0226 
0227 void TagWidget::assignTag(const SemanticInfoTag &tag)
0228 {
0229     d->mTagInfo[tag] = true;
0230     d->mAssignedTagModel->addTag(tag);
0231     d->updateCompleterModel();
0232 
0233     Q_EMIT tagAssigned(tag);
0234 }
0235 
0236 void TagWidget::removeTag(const SemanticInfoTag &tag)
0237 {
0238     d->mTagInfo.remove(tag);
0239     d->mAssignedTagModel->removeTag(tag);
0240     d->updateCompleterModel();
0241 
0242     Q_EMIT tagRemoved(tag);
0243 }
0244 
0245 } // namespace
0246 
0247 #include "moc_tagwidget.cpp"