searchlinestatus.cpp 12.1 KB
Newer Older
Laurent Montel's avatar
Laurent Montel committed
1
/*
2
  SPDX-FileCopyrightText: 2016-2020 Laurent Montel <montel@kde.org>
Laurent Montel's avatar
Laurent Montel committed
3

4
  SPDX-License-Identifier: GPL-2.0-or-later
Laurent Montel's avatar
Laurent Montel committed
5
6
7
*/

#include "searchlinestatus.h"
8
#include "messagelist_debug.h"
9

10
11
#include <KLocalizedString>
#include <QAction>
12
13
#include <QStandardPaths>
#include <QMenu>
14
15
#include <KIconEngine>
#include <KIconLoader>
Laurent Montel's avatar
Laurent Montel committed
16
17
#include <QWidgetAction>
#include <QPushButton>
18
19
20
#include <QCompleter>
#include <QContextMenuEvent>
#include <QStringListModel>
21
#include <QAbstractItemView>
22
#include <KColorScheme>
Laurent Montel's avatar
Laurent Montel committed
23

24
static const char qLineEditclearButtonActionNameC[] = "_q_qlineeditclearaction";
Laurent Montel's avatar
Laurent Montel committed
25
#define MAX_COMPLETION_ITEMS 20
Laurent Montel's avatar
Laurent Montel committed
26
27
using namespace MessageList::Core;
SearchLineStatus::SearchLineStatus(QWidget *parent)
Laurent Montel's avatar
Laurent Montel committed
28
    : QLineEdit(parent)
Laurent Montel's avatar
Laurent Montel committed
29
{
30
    mCompleter = new QCompleter(this);
31
    mCompleterListModel = new QStringListModel(this);
32
33
    mCompleter->setModel(mCompleterListModel);
    setCompleter(mCompleter);
34

35
    setClearButtonEnabled(true);
36
    initializeActions();
37
    createMenuSearch();
Laurent Montel's avatar
Laurent Montel committed
38
    auto *act = findChild<QAction *>(QLatin1String(qLineEditclearButtonActionNameC));
39
    if (act) {
40
        connect(act, &QAction::triggered, this, &SearchLineStatus::slotClear);
41
42
43
    } else {
        qCWarning(MESSAGELIST_LOG) << "Clear button name was changed ! Please verify qt code";
    }
Laurent Montel's avatar
Laurent Montel committed
44
45
46
47
48
49
}

SearchLineStatus::~SearchLineStatus()
{
}

50
51
52
53
54
55
56
57
void SearchLineStatus::keyPressEvent(QKeyEvent *e)
{
    if (e->key() == Qt::Key_Escape) {
        if (mCompleter->popup()->isVisible()) {
            QLineEdit::keyPressEvent(e);
        } else {
            Q_EMIT forceLostFocus();
        }
Laurent Montel's avatar
Laurent Montel committed
58
    } else if (e->key() == Qt::Key_Q && (e->modifiers().testFlag(Qt::ShiftModifier) && e->modifiers().testFlag(Qt::AltModifier))) {
59
        mLockAction->trigger();
60
61
62
63
64
    } else {
        QLineEdit::keyPressEvent(e);
    }
}

65
66
67
68
69
void SearchLineStatus::slotClear()
{
    Q_EMIT clearButtonClicked();
}

70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
void SearchLineStatus::updateLockAction()
{
    if (mLocked) {
        mLockAction->setIcon(QIcon::fromTheme(QStringLiteral("object-locked")));
        mLockAction->setToolTip(i18nc("@info:tooltip",
                                      "Prevent the quick search field from being cleared when changing folders"));
    } else {
        mLockAction->setIcon(QIcon::fromTheme(QStringLiteral("object-unlocked")));
        mLockAction->setToolTip(i18nc("@info:tooltip", "Clear the quick search field when changing folders"));
    }
}

void SearchLineStatus::setLocked(bool b)
{
    if (mLocked != b) {
        slotToggledLockAction();
    }
}

Laurent Montel's avatar
Laurent Montel committed
89
90
91
92
93
bool SearchLineStatus::locked() const
{
    return mLocked;
}

