diff --git a/src/DetachableTabBar.cpp b/src/DetachableTabBar.cpp index 8b9c0b5f..50b1d109 100644 --- a/src/DetachableTabBar.cpp +++ b/src/DetachableTabBar.cpp @@ -1,169 +1,225 @@ /* Copyright 2018 by Tomaz Canabrava This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "DetachableTabBar.h" #include "KonsoleSettings.h" #include "ViewContainer.h" #include #include #include #include #include #include +using namespace std; + namespace Konsole { DetachableTabBar::DetachableTabBar(QWidget *parent) : QTabBar(parent), dragType(DragType::NONE), _originalCursor(cursor()), tabId(-1) { setAcceptDrops(true); setElideMode(Qt::TextElideMode::ElideMiddle); KAcceleratorManager::setNoAccel(this); + + connect(this, &QTabBar::tabMoved, this, &DetachableTabBar::moveColor); } void DetachableTabBar::setColor(int idx, const QColor &color) { - tabColors.insert({idx, color}); + map::const_iterator itColor = tabColors.find(idx); + if (itColor != tabColors.end()) { + tabColors[idx] = color; + } else { + tabColors.insert({idx, color}); + } + update(); +} + +void DetachableTabBar::moveColor(int from, int to) +{ + map::const_iterator itFromColor = tabColors.find(from); + map::const_iterator itToColor = tabColors.find(to); + if (itFromColor != tabColors.end() && itToColor != tabColors.end()) { + // 'from' and 'to' exists color + QColor toColor = tabColors[to]; // save temporary 'to' color + tabColors[to] = tabColors[from]; // set new 'to' color + tabColors[from] = toColor; // set new 'from' color + } else { + if (itFromColor != tabColors.end() && itToColor == tabColors.end()) { + // 'from' exists color + tabColors[to] = tabColors[from]; + tabColors.erase(itFromColor); + } + if (itFromColor == tabColors.end() && itToColor != tabColors.end()) { + // 'to' exists color + tabColors[from] = tabColors[to]; + tabColors.erase(itToColor); + } + } + update(); +} + +void DetachableTabBar::removeColor(int idx) +{ + map::const_iterator itColor = tabColors.find(idx); + if (itColor != tabColors.end()) { + tabColors.erase(itColor); + } + + using pair_type = decltype(tabColors)::value_type; + auto pr = std::max_element( + std::begin(tabColors), std::end(tabColors), + [] (const pair_type &p1, const pair_type &p2) { + return p1.first < p2.first; + } + ); + + if (pr->first >= idx) { + for (int tabIndex = idx; tabIndex < count(); tabIndex++) { + moveColor(tabIndex +1, tabIndex); + } + } update(); } void DetachableTabBar::middleMouseButtonClickAt(const QPoint& pos) { tabId = tabAt(pos); if (tabId != -1) { emit closeTab(tabId); } } void DetachableTabBar::mousePressEvent(QMouseEvent *event) { QTabBar::mousePressEvent(event); _containers = window()->findChildren(); } void DetachableTabBar::mouseMoveEvent(QMouseEvent *event) { QTabBar::mouseMoveEvent(event); auto widgetAtPos = qApp->topLevelAt(event->globalPos()); if (widgetAtPos != nullptr) { if (window() == widgetAtPos->window()) { if (dragType != DragType::NONE) { dragType = DragType::NONE; setCursor(_originalCursor); } } else { if (dragType != DragType::WINDOW) { dragType = DragType::WINDOW; setCursor(QCursor(Qt::DragMoveCursor)); } } } else if (!contentsRect().adjusted(-30,-30,30,30).contains(event->pos())) { // Don't let it detach the last tab. if (count() == 1) { return; } if (dragType != DragType::OUTSIDE) { dragType = DragType::OUTSIDE; setCursor(QCursor(Qt::DragCopyCursor)); } } } void DetachableTabBar::mouseReleaseEvent(QMouseEvent *event) { QTabBar::mouseReleaseEvent(event); switch(event->button()) { case Qt::MiddleButton : if (KonsoleSettings::closeTabOnMiddleMouseButton()) { middleMouseButtonClickAt(event->pos()); } tabId = tabAt(event->pos()); if (tabId == -1) { emit newTabRequest(); } break; case Qt::LeftButton: _containers = window()->findChildren(); break; default: break; } setCursor(_originalCursor); if (contentsRect().adjusted(-30,-30,30,30).contains(event->pos())) { return; } auto widgetAtPos = qApp->topLevelAt(event->globalPos()); if (widgetAtPos == nullptr) { if (count() != 1) { emit detachTab(currentIndex()); } } else if (window() != widgetAtPos->window()) { if (_containers.size() == 1 || count() > 1) { emit moveTabToWindow(currentIndex(), widgetAtPos); } } } void DetachableTabBar::dragEnterEvent(QDragEnterEvent* event) { const auto dragId = QStringLiteral("konsole/terminal_display"); if (event->mimeData()->hasFormat(dragId)) { auto other_pid = event->mimeData()->data(dragId).toInt(); // don't accept the drop if it's another instance of konsole if (qApp->applicationPid() != other_pid) { return; } event->accept(); } } void DetachableTabBar::dragMoveEvent(QDragMoveEvent* event) { int tabIdx = tabAt(event->pos()); if (tabIdx != -1) { setCurrentIndex(tabIdx); } } void DetachableTabBar::paintEvent(QPaintEvent *event) { QTabBar::paintEvent(event); QPainter painter(this); painter.setPen(Qt::NoPen); for (const auto &map_colors : tabColors) { const auto rect = tabRect(map_colors.first); QColor color = map_colors.second; color.setAlpha(125); if (!color.isValid()) continue; painter.setBrush(color); painter.drawRect(rect); } } } diff --git a/src/DetachableTabBar.h b/src/DetachableTabBar.h index 05f000d7..35620d25 100644 --- a/src/DetachableTabBar.h +++ b/src/DetachableTabBar.h @@ -1,65 +1,67 @@ /* Copyright 2018 by Tomaz Canabrava This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef DETACHABLETABBAR_H #define DETACHABLETABBAR_H #include #include #include class QColor; class QPaintEvent; namespace Konsole { class TabbedViewContainer; class DetachableTabBar : public QTabBar { Q_OBJECT public: enum class DragType : unsigned char {NONE, OUTSIDE, WINDOW}; explicit DetachableTabBar(QWidget *parent = nullptr); void setColor(int idx, const QColor &color); + void moveColor(int from, int to); + void removeColor(int idx); Q_SIGNALS: void detachTab(int index); void moveTabToWindow(int tabIndex, QWidget *otherWindow); void closeTab(int index); void newTabRequest(); protected: void middleMouseButtonClickAt(const QPoint& pos); void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent*event) override; void mouseReleaseEvent(QMouseEvent *event) override; void dragEnterEvent(QDragEnterEvent *event) override; void dragMoveEvent(QDragMoveEvent * event) override; void paintEvent(QPaintEvent *event) override; private: DragType dragType; QCursor _originalCursor; QList _containers; int tabId; std::map tabColors; }; } #endif diff --git a/src/ViewContainer.cpp b/src/ViewContainer.cpp index 8576d8b8..2e6408f6 100644 --- a/src/ViewContainer.cpp +++ b/src/ViewContainer.cpp @@ -1,711 +1,713 @@ /* This file is part of the Konsole Terminal. Copyright 2006-2008 Robert Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ // Own #include "ViewContainer.h" #include "config-konsole.h" // Qt #include #include #include #include // KDE #include #include #include #include // Konsole #include "IncrementalSearchBar.h" #include "ViewProperties.h" #include "ProfileList.h" #include "ViewManager.h" #include "KonsoleSettings.h" #include "SessionController.h" #include "DetachableTabBar.h" #include "TerminalDisplay.h" #include "ViewSplitter.h" #include "MainWindow.h" #include "Session.h" // TODO Perhaps move everything which is Konsole-specific into different files using namespace Konsole; TabbedViewContainer::TabbedViewContainer(ViewManager *connectedViewManager, QWidget *parent) : QTabWidget(parent), _connectedViewManager(connectedViewManager), _newTabButton(new QToolButton(this)), _closeTabButton(new QToolButton(this)), _contextMenuTabIndex(-1), _navigationVisibility(ViewManager::NavigationVisibility::NavigationNotSet), _newTabBehavior(PutNewTabAtTheEnd) { setAcceptDrops(true); auto tabBarWidget = new DetachableTabBar(); setTabBar(tabBarWidget); setDocumentMode(true); setMovable(true); connect(tabBarWidget, &DetachableTabBar::moveTabToWindow, this, &TabbedViewContainer::moveTabToWindow); tabBar()->setContextMenuPolicy(Qt::CustomContextMenu); _newTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-new"))); _newTabButton->setAutoRaise(true); connect(_newTabButton, &QToolButton::clicked, this, &TabbedViewContainer::newViewRequest); _closeTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); _closeTabButton->setAutoRaise(true); connect(_closeTabButton, &QToolButton::clicked, this, [this]{ closeCurrentTab(); }); connect(tabBar(), &QTabBar::tabBarDoubleClicked, this, &Konsole::TabbedViewContainer::tabDoubleClicked); connect(tabBar(), &QTabBar::customContextMenuRequested, this, &Konsole::TabbedViewContainer::openTabContextMenu); connect(tabBarWidget, &DetachableTabBar::detachTab, this, [this](int idx) { emit detachTab(idx); }); connect(tabBarWidget, &DetachableTabBar::closeTab, this, &TabbedViewContainer::closeTerminalTab); connect(tabBarWidget, &DetachableTabBar::newTabRequest, this, [this]{ emit newViewRequest(); }); connect(this, &TabbedViewContainer::currentChanged, this, &TabbedViewContainer::currentTabChanged); connect(this, &TabbedViewContainer::setColor, tabBarWidget, &DetachableTabBar::setColor); + connect(this, &TabbedViewContainer::removeColor, tabBarWidget, &DetachableTabBar::removeColor); // The context menu of tab bar _contextPopupMenu = new QMenu(tabBar()); connect(_contextPopupMenu, &QMenu::aboutToHide, this, [this]() { // Remove the read-only action when the popup closes for (auto &action : _contextPopupMenu->actions()) { if (action->objectName() == QStringLiteral("view-readonly")) { _contextPopupMenu->removeAction(action); break; } } }); connect(tabBar(), &QTabBar::tabCloseRequested, this, &TabbedViewContainer::closeTerminalTab); auto detachAction = _contextPopupMenu->addAction( QIcon::fromTheme(QStringLiteral("tab-detach")), i18nc("@action:inmenu", "&Detach Tab"), this, [this] { emit detachTab(_contextMenuTabIndex); } ); detachAction->setObjectName(QStringLiteral("tab-detach")); auto editAction = _contextPopupMenu->addAction( QIcon::fromTheme(QStringLiteral("edit-rename")), i18nc("@action:inmenu", "&Rename Tab..."), this, [this]{ renameTab(_contextMenuTabIndex); } ); editAction->setObjectName(QStringLiteral("edit-rename")); auto closeAction = _contextPopupMenu->addAction( QIcon::fromTheme(QStringLiteral("tab-close")), i18nc("@action:inmenu", "Close Tab"), this, [this] { closeTerminalTab(_contextMenuTabIndex); } ); closeAction->setObjectName(QStringLiteral("tab-close")); auto profileMenu = new QMenu(this); auto profileList = new ProfileList(false, profileMenu); profileList->syncWidgetActions(profileMenu, true); connect(profileList, &Konsole::ProfileList::profileSelected, this, &TabbedViewContainer::newViewWithProfileRequest); _newTabButton->setMenu(profileMenu); konsoleConfigChanged(); connect(KonsoleSettings::self(), &KonsoleSettings::configChanged, this, &TabbedViewContainer::konsoleConfigChanged); } TabbedViewContainer::~TabbedViewContainer() { for(int i = 0, end = count(); i < end; i++) { auto view = widget(i); disconnect(view, &QWidget::destroyed, this, &Konsole::TabbedViewContainer::viewDestroyed); } } ViewSplitter *TabbedViewContainer::activeViewSplitter() { return viewSplitterAt(currentIndex()); } ViewSplitter *TabbedViewContainer::viewSplitterAt(int index) { return qobject_cast(widget(index)); } void TabbedViewContainer::moveTabToWindow(int index, QWidget *window) { auto splitter = viewSplitterAt(index); auto manager = window->findChild(); QHash sessionsMap = _connectedViewManager->forgetAll(splitter); const QList displays = splitter->findChildren(); for (TerminalDisplay *terminal : displays) { manager->attachView(terminal, sessionsMap[terminal]); } auto container = manager->activeContainer(); container->addSplitter(splitter); auto controller = splitter->activeTerminalDisplay()->sessionController(); container->currentSessionControllerChanged(controller); forgetView(splitter); } void TabbedViewContainer::konsoleConfigChanged() { // don't show tabs if we are in KParts mode. // This is a hack, and this needs to be rewritten. // The container should not be part of the KParts, perhaps just the // TerminalDisplay should. // ASAN issue if using sessionController->isKonsolePart(), just // duplicate code for now if (qApp->applicationName() != QLatin1String("konsole")) { tabBar()->setVisible(false); } else { // if we start with --show-tabbar or --hide-tabbar we ignore the preferences. setTabBarAutoHide(KonsoleSettings::tabBarVisibility() == KonsoleSettings::EnumTabBarVisibility::ShowTabBarWhenNeeded); if (KonsoleSettings::tabBarVisibility() == KonsoleSettings::EnumTabBarVisibility::AlwaysShowTabBar) { tabBar()->setVisible(true); } else if (KonsoleSettings::tabBarVisibility() == KonsoleSettings::EnumTabBarVisibility::AlwaysHideTabBar) { tabBar()->setVisible(false); } } setTabPosition((QTabWidget::TabPosition) KonsoleSettings::tabBarPosition()); setCornerWidget(KonsoleSettings::newTabButton() ? _newTabButton : nullptr, Qt::TopLeftCorner); _newTabButton->setVisible(KonsoleSettings::newTabButton()); setCornerWidget(KonsoleSettings::closeTabButton() == 1 ? _closeTabButton : nullptr, Qt::TopRightCorner); _closeTabButton->setVisible(KonsoleSettings::closeTabButton() == 1); tabBar()->setTabsClosable(KonsoleSettings::closeTabButton() == 0); tabBar()->setExpanding(KonsoleSettings::expandTabWidth()); tabBar()->update(); if (KonsoleSettings::tabBarUseUserStyleSheet()) { setCssFromFile(KonsoleSettings::tabBarUserStyleSheetFile()); } else { setCss(); } } void TabbedViewContainer::setCss(const QString& styleSheet) { static const QString defaultCss = QStringLiteral("QTabWidget::tab-bar, QTabWidget::pane { margin: 0; }\n"); setStyleSheet(defaultCss + styleSheet); } void TabbedViewContainer::setCssFromFile(const QUrl &url) { // Let's only deal w/ local files for now if (!url.isLocalFile()) { setStyleSheet(KonsoleSettings::tabBarStyleSheet()); } QFile file(url.toLocalFile()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { setStyleSheet(KonsoleSettings::tabBarStyleSheet()); } QTextStream in(&file); setCss(in.readAll()); } void TabbedViewContainer::moveActiveView(MoveDirection direction) { if (count() < 2) { // return if only one view return; } const int currentIndex = indexOf(currentWidget()); int newIndex = direction == MoveViewLeft ? qMax(currentIndex - 1, 0) : qMin(currentIndex + 1, count() - 1); auto swappedWidget = viewSplitterAt(newIndex); auto swappedTitle = tabBar()->tabText(newIndex); auto swappedIcon = tabBar()->tabIcon(newIndex); auto currentWidget = viewSplitterAt(currentIndex); auto currentTitle = tabBar()->tabText(currentIndex); auto currentIcon = tabBar()->tabIcon(currentIndex); if (newIndex < currentIndex) { insertTab(newIndex, currentWidget, currentIcon, currentTitle); insertTab(currentIndex, swappedWidget, swappedIcon, swappedTitle); } else { insertTab(currentIndex, swappedWidget, swappedIcon, swappedTitle); insertTab(newIndex, currentWidget, currentIcon, currentTitle); } setCurrentIndex(newIndex); } void TabbedViewContainer::terminalDisplayDropped(TerminalDisplay *terminalDisplay) { if (terminalDisplay->sessionController()->parent() != connectedViewManager()) { // Terminal from another window - recreate SessionController for current ViewManager disconnectTerminalDisplay(terminalDisplay); Session* terminalSession = terminalDisplay->sessionController()->session(); terminalDisplay->sessionController()->deleteLater(); connectedViewManager()->attachView(terminalDisplay, terminalSession); connectTerminalDisplay(terminalDisplay); } } QSize TabbedViewContainer::sizeHint() const { // QTabWidget::sizeHint() contains some margins added by widgets // style, which were making the initial window size too big. const auto tabsSize = tabBar()->sizeHint(); const auto *leftWidget = cornerWidget(Qt::TopLeftCorner); const auto *rightWidget = cornerWidget(Qt::TopRightCorner); const auto leftSize = leftWidget ? leftWidget->sizeHint() : QSize(0, 0); const auto rightSize = rightWidget ? rightWidget->sizeHint() : QSize(0, 0); auto tabBarSize = QSize(0, 0); // isVisible() won't work; this is called when the window is not yet visible if (tabBar()->isVisibleTo(this)) { tabBarSize.setWidth(leftSize.width() + tabsSize.width() + rightSize.width()); tabBarSize.setHeight(qMax(tabsSize.height(), qMax(leftSize.height(), rightSize.height()))); } const auto terminalSize = currentWidget() ? currentWidget()->sizeHint() : QSize(0, 0); // width // ├──────────────────┤ // // ┌──────────────────┐ ┬ // │ │ │ // │ Terminal │ │ // │ │ │ height // ├───┬──────────┬───┤ │ ┬ // │ L │ Tabs │ R │ │ │ tab bar height // └───┴──────────┴───┘ ┴ ┴ // // L/R = left/right widget return {qMax(terminalSize.width(), tabBarSize.width()), tabBarSize.height() + terminalSize.height()}; } void TabbedViewContainer::addSplitter(ViewSplitter *viewSplitter, int index) { index = insertTab(index, viewSplitter, QString()); connect(viewSplitter, &ViewSplitter::destroyed, this, &TabbedViewContainer::viewDestroyed); disconnect(viewSplitter, &ViewSplitter::terminalDisplayDropped, nullptr, nullptr); connect(viewSplitter, &ViewSplitter::terminalDisplayDropped, this, &TabbedViewContainer::terminalDisplayDropped); const auto terminalDisplays = viewSplitter->findChildren(); for (TerminalDisplay *terminal : terminalDisplays) { connectTerminalDisplay(terminal); } if (terminalDisplays.count() > 0) { updateTitle(qobject_cast(terminalDisplays.at(0)->sessionController())); } setCurrentIndex(index); } void TabbedViewContainer::addView(TerminalDisplay *view) { auto viewSplitter = new ViewSplitter(); viewSplitter->addTerminalDisplay(view, Qt::Horizontal); auto item = view->sessionController(); int index = _newTabBehavior == PutNewTabAfterCurrentTab ? currentIndex() + 1 : -1; index = insertTab(index, viewSplitter, item->icon(), item->title()); connectTerminalDisplay(view); connect(viewSplitter, &ViewSplitter::destroyed, this, &TabbedViewContainer::viewDestroyed); connect(viewSplitter, &ViewSplitter::terminalDisplayDropped, this, &TabbedViewContainer::terminalDisplayDropped); setCurrentIndex(index); emit viewAdded(view); } void TabbedViewContainer::splitView(TerminalDisplay *view, Qt::Orientation orientation) { auto viewSplitter = qobject_cast(currentWidget()); viewSplitter->addTerminalDisplay(view, orientation); connectTerminalDisplay(view); } void TabbedViewContainer::connectTerminalDisplay(TerminalDisplay *display) { auto item = display->sessionController(); connect(item, &Konsole::SessionController::viewFocused, this, &Konsole::TabbedViewContainer::currentSessionControllerChanged); connect(item, &Konsole::ViewProperties::titleChanged, this, &Konsole::TabbedViewContainer::updateTitle); connect(item, &Konsole::ViewProperties::colorChanged, this, &Konsole::TabbedViewContainer::updateColor); connect(item, &Konsole::ViewProperties::iconChanged, this, &Konsole::TabbedViewContainer::updateIcon); connect(item, &Konsole::ViewProperties::activity, this, &Konsole::TabbedViewContainer::updateActivity); connect(item, &Konsole::ViewProperties::notificationChanged, this, &Konsole::TabbedViewContainer::updateNotification); connect(item, &Konsole::ViewProperties::readOnlyChanged, this, &Konsole::TabbedViewContainer::updateSpecialState); connect(item, &Konsole::ViewProperties::copyInputChanged, this, &Konsole::TabbedViewContainer::updateSpecialState); } void TabbedViewContainer::disconnectTerminalDisplay(TerminalDisplay *display) { auto item = display->sessionController(); item->disconnect(this); } void TabbedViewContainer::viewDestroyed(QObject *view) { auto widget = static_cast(view); const auto idx = indexOf(widget); removeTab(idx); forgetView(widget); _tabIconState.remove(widget); } void TabbedViewContainer::forgetView(ViewSplitter *view) { Q_UNUSED(view) if (count() == 0) { emit empty(this); } } void TabbedViewContainer::activateNextView() { QWidget *active = currentWidget(); int index = indexOf(active); setCurrentIndex(index == count() - 1 ? 0 : index + 1); } void TabbedViewContainer::activateLastView() { setCurrentIndex(count() - 1); } void TabbedViewContainer::activatePreviousView() { QWidget *active = currentWidget(); int index = indexOf(active); setCurrentIndex(index == 0 ? count() - 1 : index - 1); } void TabbedViewContainer::keyReleaseEvent(QKeyEvent* event) { if (event->modifiers() == Qt::NoModifier) { _connectedViewManager->updateTerminalDisplayHistory(); } } void TabbedViewContainer::closeCurrentTab() { if (currentIndex() != -1) { closeTerminalTab(currentIndex()); } } void TabbedViewContainer::tabDoubleClicked(int index) { if (index >= 0) { renameTab(index); } else { emit newViewRequest(); } } void TabbedViewContainer::renameTab(int index) { if (index != -1) { setCurrentIndex(index); viewSplitterAt(index) -> activeTerminalDisplay() -> sessionController() -> rename(); } } void TabbedViewContainer::openTabContextMenu(const QPoint &point) { if (point.isNull()) { return; } _contextMenuTabIndex = tabBar()->tabAt(point); if (_contextMenuTabIndex < 0) { return; } //TODO: add a countChanged signal so we can remove this for. // Detaching in mac causes crashes. for(auto action : _contextPopupMenu->actions()) { if (action->objectName() == QStringLiteral("tab-detach")) { action->setEnabled(count() > 1); } } _contextPopupMenu->exec(tabBar()->mapToGlobal(point)); } void TabbedViewContainer::currentTabChanged(int index) { if (index != -1) { auto splitview = qobject_cast(widget(index)); auto view = splitview->activeTerminalDisplay(); emit activeViewChanged(view); setTabActivity(index, false); _tabIconState[splitview].notification = Session::NoNotification; updateIcon(view->sessionController()); } else { deleteLater(); } } void TabbedViewContainer::wheelScrolled(int delta) { if (delta < 0) { activateNextView(); } else { activatePreviousView(); } } void TabbedViewContainer::setTabActivity(int index, bool activity) { const QPalette &palette = tabBar()->palette(); KColorScheme colorScheme(palette.currentColorGroup()); const QColor colorSchemeActive = colorScheme.foreground(KColorScheme::ActiveText).color(); const QColor normalColor = palette.text().color(); const QColor activityColor = KColorUtils::mix(normalColor, colorSchemeActive); QColor color = activity ? activityColor : QColor(); if (color != tabBar()->tabTextColor(index)) { tabBar()->setTabTextColor(index, color); } } void TabbedViewContainer::updateTitle(ViewProperties *item) { auto controller = qobject_cast(item); auto topLevelSplitter = qobject_cast(controller->view()->parentWidget())->getToplevelSplitter(); if (controller->view() != topLevelSplitter->activeTerminalDisplay()) { return; } const int index = indexOf(topLevelSplitter); QString tabText = item->title(); setTabToolTip(index, tabText); // To avoid having & replaced with _ (shortcut indicator) tabText.replace(QLatin1Char('&'), QLatin1String("&&")); setTabText(index, tabText); } void TabbedViewContainer::updateColor(ViewProperties *item) { auto controller = qobject_cast(item); auto topLevelSplitter = qobject_cast(controller->view()->parentWidget())->getToplevelSplitter(); const int index = indexOf(topLevelSplitter); emit setColor(index, item->color()); } void TabbedViewContainer::updateIcon(ViewProperties *item) { auto controller = qobject_cast(item); auto topLevelSplitter = qobject_cast(controller->view()->parentWidget())->getToplevelSplitter(); const int index = indexOf(topLevelSplitter); const auto &state = _tabIconState[topLevelSplitter]; // Tab icon priority (from highest to lowest): // // 1. Latest Notification // - Inactive tab: Latest notification from any view in a tab. Removed // when tab is activated. // - Active tab: Latest notification from focused view. Removed when // focus changes or when the Session clears its notifications // 2. Copy input or read-only indicator when all views in the tab have // the status // 3. Active view icon QIcon icon; if (state.notification != Session::NoNotification) { switch(state.notification) { case Session::Bell: icon = QIcon::fromTheme(QLatin1String("notifications")); break; case Session::Activity: icon = QIcon::fromTheme(QLatin1String("dialog-information")); break; case Session::Silence: icon = QIcon::fromTheme(QLatin1String("system-suspend")); break; default: break; } } else if (state.broadcast) { icon = QIcon::fromTheme(QLatin1String("irc-voice")); } else if (state.readOnly) { icon = QIcon::fromTheme(QLatin1String("object-locked")); } else { icon = item->icon(); } if (tabIcon(index).name() != icon.name()) { setTabIcon(index, icon); } } void TabbedViewContainer::updateActivity(ViewProperties *item) { auto controller = qobject_cast(item); auto topLevelSplitter = qobject_cast(controller->view()->parentWidget())->getToplevelSplitter(); const int index = indexOf(topLevelSplitter); if (index != currentIndex()) { setTabActivity(index, true); } } void TabbedViewContainer::updateNotification(ViewProperties *item, Session::Notification notification, bool enabled) { auto controller = qobject_cast(item); auto topLevelSplitter = qobject_cast(controller->view()->parentWidget())->getToplevelSplitter(); const int index = indexOf(topLevelSplitter); auto &state = _tabIconState[topLevelSplitter]; if (enabled && (index != currentIndex() || controller->view()->hasCompositeFocus())) { state.notification = notification; updateIcon(item); } else if (!enabled && controller->view()->hasCompositeFocus()) { state.notification = Session::NoNotification; updateIcon(item); } } void TabbedViewContainer::updateSpecialState(ViewProperties *item) { auto controller = qobject_cast(item); auto topLevelSplitter = qobject_cast(controller->view()->parentWidget())->getToplevelSplitter(); auto &state = _tabIconState[topLevelSplitter]; state.readOnly = true; state.broadcast = true; const auto displays = topLevelSplitter->findChildren(); for (const auto display : displays) { if (!display->sessionController()->isReadOnly()) { state.readOnly = false; } if (!display->sessionController()->isCopyInputActive()) { state.broadcast = false; } } updateIcon(item); } void TabbedViewContainer::currentSessionControllerChanged(SessionController *controller) { auto topLevelSplitter = qobject_cast(controller->view()->parentWidget())->getToplevelSplitter(); const int index = indexOf(topLevelSplitter); if (index == currentIndex()) { // Active view changed in current tab - clear notifications auto &state = _tabIconState[topLevelSplitter]; state.notification = Session::NoNotification; } updateTitle(qobject_cast(controller)); updateActivity(qobject_cast(controller)); updateSpecialState(qobject_cast(controller)); } void TabbedViewContainer::closeTerminalTab(int idx) { + emit removeColor(idx); //TODO: This for should probably go to the ViewSplitter for (auto terminal : viewSplitterAt(idx)->findChildren()) { terminal->sessionController()->closeSession(); } } ViewManager *TabbedViewContainer::connectedViewManager() { return _connectedViewManager; } void TabbedViewContainer::setNavigationVisibility(ViewManager::NavigationVisibility navigationVisibility) { if (navigationVisibility == ViewManager::NavigationNotSet) { return; } setTabBarAutoHide(navigationVisibility == ViewManager::ShowNavigationAsNeeded); if (navigationVisibility == ViewManager::AlwaysShowNavigation) { tabBar()->setVisible(true); } else if (navigationVisibility == ViewManager::AlwaysHideNavigation) { tabBar()->setVisible(false); } } void TabbedViewContainer::toggleMaximizeCurrentTerminal() { if (auto *terminal = qobject_cast(sender())) { terminal->setFocus(Qt::FocusReason::OtherFocusReason); } activeViewSplitter()->toggleMaximizeCurrentTerminal(); } void TabbedViewContainer::moveTabLeft() { if (currentIndex() == 0) { return; } tabBar()->moveTab(currentIndex(), currentIndex() -1); } void TabbedViewContainer::moveTabRight() { if (currentIndex() == count() -1) { return; } tabBar()->moveTab(currentIndex(), currentIndex() + 1); } void TabbedViewContainer::setNavigationBehavior(int behavior) { _newTabBehavior = static_cast(behavior); } diff --git a/src/ViewContainer.h b/src/ViewContainer.h index 10a42cb3..a52ae6c3 100644 --- a/src/ViewContainer.h +++ b/src/ViewContainer.h @@ -1,249 +1,252 @@ /* This file is part of the Konsole Terminal. Copyright 2006-2008 Robert Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef VIEWCONTAINER_H #define VIEWCONTAINER_H // Qt #include #include // Konsole #include "Profile.h" #include "ViewManager.h" #include "Session.h" // Qt class QPoint; class QToolButton; class QMenu; namespace Konsole { class ViewProperties; class ViewManager; class TabbedViewContainer; /** * An interface for container widgets which can hold one or more views. * * The container widget typically displays a list of the views which * it has and provides a means of switching between them. * * Subclasses should reimplement the addViewWidget() and removeViewWidget() functions * to actually add or remove view widgets from the container widget, as well * as updating any navigation aids. */ class KONSOLEPRIVATE_EXPORT TabbedViewContainer : public QTabWidget { Q_OBJECT public: /** * Constructs a new view container with the specified parent. * * @param connectedViewManager Connect the new view to this manager * @param parent The parent object of the container */ TabbedViewContainer(ViewManager *connectedViewManager, QWidget *parent); /** * Called when the ViewContainer is destroyed. When reimplementing this in * subclasses, use object->deleteLater() to delete any widgets or other objects * instead of 'delete object'. */ ~TabbedViewContainer() override; /** Adds a new view to the container widget */ void addView(TerminalDisplay *view); void addSplitter(ViewSplitter *viewSplitter, int index = -1); /** splits the currently focused Splitter */ void splitView(TerminalDisplay *view, Qt::Orientation orientation); void setTabActivity(int index, bool activity); /** Sets tab title to item title if the view is active */ void updateTitle(ViewProperties *item); /** Sets tab color to item color if the view is active */ void updateColor(ViewProperties *item); /** Sets tab icon to item icon if the view is active */ void updateIcon(ViewProperties *item); /** Sets tab activity status if the tab is not active */ void updateActivity(ViewProperties *item); /** Sets tab notification */ void updateNotification(ViewProperties *item, Konsole::Session::Notification notification, bool enabled); /** Sets tab special state (copy input or read-only) */ void updateSpecialState(ViewProperties *item); /** Changes the active view to the next view */ void activateNextView(); /** Changes the active view to the previous view */ void activatePreviousView(); /** Changes the active view to the last view */ void activateLastView(); void setCss(const QString& styleSheet = QString()); void setCssFromFile(const QUrl& url); ViewSplitter *activeViewSplitter(); /** * This enum describes the directions * in which views can be re-arranged within the container * using the moveActiveView() method. */ enum MoveDirection { /** Moves the view to the left. */ MoveViewLeft, /** Moves the view to the right. */ MoveViewRight }; /** * Moves the active view within the container and * updates the order in which the views are shown * in the container's navigation widget. * * The default implementation does nothing. */ void moveActiveView(MoveDirection direction); /** Sets the menu to be shown when the new view button is clicked. * Only valid if the QuickNewView feature is enabled. * The default implementation does nothing. */ // TODO: Reenable this later. // void setNewViewMenu(QMenu *menu); void renameTab(int index); ViewManager *connectedViewManager(); void currentTabChanged(int index); void closeCurrentTab(); void wheelScrolled(int delta); void currentSessionControllerChanged(SessionController *controller); void tabDoubleClicked(int index); void openTabContextMenu(const QPoint &point); void setNavigationVisibility(ViewManager::NavigationVisibility navigationVisibility); void moveTabToWindow(int index, QWidget *window); void toggleMaximizeCurrentTerminal(); /* return the widget(int index) casted to TerminalDisplay* * * The only thing that this class holds are TerminalDisplays, so * this is the only thing that should be used to retrieve widgets. */ ViewSplitter *viewSplitterAt(int index); void connectTerminalDisplay(TerminalDisplay *display); void disconnectTerminalDisplay(TerminalDisplay *display); void moveTabLeft(); void moveTabRight(); /** * This enum describes where newly created tab should be placed. */ enum NewTabBehavior { /** Put newly created tab at the end. */ PutNewTabAtTheEnd = 0, /** Put newly created tab right after current tab. */ PutNewTabAfterCurrentTab = 1 }; void setNavigationBehavior(int behavior); void terminalDisplayDropped(TerminalDisplay* terminalDisplay); QSize sizeHint() const override; Q_SIGNALS: /** Emitted when the container has no more children */ void empty(TabbedViewContainer *container); /** Emitted when the user requests to open a new view */ void newViewRequest(); /** Requests creation of a new view, with the selected profile. */ void newViewWithProfileRequest(const Profile::Ptr&); /** a terminalDisplay was dropped in a child Splitter */ /** * Emitted when the user requests to move a view from another container * into this container. If 'success' is set to true by a connected slot * then the original view will be removed. * * @param index Index at which to insert the new view in the container * or -1 to append it. This index should be passed to addView() when * the new view has been created. * @param sessionControllerId The identifier of the view. */ void moveViewRequest(int index, int sessionControllerId); /** Emitted when the active view changes */ void activeViewChanged(TerminalDisplay *view); /** Emitted when a view is added to the container. */ void viewAdded(TerminalDisplay *view); /** Emitted when a view is removed from the container. */ void viewRemoved(TerminalDisplay *view); /** detach the specific tab */ void detachTab(int tabIdx); /** set the color tab */ void setColor(int index, const QColor &color); + /** remve the color tab */ + void removeColor(int idx); + protected: // close tabs and unregister void closeTerminalTab(int idx); void keyReleaseEvent(QKeyEvent *event) override; private Q_SLOTS: void viewDestroyed(QObject *view); void konsoleConfigChanged(); private: void forgetView(ViewSplitter *view); struct TabIconState { TabIconState(): readOnly(false), broadcast(false), notification(Session::NoNotification) {} bool readOnly; bool broadcast; Session::Notification notification; bool isAnyStateActive() const { return readOnly || broadcast || (notification != Session::NoNotification); } }; QHash _tabIconState; ViewManager *_connectedViewManager; QMenu *_contextPopupMenu; QToolButton *_newTabButton; QToolButton *_closeTabButton; int _contextMenuTabIndex; ViewManager::NavigationVisibility _navigationVisibility; NewTabBehavior _newTabBehavior; }; } #endif //VIEWCONTAINER_H