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"