Line data Source code
1 : #pragma once
2 :
3 : #include <QWidget>
4 :
5 : class QLabel;
6 : class QLineEdit;
7 : class QListWidget;
8 : class QPropertyAnimation;
9 :
10 : // CommandBar implements a Tridactyl/Vimperator-style command console at the
11 : // bottom of the main window. It supports multiple input modes:
12 : //
13 : // Command – ":" prefix, execute named commands with autocomplete
14 : // Filter – "/" prefix, real-time text filter on the mail list
15 : // FolderSwitch – "b:" prefix, pick a folder to switch to (with filtering)
16 : // MoveToFolder – "s→" prefix, pick a folder to move selected mails into
17 : //
18 : // The bar is normally hidden and slides in from the bottom when activated.
19 : // Escape closes it and returns focus to the previous widget.
20 : class CommandBar : public QWidget {
21 330 : Q_OBJECT
22 :
23 : public:
24 : enum Mode { Command, Filter, FolderSwitch, MoveToFolder, Search, AddTask };
25 :
26 : explicit CommandBar(QWidget *parent = nullptr);
27 :
28 : // Open the command bar in the given mode.
29 : void activate(Mode mode);
30 :
31 : // Close the command bar and return focus to the previous widget.
32 : void deactivate();
33 :
34 : // Sprint 75: Host the bar as an overlay child of `host` instead of a
35 : // layout child. The bar reparents to `host`, installs a Resize event
36 : // filter on it, and docks at the bottom (anchored via m_overlayInset).
37 : // When the host is nullptr (or never set), activate()/deactivate()
38 : // fall back to the legacy layout-driven behaviour so direct unit
39 : // tests that do not provide a host stay green.
40 : void setOverlayHost(QWidget *host);
41 :
42 : // Set the list of available folder paths (for FolderSwitch / MoveToFolder).
43 : void setFolderList(const QStringList &folders);
44 :
45 : // Set the list of available commands (for Command mode autocomplete).
46 : void setCommandList(const QStringList &commands);
47 :
48 : // T-180: Set search results for display in the suggestion list.
49 : void setSearchResults(const QStringList &results);
50 :
51 : // T-537: Set calendar list for AddTask mode autocomplete
52 : void setCalendarList(const QStringList &calendars);
53 :
54 : bool isActive() const;
55 51 : Mode currentMode() const { return m_mode; }
56 :
57 : // Sprint 70: layout-driven vertical extent. Returns bar + suggestions
58 : // + real layout overhead, capped at 10 visible items. The slide-in
59 : // animation reads the same value via computeTargetHeight().
60 : QSize sizeHint() const override;
61 :
62 : // Sprint 59 (U3): set/read the raw input text without changing the mode and
63 : // without emitting input signals. Used to keep the search bar in sync with
64 : // the SearchPanel (both show the same canonical query). The MainWindow sync
65 : // path — not the bar — decides when to refresh suggestions or run a search,
66 : // which keeps CommandBar a dumb input widget.
67 : void setInputText(const QString &text);
68 : QString inputText() const;
69 :
70 : signals:
71 : // Emitted when the user presses Enter in Command mode.
72 : void commandSubmitted(const QString &cmd);
73 :
74 : // Emitted on every keystroke in Filter mode (real-time).
75 : void filterTextChanged(const QString &text);
76 :
77 : // Emitted when the user selects a folder (FolderSwitch or MoveToFolder).
78 : void folderSelected(CommandBar::Mode mode, const QString &folderPath);
79 :
80 : // Emitted when the user presses Escape or the bar is deactivated.
81 : void cancelled();
82 :
83 : // Emitted when the bar opens or closes (for shortcut enable/disable).
84 : void activeChanged(bool active);
85 :
86 : // Emitted in Filter mode when arrow keys are pressed (for mail navigation).
87 : void navigateMailList(int delta); // +1 = next, -1 = prev
88 :
89 : // T-180: Emitted on every keystroke in Search mode (debounced).
90 : void searchQueryChanged(const QString &query);
91 :
92 : // T-180: Emitted when the user selects a search result (Enter/click).
93 : void searchResultSelected(int index);
94 :
95 : // Emitted when the user presses Enter in Search mode to confirm the query.
96 : void searchSubmitted(const QString &query);
97 :
98 : // T-537: Emitted when a task is submitted via AddTask mode
99 : void taskSubmitted(const QString &title, const QString &calendarPath,
100 : const QDateTime &due, int priority);
101 :
102 : private slots:
103 : void onTextChanged(const QString &text);
104 : void onReturnPressed();
105 :
106 : protected:
107 : bool eventFilter(QObject *obj, QEvent *event) override;
108 : void changeEvent(QEvent *event) override;
109 :
110 : private:
111 : void setupUi();
112 : void updateSuggestions(const QString &text);
113 : void selectSuggestion(int delta); // +1 = next, -1 = prev
114 : void acceptCurrentSuggestion();
115 : QString prefixForMode(Mode mode) const;
116 : // T-76.B3: single source for the per-mode input placeholder so both
117 : // activate() and retranslateUi() stay in sync.
118 : QString placeholderForMode(Mode mode) const;
119 : void retranslateUi();
120 :
121 : // Sprint 70: single source of truth for the CommandBar's vertical
122 : // extent. Uses QListWidget::sizeHintForRow(0) (real rendered row
123 : // height including QSS padding) plus the actual outer-layout overhead
124 : // (contentsMargins + spacing + list frame) and a 10-item ceiling.
125 : // Called by both sizeHint() and the slide animation end-value.
126 : int computeTargetHeight() const;
127 :
128 : // Sprint 75: reposition the bar inside m_host and raise it. If the
129 : // slide animation is still running (e.g. a live window resize during
130 : // slide-in), stop it and snap to the recomputed bottom-anchored
131 : // geometry so the running animation cannot overwrite the new geometry
132 : // on its next tick. No-op without a host.
133 : void positionOverlay();
134 :
135 : // Sprint 75: apply the current computeTargetHeight() to the bar. With
136 : // a host this means setFixedHeight() + positionOverlay(); without a
137 : // host it falls back to setMaximumHeight() so the legacy layout-driven
138 : // path keeps working. Centralised replacement for every previous
139 : // `setMaximumHeight(computeTargetHeight())` site.
140 : void adjustOverlayHeight();
141 :
142 : // UI elements
143 : QLabel *m_prefixLabel = nullptr;
144 : QLineEdit *m_input = nullptr;
145 : QListWidget *m_suggestionList = nullptr;
146 :
147 : // Animation
148 : QPropertyAnimation *m_slideAnim = nullptr;
149 :
150 : // Sprint 75: overlay host (the central container). When set, the bar
151 : // is reparented to it and positioned as an overlay (bottom-anchored,
152 : // inset laterally). Nullptr → legacy layout-driven fallback path.
153 : QWidget *m_host = nullptr;
154 : // Sprint 75: lateral and bottom inset between the bar and the host.
155 : // 0 = Tridactyl-style full-width panel flush with the bottom and the
156 : // host's lateral edges. The previous Nostalgy-style card design used
157 : // 4 (matching outerLayout contentsMargins); the redesign removed the
158 : // card chrome so the panel goes edge-to-edge.
159 : int m_overlayInset = 0;
160 :
161 : // State
162 : Mode m_mode = Command;
163 : bool m_active = false;
164 : bool m_showingFilterHelp = false; // True when showing filter prefix suggestions
165 : bool m_userNavigated = false; // True when user explicitly used arrow keys
166 : QWidget *m_previousFocus = nullptr;
167 :
168 : // Data
169 : QStringList m_folderPaths;
170 : QStringList m_commandNames;
171 : QStringList m_calendarPaths; // T-537: for AddTask autocomplete
172 : };
|