diff --git a/desktop/konsoleui.rc b/desktop/konsoleui.rc --- a/desktop/konsoleui.rc +++ b/desktop/konsoleui.rc @@ -1,6 +1,6 @@ - + File @@ -25,6 +25,7 @@ + diff --git a/src/Application.h b/src/Application.h --- a/src/Application.h +++ b/src/Application.h @@ -25,6 +25,7 @@ // Konsole #include "Profile.h" +#include "ViewSplitter.h" namespace Konsole { class MainWindow; @@ -68,7 +69,7 @@ private Q_SLOTS: void createWindow(const Profile::Ptr &profile, const QString &directory); - void detachView(Session *session); + void detachTerminals(ViewSplitter *splitter, const QHash& sessionsMap); void toggleBackgroundInstance(); diff --git a/src/Application.cpp b/src/Application.cpp --- a/src/Application.cpp +++ b/src/Application.cpp @@ -43,6 +43,8 @@ #include "ViewManager.h" #include "SessionController.h" #include "WindowSystemInfo.h" +#include "ViewContainer.h" +#include "TerminalDisplay.h" using namespace Konsole; @@ -168,32 +170,32 @@ connect(window, &Konsole::MainWindow::newWindowRequest, this, &Konsole::Application::createWindow); - connect(window, &Konsole::MainWindow::viewDetached, this, &Konsole::Application::detachView); + connect(window, &Konsole::MainWindow::terminalsDetached, this, &Konsole::Application::detachTerminals); return window; } void Application::createWindow(const Profile::Ptr &profile, const QString &directory) { MainWindow *window = newMainWindow(); - ViewManager *viewManager = window->viewManager(); - window->createSession(viewManager->activeContainer(), profile, directory); + window->createSession(profile, directory); finalizeNewMainWindow(window); } -void Application::detachView(Session *session) +void Application::detachTerminals(ViewSplitter *splitter,const QHash& sessionsMap) { MainWindow *currentWindow = qobject_cast(sender()); MainWindow *window = newMainWindow(); ViewManager *manager = window->viewManager(); - manager->createView(manager->activeContainer(), session); + foreach(TerminalDisplay* terminal, splitter->findChildren()) { + manager->attachView(terminal, sessionsMap[terminal]); + } + manager->activeContainer()->addSplitter(splitter); - // Since user is dragging and dropping, move dnd window to where - // the user has the cursor (correct multiple monitor setups). - window->move(QCursor::pos()); - window->resize(currentWindow->geometry().width(), currentWindow->geometry().height()); window->show(); + window->resize(currentWindow->width(), currentWindow->height()); + window->move(QCursor::pos()); } int Application::newInstance() @@ -228,8 +230,7 @@ Profile::Ptr newProfile = processProfileChangeArgs(baseProfile); // create new session - ViewManager *viewManager = window->viewManager(); - Session *session = window->createSession(viewManager->activeContainer(), newProfile, QString()); + Session *session = window->createSession(newProfile, QString()); if (m_parser->isSet(QStringLiteral("noclose"))) { session->setAutoClose(false); @@ -376,8 +377,7 @@ // Create the new session Profile::Ptr theProfile = shouldUseNewProfile ? newProfile : baseProfile; - ViewManager *viewManager = window->viewManager(); - Session *session = window->createSession(viewManager->activeContainer(), theProfile, QString()); + Session *session = window->createSession(theProfile, QString()); if (m_parser->isSet(QStringLiteral("noclose"))) { session->setAutoClose(false); diff --git a/src/DetachableTabBar.h b/src/DetachableTabBar.h --- a/src/DetachableTabBar.h +++ b/src/DetachableTabBar.h @@ -41,7 +41,6 @@ void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent*event) override; void mouseReleaseEvent(QMouseEvent *event) override; - bool droppedContainerIsNotThis(const QPoint& currentPos) const; private: DragType dragType; diff --git a/src/DetachableTabBar.cpp b/src/DetachableTabBar.cpp --- a/src/DetachableTabBar.cpp +++ b/src/DetachableTabBar.cpp @@ -33,18 +33,6 @@ tabId(-1) {} -bool DetachableTabBar::droppedContainerIsNotThis(const QPoint& currentPos) const -{ - for(const auto dropWidget : _containers) { - if (dropWidget->rect().contains(dropWidget->mapFromGlobal(currentPos))) { - if (dropWidget != parent()) { - return true; - } - } - } - return false; -} - void DetachableTabBar::middleMouseButtonClickAt(const QPoint& pos) { tabId = tabAt(pos); @@ -66,16 +54,9 @@ auto widgetAtPos = qApp->topLevelAt(event->globalPos()); if (widgetAtPos != nullptr) { if (window() == widgetAtPos->window()) { - if (droppedContainerIsNotThis(event->globalPos())) { - if (dragType != DragType::WINDOW) { - dragType = DragType::WINDOW; - setCursor(QCursor(Qt::DragMoveCursor)); - } - } else { - if (dragType != DragType::NONE) { - dragType = DragType::NONE; - setCursor(_originalCursor); - } + if (dragType != DragType::NONE) { + dragType = DragType::NONE; + setCursor(_originalCursor); } } else { if (dragType != DragType::WINDOW) { @@ -125,14 +106,9 @@ emit detachTab(currentIndex()); } } else if (window() != widgetAtPos->window()) { - // splits have a tendency to break, forbid to detach if split and it's the last tab. if (_containers.size() == 1 || count() > 1) { emit moveTabToWindow(currentIndex(), widgetAtPos); } - } else if (droppedContainerIsNotThis(event->globalPos())){ - if (count() != 1) { - emit moveTabToWindow(currentIndex(), widgetAtPos); - } } } diff --git a/src/MainWindow.h b/src/MainWindow.h --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -29,6 +29,7 @@ // Konsole #include "Profile.h" +#include "ViewSplitter.h" class QAction; class KActionMenu; @@ -74,21 +75,19 @@ /** * Create a new session. * - * @param tabWidget where the new widget should be added. * @param profile The profile to use to create the new session. * @param directory Initial working directory for the new session or empty * if the default working directory associated with the profile should be used. */ - Session *createSession(TabbedViewContainer *tabWidget, Profile::Ptr profile, const QString &directory); + Session *createSession(Profile::Ptr profile, const QString &directory); /** * create a new SSH session. * - * @param tabWidget where the new widget should be added. * @param profile The profile to use to create the new session. * @param url the URL representing the new SSH connection */ - Session *createSSHSession(TabbedViewContainer *tabWidget, Profile::Ptr profile, const QUrl &url); + Session *createSSHSession(Profile::Ptr profile, const QUrl &url); /** * Helper method to make this window get input focus @@ -125,7 +124,7 @@ /** * Emitted when a view for one session is detached from this window */ - void viewDetached(Session *session); + void terminalsDetached(ViewSplitter *splitter, QHash sessionsMap); protected: // Reimplemented for internal reasons. @@ -143,14 +142,14 @@ bool focusNextPrevChild(bool next) Q_DECL_OVERRIDE; private Q_SLOTS: - void newTab(TabbedViewContainer *tabWidget); + void newTab(); void cloneTab(); void newWindow(); void showManageProfilesDialog(); void activateMenuBar(); void showSettingsDialog(const bool showProfilePage = false); void showShortcutsDialog(); - void newFromProfile(TabbedViewContainer *tabWidget, const Profile::Ptr &profile); + void newFromProfile(const Profile::Ptr &profile); void activeViewChanged(SessionController *controller); void disconnectController(SessionController *controller); void activeViewTitleChanged(ViewProperties *); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -114,8 +114,8 @@ this, &Konsole::MainWindow::newFromProfile); connect(_viewManager, &Konsole::ViewManager::newViewRequest, this, &Konsole::MainWindow::newTab); - connect(_viewManager, &Konsole::ViewManager::viewDetached, this, - &Konsole::MainWindow::viewDetached); + connect(_viewManager, &Konsole::ViewManager::terminalsDetached, this, + &Konsole::MainWindow::terminalsDetached); setCentralWidget(_viewManager->widget()); @@ -230,6 +230,10 @@ if (controller->isValid()) { guiFactory()->removeClient(controller); } + + if (_pluggedController == controller) { + _pluggedController = nullptr; + } } void MainWindow::activeViewChanged(SessionController *controller) @@ -317,8 +321,7 @@ collection->setDefaultShortcut(_newTabMenuAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_T); collection->setShortcutsConfigurable(_newTabMenuAction, true); _newTabMenuAction->setAutoRepeat(false); - connect(_newTabMenuAction, &KActionMenu::triggered, - this, [this] { newTab(_viewManager->activeContainer());}); + connect(_newTabMenuAction, &KActionMenu::triggered, this, &MainWindow::newTab); collection->addAction(QStringLiteral("new-tab"), _newTabMenuAction); collection->setShortcutsConfigurable(_newTabMenuAction, true); @@ -391,11 +394,8 @@ { profileListChanged(list->actions()); - connect(list, &Konsole::ProfileList::profileSelected, this, - [this](const Profile::Ptr &profile) { newFromProfile(_viewManager->activeContainer(), profile);}); - - connect(list, &Konsole::ProfileList::actionsChanged, this, - &Konsole::MainWindow::profileListChanged); + connect(list, &Konsole::ProfileList::profileSelected, this, &MainWindow::newFromProfile); + connect(list, &Konsole::ProfileList::actionsChanged, this, &Konsole::MainWindow::profileListChanged); } void MainWindow::profileListChanged(const QList &sessionActions) @@ -461,17 +461,17 @@ for (const auto &url : urls) { if (url.isLocalFile()) { - createSession(_viewManager->activeContainer(), defaultProfile, url.path()); + createSession(defaultProfile, url.path()); } else if (url.scheme() == QLatin1String("ssh")) { - createSSHSession(_viewManager->activeContainer(), defaultProfile, url); + createSSHSession(defaultProfile, url); } } } -void MainWindow::newTab(TabbedViewContainer *tabWidget) +void MainWindow::newTab() { Profile::Ptr defaultProfile = ProfileManager::instance()->defaultProfile(); - createSession(tabWidget, defaultProfile, activeSessionDir()); + createSession(defaultProfile, activeSessionDir()); } void MainWindow::cloneTab() @@ -481,15 +481,15 @@ Session *session = _pluggedController->session(); Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); if (profile) { - createSession(_viewManager->activeContainer(), profile, activeSessionDir()); + createSession(profile, activeSessionDir()); } else { // something must be wrong: every session should be associated with profile Q_ASSERT(false); - newTab(_viewManager->activeContainer()); + newTab(); } } -Session *MainWindow::createSession(TabbedViewContainer *tabWidget, Profile::Ptr profile, const QString &directory) +Session *MainWindow::createSession(Profile::Ptr profile, const QString &directory) { if (!profile) { profile = ProfileManager::instance()->defaultProfile(); @@ -507,12 +507,12 @@ // doesn't suffer a change in terminal size right after the session // starts. Some applications such as GNU Screen and Midnight Commander // don't like this happening - _viewManager->createView(tabWidget, session); - + auto newView = _viewManager->createView(session); + _viewManager->activeContainer()->addView(newView); return session; } -Session *MainWindow::createSSHSession(TabbedViewContainer *tabWidget, Profile::Ptr profile, const QUrl &url) +Session *MainWindow::createSSHSession(Profile::Ptr profile, const QUrl &url) { if (!profile) { profile = ProfileManager::instance()->defaultProfile(); @@ -537,8 +537,8 @@ // doesn't suffer a change in terminal size right after the session // starts. some applications such as GNU Screen and Midnight Commander // don't like this happening - _viewManager->createView(tabWidget, session); - + auto newView = _viewManager->createView(session); + _viewManager->activeContainer()->addView(newView); return session; } @@ -711,9 +711,9 @@ } } -void MainWindow::newFromProfile(TabbedViewContainer *tabWidget, const Profile::Ptr &profile) +void MainWindow::newFromProfile(const Profile::Ptr &profile) { - createSession(tabWidget, profile, activeSessionDir()); + createSession(profile, activeSessionDir()); } void MainWindow::showManageProfilesDialog() @@ -863,20 +863,20 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) { - if (obj == _pluggedController->view()) { + if (!_pluggedController.isNull() && obj == _pluggedController->view()) { switch(event->type()) { - case QEvent::MouseButtonPress: - case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonDblClick: switch(static_cast(event)->button()) { - case Qt::ForwardButton: - triggerAction(QStringLiteral("next-view")); - break; - case Qt::BackButton: - triggerAction(QStringLiteral("previous-view")); - break; - default: ; + case Qt::ForwardButton: + triggerAction(QStringLiteral("next-view")); + break; + case Qt::BackButton: + triggerAction(QStringLiteral("previous-view")); + break; + default: ; } - default: ; + default: ; } } diff --git a/src/Part.cpp b/src/Part.cpp --- a/src/Part.cpp +++ b/src/Part.cpp @@ -213,7 +213,8 @@ session->setInitialWorkingDirectory(directory); } - _viewManager->createView(_viewManager->activeContainer(), session); + auto newView = _viewManager->createView(session); + _viewManager->activeContainer()->addView(newView); } void Part::activeViewChanged(SessionController *controller) diff --git a/src/SessionController.cpp b/src/SessionController.cpp --- a/src/SessionController.cpp +++ b/src/SessionController.cpp @@ -230,10 +230,6 @@ SessionController::~SessionController() { - if (!_view.isNull()) { - _view->setScreenWindow(nullptr); - } - _allControllers.remove(this); if (!_editProfileDialog.isNull()) { @@ -539,11 +535,7 @@ // Close Session QAction* action = collection->addAction(QStringLiteral("close-session"), this, SLOT(closeSession())); - if (isKonsolePart()) { - action->setText(i18n("&Close Session")); - } else { - action->setText(i18n("&Close Tab")); - } + action->setText(i18n("&Close Session")); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_W); diff --git a/src/Shortcut_p.h b/src/Shortcut_p.h --- a/src/Shortcut_p.h +++ b/src/Shortcut_p.h @@ -30,9 +30,13 @@ */ enum Modifier { #ifdef Q_OS_MACOS - ACCEL = Qt::META + ACCEL = Qt::META, + LEFT = Qt::Key_BracketLeft, + RIGHT = Qt::Key_BracketRight #else - ACCEL = Qt::CTRL + ACCEL = Qt::CTRL, + LEFT = Qt::Key_Left, + RIGHT = Qt::Key_Right #endif }; } diff --git a/src/ViewContainer.h b/src/ViewContainer.h --- a/src/ViewContainer.h +++ b/src/ViewContainer.h @@ -76,6 +76,10 @@ /** 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); @@ -95,14 +99,10 @@ /** Changes the active view to the last view */ void activateLastView(); - /** Changes the active view to the last used view */ - void activateLastUsedView(bool reverse); - - /** Toggle between last two views */ - void toggleLastUsedView(); - 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 @@ -134,27 +134,33 @@ 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. */ - TerminalDisplay *terminalAt(int index); + 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(TabbedViewContainer *thisContainer); + void newViewRequest(); /** Requests creation of a new view, with the selected profile. */ - void newViewWithProfileRequest(TabbedViewContainer *thisContainer, const Profile::Ptr&); + void newViewWithProfileRequest(const Profile::Ptr&); /** * Emitted when the user requests to move a view from another container @@ -178,17 +184,9 @@ void viewRemoved(TerminalDisplay *view); /** detach the specific tab */ - void detachTab(TabbedViewContainer *self, TerminalDisplay *activeView); + void detachTab(int tabIdx); protected: - /** - * Rearranges the order of widgets in the container. - * - * @param fromIndex Current index of the widget to move - * @param toIndex New index for the widget - */ - void moveViewWidget(int fromIndex, int toIndex); - // close tabs and unregister void closeTerminalTab(int idx); @@ -198,17 +196,14 @@ void konsoleConfigChanged(); private: - void forgetView(TerminalDisplay *view); - void updateTabHistory(TerminalDisplay *view, bool remove = false); + void forgetView(ViewSplitter *view); - QList _tabHistory; ViewManager *_connectedViewManager; QMenu *_contextPopupMenu; QToolButton *_newTabButton; QToolButton *_closeTabButton; int _contextMenuTabIndex; ViewManager::NavigationVisibility _navigationVisibility; - int _tabHistoryIndex; }; diff --git a/src/ViewContainer.cpp b/src/ViewContainer.cpp --- a/src/ViewContainer.cpp +++ b/src/ViewContainer.cpp @@ -47,6 +47,9 @@ #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 @@ -59,8 +62,7 @@ _newTabButton(new QToolButton()), _closeTabButton(new QToolButton()), _contextMenuTabIndex(-1), - _navigationVisibility(ViewManager::NavigationVisibility::NavigationNotSet), - _tabHistoryIndex(-1) + _navigationVisibility(ViewManager::NavigationVisibility::NavigationNotSet) { setAcceptDrops(true); @@ -72,9 +74,7 @@ tabBar()->setContextMenuPolicy(Qt::CustomContextMenu); _newTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-new"))); _newTabButton->setAutoRaise(true); - connect(_newTabButton, &QToolButton::clicked, this, [this]{ - emit newViewRequest(this); - }); + connect(_newTabButton, &QToolButton::clicked, this, &TabbedViewContainer::newViewRequest); _closeTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); _closeTabButton->setAutoRaise(true); @@ -86,13 +86,15 @@ &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(this, terminalAt(idx)); + emit detachTab(idx); }); +#endif connect(tabBarWidget, &DetachableTabBar::closeTab, this, &TabbedViewContainer::closeTerminalTab); connect(tabBarWidget, &DetachableTabBar::newTabRequest, - this, [this]{ emit newViewRequest(this); }); + this, [this]{ emit newViewRequest(); }); connect(this, &TabbedViewContainer::currentChanged, this, &TabbedViewContainer::currentTabChanged); // The context menu of tab bar @@ -113,7 +115,7 @@ auto detachAction = _contextPopupMenu->addAction( QIcon::fromTheme(QStringLiteral("tab-detach")), i18nc("@action:inmenu", "&Detach Tab"), this, - [this] { emit detachTab(this, terminalAt(_contextMenuTabIndex)); } + [this] { emit detachTab(_contextMenuTabIndex); } ); detachAction->setObjectName(QStringLiteral("tab-detach")); #endif @@ -135,8 +137,7 @@ auto profileMenu = new QMenu(); auto profileList = new ProfileList(false, profileMenu); profileList->syncWidgetActions(profileMenu, true); - connect(profileList, &Konsole::ProfileList::profileSelected, this, - [this](const Profile::Ptr &profile) { emit newViewWithProfileRequest(this, profile); }); + connect(profileList, &Konsole::ProfileList::profileSelected, this, &TabbedViewContainer::newViewWithProfileRequest); _newTabButton->setMenu(profileMenu); konsoleConfigChanged(); @@ -151,24 +152,33 @@ } } -TerminalDisplay *TabbedViewContainer::terminalAt(int index) +ViewSplitter *TabbedViewContainer::activeViewSplitter() +{ + return viewSplitterAt(currentIndex()); +} + +ViewSplitter *TabbedViewContainer::viewSplitterAt(int index) { - return qobject_cast(widget(index)); + return qobject_cast(widget(index)); } void TabbedViewContainer::moveTabToWindow(int index, QWidget *window) { - const int id = terminalAt(index)->sessionController()->identifier(); - // This one line here will be removed as soon as I finish my new split handling. - // it's hacky but it works. - const auto widgets = window->findChildren(); - const auto currentPos = QCursor::pos(); - for(const auto dropWidget : widgets) { - if (dropWidget->rect().contains(dropWidget->mapFromGlobal(currentPos))) { - emit dropWidget->moveViewRequest(-1, id); - removeView(terminalAt(index)); - } + 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() @@ -242,66 +252,117 @@ const int currentIndex = indexOf(currentWidget()); int newIndex = direction == MoveViewLeft ? qMax(currentIndex - 1, 0) : qMin(currentIndex + 1, count() - 1); - auto swappedWidget = terminalAt(newIndex); - auto currentWidget = terminalAt(currentIndex); - auto swappedContext = swappedWidget->sessionController(); - auto currentContext = currentWidget->sessionController(); + 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, currentContext->icon(), currentContext->title()); - insertTab(currentIndex, swappedWidget, swappedContext->icon(), swappedContext->title()); + insertTab(newIndex, currentWidget, currentIcon, currentTitle); + insertTab(currentIndex, swappedWidget, swappedIcon, swappedTitle); } else { - insertTab(currentIndex, swappedWidget, swappedContext->icon(), swappedContext->title()); - insertTab(newIndex, currentWidget, currentContext->icon(), currentContext->title()); + 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()) { + connectTerminalDisplay(terminal); + } + 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) { - addTab(view, item->icon(), item->title()); + index = addTab(viewSplitter, item->icon(), item->title()); } else { - insertTab(index, view, item->icon(), item->title()); + insertTab(index, viewSplitter, item->icon(), item->title()); } - _tabHistory.append(view); + 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); - connect(view, &QWidget::destroyed, this, - &Konsole::TabbedViewContainer::viewDestroyed); - emit viewAdded(view); +} + +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); + auto widget = static_cast(view); const auto idx = indexOf(widget); removeTab(idx); forgetView(widget); } -void TabbedViewContainer::forgetView(TerminalDisplay *view) +void TabbedViewContainer::forgetView(ViewSplitter *view) { - updateTabHistory(view, true); - emit viewRemoved(view); + Q_UNUSED(view); if (count() == 0) { emit empty(this); } } void TabbedViewContainer::removeView(TerminalDisplay *view) { - const int idx = indexOf(view); - disconnect(view, &QWidget::destroyed, this, &Konsole::TabbedViewContainer::viewDestroyed); - removeTab(idx); - forgetView(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() @@ -323,71 +384,10 @@ setCurrentIndex(index == 0 ? count() - 1 : index - 1); } -void TabbedViewContainer::activateLastUsedView(bool reverse) -{ - if (_tabHistory.count() <= 1) { - return; - } - - if (_tabHistoryIndex == -1) { - _tabHistoryIndex = reverse ? _tabHistory.count() - 1 : 1; - } else if (reverse) { - if (_tabHistoryIndex == 0) { - _tabHistoryIndex = _tabHistory.count() - 1; - } else { - _tabHistoryIndex--; - } - } else { - if (_tabHistoryIndex >= _tabHistory.count() - 1) { - _tabHistoryIndex = 0; - } else { - _tabHistoryIndex++; - } - } - - int index = indexOf(_tabHistory[_tabHistoryIndex]); - setCurrentIndex(index); -} - -// Jump to last view - this allows toggling between two views -// Using "Last Used Tabs" shortcut will cause "Toggle between two tabs" -// shortcut to be incorrect the first time. -void TabbedViewContainer::toggleLastUsedView() -{ - if (_tabHistory.count() <= 1) { - return; - } - - setCurrentIndex(indexOf(_tabHistory.at(1))); -} - void TabbedViewContainer::keyReleaseEvent(QKeyEvent* event) { - if (_tabHistoryIndex != -1 && event->modifiers() == Qt::NoModifier) { - _tabHistoryIndex = -1; - auto *active = qobject_cast(currentWidget()); - if (active != _tabHistory[0]) { - // Update the tab history now that we have ended the walk-through - updateTabHistory(active); - } - } -} - -void TabbedViewContainer::updateTabHistory(TerminalDisplay* view, bool remove) -{ - if (_tabHistoryIndex != -1 && !remove) { - // Do not reorder the tab history while we are walking through it - return; - } - - for (int i = 0; i < _tabHistory.count(); ++i ) { - if (_tabHistory[i] == view) { - _tabHistory.removeAt(i); - if (!remove) { - _tabHistory.prepend(view); - } - break; - } + if (event->modifiers() == Qt::NoModifier) { + _connectedViewManager->updateTerminalDisplayHistory(); } } @@ -403,14 +403,18 @@ if (index >= 0) { renameTab(index); } else { - emit newViewRequest(this); + emit newViewRequest(); } } void TabbedViewContainer::renameTab(int index) { if (index != -1) { - terminalAt(index)->sessionController()->rename(); + setCurrentIndex(index); + viewSplitterAt(index) + -> activeTerminalDisplay() + -> sessionController() + -> rename(); } } @@ -435,9 +439,13 @@ } #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 - auto controller = terminalAt(_contextMenuTabIndex)->sessionController(); - auto sessionController = qobject_cast(controller); +#if 0 + auto sessionController = terminalAt(_contextMenuTabIndex)->sessionController(); if (sessionController != nullptr) { auto collection = sessionController->actionCollection(); @@ -455,16 +463,15 @@ } } } - +#endif _contextPopupMenu->exec(tabBar()->mapToGlobal(point)); } void TabbedViewContainer::currentTabChanged(int index) { if (index != -1) { - auto *view = terminalAt(index); - view->setFocus(); - updateTabHistory(view); + auto splitview = qobject_cast(widget(index)); + auto view = splitview->activeTerminalDisplay(); emit activeViewChanged(view); setTabActivity(index, false); } else { @@ -506,11 +513,17 @@ } } +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(controller->view()); + const int index = indexOf(topLevelSplitter); QString tabText = item->title(); setTabToolTip(index, tabText); @@ -528,7 +541,10 @@ } void TabbedViewContainer::closeTerminalTab(int idx) { - terminalAt(idx)->sessionController()->closeSession(); + //TODO: This for should probably go to the ViewSplitter + for (auto terminal : viewSplitterAt(idx)->findChildren()) { + terminal->sessionController()->closeSession(); + } } ViewManager *TabbedViewContainer::connectedViewManager() @@ -548,3 +564,13 @@ tabBar()->setVisible(false); } } + +void TabbedViewContainer::maximizeCurrentTerminal() +{ + activeViewSplitter()->maximizeCurrentTerminal(); +} + +void TabbedViewContainer::restoreOtherTerminals() +{ + activeViewSplitter()->restoreOtherTerminals(); +} diff --git a/src/ViewManager.h b/src/ViewManager.h --- a/src/ViewManager.h +++ b/src/ViewManager.h @@ -21,6 +21,7 @@ #define VIEWMANAGER_H // Qt +#include #include #include #include @@ -44,10 +45,9 @@ /** * Manages the terminal display widgets in a Konsole window or part. * - * When a view manager is created, it constructs a splitter widget ( accessed via - * widget() ) to hold one or more view containers. Each view container holds - * one or more terminal displays and a navigation widget ( eg. tabs or a list ) - * to allow the user to navigate between the displays in that container. + * When a view manager is created, it constructs a tab widget ( accessed via + * widget() ) to hold one or more view splitters. Each view splitter holds + * one or more terminal displays and splitters. * * The view manager provides menu actions ( defined in the 'konsoleui.rc' XML file ) * to manipulate the views and view containers - for example, actions to split the view @@ -80,6 +80,12 @@ */ void createView(TabbedViewContainer *tabWidget, Session *session); + /* + * Applies the view-specific settings associated with specified @p profile + * to the terminal display @p view. + */ + void applyProfileToView(TerminalDisplay *view, const Profile::Ptr &profile); + /** * Return the main widget for the view manager which * holds all of the views managed by this ViewManager instance. @@ -191,17 +197,21 @@ /** returns the active tab from the view */ TabbedViewContainer *activeContainer(); - - void applyProfileToView(TerminalDisplay *view, const Profile::Ptr &profile); + TerminalDisplay *createView(Session *session); + void attachView(TerminalDisplay *terminal, Session *session); static const ColorScheme *colorSchemeForProfile(const Profile::Ptr &profile); + /** Reorder the terminal display history list */ + void updateTerminalDisplayHistory(TerminalDisplay *terminalDisplay = nullptr, bool remove = false); + QHash forgetAll(ViewSplitter* splitter); + Session* forgetTerminal(TerminalDisplay* terminal); Q_SIGNALS: /** Emitted when the last view is removed from the view manager */ void empty(); /** Emitted when a session is detached from a view owned by this ViewManager */ - void viewDetached(Session *session); + void terminalsDetached(ViewSplitter *splitter, QHash sessionsMap); /** * Emitted when the active view changes. @@ -222,16 +232,6 @@ */ void viewPropertiesChanged(const QList &propertiesList); - /** - * Emitted when the number of views containers changes. This is used to disable or - * enable menu items which can only be used when there are one or multiple containers - * visible. - * - * @param multipleViews True if there are multiple view containers open or false if there is - * just a single view. - */ - void splitViewToggle(bool multipleViews); - /** * Emitted when menu bar visibility changes because a profile that requires so is * activated. @@ -242,9 +242,9 @@ void blurSettingChanged(bool); /** Requests creation of a new view with the default profile. */ - void newViewRequest(TabbedViewContainer *tabWidget); + void newViewRequest(); /** Requests creation of a new view, with the selected profile. */ - void newViewWithProfileRequest(TabbedViewContainer *tabWidget, const Profile::Ptr&); + void newViewWithProfileRequest(const Profile::Ptr&); public Q_SLOTS: /** DBus slot that returns the number of sessions in the current view. */ @@ -314,13 +314,12 @@ // called when the "Split View Left/Right" menu item is selected void splitLeftRight(); void splitTopBottom(); - void closeActiveContainer(); - void closeOtherContainers(); void expandActiveContainer(); void shrinkActiveContainer(); // called when the "Detach View" menu item is selected void detachActiveView(); + void detachActiveTab(); void updateDetachViewState(); // called when a session terminates - the view manager will delete any @@ -336,7 +335,12 @@ // called when the active view in a ViewContainer changes, so // that we can plug the appropriate actions into the UI - void viewActivated(QWidget *view); + void viewActivated(TerminalDisplay *view); + + void focusUp(); + void focusDown(); + void focusLeft(); + void focusRight(); // called when "Next View" shortcut is activated void nextView(); @@ -375,15 +379,14 @@ // switches to the view at visual position 'index' // in the current container void switchToView(int index); + // gives focus and switches the terminal display, changing tab if needed + void switchToTerminalDisplay(TerminalDisplay *terminalDisplay); // called when a SessionController gains focus void controllerChanged(SessionController *controller); - // called when a ViewContainer requests a view be - // moved - void containerMoveViewRequest(int index, int sessionControllerId); - - void detachView(TabbedViewContainer *container, QWidget *view); + /* Detaches the tab at index tabIdx */ + void detachTab(int tabIdx); private: Q_DISABLE_COPY(ViewManager) @@ -400,8 +403,6 @@ // creates a new container which can hold terminal displays TabbedViewContainer *createContainer(); - // removes a container and emits appropriate signals - void removeContainer(TabbedViewContainer *container); // creates a new terminal display // the 'session' is used so that the terminal display's random seed @@ -412,9 +413,18 @@ // actions associated with that view, and exposes basic information // about the session ( such as title and associated icon ) to the display. SessionController *createController(Session *session, TerminalDisplay *view); + void removeController(SessionController* controller); + + // Activates a different terminal when the TerminalDisplay + // closes or is detached and another one should be focused. + // It will activate the last used terminal within the same splitView + // if possible otherwise it will focus the last used tab + void focusAnotherTerminal(ViewSplitter *toplevelSplitter); + + void activateLastUsedView(bool reverse); private: - QPointer _viewSplitter; + QPointer _viewContainer; QPointer _pluggedController; QHash _sessionMap; @@ -426,6 +436,14 @@ NewTabBehavior _newTabBehavior; int _managerId; static int lastManagerId; + QList _terminalDisplayHistory; + int _terminalDisplayHistoryIndex; + + // List of actions that should only be enabled when there are multiple view + // containers open + QList _multiTabOnlyActions; + QList _multiSplitterOnlyActions; + }; } diff --git a/src/ViewManager.cpp b/src/ViewManager.cpp --- a/src/ViewManager.cpp +++ b/src/ViewManager.cpp @@ -24,7 +24,6 @@ // Qt #include -#include #include // KDE @@ -53,36 +52,25 @@ ViewManager::ViewManager(QObject *parent, KActionCollection *collection) : QObject(parent), - _viewSplitter(nullptr), + _viewContainer(nullptr), _pluggedController(nullptr), _sessionMap(QHash()), _actionCollection(collection), _navigationMethod(NoNavigation), _navigationVisibility(NavigationNotSet), _newTabBehavior(PutNewTabAtTheEnd), - _managerId(0) + _managerId(0), + _terminalDisplayHistoryIndex(-1) { - // create main view area - _viewSplitter = new ViewSplitter(nullptr); - KAcceleratorManager::setNoAccel(_viewSplitter); - - // the ViewSplitter class supports both recursive and non-recursive splitting, - // in non-recursive mode, all containers are inserted into the same top-level splitter - // widget, and all the divider lines between the containers have the same orientation - // - // the ViewManager class is not currently able to handle a ViewSplitter in recursive-splitting - // mode - _viewSplitter->setRecursiveSplitting(false); - _viewSplitter->setFocusPolicy(Qt::NoFocus); - + _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(_viewSplitter.data(), &Konsole::ViewSplitter::allContainersEmpty, + */ + connect(_viewContainer.data(), &Konsole::TabbedViewContainer::empty, this, &Konsole::ViewManager::empty); - connect(_viewSplitter.data(), &Konsole::ViewSplitter::empty, this, - &Konsole::ViewManager::empty); // listen for profile changes connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged, @@ -96,8 +84,6 @@ _managerId = ++lastManagerId; QDBusConnection::sessionBus().registerObject(QLatin1String("/Windows/") + QString::number(_managerId), this); - - _viewSplitter->addContainer(createContainer(), Qt::Vertical); } ViewManager::~ViewManager() = default; @@ -109,17 +95,12 @@ QWidget *ViewManager::activeView() const { - TabbedViewContainer *container = _viewSplitter->activeContainer(); - if (container != nullptr) { - return container->currentWidget(); - } else { - return nullptr; - } + return _viewContainer->currentWidget(); } QWidget *ViewManager::widget() const { - return _viewSplitter; + return _viewContainer; } void ViewManager::setupActions() @@ -131,320 +112,368 @@ KActionCollection *collection = _actionCollection; - QAction *nextViewAction = new QAction(i18nc("@action Shortcut entry", "Next Tab"), this); - QAction *previousViewAction = new QAction(i18nc("@action Shortcut entry", "Previous Tab"), this); - QAction *lastViewAction = new QAction(i18nc("@action Shortcut entry", - "Switch to Last Tab"), this); - QAction *lastUsedViewAction = new QAction(i18nc("@action Shortcut entry", "Last Used Tabs"), this); - QAction *lastUsedViewReverseAction = new QAction(i18nc("@action Shortcut entry", - "Last Used Tabs (Reverse)"), this); - QAction *toggleTwoViewsAction = new QAction(i18nc("@action Shortcut entry", "Toggle Between Two Tabs"), this); - QAction *nextContainerAction = new QAction(i18nc("@action Shortcut entry", - "Next View Container"), this); - - QAction *moveViewLeftAction = new QAction(i18nc("@action Shortcut entry", "Move Tab Left"), this); - QAction *moveViewRightAction = new QAction(i18nc("@action Shortcut entry", - "Move Tab Right"), this); - - // list of actions that should only be enabled when there are multiple view - // containers open - QList multiViewOnlyActions; - multiViewOnlyActions << nextContainerAction; - - QAction *splitLeftRightAction = new QAction(QIcon::fromTheme(QStringLiteral("view-split-left-right")), - i18nc("@action:inmenu", "Split View Left/Right"), - this); - collection->setDefaultShortcut(splitLeftRightAction, Konsole::ACCEL + Qt::Key_ParenLeft); - collection->addAction(QStringLiteral("split-view-left-right"), splitLeftRightAction); - connect(splitLeftRightAction, &QAction::triggered, this, &Konsole::ViewManager::splitLeftRight); - - QAction *splitTopBottomAction = new QAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")), - i18nc("@action:inmenu", - "Split View Top/Bottom"), this); - collection->setDefaultShortcut(splitTopBottomAction, Konsole::ACCEL + Qt::Key_ParenRight); - collection->addAction(QStringLiteral("split-view-top-bottom"), splitTopBottomAction); - connect(splitTopBottomAction, &QAction::triggered, this, &Konsole::ViewManager::splitTopBottom); - - QAction *closeActiveAction = new QAction(i18nc("@action:inmenu Close Active View", "Close Active"), this); - closeActiveAction->setIcon(QIcon::fromTheme(QStringLiteral("view-close"))); - collection->setDefaultShortcut(closeActiveAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_X); - closeActiveAction->setEnabled(false); - collection->addAction(QStringLiteral("close-active-view"), closeActiveAction); - connect(closeActiveAction, &QAction::triggered, this, - &Konsole::ViewManager::closeActiveContainer); - - multiViewOnlyActions << closeActiveAction; - - QAction *closeOtherAction = new QAction(i18nc("@action:inmenu Close Other Views", - "Close Others"), this); - collection->setDefaultShortcut(closeOtherAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_O); - closeOtherAction->setEnabled(false); - collection->addAction(QStringLiteral("close-other-views"), closeOtherAction); - connect(closeOtherAction, &QAction::triggered, this, - &Konsole::ViewManager::closeOtherContainers); - - multiViewOnlyActions << closeOtherAction; - - // Expand & Shrink Active View - QAction *expandActiveAction = new QAction(i18nc("@action:inmenu", "Expand View"), this); - collection->setDefaultShortcut(expandActiveAction, - Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketRight); - expandActiveAction->setEnabled(false); - collection->addAction(QStringLiteral("expand-active-view"), expandActiveAction); - connect(expandActiveAction, &QAction::triggered, this, - &Konsole::ViewManager::expandActiveContainer); - - multiViewOnlyActions << expandActiveAction; - - QAction *shrinkActiveAction = new QAction(i18nc("@action:inmenu", "Shrink View"), this); - collection->setDefaultShortcut(shrinkActiveAction, - Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketLeft); - shrinkActiveAction->setEnabled(false); - collection->addAction(QStringLiteral("shrink-active-view"), shrinkActiveAction); - connect(shrinkActiveAction, &QAction::triggered, this, - &Konsole::ViewManager::shrinkActiveContainer); - - multiViewOnlyActions << shrinkActiveAction; + // Let's reuse the pointer, no need not to. + auto *action = new QAction(); + 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(); + 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(); + 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(); + 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) - QAction *detachViewAction = collection->addAction(QStringLiteral("detach-view")); - detachViewAction->setEnabled(true); - detachViewAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach"))); - detachViewAction->setText(i18nc("@action:inmenu", "D&etach Current Tab")); + 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(detachViewAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_H); - - connect(this, &Konsole::ViewManager::splitViewToggle, this, - &Konsole::ViewManager::updateDetachViewState); - connect(detachViewAction, &QAction::triggered, this, &Konsole::ViewManager::detachActiveView); + 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 - // Next / Previous View , Next Container - collection->addAction(QStringLiteral("next-view"), nextViewAction); - collection->addAction(QStringLiteral("previous-view"), previousViewAction); - collection->addAction(QStringLiteral("last-tab"), lastViewAction); - collection->addAction(QStringLiteral("last-used-tab"), lastUsedViewAction); - collection->addAction(QStringLiteral("last-used-tab-reverse"), lastUsedViewReverseAction); - collection->addAction(QStringLiteral("toggle-two-tabs"), toggleTwoViewsAction); - collection->addAction(QStringLiteral("next-container"), nextContainerAction); - collection->addAction(QStringLiteral("move-view-left"), moveViewLeftAction); - collection->addAction(QStringLiteral("move-view-right"), moveViewRightAction); - - // Switch to tab N shortcuts - const int SWITCH_TO_TAB_COUNT = 19; - for (int i = 0; i < SWITCH_TO_TAB_COUNT; i++) { - QAction *switchToTabAction = new QAction(i18nc("@action Shortcut entry", "Switch to Tab %1", i + 1), this); - - connect(switchToTabAction, &QAction::triggered, this, - [this, i]() { - switchToView(i); - }); - collection->addAction(QStringLiteral("switch-to-tab-%1").arg(i), switchToTabAction); - } - - foreach (QAction *action, multiViewOnlyActions) { - connect(this, &Konsole::ViewManager::splitViewToggle, action, &QAction::setEnabled); - } - // 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(nextViewAction, nextViewActionKeys); - connect(nextViewAction, &QAction::triggered, this, &Konsole::ViewManager::nextView); - _viewSplitter->addAction(nextViewAction); + 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(previousViewAction, previousViewActionKeys); - connect(previousViewAction, &QAction::triggered, this, &Konsole::ViewManager::previousView); - _viewSplitter->addAction(previousViewAction); - - collection->setDefaultShortcut(nextContainerAction, Qt::SHIFT + Qt::Key_Tab); - connect(nextContainerAction, &QAction::triggered, this, &Konsole::ViewManager::nextContainer); - _viewSplitter->addAction(nextContainerAction); - -#ifdef Q_OS_MACOS - collection->setDefaultShortcut(moveViewLeftAction, - Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketLeft); -#else - collection->setDefaultShortcut(moveViewLeftAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Left); -#endif - connect(moveViewLeftAction, &QAction::triggered, this, - &Konsole::ViewManager::moveActiveViewLeft); - _viewSplitter->addAction(moveViewLeftAction); - -#ifdef Q_OS_MACOS - collection->setDefaultShortcut(moveViewRightAction, - Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketRight); -#else - collection->setDefaultShortcut(moveViewRightAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Right); -#endif - connect(moveViewRightAction, &QAction::triggered, this, - &Konsole::ViewManager::moveActiveViewRight); - _viewSplitter->addAction(moveViewRightAction); - - connect(lastViewAction, &QAction::triggered, this, &Konsole::ViewManager::lastView); - _viewSplitter->addAction(lastViewAction); + 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")); + 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); + } - collection->setDefaultShortcut(lastUsedViewAction, Qt::CTRL + Qt::Key_Tab); - connect(lastUsedViewAction, &QAction::triggered, this, &Konsole::ViewManager::lastUsedView); - _viewSplitter->addAction(lastUsedViewAction); + 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); - collection->setDefaultShortcut(lastUsedViewReverseAction, Qt::CTRL + Qt::SHIFT + Qt::Key_Tab); - connect(lastUsedViewReverseAction, &QAction::triggered, this, &Konsole::ViewManager::lastUsedViewReverse); - _viewSplitter->addAction(lastUsedViewReverseAction); + connect(_viewContainer, &QTabWidget::currentChanged, this, &ViewManager::updateDetachViewState); - connect(toggleTwoViewsAction, &QAction::triggered, this, &Konsole::ViewManager::toggleTwoViews); - _viewSplitter->addAction(toggleTwoViewsAction); + // Initial state + handleMultiTabActionsLambda(); + updateDetachViewState(); } void ViewManager::switchToView(int index) { - _viewSplitter->activeContainer()->setCurrentIndex(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() { - Q_ASSERT(_actionCollection); - if (_actionCollection == nullptr) { - return; + 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(); +} - const bool splitView = _viewSplitter->containers().count() >= 2; - auto activeContainer = _viewSplitter->activeContainer(); - const bool shouldEnable = splitView - || ((activeContainer != nullptr) - && activeContainer->count() >= 2); +void ViewManager::focusDown() +{ + _viewContainer->activeViewSplitter()->focusDown(); +} - QAction *detachAction = _actionCollection->action(QStringLiteral("detach-view")); +void ViewManager::focusLeft() +{ + _viewContainer->activeViewSplitter()->focusLeft(); +} - if ((detachAction != nullptr) && shouldEnable != detachAction->isEnabled()) { - detachAction->setEnabled(shouldEnable); - } +void ViewManager::focusRight() +{ + _viewContainer->activeViewSplitter()->focusRight(); } void ViewManager::moveActiveViewLeft() { - TabbedViewContainer *container = _viewSplitter->activeContainer(); - Q_ASSERT(container); - container->moveActiveView(TabbedViewContainer::MoveViewLeft); + _viewContainer->moveActiveView(TabbedViewContainer::MoveViewLeft); } void ViewManager::moveActiveViewRight() { - TabbedViewContainer *container = _viewSplitter->activeContainer(); - Q_ASSERT(container); - container->moveActiveView(TabbedViewContainer::MoveViewRight); + _viewContainer->moveActiveView(TabbedViewContainer::MoveViewRight); } void ViewManager::nextContainer() { - _viewSplitter->activateNextContainer(); +// _viewSplitter->activateNextContainer(); } void ViewManager::nextView() { - TabbedViewContainer *container = _viewSplitter->activeContainer(); - Q_ASSERT(container); - container->activateNextView(); + _viewContainer->activateNextView(); } void ViewManager::previousView() { - TabbedViewContainer *container = _viewSplitter->activeContainer(); - Q_ASSERT(container); - container->activatePreviousView(); + _viewContainer->activatePreviousView(); } void ViewManager::lastView() { - TabbedViewContainer *container = _viewSplitter->activeContainer(); - Q_ASSERT(container); - container->activateLastView(); + _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() { - TabbedViewContainer *container = _viewSplitter->activeContainer(); - Q_ASSERT(container); - container->activateLastUsedView(false); + activateLastUsedView(false); } void ViewManager::lastUsedViewReverse() { - TabbedViewContainer *container = _viewSplitter->activeContainer(); - Q_ASSERT(container); - container->activateLastUsedView(true); + activateLastUsedView(true); } void ViewManager::toggleTwoViews() { - TabbedViewContainer *container = _viewSplitter->activeContainer(); - Q_ASSERT(container); - container->toggleLastUsedView(); + 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 - TabbedViewContainer *container = _viewSplitter->activeContainer(); - detachView(container, container->currentWidget()); + 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::detachView(TabbedViewContainer *container, QWidget *view) +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); +} - auto *viewToDetach = qobject_cast(view); - - if (viewToDetach == nullptr) { - return; - } - - // BR390736 - some instances are sending invalid session to viewDetached() - Session *sessionToDetach = _sessionMap[viewToDetach]; - if (sessionToDetach == nullptr) { - return; +QHash ViewManager::forgetAll(ViewSplitter* splitter) { + splitter->setParent(nullptr); + QHash detachedSessions; + foreach(TerminalDisplay* terminal, splitter->findChildren()) { + Session* session = forgetTerminal(terminal); + detachedSessions[terminal] = session; } - emit viewDetached(sessionToDetach); - - _sessionMap.remove(viewToDetach); - - // remove the view from this window - container->removeView(viewToDetach); - viewToDetach->deleteLater(); + return detachedSessions; +} - // if the container from which the view was removed is now empty then it can be deleted, - // unless it is the only container in the window, in which case it is left empty - // so that there is always an active container - if (_viewSplitter->containers().count() > 1 - && container->count() == 0) { - removeContainer(container); +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 (_viewSplitter.isNull()) { + if (_viewContainer.isNull()) { return; } auto *session = qobject_cast(sender()); Q_ASSERT(session); - // close attached views - QList children = _viewSplitter->findChildren(); + auto view = _sessionMap.key(session); + _sessionMap.remove(view); - foreach (TerminalDisplay *view, children) { - if (_sessionMap[view] == session) { - _sessionMap.remove(view); - view->deleteLater(); - } - } + // 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. @@ -454,9 +483,32 @@ // order to prevent BUG: 185466 - disappearing menu popup emit unplugController(_pluggedController); } + + updateTerminalDisplayHistory(view, true); + focusAnotherTerminal(toplevelSplitter); + updateDetachViewState(); +} + +void ViewManager::focusAnotherTerminal(ViewSplitter *toplevelSplitter) +{ + auto tabTterminalDisplays = toplevelSplitter->findChildren(); + 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(QWidget *view) +void ViewManager::viewActivated(TerminalDisplay *view) { Q_ASSERT(view != nullptr); @@ -478,85 +530,34 @@ void ViewManager::splitView(Qt::Orientation orientation) { - TabbedViewContainer *container = createContainer(); + auto viewSplitter = qobject_cast(_viewContainer->currentWidget()); - if (_viewSplitter->activeContainer()->count()) { - // get the currently applied profile and use it to create the new tab. - auto *activeContainer= _viewSplitter->activeContainer(); - auto *currentDisplay = qobject_cast(activeContainer->currentWidget()); - 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())); - - createView(session, container, 0); - } + // get the currently applied profile and use it to create the new tab. + auto *currentDisplay = viewSplitter->findChild(); + auto profile = SessionManager::instance()->sessionProfile(_sessionMap[currentDisplay]); - _viewSplitter->addContainer(container, orientation); - emit splitViewToggle(_viewSplitter->containers().count() > 0); - - // focus the new container - container->currentWidget()->setFocus(); + // Create a new session with the selected profile. + auto *session = SessionManager::instance()->createSession(profile); + session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); - // ensure that the active view is focused after the split / unsplit - TabbedViewContainer *activeContainer = _viewSplitter->activeContainer(); - QWidget *activeView = activeContainer != nullptr ? activeContainer->currentWidget() : nullptr; + auto terminalDisplay = createView(session); - if (activeView != nullptr) { - activeView->setFocus(Qt::OtherFocusReason); - } -} + _viewContainer->splitView(terminalDisplay, orientation); -void ViewManager::removeContainer(TabbedViewContainer *container) -{ - // remove session map entries for views in this container - for(int i = 0, end = container->count(); i < end; i++) { - auto view = container->widget(i); - auto *display = qobject_cast(view); - Q_ASSERT(display); - _sessionMap.remove(display); - } - - _viewSplitter->removeContainer(container); - container->deleteLater(); + updateDetachViewState(); - emit splitViewToggle(_viewSplitter->containers().count() > 1); + // focus the new container + terminalDisplay->setFocus(); } void ViewManager::expandActiveContainer() { - _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(), 10); + _viewContainer->activeViewSplitter()->adjustActiveTerminalDisplaySize(10); } void ViewManager::shrinkActiveContainer() { - _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(), -10); -} - -void ViewManager::closeActiveContainer() -{ - // only do something if there is more than one container active - if (_viewSplitter->containers().count() > 1) { - TabbedViewContainer *container = _viewSplitter->activeContainer(); - - removeContainer(container); - - // focus next container so that user can continue typing - // without having to manually focus it themselves - nextContainer(); - } -} - -void ViewManager::closeOtherContainers() -{ - TabbedViewContainer *active = _viewSplitter->activeContainer(); - - foreach (TabbedViewContainer *container, _viewSplitter->containers()) { - if (container != active) { - removeContainer(container); - } - } + _viewContainer->activeViewSplitter()->adjustActiveTerminalDisplaySize(-10); } SessionController *ViewManager::createController(Session *session, TerminalDisplay *view) @@ -583,13 +584,24 @@ 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; } - _viewSplitter->setFocusProxy(controller->view()); + updateTerminalDisplayHistory(controller->view()); _pluggedController = controller; emit activeViewChanged(controller); @@ -600,16 +612,26 @@ return _pluggedController; } -void ViewManager::createView(Session *session, TabbedViewContainer *container, int index) +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); @@ -620,32 +642,22 @@ createController(session, display); _sessionMap[display] = session; - container->addView(display, index); session->addView(display); + _terminalDisplayHistory.append(display); // tell the session whether it has a light or dark background session->setDarkBackground(colorSchemeForProfile(profile)->hasDarkBackground()); - container->setCurrentWidget(display); display->setFocus(Qt::OtherFocusReason); +// updateDetachViewState(); - updateDetachViewState(); -} - -void ViewManager::createView(TabbedViewContainer *tabWidget, Session *session) -{ - const int index = _newTabBehavior == PutNewTabAfterCurrentTab ? - _viewSplitter->activeContainer()->currentIndex() + 1 : -1; - - createView(session, tabWidget, index); + return display; } TabbedViewContainer *ViewManager::createContainer() { - - auto *container = new TabbedViewContainer(this, _viewSplitter); + auto *container = new TabbedViewContainer(this, nullptr); container->setNavigationVisibility(_navigationVisibility); - //TODO: Fix Detaching. - connect(container, &TabbedViewContainer::detachTab, this, &ViewManager::detachView); + connect(container, &TabbedViewContainer::detachTab, this, &ViewManager::detachTab); // connect signals and slots connect(container, &Konsole::TabbedViewContainer::viewAdded, this, @@ -662,28 +674,14 @@ this, &ViewManager::newViewRequest); connect(container, &Konsole::TabbedViewContainer::newViewWithProfileRequest, this, &Konsole::ViewManager::newViewWithProfileRequest); - connect(container, &Konsole::TabbedViewContainer::moveViewRequest, this, - &Konsole::ViewManager::containerMoveViewRequest); connect(container, &Konsole::TabbedViewContainer::viewRemoved, this, &Konsole::ViewManager::viewDestroyed); connect(container, &Konsole::TabbedViewContainer::activeViewChanged, this, &Konsole::ViewManager::viewActivated); return container; } -void ViewManager::containerMoveViewRequest(int index, int id) -{ - auto *container = qobject_cast(sender()); - auto *controller = qobject_cast(ViewProperties::propertiesById(id)); - Q_ASSERT(container); - Q_ASSERT(controller); - - createView(controller->session(), container, index); - controller->session()->refresh(); - container->currentWidget()->setFocus(); -} - void ViewManager::setNavigationMethod(NavigationMethod method) { Q_ASSERT(_actionCollection); @@ -731,9 +729,8 @@ void ViewManager::containerViewsChanged(TabbedViewContainer *container) { - if ((!_viewSplitter.isNull()) && container == _viewSplitter->activeContainer()) { - emit viewPropertiesChanged(viewProperties()); - } + // TODO: Verify that this is right. + emit viewPropertiesChanged(viewProperties()); } void ViewManager::viewDestroyed(QWidget *view) @@ -752,10 +749,10 @@ session->close(); } } + //we only update the focus if the splitter is still alive - if (!_viewSplitter.isNull()) { - updateDetachViewState(); - } + updateDetachViewState(); + // The below causes the menus to be messed up // Only happens when using the tab bar close button // if (_pluggedController) @@ -825,18 +822,16 @@ { QList list; - TabbedViewContainer *container = _viewSplitter->activeContainer(); + TabbedViewContainer *container = _viewContainer; if (container == nullptr) { return {}; } - list.reserve(container->count()); + auto terminalContainers = _viewContainer->findChildren(); + list.reserve(terminalContainers.size()); - for(int i = 0, end = container->count(); i < end; i++) { - auto view = container->terminalAt(i); - ViewProperties *properties = view->sessionController(); - Q_ASSERT(properties); - list << properties; + for(auto terminalDisplay : _viewContainer->findChildren()) { + list.append(terminalDisplay->sessionController()); } return list; @@ -849,15 +844,17 @@ QSet unique; int tab = 1; - TabbedViewContainer *container = _viewSplitter->activeContainer(); + TabbedViewContainer *container = _viewContainer; // 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)); @@ -871,6 +868,7 @@ } tab++; } +#endif // second: all other sessions, in random order // we don't want to have sessions restored that are not connected @@ -886,7 +884,7 @@ TabbedViewContainer *ViewManager::activeContainer() { - return _viewSplitter->activeContainer(); + return _viewContainer; } void ViewManager::restoreSessions(const KConfigGroup &group) @@ -906,7 +904,7 @@ break; } - createView(activeContainer(), session); + createView(session); if (!session->isRunning()) { session->run(); } @@ -916,14 +914,14 @@ } if (display != nullptr) { - _viewSplitter->activeContainer()->setCurrentWidget(display); + activeContainer()->setCurrentWidget(display); display->setFocus(Qt::OtherFocusReason); } if (ids.isEmpty()) { // Session file is unusable, start default Profile Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); Session *session = SessionManager::instance()->createSession(profile); - createView(activeContainer(), session); + createView(session); if (!session->isRunning()) { session->run(); } @@ -963,7 +961,7 @@ QHash::const_iterator i; for (i = _sessionMap.constBegin(); i != _sessionMap.constEnd(); ++i) { if (i.value()->sessionId() == sessionId) { - TabbedViewContainer *container = _viewSplitter->activeContainer(); + TabbedViewContainer *container = activeContainer(); if (container != nullptr) { container->setCurrentWidget(i.key()); } @@ -978,7 +976,7 @@ session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); - createView(activeContainer(), session); + createView(session); session->run(); return session->sessionId(); @@ -1000,7 +998,7 @@ session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); - createView(activeContainer(), session); + createView(session); session->run(); return session->sessionId(); @@ -1023,7 +1021,7 @@ session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); - createView(activeContainer(), session); + createView(session); session->run(); return session->sessionId(); @@ -1061,22 +1059,47 @@ void ViewManager::setTabWidthToText(bool setTabWidthToText) { - for(auto container : _viewSplitter->containers()) { - container->tabBar()->setExpanding(!setTabWidthToText); - container->tabBar()->update(); - } + _viewContainer->tabBar()->setExpanding(!setTabWidthToText); + _viewContainer->tabBar()->update(); } void ViewManager::setNavigationVisibility(NavigationVisibility navigationVisibility) { if (_navigationVisibility != navigationVisibility) { _navigationVisibility = navigationVisibility; - for(auto *container : _viewSplitter->containers()) { - container->setNavigationVisibility(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.h b/src/ViewSplitter.h --- a/src/ViewSplitter.h +++ b/src/ViewSplitter.h @@ -26,10 +26,13 @@ #include #include +// Konsole +#include "konsoleprivate_export.h" + class QFocusEvent; namespace Konsole { -class TabbedViewContainer; +class TerminalDisplay; /** * A splitter which holds a number of ViewContainer objects and allows @@ -43,7 +46,7 @@ * insert a new view container. * Containers can only be removed from the hierarchy by deleting them. */ -class ViewSplitter : public QSplitter +class KONSOLEPRIVATE_EXPORT ViewSplitter : public QSplitter { Q_OBJECT @@ -65,10 +68,10 @@ * will be created, into which the container will * be inserted. */ - void addContainer(TabbedViewContainer *container, Qt::Orientation orientation); + void addTerminalDisplay(TerminalDisplay *terminalDisplay, Qt::Orientation orientation); /** Removes a container from the splitter. The container is not deleted. */ - void removeContainer(TabbedViewContainer *container); + void removeTerminalDisplay(TerminalDisplay *terminalDisplay); /** Returns the child ViewSplitter widget which currently has the focus */ ViewSplitter *activeSplitter(); @@ -84,25 +87,25 @@ * mySplitter->activeSplitter()->activeContainer() where mySplitter * is the ViewSplitter widget at the top of the hierarchy. */ - TabbedViewContainer *activeContainer() const; + TerminalDisplay *activeTerminalDisplay() const; - /** - * Gives the focus to the active view in the specified container + /** Makes the current TerminalDisplay expanded to 100% of the view */ - void setActiveContainer(TabbedViewContainer *container); + void maximizeCurrentTerminal(); - /** - * Returns a list of the containers held by this splitter + /** Restore the sizes of the terminals. */ - QList containers() const - { - return _containers; - } + void restoreOtherTerminals(); + + void handleMinimizeMaximize(bool maximize); + + /** returns the splitter that has no splitter as a parent. */ + ViewSplitter *getToplevelSplitter(); /** - * Gives the focus to the active view in the next container + * Gives the focus to the active view in the specified container */ - void activateNextContainer(); + void setActiveTerminalDisplay(TerminalDisplay *container); /** * Changes the size of the specified @p container by a given @p percentage. @@ -113,73 +116,18 @@ * The sizes of the remaining containers are increased or decreased * uniformly to maintain the width of the splitter. */ - void adjustContainerSize(TabbedViewContainer *container, int percentage); + void adjustActiveTerminalDisplaySize(int percentage); - /** - * Gives the focus to the active view in the previous container - */ - void activatePreviousContainer(); + void focusUp(); + void focusDown(); + void focusLeft(); + void focusRight(); - /** - * Specifies whether the view may be split recursively. - * - * If this is false, all containers will be placed into the same - * top-level splitter. Adding a container with an orientation - * which is different to that specified when adding the previous - * containers will change the orientation for all dividers - * between containers. - * - * If this is true, adding a container to the view splitter with - * an orientation different to the orientation of the previous - * area will result in the previously active container being - * replaced with a new splitter containing the active container - * and the newly added container. - */ - void setRecursiveSplitting(bool recursive); - - /** - * Returns whether the view may be split recursively. - * See setRecursiveSplitting() - */ - bool recursiveSplitting() const; - -Q_SIGNALS: - /** Signal emitted when the last child widget is removed from the splitter */ - void empty(ViewSplitter *splitter); - - /** - * Signal emitted when the containers held by this splitter become empty, this - * differs from the empty() signal which is only emitted when all of the containers - * are deleted. This signal is emitted even if there are still container widgets. - * - * TODO: This does not yet work recursively (ie. when splitters inside splitters have empty containers) - */ - void allContainersEmpty(); - -protected: - //virtual void focusEvent(QFocusEvent* event); + void handleFocusDirection(Qt::Orientation orientation, int direction); + void childEvent(QChildEvent* event) override; private: - // Adds container to splitter's internal list and - // connects signals and slots - void registerContainer(TabbedViewContainer *container); - // Removes container from splitter's internal list and - // removes signals and slots - void unregisterContainer(TabbedViewContainer *container); - void updateSizes(); - -private Q_SLOTS: - // Called to indicate that a child ViewContainer is empty - void containerEmpty(TabbedViewContainer *container); - - // Called to indicate that a child ViewSplitter is empty - // (ie. all child widgets have been deleted) - void childEmpty(ViewSplitter *splitter); - -private: - QList _containers; - bool _recursiveSplitting; }; } #endif //VIEWSPLITTER_H diff --git a/src/ViewSplitter.cpp b/src/ViewSplitter.cpp --- a/src/ViewSplitter.cpp +++ b/src/ViewSplitter.cpp @@ -24,49 +24,38 @@ // Qt #include +#include +#include // Konsole #include "ViewContainer.h" +#include "TerminalDisplay.h" using Konsole::ViewSplitter; -using Konsole::TabbedViewContainer; +using Konsole::TerminalDisplay; -ViewSplitter::ViewSplitter(QWidget *parent) : - QSplitter(parent), - _containers(QList()), - _recursiveSplitting(true) -{ -} +//TODO: Connect the TerminalDisplay destroyed signal here. -void ViewSplitter::childEmpty(ViewSplitter *splitter) +ViewSplitter::ViewSplitter(QWidget *parent) : + QSplitter(parent) { - delete splitter; - - if (count() == 0) { - emit empty(this); - } } -void ViewSplitter::adjustContainerSize(TabbedViewContainer *container, int percentage) +void ViewSplitter::adjustActiveTerminalDisplaySize(int percentage) { - int containerIndex = indexOf(container); - + const int containerIndex = indexOf(activeTerminalDisplay()); Q_ASSERT(containerIndex != -1); QList containerSizes = sizes(); const int oldSize = containerSizes[containerIndex]; - const auto newSize = static_cast(oldSize * (1.0 + percentage / 100.0)); - + const int newSize = static_cast(oldSize * (1.0 + percentage / 100.0)); const int perContainerDelta = (count() == 1) ? 0 : ((newSize - oldSize) / (count() - 1)) * (-1); - for (int i = 0; i < containerSizes.count(); i++) { - if (i == containerIndex) { - containerSizes[i] = newSize; - } else { - containerSizes[i] = containerSizes[i] + perContainerDelta; - } + for (int& size : containerSizes) { + size += perContainerDelta; } + containerSizes[containerIndex] = newSize; setSizes(containerSizes); } @@ -86,194 +75,145 @@ return splitter; } -void ViewSplitter::registerContainer(TabbedViewContainer *container) -{ - _containers << container; - connect(container, &TabbedViewContainer::empty, this, &ViewSplitter::containerEmpty); -} - -void ViewSplitter::unregisterContainer(TabbedViewContainer *container) -{ - _containers.removeAll(container); - disconnect(container, nullptr, this, nullptr); -} - void ViewSplitter::updateSizes() { - int space; - - if (orientation() == Qt::Horizontal) { - space = width() / count(); - } else { - space = height() / count(); - } - - QList widgetSizes; - const int widgetCount = count(); - widgetSizes.reserve(widgetCount); - for (int i = 0; i < widgetCount; i++) { - widgetSizes << space; - } - - setSizes(widgetSizes); -} - -void ViewSplitter::setRecursiveSplitting(bool recursive) -{ - _recursiveSplitting = recursive; -} - -bool ViewSplitter::recursiveSplitting() const -{ - return _recursiveSplitting; + const int space = (orientation() == Qt::Horizontal ? width() : height()) / count(); + setSizes(QVector(count(), space).toList()); } -void ViewSplitter::removeContainer(TabbedViewContainer *container) -{ - Q_ASSERT(containers().contains(container)); - - unregisterContainer(container); -} - -void ViewSplitter::addContainer(TabbedViewContainer *container, Qt::Orientation containerOrientation) +void ViewSplitter::addTerminalDisplay(TerminalDisplay *terminalDisplay, Qt::Orientation containerOrientation) { ViewSplitter *splitter = activeSplitter(); - - if (splitter->count() < 2 - || containerOrientation == splitter->orientation() - || !_recursiveSplitting) { - splitter->registerContainer(container); - splitter->addWidget(container); - - if (splitter->orientation() != containerOrientation) { - splitter->setOrientation(containerOrientation); + 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); } - - splitter->updateSizes(); } else { - auto newSplitter = new ViewSplitter(this); - connect(newSplitter, &Konsole::ViewSplitter::empty, splitter, - &Konsole::ViewSplitter::childEmpty); - - TabbedViewContainer *oldContainer = splitter->activeContainer(); - const int oldContainerIndex = splitter->indexOf(oldContainer); + auto newSplitter = new ViewSplitter(); - splitter->unregisterContainer(oldContainer); - - newSplitter->registerContainer(oldContainer); - newSplitter->registerContainer(container); - - newSplitter->addWidget(oldContainer); - newSplitter->addWidget(container); + 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::containerEmpty(TabbedViewContainer * myContainer) +void ViewSplitter::childEvent(QChildEvent *event) { - _containers.removeAll(myContainer); - if (count() == 0) { - emit empty(this); - } - - int children = 0; - foreach (auto container, _containers) { - children += container->count(); - } - - if (children == 0) { - emit allContainersEmpty(); - } - - // This container is no more, try to find another container to focus. - ViewSplitter *currentSplitter = activeSplitter(); - while(qobject_cast(currentSplitter->parent())) { - currentSplitter = qobject_cast(currentSplitter->parent()); - } + QSplitter::childEvent(event); - for(auto tabWidget : currentSplitter->findChildren()) { - if (tabWidget != myContainer && tabWidget->count()) { - tabWidget->setCurrentIndex(0); + if (event->removed()) { + if (count() == 0) { + deleteLater(); + } + if (!findChild()) { + deleteLater(); } } } -void ViewSplitter::activateNextContainer() +void ViewSplitter::handleFocusDirection(Qt::Orientation orientation, int direction) { - TabbedViewContainer *active = activeContainer(); + auto terminalDisplay = activeTerminalDisplay(); + auto parentSplitter = qobject_cast(terminalDisplay->parentWidget()); + auto topSplitter = parentSplitter->getToplevelSplitter(); - int index = _containers.indexOf(active); + const auto handleWidth = parentSplitter->handleWidth() <= 1 ? 4 : parentSplitter->handleWidth(); - if (index == -1) { - return; - } + const auto start = QPoint(terminalDisplay->x(), terminalDisplay->y()); + const auto startMapped = parentSplitter->mapTo(topSplitter, start); - if (index == _containers.count() - 1) { - index = 0; - } else { - index++; - } + 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; - setActiveContainer(_containers.at(index)); + 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::activatePreviousContainer() +void ViewSplitter::focusUp() { - TabbedViewContainer *active = activeContainer(); + handleFocusDirection(Qt::Vertical, -1); +} - int index = _containers.indexOf(active); +void ViewSplitter::focusDown() +{ + handleFocusDirection(Qt::Vertical, +1); +} - if (index == 0) { - index = _containers.count() - 1; - } else { - index--; - } +void ViewSplitter::focusLeft() +{ + handleFocusDirection(Qt::Horizontal, -1); +} - setActiveContainer(_containers.at(index)); +void ViewSplitter::focusRight() +{ + handleFocusDirection(Qt::Horizontal, +1); } -void ViewSplitter::setActiveContainer(TabbedViewContainer *container) +TerminalDisplay *ViewSplitter::activeTerminalDisplay() const { - QWidget *activeView = container->currentWidget(); + auto focusedWidget = qobject_cast(focusWidget()); + return focusedWidget ? focusedWidget : findChild(); +} - if (activeView != nullptr) { - activeView->setFocus(Qt::OtherFocusReason); - } +void ViewSplitter::maximizeCurrentTerminal() +{ + handleMinimizeMaximize(true); } -TabbedViewContainer *ViewSplitter::activeContainer() const +void ViewSplitter::restoreOtherTerminals() { - if (QWidget *focusW = focusWidget()) { - TabbedViewContainer *focusContainer = nullptr; - - while (focusW != nullptr) { - foreach (TabbedViewContainer *container, _containers) { - if (container == focusW) { - focusContainer = container; - break; - } - } - focusW = focusW->parentWidget(); - } + handleMinimizeMaximize(false); +} - if (focusContainer != nullptr) { - return focusContainer; +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)(); } } +} - QList splitters = findChildren(); - - if (!splitters.isEmpty()) { - return splitters.last()->activeContainer(); - } else { - if (!_containers.isEmpty()) { - return _containers.last(); - } else { - return nullptr; - } +ViewSplitter *ViewSplitter::getToplevelSplitter() +{ + ViewSplitter *current = this; + while(qobject_cast(current->parentWidget())) { + current = qobject_cast(current->parentWidget()); } + return current; }