diff --git a/src/SessionManager.cpp b/src/SessionManager.cpp index 770a187f..dba59993 100644 --- a/src/SessionManager.cpp +++ b/src/SessionManager.cpp @@ -1,389 +1,395 @@ /* This source file is part of Konsole, a terminal emulator. Copyright 2006-2008 by 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 "SessionManager.h" #include "konsoledebug.h" // Qt #include #include // KDE #include #include // Konsole #include "Session.h" #include "ProfileManager.h" #include "History.h" #include "Enumeration.h" #include "TerminalDisplay.h" using namespace Konsole; SessionManager::SessionManager() : _sessions(QList()), _sessionProfiles(QHash()), _sessionRuntimeProfiles(QHash()), - _restoreMapping(QHash()) + _restoreMapping(QHash()), + _isClosingAllSessions(false) { ProfileManager *profileMananger = ProfileManager::instance(); connect(profileMananger, &Konsole::ProfileManager::profileChanged, this, &Konsole::SessionManager::profileChanged); } SessionManager::~SessionManager() { if (!_sessions.isEmpty()) { qCDebug(KonsoleDebug) << "Konsole SessionManager destroyed with" << _sessions.count() <<"session(s) still alive"; // ensure that the Session doesn't later try to call back and do things to the // SessionManager foreach (Session *session, _sessions) { disconnect(session, nullptr, this, nullptr); } } } Q_GLOBAL_STATIC(SessionManager, theSessionManager) SessionManager* SessionManager::instance() { return theSessionManager; } +bool SessionManager::isClosingAllSessions() const +{ + return _isClosingAllSessions; +} + void SessionManager::closeAllSessions() { - // close remaining sessions + _isClosingAllSessions = true; foreach (Session *session, _sessions) { session->close(); } _sessions.clear(); } const QList SessionManager::sessions() const { return _sessions; } Session *SessionManager::createSession(Profile::Ptr profile) { if (!profile) { profile = ProfileManager::instance()->defaultProfile(); } // TODO: check whether this is really needed if (!ProfileManager::instance()->loadedProfiles().contains(profile)) { ProfileManager::instance()->addProfile(profile); } //configuration information found, create a new session based on this auto session = new Session(); Q_ASSERT(session); applyProfile(session, profile, false); connect(session, &Konsole::Session::profileChangeCommandReceived, this, &Konsole::SessionManager::sessionProfileCommandReceived); //ask for notification when session dies connect(session, &Konsole::Session::finished, this, [this, session]() { sessionTerminated(session); }); //add session to active list _sessions << session; _sessionProfiles.insert(session, profile); return session; } void SessionManager::profileChanged(const Profile::Ptr &profile) { applyProfile(profile, true); } void SessionManager::sessionTerminated(Session *session) { Q_ASSERT(session); _sessions.removeAll(session); _sessionProfiles.remove(session); _sessionRuntimeProfiles.remove(session); session->deleteLater(); } void SessionManager::applyProfile(const Profile::Ptr &profile, bool modifiedPropertiesOnly) { foreach (Session *session, _sessions) { if (_sessionProfiles[session] == profile) { applyProfile(session, profile, modifiedPropertiesOnly); } } } Profile::Ptr SessionManager::sessionProfile(Session *session) const { return _sessionProfiles[session]; } void SessionManager::setSessionProfile(Session *session, Profile::Ptr profile) { if (!profile) { profile = ProfileManager::instance()->defaultProfile(); } Q_ASSERT(profile); if (_sessionProfiles[session] == profile) { return; } _sessionProfiles[session] = profile; applyProfile(session, profile, false); emit sessionUpdated(session); } void SessionManager::applyProfile(Session *session, const Profile::Ptr &profile, bool modifiedPropertiesOnly) { Q_ASSERT(profile); _sessionProfiles[session] = profile; ShouldApplyProperty apply(profile, modifiedPropertiesOnly); // Basic session settings if (apply.shouldApply(Profile::Name)) { session->setTitle(Session::NameRole, profile->name()); } if (apply.shouldApply(Profile::Command)) { session->setProgram(profile->command()); } if (apply.shouldApply(Profile::Arguments)) { session->setArguments(profile->arguments()); } if (apply.shouldApply(Profile::Directory)) { session->setInitialWorkingDirectory(profile->defaultWorkingDirectory()); } if (apply.shouldApply(Profile::Environment)) { // add environment variable containing home directory of current profile // (if specified) // prepend a 0 to the VERSION_MICRO part to make the version string // length consistent, so that conditions that depend on the exported // env var actually work // e.g. the second version should be higher than the first one: // 18.04.12 -> 180412 // 18.08.0 -> 180800 QStringList list = QStringLiteral(KONSOLE_VERSION).split(QLatin1Char('.')); if (list[2].length() < 2) { list[2].prepend(QLatin1String("0")); } const QString &numericVersion = list.join(QString()); QStringList environment = profile->environment(); environment << QStringLiteral("PROFILEHOME=%1").arg(profile->defaultWorkingDirectory()); environment << QStringLiteral("KONSOLE_PROFILE_NAME=%1").arg(profile->name()); environment << QStringLiteral("KONSOLE_VERSION=%1").arg(numericVersion); session->setEnvironment(environment); } if (apply.shouldApply(Profile::TerminalColumns) || apply.shouldApply(Profile::TerminalRows)) { const auto columns = profile->property(Profile::TerminalColumns); const auto rows = profile->property(Profile::TerminalRows); session->setPreferredSize(QSize(columns, rows)); } if (apply.shouldApply(Profile::Icon)) { session->setIconName(profile->icon()); } // Key bindings if (apply.shouldApply(Profile::KeyBindings)) { session->setKeyBindings(profile->keyBindings()); } // Tab formats // Preserve tab title changes, made by the user, when applying profile // changes or previewing color schemes if (apply.shouldApply(Profile::LocalTabTitleFormat) && !session->isTabTitleSetByUser()) { session->setTabTitleFormat(Session::LocalTabTitle, profile->localTabTitleFormat()); } if (apply.shouldApply(Profile::RemoteTabTitleFormat) && !session->isTabTitleSetByUser()) { session->setTabTitleFormat(Session::RemoteTabTitle, profile->remoteTabTitleFormat()); } // History if (apply.shouldApply(Profile::HistoryMode) || apply.shouldApply(Profile::HistorySize)) { const auto mode = profile->property(Profile::HistoryMode); switch (mode) { case Enum::NoHistory: session->setHistoryType(HistoryTypeNone()); break; case Enum::FixedSizeHistory: { int lines = profile->historySize(); session->setHistoryType(CompactHistoryType(lines)); break; } case Enum::UnlimitedHistory: session->setHistoryType(HistoryTypeFile()); break; } } // Terminal features if (apply.shouldApply(Profile::FlowControlEnabled)) { session->setFlowControlEnabled(profile->flowControlEnabled()); } // Encoding if (apply.shouldApply(Profile::DefaultEncoding)) { QByteArray name = profile->defaultEncoding().toUtf8(); session->setCodec(QTextCodec::codecForName(name)); } // Monitor Silence if (apply.shouldApply(Profile::SilenceSeconds)) { session->setMonitorSilenceSeconds(profile->silenceSeconds()); } } void SessionManager::sessionProfileCommandReceived(const QString &text) { auto *session = qobject_cast(sender()); Q_ASSERT(session); // store the font for each view if zoom was applied so that they can // be restored after applying the new profile QHash zoomFontSizes; foreach (TerminalDisplay *view, session->views()) { const QFont &viewCurFont = view->getVTFont(); if (viewCurFont != _sessionProfiles[session]->font()) { zoomFontSizes.insert(view, viewCurFont); } } ProfileCommandParser parser; QHash changes = parser.parse(text); Profile::Ptr newProfile; if (!_sessionRuntimeProfiles.contains(session)) { newProfile = new Profile(_sessionProfiles[session]); _sessionRuntimeProfiles.insert(session, newProfile); } else { newProfile = _sessionRuntimeProfiles[session]; } QHashIterator iter(changes); while (iter.hasNext()) { iter.next(); newProfile->setProperty(iter.key(), iter.value()); } _sessionProfiles[session] = newProfile; applyProfile(newProfile, true); emit sessionUpdated(session); if (!zoomFontSizes.isEmpty()) { QHashIterator it(zoomFontSizes); while (it.hasNext()) { it.next(); it.key()->setVTFont(it.value()); } } } void SessionManager::saveSessions(KConfig *config) { // The session IDs can't be restored. // So we need to map the old ID to the future new ID. int n = 1; _restoreMapping.clear(); foreach (Session *session, _sessions) { QString name = QLatin1String("Session") + QString::number(n); KConfigGroup group(config, name); group.writePathEntry("Profile", _sessionProfiles.value(session)->path()); session->saveSession(group); _restoreMapping.insert(session, n); n++; } KConfigGroup group(config, "Number"); group.writeEntry("NumberOfSessions", _sessions.count()); } int SessionManager::getRestoreId(Session *session) { return _restoreMapping.value(session); } void SessionManager::restoreSessions(KConfig *config) { KConfigGroup group(config, "Number"); const int sessions = group.readEntry("NumberOfSessions", 0); // Any sessions saved? for (int n = 1; n <= sessions; n++) { const QString name = QLatin1String("Session") + QString::number(n); KConfigGroup sessionGroup(config, name); const QString profile = sessionGroup.readPathEntry("Profile", QString()); Profile::Ptr ptr = ProfileManager::instance()->defaultProfile(); if (!profile.isEmpty()) { ptr = ProfileManager::instance()->loadProfile(profile); } Session *session = createSession(ptr); session->restoreSession(sessionGroup); } } Session *SessionManager::idToSession(int id) { foreach (Session *session, _sessions) { if (session->sessionId() == id) { return session; } } // this should not happen qCDebug(KonsoleDebug) << "Failed to find session for ID" << id; return nullptr; } diff --git a/src/SessionManager.h b/src/SessionManager.h index ee62d374..3bfc39fe 100644 --- a/src/SessionManager.h +++ b/src/SessionManager.h @@ -1,157 +1,159 @@ /* This source file is part of Konsole, a terminal emulator. Copyright 2006-2008 by 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 SESSIONMANAGER_H #define SESSIONMANAGER_H // Qt #include #include // Konsole #include "Profile.h" class KConfig; namespace Konsole { class Session; /** * Manages running terminal sessions. */ class KONSOLEPRIVATE_EXPORT SessionManager : public QObject { Q_OBJECT public: /** * Constructs a new session manager and loads information about the available * profiles. */ SessionManager(); /** * Destroys the SessionManager. All running sessions should be closed * (via closeAllSessions()) before the SessionManager is destroyed. */ ~SessionManager() Q_DECL_OVERRIDE; /** * Returns the session manager instance. */ static SessionManager *instance(); /** Kill all running sessions. */ void closeAllSessions(); /** * Creates a new session using the settings specified by the specified * profile. * * The new session has no views associated with it. A new TerminalDisplay view * must be created in order to display the output from the terminal session and * send keyboard or mouse input to it. * * @param profile A profile containing the settings for the new session. If @p profile * is null the default profile (see ProfileManager::defaultProfile()) will be used. */ Session *createSession(Profile::Ptr profile = Profile::Ptr()); /** Sets the profile associated with a session. */ void setSessionProfile(Session *session, Profile::Ptr profile); /** Returns the profile associated with a session. */ Profile::Ptr sessionProfile(Session *session) const; /** * Returns a list of active sessions. */ const QList sessions() const; // System session management void saveSessions(KConfig *config); void restoreSessions(KConfig *config); int getRestoreId(Session *session); Session *idToSession(int id); + bool isClosingAllSessions() const; Q_SIGNALS: /** * Emitted when a session's settings are updated to match * its current profile. */ void sessionUpdated(Session *session); protected Q_SLOTS: /** * Called to inform the manager that a session has finished executing. * * @param session The Session which has finished executing. */ void sessionTerminated(Session *session); private Q_SLOTS: void sessionProfileCommandReceived(const QString &text); void profileChanged(const Profile::Ptr &profile); private: Q_DISABLE_COPY(SessionManager) // applies updates to a profile // to all sessions currently using that profile // if modifiedPropertiesOnly is true, only properties which // are set in the profile @p key are updated void applyProfile(const Profile::Ptr &profile, bool modifiedPropertiesOnly); // applies updates to the profile @p profile to the session @p session // if modifiedPropertiesOnly is true, only properties which // are set in @p profile are update ( ie. properties for which profile->isPropertySet() // returns true ) void applyProfile(Session *session, const Profile::Ptr &profile, bool modifiedPropertiesOnly); QList _sessions; // list of running sessions QHash _sessionProfiles; QHash _sessionRuntimeProfiles; QHash _restoreMapping; + bool _isClosingAllSessions; }; /** Utility class to simplify code in SessionManager::applyProfile(). */ class ShouldApplyProperty { public: ShouldApplyProperty(const Profile::Ptr &profile, bool modifiedOnly) : _profile(profile), _modifiedPropertiesOnly(modifiedOnly) { } bool shouldApply(Profile::Property property) const { return !_modifiedPropertiesOnly || _profile->isPropertySet(property); } private: const Profile::Ptr _profile; bool _modifiedPropertiesOnly; }; } #endif //SESSIONMANAGER_H diff --git a/src/ViewContainer.cpp b/src/ViewContainer.cpp index a065eb88..7f1263be 100644 --- a/src/ViewContainer.cpp +++ b/src/ViewContainer.cpp @@ -1,576 +1,571 @@ /* 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 // Qt #include #include #include #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) { 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); #if defined(ENABLE_DETACHING) connect(tabBarWidget, &DetachableTabBar::detachTab, this, [this](int idx) { emit detachTab(idx); }); #endif connect(tabBarWidget, &DetachableTabBar::closeTab, this, &TabbedViewContainer::closeTerminalTab); connect(tabBarWidget, &DetachableTabBar::newTabRequest, this, [this]{ emit newViewRequest(); }); connect(this, &TabbedViewContainer::currentChanged, this, &TabbedViewContainer::currentTabChanged); // 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); #if defined(ENABLE_DETACHING) auto detachAction = _contextPopupMenu->addAction( QIcon::fromTheme(QStringLiteral("tab-detach")), i18nc("@action:inmenu", "&Detach Tab"), this, [this] { emit detachTab(_contextMenuTabIndex); } ); detachAction->setObjectName(QStringLiteral("tab-detach")); #endif 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); foreach(TerminalDisplay* terminal, splitter->findChildren()) { 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::addSplitter(ViewSplitter *viewSplitter, int index) { if (index == -1) { index = addTab(viewSplitter, QString()); } else { insertTab(index, viewSplitter, QString()); } connect(viewSplitter, &ViewSplitter::destroyed, this, &TabbedViewContainer::viewDestroyed); - foreach(TerminalDisplay* terminal, viewSplitter->findChildren()) { + auto terminalDisplays = viewSplitter->findChildren(); + foreach(TerminalDisplay* terminal, terminalDisplays) { connectTerminalDisplay(terminal); } + if (terminalDisplays.count() > 0) { + updateTitle(qobject_cast(terminalDisplays.at(0)->sessionController())); + } setCurrentIndex(index); } void TabbedViewContainer::addView(TerminalDisplay *view, int index) { auto viewSplitter = new ViewSplitter(); viewSplitter->addTerminalDisplay(view, Qt::Horizontal); auto item = view->sessionController(); if (index == -1) { index = addTab(viewSplitter, item->icon(), item->title()); } else { insertTab(index, viewSplitter, item->icon(), item->title()); } connectTerminalDisplay(view); connect(viewSplitter, &ViewSplitter::destroyed, this, &TabbedViewContainer::viewDestroyed); 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::focused, this, &Konsole::TabbedViewContainer::currentSessionControllerChanged); connect(item, &Konsole::ViewProperties::titleChanged, this, &Konsole::TabbedViewContainer::updateTitle); connect(item, &Konsole::ViewProperties::iconChanged, this, &Konsole::TabbedViewContainer::updateIcon); connect(item, &Konsole::ViewProperties::activity, this, &Konsole::TabbedViewContainer::updateActivity); } void TabbedViewContainer::disconnectTerminalDisplay(TerminalDisplay *display) { auto item = display->sessionController(); disconnect(item, &Konsole::SessionController::focused, this, &Konsole::TabbedViewContainer::currentSessionControllerChanged); disconnect(item, &Konsole::ViewProperties::titleChanged, this, &Konsole::TabbedViewContainer::updateTitle); disconnect(item, &Konsole::ViewProperties::iconChanged, this, &Konsole::TabbedViewContainer::updateIcon); disconnect(item, &Konsole::ViewProperties::activity, this, &Konsole::TabbedViewContainer::updateActivity); } void TabbedViewContainer::viewDestroyed(QObject *view) { auto widget = static_cast(view); const auto idx = indexOf(widget); removeTab(idx); forgetView(widget); } void TabbedViewContainer::forgetView(ViewSplitter *view) { Q_UNUSED(view); if (count() == 0) { emit empty(this); } } -void TabbedViewContainer::removeView(TerminalDisplay *view) -{ - /* TODO: This is absolutely the wrong place. - * We are removing a terminal display from a ViewSplitter, - * this should be inside of the view splitter or something. - */ - view->setParent(nullptr); -} - 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. #if defined(ENABLE_DETACHING) for(auto action : _contextPopupMenu->actions()) { if (action->objectName() == QStringLiteral("tab-detach")) { action->setEnabled(count() > 1); } } #endif /* This needs to nove away fro the tab or to lock every thing inside of it. * for now, disable. * */ // // Add the read-only action #if 0 auto sessionController = terminalAt(_contextMenuTabIndex)->sessionController(); if (sessionController != nullptr) { auto collection = sessionController->actionCollection(); auto readonlyAction = collection->action(QStringLiteral("view-readonly")); if (readonlyAction != nullptr) { const auto readonlyActions = _contextPopupMenu->actions(); _contextPopupMenu->insertAction(readonlyActions.last(), readonlyAction); } // Disable tab rename for (auto &action : _contextPopupMenu->actions()) { if (action->objectName() == QStringLiteral("edit-rename")) { action->setEnabled(!sessionController->isReadOnly()); break; } } } #endif _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); } 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::updateActivity(ViewProperties *item) { auto controller = qobject_cast(item); auto index = indexOf(controller->view()); if (index != currentIndex()) { setTabActivity(index, true); } } void TabbedViewContainer::currentSessionControllerChanged(SessionController *controller) { updateTitle(qobject_cast(controller)); } void TabbedViewContainer::updateTitle(ViewProperties *item) { auto controller = qobject_cast(item); auto topLevelSplitter = qobject_cast(controller->view()->parentWidget())->getToplevelSplitter(); 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::updateIcon(ViewProperties *item) { auto controller = qobject_cast(item); const int index = indexOf(controller->view()); setTabIcon(index, item->icon()); } void TabbedViewContainer::closeTerminalTab(int 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::maximizeCurrentTerminal() { activeViewSplitter()->maximizeCurrentTerminal(); } void TabbedViewContainer::restoreOtherTerminals() { activeViewSplitter()->restoreOtherTerminals(); } diff --git a/src/ViewContainer.h b/src/ViewContainer.h index 420314be..38cbd4df 100644 --- a/src/ViewContainer.h +++ b/src/ViewContainer.h @@ -1,211 +1,208 @@ /* 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 #include // Konsole #include "Profile.h" #include "ViewManager.h" // Qt class QPoint; class QToolButton; class QMenu; class QDropEvent; class QDragEnterEvent; namespace Konsole { class IncrementalSearchBar; 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() Q_DECL_OVERRIDE; /** Adds a new view to the container widget */ void addView(TerminalDisplay *view, int index = -1); void addSplitter(ViewSplitter *splitter, int index = -1); /** splits the currently focused Splitter */ void splitView(TerminalDisplay *view, Qt::Orientation orientation); - /** Removes a view from the container */ - void removeView(TerminalDisplay *view); - void setTabActivity(int index, bool activity); void updateTitle(ViewProperties *item); void updateIcon(ViewProperties *item); void updateActivity(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 maximizeCurrentTerminal(); void restoreOtherTerminals(); /* 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 *view); void disconnectTerminalDisplay(TerminalDisplay *view); 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&); /** * 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); protected: // close tabs and unregister void closeTerminalTab(int idx); void keyReleaseEvent(QKeyEvent *event) Q_DECL_OVERRIDE; private Q_SLOTS: void viewDestroyed(QObject *view); void konsoleConfigChanged(); private: void forgetView(ViewSplitter *view); ViewManager *_connectedViewManager; QMenu *_contextPopupMenu; QToolButton *_newTabButton; QToolButton *_closeTabButton; int _contextMenuTabIndex; ViewManager::NavigationVisibility _navigationVisibility; }; } #endif //VIEWCONTAINER_H diff --git a/src/ViewManager.cpp b/src/ViewManager.cpp index 054bd256..ee412d6e 100644 --- a/src/ViewManager.cpp +++ b/src/ViewManager.cpp @@ -1,1105 +1,1123 @@ /* Copyright 2006-2008 by 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 "ViewManager.h" #include // Qt #include #include // KDE #include #include #include #include // Konsole #include #include "ColorScheme.h" #include "ColorSchemeManager.h" #include "Session.h" #include "TerminalDisplay.h" #include "SessionController.h" #include "SessionManager.h" #include "ProfileManager.h" #include "ViewSplitter.h" #include "Enumeration.h" #include "ViewContainer.h" using namespace Konsole; int ViewManager::lastManagerId = 0; ViewManager::ViewManager(QObject *parent, KActionCollection *collection) : QObject(parent), _viewContainer(nullptr), _pluggedController(nullptr), _sessionMap(QHash()), _actionCollection(collection), _navigationMethod(NoNavigation), _navigationVisibility(NavigationNotSet), _newTabBehavior(PutNewTabAtTheEnd), _managerId(0), _terminalDisplayHistoryIndex(-1) { _viewContainer = createContainer(); // setup actions which are related to the views setupActions(); /* TODO: Reconnect // emit a signal when all of the views held by this view manager are destroyed */ connect(_viewContainer.data(), &Konsole::TabbedViewContainer::empty, this, &Konsole::ViewManager::empty); // listen for profile changes connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged, this, &Konsole::ViewManager::profileChanged); connect(SessionManager::instance(), &Konsole::SessionManager::sessionUpdated, this, &Konsole::ViewManager::updateViewsForSession); //prepare DBus communication new WindowAdaptor(this); _managerId = ++lastManagerId; QDBusConnection::sessionBus().registerObject(QLatin1String("/Windows/") + QString::number(_managerId), this); } ViewManager::~ViewManager() = default; int ViewManager::managerId() const { return _managerId; } QWidget *ViewManager::activeView() const { return _viewContainer->currentWidget(); } QWidget *ViewManager::widget() const { return _viewContainer; } void ViewManager::setupActions() { Q_ASSERT(_actionCollection); if (_actionCollection == nullptr) { return; } KActionCollection *collection = _actionCollection; // Let's reuse the pointer, no need not to. auto *action = new QAction(this); action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); action->setText(i18nc("@action:inmenu", "Split View Left/Right")); connect(action, &QAction::triggered, this, &ViewManager::splitLeftRight); collection->addAction(QStringLiteral("split-view-left-right"), action); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::Key_ParenLeft); action = new QAction(this); action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom"))); action->setText(i18nc("@action:inmenu", "Split View Top/Bottom")); connect(action, &QAction::triggered, this, &ViewManager::splitTopBottom); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::Key_ParenRight); collection->addAction(QStringLiteral("split-view-top-bottom"), action); action = new QAction(this); action->setText(i18nc("@action:inmenu", "Expand View")); action->setEnabled(false); connect(action, &QAction::triggered, this, &ViewManager::expandActiveContainer); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketRight); collection->addAction(QStringLiteral("expand-active-view"), action); _multiSplitterOnlyActions << action; action = new QAction(this); action->setText(i18nc("@action:inmenu", "Shrink View")); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketLeft); action->setEnabled(false); collection->addAction(QStringLiteral("shrink-active-view"), action); connect(action, &QAction::triggered, this, &ViewManager::shrinkActiveContainer); _multiSplitterOnlyActions << action; // Crashes on Mac. #if defined(ENABLE_DETACHING) action = collection->addAction(QStringLiteral("detach-view")); action->setEnabled(true); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach"))); action->setText(i18nc("@action:inmenu", "Detach Current &View")); connect(action, &QAction::triggered, this, &ViewManager::detachActiveView); _multiSplitterOnlyActions << action; // Ctrl+Shift+D is not used as a shortcut by default because it is too close // to Ctrl+D - which will terminate the session in many cases collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_H); action = collection->addAction(QStringLiteral("detach-tab")); action->setEnabled(true); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach"))); action->setText(i18nc("@action:inmenu", "Detach Current &Tab")); connect(action, &QAction::triggered, this, &ViewManager::detachActiveTab); _multiTabOnlyActions << action; // Ctrl+Shift+D is not used as a shortcut by default because it is too close // to Ctrl+D - which will terminate the session in many cases collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_L); #endif // keyboard shortcut only actions action = new QAction(i18nc("@action Shortcut entry", "Next Tab"), this); const QList nextViewActionKeys{Qt::SHIFT + Qt::Key_Right, Qt::CTRL + Qt::Key_PageDown}; collection->setDefaultShortcuts(action, nextViewActionKeys); collection->addAction(QStringLiteral("next-tab"), action); connect(action, &QAction::triggered, this, &ViewManager::nextView); _multiTabOnlyActions << action; // _viewSplitter->addAction(nextViewAction); action = new QAction(i18nc("@action Shortcut entry", "Previous Tab"), this); const QList previousViewActionKeys{Qt::SHIFT + Qt::Key_Left, Qt::CTRL + Qt::Key_PageUp}; collection->setDefaultShortcuts(action, previousViewActionKeys); collection->addAction(QStringLiteral("previous-tab"), action); connect(action, &QAction::triggered, this, &ViewManager::previousView); _multiTabOnlyActions << action; // _viewSplitter->addAction(previousViewAction); action = new QAction(i18nc("@action Shortcut entry", "Next View Container"), this); connect(action, &QAction::triggered, this, &ViewManager::focusUp); collection->addAction(QStringLiteral("next-container"), action); collection->setDefaultShortcut(action, Qt::SHIFT + Qt::CTRL + Qt::Key_Up); _viewContainer->addAction(action); _multiSplitterOnlyActions << action; action = new QAction(QStringLiteral("Focus Down"), this); collection->setDefaultShortcut(action, Qt::SHIFT + Qt::CTRL + Qt::Key_Down); connect(action, &QAction::triggered, this, &ViewManager::focusDown); _multiSplitterOnlyActions << action; _viewContainer->addAction(action); action = new QAction(i18nc("@action Shortcut entry", "Move Tab Left"), this); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Konsole::LEFT); connect(action, &QAction::triggered, this, &ViewManager::focusLeft); collection->addAction(QStringLiteral("move-view-left"), action); _multiSplitterOnlyActions << action; action = new QAction(i18nc("@action Shortcut entry", "Move Tab Right"), this); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Konsole::RIGHT); connect(action, &QAction::triggered, this, &ViewManager::focusRight); collection->addAction(QStringLiteral("move-view-right"), action); _multiSplitterOnlyActions << action; action = new QAction(i18nc("@action Shortcut entry", "Switch to Last Tab"), this); connect(action, &QAction::triggered, this, &ViewManager::lastView); collection->addAction(QStringLiteral("last-tab"), action); _multiTabOnlyActions << action; action = new QAction(i18nc("@action Shortcut entry", "Last Used Tabs"), this); connect(action, &QAction::triggered, this, &ViewManager::lastUsedView); collection->setDefaultShortcut(action, Qt::CTRL + Qt::Key_Tab); collection->addAction(QStringLiteral("last-used-tab"), action); _multiTabOnlyActions << action; action = new QAction(i18nc("@action Shortcut entry", "Toggle Between Two Tabs"), this); connect(action, &QAction::triggered, this, &Konsole::ViewManager::toggleTwoViews); collection->addAction(QStringLiteral("toggle-two-tabs"), action); _multiTabOnlyActions << action; action = new QAction(i18nc("@action Shortcut entry", "Last Used Tabs (Reverse)"), this); collection->addAction(QStringLiteral("last-used-tab-reverse"), action); collection->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_Tab); connect(action, &QAction::triggered, this, &ViewManager::lastUsedViewReverse); _multiTabOnlyActions << action; action = new QAction(i18nc("@action Shortcut entry", "Maximize current Terminal"), this); collection->addAction(QStringLiteral("maximize-current-terminal"), action); collection->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_E); connect(action, &QAction::triggered, _viewContainer, &TabbedViewContainer::maximizeCurrentTerminal); _multiSplitterOnlyActions << action; _viewContainer->addAction(action); action = new QAction(i18nc("@action Shortcut entry", "Restore other terminals"), this); collection->addAction(QStringLiteral("restore-other-terminals"), action); collection->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_Minus); connect(action, &QAction::triggered, _viewContainer, &TabbedViewContainer::restoreOtherTerminals); _multiSplitterOnlyActions << action; _viewContainer->addAction(action); // _viewSplitter->addAction(lastUsedViewReverseAction); const int SWITCH_TO_TAB_COUNT = 19; for (int i = 0; i < SWITCH_TO_TAB_COUNT; i++) { action = new QAction(i18nc("@action Shortcut entry", "Switch to Tab %1", i + 1), this); connect(action, &QAction::triggered, this, [this, i]() { switchToView(i); }); collection->addAction(QStringLiteral("switch-to-tab-%1").arg(i), action); } auto handleMultiTabActionsLambda = [=]{ const int count = _viewContainer->count(); foreach(QAction *action, _multiTabOnlyActions) { action->setEnabled(count > 1); } }; connect(_viewContainer, &TabbedViewContainer::viewAdded, this, handleMultiTabActionsLambda); connect(_viewContainer, &TabbedViewContainer::viewRemoved, this, handleMultiTabActionsLambda); connect(_viewContainer, &QTabWidget::currentChanged, this, &ViewManager::updateDetachViewState); // Initial state handleMultiTabActionsLambda(); updateDetachViewState(); } void ViewManager::switchToView(int index) { _viewContainer->setCurrentIndex(index); } void ViewManager::switchToTerminalDisplay(Konsole::TerminalDisplay* terminalDisplay) { auto splitter = qobject_cast(terminalDisplay->parentWidget()); auto toplevelSplitter = splitter->getToplevelSplitter(); // Focus the TermialDisplay terminalDisplay->setFocus(); if (_viewContainer->currentWidget() != toplevelSplitter) { // Focus the tab switchToView(_viewContainer->indexOf(toplevelSplitter)); } } void ViewManager::updateDetachViewState() { if (_viewContainer && _viewContainer->activeViewSplitter()) { const int splitCount = _viewContainer ->activeViewSplitter() ->getToplevelSplitter() ->findChildren() .count(); foreach (QAction *action, _multiSplitterOnlyActions) { action->setEnabled(splitCount > 1); } } } void ViewManager::focusUp() { _viewContainer->activeViewSplitter()->focusUp(); } void ViewManager::focusDown() { _viewContainer->activeViewSplitter()->focusDown(); } void ViewManager::focusLeft() { _viewContainer->activeViewSplitter()->focusLeft(); } void ViewManager::focusRight() { _viewContainer->activeViewSplitter()->focusRight(); } void ViewManager::moveActiveViewLeft() { _viewContainer->moveActiveView(TabbedViewContainer::MoveViewLeft); } void ViewManager::moveActiveViewRight() { _viewContainer->moveActiveView(TabbedViewContainer::MoveViewRight); } void ViewManager::nextContainer() { // _viewSplitter->activateNextContainer(); } void ViewManager::nextView() { _viewContainer->activateNextView(); } void ViewManager::previousView() { _viewContainer->activatePreviousView(); } void ViewManager::lastView() { _viewContainer->activateLastView(); } void ViewManager::activateLastUsedView(bool reverse) { if (_terminalDisplayHistory.count() <= 1) { return; } if (_terminalDisplayHistoryIndex == -1) { _terminalDisplayHistoryIndex = reverse ? _terminalDisplayHistory.count() - 1 : 1; } else if (reverse) { if (_terminalDisplayHistoryIndex == 0) { _terminalDisplayHistoryIndex = _terminalDisplayHistory.count() - 1; } else { _terminalDisplayHistoryIndex--; } } else { if (_terminalDisplayHistoryIndex >= _terminalDisplayHistory.count() - 1) { _terminalDisplayHistoryIndex = 0; } else { _terminalDisplayHistoryIndex++; } } switchToTerminalDisplay(_terminalDisplayHistory[_terminalDisplayHistoryIndex]); } void ViewManager::lastUsedView() { activateLastUsedView(false); } void ViewManager::lastUsedViewReverse() { activateLastUsedView(true); } void ViewManager::toggleTwoViews() { if (_terminalDisplayHistory.count() <= 1) { return; } switchToTerminalDisplay(_terminalDisplayHistory.at(1)); } void ViewManager::detachActiveView() { #if !defined(ENABLE_DETACHING) return; #endif // find the currently active view and remove it from its container if ((_viewContainer->findChildren()).count() > 1) { auto activeSplitter = _viewContainer->activeViewSplitter(); auto terminal = activeSplitter->activeTerminalDisplay(); auto newSplitter = new ViewSplitter(); newSplitter->addTerminalDisplay(terminal, Qt::Horizontal); QHash detachedSessions = forgetAll(newSplitter); emit terminalsDetached(newSplitter, detachedSessions); focusAnotherTerminal(activeSplitter->getToplevelSplitter()); updateDetachViewState(); } } void ViewManager::detachActiveTab() { const int currentIdx = _viewContainer->currentIndex(); detachTab(currentIdx); } void ViewManager::detachTab(int tabIdx) { #if !defined(ENABLE_DETACHING) return; #endif ViewSplitter* splitter = _viewContainer->viewSplitterAt(tabIdx); QHash detachedSessions = forgetAll(_viewContainer->viewSplitterAt(tabIdx)); emit terminalsDetached(splitter, detachedSessions); } QHash ViewManager::forgetAll(ViewSplitter* splitter) { splitter->setParent(nullptr); QHash detachedSessions; foreach(TerminalDisplay* terminal, splitter->findChildren()) { Session* session = forgetTerminal(terminal); detachedSessions[terminal] = session; } return detachedSessions; } Session* ViewManager::forgetTerminal(TerminalDisplay* terminal) { removeController(terminal->sessionController()); auto session = _sessionMap.take(terminal); if (session != nullptr) { disconnect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished); } _viewContainer->disconnectTerminalDisplay(terminal); updateTerminalDisplayHistory(terminal, true); return session; } void ViewManager::sessionFinished() { // if this slot is called after the view manager's main widget // has been destroyed, do nothing if (_viewContainer.isNull()) { return; } auto *session = qobject_cast(sender()); Q_ASSERT(session); auto view = _sessionMap.key(session); _sessionMap.remove(view); + if (SessionManager::instance()->isClosingAllSessions()){ + return; + } + // Before deleting the view, let's unmaximize if it's maximized. auto splitter = qobject_cast(view->parentWidget()); auto toplevelSplitter = splitter->getToplevelSplitter(); + toplevelSplitter->restoreOtherTerminals(); - _viewContainer->removeView(view); view->deleteLater(); + // Only remove the controller from factory() if it's actually controlling // the session from the sender. // This fixes BUG: 348478 - messed up menus after a detached tab is closed if ((!_pluggedController.isNull()) && (_pluggedController->session() == session)) { // This is needed to remove this controller from factory() in // order to prevent BUG: 185466 - disappearing menu popup emit unplugController(_pluggedController); } - updateTerminalDisplayHistory(view, true); - focusAnotherTerminal(toplevelSplitter); - updateDetachViewState(); + if (_sessionMap.size() > 0) { + updateTerminalDisplayHistory(view, true); + focusAnotherTerminal(toplevelSplitter); + updateDetachViewState(); + } } void ViewManager::focusAnotherTerminal(ViewSplitter *toplevelSplitter) { auto tabTterminalDisplays = toplevelSplitter->findChildren(); + if (tabTterminalDisplays.count() == 0) { + return; + } + if (tabTterminalDisplays.count() > 1) { // Give focus to the last used terminal in this tab for (auto *historyItem : _terminalDisplayHistory) { for (auto *terminalDisplay : tabTterminalDisplays) { if (terminalDisplay == historyItem) { terminalDisplay->setFocus(Qt::OtherFocusReason); return; } } } } else if (_terminalDisplayHistory.count() >= 1) { // Give focus to the last used terminal tab switchToTerminalDisplay(_terminalDisplayHistory[0]); } } void ViewManager::viewActivated(TerminalDisplay *view) { Q_ASSERT(view != nullptr); // focus the activated view, this will cause the SessionController // to notify the world that the view has been focused and the appropriate UI // actions will be plugged in. view->setFocus(Qt::OtherFocusReason); } void ViewManager::splitLeftRight() { splitView(Qt::Horizontal); } void ViewManager::splitTopBottom() { splitView(Qt::Vertical); } void ViewManager::splitView(Qt::Orientation orientation) { auto viewSplitter = qobject_cast(_viewContainer->currentWidget()); // get the currently applied profile and use it to create the new tab. auto *currentDisplay = viewSplitter->findChild(); auto profile = SessionManager::instance()->sessionProfile(_sessionMap[currentDisplay]); // Create a new session with the selected profile. auto *session = SessionManager::instance()->createSession(profile); session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); auto terminalDisplay = createView(session); _viewContainer->splitView(terminalDisplay, orientation); updateDetachViewState(); // focus the new container terminalDisplay->setFocus(); } void ViewManager::expandActiveContainer() { _viewContainer->activeViewSplitter()->adjustActiveTerminalDisplaySize(10); } void ViewManager::shrinkActiveContainer() { _viewContainer->activeViewSplitter()->adjustActiveTerminalDisplaySize(-10); } SessionController *ViewManager::createController(Session *session, TerminalDisplay *view) { // create a new controller for the session, and ensure that this view manager // is notified when the view gains the focus auto controller = new SessionController(session, view, this); connect(controller, &Konsole::SessionController::focused, this, &Konsole::ViewManager::controllerChanged); connect(session, &Konsole::Session::destroyed, controller, &Konsole::SessionController::deleteLater); connect(session, &Konsole::Session::primaryScreenInUse, controller, &Konsole::SessionController::setupPrimaryScreenSpecificActions); connect(session, &Konsole::Session::selectionChanged, controller, &Konsole::SessionController::selectionChanged); connect(view, &Konsole::TerminalDisplay::destroyed, controller, &Konsole::SessionController::deleteLater); // if this is the first controller created then set it as the active controller if (_pluggedController.isNull()) { controllerChanged(controller); } return controller; } // should this be handed by ViewManager::unplugController signal void ViewManager::removeController(SessionController* controller) { disconnect(controller, &Konsole::SessionController::focused, this, &Konsole::ViewManager::controllerChanged); if (_pluggedController == controller) { _pluggedController = nullptr; } controller->deleteLater(); } void ViewManager::controllerChanged(SessionController *controller) { if (controller == _pluggedController) { return; } updateTerminalDisplayHistory(controller->view()); _pluggedController = controller; emit activeViewChanged(controller); } SessionController *ViewManager::activeViewController() const { return _pluggedController; } void ViewManager::attachView(TerminalDisplay *terminal, Session *session) { connect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished, Qt::UniqueConnection); _sessionMap[terminal] = session; createController(session, terminal); updateDetachViewState(); _terminalDisplayHistory.append(terminal); } TerminalDisplay *ViewManager::createView(Session *session) { // notify this view manager when the session finishes so that its view // can be deleted // // Use Qt::UniqueConnection to avoid duplicate connection connect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished, Qt::UniqueConnection); TerminalDisplay *display = createTerminalDisplay(session); const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); applyProfileToView(display, profile); // set initial size const QSize &preferredSize = session->preferredSize(); display->setSize(preferredSize.width(), preferredSize.height()); createController(session, display); _sessionMap[display] = session; session->addView(display); _terminalDisplayHistory.append(display); // tell the session whether it has a light or dark background session->setDarkBackground(colorSchemeForProfile(profile)->hasDarkBackground()); display->setFocus(Qt::OtherFocusReason); // updateDetachViewState(); return display; } TabbedViewContainer *ViewManager::createContainer() { auto *container = new TabbedViewContainer(this, nullptr); container->setNavigationVisibility(_navigationVisibility); connect(container, &TabbedViewContainer::detachTab, this, &ViewManager::detachTab); // connect signals and slots connect(container, &Konsole::TabbedViewContainer::viewAdded, this, [this, container]() { containerViewsChanged(container); }); connect(container, &Konsole::TabbedViewContainer::viewRemoved, this, [this, container]() { containerViewsChanged(container); }); connect(container, &TabbedViewContainer::newViewRequest, this, &ViewManager::newViewRequest); connect(container, &Konsole::TabbedViewContainer::newViewWithProfileRequest, this, &Konsole::ViewManager::newViewWithProfileRequest); connect(container, &Konsole::TabbedViewContainer::viewRemoved, this, &Konsole::ViewManager::viewDestroyed); connect(container, &Konsole::TabbedViewContainer::activeViewChanged, this, &Konsole::ViewManager::viewActivated); return container; } void ViewManager::setNavigationMethod(NavigationMethod method) { Q_ASSERT(_actionCollection); if (_actionCollection == nullptr) { return; } KActionCollection *collection = _actionCollection; _navigationMethod = method; // FIXME: The following disables certain actions for the KPart that it // doesn't actually have a use for, to avoid polluting the action/shortcut // namespace of an application using the KPart (otherwise, a shortcut may // be in use twice, and the user gets to see an "ambiguous shortcut over- // load" error dialog). However, this approach sucks - it's the inverse of // what it should be. Rather than disabling actions not used by the KPart, // a method should be devised to only enable those that are used, perhaps // by using a separate action collection. const bool enable = (method != NoNavigation); auto enableAction = [&enable, &collection](const QString& actionName) { auto *action = collection->action(actionName); if (action != nullptr) { action->setEnabled(enable); } }; enableAction(QStringLiteral("next-view")); enableAction(QStringLiteral("previous-view")); enableAction(QStringLiteral("last-tab")); enableAction(QStringLiteral("last-used-tab")); enableAction(QStringLiteral("last-used-tab-reverse")); enableAction(QStringLiteral("split-view-left-right")); enableAction(QStringLiteral("split-view-top-bottom")); enableAction(QStringLiteral("rename-session")); enableAction(QStringLiteral("move-view-left")); enableAction(QStringLiteral("move-view-right")); } ViewManager::NavigationMethod ViewManager::navigationMethod() const { return _navigationMethod; } void ViewManager::containerViewsChanged(TabbedViewContainer *container) { // TODO: Verify that this is right. emit viewPropertiesChanged(viewProperties()); } void ViewManager::viewDestroyed(QWidget *view) { // Note: the received QWidget has already been destroyed, so // using dynamic_cast<> or qobject_cast<> does not work here // We only need the pointer address to look it up below auto *display = reinterpret_cast(view); // 1. detach view from session // 2. if the session has no views left, close it Session *session = _sessionMap[ display ]; _sessionMap.remove(display); if (session != nullptr) { if (session->views().count() == 0) { session->close(); } } //we only update the focus if the splitter is still alive updateDetachViewState(); // The below causes the menus to be messed up // Only happens when using the tab bar close button // if (_pluggedController) // emit unplugController(_pluggedController); } TerminalDisplay *ViewManager::createTerminalDisplay(Session *session) { auto display = new TerminalDisplay(nullptr); display->setRandomSeed(session->sessionId() * 31); return display; } const ColorScheme *ViewManager::colorSchemeForProfile(const Profile::Ptr &profile) { const ColorScheme *colorScheme = ColorSchemeManager::instance()-> findColorScheme(profile->colorScheme()); if (colorScheme == nullptr) { colorScheme = ColorSchemeManager::instance()->defaultColorScheme(); } Q_ASSERT(colorScheme); return colorScheme; } bool ViewManager::profileHasBlurEnabled(const Profile::Ptr &profile) { return colorSchemeForProfile(profile)->blur(); } void ViewManager::applyProfileToView(TerminalDisplay *view, const Profile::Ptr &profile) { Q_ASSERT(profile); view->applyProfile(profile); emit updateWindowIcon(); emit blurSettingChanged(view->colorScheme()->blur()); } void ViewManager::updateViewsForSession(Session *session) { const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); const QList sessionMapKeys = _sessionMap.keys(session); foreach (TerminalDisplay *view, sessionMapKeys) { applyProfileToView(view, profile); } } void ViewManager::profileChanged(const Profile::Ptr &profile) { // update all views associated with this profile QHashIterator iter(_sessionMap); while (iter.hasNext()) { iter.next(); // if session uses this profile, update the display if (iter.key() != nullptr && iter.value() != nullptr && SessionManager::instance()->sessionProfile(iter.value()) == profile) { applyProfileToView(iter.key(), profile); } } } QList ViewManager::viewProperties() const { QList list; TabbedViewContainer *container = _viewContainer; if (container == nullptr) { return {}; } auto terminalContainers = _viewContainer->findChildren(); list.reserve(terminalContainers.size()); for(auto terminalDisplay : _viewContainer->findChildren()) { list.append(terminalDisplay->sessionController()); } return list; } -void ViewManager::saveSessions(KConfigGroup &group) +namespace { +QJsonObject saveSessionTerminal(TerminalDisplay *terminalDisplay) { - // find all unique session restore IDs - QList ids; - QSet unique; - int tab = 1; + QJsonObject thisTerminal; + auto terminalSession = terminalDisplay->sessionController()->session(); + const int sessionRestoreId = SessionManager::instance()->getRestoreId(terminalSession); + thisTerminal.insert(QStringLiteral("SessionRestoreId"), sessionRestoreId); + return thisTerminal; +} - TabbedViewContainer *container = _viewContainer; +QJsonObject saveSessionsRecurse(QSplitter *splitter) { + QJsonObject thisSplitter; + thisSplitter.insert( + QStringLiteral("Orientation"), + splitter->orientation() == Qt::Horizontal ? QStringLiteral("Horizontal") + : QStringLiteral("Vertical") + ); - // first: sessions in the active container, preserving the order - Q_ASSERT(container); - if (container == nullptr) { - return; - } - ids.reserve(container->count()); - - //TODO: Handle sessions -#if 0 - auto *activeview = qobject_cast(container->currentWidget()); - for (int i = 0, end = container->count(); i < end; i++) { - auto *view = qobject_cast(container->widget(i)); - Q_ASSERT(view); - - Session *session = _sessionMap[view]; - ids << SessionManager::instance()->getRestoreId(session); - unique.insert(session); - if (view == activeview) { - group.writeEntry("Active", tab); - } - tab++; - } -#endif + QJsonArray internalWidgets; + for (int i = 0; i < splitter->count(); i++) { + auto *widget = splitter->widget(i); + auto *maybeSplitter = qobject_cast(widget); + auto *maybeTerminalDisplay = qobject_cast(widget); - // second: all other sessions, in random order - // we don't want to have sessions restored that are not connected - foreach (Session *session, _sessionMap) { - if (!unique.contains(session)) { - ids << SessionManager::instance()->getRestoreId(session); - unique.insert(session); + if (maybeSplitter != nullptr) { + internalWidgets.append(saveSessionsRecurse(maybeSplitter)); + } else if (maybeTerminalDisplay) { + internalWidgets.append(saveSessionTerminal(maybeTerminalDisplay)); } } - - group.writeEntry("Sessions", ids); + thisSplitter.insert(QStringLiteral("Widgets"), internalWidgets); + return thisSplitter; } -TabbedViewContainer *ViewManager::activeContainer() +} // namespace + +void ViewManager::saveSessions(KConfigGroup &group) { - return _viewContainer; + QJsonArray rootArray; + for(int i = 0; i < _viewContainer->count(); i++) { + QSplitter *splitter = qobject_cast(_viewContainer->widget(i)); + rootArray.append(saveSessionsRecurse(splitter)); + } + + group.writeEntry("Tabs", QJsonDocument(rootArray).toJson(QJsonDocument::Compact)); + group.writeEntry("Active", _viewContainer->currentIndex()); } -void ViewManager::restoreSessions(const KConfigGroup &group) +namespace { + +ViewSplitter *restoreSessionsSplitterRecurse(const QJsonObject& jsonSplitter, ViewManager *manager) { - QList ids = group.readEntry("Sessions", QList()); - int activeTab = group.readEntry("Active", 0); - TerminalDisplay *display = nullptr; + auto splitterWidgets = jsonSplitter[QStringLiteral("Widgets")].toArray(); + auto orientation = (jsonSplitter[QStringLiteral("Orientation")].toString() == QStringLiteral("Horizontal")) + ? Qt::Horizontal : Qt::Vertical; - int tab = 1; - foreach (int id, ids) { - Session *session = SessionManager::instance()->idToSession(id); + auto *currentSplitter = new ViewSplitter(); + currentSplitter->setOrientation(orientation); - if (session == nullptr) { - qWarning() << "Unable to load session with id" << id; - // Force a creation of a default session below - ids.clear(); - break; - } + for (const auto& widgetJsonValue : splitterWidgets) { + const auto widgetJsonObject = widgetJsonValue.toObject(); + const auto sessionIterator = widgetJsonObject.constFind(QStringLiteral("SessionRestoreId")); - createView(session); - if (!session->isRunning()) { - session->run(); - } - if (tab++ == activeTab) { - display = qobject_cast(activeView()); + if (sessionIterator != widgetJsonObject.constEnd()) { + Session *session = SessionManager::instance()->idToSession(sessionIterator->toInt()); + auto newView = manager->createView(session); + currentSplitter->addWidget(newView); + } else { + auto nextSplitter = restoreSessionsSplitterRecurse(widgetJsonObject, manager); + currentSplitter->addWidget(nextSplitter); } } + return currentSplitter; +} - if (display != nullptr) { - activeContainer()->setCurrentWidget(display); - display->setFocus(Qt::OtherFocusReason); +} // namespace +void ViewManager::restoreSessions(const KConfigGroup &group) +{ + const auto tabList = group.readEntry("Tabs", QByteArray("[]")); + const auto jsonTabs = QJsonDocument::fromJson(tabList).array(); + for (const auto& jsonSplitter : jsonTabs) { + auto topLevelSplitter = restoreSessionsSplitterRecurse(jsonSplitter.toObject(), this); + _viewContainer->addSplitter(topLevelSplitter, _viewContainer->count()); } - if (ids.isEmpty()) { // Session file is unusable, start default Profile + if (jsonTabs.isEmpty()) { // Session file is unusable, start default Profile Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); Session *session = SessionManager::instance()->createSession(profile); createView(session); if (!session->isRunning()) { session->run(); } } } +TabbedViewContainer *ViewManager::activeContainer() +{ + return _viewContainer; +} + int ViewManager::sessionCount() { return _sessionMap.size(); } QStringList ViewManager::sessionList() { QStringList ids; QHash::const_iterator i; for (i = _sessionMap.constBegin(); i != _sessionMap.constEnd(); ++i) { ids.append(QString::number(i.value()->sessionId())); } return ids; } int ViewManager::currentSession() { QHash::const_iterator i; for (i = _sessionMap.constBegin(); i != _sessionMap.constEnd(); ++i) { if (i.key()->isVisible()) { return i.value()->sessionId(); } } return -1; } void ViewManager::setCurrentSession(int sessionId) { QHash::const_iterator i; for (i = _sessionMap.constBegin(); i != _sessionMap.constEnd(); ++i) { if (i.value()->sessionId() == sessionId) { - TabbedViewContainer *container = activeContainer(); - if (container != nullptr) { - container->setCurrentWidget(i.key()); - } + i.key()->setFocus(Qt::OtherFocusReason); + return; } } } int ViewManager::newSession() { Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); Session *session = SessionManager::instance()->createSession(profile); session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); createView(session); session->run(); return session->sessionId(); } int ViewManager::newSession(const QString &profile) { const QList profilelist = ProfileManager::instance()->allProfiles(); Profile::Ptr profileptr = ProfileManager::instance()->defaultProfile(); for (const auto &i : profilelist) { if (i->name() == profile) { profileptr = i; break; } } Session *session = SessionManager::instance()->createSession(profileptr); session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); createView(session); session->run(); return session->sessionId(); } int ViewManager::newSession(const QString &profile, const QString &directory) { const QList profilelist = ProfileManager::instance()->allProfiles(); Profile::Ptr profileptr = ProfileManager::instance()->defaultProfile(); for (const auto &i : profilelist) { if (i->name() == profile) { profileptr = i; break; } } Session *session = SessionManager::instance()->createSession(profileptr); session->setInitialWorkingDirectory(directory); session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); createView(session); session->run(); return session->sessionId(); } QString ViewManager::defaultProfile() { return ProfileManager::instance()->defaultProfile()->name(); } QStringList ViewManager::profileList() { return ProfileManager::instance()->availableProfileNames(); } void ViewManager::nextSession() { nextView(); } void ViewManager::prevSession() { previousView(); } void ViewManager::moveSessionLeft() { moveActiveViewLeft(); } void ViewManager::moveSessionRight() { moveActiveViewRight(); } void ViewManager::setTabWidthToText(bool setTabWidthToText) { _viewContainer->tabBar()->setExpanding(!setTabWidthToText); _viewContainer->tabBar()->update(); } void ViewManager::setNavigationVisibility(NavigationVisibility navigationVisibility) { if (_navigationVisibility != navigationVisibility) { _navigationVisibility = navigationVisibility; _viewContainer->setNavigationVisibility(navigationVisibility); } } void ViewManager::setNavigationBehavior(int behavior) { _newTabBehavior = static_cast(behavior); } void ViewManager::updateTerminalDisplayHistory(TerminalDisplay* terminalDisplay, bool remove) { if (terminalDisplay == nullptr) { if (_terminalDisplayHistoryIndex >= 0) { // This is the case when we finished walking through the history // (i.e. when Ctrl-Tab has been released) terminalDisplay = _terminalDisplayHistory[_terminalDisplayHistoryIndex]; _terminalDisplayHistoryIndex = -1; } else { return; } } if (_terminalDisplayHistoryIndex >= 0 && !remove) { // Do not reorder the tab history while we are walking through it return; } for (int i = 0; i < _terminalDisplayHistory.count(); i++) { if (_terminalDisplayHistory[i] == terminalDisplay) { _terminalDisplayHistory.removeAt(i); if (!remove) { _terminalDisplayHistory.prepend(terminalDisplay); } break; } } } diff --git a/src/ViewSplitter.cpp b/src/ViewSplitter.cpp index 8e21e7a0..8e756d5e 100644 --- a/src/ViewSplitter.cpp +++ b/src/ViewSplitter.cpp @@ -1,219 +1,220 @@ /* 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 "ViewSplitter.h" // Qt #include #include #include // Konsole #include "ViewContainer.h" #include "TerminalDisplay.h" using Konsole::ViewSplitter; using Konsole::TerminalDisplay; //TODO: Connect the TerminalDisplay destroyed signal here. ViewSplitter::ViewSplitter(QWidget *parent) : QSplitter(parent) { } void ViewSplitter::adjustActiveTerminalDisplaySize(int percentage) { const int containerIndex = indexOf(activeTerminalDisplay()); Q_ASSERT(containerIndex != -1); QList containerSizes = sizes(); const int oldSize = containerSizes[containerIndex]; const int newSize = static_cast(oldSize * (1.0 + percentage / 100.0)); const int perContainerDelta = (count() == 1) ? 0 : ((newSize - oldSize) / (count() - 1)) * (-1); for (int& size : containerSizes) { size += perContainerDelta; } containerSizes[containerIndex] = newSize; setSizes(containerSizes); } +// Get the first splitter that's a parent of the current focused widget. ViewSplitter *ViewSplitter::activeSplitter() { QWidget *widget = focusWidget() != nullptr ? focusWidget() : this; ViewSplitter *splitter = nullptr; while ((splitter == nullptr) && (widget != nullptr)) { splitter = qobject_cast(widget); widget = widget->parentWidget(); } Q_ASSERT(splitter); return splitter; } void ViewSplitter::updateSizes() { const int space = (orientation() == Qt::Horizontal ? width() : height()) / count(); setSizes(QVector(count(), space).toList()); } void ViewSplitter::addTerminalDisplay(TerminalDisplay *terminalDisplay, Qt::Orientation containerOrientation) { ViewSplitter *splitter = activeSplitter(); if (splitter->count() < 2) { splitter->addWidget(terminalDisplay); splitter->setOrientation(containerOrientation); } else if (containerOrientation == splitter->orientation()) { auto activeDisplay = splitter->activeTerminalDisplay(); if (!activeDisplay) { splitter->addWidget(terminalDisplay); } else { const int currentIndex = splitter->indexOf(activeDisplay); splitter->insertWidget(currentIndex, terminalDisplay); } } else { auto newSplitter = new ViewSplitter(); TerminalDisplay *oldTerminalDisplay = splitter->activeTerminalDisplay(); const int oldContainerIndex = splitter->indexOf(oldTerminalDisplay); newSplitter->addWidget(oldTerminalDisplay); newSplitter->addWidget(terminalDisplay); newSplitter->setOrientation(containerOrientation); newSplitter->updateSizes(); newSplitter->show(); splitter->insertWidget(oldContainerIndex, newSplitter); } splitter->updateSizes(); } void ViewSplitter::childEvent(QChildEvent *event) { QSplitter::childEvent(event); if (event->removed()) { if (count() == 0) { deleteLater(); } if (!findChild()) { deleteLater(); } } } void ViewSplitter::handleFocusDirection(Qt::Orientation orientation, int direction) { auto terminalDisplay = activeTerminalDisplay(); auto parentSplitter = qobject_cast(terminalDisplay->parentWidget()); auto topSplitter = parentSplitter->getToplevelSplitter(); const auto handleWidth = parentSplitter->handleWidth() <= 1 ? 4 : parentSplitter->handleWidth(); const auto start = QPoint(terminalDisplay->x(), terminalDisplay->y()); const auto startMapped = parentSplitter->mapTo(topSplitter, start); const int newX = orientation != Qt::Horizontal ? startMapped.x() + handleWidth : direction == 1 ? startMapped.x() + terminalDisplay->width() + handleWidth : startMapped.x() - handleWidth; const int newY = orientation != Qt::Vertical ? startMapped.y() + handleWidth : direction == 1 ? startMapped.y() + terminalDisplay->height() + handleWidth : startMapped.y() - handleWidth; const auto newPoint = QPoint(newX, newY); auto child = topSplitter->childAt(newPoint); qDebug() << "Handling focus"; if (TerminalDisplay* terminal = qobject_cast(child)) { terminal->setFocus(Qt::OtherFocusReason); } else if (qobject_cast(child)) { auto terminal = qobject_cast(child->parent()); terminal->setFocus(Qt::OtherFocusReason); } else if (qobject_cast(child)) { auto targetSplitter = qobject_cast(child->parent()); auto terminal = qobject_cast(targetSplitter->widget(0)); terminal->setFocus(Qt::OtherFocusReason); } } void ViewSplitter::focusUp() { handleFocusDirection(Qt::Vertical, -1); } void ViewSplitter::focusDown() { handleFocusDirection(Qt::Vertical, +1); } void ViewSplitter::focusLeft() { handleFocusDirection(Qt::Horizontal, -1); } void ViewSplitter::focusRight() { handleFocusDirection(Qt::Horizontal, +1); } TerminalDisplay *ViewSplitter::activeTerminalDisplay() const { auto focusedWidget = qobject_cast(focusWidget()); return focusedWidget ? focusedWidget : findChild(); } void ViewSplitter::maximizeCurrentTerminal() { handleMinimizeMaximize(true); } void ViewSplitter::restoreOtherTerminals() { handleMinimizeMaximize(false); } void ViewSplitter::handleMinimizeMaximize(bool maximize) { auto viewSplitter = getToplevelSplitter(); auto terminalDisplays = viewSplitter->findChildren(); auto currentActiveTerminal = viewSplitter->activeTerminalDisplay(); auto method = maximize ? &QWidget::hide : &QWidget::show; for(auto terminal : terminalDisplays) { if (Q_LIKELY(currentActiveTerminal != terminal)) { (terminal->*method)(); } } } ViewSplitter *ViewSplitter::getToplevelSplitter() { ViewSplitter *current = this; while(qobject_cast(current->parentWidget())) { current = qobject_cast(current->parentWidget()); } return current; }