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"