MailJD nbsp;·nbsp; Test Dashboard nbsp;·nbsp; Coverage
LCOV - code coverage report
Current view: top level - ui - TabManager.cpp (source / functions) Coverage Total Hit
Test: MailJD Coverage (Unit + E2E) Lines: 94.3 % 244 230
Test Date: 2026-06-21 21:10:19 Functions: 96.3 % 27 26
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 55.8 % 428 239

             Branch data     Line data    Source code
       1                 :             : #include "TabManager.h"
       2                 :             : 
       3                 :             : #include <QLoggingCategory>
       4                 :             : #include <QMouseEvent>
       5                 :             : #include <QStackedWidget>
       6                 :             : #include <QTabBar>
       7                 :             : #include <QVariantMap>
       8                 :             : 
       9                 :             : #include "ui/MdiIconProvider.h"
      10                 :             : #include "ui/ThemeManager.h"
      11                 :             : 
      12                 :             : #include <QTimer>
      13                 :             : 
      14   [ +  +  +  -  :          55 : Q_LOGGING_CATEGORY(lcTabs, "mailjd.tabs")
             +  -  -  - ]
      15                 :             : 
      16                 :          86 : TabManager::TabManager(QTabBar *tabBar, QStackedWidget *stack, QObject *parent)
      17                 :          86 :     : QObject(parent), m_tabBar(tabBar), m_stack(stack) {
      18                 :             :   // Add the main view tab (index 0, not closable)
      19                 :          86 :   TabInfo mainTab;
      20                 :          86 :   mainTab.type = TabInfo::MainView;
      21                 :          86 :   mainTab.title = QStringLiteral("\U0001F4EC MailJD");
      22         [ +  - ]:          86 :   mainTab.widget = m_stack->widget(0); // Already added by MainWindow
      23         [ +  - ]:          86 :   m_tabs.append(mainTab);
      24                 :             : 
      25         [ +  - ]:          86 :   m_tabBar->addTab(mainTab.title);
      26         [ +  - ]:          86 :   m_tabBar->setTabsClosable(true);
      27                 :             : 
      28                 :             :   // MainView tab: remove close button
      29         [ +  - ]:          86 :   m_tabBar->setTabButton(0, QTabBar::RightSide, nullptr);
      30         [ +  - ]:          86 :   m_tabBar->setTabButton(0, QTabBar::LeftSide, nullptr);
      31                 :             : 
      32         [ +  - ]:          86 :   updateTabBarVisibility();
      33                 :             : 
      34                 :             :   // T-215: Middle-click to close tabs
      35         [ +  - ]:          86 :   m_tabBar->installEventFilter(this);
      36                 :             : 
      37                 :             :   // Wire tab bar signals
      38         [ +  - ]:          86 :   connect(m_tabBar, &QTabBar::currentChanged, this, [this](int index) {
      39   [ +  -  +  -  :          55 :     if (index >= 0 && index < m_tabs.size()) {
                   +  - ]
      40                 :             :       // T-542: Track MRU tab history
      41   [ +  +  +  +  :          55 :       if (m_tabHistory.isEmpty() || m_tabHistory.last() != index)
                   +  + ]
      42                 :          53 :         m_tabHistory.append(index);
      43                 :          55 :       m_stack->setCurrentIndex(index);
      44                 :          55 :       emit currentTabChanged(index, m_tabs[index].type);
      45                 :             :     }
      46                 :          55 :   });
      47                 :             : 
      48         [ +  - ]:          86 :   connect(m_tabBar, &QTabBar::tabCloseRequested, this, [this](int index) {
      49                 :           0 :     closeTab(index);
      50                 :           0 :   });
      51                 :          86 : }
      52                 :             : 
      53                 :          37 : int TabManager::openMailTab(qint64 uid, qint64 folderId,
      54                 :             :                             const QString &subject,
      55                 :             :                             const QString &messageId) {
      56                 :             :   // Duplicate check: if tab for this UID exists, switch to it
      57                 :          37 :   int existing = findTabByUid(uid);
      58         [ +  + ]:          37 :   if (existing >= 0) {
      59         [ +  - ]:           2 :     switchToTab(existing);
      60   [ +  -  +  -  :           4 :     qCInfo(lcTabs) << "Switched to existing tab for UID" << uid;
          +  -  +  -  +  
                      + ]
      61                 :           2 :     return existing;
      62                 :             :   }
      63                 :             : 
      64                 :             :   // Truncate title
      65                 :          35 :   QString title = subject;
      66         [ -  + ]:          35 :   if (title.isEmpty())
      67                 :           0 :     title = QStringLiteral("(Kein Betreff)");
      68         [ +  + ]:          35 :   if (title.length() > kMaxTitleLength)
      69   [ +  -  +  - ]:           1 :     title = title.left(kMaxTitleLength - 1) + QStringLiteral("\u2026");
      70                 :             : 
      71   [ +  -  +  -  :          70 :   qCInfo(lcTabs) << "Opening new tab for UID" << uid << ":" << title;
          +  -  +  -  +  
             -  +  -  +  
                      + ]
      72                 :             : 
      73                 :             :   // The actual widget will be set by MainWindow after this returns
      74                 :             :   // (it needs to create a MailTabWidget and load the body)
      75                 :          35 :   TabInfo info;
      76                 :          35 :   info.type = TabInfo::MailTab;
      77                 :          35 :   info.title = title;
      78                 :          35 :   info.mailUid = uid;
      79                 :          35 :   info.folderId = folderId;
      80                 :          35 :   info.messageId = messageId;
      81                 :          35 :   info.widget = nullptr; // Will be set by caller
      82                 :             : 
      83         [ +  - ]:          35 :   m_tabs.append(info);
      84         [ +  - ]:          35 :   int tabIndex = m_tabBar->addTab(title);
      85                 :             : 
      86         [ +  - ]:          35 :   updateTabBarVisibility();
      87         [ +  - ]:          35 :   emit tabCountChanged(m_tabs.size());
      88                 :             : 
      89                 :          35 :   return tabIndex;
      90                 :          35 : }
      91                 :             : 
      92                 :          17 : int TabManager::openCalendarTab(QWidget *widget) {
      93                 :          17 :   int existing = findTabByType(TabInfo::CalendarTab);
      94         [ +  + ]:          17 :   if (existing >= 0) {
      95         [ +  - ]:           8 :     switchToTab(existing);
      96                 :           8 :     return existing;
      97                 :             :   }
      98                 :           9 :   TabInfo info;
      99                 :           9 :   info.type = TabInfo::CalendarTab;
     100                 :           9 :   info.title = QStringLiteral("Kalender");
     101                 :           9 :   info.widget = widget;
     102         [ +  - ]:           9 :   m_tabs.append(info);
     103         [ +  - ]:           9 :   int idx = m_tabBar->addTab(info.title);
     104         [ +  - ]:           9 :   m_tabBar->setTabIcon(
     105   [ +  -  +  - ]:          27 :       idx, MdiIconProvider::instance().icon(
     106   [ +  -  +  -  :          27 :                QStringLiteral("calendar-month"), 16, QColor(ThemeManager::instance().color("@text_secondary"))));
                   +  - ]
     107         [ +  - ]:           9 :   m_stack->addWidget(widget);
     108         [ +  - ]:           9 :   switchToTab(idx);
     109         [ +  - ]:           9 :   updateTabBarVisibility();
     110         [ +  - ]:           9 :   emit tabCountChanged(m_tabs.size());
     111                 :           9 :   return idx;
     112                 :           9 : }
     113                 :             : 
     114                 :           9 : int TabManager::openTaskTab(QWidget *widget) {
     115                 :           9 :   int existing = findTabByType(TabInfo::TaskTab);
     116         [ +  + ]:           9 :   if (existing >= 0) {
     117         [ +  - ]:           1 :     switchToTab(existing);
     118                 :           1 :     return existing;
     119                 :             :   }
     120                 :           8 :   TabInfo info;
     121                 :           8 :   info.type = TabInfo::TaskTab;
     122                 :           8 :   info.title = QStringLiteral("Aufgaben");
     123                 :           8 :   info.widget = widget;
     124         [ +  - ]:           8 :   m_tabs.append(info);
     125         [ +  - ]:           8 :   int idx = m_tabBar->addTab(info.title);
     126         [ +  - ]:           8 :   m_tabBar->setTabIcon(
     127   [ +  -  +  - ]:          24 :       idx, MdiIconProvider::instance().icon(
     128   [ +  -  +  -  :          24 :                QStringLiteral("checkbox-marked-outline"), 16, QColor(ThemeManager::instance().color("@text_secondary"))));
                   +  - ]
     129         [ +  - ]:           8 :   m_stack->addWidget(widget);
     130         [ +  - ]:           8 :   switchToTab(idx);
     131         [ +  - ]:           8 :   updateTabBarVisibility();
     132         [ +  - ]:           8 :   emit tabCountChanged(m_tabs.size());
     133                 :           8 :   return idx;
     134                 :           8 : }
     135                 :             : 
     136                 :          15 : void TabManager::closeTab(int index) {
     137   [ +  +  -  +  :          15 :   if (index <= 0 || index >= m_tabs.size()) {
                   +  + ]
     138                 :             :     // Cannot close main view (index 0) or invalid index
     139                 :           4 :     return;
     140                 :             :   }
     141                 :             : 
     142   [ +  -  +  -  :          22 :   qCInfo(lcTabs) << "Closing tab" << index << ":" << m_tabs[index].title;
          +  -  +  -  +  
          -  +  -  +  -  
                   +  + ]
     143                 :             : 
     144         [ +  - ]:          11 :   QWidget *widget = m_tabs[index].widget;
     145         [ +  - ]:          11 :   m_tabs.removeAt(index);
     146                 :             : 
     147                 :             :   // T-542: Find MRU target before removing the tab from the bar.
     148                 :             :   // Walk history backwards, skip the closed index, adjust for shift.
     149                 :          11 :   int mruTarget = -1;
     150         [ +  + ]:          18 :   for (int i = m_tabHistory.size() - 1; i >= 0; --i) {
     151         [ +  - ]:          11 :     int h = m_tabHistory[i];
     152         [ +  + ]:          11 :     if (h == index) continue; // skip the tab being closed
     153         [ -  + ]:           4 :     mruTarget = (h > index) ? h - 1 : h;
     154   [ +  -  +  -  :           4 :     if (mruTarget >= 0 && mruTarget < m_tabs.size()) break;
                   +  - ]
     155                 :           0 :     mruTarget = -1;
     156                 :             :   }
     157                 :             :   // Purge closed index and adjust remaining entries
     158                 :          11 :   QList<int> newHistory;
     159   [ +  -  +  -  :          32 :   for (int h : m_tabHistory) {
                   +  + ]
     160         [ +  + ]:          21 :     if (h == index) continue;
     161   [ -  +  +  - ]:          10 :     newHistory.append(h > index ? h - 1 : h);
     162                 :             :   }
     163                 :          11 :   m_tabHistory = newHistory;
     164                 :             : 
     165                 :             :   // Block currentChanged during removal to prevent stale widget display
     166                 :             :   {
     167                 :          11 :     QSignalBlocker blocker(m_tabBar);
     168         [ +  - ]:          11 :     m_tabBar->removeTab(index);
     169                 :          11 :   }
     170         [ +  - ]:          11 :   m_stack->removeWidget(widget);
     171         [ +  + ]:          11 :   if (widget)
     172         [ +  - ]:           6 :     widget->deleteLater();
     173                 :             : 
     174                 :             :   // T-542: Switch to MRU target, or fall back to QTabBar's choice
     175   [ +  +  +  - ]:          11 :   int newCurrent = (mruTarget >= 0) ? mruTarget : m_tabBar->currentIndex();
     176   [ +  -  +  -  :          11 :   if (newCurrent >= 0 && newCurrent < m_tabs.size()) {
                   +  - ]
     177         [ +  - ]:          11 :     m_tabBar->setCurrentIndex(newCurrent);
     178         [ +  - ]:          11 :     m_stack->setCurrentIndex(newCurrent);
     179                 :             :   }
     180                 :             : 
     181         [ +  - ]:          11 :   updateTabBarVisibility();
     182         [ +  - ]:          11 :   emit tabCountChanged(m_tabs.size());
     183   [ +  -  +  -  :          11 :   if (newCurrent >= 0 && newCurrent < m_tabs.size())
                   +  - ]
     184   [ +  -  +  - ]:          11 :     emit currentTabChanged(newCurrent, m_tabs[newCurrent].type);
     185                 :          11 : }
     186                 :             : 
     187                 :          10 : void TabManager::closeCurrentTab() {
     188                 :          10 :   closeTab(m_tabBar->currentIndex());
     189                 :          10 : }
     190                 :             : 
     191                 :          70 : void TabManager::switchToTab(int index) {
     192   [ +  -  +  +  :          70 :   if (index >= 0 && index < m_tabs.size()) {
                   +  + ]
     193                 :          69 :     m_tabBar->setCurrentIndex(index);
     194                 :             :   }
     195                 :          70 : }
     196                 :             : 
     197                 :           8 : void TabManager::switchToMainView() { switchToTab(0); }
     198                 :             : 
     199                 :           8 : void TabManager::switchToNextTab() {
     200         [ +  + ]:           8 :   if (m_tabs.size() <= 1) return;
     201                 :           6 :   int next = (m_tabBar->currentIndex() + 1) % m_tabs.size();
     202                 :           6 :   switchToTab(next);
     203                 :             : }
     204                 :             : 
     205                 :           5 : void TabManager::switchToPreviousTab() {
     206         [ +  + ]:           5 :   if (m_tabs.size() <= 1) return;
     207                 :           3 :   int prev = (m_tabBar->currentIndex() - 1 + m_tabs.size()) % m_tabs.size();
     208                 :           3 :   switchToTab(prev);
     209                 :             : }
     210                 :             : 
     211                 :          30 : int TabManager::tabCount() const { return m_tabs.size(); }
     212                 :             : 
     213                 :          14 : int TabManager::currentIndex() const { return m_tabBar->currentIndex(); }
     214                 :             : 
     215                 :           3 : TabInfo TabManager::currentTabInfo() const {
     216                 :           3 :   int idx = m_tabBar->currentIndex();
     217   [ +  -  +  -  :           3 :   if (idx >= 0 && idx < m_tabs.size())
                   +  - ]
     218                 :           3 :     return m_tabs[idx];
     219                 :           0 :   return {};
     220                 :             : }
     221                 :             : 
     222                 :         467 : bool TabManager::isMainView() const {
     223                 :         467 :   return m_tabBar->currentIndex() == 0;
     224                 :             : }
     225                 :             : 
     226                 :          57 : int TabManager::findTabByUid(qint64 uid) const {
     227         [ +  + ]:         125 :   for (int i = 0; i < m_tabs.size(); ++i) {
     228   [ +  +  +  +  :          75 :     if (m_tabs[i].type == TabInfo::MailTab && m_tabs[i].mailUid == uid)
                   +  + ]
     229                 :           7 :       return i;
     230                 :             :   }
     231                 :          50 :   return -1;
     232                 :             : }
     233                 :             : 
     234                 :          28 : int TabManager::findTabByType(TabInfo::Type type) const {
     235         [ +  + ]:          70 :   for (int i = 0; i < m_tabs.size(); ++i) {
     236         [ +  + ]:          53 :     if (m_tabs[i].type == type)
     237                 :          11 :       return i;
     238                 :             :   }
     239                 :          17 :   return -1;
     240                 :             : }
     241                 :             : 
     242                 :           2 : void TabManager::updateTabFolder(int index, qint64 newFolderId) {
     243   [ +  -  +  -  :           2 :   if (index > 0 && index < m_tabs.size()) {
                   +  - ]
     244                 :           2 :     m_tabs[index].folderId = newFolderId;
     245                 :             :   }
     246                 :           2 : }
     247                 :             : 
     248                 :           0 : void TabManager::updateTabUid(int index, qint64 newUid) {
     249   [ #  #  #  #  :           0 :   if (index > 0 && index < m_tabs.size()) {
                   #  # ]
     250                 :           0 :     m_tabs[index].mailUid = newUid;
     251                 :             :   }
     252                 :           0 : }
     253                 :             : 
     254                 :           6 : void TabManager::setTabWidget(int index, QWidget *widget) {
     255   [ +  -  +  -  :           6 :   if (index >= 0 && index < m_tabs.size()) {
                   +  - ]
     256                 :           6 :     m_tabs[index].widget = widget;
     257                 :             :   }
     258                 :           6 : }
     259                 :             : 
     260                 :          32 : QVariantList TabManager::saveState() const {
     261                 :          32 :   QVariantList result;
     262                 :             :   // T-430: Save active tab index as metadata entry
     263                 :          32 :   QVariantMap meta;
     264   [ +  -  +  - ]:          64 :   meta[QStringLiteral("_activeIndex")] = m_tabBar->currentIndex();
     265         [ +  - ]:          32 :   result.append(meta);
     266                 :             :   // Skip index 0 (MainView — always present)
     267         [ +  + ]:          73 :   for (int i = 1; i < m_tabs.size(); ++i) {
     268                 :          41 :     const auto &tab = m_tabs[i];
     269                 :          41 :     QVariantMap map;
     270         [ +  - ]:          82 :     map[QStringLiteral("type")] =
     271   [ +  +  -  - ]:         150 :         tab.type == TabInfo::MailTab ? QStringLiteral("mail")
     272   [ +  +  +  +  :          55 :         : tab.type == TabInfo::CalendarTab ? QStringLiteral("calendar")
                   -  - ]
     273   [ +  -  +  +  :          45 :         : tab.type == TabInfo::TaskTab ? QStringLiteral("task")
                   -  - ]
     274   [ -  +  +  +  :         164 :                                     : QStringLiteral("unknown");
                   -  - ]
     275         [ +  - ]:          82 :     map[QStringLiteral("uid")] = tab.mailUid;
     276         [ +  - ]:          82 :     map[QStringLiteral("folderId")] = tab.folderId;
     277         [ +  - ]:          82 :     map[QStringLiteral("title")] = tab.title;
     278         [ +  - ]:          82 :     map[QStringLiteral("messageId")] = tab.messageId;
     279         [ +  - ]:          41 :     result.append(map);
     280                 :          41 :   }
     281                 :          32 :   return result;
     282                 :          32 : }
     283                 :             : 
     284                 :           5 : void TabManager::restoreState(const QVariantList &state) {
     285                 :           5 :   int savedActiveIndex = -1;
     286         [ +  + ]:          17 :   for (const auto &entry : state) {
     287         [ +  - ]:          12 :     auto map = entry.toMap();
     288                 :             :     // T-430: Parse active tab index metadata
     289   [ +  -  +  + ]:          12 :     if (map.contains(QStringLiteral("_activeIndex"))) {
     290   [ +  -  +  - ]:           5 :       savedActiveIndex = map[QStringLiteral("_activeIndex")].toInt();
     291                 :           5 :       continue;
     292                 :             :     }
     293   [ +  -  +  - ]:          14 :     QString type = map.value(QStringLiteral("type")).toString();
     294                 :             : 
     295         [ -  + ]:           7 :     if (type == QStringLiteral("calendar")) {
     296   [ #  #  #  #  :           0 :       qCInfo(lcTabs) << "Restoring calendar tab";
             #  #  #  # ]
     297         [ #  # ]:           0 :       emit calendarTabRequested();
     298         [ -  + ]:           7 :     } else if (type == QStringLiteral("task")) {
     299   [ #  #  #  #  :           0 :       qCInfo(lcTabs) << "Restoring task tab";
             #  #  #  # ]
     300         [ #  # ]:           0 :       emit taskTabRequested();
     301         [ +  - ]:           7 :     } else if (type == QStringLiteral("mail")) {
     302   [ +  -  +  - ]:          14 :       qint64 uid = map.value(QStringLiteral("uid")).toLongLong();
     303   [ +  -  +  - ]:          14 :       qint64 folderId = map.value(QStringLiteral("folderId")).toLongLong();
     304   [ +  -  +  - ]:          14 :       QString title = map.value(QStringLiteral("title")).toString();
     305   [ +  -  +  - ]:          14 :       QString messageId = map.value(QStringLiteral("messageId")).toString();
     306                 :             : 
     307         [ -  + ]:           7 :       if (uid <= 0)
     308                 :           0 :         continue;
     309                 :             : 
     310   [ +  -  +  -  :          14 :       qCInfo(lcTabs) << "Restoring tab for UID" << uid << ":" << title;
          +  -  +  -  +  
             -  +  -  +  
                      + ]
     311                 :             : 
     312                 :           7 :       TabInfo info;
     313                 :           7 :       info.type = TabInfo::MailTab;
     314                 :           7 :       info.title = title;
     315                 :           7 :       info.mailUid = uid;
     316                 :           7 :       info.folderId = folderId;
     317                 :           7 :       info.messageId = messageId;
     318                 :           7 :       info.widget = nullptr;
     319                 :             : 
     320         [ +  - ]:           7 :       m_tabs.append(info);
     321         [ +  - ]:           7 :       m_tabBar->addTab(title);
     322                 :             : 
     323         [ +  - ]:           7 :       emit mailTabRequested(uid, folderId, messageId);
     324   [ +  -  +  - ]:           7 :     }
     325   [ +  -  +  + ]:          12 :   }
     326                 :             : 
     327                 :           5 :   updateTabBarVisibility();
     328         [ +  + ]:           5 :   if (m_tabs.size() > 1) {
     329                 :           4 :     emit tabCountChanged(m_tabs.size());
     330                 :             :   }
     331                 :             : 
     332                 :             :   // T-430: Restore active tab (deferred to allow widgets to initialize)
     333   [ +  -  +  -  :           5 :   if (savedActiveIndex >= 0 && savedActiveIndex < m_tabs.size()) {
                   +  - ]
     334         [ +  - ]:           5 :     QTimer::singleShot(0, this, [this, savedActiveIndex]() {
     335                 :           3 :       switchToTab(savedActiveIndex);
     336                 :           3 :     });
     337                 :             :   }
     338                 :           5 : }
     339                 :             : 
     340                 :         154 : void TabManager::updateTabBarVisibility() {
     341                 :         154 :   m_tabBar->setVisible(m_tabs.size() > 1);
     342                 :         154 : }
     343                 :             : 
     344                 :        1329 : bool TabManager::eventFilter(QObject *obj, QEvent *event) {
     345   [ +  -  +  +  :        1329 :   if (obj == m_tabBar && event->type() == QEvent::MouseButtonRelease) {
                   +  + ]
     346                 :           2 :     auto *me = static_cast<QMouseEvent *>(event);
     347         [ +  - ]:           2 :     if (me->button() == Qt::MiddleButton) {
     348   [ +  -  +  - ]:           2 :       int idx = m_tabBar->tabAt(me->pos());
     349         [ +  + ]:           2 :       if (idx > 0) closeTab(idx);
     350                 :           2 :       return true;
     351                 :             :     }
     352                 :             :   }
     353                 :        1327 :   return QObject::eventFilter(obj, event);
     354                 :             : }
        

Generated by: LCOV version 2.0-1