94
95
96
void SearchLineStatus::initializeActions()
{
    mLockAction = addAction(QIcon::fromTheme(QStringLiteral("object-locked")), QLineEdit::TrailingPosition);
Laurent Montel's avatar
Laurent Montel committed
97
98
99
100
101
102
    mLockAction->setWhatsThis(
        i18nc("@info:whatsthis",
              "Toggle this button if you want to keep your quick search "
              "locked when moving to other folders or when narrowing the search "
              "by message status."));

103
104
    connect(mLockAction, &QAction::triggered, this, &SearchLineStatus::slotToggledLockAction);
    updateLockAction();
105

106
107
108
109
    const QStringList overlays = QStringList() << QStringLiteral("list-add");
    mWithFilter = QIcon(new KIconEngine(QStringLiteral("view-filter"), KIconLoader::global(), overlays));
    mWithoutFilter = QIcon::fromTheme(QStringLiteral("view-filter"));
    mFiltersAction = addAction(mWithoutFilter, QLineEdit::LeadingPosition);
Laurent Montel's avatar
Laurent Montel committed
110
111
    mFiltersAction->setToolTip(i18n("Filter Mails by Status"));

112
    connect(mFiltersAction, &QAction::triggered, this, &SearchLineStatus::showMenu);
113
114
115
116
117
118
119
}

void SearchLineStatus::slotToggledLockAction()
{
    mLocked = !mLocked;
    updateLockAction();
}
120

Laurent Montel's avatar
Laurent Montel committed
121
122
void SearchLineStatus::updateFilters()
{
Laurent Montel's avatar
Laurent Montel committed
123
    QVector<Akonadi::MessageStatus> lstStatus;
Laurent Montel's avatar
Laurent Montel committed
124

Laurent Montel's avatar
Laurent Montel committed
125
    for (QAction *act : qAsConst(mFilterListActions)) {
Laurent Montel's avatar
Laurent Montel committed
126
127
128
129
130
131
132
133
134
135
136
        if (act->isChecked()) {
            Akonadi::MessageStatus status;
            status.fromQInt32(static_cast< qint32 >(act->data().toInt()));
            lstStatus.append(status);
        }
    }
    mHasFilter = !lstStatus.isEmpty();
    Q_EMIT filterActionChanged(lstStatus);
    updateFilterActionIcon();
}

137
138
139
void SearchLineStatus::showMenu()
{
    if (mFilterMenu->exec(mapToGlobal(QPoint(0, height())))) {
Laurent Montel's avatar
Laurent Montel committed
140
        updateFilters();
141
142
143
144
145
    }
}

void SearchLineStatus::clearFilterAction()
{
Laurent Montel's avatar
Laurent Montel committed
146
    for (QAction *act : qAsConst(mFilterListActions)) {
147
148
        act->setChecked(false);
    }
149
    mHasFilter = false;
Laurent Montel's avatar
Laurent Montel committed
150
    updateFilterActionIcon();
151
152
153
154
}

void SearchLineStatus::createFilterAction(const QIcon &icon, const QString &text, int value)
{
Laurent Montel's avatar
Laurent Montel committed
155
    auto *act = new QAction(icon, text, this);
156
157
158
159
160
161
    act->setCheckable(true);
    act->setData(value);
    mFilterMenu->addAction(act);
    mFilterListActions.append(act);
}

162
163
164
void SearchLineStatus::updateFilterActionIcon()
{
    mFiltersAction->setIcon(mHasFilter ? mWithFilter : mWithoutFilter);
165
166
167
168
169
170
    if (mColorName.isEmpty()) {
        const KColorScheme::BackgroundRole bgColorScheme(KColorScheme::PositiveBackground);
        KStatefulBrush bgBrush(KColorScheme::View, bgColorScheme);
        mColorName = bgBrush.brush(this).color().name();
    }
    setStyleSheet(mHasFilter ? QStringLiteral("QLineEdit{ background-color:%1 }").arg(mColorName) : QString());
171
172
}

Laurent Montel's avatar
Laurent Montel committed
173
174
175
void SearchLineStatus::clearFilterButtonClicked()
{
    clearFilterAction();
Laurent Montel's avatar
Laurent Montel committed
176
    clearFilterByAction();
Laurent Montel's avatar
Laurent Montel committed
177
178
179
    updateFilters();
}

