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"