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"