Branch data Line data Source code
1 : : #include "SearchPanel.h"
2 : : #include "FilterChipWidget.h"
3 : : #include "FilterPopoverWidget.h"
4 : : #include "FlowLayout.h"
5 : : #include "app/SearchQuery.h"
6 : :
7 : : #include <QLineEdit>
8 : : #include <QToolButton>
9 : : #include <QVBoxLayout>
10 : : #include <QHBoxLayout>
11 : : #include <QEvent>
12 : : #include <QSignalBlocker>
13 : : #include <QMouseEvent>
14 : : #include <QStyle>
15 : :
16 : : using Tri = MailCache::SearchFilter::Tri;
17 : :
18 [ + - ]: 66 : SearchPanel::SearchPanel(QWidget *parent) : QWidget(parent) {
19 [ + - ]: 66 : setupUi();
20 : 66 : }
21 : :
22 : 66 : void SearchPanel::setupUi() {
23 [ + - - + : 66 : auto *outerLayout = new QVBoxLayout(this);
- - ]
24 : 66 : outerLayout->setContentsMargins(6, 6, 6, 6);
25 : 66 : outerLayout->setSpacing(0);
26 : :
27 : : // --- Fake Search Bar Container ---
28 [ + - - + : 66 : m_searchBarContainer = new QWidget(this);
- - ]
29 [ + - ]: 132 : m_searchBarContainer->setObjectName("searchBarContainer");
30 [ + - + - ]: 66 : m_searchBarContainer->setCursor(Qt::IBeamCursor);
31 : : // Styled to look like a line edit in AppStyle.cpp
32 : :
33 [ + - - + : 66 : m_flowLayout = new FlowLayout(m_searchBarContainer, 4, 4, 4);
- - ]
34 : :
35 [ + - - + : 66 : m_freeText = new QLineEdit(m_searchBarContainer);
- - ]
36 : 66 : m_freeText->setFrame(false);
37 : 66 : m_freeText->setAttribute(Qt::WA_MacShowFocusRect, false);
38 [ + - ]: 132 : m_freeText->setObjectName("searchPanelFreeText");
39 [ + - + - ]: 66 : m_freeText->setPlaceholderText(tr("Search…"));
40 : : // Let the QLineEdit expand if there is room
41 : 66 : m_freeText->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
42 : 66 : m_freeText->setMinimumWidth(100);
43 : :
44 : 66 : m_flowLayout->addWidget(m_freeText);
45 : :
46 : : // Popover button
47 [ + - - + : 66 : m_popoverButton = new QToolButton(m_searchBarContainer);
- - ]
48 [ + - ]: 132 : m_popoverButton->setObjectName("searchPanelPopoverButton");
49 [ + - + - ]: 66 : m_popoverButton->setCursor(Qt::PointingHandCursor);
50 [ + - + - ]: 66 : m_popoverButton->setToolTip(tr("Advanced Filters"));
51 [ + - + - ]: 66 : m_popoverButton->setText("▼"); // or an icon
52 : 66 : m_flowLayout->addWidget(m_popoverButton);
53 : :
54 [ + - ]: 66 : outerLayout->addWidget(m_searchBarContainer);
55 : :
56 [ + - - + : 66 : m_popover = new FilterPopoverWidget(this);
- - ]
57 : :
58 [ + - ]: 66 : connect(m_popoverButton, &QToolButton::clicked, this, [this]() {
59 [ + - ]: 1 : m_popover->adjustSize();
60 [ + - ]: 1 : QPoint pos = m_searchBarContainer->mapToGlobal(QPoint(m_searchBarContainer->width() - m_popover->width(), m_searchBarContainer->height()));
61 [ + - ]: 1 : m_popover->move(pos);
62 [ + - ]: 1 : m_popover->show();
63 : 1 : });
64 : :
65 [ + - ]: 66 : connect(m_freeText, &QLineEdit::returnPressed, this, &SearchPanel::searchRequested);
66 [ + - ]: 66 : connect(m_freeText, &QLineEdit::textChanged, this, &SearchPanel::onQueryFieldEdited);
67 [ + - ]: 66 : connect(m_popover, &FilterPopoverWidget::filterEdited, this, &SearchPanel::onPopoverFilterEdited);
68 : 66 : }
69 : :
70 : 1 : void SearchPanel::mousePressEvent(QMouseEvent *event) {
71 [ + - + - ]: 1 : if (m_searchBarContainer->geometry().contains(event->pos())) {
72 : 1 : m_freeText->setFocus();
73 : : }
74 : 1 : QWidget::mousePressEvent(event);
75 : 1 : }
76 : :
77 : 1561 : void SearchPanel::changeEvent(QEvent *event) {
78 [ + + ]: 1561 : if (event->type() == QEvent::LanguageChange)
79 : 86 : retranslateUi();
80 : 1561 : QWidget::changeEvent(event);
81 : 1561 : }
82 : :
83 : 86 : void SearchPanel::retranslateUi() {
84 [ + - + - ]: 86 : m_freeText->setPlaceholderText(tr("Search…"));
85 [ + - + - ]: 86 : m_popoverButton->setToolTip(tr("Advanced Filters"));
86 : 86 : }
87 : :
88 : 53 : QString SearchPanel::freeText() const {
89 : 53 : MailCache::SearchFilter ignored;
90 [ + - + - ]: 106 : return parseSearchQuery(m_freeText->text(), ignored);
91 : 53 : }
92 : :
93 : 99 : MailCache::SearchFilter SearchPanel::filter() const {
94 : 99 : return m_popover->filter();
95 : : }
96 : :
97 : 92 : void SearchPanel::setState(const QString &freeText, const MailCache::SearchFilter &f) {
98 : : {
99 : 92 : const QSignalBlocker block(m_freeText);
100 [ + - ]: 92 : QString newText = buildSearchQuery(freeText, f);
101 [ + - + + ]: 92 : if (m_freeText->text() != newText) {
102 [ + - ]: 45 : int cursorPos = m_freeText->cursorPosition();
103 [ + - ]: 45 : m_freeText->setText(newText);
104 [ + - ]: 45 : m_freeText->setCursorPosition(cursorPos);
105 : : }
106 : 92 : }
107 : 92 : applyFilterToControls(f);
108 : 92 : }
109 : :
110 : 96 : void SearchPanel::applyFilterToControls(const MailCache::SearchFilter &f) {
111 : 96 : const QSignalBlocker block(m_popover);
112 [ + - ]: 96 : m_popover->setFilter(f);
113 [ + - ]: 96 : syncChipsFromFilter(f);
114 : 96 : }
115 : :
116 : 132 : void SearchPanel::clearChips() {
117 [ + - + - : 148 : for (auto *chip : m_chips) {
+ + ]
118 [ + - ]: 16 : m_flowLayout->removeWidget(chip);
119 [ + - ]: 16 : chip->deleteLater();
120 : : }
121 : 132 : m_chips.clear();
122 : 132 : }
123 : :
124 : 132 : void SearchPanel::syncChipsFromFilter(const MailCache::SearchFilter &f) {
125 [ + - ]: 132 : clearChips();
126 : :
127 : 413 : auto addChip = [this](const QString &field, const QString &val) {
128 [ + + ]: 413 : if (val.isEmpty()) return;
129 [ + - - + : 31 : auto *chip = new FilterChipWidget(field, val, m_searchBarContainer);
- - ]
130 [ + - ]: 31 : connect(chip, &FilterChipWidget::removeRequested, this, [this, field]() {
131 : 3 : onChipRemoveRequested(field);
132 : 3 : });
133 : : // Insert before the text edit
134 : 31 : m_flowLayout->takeAt(m_flowLayout->count() - 2); // remove popover
135 : 31 : m_flowLayout->takeAt(m_flowLayout->count() - 1); // remove text edit
136 : :
137 : 31 : m_flowLayout->addWidget(chip);
138 : 31 : m_flowLayout->addWidget(m_freeText);
139 : 31 : m_flowLayout->addWidget(m_popoverButton);
140 : 31 : m_chips.append(chip);
141 : 132 : };
142 : :
143 [ + - + - ]: 132 : addChip("Subject", f.subjectFilter);
144 [ + - + - ]: 132 : addChip("From", f.fromFilter);
145 [ + - + - ]: 132 : addChip("To", f.toFilter);
146 : :
147 [ + + ]: 132 : if (!f.folderPatterns.isEmpty()) {
148 [ + - + - : 4 : addChip("Folder", f.folderPatterns.join(", "));
+ - + - ]
149 : : }
150 [ + + ]: 132 : if (!f.tags.isEmpty()) {
151 [ + - + - : 1 : addChip("Tags", f.tags.join(", "));
+ - + - ]
152 : : }
153 : :
154 [ + + + - : 132 : if (f.unread != Tri::Any) addChip("Unread", f.unread == Tri::Yes ? "Yes" : "No");
+ - + - +
- ]
155 [ + + - + : 132 : if (f.flagged != Tri::Any) addChip("Flagged", f.flagged == Tri::Yes ? "Yes" : "No");
+ - + - +
- ]
156 [ + + + - : 132 : if (f.answered != Tri::Any) addChip("Answered", f.answered == Tri::Yes ? "Yes" : "No");
+ - + - +
- ]
157 [ + + + + : 132 : if (f.hasAttachment != Tri::Any) addChip("Attachment", f.hasAttachment == Tri::Yes ? "Yes" : "No");
+ - + - +
- ]
158 : :
159 [ + - + + : 132 : if (f.dateFrom.isValid() || f.dateTo.isValid()) {
+ - - + +
+ ]
160 : 1 : QString dateStr;
161 [ + - + - : 1 : if (f.dateFrom.isValid() && f.dateTo.isValid()) {
+ - + - +
- ]
162 [ + - + - : 1 : dateStr = f.dateFrom.date().toString(Qt::ISODate) + " - " + f.dateTo.date().toString(Qt::ISODate);
+ - + - +
- + - ]
163 [ # # # # ]: 0 : } else if (f.dateFrom.isValid()) {
164 [ # # # # : 0 : dateStr = "> " + f.dateFrom.date().toString(Qt::ISODate);
# # ]
165 : : } else {
166 [ # # # # : 0 : dateStr = "< " + f.dateTo.date().toString(Qt::ISODate);
# # ]
167 : : }
168 [ + - + - ]: 1 : addChip("Date", dateStr);
169 : 1 : }
170 : 132 : }
171 : :
172 : 3 : void SearchPanel::removeFilterField(MailCache::SearchFilter &f, const QString &field) {
173 [ + + ]: 3 : if (field == "Subject") f.subjectFilter.clear();
174 [ + + ]: 2 : else if (field == "From") f.fromFilter.clear();
175 [ + - ]: 1 : else if (field == "To") f.toFilter.clear();
176 [ # # ]: 0 : else if (field == "Folder") f.folderPatterns.clear();
177 [ # # ]: 0 : else if (field == "Tags") f.tags.clear();
178 [ # # ]: 0 : else if (field == "Unread") f.unread = Tri::Any;
179 [ # # ]: 0 : else if (field == "Flagged") f.flagged = Tri::Any;
180 [ # # ]: 0 : else if (field == "Answered") f.answered = Tri::Any;
181 [ # # ]: 0 : else if (field == "Attachment") f.hasAttachment = Tri::Any;
182 [ # # ]: 0 : else if (field == "Date") {
183 : 0 : f.dateFrom = QDateTime();
184 : 0 : f.dateTo = QDateTime();
185 : : }
186 : 3 : }
187 : :
188 : 3 : void SearchPanel::onChipRemoveRequested(const QString &field) {
189 [ + - ]: 3 : MailCache::SearchFilter f = filter();
190 [ + - ]: 3 : removeFilterField(f, field);
191 : :
192 : : // Re-sync UI
193 [ + - ]: 3 : applyFilterToControls(f);
194 [ + - ]: 3 : rebuildQueryField();
195 : :
196 [ + - ]: 3 : emit filterEdited();
197 [ + - ]: 3 : emit searchRequested();
198 : 3 : }
199 : :
200 : 39 : void SearchPanel::rebuildQueryField() {
201 : 39 : MailCache::SearchFilter ignored;
202 [ + - + - ]: 39 : const QString freeRemainder = parseSearchQuery(m_freeText->text(), ignored);
203 : 39 : const QSignalBlocker block(m_freeText);
204 [ + - + - ]: 39 : QString newText = buildSearchQuery(freeRemainder, filter());
205 [ + - + + ]: 39 : if (m_freeText->text() != newText) {
206 [ + - ]: 5 : int cursorPos = m_freeText->cursorPosition();
207 [ + - ]: 5 : m_freeText->setText(newText);
208 [ + - ]: 5 : m_freeText->setCursorPosition(cursorPos);
209 : : }
210 : 39 : }
211 : :
212 : 1 : void SearchPanel::onQueryFieldEdited() {
213 [ + - ]: 1 : int cursorPos = m_freeText->cursorPosition();
214 : 1 : MailCache::SearchFilter f;
215 [ + - + - ]: 1 : parseSearchQuery(m_freeText->text(), f);
216 [ + - ]: 1 : applyFilterToControls(f);
217 [ + - ]: 1 : m_freeText->setCursorPosition(cursorPos);
218 [ + - ]: 1 : emit filterEdited();
219 : 1 : }
220 : :
221 : 36 : void SearchPanel::onPopoverFilterEdited() {
222 [ + - ]: 36 : MailCache::SearchFilter f = m_popover->filter();
223 [ + - ]: 36 : syncChipsFromFilter(f);
224 [ + - ]: 36 : rebuildQueryField();
225 [ + - ]: 36 : emit filterEdited();
226 : 36 : }
227 : :
228 : 36 : void SearchPanel::setKnownFolders(const QStringList &paths) {
229 : 36 : m_popover->setKnownFolders(paths);
230 : 36 : }
231 : :
232 : 36 : void SearchPanel::setKnownTags(const QStringList &tags) {
233 : 36 : m_popover->setKnownTags(tags);
234 : 36 : }
|