File indexing completed on 2024-04-28 05:50:55

0001 /*
0002     SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // Own
0008 #include "widgets/ViewContainer.h"
0009 #include "config-konsole.h"
0010 
0011 // Qt
0012 #include <QFile>
0013 #include <QKeyEvent>
0014 #include <QMenu>
0015 #include <QTabBar>
0016 
0017 // KDE
0018 #include <KActionCollection>
0019 #include <KColorScheme>
0020 #include <KColorUtils>
0021 #include <KLocalizedString>
0022 
0023 // Konsole
0024 #include "DetachableTabBar.h"
0025 #include "KonsoleSettings.h"
0026 #include "ViewProperties.h"
0027 #include "profile/ProfileList.h"
0028 #include "session/SessionController.h"
0029 #include "session/SessionManager.h"
0030 #include "terminalDisplay/TerminalDisplay.h"
0031 #include "widgets/IncrementalSearchBar.h"
0032 #include "widgets/ViewSplitter.h"
0033 
0034 // TODO Perhaps move everything which is Konsole-specific into different files
0035 
0036 using namespace Konsole;
0037 
0038 TabbedViewContainer::TabbedViewContainer(ViewManager *connectedViewManager, QWidget *parent)
0039     : QTabWidget(parent)
0040     , _connectedViewManager(connectedViewManager)
0041     , _newTabButton(new QToolButton(this))
0042     , _closeTabButton(new QToolButton(this))
0043     , _contextMenuTabIndex(-1)
0044     , _newTabBehavior(PutNewTabAtTheEnd)
0045 {
0046     setAcceptDrops(true);
0047 
0048     auto tabBarWidget = new DetachableTabBar();
0049     setTabBar(tabBarWidget);
0050     setDocumentMode(true);
0051     setMovable(true);
0052     connect(tabBarWidget, &DetachableTabBar::moveTabToWindow, this, &TabbedViewContainer::moveTabToWindow);
0053     tabBar()->setContextMenuPolicy(Qt::CustomContextMenu);
0054     _newTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-new")));
0055     _newTabButton->setAutoRaise(true);
0056     _newTabButton->setToolTip(i18nc("@info:tooltip", "Open a new tab"));
0057     connect(_newTabButton, &QToolButton::clicked, this, &TabbedViewContainer::newViewRequest);
0058 
0059     _closeTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
0060     _closeTabButton->setAutoRaise(true);
0061     _closeTabButton->setToolTip(i18nc("@info:tooltip", "Close this tab"));
0062     connect(_closeTabButton, &QToolButton::clicked, this, [this] {
0063         closeCurrentTab();
0064     });
0065 
0066     connect(tabBar(), &QTabBar::tabBarDoubleClicked, this, &Konsole::TabbedViewContainer::tabDoubleClicked);
0067     connect(tabBar(), &QTabBar::customContextMenuRequested, this, &Konsole::TabbedViewContainer::openTabContextMenu);
0068     connect(tabBarWidget, &DetachableTabBar::detachTab, this, [this](int idx) {
0069         Q_EMIT detachTab(idx);
0070     });
0071     connect(tabBarWidget, &DetachableTabBar::closeTab, this, &TabbedViewContainer::closeTerminalTab);
0072     connect(tabBarWidget, &DetachableTabBar::newTabRequest, this, [this] {
0073         Q_EMIT newViewRequest();
0074     });
0075     connect(this, &TabbedViewContainer::currentChanged, this, &TabbedViewContainer::currentTabChanged);
0076 
0077     connect(this, &TabbedViewContainer::setColor, tabBarWidget, &DetachableTabBar::setColor);
0078     connect(this, &TabbedViewContainer::removeColor, tabBarWidget, &DetachableTabBar::removeColor);
0079 
0080     // The context menu of tab bar
0081     _contextPopupMenu = new QMenu(tabBar());
0082     connect(_contextPopupMenu, &QMenu::aboutToHide, this, [this]() {
0083         // Remove the read-only action when the popup closes
0084         for (auto &action : _contextPopupMenu->actions()) {
0085             if (action->objectName() == QStringLiteral("view-readonly")) {
0086                 _contextPopupMenu->removeAction(action);
0087                 break;
0088             }
0089         }
0090     });
0091 
0092     connect(tabBar(), &QTabBar::tabCloseRequested, this, &TabbedViewContainer::closeTerminalTab);
0093 
0094     auto detachAction = _contextPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("tab-detach")), i18nc("@action:inmenu", "&Detach Tab"), this, [this] {
0095         Q_EMIT detachTab(_contextMenuTabIndex);
0096     });
0097     detachAction->setObjectName(QStringLiteral("tab-detach"));
0098 
0099     auto editAction =
0100         _contextPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18nc("@action:inmenu", "&Configure or Rename Tab..."), this, [this] {
0101             renameTab(_contextMenuTabIndex);
0102         });
0103     editAction->setObjectName(QStringLiteral("edit-rename"));
0104 
0105     auto closeAction = _contextPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("tab-close")), i18nc("@action:inmenu", "Close Tab"), this, [this] {
0106         closeTerminalTab(_contextMenuTabIndex);
0107     });
0108     closeAction->setObjectName(QStringLiteral("tab-close"));
0109 
0110     auto profileMenu = new QMenu(this);
0111     auto profileList = new ProfileList(false, profileMenu);
0112     profileList->syncWidgetActions(profileMenu, true);
0113     connect(profileList, &Konsole::ProfileList::profileSelected, this, &TabbedViewContainer::newViewWithProfileRequest);
0114     _newTabButton->setMenu(profileMenu);
0115 
0116     konsoleConfigChanged();
0117     connect(KonsoleSettings::self(), &KonsoleSettings::configChanged, this, &TabbedViewContainer::konsoleConfigChanged);
0118 }
0119 
0120 TabbedViewContainer::~TabbedViewContainer()
0121 {
0122     for (int i = 0, end = count(); i < end; i++) {
0123         auto view = widget(i);
0124         disconnect(view, &QWidget::destroyed, this, &Konsole::TabbedViewContainer::viewDestroyed);
0125     }
0126 }
0127 
0128 ViewSplitter *TabbedViewContainer::activeViewSplitter()
0129 {
0130     return viewSplitterAt(currentIndex());
0131 }
0132 
0133 ViewSplitter *TabbedViewContainer::viewSplitterAt(int index)
0134 {
0135     return qobject_cast<ViewSplitter *>(widget(index));
0136 }
0137 
0138 ViewSplitter *TabbedViewContainer::findSplitter(int id)
0139 {
0140     for (int i = 0; i < count(); ++i) {
0141         auto toplevelSplitter = viewSplitterAt(i);
0142 
0143         if (toplevelSplitter->id() == id)
0144             return toplevelSplitter;
0145 
0146         if (auto result = toplevelSplitter->getChildSplitter(id))
0147             return result;
0148     }
0149 
0150     return nullptr;
0151 }
0152 
0153 int TabbedViewContainer::currentTabViewCount()
0154 {
0155     if (auto *splitter = activeViewSplitter()) {
0156         return splitter->findChildren<TerminalDisplay *>().count();
0157     }
0158 
0159     return 1;
0160 }
0161 
0162 void TabbedViewContainer::moveTabToWindow(int index, QWidget *window)
0163 {
0164     auto splitter = viewSplitterAt(index);
0165     auto manager = window->findChild<ViewManager *>();
0166 
0167     QHash<TerminalDisplay *, Session *> sessionsMap = _connectedViewManager->forgetAll(splitter);
0168 
0169     const QList<TerminalDisplay *> displays = splitter->findChildren<TerminalDisplay *>();
0170     for (TerminalDisplay *terminal : displays) {
0171         manager->attachView(terminal, sessionsMap[terminal]);
0172     }
0173     auto container = manager->activeContainer();
0174     container->addSplitter(splitter);
0175 
0176     auto controller = splitter->activeTerminalDisplay()->sessionController();
0177     container->currentSessionControllerChanged(controller);
0178 
0179     forgetView();
0180 }
0181 
0182 void TabbedViewContainer::konsoleConfigChanged()
0183 {
0184     // don't show tabs if we are in KParts mode.
0185     // This is a hack, and this needs to be rewritten.
0186     // The container should not be part of the KParts, perhaps just the
0187     // TerminalDisplay should.
0188 
0189     // ASAN issue if using sessionController->isKonsolePart(), just
0190     // duplicate code for now
0191     if (qApp->applicationName() != QLatin1String("konsole")) {
0192         tabBar()->setVisible(false);
0193     } else {
0194         // if we start with --show-tabbar or --hide-tabbar we ignore the preferences.
0195         setTabBarAutoHide(KonsoleSettings::tabBarVisibility() == KonsoleSettings::EnumTabBarVisibility::ShowTabBarWhenNeeded);
0196         if (KonsoleSettings::tabBarVisibility() == KonsoleSettings::EnumTabBarVisibility::AlwaysShowTabBar) {
0197             tabBar()->setVisible(true);
0198         } else if (KonsoleSettings::tabBarVisibility() == KonsoleSettings::EnumTabBarVisibility::AlwaysHideTabBar) {
0199             tabBar()->setVisible(false);
0200         }
0201     }
0202 
0203     setTabPosition((QTabWidget::TabPosition)KonsoleSettings::tabBarPosition());
0204 
0205     setCornerWidget(KonsoleSettings::newTabButton() ? _newTabButton : nullptr, Qt::TopLeftCorner);
0206     _newTabButton->setVisible(KonsoleSettings::newTabButton());
0207 
0208     setCornerWidget(KonsoleSettings::closeTabButton() == 1 ? _closeTabButton : nullptr, Qt::TopRightCorner);
0209     _closeTabButton->setVisible(KonsoleSettings::closeTabButton() == 1);
0210 
0211     tabBar()->setTabsClosable(KonsoleSettings::closeTabButton() == 0);
0212 
0213     tabBar()->setExpanding(KonsoleSettings::expandTabWidth());
0214     tabBar()->update();
0215 
0216     if (KonsoleSettings::tabBarUseUserStyleSheet()) {
0217         setCssFromFile(KonsoleSettings::tabBarUserStyleSheetFile());
0218         _stylesheetSet = true;
0219     } else {
0220         if (_stylesheetSet) {
0221             setStyleSheet(QString());
0222             _stylesheetSet = false;
0223         }
0224     }
0225 }
0226 
0227 void TabbedViewContainer::setCssFromFile(const QUrl &url)
0228 {
0229     // Let's only deal w/ local files for now
0230     if (!url.isLocalFile()) {
0231         setStyleSheet(KonsoleSettings::tabBarStyleSheet());
0232     }
0233 
0234     QFile file(url.toLocalFile());
0235     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
0236         setStyleSheet(KonsoleSettings::tabBarStyleSheet());
0237     }
0238 
0239     QTextStream in(&file);
0240     setStyleSheet(in.readAll());
0241 }
0242 
0243 void TabbedViewContainer::moveActiveView(MoveDirection direction)
0244 {
0245     if (count() < 2) { // return if only one view
0246         return;
0247     }
0248     const int currentIndex = indexOf(currentWidget());
0249     int newIndex = direction == MoveViewLeft ? qMax(currentIndex - 1, 0) : qMin(currentIndex + 1, count() - 1);
0250 
0251     auto swappedWidget = viewSplitterAt(newIndex);
0252     auto swappedTitle = tabBar()->tabText(newIndex);
0253     auto swappedIcon = tabBar()->tabIcon(newIndex);
0254 
0255     auto currentWidget = viewSplitterAt(currentIndex);
0256     auto currentTitle = tabBar()->tabText(currentIndex);
0257     auto currentIcon = tabBar()->tabIcon(currentIndex);
0258 
0259     if (newIndex < currentIndex) {
0260         insertTab(newIndex, currentWidget, currentIcon, currentTitle);
0261         insertTab(currentIndex, swappedWidget, swappedIcon, swappedTitle);
0262     } else {
0263         insertTab(currentIndex, swappedWidget, swappedIcon, swappedTitle);
0264         insertTab(newIndex, currentWidget, currentIcon, currentTitle);
0265     }
0266     setCurrentIndex(newIndex);
0267 }
0268 
0269 void TabbedViewContainer::terminalDisplayDropped(TerminalDisplay *terminalDisplay)
0270 {
0271     auto *controller = terminalDisplay->sessionController();
0272     if (controller->parent() != connectedViewManager()) {
0273         // Terminal from another window - recreate SessionController for current ViewManager
0274         disconnectTerminalDisplay(terminalDisplay);
0275         Session *terminalSession = controller->session();
0276         Q_EMIT controller->viewDragAndDropped(controller);
0277         connectedViewManager()->attachView(terminalDisplay, terminalSession);
0278         connectTerminalDisplay(terminalDisplay);
0279     }
0280 }
0281 
0282 QSize TabbedViewContainer::sizeHint() const
0283 {
0284     // QTabWidget::sizeHint() contains some margins added by widgets
0285     // style, which were making the initial window size too big.
0286     const auto tabsSize = tabBar()->sizeHint();
0287     const auto *leftWidget = cornerWidget(Qt::TopLeftCorner);
0288     const auto *rightWidget = cornerWidget(Qt::TopRightCorner);
0289     const auto leftSize = leftWidget != nullptr ? leftWidget->sizeHint() : QSize(0, 0);
0290     const auto rightSize = rightWidget != nullptr ? rightWidget->sizeHint() : QSize(0, 0);
0291 
0292     auto tabBarSize = QSize(0, 0);
0293     // isVisible() won't work; this is called when the window is not yet visible
0294     if (tabBar()->isVisibleTo(this)) {
0295         tabBarSize.setWidth(leftSize.width() + tabsSize.width() + rightSize.width());
0296         tabBarSize.setHeight(qMax(tabsSize.height(), qMax(leftSize.height(), rightSize.height())));
0297     }
0298 
0299     const auto terminalSize = currentWidget() != nullptr ? currentWidget()->sizeHint() : QSize(0, 0);
0300 
0301     //        width
0302     // ├──────────────────┤
0303     //
0304     // ┌──────────────────┐  ┬
0305     // │                  │  │
0306     // │     Terminal     │  │
0307     // │                  │  │ height
0308     // ├───┬──────────┬───┤  │  ┬
0309     // │ L │   Tabs   │ R │  │  │ tab bar height
0310     // └───┴──────────┴───┘  ┴  ┴
0311     //
0312     // L/R = left/right widget
0313 
0314     return {qMax(terminalSize.width(), tabBarSize.width()), tabBarSize.height() + terminalSize.height()};
0315 }
0316 
0317 void TabbedViewContainer::addSplitter(ViewSplitter *viewSplitter, int index)
0318 {
0319     index = insertTab(index, viewSplitter, QString());
0320     connect(viewSplitter, &ViewSplitter::destroyed, this, &TabbedViewContainer::viewDestroyed);
0321 
0322     disconnect(viewSplitter, &ViewSplitter::terminalDisplayDropped, nullptr, nullptr);
0323     connect(viewSplitter, &ViewSplitter::terminalDisplayDropped, this, &TabbedViewContainer::terminalDisplayDropped);
0324 
0325     const auto terminalDisplays = viewSplitter->findChildren<TerminalDisplay *>();
0326     for (TerminalDisplay *terminal : terminalDisplays) {
0327         connectTerminalDisplay(terminal);
0328     }
0329     if (terminalDisplays.count() > 0) {
0330         updateTitle(qobject_cast<ViewProperties *>(terminalDisplays.at(0)->sessionController()));
0331         updateColor(qobject_cast<ViewProperties *>(terminalDisplays.at(0)->sessionController()));
0332     }
0333     setCurrentIndex(index);
0334 }
0335 
0336 void TabbedViewContainer::addView(TerminalDisplay *view)
0337 {
0338     auto viewSplitter = new ViewSplitter();
0339     viewSplitter->addTerminalDisplay(view, Qt::Horizontal);
0340     auto item = view->sessionController();
0341     int index = _newTabBehavior == PutNewTabAfterCurrentTab ? currentIndex() + 1 : -1;
0342     index = insertTab(index, viewSplitter, item->icon(), item->title());
0343 
0344     connectTerminalDisplay(view);
0345     connect(viewSplitter, &ViewSplitter::destroyed, this, &TabbedViewContainer::viewDestroyed);
0346     connect(viewSplitter, &ViewSplitter::terminalDisplayDropped, this, &TabbedViewContainer::terminalDisplayDropped);
0347 
0348     // Put this view on the foreground if it requests so, eg. on bell activity
0349     connect(view, &TerminalDisplay::activationRequest, this, &Konsole::TabbedViewContainer::activateView);
0350 
0351     setCurrentIndex(index);
0352     Q_EMIT viewAdded(view);
0353 }
0354 
0355 void TabbedViewContainer::splitView(TerminalDisplay *view, Qt::Orientation orientation)
0356 {
0357     auto viewSplitter = qobject_cast<ViewSplitter *>(currentWidget());
0358     viewSplitter->clearMaximized();
0359     viewSplitter->addTerminalDisplay(view, orientation);
0360     connectTerminalDisplay(view);
0361     // Put this view on the foreground if it requests so, eg. on bell activity
0362     connect(view, &TerminalDisplay::activationRequest, this, &Konsole::TabbedViewContainer::activateView);
0363 }
0364 
0365 void TabbedViewContainer::connectTerminalDisplay(TerminalDisplay *display)
0366 {
0367     auto item = display->sessionController();
0368     connect(item, &Konsole::SessionController::viewFocused, this, &Konsole::TabbedViewContainer::currentSessionControllerChanged);
0369 
0370     connect(item, &Konsole::ViewProperties::titleChanged, this, &Konsole::TabbedViewContainer::updateTitle);
0371 
0372     connect(item, &Konsole::ViewProperties::colorChanged, this, &Konsole::TabbedViewContainer::updateColor);
0373 
0374     connect(item, &Konsole::ViewProperties::iconChanged, this, &Konsole::TabbedViewContainer::updateIcon);
0375 
0376     connect(item, &Konsole::ViewProperties::activity, this, &Konsole::TabbedViewContainer::updateActivity);
0377 
0378     connect(item, &Konsole::ViewProperties::notificationChanged, this, &Konsole::TabbedViewContainer::updateNotification);
0379 
0380     connect(item, &Konsole::ViewProperties::readOnlyChanged, this, &Konsole::TabbedViewContainer::updateSpecialState);
0381 
0382     connect(item, &Konsole::ViewProperties::copyInputChanged, this, &Konsole::TabbedViewContainer::updateSpecialState);
0383 }
0384 
0385 void TabbedViewContainer::disconnectTerminalDisplay(TerminalDisplay *display)
0386 {
0387     auto item = display->sessionController();
0388     item->disconnect(this);
0389 }
0390 
0391 void TabbedViewContainer::viewDestroyed(QObject *view)
0392 {
0393     QWidget *widget = qobject_cast<QWidget *>(view);
0394     Q_ASSERT(widget);
0395     const int idx = indexOf(widget);
0396 
0397     removeTab(idx);
0398     forgetView();
0399     _tabIconState.remove(widget);
0400 
0401     Q_EMIT viewRemoved();
0402 }
0403 
0404 void TabbedViewContainer::forgetView()
0405 {
0406     if (count() == 0) {
0407         Q_EMIT empty(this);
0408     }
0409 }
0410 
0411 void TabbedViewContainer::activateView(const QString & /*xdgActivationToken*/)
0412 {
0413     if (QWidget *widget = qobject_cast<QWidget *>(sender())) {
0414         auto topLevelSplitter = qobject_cast<ViewSplitter *>(widget->parentWidget())->getToplevelSplitter();
0415         setCurrentWidget(topLevelSplitter);
0416         widget->setFocus();
0417     }
0418 }
0419 
0420 void TabbedViewContainer::activateNextView()
0421 {
0422     QWidget *active = currentWidget();
0423     int index = indexOf(active);
0424     setCurrentIndex(index == count() - 1 ? 0 : index + 1);
0425 }
0426 
0427 void TabbedViewContainer::activateLastView()
0428 {
0429     setCurrentIndex(count() - 1);
0430 }
0431 
0432 void TabbedViewContainer::activatePreviousView()
0433 {
0434     QWidget *active = currentWidget();
0435     int index = indexOf(active);
0436     setCurrentIndex(index == 0 ? count() - 1 : index - 1);
0437 }
0438 
0439 void TabbedViewContainer::keyReleaseEvent(QKeyEvent *event)
0440 {
0441     if (event->modifiers() == Qt::NoModifier) {
0442         _connectedViewManager->updateTerminalDisplayHistory();
0443     }
0444 }
0445 
0446 void TabbedViewContainer::closeCurrentTab()
0447 {
0448     if (currentIndex() != -1) {
0449         closeTerminalTab(currentIndex());
0450     }
0451 }
0452 
0453 void TabbedViewContainer::tabDoubleClicked(int index)
0454 {
0455     if (index >= 0) {
0456         renameTab(index);
0457     } else {
0458         Q_EMIT newViewRequest();
0459     }
0460 }
0461 
0462 void TabbedViewContainer::renameTab(int index)
0463 {
0464     if (index != -1) {
0465         setCurrentIndex(index);
0466         viewSplitterAt(index)->activeTerminalDisplay()->sessionController()->rename();
0467     }
0468 }
0469 
0470 void TabbedViewContainer::openTabContextMenu(const QPoint &point)
0471 {
0472     if (point.isNull()) {
0473         return;
0474     }
0475 
0476     _contextMenuTabIndex = tabBar()->tabAt(point);
0477     if (_contextMenuTabIndex < 0) {
0478         return;
0479     }
0480 
0481     // TODO: add a countChanged signal so we can remove this for.
0482     // Detaching in mac causes crashes.
0483     for (auto action : _contextPopupMenu->actions()) {
0484         if (action->objectName() == QStringLiteral("tab-detach")) {
0485             action->setEnabled(count() > 1);
0486         }
0487     }
0488 
0489     _contextPopupMenu->exec(tabBar()->mapToGlobal(point));
0490 }
0491 
0492 void TabbedViewContainer::currentTabChanged(int index)
0493 {
0494     if (index != -1) {
0495         auto splitview = qobject_cast<ViewSplitter *>(widget(index));
0496         auto view = splitview->activeTerminalDisplay();
0497         setTabActivity(index, false);
0498         _tabIconState[splitview].notification = Session::NoNotification;
0499         if (view != nullptr) {
0500             Q_EMIT activeViewChanged(view);
0501             updateIcon(view->sessionController());
0502         }
0503     } else {
0504         deleteLater();
0505     }
0506 }
0507 
0508 void TabbedViewContainer::wheelScrolled(int delta)
0509 {
0510     if (delta < 0) {
0511         activateNextView();
0512     } else {
0513         activatePreviousView();
0514     }
0515 }
0516 
0517 void TabbedViewContainer::setTabActivity(int index, bool activity)
0518 {
0519     const QPalette &palette = tabBar()->palette();
0520     KColorScheme colorScheme(palette.currentColorGroup());
0521     const QColor colorSchemeActive = colorScheme.foreground(KColorScheme::ActiveText).color();
0522 
0523     const QColor normalColor = palette.text().color();
0524     const QColor activityColor = KColorUtils::mix(normalColor, colorSchemeActive);
0525 
0526     QColor color = activity ? activityColor : QColor();
0527 
0528     if (color != tabBar()->tabTextColor(index)) {
0529         tabBar()->setTabTextColor(index, color);
0530     }
0531 }
0532 
0533 void TabbedViewContainer::updateTitle(ViewProperties *item)
0534 {
0535     auto controller = qobject_cast<SessionController *>(item);
0536     auto topLevelSplitter = qobject_cast<ViewSplitter *>(controller->view()->parentWidget())->getToplevelSplitter();
0537     if (controller->view() != topLevelSplitter->activeTerminalDisplay()) {
0538         return;
0539     }
0540     const int index = indexOf(topLevelSplitter);
0541     QString tabText = item->title();
0542 
0543     setTabToolTip(index, tabText);
0544 
0545     // To avoid having & replaced with _ (shortcut indicator)
0546     tabText.replace(QLatin1Char('&'), QLatin1String("&&"));
0547     setTabText(index, tabText);
0548 }
0549 
0550 void TabbedViewContainer::updateColor(ViewProperties *item)
0551 {
0552     auto controller = qobject_cast<SessionController *>(item);
0553     auto topLevelSplitter = qobject_cast<ViewSplitter *>(controller->view()->parentWidget())->getToplevelSplitter();
0554     const int index = indexOf(topLevelSplitter);
0555 
0556     Q_EMIT setColor(index, item->color());
0557 }
0558 
0559 void TabbedViewContainer::updateIcon(ViewProperties *item)
0560 {
0561     auto controller = qobject_cast<SessionController *>(item);
0562     auto topLevelSplitter = qobject_cast<ViewSplitter *>(controller->view()->parentWidget())->getToplevelSplitter();
0563     const int index = indexOf(topLevelSplitter);
0564     const auto &state = _tabIconState[topLevelSplitter];
0565 
0566     // Tab icon priority (from highest to lowest):
0567     //
0568     // 1. Latest Notification
0569     //    - Inactive tab: Latest notification from any view in a tab. Removed
0570     //      when tab is activated.
0571     //    - Active tab: Latest notification from focused view. Removed when
0572     //      focus changes or when the Session clears its notifications
0573     // 2. Copy input or read-only indicator when all views in the tab have
0574     //    the status
0575     // 3. Active view icon
0576 
0577     QIcon icon = item->icon();
0578     if (state.notification != Session::NoNotification) {
0579         switch (state.notification) {
0580         case Session::Bell: {
0581             auto session = controller->session();
0582             auto profilePtr = SessionManager::instance()->sessionProfile(session);
0583             if (profilePtr->property<int>(Profile::BellMode) != Enum::NoBell) {
0584                 icon = QIcon::fromTheme(QLatin1String("notifications"));
0585             }
0586         } break;
0587         case Session::Activity:
0588             icon = QIcon::fromTheme(QLatin1String("dialog-information"));
0589             break;
0590         case Session::Silence:
0591             icon = QIcon::fromTheme(QLatin1String("system-suspend"));
0592             break;
0593         default:
0594             break;
0595         }
0596     } else if (state.broadcast) {
0597         icon = QIcon::fromTheme(QLatin1String("irc-voice"));
0598     } else if (state.readOnly) {
0599         icon = QIcon::fromTheme(QLatin1String("object-locked"));
0600     }
0601 
0602     if (tabIcon(index).name() != icon.name()) {
0603         setTabIcon(index, icon);
0604     }
0605 }
0606 
0607 void TabbedViewContainer::updateActivity(ViewProperties *item)
0608 {
0609     auto controller = qobject_cast<SessionController *>(item);
0610     auto topLevelSplitter = qobject_cast<ViewSplitter *>(controller->view()->parentWidget())->getToplevelSplitter();
0611 
0612     const int index = indexOf(topLevelSplitter);
0613     if (index != currentIndex()) {
0614         setTabActivity(index, true);
0615     }
0616 }
0617 
0618 void TabbedViewContainer::updateNotification(ViewProperties *item, Session::Notification notification, bool enabled)
0619 {
0620     auto controller = qobject_cast<SessionController *>(item);
0621     auto topLevelSplitter = qobject_cast<ViewSplitter *>(controller->view()->parentWidget())->getToplevelSplitter();
0622     const int index = indexOf(topLevelSplitter);
0623     auto &state = _tabIconState[topLevelSplitter];
0624 
0625     if (enabled && (index != currentIndex() || controller->view()->hasCompositeFocus())) {
0626         state.notification = notification;
0627         updateIcon(item);
0628     } else if (!enabled && controller->view()->hasCompositeFocus()) {
0629         state.notification = Session::NoNotification;
0630         updateIcon(item);
0631     }
0632 }
0633 
0634 void TabbedViewContainer::updateSpecialState(ViewProperties *item)
0635 {
0636     auto controller = qobject_cast<SessionController *>(item);
0637     auto topLevelSplitter = qobject_cast<ViewSplitter *>(controller->view()->parentWidget())->getToplevelSplitter();
0638 
0639     auto &state = _tabIconState[topLevelSplitter];
0640     state.readOnly = true;
0641     state.broadcast = true;
0642     const auto displays = topLevelSplitter->findChildren<TerminalDisplay *>();
0643     for (const auto display : displays) {
0644         if (!display->sessionController()->isReadOnly()) {
0645             state.readOnly = false;
0646         }
0647         if (!display->sessionController()->isCopyInputActive()) {
0648             state.broadcast = false;
0649         }
0650     }
0651     updateIcon(item);
0652 }
0653 
0654 void TabbedViewContainer::currentSessionControllerChanged(SessionController *controller)
0655 {
0656     auto topLevelSplitter = qobject_cast<ViewSplitter *>(controller->view()->parentWidget())->getToplevelSplitter();
0657     const int index = indexOf(topLevelSplitter);
0658 
0659     if (index == currentIndex()) {
0660         // Active view changed in current tab - clear notifications
0661         auto &state = _tabIconState[topLevelSplitter];
0662         state.notification = Session::NoNotification;
0663     }
0664 
0665     updateTitle(qobject_cast<ViewProperties *>(controller));
0666     updateColor(qobject_cast<ViewProperties *>(controller));
0667     updateActivity(qobject_cast<ViewProperties *>(controller));
0668     updateSpecialState(qobject_cast<ViewProperties *>(controller));
0669 }
0670 
0671 void TabbedViewContainer::closeTerminalTab(int idx)
0672 {
0673     Q_EMIT removeColor(idx);
0674     // TODO: This for should probably go to the ViewSplitter
0675     for (auto terminal : viewSplitterAt(idx)->findChildren<TerminalDisplay *>()) {
0676         terminal->sessionController()->closeSession();
0677     }
0678 }
0679 
0680 ViewManager *TabbedViewContainer::connectedViewManager()
0681 {
0682     return _connectedViewManager;
0683 }
0684 
0685 void TabbedViewContainer::setNavigationVisibility(ViewManager::NavigationVisibility navigationVisibility)
0686 {
0687     if (navigationVisibility == ViewManager::NavigationNotSet) {
0688         return;
0689     }
0690 
0691     setTabBarAutoHide(navigationVisibility == ViewManager::ShowNavigationAsNeeded);
0692     if (navigationVisibility == ViewManager::AlwaysShowNavigation) {
0693         tabBar()->setVisible(true);
0694     } else if (navigationVisibility == ViewManager::AlwaysHideNavigation) {
0695         tabBar()->setVisible(false);
0696     }
0697 }
0698 
0699 void TabbedViewContainer::toggleMaximizeCurrentTerminal()
0700 {
0701     if (auto *terminal = qobject_cast<TerminalDisplay *>(sender())) {
0702         terminal->setFocus(Qt::FocusReason::OtherFocusReason);
0703     }
0704 
0705     activeViewSplitter()->toggleMaximizeCurrentTerminal();
0706 }
0707 
0708 
0709 void TabbedViewContainer::toggleZoomMaximizeCurrentTerminal()
0710 {
0711     if (auto *terminal = qobject_cast<TerminalDisplay *>(sender())) {
0712         terminal->setFocus(Qt::FocusReason::OtherFocusReason);
0713     }
0714 
0715     activeViewSplitter()->toggleZoomMaximizeCurrentTerminal();
0716 }
0717 
0718 void TabbedViewContainer::moveTabLeft()
0719 {
0720     if (currentIndex() == 0) {
0721         return;
0722     }
0723     tabBar()->moveTab(currentIndex(), currentIndex() - 1);
0724 }
0725 
0726 void TabbedViewContainer::moveTabRight()
0727 {
0728     if (currentIndex() == count() - 1) {
0729         return;
0730     }
0731     tabBar()->moveTab(currentIndex(), currentIndex() + 1);
0732 }
0733 
0734 void TabbedViewContainer::setNavigationBehavior(int behavior)
0735 {
0736     _newTabBehavior = static_cast<NewTabBehavior>(behavior);
0737 }
0738 
0739 void TabbedViewContainer::moveToNewTab(TerminalDisplay *display)
0740 {
0741     // Ensure that the current terminal is not maximized so that the other views will be shown properly
0742     activeViewSplitter()->clearMaximized();
0743     addView(display);
0744 }
0745 
0746 #include "moc_ViewContainer.cpp"