File indexing completed on 2024-12-15 04:54:36
0001 /* 0002 SPDX-FileCopyrightText: 2016-2024 Laurent Montel <montel@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "searchlinestatus.h" 0008 #include "configurefiltersdialog.h" 0009 #include "core/filtersavedmanager.h" 0010 #include "filtersavedmenu.h" 0011 #include "messagelist_debug.h" 0012 #include <KStatefulBrush> 0013 0014 #include <KColorScheme> 0015 #include <KIconEngine> 0016 #include <KIconLoader> 0017 #include <KLocalizedString> 0018 #include <QAbstractItemView> 0019 #include <QAction> 0020 #include <QActionGroup> 0021 #include <QCompleter> 0022 #include <QContextMenuEvent> 0023 #include <QMenu> 0024 #include <QPushButton> 0025 #include <QStandardPaths> 0026 #include <QStringListModel> 0027 #include <QWidgetAction> 0028 0029 static const char qLineEditclearButtonActionNameC[] = "_q_qlineeditclearaction"; 0030 #define MAX_COMPLETION_ITEMS 20 0031 using namespace MessageList::Core; 0032 SearchLineStatus::SearchLineStatus(QWidget *parent) 0033 : QLineEdit(parent) 0034 , mCompleter(new QCompleter(this)) 0035 , mCompleterListModel(new QStringListModel(this)) 0036 { 0037 setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::BottomEdge})); 0038 mCompleter->setCaseSensitivity(Qt::CaseInsensitive); 0039 mCompleter->setModel(mCompleterListModel); 0040 setCompleter(mCompleter); 0041 0042 setClearButtonEnabled(true); 0043 initializeActions(); 0044 createMenuSearch(); 0045 auto act = findChild<QAction *>(QLatin1StringView(qLineEditclearButtonActionNameC)); 0046 if (act) { 0047 connect(act, &QAction::triggered, this, &SearchLineStatus::slotClear); 0048 } else { 0049 qCWarning(MESSAGELIST_LOG) << "Clear button name was changed ! Please verify qt code"; 0050 } 0051 connect(FilterSavedManager::self(), &FilterSavedManager::activateFilter, this, &SearchLineStatus::slotActivateFilter); 0052 } 0053 0054 SearchLineStatus::~SearchLineStatus() = default; 0055 0056 void SearchLineStatus::keyPressEvent(QKeyEvent *e) 0057 { 0058 if (e->key() == Qt::Key_Escape) { 0059 if (mCompleter->popup()->isVisible()) { 0060 QLineEdit::keyPressEvent(e); 0061 } else { 0062 Q_EMIT forceLostFocus(); 0063 } 0064 } else if (e->key() == Qt::Key_Q && (e->modifiers().testFlag(Qt::ShiftModifier) && e->modifiers().testFlag(Qt::AltModifier))) { 0065 mLockAction->trigger(); 0066 } else { 0067 QLineEdit::keyPressEvent(e); 0068 } 0069 } 0070 0071 void SearchLineStatus::slotClear() 0072 { 0073 Q_EMIT clearButtonClicked(); 0074 } 0075 0076 void SearchLineStatus::updateLockAction() 0077 { 0078 if (mLocked) { 0079 mLockAction->setIcon(QIcon::fromTheme(QStringLiteral("object-locked"))); 0080 mLockAction->setToolTip(i18nc("@info:tooltip", "Prevent the quick search field from being cleared when changing folders")); 0081 } else { 0082 mLockAction->setIcon(QIcon::fromTheme(QStringLiteral("object-unlocked"))); 0083 mLockAction->setToolTip(i18nc("@info:tooltip", "Clear the quick search field when changing folders")); 0084 } 0085 } 0086 0087 void SearchLineStatus::setLocked(bool b) 0088 { 0089 if (mLocked != b) { 0090 slotToggledLockAction(); 0091 } 0092 } 0093 0094 bool SearchLineStatus::locked() const 0095 { 0096 return mLocked; 0097 } 0098 0099 void SearchLineStatus::initializeActions() 0100 { 0101 mLockAction = addAction(QIcon::fromTheme(QStringLiteral("object-locked")), QLineEdit::TrailingPosition); 0102 mLockAction->setWhatsThis(i18nc("@info:whatsthis", 0103 "Toggle this button if you want to keep your quick search " 0104 "locked when moving to other folders or when narrowing the search " 0105 "by message status.")); 0106 0107 connect(mLockAction, &QAction::triggered, this, &SearchLineStatus::slotToggledLockAction); 0108 updateLockAction(); 0109 0110 const QStringList overlays = QStringList() << QStringLiteral("list-add"); 0111 mWithFilter = QIcon(new KIconEngine(QStringLiteral("view-filter"), KIconLoader::global(), overlays)); 0112 mWithoutFilter = QIcon::fromTheme(QStringLiteral("view-filter")); 0113 mFiltersAction = addAction(mWithoutFilter, QLineEdit::LeadingPosition); 0114 mFiltersAction->setToolTip(i18n("Filter Mails by Status")); 0115 connect(mFiltersAction, &QAction::triggered, this, &SearchLineStatus::showMenu); 0116 0117 mSaveFilterAction = addAction(QIcon::fromTheme(QStringLiteral("edit-find")), QLineEdit::LeadingPosition); 0118 mSaveFilterAction->setToolTip(i18n("Saved Filter")); 0119 mFilterSavedMenu = new FilterSavedMenu(this); 0120 mSaveFilterAction->setMenu(mFilterSavedMenu); 0121 connect(mSaveFilterAction, &QAction::triggered, this, &SearchLineStatus::showSavedFiltersMenu); 0122 connect(mFilterSavedMenu, &FilterSavedMenu::saveFilter, this, &SearchLineStatus::saveFilter); 0123 connect(mFilterSavedMenu, &FilterSavedMenu::configureFilters, this, &SearchLineStatus::slotConfigureFilters); 0124 } 0125 0126 void SearchLineStatus::slotActivateFilter(const QString &identifier) 0127 { 0128 Filter *f = FilterSavedManager::self()->loadFilter(identifier); 0129 if (f) { 0130 Q_EMIT activateFilter(f); 0131 } else { 0132 qCWarning(MESSAGELIST_LOG) << "Impossible to load Filter from identifier :" << identifier; 0133 } 0134 } 0135 0136 void SearchLineStatus::slotConfigureFilters() 0137 { 0138 ConfigureFiltersDialog dlg(this); 0139 dlg.exec(); 0140 } 0141 0142 void SearchLineStatus::slotToggledLockAction() 0143 { 0144 mLocked = !mLocked; 0145 updateLockAction(); 0146 } 0147 0148 void SearchLineStatus::updateFilters() 0149 { 0150 QList<Akonadi::MessageStatus> lstStatus; 0151 0152 for (QAction *act : std::as_const(mFilterListActions)) { 0153 if (act->isChecked()) { 0154 Akonadi::MessageStatus status; 0155 status.fromQInt32(static_cast<qint32>(act->data().toInt())); 0156 lstStatus.append(status); 0157 } 0158 } 0159 mHasFilter = !lstStatus.isEmpty(); 0160 Q_EMIT filterActionChanged(lstStatus); 0161 updateFilterActionIcon(); 0162 } 0163 0164 void SearchLineStatus::showSavedFiltersMenu() 0165 { 0166 mFilterSavedMenu->exec(mapToGlobal(QPoint(0, height()))); 0167 } 0168 0169 void SearchLineStatus::showMenu() 0170 { 0171 if (mFilterMenu->exec(mapToGlobal(QPoint(0, height())))) { 0172 updateFilters(); 0173 } 0174 } 0175 0176 void SearchLineStatus::clearFilterAction() 0177 { 0178 for (QAction *act : std::as_const(mFilterListActions)) { 0179 act->setChecked(false); 0180 } 0181 mHasFilter = false; 0182 updateFilterActionIcon(); 0183 } 0184 0185 void SearchLineStatus::createFilterAction(const QIcon &icon, const QString &text, int value) 0186 { 0187 auto act = new QAction(icon, text, this); 0188 act->setCheckable(true); 0189 act->setData(value); 0190 mFilterMenu->addAction(act); 0191 mFilterListActions.append(act); 0192 } 0193 0194 void SearchLineStatus::updateFilterActionIcon() 0195 { 0196 mFiltersAction->setIcon(mHasFilter ? mWithFilter : mWithoutFilter); 0197 if (mColorName.isEmpty()) { 0198 const KColorScheme::BackgroundRole bgColorScheme(KColorScheme::PositiveBackground); 0199 KStatefulBrush bgBrush(KColorScheme::View, bgColorScheme); 0200 mColorName = bgBrush.brush(palette()).color().name(); 0201 } 0202 setStyleSheet(mHasFilter ? QStringLiteral("QLineEdit{ background-color:%1 }").arg(mColorName) : QString()); 0203 } 0204 0205 void SearchLineStatus::clearFilterButtonClicked() 0206 { 0207 clearFilterAction(); 0208 clearFilterByAction(); 0209 updateFilters(); 0210 } 0211 0212 void SearchLineStatus::createMenuSearch() 0213 { 0214 mFilterMenu = new QMenu(this); 0215 mFilterMenu->setObjectName(QLatin1StringView("filtermenu")); 0216 auto clearWidgetAction = new QWidgetAction(mFilterMenu); 0217 auto clearFilterButton = new QPushButton(i18n("Clear Filter"), mFilterMenu); 0218 connect(clearFilterButton, &QPushButton::clicked, this, &SearchLineStatus::clearFilterButtonClicked); 0219 0220 clearWidgetAction->setDefaultWidget(clearFilterButton); 0221 mFilterMenu->addAction(clearWidgetAction); 0222 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-unread")), 0223 i18nc("@action:inmenu Status of a message", "Unread"), 0224 Akonadi::MessageStatus::statusUnread().toQInt32()); 0225 0226 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-replied")), 0227 i18nc("@action:inmenu Status of a message", "Replied"), 0228 Akonadi::MessageStatus::statusReplied().toQInt32()); 0229 0230 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-forwarded")), 0231 i18nc("@action:inmenu Status of a message", "Forwarded"), 0232 Akonadi::MessageStatus::statusForwarded().toQInt32()); 0233 0234 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-mark-important")), 0235 i18nc("@action:inmenu Status of a message", "Important"), 0236 Akonadi::MessageStatus::statusImportant().toQInt32()); 0237 0238 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-task")), 0239 i18nc("@action:inmenu Status of a message", "Action Item"), 0240 Akonadi::MessageStatus::statusToAct().toQInt32()); 0241 0242 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-thread-watch")), 0243 i18nc("@action:inmenu Status of a message", "Watched"), 0244 Akonadi::MessageStatus::statusWatched().toQInt32()); 0245 0246 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-thread-ignored")), 0247 i18nc("@action:inmenu Status of a message", "Ignored"), 0248 Akonadi::MessageStatus::statusIgnored().toQInt32()); 0249 0250 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), 0251 i18nc("@action:inmenu Status of a message", "Has Attachment"), 0252 Akonadi::MessageStatus::statusHasAttachment().toQInt32()); 0253 0254 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-invitation")), 0255 i18nc("@action:inmenu Status of a message", "Has Invitation"), 0256 Akonadi::MessageStatus::statusHasInvitation().toQInt32()); 0257 0258 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-mark-junk")), 0259 i18nc("@action:inmenu Status of a message", "Spam"), 0260 Akonadi::MessageStatus::statusSpam().toQInt32()); 0261 0262 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-mark-notjunk")), 0263 i18nc("@action:inmenu Status of a message", "Ham"), 0264 Akonadi::MessageStatus::statusHam().toQInt32()); 0265 createFilterByAction(); 0266 } 0267 0268 void SearchLineStatus::createFilterByAction() 0269 { 0270 mFilterMenu->addSeparator(); 0271 auto grp = new QActionGroup(mFilterMenu); 0272 0273 mSearchEveryWhereAction = new QAction(i18n("Full Message"), mFilterMenu); 0274 mSearchEveryWhereAction->setCheckable(true); 0275 mSearchEveryWhereAction->setChecked(true); 0276 0277 mFilterMenu->addAction(mSearchEveryWhereAction); 0278 grp->addAction(mSearchEveryWhereAction); 0279 0280 mSearchAgainstBodyAction = new QAction(i18n("Body"), mFilterMenu); 0281 grp->addAction(mSearchAgainstBodyAction); 0282 mFilterMenu->addAction(mSearchAgainstBodyAction); 0283 mSearchAgainstBodyAction->setCheckable(true); 0284 0285 mSearchAgainstSubjectAction = new QAction(i18n("Subject"), mFilterMenu); 0286 grp->addAction(mSearchAgainstSubjectAction); 0287 mFilterMenu->addAction(mSearchAgainstSubjectAction); 0288 mSearchAgainstSubjectAction->setCheckable(true); 0289 0290 mSearchAgainstFromOrToAction = new QAction(mFilterMenu); 0291 changeSearchAgainstFromOrToText(); 0292 grp->addAction(mSearchAgainstFromOrToAction); 0293 mFilterMenu->addAction(mSearchAgainstFromOrToAction); 0294 mSearchAgainstFromOrToAction->setCheckable(true); 0295 0296 mSearchAgainstBccAction = new QAction(i18n("BCC"), mFilterMenu); 0297 grp->addAction(mSearchAgainstBccAction); 0298 mFilterMenu->addAction(mSearchAgainstBccAction); 0299 mSearchAgainstBccAction->setCheckable(true); 0300 connect(grp, &QActionGroup::triggered, this, &SearchLineStatus::slotFilterActionClicked); 0301 } 0302 0303 void SearchLineStatus::clearFilterByAction() 0304 { 0305 mSearchEveryWhereAction->setChecked(true); 0306 } 0307 0308 bool SearchLineStatus::containsOutboundMessages() const 0309 { 0310 return mContainsOutboundMessages; 0311 } 0312 0313 void SearchLineStatus::setContainsOutboundMessages(bool containsOutboundMessages) 0314 { 0315 if (mContainsOutboundMessages != containsOutboundMessages) { 0316 mContainsOutboundMessages = containsOutboundMessages; 0317 changeSearchAgainstFromOrToText(); 0318 } 0319 } 0320 0321 void SearchLineStatus::changeSearchAgainstFromOrToText() 0322 { 0323 if (mContainsOutboundMessages) { 0324 mSearchAgainstFromOrToAction->setText(i18n("To")); 0325 } else { 0326 mSearchAgainstFromOrToAction->setText(i18n("From")); 0327 } 0328 } 0329 0330 QuickSearchLine::SearchOptions SearchLineStatus::searchOptions() const 0331 { 0332 QuickSearchLine::SearchOptions searchOptions; 0333 if (mSearchEveryWhereAction->isChecked()) { 0334 searchOptions |= QuickSearchLine::SearchEveryWhere; 0335 } 0336 if (mSearchAgainstBodyAction->isChecked()) { 0337 searchOptions |= QuickSearchLine::SearchAgainstBody; 0338 } 0339 if (mSearchAgainstSubjectAction->isChecked()) { 0340 searchOptions |= QuickSearchLine::SearchAgainstSubject; 0341 } 0342 if (mSearchAgainstFromOrToAction->isChecked()) { 0343 if (mContainsOutboundMessages) { 0344 searchOptions |= QuickSearchLine::SearchAgainstTo; 0345 } else { 0346 searchOptions |= QuickSearchLine::SearchAgainstFrom; 0347 } 0348 } 0349 if (mSearchAgainstBccAction->isChecked()) { 0350 searchOptions |= QuickSearchLine::SearchAgainstBcc; 0351 } 0352 return searchOptions; 0353 } 0354 0355 void SearchLineStatus::setSearchOptions(QuickSearchLine::SearchOptions opts) 0356 { 0357 mSearchEveryWhereAction->setChecked(opts & QuickSearchLine::SearchEveryWhere); 0358 mSearchAgainstBodyAction->setChecked(opts & QuickSearchLine::SearchAgainstBody); 0359 mSearchAgainstSubjectAction->setChecked(opts & QuickSearchLine::SearchAgainstSubject); 0360 mSearchAgainstBccAction->setChecked(opts & QuickSearchLine::SearchAgainstBcc); 0361 if (mContainsOutboundMessages) { 0362 mSearchAgainstFromOrToAction->setChecked(opts & QuickSearchLine::SearchAgainstTo); 0363 } else { 0364 mSearchAgainstFromOrToAction->setChecked(opts & QuickSearchLine::SearchAgainstFrom); 0365 } 0366 } 0367 0368 void SearchLineStatus::setFilterMessageStatus(const QList<Akonadi::MessageStatus> &newLstStatus) 0369 { 0370 clearFilterAction(); 0371 clearFilterByAction(); 0372 // TODO unchecked all checkbox 0373 for (const Akonadi::MessageStatus &status : newLstStatus) { 0374 for (QAction *act : std::as_const(mFilterListActions)) { 0375 if (static_cast<qint32>(act->data().toInt()) == status.toQInt32()) { 0376 act->setChecked(true); 0377 mHasFilter = true; 0378 break; 0379 } 0380 } 0381 } 0382 updateFilterActionIcon(); 0383 } 0384 0385 void SearchLineStatus::slotFilterActionClicked(QAction *act) 0386 { 0387 Q_UNUSED(act) 0388 Q_EMIT searchOptionChanged(); 0389 } 0390 0391 void SearchLineStatus::addCompletionItem(const QString &str) 0392 { 0393 mListCompetion.removeAll(str); 0394 mListCompetion.prepend(str); 0395 while (mListCompetion.size() > MAX_COMPLETION_ITEMS) { 0396 mListCompetion.removeLast(); 0397 } 0398 mCompleterListModel->setStringList(mListCompetion); 0399 } 0400 0401 void SearchLineStatus::contextMenuEvent(QContextMenuEvent *e) 0402 { 0403 QMenu *popup = QLineEdit::createStandardContextMenu(); 0404 if (popup) { 0405 popup->addSeparator(); 0406 popup->addAction(QIcon::fromTheme(QStringLiteral("edit-clear-locationbar-rtl")), i18n("Clear History"), this, &SearchLineStatus::slotClearHistory); 0407 popup->exec(e->globalPos()); 0408 delete popup; 0409 } 0410 } 0411 0412 void SearchLineStatus::slotClearHistory() 0413 { 0414 mListCompetion.clear(); 0415 mCompleterListModel->setStringList(mListCompetion); 0416 } 0417 0418 #include "moc_searchlinestatus.cpp"