Line data Source code
1 : #pragma once
2 :
3 : #include <QObject>
4 : #include <QPair>
5 : #include <QSet>
6 : #include <QStringList>
7 : #include <functional>
8 :
9 : #include "data/MailCache.h"
10 : #include "data/Models.h"
11 :
12 : class CommandBar;
13 : class FolderTree;
14 : class MailController;
15 : class MailFilterProxyModel;
16 : class MailListModel;
17 : class QTimer;
18 : class SearchPanel;
19 :
20 : // SearchCoordinator owns the complete search-mode state and logic that
21 : // historically lived inside MainWindow (Sprint 65 P2.1 extraction):
22 : // - running a search (local FTS page + server-side IMAP SEARCH)
23 : // - local-result pagination (":search-more")
24 : // - the CommandBar live quick-search dropdown results
25 : // - the SearchPanel wiring (explicit run, live-filter debounce, reset)
26 : // - the virtual "Suche" folder node lifecycle
27 : // - the three historic "leave search" variants (kept behavior-identical):
28 : // exitSearch() — Esc on results / panel reset / node close
29 : // onRealFolderSelected() — user picked a real folder; the search node
30 : // and its query survive for later restore
31 : // onCommandBarCancelled() — Esc while the bar is open; node survives
32 : //
33 : // It communicates with collaborators it was given (cache, controller, models,
34 : // tree, panel, bar) directly, and with MainWindow exclusively via signals —
35 : // no back-pointer.
36 : class SearchCoordinator : public QObject {
37 2 : Q_OBJECT
38 : #ifdef MAILJD_UNIT_TEST
39 : friend class TestSearchCoordinator;
40 : friend class TestMainWindow;
41 : friend class TestSprint59MainWindow;
42 : friend class TestMainWindow;
43 : #endif
44 :
45 : public:
46 : struct Deps {
47 : MailCache *cache = nullptr;
48 : MailController *controller = nullptr;
49 : MailListModel *listModel = nullptr;
50 : MailFilterProxyModel *proxy = nullptr;
51 : FolderTree *folderTree = nullptr;
52 : SearchPanel *searchPanel = nullptr;
53 : CommandBar *commandBar = nullptr;
54 : // 67.A3: snapshot of the currently selected mail as (folderId, uid),
55 : // (-1, -1) when nothing is selected. Used to restore the selection
56 : // after a search re-run rebuilds the result list (model reset).
57 : std::function<QPair<qint64, qint64>()> currentSelection;
58 : };
59 :
60 : explicit SearchCoordinator(const Deps &deps, QObject *parent = nullptr);
61 :
62 : // Active search mode = a canonical query is set (free text and/or facets).
63 466 : bool isSearchMode() const { return !m_lastSearchQuery.isEmpty(); }
64 2 : QString lastSearchQuery() const { return m_lastSearchQuery; }
65 5 : QString searchNodeQuery() const { return m_searchNodeQuery; }
66 :
67 : // Folder paths offered as completion in the SearchPanel folder facet.
68 6 : void setKnownFolders(const QStringList &paths) { m_knownFolders = paths; }
69 :
70 : // Run a full search (local FTS + server IMAP SEARCH) for the given raw
71 : // query. Shared by the CommandBar submit, the SearchPanel and the "Suche"
72 : // node restore.
73 : void runSearch(const QString &query);
74 :
75 : // Cancel a running server search, clear all search state, remove the
76 : // "Suche" node and return to the pre-search folder.
77 : void exitSearch();
78 :
79 : // ":search-more" — append the next local FTS page (emits status feedback).
80 : void loadMoreLocalResults();
81 :
82 : // Leave-search variant: a real folder was selected. Search-mode state is
83 : // cleared but the "Suche" node and its query survive for later restore.
84 : void onRealFolderSelected();
85 :
86 : // Leave-search variant: Esc pressed while the CommandBar was open in
87 : // Search mode. Full UI teardown + pre-search folder restore, but no
88 : // server-search cancellation (historic behavior).
89 : void onSearchBarEscape();
90 :
91 : // Leave-search variant: CommandBar cancelled. Cancels the server search
92 : // and restores the folder, but the node, panel and keyed status survive
93 : // (historic behavior).
94 : void onCommandBarCancelled();
95 :
96 : // CommandBar live quick-search dropdown: query → top-20 local results.
97 : void updateQuickResults(const QString &query);
98 2 : int quickResultCount() const { return m_quickResults.size(); }
99 : MailCache::SearchResult quickResultAt(int index) const;
100 :
101 : signals:
102 : void windowTitleChangeRequested(const QString &title);
103 : void statusMessage(const QString &message);
104 : void keyedStatusMessage(const QString &key, const QString &message,
105 : int timeoutMs);
106 : void statusCleared(const QString &key);
107 : void mailListFocusRequested();
108 : void mailSelectionClearRequested();
109 : // 67.A3: re-select this mail after a result-list rebuild (ClearAndSelect
110 : // + scroll — handled by MainWindow's reveal helper).
111 : void mailRevealRequested(qint64 folderId, qint64 uid);
112 :
113 : private:
114 : // Resolve cache search results to display headers: de-duplicates via
115 : // Message-Id and prefixes the subject with the folder path.
116 : QList<MailHeader> headersForSearchResults(
117 : const QList<MailCache::SearchResult> &results);
118 : int appendLocalSearchPage(const QString &displayQuery, bool replace);
119 : void clearLocalSearchPagination();
120 : void resetSearchPanel();
121 :
122 : Deps m_d;
123 :
124 : QStringList m_knownFolders;
125 : QList<MailCache::SearchResult> m_quickResults;
126 :
127 : QString m_lastSearchQuery; // canonical query while search mode active
128 : QString m_lastLocalSearchQuery; // parsed FTS query for local pagination
129 : MailCache::SearchFilter m_lastSearchFilter;
130 : int m_localSearchOffset = 0;
131 : bool m_localSearchHasMore = false;
132 : QString m_preSearchFolder; // folder to return to on Esc
133 : // Raw query backing the "Suche" tree node; kept while the node exists so
134 : // the search can be restored even after navigating to a real folder.
135 : QString m_searchNodeQuery;
136 : QSet<QString> m_searchMessageIds; // result de-dup across local + server
137 : QTimer *m_searchDebounce = nullptr; // live-search debounce (SearchPanel)
138 : };
|