File indexing completed on 2024-12-15 04:54:46

0001 /*
0002     SPDX-FileCopyrightText: 2009 Kevin Ottens <ervin@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "pane.h"
0008 
0009 #include <KActionCollection>
0010 #include <KActionMenu>
0011 #include <KLocalizedString>
0012 #include <KToggleAction>
0013 #include <KXMLGUIClient>
0014 #include <QAction>
0015 #include <QIcon>
0016 #include <QMenu>
0017 
0018 #include <QAbstractItemModel>
0019 #include <QAbstractProxyModel>
0020 #include <QApplication>
0021 #include <QHeaderView>
0022 #include <QItemSelectionModel>
0023 #include <QMouseEvent>
0024 #include <QRegularExpression>
0025 #include <QTabBar>
0026 #include <QToolButton>
0027 
0028 #include "core/model.h"
0029 #include "core/widgets/quicksearchline.h"
0030 #include "messagelistsettings.h"
0031 #include "messagelistutil_p.h"
0032 #include "storagemodel.h"
0033 #include "widget.h"
0034 #include <Akonadi/ETMViewStateSaver>
0035 #include <Akonadi/MessageStatus>
0036 
0037 namespace MessageList
0038 {
0039 class Pane::PanePrivate
0040 {
0041 public:
0042     PanePrivate(Pane *owner)
0043         : q(owner)
0044     {
0045     }
0046 
0047     void setCurrentFolder(const QModelIndex &etmIndex);
0048     void onNewTabClicked();
0049     void onCloseTabClicked();
0050     void activateTab();
0051     void closeTab(QWidget *);
0052     void onCurrentTabChanged();
0053     void onTabContextMenuRequest(const QPoint &pos);
0054     void activateNextTab();
0055     void activatePreviousTab();
0056     void moveTabLeft();
0057     void moveTabRight();
0058     void moveTabBackward();
0059     void moveTabForward();
0060     void changeQuicksearchVisibility(bool);
0061     void addActivateTabAction(int i);
0062     void slotTabCloseRequested(int index);
0063     [[nodiscard]] QItemSelection mapSelectionFromSource(const QItemSelection &selection) const;
0064     void updateTabControls();
0065 
0066     Pane *const q;
0067 
0068     KXMLGUIClient *mXmlGuiClient = nullptr;
0069     KActionMenu *mActionMenu = nullptr;
0070 
0071     QAbstractItemModel *mModel = nullptr;
0072     QItemSelectionModel *mSelectionModel = nullptr;
0073     Core::PreSelectionMode mPreSelectionMode = Core::PreSelectLastSelected;
0074 
0075     QHash<Widget *, QItemSelectionModel *> mWidgetSelectionHash;
0076     QList<const QAbstractProxyModel *> mProxyStack;
0077 
0078     QToolButton *mNewTabButton = nullptr;
0079     QToolButton *mCloseTabButton = nullptr;
0080     QAction *mCloseTabAction = nullptr;
0081     QAction *mActivateNextTabAction = nullptr;
0082     QAction *mActivatePreviousTabAction = nullptr;
0083     QAction *mMoveTabLeftAction = nullptr;
0084     QAction *mMoveTabRightAction = nullptr;
0085     QString mQuickSearchPlaceHolderMessage;
0086     bool mPreferEmptyTab = false;
0087     int mMaxTabCreated = 0;
0088 };
0089 } // namespace MessageList
0090 
0091 using namespace Akonadi;
0092 using namespace MessageList;
0093 
0094 Pane::Pane(bool restoreSession, QAbstractItemModel *model, QItemSelectionModel *selectionModel, QWidget *parent)
0095     : QTabWidget(parent)
0096     , d(new PanePrivate(this))
0097 {
0098     setDocumentMode(true);
0099     d->mModel = model;
0100     d->mSelectionModel = selectionModel;
0101 
0102     // Build the proxy stack
0103     const auto *proxyModel = qobject_cast<const QAbstractProxyModel *>(d->mSelectionModel->model());
0104 
0105     while (proxyModel) {
0106         if (proxyModel == d->mModel) {
0107             break;
0108         }
0109 
0110         d->mProxyStack << proxyModel;
0111         const auto nextProxyModel = qobject_cast<const QAbstractProxyModel *>(proxyModel->sourceModel());
0112 
0113         if (!nextProxyModel) {
0114             // It's the final model in the chain, so it is necessarily the sourceModel.
0115             Q_ASSERT(proxyModel->sourceModel() == d->mModel);
0116             break;
0117         }
0118         proxyModel = nextProxyModel;
0119     } // Proxy stack done
0120 
0121     d->mNewTabButton = new QToolButton(this);
0122     d->mNewTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-new")));
0123     d->mNewTabButton->adjustSize();
0124     d->mNewTabButton->setToolTip(i18nc("@info:tooltip", "Open a new tab"));
0125 #ifndef QT_NO_ACCESSIBILITY
0126     d->mNewTabButton->setAccessibleName(i18n("New tab"));
0127 #endif
0128     setCornerWidget(d->mNewTabButton, Qt::TopLeftCorner);
0129     connect(d->mNewTabButton, &QToolButton::clicked, this, [this]() {
0130         d->onNewTabClicked();
0131     });
0132 
0133     d->mCloseTabButton = new QToolButton(this);
0134     d->mCloseTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
0135     d->mCloseTabButton->adjustSize();
0136     d->mCloseTabButton->setToolTip(i18nc("@info:tooltip", "Close the current tab"));
0137 #ifndef QT_NO_ACCESSIBILITY
0138     d->mCloseTabButton->setAccessibleName(i18n("Close tab"));
0139 #endif
0140     setCornerWidget(d->mCloseTabButton, Qt::TopRightCorner);
0141     connect(d->mCloseTabButton, &QToolButton::clicked, this, [this]() {
0142         d->onCloseTabClicked();
0143     });
0144 
0145     setTabsClosable(true);
0146     connect(this, &Pane::tabCloseRequested, this, [this](int index) {
0147         d->slotTabCloseRequested(index);
0148     });
0149 
0150     readConfig(restoreSession);
0151     setMovable(true);
0152 
0153     connect(this, &Pane::currentChanged, this, [this]() {
0154         d->onCurrentTabChanged();
0155     });
0156 
0157     setContextMenuPolicy(Qt::CustomContextMenu);
0158     connect(this, &Pane::customContextMenuRequested, this, [this](const QPoint &point) {
0159         d->onTabContextMenuRequest(point);
0160     });
0161 
0162     connect(MessageListSettings::self(), &MessageListSettings::configChanged, this, [this]() {
0163         d->updateTabControls();
0164     });
0165 
0166     // connect(this, &QTabWidget::tabBarDoubleClicked, this, &Pane::createNewTab);
0167 
0168     tabBar()->installEventFilter(this);
0169 }
0170 
0171 Pane::~Pane()
0172 {
0173     saveCurrentSelection();
0174     writeConfig(true);
0175 }
0176 
0177 void Pane::PanePrivate::addActivateTabAction(int i)
0178 {
0179     const QString actionname = QString::asprintf("activate_tab_%02d", i);
0180     auto action = new QAction(i18n("Activate Tab %1", i), q);
0181     mXmlGuiClient->actionCollection()->addAction(actionname, action);
0182     mXmlGuiClient->actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("Alt+%1").arg(i)));
0183     connect(action, &QAction::triggered, q, [this]() {
0184         activateTab();
0185     });
0186 }
0187 
0188 void Pane::PanePrivate::slotTabCloseRequested(int index)
0189 {
0190     QWidget *w = q->widget(index);
0191     if (w) {
0192         closeTab(w);
0193     }
0194 }
0195 
0196 void Pane::setXmlGuiClient(KXMLGUIClient *xmlGuiClient)
0197 {
0198     d->mXmlGuiClient = xmlGuiClient;
0199 
0200     auto const showHideQuicksearch = new KToggleAction(i18n("Show Quick Search Bar"), this);
0201     d->mXmlGuiClient->actionCollection()->setDefaultShortcut(showHideQuicksearch, Qt::CTRL | Qt::Key_H);
0202     showHideQuicksearch->setChecked(MessageListSettings::showQuickSearch());
0203 
0204     d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("show_quick_search"), showHideQuicksearch);
0205     connect(showHideQuicksearch, &KToggleAction::triggered, this, [this](bool state) {
0206         d->changeQuicksearchVisibility(state);
0207     });
0208 
0209     for (int i = 0; i < count(); ++i) {
0210         auto w = qobject_cast<Widget *>(widget(i));
0211         if (w) {
0212             w->setXmlGuiClient(d->mXmlGuiClient);
0213         }
0214     }
0215 
0216     // Setup "View->Message List" actions.
0217     if (xmlGuiClient) {
0218         if (d->mActionMenu) {
0219             d->mXmlGuiClient->actionCollection()->removeAction(d->mActionMenu);
0220         }
0221         d->mActionMenu = new KActionMenu(QIcon(), i18n("Message List"), this);
0222         d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("view_message_list"), d->mActionMenu);
0223         MessageList::Util::fillViewMenu(d->mActionMenu->menu(), this);
0224 
0225         d->mActionMenu->addSeparator();
0226 
0227         auto action = new QAction(i18n("Create New Tab"), this);
0228         d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("create_new_tab"), action);
0229         d->mXmlGuiClient->actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_O));
0230         connect(action, &QAction::triggered, this, [this]() {
0231             d->onNewTabClicked();
0232         });
0233         d->mActionMenu->addAction(action);
0234         d->mActionMenu->addSeparator();
0235 
0236         d->mMaxTabCreated = count();
0237         for (int i = 1; i < 10 && i <= count(); ++i) {
0238             d->addActivateTabAction(i);
0239         }
0240 
0241         QList<QKeySequence> nextShortcut;
0242         QList<QKeySequence> prevShortcut;
0243 
0244         QString nextIcon;
0245         QString prevIcon;
0246         if (QApplication::isRightToLeft()) {
0247             nextShortcut.append(KStandardShortcut::tabPrev());
0248             prevShortcut.append(KStandardShortcut::tabNext());
0249             nextIcon = QStringLiteral("go-previous-view");
0250             prevIcon = QStringLiteral("go-next-view");
0251         } else {
0252             nextShortcut.append(KStandardShortcut::tabNext());
0253             prevShortcut.append(KStandardShortcut::tabPrev());
0254             nextIcon = QStringLiteral("go-next-view");
0255             prevIcon = QStringLiteral("go-previous-view");
0256         }
0257 
0258         d->mActivateNextTabAction = new QAction(i18n("Activate Next Tab"), this);
0259         d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("activate_next_tab"), d->mActivateNextTabAction);
0260         d->mActivateNextTabAction->setEnabled(false);
0261         d->mActivateNextTabAction->setIcon(QIcon::fromTheme(nextIcon));
0262         d->mXmlGuiClient->actionCollection()->setDefaultShortcuts(d->mActivateNextTabAction, nextShortcut);
0263         connect(d->mActivateNextTabAction, &QAction::triggered, this, [this]() {
0264             d->activateNextTab();
0265         });
0266         d->mActionMenu->addAction(d->mActivateNextTabAction);
0267 
0268         d->mActivatePreviousTabAction = new QAction(i18n("Activate Previous Tab"), this);
0269         d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("activate_previous_tab"), d->mActivatePreviousTabAction);
0270         d->mXmlGuiClient->actionCollection()->setDefaultShortcuts(d->mActivatePreviousTabAction, prevShortcut);
0271         d->mActivatePreviousTabAction->setIcon(QIcon::fromTheme(prevIcon));
0272         d->mActivatePreviousTabAction->setEnabled(false);
0273         connect(d->mActivatePreviousTabAction, &QAction::triggered, this, [this]() {
0274             d->activatePreviousTab();
0275         });
0276         d->mActionMenu->addAction(d->mActivatePreviousTabAction);
0277 
0278         d->mActionMenu->addSeparator();
0279 
0280         d->mMoveTabLeftAction = new QAction(i18n("Move Tab Left"), this);
0281         d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("move_tab_left"), d->mMoveTabLeftAction);
0282         d->mMoveTabLeftAction->setEnabled(false);
0283         connect(d->mMoveTabLeftAction, &QAction::triggered, this, [this]() {
0284             d->moveTabLeft();
0285         });
0286         d->mActionMenu->addAction(d->mMoveTabLeftAction);
0287 
0288         d->mMoveTabRightAction = new QAction(i18n("Move Tab Right"), this);
0289         d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("move_tab_right"), d->mMoveTabRightAction);
0290         d->mMoveTabRightAction->setEnabled(false);
0291         connect(d->mMoveTabRightAction, &QAction::triggered, this, [this]() {
0292             d->moveTabRight();
0293         });
0294         d->mActionMenu->addAction(d->mMoveTabRightAction);
0295 
0296         d->mActionMenu->addSeparator();
0297 
0298         d->mCloseTabAction = new QAction(i18n("Close Tab"), this);
0299         d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("close_current_tab"), d->mCloseTabAction);
0300         d->mXmlGuiClient->actionCollection()->setDefaultShortcuts(d->mCloseTabAction,
0301                                                                   QList<QKeySequence>()
0302                                                                       << QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_W) << QKeySequence(Qt::CTRL | Qt::Key_W));
0303         connect(d->mCloseTabAction, &QAction::triggered, this, [this]() {
0304             d->onCloseTabClicked();
0305         });
0306         d->mActionMenu->addAction(d->mCloseTabAction);
0307         d->mCloseTabAction->setEnabled(false);
0308     }
0309 }
0310 
0311 bool Pane::selectNextMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter,
0312                                  MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour,
0313                                  bool centerItem,
0314                                  bool loop)
0315 {
0316     auto w = static_cast<Widget *>(currentWidget());
0317 
0318     if (w) {
0319         if (w->view()->model()->isLoading()) {
0320             return true;
0321         }
0322 
0323         return w->selectNextMessageItem(messageTypeFilter, existingSelectionBehaviour, centerItem, loop);
0324     } else {
0325         return false;
0326     }
0327 }
0328 
0329 bool Pane::selectPreviousMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter,
0330                                      MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour,
0331                                      bool centerItem,
0332                                      bool loop)
0333 {
0334     auto w = static_cast<Widget *>(currentWidget());
0335 
0336     if (w) {
0337         if (w->view()->model()->isLoading()) {
0338             return true;
0339         }
0340 
0341         return w->selectPreviousMessageItem(messageTypeFilter, existingSelectionBehaviour, centerItem, loop);
0342     } else {
0343         return false;
0344     }
0345 }
0346 
0347 bool Pane::focusNextMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop)
0348 {
0349     auto w = static_cast<Widget *>(currentWidget());
0350 
0351     if (w) {
0352         if (w->view()->model()->isLoading()) {
0353             return true;
0354         }
0355 
0356         return w->focusNextMessageItem(messageTypeFilter, centerItem, loop);
0357     } else {
0358         return false;
0359     }
0360 }
0361 
0362 bool Pane::focusPreviousMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop)
0363 {
0364     auto w = static_cast<Widget *>(currentWidget());
0365 
0366     if (w) {
0367         if (w->view()->model()->isLoading()) {
0368             return true;
0369         }
0370 
0371         return w->focusPreviousMessageItem(messageTypeFilter, centerItem, loop);
0372     } else {
0373         return false;
0374     }
0375 }
0376 
0377 void Pane::selectFocusedMessageItem(bool centerItem)
0378 {
0379     auto w = static_cast<Widget *>(currentWidget());
0380 
0381     if (w) {
0382         if (w->view()->model()->isLoading()) {
0383             return;
0384         }
0385 
0386         w->selectFocusedMessageItem(centerItem);
0387     }
0388 }
0389 
0390 bool Pane::selectFirstMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem)
0391 {
0392     auto w = static_cast<Widget *>(currentWidget());
0393 
0394     if (w) {
0395         if (w->view()->model()->isLoading()) {
0396             return true;
0397         }
0398 
0399         return w->selectFirstMessageItem(messageTypeFilter, centerItem);
0400     } else {
0401         return false;
0402     }
0403 }
0404 
0405 bool Pane::selectLastMessageItem(Core::MessageTypeFilter messageTypeFilter, bool centerItem)
0406 {
0407     auto w = static_cast<Widget *>(currentWidget());
0408 
0409     if (w) {
0410         if (w->view()->model()->isLoading()) {
0411             return true;
0412         }
0413 
0414         return w->selectLastMessageItem(messageTypeFilter, centerItem);
0415     } else {
0416         return false;
0417     }
0418 }
0419 
0420 void Pane::selectAll()
0421 {
0422     auto w = static_cast<Widget *>(currentWidget());
0423 
0424     if (w) {
0425         if (w->view()->model()->isLoading()) {
0426             return;
0427         }
0428 
0429         w->selectAll();
0430     }
0431 }
0432 
0433 void Pane::setCurrentThreadExpanded(bool expand)
0434 {
0435     auto w = static_cast<Widget *>(currentWidget());
0436 
0437     if (w) {
0438         if (w->view()->model()->isLoading()) {
0439             return;
0440         }
0441 
0442         w->setCurrentThreadExpanded(expand);
0443     }
0444 }
0445 
0446 void Pane::setAllThreadsExpanded(bool expand)
0447 {
0448     auto w = static_cast<Widget *>(currentWidget());
0449 
0450     if (w) {
0451         if (w->view()->model()->isLoading()) {
0452             return;
0453         }
0454 
0455         w->setAllThreadsExpanded(expand);
0456     }
0457 }
0458 
0459 void Pane::setAllGroupsExpanded(bool expand)
0460 {
0461     auto w = static_cast<Widget *>(currentWidget());
0462 
0463     if (w) {
0464         if (w->view()->model()->isLoading()) {
0465             return;
0466         }
0467 
0468         w->setAllGroupsExpanded(expand);
0469     }
0470 }
0471 
0472 void Pane::focusQuickSearch(const QString &selectedText)
0473 {
0474     auto w = static_cast<Widget *>(currentWidget());
0475 
0476     if (w) {
0477         w->focusQuickSearch(selectedText);
0478     }
0479 }
0480 
0481 void Pane::setQuickSearchClickMessage(const QString &msg)
0482 {
0483     d->mQuickSearchPlaceHolderMessage = msg;
0484     for (int i = 0; i < count(); ++i) {
0485         auto w = qobject_cast<Widget *>(widget(i));
0486         if (w) {
0487             w->setQuickSearchClickMessage(d->mQuickSearchPlaceHolderMessage);
0488         }
0489     }
0490 }
0491 
0492 void Pane::PanePrivate::setCurrentFolder(const QModelIndex &etmIndex)
0493 {
0494     if (mPreferEmptyTab) {
0495         q->createNewTab();
0496     }
0497 
0498     auto w = static_cast<Widget *>(q->currentWidget());
0499     QItemSelectionModel *s = mWidgetSelectionHash[w];
0500 
0501     w->saveCurrentSelection();
0502 
0503     // Deselect old before we select new - so that the messagelist can clear first.
0504     s->clear();
0505     if (s->selection().isEmpty()) {
0506         w->view()->model()->setPreSelectionMode(mPreSelectionMode);
0507     }
0508     Q_ASSERT(s->model() == etmIndex.model());
0509     s->select(etmIndex, QItemSelectionModel::Select);
0510 
0511     QString label;
0512     QIcon icon;
0513     QString toolTip;
0514     for (const QModelIndex &index : s->selectedRows()) {
0515         label += index.data(Qt::DisplayRole).toString() + QLatin1StringView(", ");
0516     }
0517     label.chop(2);
0518 
0519     if (label.isEmpty()) {
0520         label = i18nc("@title:tab Empty messagelist", "Empty");
0521         icon = QIcon();
0522     } else if (s->selectedRows().size() == 1) {
0523         icon = s->selectedRows().first().data(Qt::DecorationRole).value<QIcon>();
0524         QModelIndex idx = s->selectedRows().first().parent();
0525         toolTip = label;
0526         while (idx != QModelIndex()) {
0527             toolTip = idx.data().toString() + QLatin1Char('/') + toolTip;
0528             idx = idx.parent();
0529         }
0530     } else {
0531         icon = QIcon::fromTheme(QStringLiteral("folder"));
0532     }
0533 
0534     const int index = q->indexOf(w);
0535     q->setTabText(index, label);
0536     q->setTabIcon(index, icon);
0537     q->setTabToolTip(index, toolTip);
0538     if (mPreferEmptyTab) {
0539         mSelectionModel->select(mapSelectionFromSource(s->selection()), QItemSelectionModel::ClearAndSelect);
0540     }
0541     mPreferEmptyTab = false;
0542 }
0543 
0544 void Pane::PanePrivate::activateTab()
0545 {
0546     q->tabBar()->setCurrentIndex(QStringView(q->sender()->objectName()).right(2).toInt() - 1);
0547 }
0548 
0549 void Pane::PanePrivate::moveTabRight()
0550 {
0551     const int numberOfTab = q->tabBar()->count();
0552     if (numberOfTab == 1) {
0553         return;
0554     }
0555     if (QApplication::isRightToLeft()) {
0556         moveTabForward();
0557     } else {
0558         moveTabBackward();
0559     }
0560 }
0561 
0562 void Pane::PanePrivate::moveTabLeft()
0563 {
0564     const int numberOfTab = q->tabBar()->count();
0565     if (numberOfTab == 1) {
0566         return;
0567     }
0568     if (QApplication::isRightToLeft()) {
0569         moveTabBackward();
0570     } else {
0571         moveTabForward();
0572     }
0573 }
0574 
0575 void Pane::PanePrivate::moveTabForward()
0576 {
0577     const int currentIndex = q->tabBar()->currentIndex();
0578     if (currentIndex == q->tabBar()->count() - 1) {
0579         return;
0580     }
0581     q->tabBar()->moveTab(currentIndex, currentIndex + 1);
0582 }
0583 
0584 void Pane::PanePrivate::moveTabBackward()
0585 {
0586     const int currentIndex = q->tabBar()->currentIndex();
0587     if (currentIndex == 0) {
0588         return;
0589     }
0590     q->tabBar()->moveTab(currentIndex, currentIndex - 1);
0591 }
0592 
0593 void Pane::PanePrivate::activateNextTab()
0594 {
0595     const int numberOfTab = q->tabBar()->count();
0596     if (numberOfTab == 1) {
0597         return;
0598     }
0599 
0600     int indexTab = (q->tabBar()->currentIndex() + 1);
0601 
0602     if (indexTab == numberOfTab) {
0603         indexTab = 0;
0604     }
0605 
0606     q->tabBar()->setCurrentIndex(indexTab);
0607 }
0608 
0609 void Pane::PanePrivate::activatePreviousTab()
0610 {
0611     const int numberOfTab = q->tabBar()->count();
0612     if (numberOfTab == 1) {
0613         return;
0614     }
0615 
0616     int indexTab = (q->tabBar()->currentIndex() - 1);
0617 
0618     if (indexTab == -1) {
0619         indexTab = numberOfTab - 1;
0620     }
0621 
0622     q->tabBar()->setCurrentIndex(indexTab);
0623 }
0624 
0625 void Pane::PanePrivate::onNewTabClicked()
0626 {
0627     q->createNewTab();
0628 }
0629 
0630 void Pane::PanePrivate::onCloseTabClicked()
0631 {
0632     closeTab(q->currentWidget());
0633 }
0634 
0635 void Pane::PanePrivate::closeTab(QWidget *w)
0636 {
0637     if (!w || (q->count() < 2)) {
0638         return;
0639     }
0640 
0641     auto wWidget = qobject_cast<Widget *>(w);
0642     if (wWidget) {
0643         const bool isLocked = wWidget->isLocked();
0644         if (isLocked) {
0645             return;
0646         }
0647         wWidget->saveCurrentSelection();
0648     }
0649 
0650     delete w;
0651     updateTabControls();
0652 }
0653 
0654 void Pane::PanePrivate::changeQuicksearchVisibility(bool show)
0655 {
0656     for (int i = 0; i < q->count(); ++i) {
0657         auto w = qobject_cast<Widget *>(q->widget(i));
0658         if (w) {
0659             w->changeQuicksearchVisibility(show);
0660         }
0661     }
0662 }
0663 
0664 bool Pane::eventFilter(QObject *object, QEvent *event)
0665 {
0666     if (event->type() == QEvent::MouseButtonPress) {
0667         auto const mouseEvent = static_cast<QMouseEvent *>(event);
0668         if (mouseEvent->button() == Qt::MiddleButton) {
0669             return true;
0670         }
0671     }
0672     return QTabWidget::eventFilter(object, event);
0673 }
0674 
0675 void Pane::PanePrivate::onCurrentTabChanged()
0676 {
0677     Q_EMIT q->currentTabChanged();
0678 
0679     auto w = static_cast<Widget *>(q->currentWidget());
0680     mCloseTabButton->setEnabled(!w->isLocked());
0681 
0682     QItemSelectionModel *s = mWidgetSelectionHash[w];
0683 
0684     mSelectionModel->select(mapSelectionFromSource(s->selection()), QItemSelectionModel::ClearAndSelect);
0685 }
0686 
0687 void Pane::PanePrivate::onTabContextMenuRequest(const QPoint &pos)
0688 {
0689     QTabBar *bar = q->tabBar();
0690     if (q->count() <= 1) {
0691         return;
0692     }
0693 
0694     const int indexBar = bar->tabAt(bar->mapFrom(q, pos));
0695     if (indexBar == -1) {
0696         return;
0697     }
0698 
0699     auto w = qobject_cast<Widget *>(q->widget(indexBar));
0700     if (!w) {
0701         return;
0702     }
0703 
0704     QMenu menu(q);
0705 
0706     QAction *closeTabAction = nullptr;
0707     if (!w->isLocked()) {
0708         closeTabAction = menu.addAction(i18nc("@action:inmenu", "Close Tab"));
0709         closeTabAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
0710     }
0711 
0712     QAction *allOtherAction = menu.addAction(i18nc("@action:inmenu", "Close All Other Tabs"));
0713     allOtherAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-close-other")));
0714 
0715     menu.addSeparator();
0716 
0717     QAction *lockTabAction = menu.addAction(w->isLocked() ? i18nc("@action:inmenu", "Unlock Tab") : i18nc("@action:inmenu", "Lock Tab"));
0718     lockTabAction->setIcon(w->isLocked() ? QIcon::fromTheme(QStringLiteral("lock")) : QIcon::fromTheme(QStringLiteral("unlock")));
0719 
0720     QAction *action = menu.exec(q->mapToGlobal(pos));
0721 
0722     if (action == allOtherAction) { // Close all other tabs
0723         QList<Widget *> widgets;
0724         const int index = q->indexOf(w);
0725 
0726         for (int i = 0; i < q->count(); ++i) {
0727             if (i == index) {
0728                 continue; // Skip the current one
0729             }
0730 
0731             auto other = qobject_cast<Widget *>(q->widget(i));
0732             const bool isLocked = other->isLocked();
0733             if (!isLocked && other) {
0734                 widgets << other;
0735             }
0736         }
0737 
0738         for (Widget *other : std::as_const(widgets)) {
0739             other->saveCurrentSelection();
0740             delete other;
0741         }
0742 
0743         updateTabControls();
0744     } else if (closeTabAction && (action == closeTabAction)) {
0745         closeTab(q->widget(indexBar));
0746     } else if (action == lockTabAction) {
0747         auto tab = qobject_cast<Widget *>(q->widget(indexBar));
0748         const bool isLocked = !tab->isLocked();
0749         tab->setLockTab(isLocked);
0750         q->setTabIcon(indexBar, isLocked ? QIcon::fromTheme(QStringLiteral("lock")) : QIcon::fromTheme(QStringLiteral("unlock")));
0751         q->tabBar()->tabButton(indexBar, QTabBar::RightSide)->setEnabled(!isLocked);
0752         if (q->tabBar()->currentIndex() == indexBar) {
0753             mCloseTabButton->setEnabled(!isLocked);
0754         }
0755     }
0756 }
0757 
0758 MessageList::StorageModel *Pane::createStorageModel(QAbstractItemModel *model, QItemSelectionModel *selectionModel, QObject *parent)
0759 {
0760     return new MessageList::StorageModel(model, selectionModel, parent);
0761 }
0762 
0763 Akonadi::Collection Pane::currentFolder() const
0764 {
0765     auto w = static_cast<Widget *>(currentWidget());
0766     if (w) {
0767         return w->currentCollection();
0768     }
0769     return {};
0770 }
0771 
0772 void Pane::setCurrentFolder(const Akonadi::Collection &collection,
0773                             const QModelIndex &etmIndex,
0774                             bool,
0775                             Core::PreSelectionMode preSelectionMode,
0776                             const QString &overrideLabel)
0777 {
0778     auto w = static_cast<Widget *>(currentWidget());
0779     if (!w->isLocked()) {
0780         d->setCurrentFolder(etmIndex);
0781         d->mPreSelectionMode = preSelectionMode;
0782         if (w) {
0783             w->setCurrentFolder(collection);
0784             QItemSelectionModel *s = d->mWidgetSelectionHash[w];
0785             MessageList::StorageModel *m = createStorageModel(d->mModel, s, w);
0786             w->setStorageModel(m, preSelectionMode);
0787             if (!overrideLabel.isEmpty()) {
0788                 const int index = indexOf(w);
0789                 setTabText(index, overrideLabel);
0790             }
0791         }
0792     }
0793 }
0794 
0795 void Pane::updateTabIconText(const Akonadi::Collection &collection, const QString &label, const QIcon &icon)
0796 {
0797     for (int i = 0; i < count(); ++i) {
0798         auto w = qobject_cast<Widget *>(widget(i));
0799         if (w && (w->currentCollection() == collection)) {
0800             const int index = indexOf(w);
0801             setTabText(index, label);
0802             setTabIcon(index, icon);
0803         }
0804     }
0805 }
0806 
0807 QItemSelectionModel *Pane::createNewTab()
0808 {
0809     auto w = new Widget(this);
0810     w->setXmlGuiClient(d->mXmlGuiClient);
0811 
0812     addTab(w, i18nc("@title:tab Empty messagelist", "Empty"));
0813     if (!d->mQuickSearchPlaceHolderMessage.isEmpty()) {
0814         w->setQuickSearchClickMessage(d->mQuickSearchPlaceHolderMessage);
0815     }
0816     if (d->mXmlGuiClient && count() < 10) {
0817         if (d->mMaxTabCreated < count()) {
0818             d->mMaxTabCreated = count();
0819             d->addActivateTabAction(d->mMaxTabCreated);
0820         }
0821     }
0822 
0823     auto s = new QItemSelectionModel(d->mModel, w);
0824     MessageList::StorageModel *m = createStorageModel(d->mModel, s, w);
0825     w->setStorageModel(m);
0826 
0827     d->mWidgetSelectionHash[w] = s;
0828 
0829     connect(w, &Widget::messageSelected, this, &Pane::messageSelected);
0830     connect(w, &Widget::messageActivated, this, &Pane::messageActivated);
0831     connect(w, &Widget::selectionChanged, this, &Pane::selectionChanged);
0832     connect(w, &Widget::messageStatusChangeRequest, this, &Pane::messageStatusChangeRequest);
0833 
0834     connect(w, &Core::Widget::statusMessage, this, &Pane::statusMessage);
0835 
0836     connect(w, &Core::Widget::forceLostFocus, this, &Pane::forceLostFocus);
0837     connect(w, &Core::Widget::unlockTabRequested, this, [this, w]() {
0838         for (int i = 0; i < count(); ++i) {
0839             if (w == qobject_cast<Widget *>(widget(i))) {
0840                 setTabIcon(i, QIcon::fromTheme(QStringLiteral("unlock")));
0841             }
0842         }
0843     });
0844 
0845     d->updateTabControls();
0846     setCurrentWidget(w);
0847     return s;
0848 }
0849 
0850 QItemSelection Pane::PanePrivate::mapSelectionFromSource(const QItemSelection &selection) const
0851 {
0852     QItemSelection result = selection;
0853 
0854     using Iterator = QList<const QAbstractProxyModel *>::ConstIterator;
0855 
0856     for (Iterator it = mProxyStack.end() - 1; it != mProxyStack.begin(); --it) {
0857         result = (*it)->mapSelectionFromSource(result);
0858     }
0859     result = mProxyStack.first()->mapSelectionFromSource(result);
0860 
0861     return result;
0862 }
0863 
0864 void Pane::PanePrivate::updateTabControls()
0865 {
0866     const bool enableAction = (q->count() > 1);
0867     if (enableAction) {
0868         q->setCornerWidget(mCloseTabButton, Qt::TopRightCorner);
0869         mCloseTabButton->setVisible(true);
0870     } else {
0871         q->setCornerWidget(nullptr, Qt::TopRightCorner);
0872     }
0873     if (mCloseTabAction) {
0874         mCloseTabAction->setEnabled(enableAction);
0875     }
0876     if (mActivatePreviousTabAction) {
0877         mActivatePreviousTabAction->setEnabled(enableAction);
0878     }
0879     if (mActivateNextTabAction) {
0880         mActivateNextTabAction->setEnabled(enableAction);
0881     }
0882     if (mMoveTabRightAction) {
0883         mMoveTabRightAction->setEnabled(enableAction);
0884     }
0885     if (mMoveTabLeftAction) {
0886         mMoveTabLeftAction->setEnabled(enableAction);
0887     }
0888 
0889     q->tabBar()->setVisible(enableAction);
0890     if (enableAction) {
0891         q->setCornerWidget(mNewTabButton, Qt::TopLeftCorner);
0892         mNewTabButton->setVisible(true);
0893     } else {
0894         q->setCornerWidget(nullptr, Qt::TopLeftCorner);
0895     }
0896 
0897     q->setTabsClosable(true);
0898     const int numberOfTab(q->count());
0899     if (numberOfTab == 1) {
0900         q->tabBar()->tabButton(0, QTabBar::RightSide)->setEnabled(false);
0901     } else if (numberOfTab > 1) {
0902         q->tabBar()->tabButton(0, QTabBar::RightSide)->setEnabled(true);
0903     }
0904 }
0905 
0906 Akonadi::Item Pane::currentItem() const
0907 {
0908     auto w = static_cast<Widget *>(currentWidget());
0909 
0910     if (!w) {
0911         return {};
0912     }
0913 
0914     return w->currentItem();
0915 }
0916 
0917 KMime::Message::Ptr Pane::currentMessage() const
0918 {
0919     auto w = static_cast<Widget *>(currentWidget());
0920 
0921     if (!w) {
0922         return {};
0923     }
0924 
0925     return w->currentMessage();
0926 }
0927 
0928 QList<KMime::Message::Ptr> Pane::selectionAsMessageList(bool includeCollapsedChildren) const
0929 {
0930     auto w = static_cast<Widget *>(currentWidget());
0931     if (!w) {
0932         return {};
0933     }
0934     return w->selectionAsMessageList(includeCollapsedChildren);
0935 }
0936 
0937 Akonadi::Item::List Pane::selectionAsMessageItemList(bool includeCollapsedChildren) const
0938 {
0939     auto w = static_cast<Widget *>(currentWidget());
0940     if (!w) {
0941         return {};
0942     }
0943     return w->selectionAsMessageItemList(includeCollapsedChildren);
0944 }
0945 
0946 QList<Akonadi::Item::Id> Pane::selectionAsListMessageId(bool includeCollapsedChildren) const
0947 {
0948     auto w = static_cast<Widget *>(currentWidget());
0949     if (!w) {
0950         return {};
0951     }
0952     return w->selectionAsListMessageId(includeCollapsedChildren);
0953 }
0954 
0955 QList<qlonglong> Pane::selectionAsMessageItemListId(bool includeCollapsedChildren) const
0956 {
0957     auto w = static_cast<Widget *>(currentWidget());
0958     if (!w) {
0959         return {};
0960     }
0961     return w->selectionAsMessageItemListId(includeCollapsedChildren);
0962 }
0963 
0964 Akonadi::Item::List Pane::currentThreadAsMessageList() const
0965 {
0966     auto w = static_cast<Widget *>(currentWidget());
0967     if (!w) {
0968         return {};
0969     }
0970     return w->currentThreadAsMessageList();
0971 }
0972 
0973 Akonadi::Item::List Pane::itemListFromPersistentSet(MessageList::Core::MessageItemSetReference ref)
0974 {
0975     auto w = static_cast<Widget *>(currentWidget());
0976     if (w) {
0977         return w->itemListFromPersistentSet(ref);
0978     }
0979     return {};
0980 }
0981 
0982 void Pane::deletePersistentSet(MessageList::Core::MessageItemSetReference ref)
0983 {
0984     auto w = static_cast<Widget *>(currentWidget());
0985     if (w) {
0986         w->deletePersistentSet(ref);
0987     }
0988 }
0989 
0990 void Pane::markMessageItemsAsAboutToBeRemoved(MessageList::Core::MessageItemSetReference ref, bool bMark)
0991 {
0992     auto w = static_cast<Widget *>(currentWidget());
0993     if (w) {
0994         w->markMessageItemsAsAboutToBeRemoved(ref, bMark);
0995     }
0996 }
0997 
0998 QList<Akonadi::MessageStatus> Pane::currentFilterStatus() const
0999 {
1000     auto w = static_cast<Widget *>(currentWidget());
1001     if (!w) {
1002         return {};
1003     }
1004     return w->currentFilterStatus();
1005 }
1006 
1007 Core::QuickSearchLine::SearchOptions Pane::currentOptions() const
1008 {
1009     auto w = static_cast<Widget *>(currentWidget());
1010     if (!w) {
1011         return Core::QuickSearchLine::SearchEveryWhere;
1012     }
1013     return w->currentOptions();
1014 }
1015 
1016 QString Pane::currentFilterSearchString() const
1017 {
1018     auto w = static_cast<Widget *>(currentWidget());
1019     if (w) {
1020         return w->currentFilterSearchString();
1021     }
1022     return {};
1023 }
1024 
1025 bool Pane::isThreaded() const
1026 {
1027     auto w = static_cast<Widget *>(currentWidget());
1028     if (w) {
1029         return w->isThreaded();
1030     }
1031     return false;
1032 }
1033 
1034 bool Pane::selectionEmpty() const
1035 {
1036     auto w = static_cast<Widget *>(currentWidget());
1037     if (w) {
1038         return w->selectionEmpty();
1039     }
1040     return false;
1041 }
1042 
1043 bool Pane::getSelectionStats(Akonadi::Item::List &selectedItems,
1044                              Akonadi::Item::List &selectedVisibleItems,
1045                              bool *allSelectedBelongToSameThread,
1046                              bool includeCollapsedChildren) const
1047 {
1048     auto w = static_cast<Widget *>(currentWidget());
1049     if (!w) {
1050         return false;
1051     }
1052 
1053     return w->getSelectionStats(selectedItems, selectedVisibleItems, allSelectedBelongToSameThread, includeCollapsedChildren);
1054 }
1055 
1056 MessageList::Core::MessageItemSetReference Pane::selectionAsPersistentSet(bool includeCollapsedChildren) const
1057 {
1058     auto w = static_cast<Widget *>(currentWidget());
1059     if (w) {
1060         return w->selectionAsPersistentSet(includeCollapsedChildren);
1061     }
1062     return -1;
1063 }
1064 
1065 MessageList::Core::MessageItemSetReference Pane::currentThreadAsPersistentSet() const
1066 {
1067     auto w = static_cast<Widget *>(currentWidget());
1068     if (w) {
1069         return w->currentThreadAsPersistentSet();
1070     }
1071     return -1;
1072 }
1073 
1074 void Pane::focusView()
1075 {
1076     auto w = static_cast<Widget *>(currentWidget());
1077     if (w) {
1078         QWidget *view = w->view();
1079         if (view) {
1080             view->setFocus();
1081         }
1082     }
1083 }
1084 
1085 void Pane::reloadGlobalConfiguration()
1086 {
1087     d->updateTabControls();
1088 }
1089 
1090 QItemSelectionModel *Pane::currentItemSelectionModel()
1091 {
1092     auto w = static_cast<Widget *>(currentWidget());
1093     if (w) {
1094         return w->view()->selectionModel();
1095     }
1096     return nullptr;
1097 }
1098 
1099 void Pane::resetModelStorage()
1100 {
1101     auto w = static_cast<Widget *>(currentWidget());
1102     if (w) {
1103         auto m = static_cast<MessageList::StorageModel *>(w->storageModel());
1104         if (m) {
1105             m->resetModelStorage();
1106         }
1107     }
1108 }
1109 
1110 void Pane::setPreferEmptyTab(bool emptyTab)
1111 {
1112     d->mPreferEmptyTab = emptyTab;
1113 }
1114 
1115 void Pane::saveCurrentSelection()
1116 {
1117     for (int i = 0; i < count(); ++i) {
1118         auto w = qobject_cast<Widget *>(widget(i));
1119         if (w) {
1120             w->saveCurrentSelection();
1121         }
1122     }
1123 }
1124 
1125 void Pane::updateTagComboBox()
1126 {
1127     for (int i = 0; i < count(); ++i) {
1128         auto w = qobject_cast<Widget *>(widget(i));
1129         if (w) {
1130             w->populateStatusFilterCombo();
1131         }
1132     }
1133 }
1134 
1135 void Pane::writeConfig(bool restoreSession)
1136 {
1137     KConfigGroup conf(MessageList::MessageListSettings::self()->config(), QStringLiteral("MessageListPane"));
1138 
1139     // Delete list before
1140     const QStringList list = MessageList::MessageListSettings::self()->config()->groupList().filter(QRegularExpression(QStringLiteral("MessageListTab\\d+")));
1141     for (const QString &group : list) {
1142         MessageList::MessageListSettings::self()->config()->deleteGroup(group);
1143     }
1144 
1145     if (restoreSession) {
1146         conf.writeEntry(QStringLiteral("currentIndex"), currentIndex());
1147 
1148         int elementTab = 0;
1149         for (int i = 0; i < count(); ++i) {
1150             auto w = qobject_cast<Widget *>(widget(i));
1151             if (w && w->currentCollection().isValid()) {
1152                 KConfigGroup grp(MessageList::MessageListSettings::self()->config(), QStringLiteral("MessageListTab%1").arg(elementTab));
1153                 grp.writeEntry(QStringLiteral("collectionId"), w->currentCollection().id());
1154                 grp.writeEntry(QStringLiteral("HeaderState"), w->view()->header()->saveState());
1155                 elementTab++;
1156             }
1157         }
1158         conf.writeEntry(QStringLiteral("tabNumber"), elementTab);
1159     }
1160     conf.sync();
1161 }
1162 
1163 void Pane::readConfig(bool restoreSession)
1164 {
1165     if (MessageList::MessageListSettings::self()->config()->hasGroup(QStringLiteral("MessageListPane"))) {
1166         KConfigGroup conf(MessageList::MessageListSettings::self()->config(), QStringLiteral("MessageListPane"));
1167         const int numberOfTab = conf.readEntry(QStringLiteral("tabNumber"), 0);
1168         if (numberOfTab == 0) {
1169             createNewTab();
1170         } else {
1171             for (int i = 0; i < numberOfTab; ++i) {
1172                 createNewTab();
1173                 restoreHeaderSettings(i, restoreSession);
1174             }
1175             setCurrentIndex(conf.readEntry(QStringLiteral("currentIndex"), 0));
1176         }
1177     } else {
1178         createNewTab();
1179         restoreHeaderSettings(0, false);
1180     }
1181 }
1182 
1183 void Pane::restoreHeaderSettings(int index, bool restoreSession)
1184 {
1185     KConfigGroup grp(MessageList::MessageListSettings::self()->config(), QStringLiteral("MessageListTab%1").arg(index));
1186     if (grp.exists()) {
1187         auto w = qobject_cast<Widget *>(widget(index));
1188         if (w) {
1189             w->view()->header()->restoreState(grp.readEntry(QStringLiteral("HeaderState"), QByteArray()));
1190         }
1191         if (restoreSession) {
1192             const Akonadi::Collection::Id id = grp.readEntry(QStringLiteral("collectionId"), -1);
1193             if (id != -1) {
1194                 auto saver = new Akonadi::ETMViewStateSaver;
1195                 saver->setSelectionModel(d->mSelectionModel);
1196                 saver->selectCollections(Akonadi::Collection::List() << Akonadi::Collection(id));
1197                 saver->restoreCurrentItem(QString::fromLatin1("c%1").arg(id));
1198                 saver->restoreState(grp);
1199             }
1200         }
1201     }
1202 }
1203 
1204 bool Pane::searchEditHasFocus() const
1205 {
1206     auto w = static_cast<Widget *>(currentWidget());
1207     if (w) {
1208         return w->searchEditHasFocus();
1209     }
1210     return false;
1211 }
1212 
1213 void Pane::sortOrderMenuAboutToShow()
1214 {
1215     auto menu = qobject_cast<QMenu *>(sender());
1216     if (!menu) {
1217         return;
1218     }
1219     const Widget *const w = static_cast<Widget *>(currentWidget());
1220     w->view()->sortOrderMenuAboutToShow(menu);
1221 }
1222 
1223 void Pane::aggregationMenuAboutToShow()
1224 {
1225     auto menu = qobject_cast<QMenu *>(sender());
1226     if (!menu) {
1227         return;
1228     }
1229     const Widget *const w = static_cast<Widget *>(currentWidget());
1230     w->view()->aggregationMenuAboutToShow(menu);
1231 }
1232 
1233 void Pane::themeMenuAboutToShow()
1234 {
1235     auto menu = qobject_cast<QMenu *>(sender());
1236     if (!menu) {
1237         return;
1238     }
1239     const Widget *const w = static_cast<Widget *>(currentWidget());
1240     w->view()->themeMenuAboutToShow(menu);
1241 }
1242 
1243 void Pane::populateStatusFilterCombo()
1244 {
1245     for (int i = 0; i < count(); ++i) {
1246         auto w = qobject_cast<Widget *>(widget(i));
1247         if (w) {
1248             w->populateStatusFilterCombo();
1249         }
1250     }
1251 }
1252 
1253 #include "moc_pane.cpp"