MailJD nbsp;·nbsp; Test Dashboard nbsp;·nbsp; Coverage
LCOV - code coverage report
Current view: top level - app - MainWindow.h (source / functions) Coverage Total Hit
Test: MailJD Coverage (Unit + E2E) Lines: 100.0 % 3 3
Test Date: 2026-06-21 21:10:19 Functions: 100.0 % 3 3
Legend: Lines:     hit not hit

            Line data    Source code
       1              : #pragma once
       2              : 
       3              : #include <QMainWindow>
       4              : #include <QSettings>
       5              : #include <functional>
       6              : 
       7              : class QAction;
       8              : class QDialog;
       9              : 
      10              : #include "data/CalendarModels.h"
      11              : #include "data/Models.h"
      12              : 
      13              : class QSplitter;
      14              : class QStackedWidget;
      15              : class QTabBar;
      16              : class QTreeView;
      17              : class QLabel;
      18              : class QMenu;
      19              : class QSystemTrayIcon;
      20              : class QThread;
      21              : 
      22              : class CalDavClient;
      23              : class CommandBar;
      24              : class ComposeWindow;
      25              : class ContactStore;
      26              : class CardDavClient;
      27              : class ConnectionHealthMonitor;
      28              : class DesktopNotifier;
      29              : class NotificationBatcher;
      30              : class FolderPredictor;
      31              : class FolderTree;
      32              : class ImapService;
      33              : class MailTabWidget;
      34              : class TabManager;
      35              : #ifdef MAILJD_KDE_INTEGRATION
      36              : class KStatusNotifierItem;
      37              : #endif
      38              : #include "data/MailCache.h" // T-180: needed for MailCache::SearchResult
      39              : class MailController;
      40              : class UndoManager;
      41              : class MailFilterProxyModel;
      42              : class MailListModel;
      43              : class MailThreadModel;
      44              : class FolderOperationsController;
      45              : class MailView;
      46              : class SearchCoordinator;
      47              : class SearchPanel;
      48              : class SettingsSyncService;
      49              : class ShortcutHelpOverlay;
      50              : struct AccountConfig;
      51              : #include "data/AccountConfig.h" // T-271: For ImapConfig in reconnect
      52              : 
      53              : // MainWindow implements the 3-pane mail client layout:
      54              : //   Left:         FolderTree (account folders)
      55              : //   Top-right:    MailList (message headers)
      56              : //   Bottom-right: MailView (selected message content)
      57              : class MainWindow : public QMainWindow {
      58         2651 :   Q_OBJECT
      59              : #ifdef MAILJD_UNIT_TEST
      60              :   friend class TestMainWindow;
      61              :   friend class TestSprint55Ui;
      62              :   friend class TestSprint49Desktop;
      63              :   friend class TestSprint59MainWindow;
      64              :   friend class TestSearchCoordinator;
      65              :   friend class TestFolderOperations;
      66              :   friend class TestSprint68MainWindow;
      67              :   friend class TestSprint76;
      68              : #endif
      69              : #ifdef MAILJD_E2E_TESTING
      70              :   friend class TestE2EInteractive;
      71              : #endif
      72              : 
      73              : public:
      74              :   explicit MainWindow(QWidget *parent = nullptr);
      75              :   ~MainWindow() override;
      76              : 
      77              :   // Sprint 49: public API for D-Bus service
      78              :   void openComposeNew();
      79              :   bool openMailtoUrl(const QString &url);
      80              :   void triggerPollNow();
      81              : 
      82              :   // Sprint 76 (T-76.A1): central foreground-activation helper used by the
      83              :   // notification "Open" action, the D-Bus entry points (Activate/Compose/
      84              :   // ComposeMailto) and the tray click handlers. Restores from minimized,
      85              :   // raises the window and requests compositor focus. With
      86              :   // MAILJD_HAVE_KWINDOWSYSTEM this uses KWindowSystem::activateWindow
      87              :   // (Wayland xdg-activation-v1 / X11 _NET_ACTIVE_WINDOW); without it the
      88              :   // Qt-only raise()+activateWindow() fallback remains.
      89              :   void bringToFront();
      90              : 
      91              : protected:
      92              :   void closeEvent(QCloseEvent *event) override;
      93              :   bool eventFilter(QObject *obj, QEvent *event) override; // T-181
      94              : 
      95              : private:
      96              :   // T-304: Runtime language switching
      97              :   void retranslateUi();
      98              : 
      99              : protected:
     100              :   void changeEvent(QEvent *event) override;
     101              : 
     102              :   void setupUi();
     103              :   void setupMenuBar();
     104              :   void setupStatusBar();
     105              :   void connectSignals();
     106              :   void loadAccounts();
     107              :   void reloadAccounts();
     108              :   void restoreLayout();
     109              :   void saveLayout();
     110              :   void refreshTreeWithBadges(); // Bug 4: restores badges after tree rebuild
     111              :   void setStatus(const QString &message);
     112              :   void setStatus(const QString &key, const QString &message, int timeoutMs = 0);
     113              :   void clearStatus(const QString &key);
     114              : 
     115              :   void showSettings();
     116              :   void showSetupWizard();
     117              :   void showSubscriptionDialog();
     118              :   void showContactManager(); // T-163
     119              :   // Fix B: DRY helper for wiring ContactStore + recipientsSent on ComposeWindow
     120              :   void configureComposeWindow(ComposeWindow *compose) const;
     121              :   void setupComposeTracking(ComposeWindow *compose);
     122              :   QString detectSpecialFolder(const QString &kind) const; // T-178
     123              :   void deleteOldDraft(qint64 draftUid);   // T-177: STORE \Deleted + EXPUNGE
     124              :   void openDraftInCompose(qint64 uid, const MailHeader &header); // T-177
     125              : 
     126              :   // T-290 folder management lives in FolderOperationsController (Sprint 65
     127              :   // P2.2).
     128              :   static QString messageIdHeaderValue(const QString &messageId);
     129              :   static QStringList replyReferencesForHeader(const MailHeader &header);
     130              :   static QString attachmentSaveDialogPath(const QString &filename);
     131              :   // Search-mode state and logic live in SearchCoordinator (Sprint 65 P2.1).
     132              : 
     133              :   // T-092: Reply / Forward compose helpers
     134              :   void openReply(qint64 uid, bool replyAll);
     135              :   void openForward(qint64 uid);
     136              : 
     137              :   // T-099: Thread view toggle
     138              :   void toggleThreadView(bool threaded);
     139              : 
     140              :   // T-142: CommandBar command dispatcher
     141              :   void executeCommand(const QString &cmd);
     142              : 
     143              :   // T-151: Toggle normal-mode shortcuts on/off
     144              :   void setNormalMode(bool enabled);
     145              : 
     146              :   // T-147: Detect special folders (Trash, Archive) from folder flags
     147              :   void detectSpecialFolders();
     148              : 
     149              :   // T-145: Vim navigation helpers
     150              :   void moveMailSelection(int delta);
     151              :   void moveMailSelectionPage(int delta);
     152              :   void moveMailSelectionToEnd(bool top);
     153              : 
     154              :   // T-148: Show shortcut help overlay
     155              :   void showShortcutHelp();
     156              : 
     157              :   // T-147: Select next mail after a move/delete
     158              :   void selectNextAfterMove();
     159              :   void jumpToNextUnread();       // Space: jump to next unread in folder or switch folder
     160              :   void jumpToNextUnreadFolder(); // Helper: switch to next folder with unread mails
     161              :   void copyTabCacheToFolder(const QList<qint64> &uids, const QString &targetFolder);
     162              : 
     163              :   // T-168: Update folder suggestion label for current mail
     164              :   void updateSuggestion();
     165              : 
     166              :   // T-169: Quick-move to suggested folder
     167              :   void quickMoveToSuggestion();
     168              : 
     169              :   // T-232: Viewport-based suggestion computation
     170              :   void computeVisibleSuggestions();
     171              :   QList<MailHeader> getVisibleHeaders() const;
     172              : 
     173              :   // T-407: Resolved mail identity for search-mode-safe actions
     174              :   struct MailId {
     175              :     qint64 uid = -1;
     176              :     qint64 folderId = -1;
     177              :     QString folderPath;
     178           26 :     bool isValid() const { return uid >= 0; }
     179           10 :     bool hasFolderId() const { return folderId > 0; }
     180              :   };
     181              :   MailId mailIdFromViewIndex(const QModelIndex &viewIdx) const;
     182              :   MailId currentMailId() const;
     183              :   QList<MailId> getSelectedMailIds() const;
     184              :   bool isSearchMode() const;
     185              : 
     186              :   // T-170: Train predictor after a move action
     187              :   void trainAfterMove(const QList<MailId> &mails, const QString &targetFolder);
     188              : 
     189              :   // T-213: Open a mail in a separate tab
     190              :   void openMailInTab(qint64 uid);
     191              : 
     192              :   // Get UIDs of selected mails (falls back to currentIndex if no selection)
     193              :   QList<qint64> getSelectedUids() const;
     194              : 
     195              :   // Session state restore helpers (called after IMAP data arrives)
     196              :   void restoreSessionFolder();
     197              :   void restoreSessionMail();
     198              : 
     199              :   // Resolves a QTreeView index to a UID,
     200              :   // correctly handling both flat and thread view.
     201              :   qint64 uidFromViewIndex(const QModelIndex &viewIdx) const;
     202              : 
     203              :   // (Re-)connects the selection model handler for mail selection.
     204              :   // Disconnects any previous handler to prevent accumulation.
     205              :   void reconnectSelectionHandler();
     206              : 
     207              :   // T-124: System tray
     208              :   void setupTray();
     209              :   void updateTrayIcon(int unreadCount);
     210              :   void quitApp();
     211              :   bool m_reallyQuit = false;
     212              : 
     213              :   // Sprint 49: Tray helpers
     214              :   void rebuildTrayMenu();
     215              : 
     216              :   // Sprint 49: Desktop notifications
     217              :   void notifyNewMail(const QString &from, const QString &subject, qint64 uid,
     218              :                      qint64 folderId = -1);
     219              :   // 67.A2: summary popup for a clustered burst (replaces in place)
     220              :   void notifySummaryPopup(const QString &title, const QString &body,
     221              :                           int count, qint64 folderId, qint64 newestUid);
     222              :   void onNotificationAction(uint id, const QString &action);
     223              :   DesktopNotifier *m_desktopNotifier = nullptr;
     224              :   NotificationBatcher *m_notificationBatcher = nullptr; // 67.A2
     225              :   uint m_summaryNotificationId = 0; // summary still on screen (0 = none)
     226              :   // 67.A1: notification id → (folderId, uid) so "open" can switch folders
     227              :   QMap<uint, QPair<qint64, qint64>> m_notificationUids;
     228              : 
     229              :   // 67.A1: Select + center-scroll a mail, switching folders if needed.
     230              :   // When the headers are not loaded yet, the reveal is deferred via
     231              :   // m_pendingRestoreUid (consumed by restoreSessionMail on modelReset).
     232              :   void selectAndRevealMail(qint64 folderId, qint64 uid);
     233              :   // Resolve uid through the active model (flat or thread), select the row
     234              :   // (ClearAndSelect) and scroll it to center. False if uid not in model.
     235              :   // folderId <= 0 → controller's current folder (67.A3: search results
     236              :   // pass their own folderId).
     237              :   bool trySelectMailInView(qint64 uid, qint64 folderId = -1);
     238              : 
     239              :   // T-316: Settings sync
     240              :   void initSettingsSync();
     241              :   void triggerSettingsUpload();   // Debounced wrapper (restarts 500ms timer)
     242              :   void doSettingsUpload();       // Actual upload (called by timer)
     243              :   void onRemoteSettingsReceived(const struct SyncPayload &payload);
     244              : 
     245              :   // UI Panels
     246              :   FolderTree *m_folderTree = nullptr;
     247              :   QTreeView *m_mailList = nullptr;
     248              :   MailView *m_mailView = nullptr;
     249              :   MailListModel *m_mailListModel = nullptr;
     250              :   MailThreadModel *m_mailThreadModel = nullptr;  // T-099
     251              :   MailFilterProxyModel *m_mailListProxy = nullptr;
     252              :   CommandBar *m_commandBar = nullptr;            // T-141
     253              :   SearchPanel *m_searchPanel = nullptr;          // Sprint 59 (U2)
     254              :   SearchCoordinator *m_search = nullptr;         // Sprint 65 (P2.1)
     255              :   FolderOperationsController *m_folderOps = nullptr; // Sprint 65 (P2.2)
     256              :   ShortcutHelpOverlay *m_helpOverlay = nullptr;  // T-148
     257              : 
     258              :   // Layout
     259              :   QSplitter *m_horizontalSplitter = nullptr;
     260              :   QSplitter *m_verticalSplitter = nullptr;
     261              : 
     262              :   // T-213: Tab system
     263              :   QTabBar *m_tabBar = nullptr;
     264              :   QStackedWidget *m_tabStack = nullptr;
     265              :   TabManager *m_tabManager = nullptr;
     266              : 
     267              :   // Status
     268              :   QLabel *m_statusLabel = nullptr;
     269              :   QMap<QString, QString> m_statusMessages;  // T-196: keyed messages
     270              :   QMap<QString, QTimer *> m_statusTimers;   // T-196: auto-clear timers
     271              :   void renderStatusBar();
     272              : 
     273              :   // T-168: Folder suggestion label (right side of status bar)
     274              :   QLabel *m_suggestionLabel = nullptr;
     275              : 
     276              :   // Services
     277              :   ImapService *m_imapService = nullptr;
     278              :   // T-720: monitors m_imapService for dead-socket detection, exponential-
     279              :   // backoff reconnect, and suspend/network reactivity. Owns its own timers.
     280              :   ConnectionHealthMonitor *m_imapHealth = nullptr;
     281              :   MailCache *m_cache = nullptr;
     282              :   MailController *m_controller = nullptr;
     283              :   UndoManager *m_undoManager = nullptr; // T-211
     284              : 
     285              :   // Test seam: how modal dialogs are run. Default executes dialog->exec();
     286              :   // unit tests override this to run dialogs non-blocking (no real modal loop).
     287              :   std::function<int(QDialog *)> m_runDialog;
     288              :   // Test seams for the static modal helpers (QInputDialog / QMessageBox).
     289              :   // Defaults call the real Qt dialogs; unit tests override them.
     290              :   std::function<QString(const QString &title, const QString &label,
     291              :                         const QString &initial, bool *ok)>
     292              :       m_promptText;
     293              :   std::function<bool(const QString &title, const QString &text)> m_confirm;
     294              : 
     295              :   // T-163: Contact management
     296              :   ContactStore *m_contactStore = nullptr;
     297              :   CardDavClient *m_cardDavClient = nullptr;
     298              :   QTimer *m_cardDavSyncTimer = nullptr;
     299              : 
     300              :   // T-339: Calendar management (Sprint 32)
     301              :   class CalendarStore *m_calendarStore = nullptr;
     302              :   class CalendarWidget *m_calendarWidget = nullptr;
     303              :   class TaskListWidget *m_taskListWidget = nullptr;
     304              :   QTimer *m_calDavSyncTimer = nullptr;
     305              :   void initCalendarSync();
     306              :   void triggerCalDavSync();
     307              :   void openCalendarTab();
     308              :   void openTaskTab();
     309              : 
     310              :   // Sprint 39: Calendar/task editing helpers
     311              :   CalDavClient *createCalDavWriteClient(const QString &calendarPath,
     312              :                                         const QString &accountId = {});
     313              :   void showEventEditDialog(const CalendarEvent &event, bool isNew,
     314              :                            const QDate &defaultDate = {},
     315              :                            const QTime &defaultStart = {},
     316              :                            const QTime &defaultEnd = {});
     317              :   void showTaskEditDialog(const CalendarTask &task, bool isNew);
     318              :   void onEventSaved(const CalendarEvent &event, bool wasNew);
     319              :   void onEventDeleted(const CalendarEvent &event);
     320              :   void onTaskSaved(const CalendarTask &task, bool wasNew);
     321              :   void onTaskDeleted(const CalendarTask &task);
     322              :   void onCalDavEventWriteSucceeded(const CalendarEvent &event);
     323              :   void onCalDavTaskWriteSucceeded(const CalendarTask &task);
     324              : 
     325              :   // T-166/T-171: FolderPredictor for automatic folder suggestions
     326              :   FolderPredictor *m_folderPredictor = nullptr;
     327              :   QString m_currentSuggestion;
     328              :   double m_currentSuggestionConfidence = 0.0;
     329              :   QSet<qint64> m_alternateUids;           // T-234: UIDs showing alternate suggestion
     330              :   QString m_currentAltSuggestion;         // T-234: 2nd suggestion for current mail
     331              :   double m_currentAltConfidence = 0.0;    // T-234: Confidence of 2nd suggestion
     332              :   bool m_suggestionColumnVisible = false; // T-232: Column toggle state
     333              :   QString m_predictorDbPath;              // T-232: DB path for async worker
     334              :   QThread *m_suggestionThread = nullptr;            // T-232: Persistent worker thread
     335              :   class SuggestionWorker *m_suggestionWorker = nullptr; // T-232: Persistent worker
     336              :   QTimer *m_scrollDebounce = nullptr;     // T-232: Scroll debounce
     337              :   QSet<qint64> m_suggestedUids;           // T-232: Already-computed UIDs
     338              : 
     339              :   // Configuration
     340              :   QSettings m_settings{"MailJD", "MailJD"};
     341              :   AccountConfig m_primaryAccount;
     342              :   bool m_hasPrimaryAccount = false;
     343              : 
     344              :   // Pending session restore state (consumed once after IMAP connects)
     345              :   QString m_pendingRestoreFolder;
     346              :   qint64 m_pendingRestoreUid = -1;
     347              :   QStringList m_pendingExpandedFolders;
     348              :   QStringList m_reconnectExpandedFolders; // T-546: expand state saved before reconnect setFolders
     349              :   QStringList m_allFolderPaths; // T-062: cached for subscription dialog
     350              :   QList<FolderInfo> m_lastFolderList; // T-069: cached for tree refresh after hide
     351              :   bool m_threadViewActive = false;     // T-099: thread view toggle state
     352              :   QAction *m_threadViewAction = nullptr; // T-127: persisted thread view
     353              :   bool m_pendingThreadView = false;      // T-127: restore on first load
     354              : 
     355              :   // T-124: System tray
     356              :   QSystemTrayIcon *m_trayIcon = nullptr;
     357              :   QMenu *m_trayMenu = nullptr;
     358              :   bool m_trayNotificationShown = false;
     359              : #ifdef MAILJD_KDE_INTEGRATION
     360              :   KStatusNotifierItem *m_sni = nullptr;
     361              : #endif
     362              : 
     363              :   // Selection handler connection (to disconnect before reconnecting)
     364              :   QMetaObject::Connection m_selectionConnection;
     365              : 
     366              :   // Bug 2: Persist expand/collapse state of threads across model resets
     367              :   QSet<qint64> m_expandedThreadUids;
     368              :   bool m_threadExpandedInitial = false; // true = user has custom state, don't expandAll
     369              :   void saveExpandedState();
     370              :   void restoreExpandedState();
     371              : 
     372              :   // T-151: Normal-mode shortcut actions (disabled when CommandBar is active)
     373              :   QList<QAction *> m_normalModeActions;
     374              :   QMap<QAction *, QKeySequence> m_savedShortcuts;
     375              : 
     376              :   // T-147: Special folder paths detected from IMAP flags
     377              :   QString m_trashFolder;
     378              :   QString m_archiveFolder;
     379              :   QString m_junkFolder;
     380              :   QString m_draftsFolder;  // T-177: Cached Drafts folder path
     381              :   QString m_sentFolder;    // T-178: Cached Sent folder path
     382              :   QString m_previousFolder; // T-266: Last folder for Shift+B navigation
     383              :   QString m_imapDelimiter;  // T-290: IMAP hierarchy delimiter (e.g. "." or "/")
     384              : 
     385              :   // T-145: gg sequence detection
     386              :   QTimer *m_ggTimer = nullptr;
     387              :   bool m_gPending = false;
     388              : 
     389              :   // T-271: IMAP reconnect state
     390              :   // T-720: reconnect logic moved into ConnectionHealthMonitor. We keep
     391              :   // m_reconnectImapConfig because SettingsSyncService::configure() reads
     392              :   // it (MainWindow.cpp:2803).
     393              :   ImapConfig m_reconnectImapConfig;
     394              : 
     395              :   // T-215: Pending tab state for restore after cache init
     396              :   QVariantList m_pendingTabState;
     397              :   // True once the saved tabs have been restored. Until then we must NOT persist
     398              :   // tab state (the in-progress restore would overwrite the saved state).
     399              :   bool m_tabRestoreDone = false;
     400              :   // Persist open tabs + active index. Called on every tab change so the state
     401              :   // survives an unclean exit (e.g. Ctrl+C), where quitApp()/saveLayout() never
     402              :   // run.
     403              :   void persistTabState();
     404              : 
     405              :   // T-316: Settings sync service
     406              :   SettingsSyncService *m_syncService = nullptr;
     407              :   QString m_syncFolderPath;          // e.g. "MailJD-Settings"
     408              :   QTimer *m_syncDebounce = nullptr;  // Bundles rapid changes into one upload
     409              : };
        

Generated by: LCOV version 2.0-1