File indexing completed on 2024-04-21 05:51:28

0001 /*
0002     SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // Own
0008 #include "ViewManager.h"
0009 
0010 #include "config-konsole.h"
0011 
0012 // Qt
0013 #include <QFile>
0014 #include <QFileDialog>
0015 #include <QStandardPaths>
0016 #include <QStringList>
0017 #include <QTabBar>
0018 
0019 #include <QJsonArray>
0020 #include <QJsonDocument>
0021 
0022 #include <QDBusArgument>
0023 #include <QDBusMetaType>
0024 
0025 // KDE
0026 #include <KActionCollection>
0027 #include <KActionMenu>
0028 #include <KConfigGroup>
0029 #include <KLocalizedString>
0030 #include <KMessageBox>
0031 
0032 // Konsole
0033 #include <windowadaptor.h>
0034 
0035 #include "colorscheme/ColorScheme.h"
0036 #include "colorscheme/ColorSchemeManager.h"
0037 
0038 #include "profile/ProfileManager.h"
0039 
0040 #include "session/Session.h"
0041 #include "session/SessionController.h"
0042 #include "session/SessionManager.h"
0043 
0044 #include "terminalDisplay/TerminalDisplay.h"
0045 #include "widgets/ViewContainer.h"
0046 #include "widgets/ViewSplitter.h"
0047 
0048 using namespace Konsole;
0049 
0050 int ViewManager::lastManagerId = 0;
0051 
0052 Q_DECLARE_METATYPE(QList<double>);
0053 
0054 ViewManager::ViewManager(QObject *parent, KActionCollection *collection)
0055     : QObject(parent)
0056     , _viewContainer(nullptr)
0057     , _pluggedController(nullptr)
0058     , _sessionMap(QHash<TerminalDisplay *, Session *>())
0059     , _actionCollection(collection)
0060     , _navigationMethod(TabbedNavigation)
0061     , _navigationVisibility(NavigationNotSet)
0062     , _managerId(0)
0063     , _terminalDisplayHistoryIndex(-1)
0064 {
0065     qDBusRegisterMetaType<QList<double>>();
0066 
0067     _viewContainer = createContainer();
0068     // setup actions which are related to the views
0069     setupActions();
0070 
0071     /* TODO: Reconnect
0072     // emit a signal when all of the views held by this view manager are destroyed
0073     */
0074     connect(_viewContainer.data(), &Konsole::TabbedViewContainer::empty, this, &Konsole::ViewManager::empty);
0075 
0076     // listen for profile changes
0077     connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged, this, &Konsole::ViewManager::profileChanged);
0078     connect(SessionManager::instance(), &Konsole::SessionManager::sessionUpdated, this, &Konsole::ViewManager::updateViewsForSession);
0079 
0080     // prepare DBus communication
0081     new WindowAdaptor(this);
0082 
0083     _managerId = ++lastManagerId;
0084     QDBusConnection::sessionBus().registerObject(QLatin1String("/Windows/") + QString::number(_managerId), this);
0085 }
0086 
0087 ViewManager::~ViewManager() = default;
0088 
0089 int ViewManager::managerId() const
0090 {
0091     return _managerId;
0092 }
0093 
0094 QWidget *ViewManager::activeView() const
0095 {
0096     return _viewContainer->currentWidget();
0097 }
0098 
0099 QWidget *ViewManager::widget() const
0100 {
0101     return _viewContainer;
0102 }
0103 
0104 void ViewManager::setupActions()
0105 {
0106     Q_ASSERT(_actionCollection);
0107     if (_actionCollection == nullptr) {
0108         return;
0109     }
0110 
0111     KActionCollection *collection = _actionCollection;
0112     KActionMenu *splitViewActions =
0113         new KActionMenu(QIcon::fromTheme(QStringLiteral("view-split-left-right")), i18nc("@action:inmenu", "Split View"), collection);
0114     splitViewActions->setPopupMode(QToolButton::InstantPopup);
0115     collection->addAction(QStringLiteral("split-view"), splitViewActions);
0116 
0117     // Let's reuse the pointer, no need not to.
0118     auto *action = new QAction(this);
0119     action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right")));
0120     action->setText(i18nc("@action:inmenu", "Split View Left/Right"));
0121     connect(action, &QAction::triggered, this, &ViewManager::splitLeftRight);
0122     collection->addAction(QStringLiteral("split-view-left-right"), action);
0123     collection->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_ParenLeft));
0124     splitViewActions->addAction(action);
0125 
0126     action = new QAction(this);
0127     action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")));
0128     action->setText(i18nc("@action:inmenu", "Split View Top/Bottom"));
0129     connect(action, &QAction::triggered, this, &ViewManager::splitTopBottom);
0130     collection->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_ParenRight));
0131     collection->addAction(QStringLiteral("split-view-top-bottom"), action);
0132     splitViewActions->addAction(action);
0133 
0134     action = new QAction(this);
0135     action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-auto")));
0136     action->setText(i18nc("@action:inmenu", "Split View Automatically"));
0137     connect(action, &QAction::triggered, this, &ViewManager::splitAuto);
0138     collection->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Asterisk));
0139     collection->addAction(QStringLiteral("split-view-auto"), action);
0140     splitViewActions->addAction(action);
0141 
0142     splitViewActions->addSeparator();
0143 
0144     action = new QAction(this);
0145     action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right")));
0146     action->setText(i18nc("@action:inmenu", "Split View Left/Right from next tab"));
0147     connect(action, &QAction::triggered, this, &ViewManager::splitLeftRightNextTab);
0148     collection->addAction(QStringLiteral("split-view-left-right-next-tab"), action);
0149     splitViewActions->addAction(action);
0150     _multiTabOnlyActions << action;
0151 
0152     action = new QAction(this);
0153     action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")));
0154     action->setText(i18nc("@action:inmenu", "Split View Top/Bottom from next tab"));
0155     connect(action, &QAction::triggered, this, &ViewManager::splitTopBottomNextTab);
0156     collection->addAction(QStringLiteral("split-view-top-bottom-next-tab"), action);
0157     splitViewActions->addAction(action);
0158     _multiTabOnlyActions << action;
0159 
0160     action = new QAction(this);
0161     action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-auto")));
0162     action->setText(i18nc("@action:inmenu", "Split View Automatically from next tab"));
0163     connect(action, &QAction::triggered, this, &ViewManager::splitAutoNextTab);
0164     collection->addAction(QStringLiteral("split-view-auto-next-tab"), action);
0165     splitViewActions->addAction(action);
0166     _multiTabOnlyActions << action;
0167 
0168     splitViewActions->addSeparator();
0169 
0170     action = new QAction(this);
0171     action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")));
0172     action->setText(i18nc("@action:inmenu", "Load a new tab with layout 2x2 terminals"));
0173     connect(action, &QAction::triggered, this, [this]() {
0174         this->loadLayout(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("konsole/2x2-terminals.json")));
0175     });
0176     collection->addAction(QStringLiteral("load-terminals-layout-2x2"), action);
0177     splitViewActions->addAction(action);
0178 
0179     action = new QAction(this);
0180     action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right")));
0181     action->setText(i18nc("@action:inmenu", "Load a new tab with layout 2x1 terminals"));
0182     connect(action, &QAction::triggered, this, [this]() {
0183         this->loadLayout(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("konsole/2x1-terminals.json")));
0184     });
0185     collection->addAction(QStringLiteral("load-terminals-layout-2x1"), action);
0186     splitViewActions->addAction(action);
0187 
0188     action = new QAction(this);
0189     action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")));
0190     action->setText(i18nc("@action:inmenu", "Load a new tab with layout 1x2 terminals"));
0191     connect(action, &QAction::triggered, this, [this]() {
0192         this->loadLayout(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("konsole/1x2-terminals.json")));
0193     });
0194     collection->addAction(QStringLiteral("load-terminals-layout-1x2"), action);
0195     splitViewActions->addAction(action);
0196 
0197     action = new QAction(this);
0198     action->setText(i18nc("@action:inmenu", "Expand View"));
0199     action->setEnabled(false);
0200     connect(action, &QAction::triggered, this, &ViewManager::expandActiveContainer);
0201     collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::Key_BracketRight);
0202     collection->addAction(QStringLiteral("expand-active-view"), action);
0203     _multiSplitterOnlyActions << action;
0204 
0205     action = new QAction(this);
0206     action->setText(i18nc("@action:inmenu", "Shrink View"));
0207     collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::Key_BracketLeft);
0208     action->setEnabled(false);
0209     collection->addAction(QStringLiteral("shrink-active-view"), action);
0210     connect(action, &QAction::triggered, this, &ViewManager::shrinkActiveContainer);
0211     _multiSplitterOnlyActions << action;
0212 
0213     action = collection->addAction(QStringLiteral("detach-view"));
0214     action->setEnabled(true);
0215     action->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach")));
0216     action->setText(i18nc("@action:inmenu", "Detach Current &View"));
0217 
0218     connect(action, &QAction::triggered, this, &ViewManager::detachActiveView);
0219     _multiSplitterOnlyActions << action;
0220 
0221     // Ctrl+Shift+D is not used as a shortcut by default because it is too close
0222     // to Ctrl+D - which will terminate the session in many cases
0223     collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::Key_H);
0224 
0225     action = collection->addAction(QStringLiteral("detach-tab"));
0226     action->setEnabled(true);
0227     action->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach")));
0228     action->setText(i18nc("@action:inmenu", "Detach Current &Tab"));
0229     connect(action, &QAction::triggered, this, &ViewManager::detachActiveTab);
0230     _multiTabOnlyActions << action;
0231 
0232     // keyboard shortcut only actions
0233     action = new QAction(i18nc("@action Shortcut entry", "Next Tab"), this);
0234     const QList<QKeySequence> nextViewActionKeys{QKeySequence{Qt::SHIFT | Qt::Key_Right}, QKeySequence{Qt::CTRL | Qt::Key_PageDown}};
0235     collection->setDefaultShortcuts(action, nextViewActionKeys);
0236     collection->addAction(QStringLiteral("next-tab"), action);
0237     connect(action, &QAction::triggered, this, &ViewManager::nextView);
0238     _multiTabOnlyActions << action;
0239     // _viewSplitter->addAction(nextViewAction);
0240 
0241     action = new QAction(i18nc("@action Shortcut entry", "Previous Tab"), this);
0242     const QList<QKeySequence> previousViewActionKeys{QKeySequence{Qt::SHIFT | Qt::Key_Left}, QKeySequence{Qt::CTRL | Qt::Key_PageUp}};
0243     collection->setDefaultShortcuts(action, previousViewActionKeys);
0244     collection->addAction(QStringLiteral("previous-tab"), action);
0245     connect(action, &QAction::triggered, this, &ViewManager::previousView);
0246     _multiTabOnlyActions << action;
0247     // _viewSplitter->addAction(previousViewAction);
0248 
0249     action = new QAction(i18nc("@action Shortcut entry", "Focus Above Terminal"), this);
0250     connect(action, &QAction::triggered, this, &ViewManager::focusUp);
0251     collection->addAction(QStringLiteral("focus-view-above"), action);
0252     collection->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_Up);
0253     _viewContainer->addAction(action);
0254     _multiSplitterOnlyActions << action;
0255 
0256     action = new QAction(i18nc("@action Shortcut entry", "Focus Below Terminal"), this);
0257     collection->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_Down);
0258     collection->addAction(QStringLiteral("focus-view-below"), action);
0259     connect(action, &QAction::triggered, this, &ViewManager::focusDown);
0260     _multiSplitterOnlyActions << action;
0261     _viewContainer->addAction(action);
0262 
0263     action = new QAction(i18nc("@action Shortcut entry", "Focus Left Terminal"), this);
0264     collection->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_Left);
0265     connect(action, &QAction::triggered, this, &ViewManager::focusLeft);
0266     collection->addAction(QStringLiteral("focus-view-left"), action);
0267     _multiSplitterOnlyActions << action;
0268 
0269     action = new QAction(i18nc("@action Shortcut entry", "Focus Right Terminal"), this);
0270     collection->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_Right);
0271     connect(action, &QAction::triggered, this, &ViewManager::focusRight);
0272     collection->addAction(QStringLiteral("focus-view-right"), action);
0273     _multiSplitterOnlyActions << action;
0274 
0275     action = new QAction(i18nc("@action Shortcut entry", "Switch to Last Tab"), this);
0276     connect(action, &QAction::triggered, this, &ViewManager::lastView);
0277     collection->addAction(QStringLiteral("last-tab"), action);
0278     _multiTabOnlyActions << action;
0279 
0280     action = new QAction(i18nc("@action Shortcut entry", "Last Used Tabs"), this);
0281     connect(action, &QAction::triggered, this, &ViewManager::lastUsedView);
0282     collection->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Tab));
0283     collection->addAction(QStringLiteral("last-used-tab"), action);
0284 
0285     action = new QAction(i18nc("@action Shortcut entry", "Toggle Between Two Tabs"), this);
0286     connect(action, &QAction::triggered, this, &Konsole::ViewManager::toggleTwoViews);
0287     collection->addAction(QStringLiteral("toggle-two-tabs"), action);
0288     _multiTabOnlyActions << action;
0289 
0290     action = new QAction(i18nc("@action Shortcut entry", "Last Used Tabs (Reverse)"), this);
0291     collection->addAction(QStringLiteral("last-used-tab-reverse"), action);
0292     collection->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_Tab);
0293     connect(action, &QAction::triggered, this, &ViewManager::lastUsedViewReverse);
0294 
0295     action = new QAction(i18nc("@action Shortcut entry", "Toggle maximize current view"), this);
0296     action->setText(i18nc("@action:inmenu", "Toggle maximize current view"));
0297     action->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen")));
0298     collection->addAction(QStringLiteral("toggle-maximize-current-view"), action);
0299     collection->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_E);
0300     connect(action, &QAction::triggered, _viewContainer, &TabbedViewContainer::toggleMaximizeCurrentTerminal);
0301     _multiSplitterOnlyActions << action;
0302     _viewContainer->addAction(action);
0303 
0304     action = new QAction(i18nc("@action Shortcut entry", "Toggle zoom-maximize current view"), this);
0305     action->setText(i18nc("@action:inmenu", "Toggle zoom-maximize current view"));
0306     action->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen")));
0307     collection->addAction(QStringLiteral("toggle-zoom-current-view"), action);
0308     collection->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_Z);
0309     connect(action, &QAction::triggered, _viewContainer, &TabbedViewContainer::toggleZoomMaximizeCurrentTerminal);
0310     _multiSplitterOnlyActions << action;
0311     _viewContainer->addAction(action);
0312 
0313     action = new QAction(i18nc("@action Shortcut entry", "Move tab to the right"), this);
0314     collection->addAction(QStringLiteral("move-tab-to-right"), action);
0315     collection->setDefaultShortcut(action, Qt::CTRL | Qt::ALT | Qt::Key_Right);
0316     connect(action, &QAction::triggered, _viewContainer, &TabbedViewContainer::moveTabRight);
0317     _multiTabOnlyActions << action;
0318     _viewContainer->addAction(action);
0319 
0320     action = new QAction(i18nc("@action Shortcut entry", "Move tab to the left"), this);
0321     collection->addAction(QStringLiteral("move-tab-to-left"), action);
0322     collection->setDefaultShortcut(action, Qt::CTRL | Qt::ALT | Qt::Key_Left);
0323     connect(action, &QAction::triggered, _viewContainer, &TabbedViewContainer::moveTabLeft);
0324     _multiTabOnlyActions << action;
0325     _viewContainer->addAction(action);
0326 
0327     action = new QAction(i18nc("@action Shortcut entry", "Setup semantic integration (bash)"), this);
0328     collection->addAction(QStringLiteral("semantic-setup-bash"), action);
0329     collection->setDefaultShortcut(action, Qt::CTRL | Qt::ALT | Qt::Key_BracketRight);
0330     connect(action, &QAction::triggered, this, &ViewManager::semanticSetupBash);
0331     _viewContainer->addAction(action);
0332 
0333     action = new QAction(i18nc("@action Shortcut entry", "Toggle semantic hints display"), this);
0334     collection->addAction(QStringLiteral("toggle-semantic-hints"), action);
0335     collection->setDefaultShortcut(action, Qt::CTRL | Qt::ALT | Qt::Key_BracketLeft);
0336     connect(action, &QAction::triggered, this, &ViewManager::toggleSemanticHints);
0337     _viewContainer->addAction(action);
0338 
0339     action = new QAction(i18nc("@action Shortcut entry", "Toggle line numbers display"), this);
0340     collection->addAction(QStringLiteral("toggle-line-numbers"), action);
0341     collection->setDefaultShortcut(action, Qt::CTRL | Qt::ALT | Qt::Key_Backslash);
0342     connect(action, &QAction::triggered, this, &ViewManager::toggleLineNumbers);
0343     _viewContainer->addAction(action);
0344 
0345     action = new QAction(this);
0346     action->setText(i18nc("@action:inmenu", "Equal size to all views"));
0347     collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::SHIFT | Qt::Key_Backslash);
0348     action->setEnabled(false);
0349     collection->addAction(QStringLiteral("equal-size-view"), action);
0350     connect(action, &QAction::triggered, this, &ViewManager::equalSizeAllContainers);
0351     _multiSplitterOnlyActions << action;
0352 
0353     // _viewSplitter->addAction(lastUsedViewReverseAction);
0354     const int SWITCH_TO_TAB_COUNT = 19;
0355     for (int i = 0; i < SWITCH_TO_TAB_COUNT; ++i) {
0356         action = new QAction(i18nc("@action Shortcut entry", "Switch to Tab %1", i + 1), this);
0357         connect(action, &QAction::triggered, this, [this, i]() {
0358             switchToView(i);
0359         });
0360         collection->addAction(QStringLiteral("switch-to-tab-%1").arg(i), action);
0361         _multiTabOnlyActions << action;
0362 
0363         // only add default shortcut bindings for the first 10 tabs, regardless of SWITCH_TO_TAB_COUNT
0364         if (i < 9) {
0365             collection->setDefaultShortcut(action, QStringLiteral("Alt+%1").arg(i + 1));
0366         } else if (i == 9) {
0367             // add shortcut for 10th tab
0368             collection->setDefaultShortcut(action, QKeySequence(Qt::ALT | Qt::Key_0));
0369         }
0370     }
0371 
0372     connect(_viewContainer, &TabbedViewContainer::viewAdded, this, &ViewManager::toggleActionsBasedOnState);
0373     connect(_viewContainer, &QTabWidget::currentChanged, this, &ViewManager::toggleActionsBasedOnState);
0374     // Let the view container remove the view before counting tabs
0375     connect(_viewContainer, &TabbedViewContainer::viewRemoved, this, &ViewManager::toggleActionsBasedOnState);
0376 
0377     toggleActionsBasedOnState();
0378 }
0379 
0380 void ViewManager::toggleActionsBasedOnState()
0381 {
0382     const int count = _viewContainer->count();
0383     for (QAction *tabOnlyAction : std::as_const(_multiTabOnlyActions)) {
0384         tabOnlyAction->setEnabled(count > 1);
0385     }
0386 
0387     if ((_viewContainer != nullptr) && (_viewContainer->activeViewSplitter() != nullptr)) {
0388         const int splitCount = _viewContainer->activeViewSplitter()->getToplevelSplitter()->findChildren<TerminalDisplay *>().count();
0389 
0390         for (QAction *action : std::as_const(_multiSplitterOnlyActions)) {
0391             action->setEnabled(splitCount > 1);
0392         }
0393     }
0394 }
0395 
0396 void ViewManager::switchToView(int index)
0397 {
0398     _viewContainer->setCurrentIndex(index);
0399 }
0400 
0401 void ViewManager::switchToTerminalDisplay(Konsole::TerminalDisplay *terminalDisplay)
0402 {
0403     auto splitter = qobject_cast<ViewSplitter *>(terminalDisplay->parentWidget());
0404     auto toplevelSplitter = splitter->getToplevelSplitter();
0405 
0406     // Focus the TermialDisplay
0407     terminalDisplay->setFocus();
0408 
0409     if (_viewContainer->currentWidget() != toplevelSplitter) {
0410         // Focus the tab
0411         switchToView(_viewContainer->indexOf(toplevelSplitter));
0412     }
0413 }
0414 
0415 void ViewManager::focusUp()
0416 {
0417     _viewContainer->activeViewSplitter()->focusUp();
0418 }
0419 
0420 void ViewManager::focusDown()
0421 {
0422     _viewContainer->activeViewSplitter()->focusDown();
0423 }
0424 
0425 void ViewManager::focusLeft()
0426 {
0427     _viewContainer->activeViewSplitter()->focusLeft();
0428 }
0429 
0430 void ViewManager::focusRight()
0431 {
0432     _viewContainer->activeViewSplitter()->focusRight();
0433 }
0434 
0435 void ViewManager::moveActiveViewLeft()
0436 {
0437     _viewContainer->moveActiveView(TabbedViewContainer::MoveViewLeft);
0438 }
0439 
0440 void ViewManager::moveActiveViewRight()
0441 {
0442     _viewContainer->moveActiveView(TabbedViewContainer::MoveViewRight);
0443 }
0444 
0445 void ViewManager::nextContainer()
0446 {
0447     //    _viewSplitter->activateNextContainer();
0448 }
0449 
0450 void ViewManager::nextView()
0451 {
0452     _viewContainer->activateNextView();
0453 }
0454 
0455 void ViewManager::previousView()
0456 {
0457     _viewContainer->activatePreviousView();
0458 }
0459 
0460 void ViewManager::lastView()
0461 {
0462     _viewContainer->activateLastView();
0463 }
0464 
0465 void ViewManager::activateLastUsedView(bool reverse)
0466 {
0467     if (_terminalDisplayHistory.count() <= 1) {
0468         return;
0469     }
0470 
0471     if (_terminalDisplayHistoryIndex == -1) {
0472         _terminalDisplayHistoryIndex = reverse ? _terminalDisplayHistory.count() - 1 : 1;
0473     } else if (reverse) {
0474         if (_terminalDisplayHistoryIndex == 0) {
0475             _terminalDisplayHistoryIndex = _terminalDisplayHistory.count() - 1;
0476         } else {
0477             _terminalDisplayHistoryIndex--;
0478         }
0479     } else {
0480         if (_terminalDisplayHistoryIndex >= _terminalDisplayHistory.count() - 1) {
0481             _terminalDisplayHistoryIndex = 0;
0482         } else {
0483             _terminalDisplayHistoryIndex++;
0484         }
0485     }
0486 
0487     switchToTerminalDisplay(_terminalDisplayHistory[_terminalDisplayHistoryIndex]);
0488 }
0489 
0490 void ViewManager::lastUsedView()
0491 {
0492     activateLastUsedView(false);
0493 }
0494 
0495 void ViewManager::lastUsedViewReverse()
0496 {
0497     activateLastUsedView(true);
0498 }
0499 
0500 void ViewManager::toggleTwoViews()
0501 {
0502     if (_terminalDisplayHistory.count() <= 1) {
0503         return;
0504     }
0505 
0506     switchToTerminalDisplay(_terminalDisplayHistory.at(1));
0507 }
0508 
0509 void ViewManager::detachActiveView()
0510 {
0511     // find the currently active view and remove it from its container
0512     if ((_viewContainer->findChildren<TerminalDisplay *>()).count() > 1) {
0513         auto activeSplitter = _viewContainer->activeViewSplitter();
0514         activeSplitter->clearMaximized();
0515         auto terminal = activeSplitter->activeTerminalDisplay();
0516         auto newSplitter = new ViewSplitter();
0517         newSplitter->addTerminalDisplay(terminal, Qt::Horizontal);
0518         QHash<TerminalDisplay *, Session *> detachedSessions = forgetAll(newSplitter);
0519         Q_EMIT terminalsDetached(newSplitter, detachedSessions);
0520         focusAnotherTerminal(activeSplitter->getToplevelSplitter());
0521         toggleActionsBasedOnState();
0522     }
0523 }
0524 
0525 void ViewManager::detachActiveTab()
0526 {
0527     if (_viewContainer->count() < 2) {
0528         return;
0529     }
0530     const int currentIdx = _viewContainer->currentIndex();
0531     detachTab(currentIdx);
0532 }
0533 
0534 void ViewManager::detachTab(int tabIdx)
0535 {
0536     ViewSplitter *splitter = _viewContainer->viewSplitterAt(tabIdx);
0537     QHash<TerminalDisplay *, Session *> detachedSessions = forgetAll(_viewContainer->viewSplitterAt(tabIdx));
0538     Q_EMIT terminalsDetached(splitter, detachedSessions);
0539 }
0540 
0541 void ViewManager::semanticSetupBash()
0542 {
0543     int currentSessionId = currentSession();
0544     // At least one display/session exists if we are splitting
0545     Q_ASSERT(currentSessionId >= 0);
0546 
0547     Session *activeSession = SessionManager::instance()->idToSession(currentSessionId);
0548     Q_ASSERT(activeSession);
0549 
0550     activeSession->sendTextToTerminal(QStringLiteral(R"(if [[ ! $PS1 =~ 133 ]] ; then
0551         PS1='\[\e]133;L\a\]\[\e]133;D;$?\]\[\e]133;A\a\]'$PS1'\[\e]133;B\a\]' ;
0552         PS2='\[\e]133;A\a\]'$PS2'\[\e]133;B\a\]' ;
0553         PS0='\[\e]133;C\a\]' ; fi)"),
0554                                       QChar());
0555 }
0556 
0557 void ViewManager::toggleSemanticHints()
0558 {
0559     int currentSessionId = currentSession();
0560     Q_ASSERT(currentSessionId >= 0);
0561     Session *activeSession = SessionManager::instance()->idToSession(currentSessionId);
0562     Q_ASSERT(activeSession);
0563     auto profile = SessionManager::instance()->sessionProfile(activeSession);
0564 
0565     profile->setProperty(Profile::SemanticHints, (profile->semanticHints() + 1) % 3);
0566 
0567     auto activeTerminalDisplay = _viewContainer->activeViewSplitter()->activeTerminalDisplay();
0568     const char *names[3] = {"Never", "Sometimes", "Always"};
0569     activeTerminalDisplay->showNotification(i18n("Semantic hints ") + i18n(names[profile->semanticHints()]));
0570     activeTerminalDisplay->update();
0571 }
0572 
0573 void ViewManager::toggleLineNumbers()
0574 {
0575     int currentSessionId = currentSession();
0576     Q_ASSERT(currentSessionId >= 0);
0577     Session *activeSession = SessionManager::instance()->idToSession(currentSessionId);
0578     Q_ASSERT(activeSession);
0579     auto profile = SessionManager::instance()->sessionProfile(activeSession);
0580 
0581     profile->setProperty(Profile::LineNumbers, (profile->lineNumbers() + 1) % 3);
0582 
0583     auto activeTerminalDisplay = _viewContainer->activeViewSplitter()->activeTerminalDisplay();
0584     const char *names[3] = {"Never", "Sometimes", "Always"};
0585     activeTerminalDisplay->showNotification(i18n("Line numbers ") + i18n(names[profile->lineNumbers()]));
0586     activeTerminalDisplay->update();
0587 }
0588 
0589 QHash<TerminalDisplay *, Session *> ViewManager::forgetAll(ViewSplitter *splitter)
0590 {
0591     splitter->setParent(nullptr);
0592     QHash<TerminalDisplay *, Session *> detachedSessions;
0593     const QList<TerminalDisplay *> displays = splitter->findChildren<TerminalDisplay *>();
0594     for (TerminalDisplay *terminal : displays) {
0595         Session *session = forgetTerminal(terminal);
0596         detachedSessions[terminal] = session;
0597     }
0598     return detachedSessions;
0599 }
0600 
0601 Session *ViewManager::forgetTerminal(TerminalDisplay *terminal)
0602 {
0603     unregisterTerminal(terminal);
0604 
0605     removeController(terminal->sessionController());
0606     auto session = _sessionMap.take(terminal);
0607     if (session != nullptr) {
0608         disconnect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished);
0609     }
0610     _viewContainer->disconnectTerminalDisplay(terminal);
0611     updateTerminalDisplayHistory(terminal, true);
0612     return session;
0613 }
0614 
0615 Session *ViewManager::createSession(const Profile::Ptr &profile, const QString &directory)
0616 {
0617     Session *session = SessionManager::instance()->createSession(profile);
0618     Q_ASSERT(session);
0619     if (!directory.isEmpty()) {
0620         session->setInitialWorkingDirectory(directory);
0621     }
0622     session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId()));
0623     return session;
0624 }
0625 
0626 void ViewManager::sessionFinished(Session *session)
0627 {
0628     // if this slot is called after the view manager's main widget
0629     // has been destroyed, do nothing
0630     if (_viewContainer.isNull()) {
0631         return;
0632     }
0633 
0634     if (_navigationMethod == TabbedNavigation) {
0635         // The last session/tab, and only one view (no splits), emit empty()
0636         // so that close() is called in MainWindow, fixes #432077
0637         if (_viewContainer->count() == 1 && _viewContainer->currentTabViewCount() == 1) {
0638             Q_EMIT empty();
0639             return;
0640         }
0641     }
0642 
0643     Q_ASSERT(session);
0644 
0645     auto view = _sessionMap.key(session);
0646     _sessionMap.remove(view);
0647 
0648     if (SessionManager::instance()->isClosingAllSessions()) {
0649         return;
0650     }
0651 
0652     // Before deleting the view, let's unmaximize if it's maximized.
0653     auto *splitter = qobject_cast<ViewSplitter *>(view->parentWidget());
0654     if (splitter == nullptr) {
0655         return;
0656     }
0657     splitter->clearMaximized();
0658 
0659     view->deleteLater();
0660     connect(view, &QObject::destroyed, this, [this]() {
0661         toggleActionsBasedOnState();
0662     });
0663 
0664     // Only remove the controller from factory() if it's actually controlling
0665     // the session from the sender.
0666     // This fixes BUG: 348478 - messed up menus after a detached tab is closed
0667     if ((!_pluggedController.isNull()) && (_pluggedController->session() == session)) {
0668         // This is needed to remove this controller from factory() in
0669         // order to prevent BUG: 185466 - disappearing menu popup
0670         Q_EMIT unplugController(_pluggedController);
0671     }
0672 
0673     if (!_sessionMap.empty()) {
0674         updateTerminalDisplayHistory(view, true);
0675         focusAnotherTerminal(splitter->getToplevelSplitter());
0676     }
0677 }
0678 
0679 void ViewManager::focusAnotherTerminal(ViewSplitter *toplevelSplitter)
0680 {
0681     auto tabTterminalDisplays = toplevelSplitter->findChildren<TerminalDisplay *>();
0682     if (tabTterminalDisplays.count() == 0) {
0683         return;
0684     }
0685 
0686     if (tabTterminalDisplays.count() > 1) {
0687         // Give focus to the last used terminal in this tab
0688         for (auto *historyItem : _terminalDisplayHistory) {
0689             for (auto *terminalDisplay : tabTterminalDisplays) {
0690                 if (terminalDisplay == historyItem) {
0691                     terminalDisplay->setFocus(Qt::OtherFocusReason);
0692                     return;
0693                 }
0694             }
0695         }
0696     }
0697 
0698     if (_terminalDisplayHistory.count() >= 1) {
0699         // Give focus to the last used terminal tab
0700         switchToTerminalDisplay(_terminalDisplayHistory[0]);
0701     }
0702 }
0703 
0704 void ViewManager::activateView(TerminalDisplay *view)
0705 {
0706     if (view) {
0707         // focus the activated view, this will cause the SessionController
0708         // to notify the world that the view has been focused and the appropriate UI
0709         // actions will be plugged in.
0710         view->setFocus(Qt::OtherFocusReason);
0711     }
0712 }
0713 
0714 void ViewManager::splitLeftRight()
0715 {
0716     splitView(Qt::Horizontal);
0717 }
0718 
0719 void ViewManager::splitTopBottom()
0720 {
0721     splitView(Qt::Vertical);
0722 }
0723 
0724 void ViewManager::splitAuto(bool fromNextTab)
0725 {
0726     Qt::Orientation orientation;
0727     auto activeTerminalDisplay = _viewContainer->activeViewSplitter()->activeTerminalDisplay();
0728     if (activeTerminalDisplay->width() > activeTerminalDisplay->height()) {
0729         orientation = Qt::Horizontal;
0730     } else {
0731         orientation = Qt::Vertical;
0732     }
0733     splitView(orientation, fromNextTab);
0734 }
0735 
0736 void ViewManager::splitLeftRightNextTab()
0737 {
0738     splitView(Qt::Horizontal, true);
0739 }
0740 
0741 void ViewManager::splitTopBottomNextTab()
0742 {
0743     splitView(Qt::Vertical, true);
0744 }
0745 
0746 void ViewManager::splitAutoNextTab()
0747 {
0748     splitAuto(true);
0749 }
0750 
0751 void ViewManager::splitView(Qt::Orientation orientation, bool fromNextTab)
0752 {
0753     TerminalDisplay *terminalDisplay;
0754     TerminalDisplay *focused;
0755     if (fromNextTab) {
0756         int tabId = _viewContainer->indexOf(_viewContainer->activeViewSplitter());
0757         auto nextTab = _viewContainer->viewSplitterAt(tabId + 1);
0758 
0759         if (!nextTab) {
0760             return;
0761         }
0762         terminalDisplay = nextTab->activeTerminalDisplay();
0763         focused = _viewContainer->activeViewSplitter()->activeTerminalDisplay();
0764     } else {
0765         int currentSessionId = currentSession();
0766         // At least one display/session exists if we are splitting
0767         Q_ASSERT(currentSessionId >= 0);
0768 
0769         Session *activeSession = SessionManager::instance()->idToSession(currentSessionId);
0770         Q_ASSERT(activeSession);
0771 
0772         auto profile = SessionManager::instance()->sessionProfile(activeSession);
0773 
0774         const QString directory = profile->startInCurrentSessionDir() ? activeSession->currentWorkingDirectory() : QString();
0775         auto *session = createSession(profile, directory);
0776 
0777         focused = terminalDisplay = createView(session);
0778     }
0779 
0780     _viewContainer->splitView(terminalDisplay, orientation);
0781 
0782     toggleActionsBasedOnState();
0783 
0784     // focus the new container if created, else keep the currently focused view
0785     focused->setFocus();
0786 }
0787 
0788 void ViewManager::expandActiveContainer()
0789 {
0790     _viewContainer->activeViewSplitter()->adjustActiveTerminalDisplaySize(10);
0791 }
0792 
0793 void ViewManager::shrinkActiveContainer()
0794 {
0795     _viewContainer->activeViewSplitter()->adjustActiveTerminalDisplaySize(-10);
0796 }
0797 
0798 void ViewManager::equalSizeAllContainers()
0799 {
0800     std::function<void(ViewSplitter *)> processChildren = [&processChildren](ViewSplitter *viewSplitter) -> void {
0801         // divide the size of the parent widget by the amount of children splits
0802         auto hintSize = viewSplitter->size();
0803         auto sizes = viewSplitter->sizes();
0804         auto sharedSize = hintSize / sizes.size();
0805         if (viewSplitter->orientation() == Qt::Horizontal) {
0806             for (auto &&size : sizes) {
0807                 size = sharedSize.width();
0808             }
0809         } else {
0810             for (auto &&size : sizes) {
0811                 size = sharedSize.height();
0812             }
0813         }
0814         // set new sizes
0815         viewSplitter->setSizes(sizes);
0816 
0817         // set equal sizes for each splitter children
0818         for (auto &&child : viewSplitter->children()) {
0819             auto childViewSplitter = qobject_cast<ViewSplitter *>(child);
0820             if (childViewSplitter) {
0821                 processChildren(childViewSplitter);
0822             }
0823         }
0824     };
0825     processChildren(_viewContainer->activeViewSplitter()->getToplevelSplitter());
0826 }
0827 
0828 SessionController *ViewManager::createController(Session *session, TerminalDisplay *view)
0829 {
0830     // create a new controller for the session, and ensure that this view manager
0831     // is notified when the view gains the focus
0832     auto controller = new SessionController(session, view, this);
0833     connect(controller, &Konsole::SessionController::viewFocused, this, &Konsole::ViewManager::controllerChanged);
0834     connect(session, &Konsole::Session::destroyed, controller, &Konsole::SessionController::deleteLater);
0835     connect(session, &Konsole::Session::primaryScreenInUse, controller, &Konsole::SessionController::setupPrimaryScreenSpecificActions);
0836     connect(session, &Konsole::Session::selectionChanged, controller, &Konsole::SessionController::selectionChanged);
0837     connect(view, &Konsole::TerminalDisplay::destroyed, controller, &Konsole::SessionController::deleteLater);
0838     connect(controller, &Konsole::SessionController::viewDragAndDropped, this, &Konsole::ViewManager::forgetController);
0839     connect(controller, &Konsole::SessionController::requestSplitViewLeftRight, this, &Konsole::ViewManager::splitLeftRight);
0840     connect(controller, &Konsole::SessionController::requestSplitViewTopBotton, this, &Konsole::ViewManager::splitTopBottom);
0841 
0842     // if this is the first controller created then set it as the active controller
0843     if (_pluggedController.isNull()) {
0844         controllerChanged(controller);
0845     }
0846 
0847     return controller;
0848 }
0849 
0850 void ViewManager::forgetController(SessionController *controller)
0851 {
0852     Q_ASSERT(controller->session() != nullptr && controller->view() != nullptr);
0853 
0854     forgetTerminal(controller->view());
0855     toggleActionsBasedOnState();
0856 }
0857 
0858 // should this be handed by ViewManager::unplugController signal
0859 void ViewManager::removeController(SessionController *controller)
0860 {
0861     Q_EMIT unplugController(controller);
0862 
0863     if (_pluggedController == controller) {
0864         _pluggedController.clear();
0865     }
0866     // disconnect now!! important as a focus change may happen in between and we will end up using a deleted controller
0867     disconnect(controller, &Konsole::SessionController::viewFocused, this, &Konsole::ViewManager::controllerChanged);
0868     controller->deleteLater();
0869 }
0870 
0871 void ViewManager::controllerChanged(SessionController *controller)
0872 {
0873     if (controller == _pluggedController) {
0874         return;
0875     }
0876 
0877     _viewContainer->setFocusProxy(controller->view());
0878     updateTerminalDisplayHistory(controller->view());
0879 
0880     _pluggedController = controller;
0881     Q_EMIT activeViewChanged(controller);
0882 }
0883 
0884 SessionController *ViewManager::activeViewController() const
0885 {
0886     return _pluggedController;
0887 }
0888 
0889 void ViewManager::attachView(TerminalDisplay *terminal, Session *session)
0890 {
0891     connect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished, Qt::UniqueConnection);
0892 
0893     // Disconnect from the other viewcontainer.
0894     unregisterTerminal(terminal);
0895 
0896     // reconnect on this container.
0897     registerTerminal(terminal);
0898 
0899     _sessionMap[terminal] = session;
0900     createController(session, terminal);
0901     toggleActionsBasedOnState();
0902     _terminalDisplayHistory.append(terminal);
0903 }
0904 
0905 TerminalDisplay *ViewManager::findTerminalDisplay(int viewId)
0906 {
0907     for (auto i = _sessionMap.keyBegin(); i != _sessionMap.keyEnd(); ++i) {
0908         TerminalDisplay *view = *i;
0909         if (view->id() == viewId)
0910             return view;
0911     }
0912 
0913     return nullptr;
0914 }
0915 
0916 void ViewManager::setCurrentView(TerminalDisplay *view)
0917 {
0918     auto parentSplitter = qobject_cast<ViewSplitter *>(view->parentWidget());
0919     _viewContainer->setCurrentWidget(parentSplitter->getToplevelSplitter());
0920     view->setFocus();
0921     setCurrentSession(_sessionMap[view]->sessionId());
0922 }
0923 
0924 TerminalDisplay *ViewManager::createView(Session *session)
0925 {
0926     // notify this view manager when the session finishes so that its view
0927     // can be deleted
0928     //
0929     // Use Qt::UniqueConnection to avoid duplicate connection
0930     connect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished, Qt::UniqueConnection);
0931     TerminalDisplay *display = createTerminalDisplay(session);
0932 
0933     const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);
0934     applyProfileToView(display, profile);
0935 
0936     // set initial size
0937     const QSize &preferredSize = session->preferredSize();
0938 
0939     display->setSize(preferredSize.width(), preferredSize.height());
0940     createController(session, display);
0941 
0942     _sessionMap[display] = session;
0943     session->addView(display);
0944     _terminalDisplayHistory.append(display);
0945 
0946     // tell the session whether it has a light or dark background
0947     session->setDarkBackground(colorSchemeForProfile(profile)->hasDarkBackground());
0948     display->setFocus(Qt::OtherFocusReason);
0949     //     updateDetachViewState();
0950     connect(display, &TerminalDisplay::activationRequest, this, &Konsole::ViewManager::activationRequest);
0951 
0952     return display;
0953 }
0954 
0955 TabbedViewContainer *ViewManager::createContainer()
0956 {
0957     auto *container = new TabbedViewContainer(this, nullptr);
0958     container->setNavigationVisibility(_navigationVisibility);
0959     connect(container, &TabbedViewContainer::detachTab, this, &ViewManager::detachTab);
0960 
0961     // connect signals and slots
0962     connect(container, &Konsole::TabbedViewContainer::viewAdded, this, [this, container]() {
0963         containerViewsChanged(container);
0964     });
0965     connect(container, &Konsole::TabbedViewContainer::viewRemoved, this, [this, container]() {
0966         containerViewsChanged(container);
0967     });
0968 
0969     connect(container, &TabbedViewContainer::newViewRequest, this, &ViewManager::newViewRequest);
0970     connect(container, &Konsole::TabbedViewContainer::newViewWithProfileRequest, this, &Konsole::ViewManager::newViewWithProfileRequest);
0971     connect(container, &Konsole::TabbedViewContainer::activeViewChanged, this, &Konsole::ViewManager::activateView);
0972 
0973     return container;
0974 }
0975 
0976 void ViewManager::setNavigationMethod(NavigationMethod method)
0977 {
0978     Q_ASSERT(_actionCollection);
0979     if (_actionCollection == nullptr) {
0980         return;
0981     }
0982     KActionCollection *collection = _actionCollection;
0983 
0984     _navigationMethod = method;
0985 
0986     // FIXME: The following disables certain actions for the KPart that it
0987     // doesn't actually have a use for, to avoid polluting the action/shortcut
0988     // namespace of an application using the KPart (otherwise, a shortcut may
0989     // be in use twice, and the user gets to see an "ambiguous shortcut over-
0990     // load" error dialog). However, this approach sucks - it's the inverse of
0991     // what it should be. Rather than disabling actions not used by the KPart,
0992     // a method should be devised to only enable those that are used, perhaps
0993     // by using a separate action collection.
0994 
0995     const bool enable = (method != NoNavigation);
0996 
0997     auto enableAction = [&enable, &collection](const QString &actionName) {
0998         auto *action = collection->action(actionName);
0999         if (action != nullptr) {
1000             action->setEnabled(enable);
1001         }
1002     };
1003 
1004     enableAction(QStringLiteral("next-view"));
1005     enableAction(QStringLiteral("previous-view"));
1006     enableAction(QStringLiteral("last-tab"));
1007     enableAction(QStringLiteral("last-used-tab"));
1008     enableAction(QStringLiteral("last-used-tab-reverse"));
1009     enableAction(QStringLiteral("split-view-left-right"));
1010     enableAction(QStringLiteral("split-view-top-bottom"));
1011     enableAction(QStringLiteral("split-view-left-right-next-tab"));
1012     enableAction(QStringLiteral("split-view-top-bottom-next-tab"));
1013     enableAction(QStringLiteral("rename-session"));
1014     enableAction(QStringLiteral("move-view-left"));
1015     enableAction(QStringLiteral("move-view-right"));
1016 }
1017 
1018 ViewManager::NavigationMethod ViewManager::navigationMethod() const
1019 {
1020     return _navigationMethod;
1021 }
1022 
1023 void ViewManager::containerViewsChanged(TabbedViewContainer *container)
1024 {
1025     Q_UNUSED(container)
1026     // TODO: Verify that this is right.
1027     Q_EMIT viewPropertiesChanged(viewProperties());
1028 }
1029 
1030 void ViewManager::viewDestroyed(QWidget *view)
1031 {
1032     // Note: the received QWidget has already been destroyed, so
1033     // using dynamic_cast<> or qobject_cast<> does not work here
1034     // We only need the pointer address to look it up below
1035     auto *display = reinterpret_cast<TerminalDisplay *>(view);
1036 
1037     // 1. detach view from session
1038     // 2. if the session has no views left, close it
1039     Session *session = _sessionMap[display];
1040     _sessionMap.remove(display);
1041     if (session != nullptr) {
1042         if (session->views().count() == 0) {
1043             session->close();
1044         }
1045     }
1046 
1047     // we only update the focus if the splitter is still alive
1048     toggleActionsBasedOnState();
1049 
1050     // The below causes the menus  to be messed up
1051     // Only happens when using the tab bar close button
1052     //    if (_pluggedController)
1053     //        Q_EMIT unplugController(_pluggedController);
1054 }
1055 
1056 TerminalDisplay *ViewManager::createTerminalDisplay(Session *session)
1057 {
1058     auto display = new TerminalDisplay(nullptr);
1059     display->setRandomSeed(session->sessionId() | (qApp->applicationPid() << 10));
1060     registerTerminal(display);
1061 
1062     return display;
1063 }
1064 
1065 std::shared_ptr<const ColorScheme> ViewManager::colorSchemeForProfile(const Profile::Ptr &profile)
1066 {
1067     std::shared_ptr<const ColorScheme> colorScheme = ColorSchemeManager::instance()->findColorScheme(profile->colorScheme());
1068     if (colorScheme == nullptr) {
1069         colorScheme = ColorSchemeManager::instance()->defaultColorScheme();
1070     }
1071     Q_ASSERT(colorScheme);
1072 
1073     return colorScheme;
1074 }
1075 
1076 bool ViewManager::profileHasBlurEnabled(const Profile::Ptr &profile)
1077 {
1078     return colorSchemeForProfile(profile)->blur();
1079 }
1080 
1081 void ViewManager::applyProfileToView(TerminalDisplay *view, const Profile::Ptr &profile)
1082 {
1083     Q_ASSERT(profile);
1084     view->applyProfile(profile);
1085     Q_EMIT updateWindowIcon();
1086     Q_EMIT blurSettingChanged(view->colorScheme()->blur());
1087 }
1088 
1089 void ViewManager::updateViewsForSession(Session *session)
1090 {
1091     const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);
1092 
1093     const QList<TerminalDisplay *> sessionMapKeys = _sessionMap.keys(session);
1094     for (TerminalDisplay *view : sessionMapKeys) {
1095         applyProfileToView(view, profile);
1096     }
1097 }
1098 
1099 void ViewManager::profileChanged(const Profile::Ptr &profile)
1100 {
1101     // update all views associated with this profile
1102     QHashIterator<TerminalDisplay *, Session *> iter(_sessionMap);
1103     while (iter.hasNext()) {
1104         iter.next();
1105 
1106         // if session uses this profile, update the display
1107         if (iter.key() != nullptr && iter.value() != nullptr && SessionManager::instance()->sessionProfile(iter.value()) == profile) {
1108             applyProfileToView(iter.key(), profile);
1109         }
1110     }
1111 }
1112 
1113 QList<ViewProperties *> ViewManager::viewProperties() const
1114 {
1115     QList<ViewProperties *> list;
1116 
1117     TabbedViewContainer *container = _viewContainer;
1118     if (container == nullptr) {
1119         return {};
1120     }
1121 
1122     auto terminalContainers = _viewContainer->findChildren<TerminalDisplay *>();
1123     list.reserve(terminalContainers.size());
1124 
1125     for (auto terminalDisplay : _viewContainer->findChildren<TerminalDisplay *>()) {
1126         list.append(terminalDisplay->sessionController());
1127     }
1128 
1129     return list;
1130 }
1131 
1132 namespace
1133 {
1134 QJsonObject saveSessionTerminal(TerminalDisplay *terminalDisplay)
1135 {
1136     QJsonObject thisTerminal;
1137     auto terminalSession = terminalDisplay->sessionController()->session();
1138     const int sessionRestoreId = SessionManager::instance()->getRestoreId(terminalSession);
1139     thisTerminal.insert(QStringLiteral("SessionRestoreId"), sessionRestoreId);
1140     return thisTerminal;
1141 }
1142 
1143 QJsonObject saveSessionsRecurse(QSplitter *splitter)
1144 {
1145     QJsonObject thisSplitter;
1146     thisSplitter.insert(QStringLiteral("Orientation"), splitter->orientation() == Qt::Horizontal ? QStringLiteral("Horizontal") : QStringLiteral("Vertical"));
1147 
1148     QJsonArray internalWidgets;
1149     for (int i = 0; i < splitter->count(); i++) {
1150         auto *widget = splitter->widget(i);
1151         auto *maybeSplitter = qobject_cast<QSplitter *>(widget);
1152         auto *maybeTerminalDisplay = qobject_cast<TerminalDisplay *>(widget);
1153 
1154         if (maybeSplitter != nullptr) {
1155             internalWidgets.append(saveSessionsRecurse(maybeSplitter));
1156         } else if (maybeTerminalDisplay != nullptr) {
1157             internalWidgets.append(saveSessionTerminal(maybeTerminalDisplay));
1158         }
1159     }
1160     thisSplitter.insert(QStringLiteral("Widgets"), internalWidgets);
1161     return thisSplitter;
1162 }
1163 
1164 } // namespace
1165 
1166 void ViewManager::saveLayoutFile()
1167 {
1168     QString fileName(QFileDialog::getSaveFileName(this->widget(),
1169                                                   i18nc("@title:window", "Save Tab Layout"),
1170                                                   QStringLiteral("~/"),
1171                                                   i18nc("@item:inlistbox", "Konsole View Layout (*.json)")));
1172 
1173     // User pressed cancel in dialog
1174     if (fileName.isEmpty()) {
1175         return;
1176     }
1177 
1178     if (!fileName.endsWith(QStringLiteral(".json"))) {
1179         fileName.append(QStringLiteral(".json"));
1180     }
1181 
1182     QFile file(fileName);
1183     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
1184         KMessageBox::error(this->widget(), i18nc("@label:textbox", "A problem occurred when saving the Layout.\n%1", file.fileName()));
1185     }
1186 
1187     QJsonObject jsonSplit = saveSessionsRecurse(_viewContainer->activeViewSplitter());
1188 
1189     if (!jsonSplit.isEmpty()) {
1190         file.write(QJsonDocument(jsonSplit).toJson());
1191         qDebug() << "Maybe was saved";
1192     }
1193 }
1194 
1195 void ViewManager::saveSessions(KConfigGroup &group)
1196 {
1197     QJsonArray rootArray;
1198     for (int i = 0; i < _viewContainer->count(); i++) {
1199         auto *splitter = qobject_cast<QSplitter *>(_viewContainer->widget(i));
1200         rootArray.append(saveSessionsRecurse(splitter));
1201     }
1202 
1203     group.writeEntry("Tabs", QJsonDocument(rootArray).toJson(QJsonDocument::Compact));
1204     group.writeEntry("Active", _viewContainer->currentIndex());
1205 }
1206 
1207 namespace
1208 {
1209 ViewSplitter *restoreSessionsSplitterRecurse(const QJsonObject &jsonSplitter, ViewManager *manager, bool useSessionId)
1210 {
1211     const QJsonArray splitterWidgets = jsonSplitter[QStringLiteral("Widgets")].toArray();
1212     auto orientation = (jsonSplitter[QStringLiteral("Orientation")].toString() == QStringLiteral("Horizontal")) ? Qt::Horizontal : Qt::Vertical;
1213 
1214     auto *currentSplitter = new ViewSplitter();
1215     currentSplitter->setOrientation(orientation);
1216 
1217     for (const auto widgetJsonValue : splitterWidgets) {
1218         const auto widgetJsonObject = widgetJsonValue.toObject();
1219         const auto sessionIterator = widgetJsonObject.constFind(QStringLiteral("SessionRestoreId"));
1220 
1221         if (sessionIterator != widgetJsonObject.constEnd()) {
1222             Session *session = useSessionId ? SessionManager::instance()->idToSession(sessionIterator->toInt()) : SessionManager::instance()->createSession();
1223 
1224             auto newView = manager->createView(session);
1225             currentSplitter->addWidget(newView);
1226         } else {
1227             auto nextSplitter = restoreSessionsSplitterRecurse(widgetJsonObject, manager, useSessionId);
1228             currentSplitter->addWidget(nextSplitter);
1229         }
1230     }
1231     return currentSplitter;
1232 }
1233 
1234 } // namespace
1235 void ViewManager::loadLayout(QString file)
1236 {
1237     // User pressed cancel in dialog
1238     if (file.isEmpty()) {
1239         return;
1240     }
1241 
1242     QFile jsonFile(file);
1243 
1244     if (!jsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
1245         KMessageBox::error(this->widget(), i18nc("@label:textbox", "A problem occurred when loading the Layout.\n%1", jsonFile.fileName()));
1246     }
1247     auto json = QJsonDocument::fromJson(jsonFile.readAll());
1248     if (!json.isEmpty()) {
1249         auto splitter = restoreSessionsSplitterRecurse(json.object(), this, false);
1250         _viewContainer->addSplitter(splitter, _viewContainer->count());
1251     }
1252 }
1253 void ViewManager::loadLayoutFile()
1254 {
1255     loadLayout(QFileDialog::getOpenFileName(this->widget(),
1256                                             i18nc("@title:window", "Load Tab Layout"),
1257                                             QStringLiteral("~/"),
1258                                             i18nc("@item:inlistbox", "Konsole View Layout (*.json)")));
1259 }
1260 
1261 void ViewManager::restoreSessions(const KConfigGroup &group)
1262 {
1263     const auto tabList = group.readEntry("Tabs", QByteArray("[]"));
1264     const auto jsonTabs = QJsonDocument::fromJson(tabList).array();
1265     for (const auto &jsonSplitter : jsonTabs) {
1266         auto topLevelSplitter = restoreSessionsSplitterRecurse(jsonSplitter.toObject(), this, true);
1267         _viewContainer->addSplitter(topLevelSplitter, _viewContainer->count());
1268     }
1269 
1270     if (!jsonTabs.isEmpty())
1271         return;
1272 
1273     // Session file is unusable, try older format
1274     QList<int> ids = group.readEntry("Sessions", QList<int>());
1275     int activeTab = group.readEntry("Active", 0);
1276     TerminalDisplay *display = nullptr;
1277 
1278     int tab = 1;
1279     for (auto it = ids.cbegin(); it != ids.cend(); ++it) {
1280         const int &id = *it;
1281         Session *session = SessionManager::instance()->idToSession(id);
1282 
1283         if (session == nullptr) {
1284             qWarning() << "Unable to load session with id" << id;
1285             // Force a creation of a default session below
1286             ids.clear();
1287             break;
1288         }
1289 
1290         activeContainer()->addView(createView(session));
1291         if (!session->isRunning()) {
1292             session->run();
1293         }
1294         if (tab++ == activeTab) {
1295             display = qobject_cast<TerminalDisplay *>(activeView());
1296         }
1297     }
1298 
1299     if (display != nullptr) {
1300         activeContainer()->setCurrentWidget(display);
1301         display->setFocus(Qt::OtherFocusReason);
1302     }
1303 
1304     if (ids.isEmpty()) { // Session file is unusable, start default Profile
1305         Profile::Ptr profile = ProfileManager::instance()->defaultProfile();
1306         Session *session = SessionManager::instance()->createSession(profile);
1307         activeContainer()->addView(createView(session));
1308         if (!session->isRunning()) {
1309             session->run();
1310         }
1311     }
1312 }
1313 
1314 TabbedViewContainer *ViewManager::activeContainer()
1315 {
1316     return _viewContainer;
1317 }
1318 
1319 int ViewManager::sessionCount()
1320 {
1321     return _sessionMap.size();
1322 }
1323 
1324 QStringList ViewManager::sessionList()
1325 {
1326     QStringList ids;
1327 
1328     for (int i = 0; i < _viewContainer->count(); i++) {
1329         auto terminaldisplayList = _viewContainer->widget(i)->findChildren<TerminalDisplay *>();
1330         for (auto *terminaldisplay : terminaldisplayList) {
1331             ids.append(QString::number(terminaldisplay->sessionController()->session()->sessionId()));
1332         }
1333     }
1334 
1335     return ids;
1336 }
1337 
1338 int ViewManager::currentSession()
1339 {
1340     if (_pluggedController != nullptr) {
1341         Q_ASSERT(_pluggedController->session() != nullptr);
1342         return _pluggedController->session()->sessionId();
1343     }
1344     return -1;
1345 }
1346 
1347 void ViewManager::setCurrentSession(int sessionId)
1348 {
1349     auto *session = SessionManager::instance()->idToSession(sessionId);
1350     if (session == nullptr || session->views().count() == 0) {
1351         return;
1352     }
1353 
1354     auto *display = session->views().at(0);
1355     if (display != nullptr) {
1356         display->setFocus(Qt::OtherFocusReason);
1357 
1358         auto *splitter = qobject_cast<ViewSplitter *>(display->parent());
1359         if (splitter != nullptr) {
1360             _viewContainer->setCurrentWidget(splitter->getToplevelSplitter());
1361         }
1362     }
1363 }
1364 
1365 int ViewManager::newSession()
1366 {
1367     return newSession(QString(), QString());
1368 }
1369 
1370 int ViewManager::newSession(const QString &profile)
1371 {
1372     return newSession(profile, QString());
1373 }
1374 
1375 int ViewManager::newSession(const QString &profile, const QString &directory)
1376 {
1377     Profile::Ptr profileptr = ProfileManager::instance()->defaultProfile();
1378     if (!profile.isEmpty()) {
1379         const QList<Profile::Ptr> profilelist = ProfileManager::instance()->allProfiles();
1380 
1381         for (const auto &i : profilelist) {
1382             if (i->name() == profile) {
1383                 profileptr = i;
1384                 break;
1385             }
1386         }
1387     }
1388 
1389     Session *session = createSession(profileptr, directory);
1390 
1391     auto newView = createView(session);
1392     activeContainer()->addView(newView);
1393     session->run();
1394 
1395     return session->sessionId();
1396 }
1397 
1398 QString ViewManager::defaultProfile()
1399 {
1400     return ProfileManager::instance()->defaultProfile()->name();
1401 }
1402 
1403 void ViewManager::setDefaultProfile(const QString &profileName)
1404 {
1405     const QList<Profile::Ptr> profiles = ProfileManager::instance()->allProfiles();
1406     for (const Profile::Ptr &profile : profiles) {
1407         if (profile->name() == profileName) {
1408             ProfileManager::instance()->setDefaultProfile(profile);
1409         }
1410     }
1411 }
1412 
1413 QStringList ViewManager::profileList()
1414 {
1415     return ProfileManager::instance()->availableProfileNames();
1416 }
1417 
1418 void ViewManager::nextSession()
1419 {
1420     nextView();
1421 }
1422 
1423 void ViewManager::prevSession()
1424 {
1425     previousView();
1426 }
1427 
1428 void ViewManager::moveSessionLeft()
1429 {
1430     moveActiveViewLeft();
1431 }
1432 
1433 void ViewManager::moveSessionRight()
1434 {
1435     moveActiveViewRight();
1436 }
1437 
1438 void ViewManager::setTabWidthToText(bool setTabWidthToText)
1439 {
1440     _viewContainer->tabBar()->setExpanding(!setTabWidthToText);
1441     _viewContainer->tabBar()->update();
1442 }
1443 
1444 QStringList ViewManager::viewHierarchy()
1445 {
1446     QStringList list;
1447 
1448     for (int i = 0; i < _viewContainer->count(); ++i) {
1449         list.append(_viewContainer->viewSplitterAt(i)->getChildWidgetsLayout());
1450     }
1451 
1452     return list;
1453 }
1454 
1455 QList<double> ViewManager::getSplitProportions(int splitterId)
1456 {
1457     auto splitter = _viewContainer->findSplitter(splitterId);
1458     if (splitter == nullptr)
1459         return QList<double>();
1460 
1461     int totalSize = 0;
1462     QList<double> percentages;
1463 
1464     for (auto size : splitter->sizes()) {
1465         totalSize += size;
1466     }
1467 
1468     if (totalSize == 0)
1469         return QList<double>();
1470 
1471     for (auto size : splitter->sizes()) {
1472         percentages.append((size / static_cast<double>(totalSize)) * 100);
1473     }
1474 
1475     return percentages;
1476 }
1477 
1478 bool ViewManager::createSplit(int viewId, bool horizontalSplit)
1479 {
1480     if (auto view = findTerminalDisplay(viewId)) {
1481         setCurrentView(view);
1482         splitView(horizontalSplit ? Qt::Horizontal : Qt::Vertical, false);
1483         return true;
1484     }
1485 
1486     return false;
1487 }
1488 
1489 bool ViewManager::createSplitWithExisting(int targetSplitterId, QStringList widgetInfos, int idx, bool horizontalSplit)
1490 {
1491     auto targetSplitter = _viewContainer->findSplitter(targetSplitterId);
1492     if (targetSplitter == nullptr || idx < 0)
1493         return false;
1494 
1495     QVector<QWidget *> linearLayout;
1496     QList<int> forbiddenSplitters, forbiddenViews;
1497 
1498     // specify that top level splitters should not be used as children for created splittter
1499     for (int i = 0; i < _viewContainer->count(); ++i) {
1500         forbiddenSplitters.append(_viewContainer->viewSplitterAt(i)->id());
1501     }
1502 
1503     // specify that parent splitters of the splitter with targetSplitterId id should not be used
1504     // as children for created splitter
1505     for (auto splitter = targetSplitter; splitter != targetSplitter->getToplevelSplitter(); splitter = qobject_cast<ViewSplitter *>(splitter->parentWidget())) {
1506         forbiddenSplitters.append(splitter->id());
1507     }
1508 
1509     // to make positioning clearer by avoiding situtations where
1510     // e.g. splitter to be created is at index x of targetSplitter
1511     // and some direct children of targetSplitter are used as
1512     // children of created splitter, causing the final position
1513     // of created splitter to may not be at x
1514     for (int i = 0; i < targetSplitter->count(); ++i) {
1515         auto w = targetSplitter->widget(i);
1516 
1517         if (auto s = qobject_cast<ViewSplitter *>(w))
1518             forbiddenSplitters.append(s->id());
1519         else
1520             forbiddenViews.append(qobject_cast<TerminalDisplay *>(w)->id());
1521     }
1522 
1523     for (auto &info : widgetInfos) {
1524         auto typeAndId = info.split(QLatin1Char('-'));
1525         if (typeAndId.size() != 2)
1526             return false;
1527 
1528         int id = typeAndId[1].toInt();
1529         QChar type = typeAndId[0][0];
1530 
1531         if (type == QLatin1Char('s') && !forbiddenSplitters.removeOne(id)) {
1532             if (auto s = _viewContainer->findSplitter(id)) {
1533                 linearLayout.append(s);
1534                 continue;
1535             }
1536         } else if (type == QLatin1Char('v') && !forbiddenViews.removeOne(id)) {
1537             if (auto v = findTerminalDisplay(id)) {
1538                 linearLayout.append(v);
1539                 continue;
1540             }
1541         }
1542 
1543         return false;
1544     }
1545 
1546     if (linearLayout.count() == 1) {
1547         if (auto onlyChildSplitter = qobject_cast<ViewSplitter *>(linearLayout[0])) {
1548             targetSplitter->addSplitter(onlyChildSplitter, idx);
1549         } else {
1550             auto onlyChildView = qobject_cast<TerminalDisplay *>(linearLayout[0]);
1551             targetSplitter->addTerminalDisplay(onlyChildView, idx);
1552         }
1553     } else {
1554         ViewSplitter *createdSplitter = new ViewSplitter();
1555         createdSplitter->setOrientation(horizontalSplit ? Qt::Horizontal : Qt::Vertical);
1556 
1557         for (auto widget : linearLayout) {
1558             if (auto s = qobject_cast<ViewSplitter *>(widget))
1559                 createdSplitter->addSplitter(s);
1560             else
1561                 createdSplitter->addTerminalDisplay(qobject_cast<TerminalDisplay *>(widget));
1562         }
1563 
1564         targetSplitter->addSplitter(createdSplitter, idx);
1565     }
1566 
1567     setCurrentView(targetSplitter->activeTerminalDisplay());
1568     return true;
1569 }
1570 
1571 bool ViewManager::setCurrentView(int viewId)
1572 {
1573     if (auto view = findTerminalDisplay(viewId)) {
1574         setCurrentView(view);
1575         return true;
1576     }
1577 
1578     return false;
1579 }
1580 
1581 bool ViewManager::resizeSplits(int splitterId, QList<double> percentages)
1582 {
1583     auto splitter = _viewContainer->findSplitter(splitterId);
1584     int totalP = 0;
1585 
1586     for (auto p : percentages) {
1587         if (p < 1)
1588             return false;
1589 
1590         totalP += p;
1591     }
1592 
1593     // make sure that the sum of percentages is very close
1594     // to but not exceeding 100. above 99% but less than 100 %
1595     // seems like good constraint
1596     if (splitter == nullptr || percentages.count() != splitter->sizes().count() || totalP > 100 || totalP < 99)
1597         return false;
1598 
1599     int sum = 0;
1600     QList<int> newSizes;
1601 
1602     for (int size : splitter->sizes()) {
1603         sum += size;
1604     }
1605 
1606     for (int i = 0; i < percentages.count(); ++i) {
1607         newSizes.append(static_cast<int>(sum * percentages.at(i)));
1608     }
1609 
1610     splitter->setSizes(newSizes);
1611     setCurrentView(splitter->activeTerminalDisplay());
1612     return true;
1613 }
1614 
1615 bool ViewManager::moveSplitter(int splitterId, int targetSplitterId, int idx)
1616 {
1617     auto splitter = _viewContainer->findSplitter(splitterId);
1618     auto targetSplitter = _viewContainer->findSplitter(targetSplitterId);
1619 
1620     if (splitter == nullptr || targetSplitter == nullptr || idx < 0)
1621         return false;
1622 
1623     for (auto s = targetSplitter; s != s->getToplevelSplitter(); s = qobject_cast<ViewSplitter *>(s->parentWidget())) {
1624         if (s == splitter)
1625             return false;
1626     }
1627 
1628     for (int i = 0; i < _viewContainer->count(); ++i) {
1629         if (splitter == _viewContainer->viewSplitterAt(i))
1630             return false;
1631     }
1632 
1633     targetSplitter->addSplitter(splitter, idx);
1634     setCurrentView(splitter->activeTerminalDisplay());
1635     return true;
1636 }
1637 
1638 bool ViewManager::moveView(int viewId, int targetSplitterId, int idx)
1639 {
1640     auto view = findTerminalDisplay(viewId);
1641     auto targetSplitter = _viewContainer->findSplitter(targetSplitterId);
1642 
1643     if (view == nullptr || targetSplitter == nullptr || idx < 0)
1644         return false;
1645 
1646     targetSplitter->addTerminalDisplay(view, idx);
1647     setCurrentView(view);
1648     return true;
1649 }
1650 
1651 void ViewManager::setNavigationVisibility(NavigationVisibility navigationVisibility)
1652 {
1653     if (_navigationVisibility != navigationVisibility) {
1654         _navigationVisibility = navigationVisibility;
1655         _viewContainer->setNavigationVisibility(navigationVisibility);
1656     }
1657 }
1658 
1659 void ViewManager::updateTerminalDisplayHistory(TerminalDisplay *terminalDisplay, bool remove)
1660 {
1661     if (terminalDisplay == nullptr) {
1662         if (_terminalDisplayHistoryIndex >= 0) {
1663             // This is the case when we finished walking through the history
1664             // (i.e. when Ctrl-Tab has been released)
1665             terminalDisplay = _terminalDisplayHistory[_terminalDisplayHistoryIndex];
1666             _terminalDisplayHistoryIndex = -1;
1667         } else {
1668             return;
1669         }
1670     }
1671 
1672     if (_terminalDisplayHistoryIndex >= 0 && !remove) {
1673         // Do not reorder the tab history while we are walking through it
1674         return;
1675     }
1676 
1677     for (int i = 0; i < _terminalDisplayHistory.count(); i++) {
1678         if (_terminalDisplayHistory[i] == terminalDisplay) {
1679             _terminalDisplayHistory.removeAt(i);
1680             if (!remove) {
1681                 _terminalDisplayHistory.prepend(terminalDisplay);
1682             }
1683             break;
1684         }
1685     }
1686 }
1687 
1688 void ViewManager::registerTerminal(TerminalDisplay *terminal)
1689 {
1690     connect(terminal, &TerminalDisplay::requestToggleExpansion, _viewContainer, &TabbedViewContainer::toggleMaximizeCurrentTerminal, Qt::UniqueConnection);
1691     connect(terminal, &TerminalDisplay::requestMoveToNewTab, _viewContainer, &TabbedViewContainer::moveToNewTab, Qt::UniqueConnection);
1692 }
1693 
1694 void ViewManager::unregisterTerminal(TerminalDisplay *terminal)
1695 {
1696     disconnect(terminal, &TerminalDisplay::requestToggleExpansion, nullptr, nullptr);
1697     disconnect(terminal, &TerminalDisplay::requestMoveToNewTab, nullptr, nullptr);
1698 }
1699 
1700 #include "moc_ViewManager.cpp"