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 AccountFormWidget;
19 : class ContactStore;
20 : class CalendarStore;
21 : class MailCache;
22 : class NextcloudAuth;
23 : struct AccountConfig;
24 :
25 : // Settings dialog with tabbed interface.
26 : // Tab "Accounts": Account list + shared AccountFormWidget for editing.
27 : // Tab "General": Default view mode and external content policy.
28 : class SettingsDialog : public QDialog {
29 6344 : Q_OBJECT
30 : friend class TestSprint55Dialogs;
31 : #ifdef MAILJD_UNIT_TEST
32 : friend class TestSettingsDialogDeep;
33 : #endif
34 :
35 : public:
36 : explicit SettingsDialog(QWidget *parent = nullptr);
37 :
38 : // Pass the config directory for testing (default:
39 : // AccountConfigLoader::defaultConfigDir())
40 : void setConfigDir(const QString &dir);
41 :
42 : // True if account configs were modified (added/deleted/changed).
43 : // Only valid after accept(). If false, only view settings changed.
44 4 : bool accountsChanged() const { return m_accountsChanged; }
45 :
46 : void setCache(MailCache *cache);
47 :
48 : // Fix C: Inject ContactStore for Kontakte tab
49 : void setContactStore(ContactStore *store);
50 : void setCalendarStore(CalendarStore *store);
51 :
52 : signals:
53 : void languageChangeRequested(const QString &locale);
54 :
55 : private slots:
56 : void onAccountSelected(int row);
57 : void addAccount();
58 : void deleteAccount();
59 : void saveAll();
60 :
61 : private:
62 : // T-304: Runtime language switching
63 : void retranslateUi();
64 :
65 : protected:
66 : void changeEvent(QEvent *event) override;
67 :
68 : void setupUi();
69 : void setupShortcuts();
70 : void loadAccountList();
71 : bool hasUnsavedChanges() const;
72 : void reject() override; // Override to check unsaved changes
73 :
74 : QTabWidget *m_tabs = nullptr;
75 :
76 : // Accounts tab
77 : QListWidget *m_accountList = nullptr;
78 : AccountFormWidget *m_accountForm = nullptr;
79 : QPushButton *m_addButton = nullptr;
80 : QPushButton *m_deleteButton = nullptr;
81 : QPushButton *m_saveButton = nullptr;
82 : QPushButton *m_cancelButton = nullptr;
83 :
84 : // General tab
85 : QComboBox *m_themeCombo = nullptr; // 67.B2: Light/Dark/System
86 : QComboBox *m_defaultViewCombo = nullptr;
87 : QComboBox *m_externalContentCombo = nullptr;
88 : QComboBox *m_languageCombo = nullptr;
89 :
90 : // Account data
91 : std::vector<AccountConfig> m_accounts;
92 : int m_currentIndex = -1;
93 : bool m_dirty = false;
94 : bool m_accountsChanged = false;
95 :
96 : // Snapshot of accounts at dialog open, used to detect real changes
97 : std::vector<AccountConfig> m_originalAccounts;
98 :
99 : QString m_configDir;
100 :
101 : // Modal test seams (same pattern as MainWindow/FolderOperationsController):
102 : // default to the real Qt dialogs, overridden in unit tests so the CardDAV/
103 : // CalDAV account flows run headless.
104 : std::function<QString(const QString &title, const QString &label,
105 : const QString &initial, bool *ok)>
106 : m_promptText;
107 : std::function<bool(const QString &title, const QString &text)> m_confirm;
108 : std::function<void(const QString &title, const QString &text)> m_warn;
109 : // Test seam: injected into CardDavClient/CalDavClient instances created by
110 : // the discover/sync flows so tests can serve canned PROPFIND/REPORT
111 : // responses (tests/fake_network.h).
112 : QNetworkAccessManager *m_testNam = nullptr;
113 :
114 : // T-122: Whitelist tab
115 : MailCache *m_cache = nullptr;
116 : QTableWidget *m_whitelistTable = nullptr;
117 : QComboBox *m_whitelistTypeCombo = nullptr;
118 : QLineEdit *m_whitelistValueEdit = nullptr;
119 : void loadWhitelistTable();
120 :
121 : // Kontakte tab – multi-server CardDAV
122 : struct CdAccount {
123 : QString id; // UUID
124 : QString serverUrl;
125 : QString username;
126 : QString password;
127 : QStringList selectedBooks; // paths to sync
128 : QMap<QString, QString> discoveredBooks; // path → displayName
129 : };
130 :
131 : ContactStore *m_contactStore = nullptr;
132 : CalendarStore *m_calendarStore = nullptr;
133 : NextcloudAuth *m_ncAuth = nullptr;
134 :
135 : QTableWidget *m_cdAccountTable = nullptr;
136 : QPushButton *m_cdAddBtn = nullptr;
137 : QPushButton *m_cdRemoveBtn = nullptr;
138 : QPushButton *m_cdAuthorizeBtn = nullptr;
139 : QListWidget *m_cdBookList = nullptr;
140 : QPushButton *m_cdDiscoverBtn = nullptr;
141 : QPushButton *m_cdSyncBtn = nullptr;
142 : QLabel *m_cdStatusLabel = nullptr;
143 : QComboBox *m_cdIntervalCombo = nullptr;
144 :
145 : std::vector<CdAccount> m_cdAccounts;
146 : int m_cdCurrentIdx = -1;
147 :
148 : // Sprint 73: stable identifier for the account awaiting an async Nextcloud
149 : // login flow. Resolved by account ID on loginSuccess so a selection change
150 : // during browser login can never write credentials to the wrong account.
151 : QString m_pendingDavAuthAccountId;
152 :
153 : void loadCdAccounts();
154 : void saveCdAccounts();
155 : QString cdPasswordForAccount(const CdAccount &account) const;
156 : // Sprint 73: a DAV account is "locally authorized" only when metadata
157 : // (serverUrl + username) and a non-empty keyring secret are all present.
158 : // Synced accounts restore metadata but not secrets, so they appear
159 : // complete until discovery/sync fails. The UI derives this state on demand
160 : // rather than persisting a boolean that could go stale.
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 : };
|