180
181
182
void SearchLineStatus::createMenuSearch()
{
    mFilterMenu = new QMenu(this);
Laurent Montel's avatar
Laurent Montel committed
183
    mFilterMenu->setObjectName(QStringLiteral("filtermenu"));
Laurent Montel's avatar
Laurent Montel committed
184
    auto *clearWidgetAction = new QWidgetAction(mFilterMenu);
Laurent Montel's avatar
Laurent Montel committed
185
186
187
188
189
    QPushButton *clearFilterButton = new QPushButton(i18n("Clear Filter"), mFilterMenu);
    connect(clearFilterButton, &QPushButton::clicked, this, &SearchLineStatus::clearFilterButtonClicked);

    clearWidgetAction->setDefaultWidget(clearFilterButton);
    mFilterMenu->addAction(clearWidgetAction);
190
    createFilterAction(QIcon::fromTheme(QStringLiteral("mail-unread")), i18nc("@action:inmenu Status of a message", "Unread"),
Laurent Montel's avatar
Laurent Montel committed
191
                       Akonadi::MessageStatus::statusUnread().toQInt32());
192
193

    createFilterAction(QIcon::fromTheme(QStringLiteral("mail-replied")),
Laurent Montel's avatar
Laurent Montel committed
194
195
                       i18nc("@action:inmenu Status of a message", "Replied"),
                       Akonadi::MessageStatus::statusReplied().toQInt32());
196
197

    createFilterAction(QIcon::fromTheme(QStringLiteral("mail-forwarded")),
Laurent Montel's avatar
Laurent Montel committed
198
199
                       i18nc("@action:inmenu Status of a message", "Forwarded"),
                       Akonadi::MessageStatus::statusForwarded().toQInt32());
200

201
    createFilterAction(QIcon::fromTheme(QStringLiteral("mail-mark-important")),
Laurent Montel's avatar
Laurent Montel committed
202
203
                       i18nc("@action:inmenu Status of a message", "Important"),
                       Akonadi::MessageStatus::statusImportant().toQInt32());
204
205

    createFilterAction(QIcon::fromTheme(QStringLiteral("mail-task")),
Laurent Montel's avatar
Laurent Montel committed
206
207
                       i18nc("@action:inmenu Status of a message", "Action Item"),
                       Akonadi::MessageStatus::statusToAct().toQInt32());
208

209
    createFilterAction(QIcon::fromTheme(QStringLiteral("mail-thread-watch")),
Laurent Montel's avatar
Laurent Montel committed
210
211
                       i18nc("@action:inmenu Status of a message", "Watched"),
                       Akonadi::MessageStatus::statusWatched().toQInt32());
212

213
    createFilterAction(QIcon::fromTheme(QStringLiteral("mail-thread-ignored")),
Laurent Montel's avatar
Laurent Montel committed
214
215
                       i18nc("@action:inmenu Status of a message", "Ignored"),
                       Akonadi::MessageStatus::statusIgnored().toQInt32());
216
217

    createFilterAction(QIcon::fromTheme(QStringLiteral("mail-attachment")),
Laurent Montel's avatar
Laurent Montel committed
218
219
                       i18nc("@action:inmenu Status of a message", "Has Attachment"),
                       Akonadi::MessageStatus::statusHasAttachment().toQInt32());
220
221

    createFilterAction(QIcon::fromTheme(QStringLiteral("mail-invitation")),
Laurent Montel's avatar
Laurent Montel committed
222
223
                       i18nc("@action:inmenu Status of a message", "Has Invitation"),
                       Akonadi::MessageStatus::statusHasInvitation().toQInt32());
224
225

    createFilterAction(QIcon::fromTheme(QStringLiteral("mail-mark-junk")),
Laurent Montel's avatar
Laurent Montel committed
226
227
                       i18nc("@action:inmenu Status of a message", "Spam"),
                       Akonadi::MessageStatus::statusSpam().toQInt32());
228
229

    createFilterAction(QIcon::fromTheme(QStringLiteral("mail-mark-notjunk")),
Laurent Montel's avatar
Laurent Montel committed
230
231
                       i18nc("@action:inmenu Status of a message", "Ham"),
                       Akonadi::MessageStatus::statusHam().toQInt32());
Laurent Montel's avatar
Laurent Montel committed
232
233
234
235
236
237
    createFilterByAction();
}

