Line data Source code
1 : #pragma once
2 :
3 : #include <QDialog>
4 : #include <QMap>
5 : #include <functional>
6 : #include <vector>
7 :
8 : class QNetworkAccessManager;
9 :
10 : class QCheckBox;
11 : class QComboBox;
12 : class QLabel;
13 : class QTabWidget;
14 : class QListWidget;
15 : class QTableWidget;
16 : class QPushButton;
17 : class QLineEdit;
18 : class QSpinBox;
19 : class AccountFormWidget;
20 : class ContactStore;
21 : class CalendarStore;
22 : class MailCache;
23 : class NextcloudAuth;
24 : struct AccountConfig;
25 :
26 : // Settings dialog with tabbed interface.
27 : // Tab "Accounts": Account list + shared AccountFormWidget for editing.
28 : // Tab "General": Default view mode and external content policy.
29 : class SettingsDialog : public QDialog {
30 6908 : Q_OBJECT
31 : friend class TestSprint55Dialogs;
32 : #ifdef MAILJD_UNIT_TEST
33 : friend class TestSettingsDialogDeep;
34 : #endif
35 :
36 : public:
37 : explicit SettingsDialog(QWidget *parent = nullptr);
38 :
39 : // Pass the config directory for testing (default:
40 : // AccountConfigLoader::defaultConfigDir())
41 : void setConfigDir(const QString &dir);
42 :
43 : // True if account configs were modified (added/deleted/changed).
44 : // Only valid after accept(). If false, only view settings changed.
45 4 : bool accountsChanged() const { return m_accountsChanged; }
46 :
47 : void setCache(MailCache *cache);
48 :
49 : // Fix C: Inject ContactStore for Kontakte tab
50 : void setContactStore(ContactStore *store);
51 : void setCalendarStore(CalendarStore *store);
52 :
53 : signals:
54 : void languageChangeRequested(const QString &locale);
55 :
56 : private slots:
57 : void onAccountSelected(int row);
58 : void addAccount();
59 : void deleteAccount();
60 : void saveAll();
61 :
62 : private:
63 : // T-304: Runtime language switching
64 : void retranslateUi();
65 :
66 : protected:
67 : void changeEvent(QEvent *event) override;
68 :
69 : void setupUi();
70 : void setupShortcuts();
71 : void loadAccountList();
72 : bool hasUnsavedChanges() const;
73 : void reject() override; // Override to check unsaved changes
74 :
75 : QTabWidget *m_tabs = nullptr;
76 :
77 : // Accounts tab
78 : QListWidget *m_accountList = nullptr;
79 : AccountFormWidget *m_accountForm = nullptr;
80 : QPushButton *m_addButton = nullptr;
81 : QPushButton *m_deleteButton = nullptr;
82 : QPushButton *m_saveButton = nullptr;
83 : QPushButton *m_cancelButton = nullptr;
84 :
85 : // General tab
86 : QComboBox *m_themeCombo = nullptr; // 67.B2: Light/Dark/System
87 : QComboBox *m_defaultViewCombo = nullptr;
88 : QComboBox *m_externalContentCombo = nullptr;
89 : QComboBox *m_languageCombo = nullptr;
90 : QSpinBox *m_maxMessageSizeSpin = nullptr;
91 :
92 : // Account data
93 : std::vector<AccountConfig> m_accounts;
94 : int m_currentIndex = -1;
95 : bool m_dirty = false;
96 : bool m_accountsChanged = false;
97 :
98 : // Snapshot of accounts at dialog open, used to detect real changes
99 : std::vector<AccountConfig> m_originalAccounts;
100 :
101 : QString m_configDir;
102 :
103 : // Modal test seams (same pattern as MainWindow/FolderOperationsController):
104 : // default to the real Qt dialogs, overridden in unit tests so the CardDAV/
105 : // CalDAV account flows run headless.
106 : std::function<QString(const QString &title, const QString &label,
107 : const QString &initial, bool *ok)>
108 : m_promptText;
109 : std::function<bool(const QString &title, const QString &text)> m_confirm;
110 : std::function<void(const QString &title, const QString &text)> m_warn;
111 : // Test seam: injected into CardDavClient/CalDavClient instances created by
112 : // the discover/sync flows so tests can serve canned PROPFIND/REPORT
113 : // responses (tests/fake_network.h).
114 : QNetworkAccessManager *m_testNam = nullptr;
115 :
116 : // T-122: Whitelist tab
117 : MailCache *m_cache = nullptr;
118 : QTableWidget *m_whitelistTable = nullptr;
119 : QComboBox *m_whitelistTypeCombo = nullptr;
120 : QLineEdit *m_whitelistValueEdit = nullptr;
121 : void loadWhitelistTable();
122 :
123 : // Kontakte tab – multi-server CardDAV
124 : struct CdAccount {
125 : QString id; // UUID
126 : QString serverUrl;
127 : QString username;
128 : QString password;
129 : bool requiresLocalAuthorization = false;
130 : QStringList selectedBooks; // paths to sync
131 : QMap<QString, QString> discoveredBooks; // path → displayName
132 : };
133 :
134 : ContactStore *m_contactStore = nullptr;
135 : CalendarStore *m_calendarStore = nullptr;
136 : NextcloudAuth *m_ncAuth = nullptr;
137 :
138 : QTableWidget *m_cdAccountTable = nullptr;
139 : QPushButton *m_cdAddBtn = nullptr;
140 : QPushButton *m_cdRemoveBtn = nullptr;
141 : QPushButton *m_cdAuthorizeBtn = nullptr;
142 : QListWidget *m_cdBookList = nullptr;
143 : QPushButton *m_cdDiscoverBtn = nullptr;
144 : QPushButton *m_cdSyncBtn = nullptr;
145 : QLabel *m_cdStatusLabel = nullptr;
146 : QComboBox *m_cdIntervalCombo = nullptr;
147 :
148 : std::vector<CdAccount> m_cdAccounts;
149 : int m_cdCurrentIdx = -1;
150 :
151 : // Sprint 73: stable identifier for the account awaiting an async Nextcloud
152 : // login flow. Resolved by account ID on loginSuccess so a selection change
153 : // during browser login can never write credentials to the wrong account.
154 : QString m_pendingDavAuthAccountId;
155 :
156 : void loadCdAccounts();
157 : bool saveCdAccounts();
158 : QString cdPasswordForAccount(const CdAccount &account) const;
159 : // Synced DAV metadata remains blocked until an explicit local login clears
160 : // requiresLocalAuthorization and stores a new endpoint-bound secret.
161 : bool cdAccountHasLocalCredentials(const CdAccount &account) const;
162 : int findCdAccountIndexById(const QString &accountId) const;
163 : void updateCdAccountTable();
164 : void onCdAccountSelected(int row);
165 : void addCdAccount();
166 : void removeCdAccount();
167 : void authorizeCdAccount();
168 : void discoverBooks();
169 : void syncAllAccounts();
170 :
171 : // T-315: Sync tab
172 : QCheckBox *m_syncEnabledCheck = nullptr;
173 : QLineEdit *m_syncFolderEdit = nullptr;
174 : QCheckBox *m_syncCatIcons = nullptr;
175 : QCheckBox *m_syncCatColors = nullptr;
176 : QCheckBox *m_syncCatCalColors = nullptr;
177 : QCheckBox *m_syncCatHidden = nullptr;
178 : QCheckBox *m_syncCatWhitelist = nullptr;
179 : QCheckBox *m_syncCatDav = nullptr;
180 : QCheckBox *m_syncCatGeneral = nullptr;
181 : QPushButton *m_syncNowBtn = nullptr;
182 : QPushButton *m_syncResetBtn = nullptr;
183 : QLabel *m_syncStatusLabel = nullptr;
184 :
185 : // T-335: Kalender tab (Sprint 32)
186 : QComboBox *m_calAccountCombo = nullptr;
187 : QListWidget *m_calCalendarList = nullptr;
188 : QPushButton *m_calDiscoverBtn = nullptr;
189 : QPushButton *m_calSyncBtn = nullptr;
190 : QLabel *m_calStatusLabel = nullptr;
191 : QComboBox *m_calIntervalCombo = nullptr;
192 : QLabel *m_calInfoLabel = nullptr;
193 :
194 : // Sprint 75: opt-in exec-seam for the calendar list context menu.
195 : // When m_interceptCalMenuExec is true, the context-menu handler
196 : // stores the received global position in m_lastCalMenuExecGlobalPos
197 : // and returns WITHOUT calling QMenu::exec() — so unit tests can
198 : // deterministically assert the exec coordinate (viewport-mapped via
199 : // QListWidget::viewport()->mapToGlobal) without blocking in the
200 : // popup event loop and without relying on QMenu's post-adjustment
201 : // screen geometry. Production and the default test path keep the
202 : // real exec().
203 : bool m_interceptCalMenuExec = false;
204 : QPoint m_lastCalMenuExecGlobalPos;
205 :
206 : struct CalConfig {
207 : QString carddavAccountId;
208 : QStringList selectedCalendars;
209 : QStringList readOnlyCalendars;
210 : QMap<QString, QString> discoveredCalendars; // path → displayName
211 : int syncIntervalMinutes = 15;
212 : };
213 : std::vector<CalConfig> m_calConfigs;
214 :
215 : void loadCalConfigs();
216 : void saveCalConfigs();
217 : void onCalAccountSelected(int index);
218 : void discoverCalendars();
219 : void syncCalendars();
220 : int findCalConfigForAccount(const QString &accountId) const;
221 :
222 : signals:
223 : void calendarSyncRequested();
224 : void syncRequested();
225 : void syncResetRequested();
226 : void syncSettingsChanged();
227 : };
|