make the setings dialog adapt to the pages size
[trojita.git] / src / Gui / MessageListWidget.cpp
blob30c96fe5121db72bdf85a40418939fa6a7464e62
1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
3 This file is part of the Trojita Qt IMAP e-mail client,
4 http://trojita.flaska.net/
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License as
8 published by the Free Software Foundation; either version 2 of
9 the License or (at your option) version 3 or any later version
10 accepted by the membership of KDE e.V. (or its successor approved
11 by the membership of KDE e.V.), which shall act as a proxy
12 defined in Section 14 of version 3 of the license.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "MessageListWidget.h"
24 #include <QAction>
25 #include <QApplication>
26 #include <QCheckBox>
27 #include <QFrame>
28 #include <QMenu>
29 #include <QTimer>
30 #include <QToolButton>
31 #include <QVBoxLayout>
32 #include <QWidgetAction>
33 #include "IconLoader.h"
34 #include "LineEdit.h"
35 #include "MsgListView.h"
36 #include "ReplaceCharValidator.h"
38 namespace Gui {
40 MessageListWidget::MessageListWidget(QWidget *parent) :
41 QWidget(parent), m_supportsFuzzySearch(false)
43 tree = new MsgListView(this);
45 m_quickSearchText = new LineEdit(this);
46 m_quickSearchText->setHistoryEnabled(true);
47 // Filter out newline. It will wreak havoc into the direct IMAP passthrough and could lead to data loss.
48 QValidator *validator = new ReplaceCharValidator(QLatin1Char('\n'), QLatin1Char(' '), m_quickSearchText);
49 m_quickSearchText->setValidator(validator);
50 #if QT_VERSION >= 0x040700
51 m_quickSearchText->setPlaceholderText(tr("Quick Search"));
52 #endif
53 m_quickSearchText->setToolTip(tr("Type in a text to search for within this mailbox. "
54 "The icon on the left can be used to limit the search options "
55 "(like whether to include addresses or message bodies, etc)."
56 "<br/><hr/>"
57 "Experts who have read RFC3501 can use the <code>:=</code> prefix and switch to a raw IMAP mode."));
58 m_queryPlaceholder = tr("<query>");
60 connect(m_quickSearchText, SIGNAL(returnPressed()), this, SLOT(slotApplySearch()));
61 connect(m_quickSearchText, SIGNAL(textChanged(QString)), this, SLOT(slotConditionalSearchReset()));
62 connect(m_quickSearchText, SIGNAL(cursorPositionChanged(int, int)), this, SLOT(slotUpdateSearchCursor()));
64 m_searchOptions = new QToolButton(this);
65 m_searchOptions->setAutoRaise(true);
66 m_searchOptions->setPopupMode(QToolButton::InstantPopup);
67 m_searchOptions->setText(QLatin1String("*"));
68 m_searchOptions->setIcon(loadIcon(QLatin1String("imap-search-details")));
69 QMenu *optionsMenu = new QMenu(m_searchOptions);
70 m_searchFuzzy = optionsMenu->addAction(tr("Fuzzy Search"));
71 m_searchFuzzy->setCheckable(true);
72 optionsMenu->addSeparator();
73 m_searchInSubject = optionsMenu->addAction(tr("Subject"));
74 m_searchInSubject->setCheckable(true);
75 m_searchInSubject->setChecked(true);
76 m_searchInBody = optionsMenu->addAction(tr("Body"));
77 m_searchInBody->setCheckable(true);
78 m_searchInSenders = optionsMenu->addAction(tr("Senders"));
79 m_searchInSenders->setCheckable(true);
80 m_searchInSenders->setChecked(true);
81 m_searchInRecipients = optionsMenu->addAction(tr("Recipients"));
82 m_searchInRecipients->setCheckable(true);
84 optionsMenu->addSeparator();
86 QMenu *complexMenu = new QMenu(tr("Complex IMAP query"), optionsMenu);
87 connect(complexMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotComplexSearchInput(QAction*)));
88 complexMenu->addAction(tr("Not ..."))->setData(QLatin1String("NOT ") + m_queryPlaceholder);
89 complexMenu->addAction(tr("Either... or..."))->setData(QLatin1String("OR ") + m_queryPlaceholder + QLatin1Char(' ') + m_queryPlaceholder);
90 complexMenu->addSeparator();
91 complexMenu->addAction(tr("From sender"))->setData(QLatin1String("FROM ") + m_queryPlaceholder);
92 complexMenu->addAction(tr("To receiver"))->setData(QLatin1String("TO ") + m_queryPlaceholder);
93 complexMenu->addSeparator();
94 complexMenu->addAction(tr("About subject"))->setData(QLatin1String("SUBJECT " )+ m_queryPlaceholder);
95 complexMenu->addAction(tr("Message contains ..."))->setData(QLatin1String("BODY ") + m_queryPlaceholder);
96 complexMenu->addSeparator();
97 complexMenu->addAction(tr("Before date"))->setData(QLatin1String("BEFORE <d-mmm-yyyy>"));
98 complexMenu->addAction(tr("Since date"))->setData(QLatin1String("SINCE <d-mmm-yyyy>"));
99 complexMenu->addSeparator();
100 complexMenu->addAction(tr("Has been seen"))->setData(QLatin1String("SEEN"));
102 m_rawSearch = optionsMenu->addAction(tr("Allow raw IMAP search"));
103 m_rawSearch->setCheckable(true);
104 QAction *rawSearchMenu = optionsMenu->addMenu(complexMenu);
105 rawSearchMenu->setVisible(false);
106 connect (m_rawSearch, SIGNAL(toggled(bool)), rawSearchMenu, SLOT(setVisible(bool)));
107 connect (m_rawSearch, SIGNAL(toggled(bool)), SIGNAL(rawSearchSettingChanged(bool)));
109 m_searchOptions->setMenu(optionsMenu);
110 connect (optionsMenu, SIGNAL(aboutToShow()), SLOT(slotDeActivateSimpleSearch()));
112 delete m_quickSearchText->layout();
113 QHBoxLayout *hlayout = new QHBoxLayout(m_quickSearchText);
114 hlayout->setContentsMargins(0, 0, 0, 0);
115 hlayout->addWidget(m_searchOptions);
116 hlayout->addStretch();
117 hlayout->addWidget(m_quickSearchText->clearButton());
118 hlayout->activate(); // this processes the layout and ensures the toolbutton has it's final dimensions
119 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
120 if (QGuiApplication::isLeftToRight())
121 #else
122 if (qApp->keyboardInputDirection() == Qt::LeftToRight)
123 #endif
124 m_quickSearchText->setTextMargins(m_searchOptions->width(), 0, 0, 0);
125 else // ppl. in N Africa and the middle east write the wrong direction...
126 m_quickSearchText->setTextMargins(0, 0, m_searchOptions->width(), 0);
127 m_searchOptions->setCursor(Qt::ArrowCursor); // inherits I-Beam from lineedit otherwise
129 QVBoxLayout *layout = new QVBoxLayout(this);
130 layout->setSpacing(0);
131 layout->addWidget(m_quickSearchText);
132 layout->addWidget(tree);
134 m_searchResetTimer = new QTimer(this);
135 m_searchResetTimer->setSingleShot(true);
136 connect(m_searchResetTimer, SIGNAL(timeout()), SLOT(slotApplySearch()));
138 slotAutoEnableDisableSearch();
141 void MessageListWidget::focusSearch()
143 if (!m_quickSearchText->isEnabled() || m_quickSearchText->hasFocus())
144 return;
145 m_quickSearchText->setFocus(Qt::ShortcutFocusReason);
148 void MessageListWidget::focusRawSearch()
150 if (!m_quickSearchText->isEnabled() || m_quickSearchText->hasFocus() || !m_rawSearch->isChecked())
151 return;
152 m_quickSearchText->setFocus(Qt::ShortcutFocusReason);
153 m_quickSearchText->setText(QLatin1String(":="));
154 m_quickSearchText->deselect();
155 m_quickSearchText->setCursorPosition(m_quickSearchText->text().length());
158 void MessageListWidget::slotApplySearch()
160 emit requestingSearch(searchConditions());
163 void MessageListWidget::slotAutoEnableDisableSearch()
165 bool isEnabled;
166 if (!m_quickSearchText->text().isEmpty()) {
167 // Some search criteria are in effect and suddenly all matching messages
168 // disappear. We have to make sure that the search bar remains enabled.
169 isEnabled = true;
170 } else if (tree && tree->model()) {
171 isEnabled = tree->model()->rowCount();
172 } else {
173 isEnabled = false;
175 m_quickSearchText->setEnabled(isEnabled);
176 m_searchOptions->setEnabled(isEnabled);
179 void MessageListWidget::slotSortingFailed()
181 QPalette pal = m_quickSearchText->palette();
182 pal.setColor(m_quickSearchText->backgroundRole(), Qt::red);
183 pal.setColor(m_quickSearchText->foregroundRole(), Qt::white);
184 m_quickSearchText->setPalette(pal);
185 QTimer::singleShot(500, this, SLOT(slotResetSortingFailed()));
188 void MessageListWidget::slotResetSortingFailed()
190 m_quickSearchText->setPalette(QPalette());
193 void MessageListWidget::slotConditionalSearchReset()
195 if (m_quickSearchText->text().isEmpty())
196 m_searchResetTimer->start(250);
197 else
198 m_searchResetTimer->stop();
201 void MessageListWidget::slotUpdateSearchCursor()
203 int cp = m_quickSearchText->cursorPosition();
204 int ts = -1, te = -1;
205 for (int i = cp-1; i > -1; --i) {
206 if (m_quickSearchText->text().at(i) == QLatin1Char('>'))
207 break; // invalid
208 if (m_quickSearchText->text().at(i) == QLatin1Char('<')) {
209 ts = i;
210 break; // found TagStart
213 if (ts < 0)
214 return; // not inside tag!
215 for (int i = cp; i < m_quickSearchText->text().length(); ++i) {
216 if (m_quickSearchText->text().at(i) == QLatin1Char('<'))
217 break; // invalid
218 if (m_quickSearchText->text().at(i) == QLatin1Char('>')) {
219 te = i;
220 break; // found TagEnd
223 if (te < 0)
224 return; // not inside tag?
225 if (m_quickSearchText->text().midRef(ts, m_queryPlaceholder.length()) == m_queryPlaceholder)
226 m_quickSearchText->setSelection(ts, m_queryPlaceholder.length());
229 void MessageListWidget::slotComplexSearchInput(QAction *act)
231 QString s = act->data().toString();
232 const int selectionStart = m_quickSearchText->selectionStart() - 1;
233 if (selectionStart > -1 && m_quickSearchText->text().at(selectionStart) != QLatin1Char(' '))
234 s.prepend(QLatin1Char(' '));
235 m_quickSearchText->insert(s);
236 if (!m_quickSearchText->text().startsWith(QLatin1String(":="))) {
237 s = m_quickSearchText->text().trimmed();
238 m_quickSearchText->setText(QLatin1String(":=") + s);
240 m_quickSearchText->setFocus();
241 const int pos = m_quickSearchText->text().indexOf(m_queryPlaceholder);
242 if (pos > -1)
243 m_quickSearchText->setSelection(pos, m_queryPlaceholder.length());
246 void MessageListWidget::slotDeActivateSimpleSearch()
248 const bool isEnabled = !(m_rawSearch->isChecked() && m_quickSearchText->text().startsWith(QLatin1String(":=")));
249 m_searchInSubject->setEnabled(isEnabled);
250 m_searchInBody->setEnabled(isEnabled);
251 m_searchInSenders->setEnabled(isEnabled);
252 m_searchInRecipients->setEnabled(isEnabled);
253 m_searchFuzzy->setEnabled(isEnabled && m_supportsFuzzySearch);
256 QStringList MessageListWidget::searchConditions() const
258 if (!m_quickSearchText->isEnabled() || m_quickSearchText->text().isEmpty())
259 return QStringList();
261 static QString rawPrefix = QLatin1String(":=");
263 if (m_rawSearch->isChecked() && m_quickSearchText->text().startsWith(rawPrefix)) {
264 // It's a "raw" IMAP search, let's simply pass it through
265 return QStringList() << m_quickSearchText->text().mid(rawPrefix.size());
268 QStringList keys;
269 if (m_searchInSubject->isChecked())
270 keys << QLatin1String("SUBJECT");
271 if (m_searchInBody->isChecked())
272 keys << QLatin1String("BODY");
273 if (m_searchInRecipients->isChecked())
274 keys << QLatin1String("TO") << QLatin1String("CC") << QLatin1String("BCC");
275 if (m_searchInSenders->isChecked())
276 keys << QLatin1String("FROM");
278 if (keys.isEmpty())
279 return keys;
281 QStringList res;
282 Q_FOREACH(const QString &key, keys) {
283 if (m_supportsFuzzySearch)
284 res << QLatin1String("FUZZY");
285 res << key << m_quickSearchText->text();
287 if (keys.size() > 1) {
288 // Got to make this a conjunction. The OR operator's reverse-polish-notation accepts just two operands, though.
289 int num = keys.size() - 1;
290 for (int i = 0; i < num; ++i) {
291 res.prepend(QLatin1String("OR"));
295 return res;
298 void MessageListWidget::setFuzzySearchSupported(bool supported)
300 m_supportsFuzzySearch = supported;
301 m_searchFuzzy->setEnabled(supported);
302 m_searchFuzzy->setChecked(supported);
305 void MessageListWidget::setRawSearchEnabled(bool enabled)
307 m_rawSearch->setChecked(enabled);