void SearchLineStatus::createFilterByAction()
{
    mFilterMenu->addSeparator();
Laurent Montel's avatar
Laurent Montel committed
238
    auto *grp = new QActionGroup(mFilterMenu);
Laurent Montel's avatar
Laurent Montel committed
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257

    mSearchEveryWhereAction = new QAction(i18n("Full Message"), mFilterMenu);
    mSearchEveryWhereAction->setCheckable(true);
    mSearchEveryWhereAction->setChecked(true);

    mFilterMenu->addAction(mSearchEveryWhereAction);
    grp->addAction(mSearchEveryWhereAction);

    mSearchAgainstBodyAction = new QAction(i18n("Body"), mFilterMenu);
    grp->addAction(mSearchAgainstBodyAction);
    mFilterMenu->addAction(mSearchAgainstBodyAction);
    mSearchAgainstBodyAction->setCheckable(true);

    mSearchAgainstSubjectAction = new QAction(i18n("Subject"), mFilterMenu);
    grp->addAction(mSearchAgainstSubjectAction);
    mFilterMenu->addAction(mSearchAgainstSubjectAction);
    mSearchAgainstSubjectAction->setCheckable(true);

    mSearchAgainstFromOrToAction = new QAction(mFilterMenu);
258
    changeSearchAgainstFromOrToText();
Laurent Montel's avatar
Laurent Montel committed
259
260
261
262
    grp->addAction(mSearchAgainstFromOrToAction);
    mFilterMenu->addAction(mSearchAgainstFromOrToAction);
    mSearchAgainstFromOrToAction->setCheckable(true);

Luigi Toscano's avatar
Luigi Toscano committed
263
    mSearchAgainstBccAction = new QAction(i18n("BCC"), mFilterMenu);
Laurent Montel's avatar
Laurent Montel committed
264
265
266
    grp->addAction(mSearchAgainstBccAction);
    mFilterMenu->addAction(mSearchAgainstBccAction);
    mSearchAgainstBccAction->setCheckable(true);
267
    connect(grp, &QActionGroup::triggered, this, &SearchLineStatus::slotFilterActionClicked);
Laurent Montel's avatar
Laurent Montel committed
268
269
270
271
272
}

void SearchLineStatus::clearFilterByAction()
{
    mSearchEveryWhereAction->setChecked(true);
273
}
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324

bool SearchLineStatus::containsOutboundMessages() const
{
    return mContainsOutboundMessages;
}

void SearchLineStatus::setContainsOutboundMessages(bool containsOutboundMessages)
{
    if (mContainsOutboundMessages != containsOutboundMessages) {
        mContainsOutboundMessages = containsOutboundMessages;
        changeSearchAgainstFromOrToText();
    }
}

void SearchLineStatus::changeSearchAgainstFromOrToText()
{
    if (mContainsOutboundMessages) {
        mSearchAgainstFromOrToAction->setText(i18n("To"));
    } else {
        mSearchAgainstFromOrToAction->setText(i18n("From"));
    }
}

QuickSearchLine::SearchOptions SearchLineStatus::searchOptions() const
{
    QuickSearchLine::SearchOptions searchOptions;
    if (mSearchEveryWhereAction->isChecked()) {
        searchOptions |= QuickSearchLine::SearchEveryWhere;
    }
    if (mSearchAgainstBodyAction->isChecked()) {
        searchOptions |= QuickSearchLine::SearchAgainstBody;
    }
    if (mSearchAgainstSubjectAction->isChecked()) {
        searchOptions |= QuickSearchLine::SearchAgainstSubject;
    }
    if (mSearchAgainstFromOrToAction->isChecked()) {
        if (mContainsOutboundMessages) {
            searchOptions |= QuickSearchLine::SearchAgainstTo;
        } else {
            searchOptions |= QuickSearchLine::SearchAgainstFrom;
        }
    }
    if (mSearchAgainstBccAction->isChecked()) {
        searchOptions |= QuickSearchLine::SearchAgainstBcc;
    }
    return searchOptions;
}

void SearchLineStatus::slotFilterActionClicked(QAction *act)
{
    Q_UNUSED(act);
325
    Q_EMIT searchOptionChanged();
326
}
327
328
329
330
331

void SearchLineStatus::addCompletionItem(const QString &str)
{
    mListCompetion.removeAll(str);
    mListCompetion.prepend(str);
Laurent Montel's avatar
Laurent Montel committed
332
    while (mListCompetion.size() > MAX_COMPLETION_ITEMS) {
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
        mListCompetion.removeLast();
    }
    mCompleterListModel->setStringList(mListCompetion);
}

void SearchLineStatus::contextMenuEvent(QContextMenuEvent *e)
{
    QMenu *popup = QLineEdit::createStandardContextMenu();
    if (popup) {
        popup->addSeparator();
        popup->addAction(QIcon::fromTheme(QStringLiteral("edit-clear-locationbar-rtl")), i18n("Clear History"), this, &SearchLineStatus::slotClearHistory);
        popup->exec(e->globalPos());
        delete popup;
    }
}

void SearchLineStatus::slotClearHistory()
{
    mListCompetion.clear();
    mCompleterListModel->setStringList(mListCompetion);
}