diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index dcdda827..461b9e54 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1,926 +1,921 @@ /* Copyright 2006-2008 by Robert Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ // Own #include "MainWindow.h" // Qt #include // KDE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Konsole #include "BookmarkHandler.h" #include "SessionController.h" #include "ProfileList.h" #include "Session.h" #include "ViewContainer.h" #include "ViewManager.h" #include "SessionManager.h" #include "ProfileManager.h" #include "KonsoleSettings.h" #include "WindowSystemInfo.h" #include "TerminalDisplay.h" #include "settings/ConfigurationDialog.h" #include "settings/TemporaryFilesSettings.h" #include "settings/GeneralSettings.h" #include "settings/ProfileSettings.h" #include "settings/TabBarSettings.h" using namespace Konsole; MainWindow::MainWindow() : KXmlGuiWindow(), _viewManager(nullptr), _bookmarkHandler(nullptr), _toggleMenuBarAction(nullptr), _newTabMenuAction(nullptr), _pluggedController(nullptr), _menuBarInitialVisibility(true), _menuBarInitialVisibilityApplied(false) { if (!KonsoleSettings::saveGeometryOnExit()) { // If we are not using the global Konsole save geometry on exit, // remove all Height and Width from [MainWindow] from konsolerc // Each screen resolution will have entries (Width 1280=619) KSharedConfigPtr konsoleConfig = KSharedConfig::openConfig(QStringLiteral("konsolerc")); KConfigGroup group = konsoleConfig->group("MainWindow"); QMap configEntries = group.entryMap(); QMapIterator i(configEntries); while (i.hasNext()) { i.next(); if (i.key().startsWith(QLatin1String("Width")) || i.key().startsWith(QLatin1String("Height"))) { group.deleteEntry(i.key()); } } } updateUseTransparency(); // create actions for menus setupActions(); // create view manager _viewManager = new ViewManager(this, actionCollection()); connect(_viewManager, &Konsole::ViewManager::empty, this, &Konsole::MainWindow::close); connect(_viewManager, &Konsole::ViewManager::activeViewChanged, this, &Konsole::MainWindow::activeViewChanged); connect(_viewManager, &Konsole::ViewManager::unplugController, this, &Konsole::MainWindow::disconnectController); connect(_viewManager, &Konsole::ViewManager::viewPropertiesChanged, bookmarkHandler(), &Konsole::BookmarkHandler::setViews); connect(_viewManager, &Konsole::ViewManager::blurSettingChanged, this, &Konsole::MainWindow::setBlur); connect(_viewManager, &Konsole::ViewManager::updateWindowIcon, this, &Konsole::MainWindow::updateWindowIcon); connect(_viewManager, &Konsole::ViewManager::newViewWithProfileRequest, this, &Konsole::MainWindow::newFromProfile); connect(_viewManager, &Konsole::ViewManager::newViewRequest, this, &Konsole::MainWindow::newTab); connect(_viewManager, &Konsole::ViewManager::terminalsDetached, this, &Konsole::MainWindow::terminalsDetached); setCentralWidget(_viewManager->widget()); // disable automatically generated accelerators in top-level // menu items - to avoid conflicting with Alt+[Letter] shortcuts // in terminal applications KAcceleratorManager::setNoAccel(menuBar()); // create menus createGUI(); // remember the original menu accelerators for later use rememberMenuAccelerators(); // replace standard shortcuts which cannot be used in a terminal // emulator (as they are reserved for use by terminal applications) correctStandardShortcuts(); setProfileList(new ProfileList(true, this)); // this must come at the end applyKonsoleSettings(); connect(KonsoleSettings::self(), &Konsole::KonsoleSettings::configChanged, this, &Konsole::MainWindow::applyKonsoleSettings); } void MainWindow::updateUseTransparency() { if (!WindowSystemInfo::HAVE_TRANSPARENCY) { return; } bool useTranslucency = KWindowSystem::compositingActive(); setAttribute(Qt::WA_TranslucentBackground, useTranslucency); setAttribute(Qt::WA_NoSystemBackground, false); WindowSystemInfo::HAVE_TRANSPARENCY = useTranslucency; } void MainWindow::rememberMenuAccelerators() { foreach (QAction *menuItem, menuBar()->actions()) { QString itemText = menuItem->text(); menuItem->setData(itemText); } } // remove accelerators for standard menu items (eg. &File, &View, &Edit) // etc. which are defined in kdelibs/kdeui/xmlgui/ui_standards.rc, again, // to avoid conflicting with Alt+[Letter] terminal shortcuts // // TODO - Modify XMLGUI so that it allows the text for standard actions // defined in ui_standards.rc to be re-defined in the local application // XMLGUI file (konsoleui.rc in this case) - the text for standard items // can then be redefined there to exclude the standard accelerators void MainWindow::removeMenuAccelerators() { foreach (QAction *menuItem, menuBar()->actions()) { menuItem->setText(menuItem->text().replace(QLatin1Char('&'), QString())); } } void MainWindow::restoreMenuAccelerators() { foreach (QAction *menuItem, menuBar()->actions()) { QString itemText = menuItem->data().toString(); menuItem->setText(itemText); } } void MainWindow::correctStandardShortcuts() { // replace F1 shortcut for help contents QAction *helpAction = actionCollection()->action(QStringLiteral("help_contents")); if (helpAction != nullptr) { actionCollection()->setDefaultShortcut(helpAction, QKeySequence()); } // replace Ctrl+B shortcut for bookmarks only if user hasn't already // changed the shortcut; however, if the user changed it to Ctrl+B // this will still get changed to Ctrl+Shift+B QAction *bookmarkAction = actionCollection()->action(QStringLiteral("add_bookmark")); if ((bookmarkAction != nullptr) && bookmarkAction->shortcut() == QKeySequence(Konsole::ACCEL + Qt::Key_B)) { actionCollection()->setDefaultShortcut(bookmarkAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_B); } } ViewManager *MainWindow::viewManager() const { return _viewManager; } void MainWindow::disconnectController(SessionController *controller) { disconnect(controller, &Konsole::SessionController::titleChanged, this, &Konsole::MainWindow::activeViewTitleChanged); disconnect(controller, &Konsole::SessionController::rawTitleChanged, this, &Konsole::MainWindow::updateWindowCaption); disconnect(controller, &Konsole::SessionController::iconChanged, this, &Konsole::MainWindow::updateWindowIcon); if (auto view = controller->view()) { view->removeEventFilter(this); } // KXmlGuiFactory::removeClient() will try to access actions associated // with the controller internally, which may not be valid after the controller // itself is no longer valid (after the associated session and or view have // been destroyed) if (controller->isValid()) { guiFactory()->removeClient(controller); } if (_pluggedController == controller) { _pluggedController = nullptr; } } void MainWindow::activeViewChanged(SessionController *controller) { if (!SessionManager::instance()->sessionProfile(controller->session())) { return; } // associate bookmark menu with current session bookmarkHandler()->setActiveView(controller); disconnect(bookmarkHandler(), &Konsole::BookmarkHandler::openUrl, nullptr, nullptr); connect(bookmarkHandler(), &Konsole::BookmarkHandler::openUrl, controller, &Konsole::SessionController::openUrl); if (!_pluggedController.isNull()) { disconnectController(_pluggedController); } Q_ASSERT(controller); _pluggedController = controller; _pluggedController->view()->installEventFilter(this); setBlur(ViewManager::profileHasBlurEnabled(SessionManager::instance()->sessionProfile(_pluggedController->session()))); // listen for title changes from the current session connect(controller, &Konsole::SessionController::titleChanged, this, &Konsole::MainWindow::activeViewTitleChanged); connect(controller, &Konsole::SessionController::rawTitleChanged, this, &Konsole::MainWindow::updateWindowCaption); connect(controller, &Konsole::SessionController::iconChanged, this, &Konsole::MainWindow::updateWindowIcon); controller->setShowMenuAction(_toggleMenuBarAction); guiFactory()->addClient(controller); // update session title to match newly activated session activeViewTitleChanged(controller); // Update window icon to newly activated session's icon updateWindowIcon(); } void MainWindow::activeViewTitleChanged(ViewProperties *properties) { Q_UNUSED(properties); updateWindowCaption(); } void MainWindow::updateWindowCaption() { if (_pluggedController.isNull()) { return; } const QString &title = _pluggedController->title(); const QString &userTitle = _pluggedController->userTitle(); // use tab title as caption by default QString caption = title; // use window title as caption when this setting is enabled // if the userTitle is empty, use a blank space (using an empty string // removes the dash — before the application name; leaving the dash // looks better) if (KonsoleSettings::showWindowTitleOnTitleBar()) { !userTitle.isEmpty() ? caption = userTitle : caption = QStringLiteral(" "); } setCaption(caption); } void MainWindow::updateWindowIcon() { if ((!_pluggedController.isNull()) && !_pluggedController->icon().isNull()) { setWindowIcon(_pluggedController->icon()); } } void MainWindow::setupActions() { KActionCollection *collection = actionCollection(); // File Menu _newTabMenuAction = new KActionMenu(QIcon::fromTheme(QStringLiteral("tab-new")), i18nc("@action:inmenu", "&New Tab"), collection); collection->setDefaultShortcut(_newTabMenuAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_T); collection->setShortcutsConfigurable(_newTabMenuAction, true); _newTabMenuAction->setAutoRepeat(false); connect(_newTabMenuAction, &KActionMenu::triggered, this, &MainWindow::newTab); collection->addAction(QStringLiteral("new-tab"), _newTabMenuAction); collection->setShortcutsConfigurable(_newTabMenuAction, true); QAction* menuAction = collection->addAction(QStringLiteral("clone-tab")); menuAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-duplicate"))); menuAction->setText(i18nc("@action:inmenu", "&Clone Tab")); collection->setDefaultShortcut(menuAction, QKeySequence()); menuAction->setAutoRepeat(false); connect(menuAction, &QAction::triggered, this, &Konsole::MainWindow::cloneTab); menuAction = collection->addAction(QStringLiteral("new-window")); menuAction->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); menuAction->setText(i18nc("@action:inmenu", "New &Window")); collection->setDefaultShortcut(menuAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_N); menuAction->setAutoRepeat(false); connect(menuAction, &QAction::triggered, this, &Konsole::MainWindow::newWindow); menuAction = collection->addAction(QStringLiteral("close-window")); menuAction->setIcon(QIcon::fromTheme(QStringLiteral("window-close"))); menuAction->setText(i18nc("@action:inmenu", "Close Window")); collection->setDefaultShortcut(menuAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Q); connect(menuAction, &QAction::triggered, this, &Konsole::MainWindow::close); // Bookmark Menu KActionMenu *bookmarkMenu = new KActionMenu(i18nc("@title:menu", "&Bookmarks"), collection); _bookmarkHandler = new BookmarkHandler(collection, bookmarkMenu->menu(), true, this); collection->addAction(QStringLiteral("bookmark"), bookmarkMenu); connect(_bookmarkHandler, &Konsole::BookmarkHandler::openUrls, this, &Konsole::MainWindow::openUrls); // Settings Menu _toggleMenuBarAction = KStandardAction::showMenubar(menuBar(), SLOT(setVisible(bool)), collection); collection->setDefaultShortcut(_toggleMenuBarAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_M); // Full Screen menuAction = KStandardAction::fullScreen(this, SLOT(viewFullScreen(bool)), this, collection); collection->setDefaultShortcut(menuAction, Qt::Key_F11); KStandardAction::configureNotifications(this, SLOT(configureNotifications()), collection); KStandardAction::keyBindings(this, SLOT(showShortcutsDialog()), collection); KStandardAction::preferences(this, SLOT(showSettingsDialog()), collection); menuAction = collection->addAction(QStringLiteral("manage-profiles")); menuAction->setText(i18nc("@action:inmenu", "Manage Profiles...")); menuAction->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect(menuAction, &QAction::triggered, this, &Konsole::MainWindow::showManageProfilesDialog); // Set up an shortcut-only action for activating menu bar. menuAction = collection->addAction(QStringLiteral("activate-menu")); menuAction->setText(i18nc("@item", "Activate Menu")); collection->setDefaultShortcut(menuAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_F10); connect(menuAction, &QAction::triggered, this, &Konsole::MainWindow::activateMenuBar); } void MainWindow::viewFullScreen(bool fullScreen) { if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); } else { setWindowState(windowState() & ~Qt::WindowFullScreen); } } BookmarkHandler *MainWindow::bookmarkHandler() const { return _bookmarkHandler; } void MainWindow::setProfileList(ProfileList *list) { profileListChanged(list->actions()); connect(list, &Konsole::ProfileList::profileSelected, this, &MainWindow::newFromProfile); connect(list, &Konsole::ProfileList::actionsChanged, this, &Konsole::MainWindow::profileListChanged); } void MainWindow::profileListChanged(const QList &sessionActions) { // If only 1 profile is to be shown in the menu, only display // it if it is the non-default profile. if (sessionActions.size() > 2) { // Update the 'New Tab' KActionMenu if (_newTabMenuAction->menu() != nullptr) { _newTabMenuAction->menu()->clear(); } else { _newTabMenuAction->setMenu(new QMenu()); } foreach (QAction *sessionAction, sessionActions) { _newTabMenuAction->menu()->addAction(sessionAction); // NOTE: defaultProfile seems to not work here, sigh. Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); if (profile && profile->name() == sessionAction->text().remove(QLatin1Char('&'))) { QIcon icon(KIconLoader::global()->loadIcon(profile->icon(), KIconLoader::Small, 0, KIconLoader::DefaultState, QStringList(QStringLiteral("emblem-favorite")))); sessionAction->setIcon(icon); _newTabMenuAction->menu()->setDefaultAction(sessionAction); QFont actionFont = sessionAction->font(); actionFont.setBold(true); sessionAction->setFont(actionFont); } } } else { if (_newTabMenuAction->menu() != nullptr) { _newTabMenuAction->menu()->clear(); } else { _newTabMenuAction->setMenu(new QMenu()); } Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); // NOTE: Compare names w/o any '&' if (sessionActions.size() == 2 && sessionActions[1]->text().remove(QLatin1Char('&')) != profile->name()) { _newTabMenuAction->menu()->addAction(sessionActions[1]); } else { _newTabMenuAction->menu()->deleteLater(); } } } QString MainWindow::activeSessionDir() const { if (!_pluggedController.isNull()) { if (Session *session = _pluggedController->session()) { // For new tabs to get the correct working directory, // force the updating of the currentWorkingDirectory. session->getDynamicTitle(); } return _pluggedController->currentDir(); } else { return QString(); } } void MainWindow::openUrls(const QList &urls) { Profile::Ptr defaultProfile = ProfileManager::instance()->defaultProfile(); for (const auto &url : urls) { if (url.isLocalFile()) { createSession(defaultProfile, url.path()); } else if (url.scheme() == QLatin1String("ssh")) { createSSHSession(defaultProfile, url); } } } void MainWindow::newTab() { Profile::Ptr defaultProfile = ProfileManager::instance()->defaultProfile(); createSession(defaultProfile, activeSessionDir()); } void MainWindow::cloneTab() { Q_ASSERT(_pluggedController); Session *session = _pluggedController->session(); Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); if (profile) { createSession(profile, activeSessionDir()); } else { // something must be wrong: every session should be associated with profile Q_ASSERT(false); newTab(); } } Session *MainWindow::createSession(Profile::Ptr profile, const QString &directory) { if (!profile) { profile = ProfileManager::instance()->defaultProfile(); } - Session *session = SessionManager::instance()->createSession(profile); - - if (!directory.isEmpty() && profile->startInCurrentSessionDir()) { - session->setInitialWorkingDirectory(directory); - } - - session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(_viewManager->managerId())); + const QString newSessionDirectory = profile->startInCurrentSessionDir() ? directory : QString(); + Session *session = _viewManager->createSession(profile, newSessionDirectory); // create view before starting the session process so that the session // doesn't suffer a change in terminal size right after the session // starts. Some applications such as GNU Screen and Midnight Commander // don't like this happening auto newView = _viewManager->createView(session); _viewManager->activeContainer()->addView(newView); return session; } Session *MainWindow::createSSHSession(Profile::Ptr profile, const QUrl &url) { if (!profile) { profile = ProfileManager::instance()->defaultProfile(); } Session *session = SessionManager::instance()->createSession(profile); QString sshCommand = QStringLiteral("ssh "); if (url.port() > -1) { sshCommand += QStringLiteral("-p %1 ").arg(url.port()); } if (!url.userName().isEmpty()) { sshCommand += (url.userName() + QLatin1Char('@')); } if (!url.host().isEmpty()) { sshCommand += url.host(); } session->sendTextToTerminal(sshCommand, QLatin1Char('\r')); // create view before starting the session process so that the session // doesn't suffer a change in terminal size right after the session // starts. some applications such as GNU Screen and Midnight Commander // don't like this happening auto newView = _viewManager->createView(session); _viewManager->activeContainer()->addView(newView); return session; } void MainWindow::setFocus() { _viewManager->activeView()->setFocus(); } void MainWindow::newWindow() { Profile::Ptr defaultProfile = ProfileManager::instance()->defaultProfile(); emit newWindowRequest(defaultProfile, activeSessionDir()); } bool MainWindow::queryClose() { // Do not ask for confirmation during log out and power off // TODO: rework the dealing of this case to make it has its own confirmation // dialog. if (qApp->isSavingSession()) { return true; } // Check what processes are running, excluding the shell QStringList processesRunning; const auto uniqueSessions = QSet::fromList(_viewManager->sessions()); foreach (Session *session, uniqueSessions) { if ((session == nullptr) || !session->isForegroundProcessActive()) { continue; } const QString defaultProc = session->program().split(QLatin1Char('/')).last(); const QString currentProc = session->foregroundProcessName().split(QLatin1Char('/')).last(); if (currentProc.isEmpty()) { continue; } if (defaultProc != currentProc) { processesRunning.append(currentProc); } } // Get number of open tabs const int openTabs = _viewManager->viewProperties().count(); // If no processes running (except the shell) and no extra tabs, just close if (processesRunning.count() == 0 && openTabs < 2) { return true; } // NOTE: Some, if not all, of the below KWindowSystem calls are only // implemented under x11 (KDE4.8 kdelibs/kdeui/windowmanagement). // make sure the window is shown on current desktop and is not minimized KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop()); if (isMinimized()) { KWindowSystem::unminimizeWindow(winId()); } int result; if (!processesRunning.isEmpty()) { if (openTabs == 1) { result = KMessageBox::warningYesNoList(this, i18ncp("@info", "There is a process running in this window. " "Do you still want to quit?", "There are %1 processes running in this window. " "Do you still want to quit?", processesRunning.count()), processesRunning, i18nc("@title", "Confirm Close"), KGuiItem(i18nc("@action:button", "Close &Window"), QStringLiteral("window-close")), KStandardGuiItem::cancel(), // don't ask again name is wrong but I can't update. // this is not about tabs anymore. it's about empty tabs *or* splits. QStringLiteral("CloseAllTabs")); if (result == KMessageBox::No) // No is equal to cancel closing result = KMessageBox::Cancel; } else { result = KMessageBox::warningYesNoCancelList(this, i18ncp("@info", "There is a process running in this window. " "Do you still want to quit?", "There are %1 processes running in this window. " "Do you still want to quit?", processesRunning.count()), processesRunning, i18nc("@title", "Confirm Close"), KGuiItem(i18nc("@action:button", "Close &Window"), QStringLiteral("window-close")), KGuiItem(i18nc("@action:button", "Close Current &Tab"), QStringLiteral("tab-close")), KStandardGuiItem::cancel(), // don't ask again name is wrong but I can't update. // this is not about tabs anymore. it's about empty tabs *or* splits. QStringLiteral("CloseAllTabs")); } } else { result = KMessageBox::warningYesNoCancel(this, i18nc("@info", "There are %1 open terminals in this window. " "Do you still want to quit?", openTabs), i18nc("@title", "Confirm Close"), KGuiItem(i18nc("@action:button", "Close &Window"), QStringLiteral("window-close")), KGuiItem(i18nc("@action:button", "Close Current &Tab"), QStringLiteral("tab-close")), KStandardGuiItem::cancel(), // don't ask again name is wrong but I can't update. // this is not about tabs anymore. it's about empty tabs *or* splits. QStringLiteral("CloseAllEmptyTabs")); } switch (result) { case KMessageBox::Yes: return true; case KMessageBox::No: if ((!_pluggedController.isNull()) && (!_pluggedController->session().isNull())) { if (!(_pluggedController->session()->closeInNormalWay())) { if (_pluggedController->confirmForceClose()) { _pluggedController->session()->closeInForceWay(); } } } return false; case KMessageBox::Cancel: return false; } return true; } void MainWindow::saveProperties(KConfigGroup &group) { _viewManager->saveSessions(group); } void MainWindow::readProperties(const KConfigGroup &group) { _viewManager->restoreSessions(group); } void MainWindow::saveGlobalProperties(KConfig *config) { SessionManager::instance()->saveSessions(config); } void MainWindow::readGlobalProperties(KConfig *config) { SessionManager::instance()->restoreSessions(config); } void MainWindow::syncActiveShortcuts(KActionCollection *dest, const KActionCollection *source) { foreach (QAction *qAction, source->actions()) { if (QAction *destQAction = dest->action(qAction->objectName())) { destQAction->setShortcut(qAction->shortcut()); } } } void MainWindow::showShortcutsDialog() { KShortcutsDialog dialog(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsDisallowed, this); // add actions from this window and the current session controller foreach (KXMLGUIClient *client, guiFactory()->clients()) { dialog.addCollection(client->actionCollection()); } if (dialog.configure()) { // sync shortcuts for non-session actions (defined in "konsoleui.rc") in other main windows foreach (QWidget *mainWindowWidget, QApplication::topLevelWidgets()) { auto *mainWindow = qobject_cast(mainWindowWidget); if ((mainWindow != nullptr) && mainWindow != this) { syncActiveShortcuts(mainWindow->actionCollection(), actionCollection()); } } // sync shortcuts for session actions (defined in "sessionui.rc") in other session controllers. // Controllers which are currently plugged in (ie. their actions are part of the current menu) // must be updated immediately via syncActiveShortcuts(). Other controllers will be updated // when they are plugged into a main window. foreach (SessionController *controller, SessionController::allControllers()) { controller->reloadXML(); if ((controller->factory() != nullptr) && controller != _pluggedController) { syncActiveShortcuts(controller->actionCollection(), _pluggedController->actionCollection()); } } } } void MainWindow::newFromProfile(const Profile::Ptr &profile) { createSession(profile, activeSessionDir()); } void MainWindow::showManageProfilesDialog() { showSettingsDialog(true); } void MainWindow::showSettingsDialog(const bool showProfilePage) { static ConfigurationDialog *confDialog = nullptr; if (confDialog != nullptr) { confDialog->show(); return; } confDialog = new ConfigurationDialog(this, KonsoleSettings::self()); const QString generalPageName = i18nc("@title Preferences page name", "General"); auto *generalPage = new KPageWidgetItem(new GeneralSettings(confDialog), generalPageName); generalPage->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); confDialog->addPage(generalPage, true); const QString profilePageName = i18nc("@title Preferences page name", "Profiles"); auto profilePage = new KPageWidgetItem(new ProfileSettings(confDialog), profilePageName); profilePage->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-profiles"))); confDialog->addPage(profilePage, true); const QString tabBarPageName = i18nc("@title Preferences page name", "Tab Bar"); auto tabBarPage = new KPageWidgetItem(new TabBarSettings(confDialog), tabBarPageName); tabBarPage->setIcon(QIcon::fromTheme(QStringLiteral("system-run"))); confDialog->addPage(tabBarPage, true); const QString temporaryFilesPageName = i18nc("@title Preferences page name", "Temporary Files"); auto temporaryFilesPage = new KPageWidgetItem(new TemporaryFilesSettings(confDialog), temporaryFilesPageName); temporaryFilesPage->setIcon(QIcon::fromTheme(QStringLiteral("folder-temp"))); confDialog->addPage(temporaryFilesPage, true); if (showProfilePage) { confDialog->setCurrentPage(profilePage); } confDialog->show(); } void MainWindow::applyKonsoleSettings() { setMenuBarInitialVisibility(KonsoleSettings::showMenuBarByDefault()); setRemoveWindowTitleBarAndFrame(KonsoleSettings::removeWindowTitleBarAndFrame()); if (KonsoleSettings::allowMenuAccelerators()) { restoreMenuAccelerators(); } else { removeMenuAccelerators(); } _viewManager->activeContainer()->setNavigationBehavior(KonsoleSettings::newTabBehavior()); setAutoSaveSettings(QStringLiteral("MainWindow"), KonsoleSettings::saveGeometryOnExit()); updateWindowCaption(); } void MainWindow::activateMenuBar() { const QList menuActions = menuBar()->actions(); if (menuActions.isEmpty()) { return; } // Show menubar if it is hidden at the moment if (menuBar()->isHidden()) { menuBar()->setVisible(true); _toggleMenuBarAction->setChecked(true); } // First menu action should be 'File' QAction *menuAction = menuActions.first(); // TODO: Handle when menubar is top level (MacOS) menuBar()->setActiveAction(menuAction); } void MainWindow::configureNotifications() { KNotifyConfigWidget::configure(this); } void MainWindow::setBlur(bool blur) { if (_pluggedController.isNull()) { return; } if (!_pluggedController->isKonsolePart()) { KWindowEffects::enableBlurBehind(winId(), blur); } } void MainWindow::setMenuBarInitialVisibility(bool visible) { _menuBarInitialVisibility = visible; } void MainWindow::setRemoveWindowTitleBarAndFrame(bool frameless) { Qt::WindowFlags newFlags = frameless ? Qt::FramelessWindowHint : Qt::Window; // The window is not yet visible if (!isVisible()) { setWindowFlags(newFlags); // The window is visible and the setting changed } else if (windowFlags().testFlag(Qt::FramelessWindowHint) != frameless) { const auto oldGeometry = saveGeometry(); // This happens for every Konsole window. It depends on // the fact that every window is processed in single thread const auto oldActiveWindow = KWindowSystem::activeWindow(); setWindowFlags(newFlags); // The setWindowFlags() has hidden the window. Show it again // with previous geometry restoreGeometry(oldGeometry); setVisible(true); KWindowSystem::activateWindow(oldActiveWindow); } } void MainWindow::showEvent(QShowEvent *event) { // Make sure the 'initial' visibility is applied only once. if (!_menuBarInitialVisibilityApplied) { // the initial visibility of menubar should be applied at this last // moment. Otherwise, the initial visibility will be determined by // what KMainWindow has automatically stored in konsolerc, but not by // what users has explicitly configured . menuBar()->setVisible(_menuBarInitialVisibility); _toggleMenuBarAction->setChecked(_menuBarInitialVisibility); _menuBarInitialVisibilityApplied = true; if (!KonsoleSettings::saveGeometryOnExit()) { resize(sizeHint()); } } // Call parent method KXmlGuiWindow::showEvent(event); } void MainWindow::triggerAction(const QString &name) const { if (auto action = actionCollection()->action(name)) { if (action->isEnabled()) { action->trigger(); } } } bool MainWindow::eventFilter(QObject *obj, QEvent *event) { if (!_pluggedController.isNull() && obj == _pluggedController->view()) { switch(event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: switch(static_cast(event)->button()) { case Qt::ForwardButton: triggerAction(QStringLiteral("next-view")); break; case Qt::BackButton: triggerAction(QStringLiteral("previous-view")); break; default: ; } default: ; } } return KXmlGuiWindow::eventFilter(obj, event); } bool MainWindow::focusNextPrevChild(bool) { // In stand-alone konsole, always disable implicit focus switching // through 'Tab' and 'Shift+Tab' // // Kpart is another different story return false; } diff --git a/src/ViewManager.cpp b/src/ViewManager.cpp index 028f300d..2d3c7673 100644 --- a/src/ViewManager.cpp +++ b/src/ViewManager.cpp @@ -1,1168 +1,1143 @@ /* Copyright 2006-2008 by Robert Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ // Own #include "ViewManager.h" #include "config-konsole.h" // Qt #include #include // KDE #include #include #include // Konsole #include #include "ColorScheme.h" #include "ColorSchemeManager.h" #include "Session.h" #include "TerminalDisplay.h" #include "SessionController.h" #include "SessionManager.h" #include "ProfileManager.h" #include "ViewSplitter.h" #include "Enumeration.h" #include "ViewContainer.h" using namespace Konsole; int ViewManager::lastManagerId = 0; ViewManager::ViewManager(QObject *parent, KActionCollection *collection) : QObject(parent), _viewContainer(nullptr), _pluggedController(nullptr), _sessionMap(QHash()), _actionCollection(collection), _navigationMethod(NoNavigation), _navigationVisibility(NavigationNotSet), _managerId(0), _terminalDisplayHistoryIndex(-1) { _viewContainer = createContainer(); // setup actions which are related to the views setupActions(); /* TODO: Reconnect // emit a signal when all of the views held by this view manager are destroyed */ connect(_viewContainer.data(), &Konsole::TabbedViewContainer::empty, this, &Konsole::ViewManager::empty); // listen for profile changes connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged, this, &Konsole::ViewManager::profileChanged); connect(SessionManager::instance(), &Konsole::SessionManager::sessionUpdated, this, &Konsole::ViewManager::updateViewsForSession); //prepare DBus communication new WindowAdaptor(this); _managerId = ++lastManagerId; QDBusConnection::sessionBus().registerObject(QLatin1String("/Windows/") + QString::number(_managerId), this); } ViewManager::~ViewManager() = default; int ViewManager::managerId() const { return _managerId; } QWidget *ViewManager::activeView() const { return _viewContainer->currentWidget(); } QWidget *ViewManager::widget() const { return _viewContainer; } void ViewManager::setupActions() { Q_ASSERT(_actionCollection); if (_actionCollection == nullptr) { return; } KActionCollection *collection = _actionCollection; // Let's reuse the pointer, no need not to. auto *action = new QAction(this); action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); action->setText(i18nc("@action:inmenu", "Split View Left/Right")); connect(action, &QAction::triggered, this, &ViewManager::splitLeftRight); collection->addAction(QStringLiteral("split-view-left-right"), action); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::Key_ParenLeft); action = new QAction(this); action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom"))); action->setText(i18nc("@action:inmenu", "Split View Top/Bottom")); connect(action, &QAction::triggered, this, &ViewManager::splitTopBottom); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::Key_ParenRight); collection->addAction(QStringLiteral("split-view-top-bottom"), action); action = new QAction(this); action->setText(i18nc("@action:inmenu", "Expand View")); action->setEnabled(false); connect(action, &QAction::triggered, this, &ViewManager::expandActiveContainer); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketRight); collection->addAction(QStringLiteral("expand-active-view"), action); _multiSplitterOnlyActions << action; action = new QAction(this); action->setText(i18nc("@action:inmenu", "Shrink View")); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketLeft); action->setEnabled(false); collection->addAction(QStringLiteral("shrink-active-view"), action); connect(action, &QAction::triggered, this, &ViewManager::shrinkActiveContainer); _multiSplitterOnlyActions << action; action = collection->addAction(QStringLiteral("detach-view")); action->setEnabled(true); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach"))); action->setText(i18nc("@action:inmenu", "Detach Current &View")); connect(action, &QAction::triggered, this, &ViewManager::detachActiveView); _multiSplitterOnlyActions << action; // Ctrl+Shift+D is not used as a shortcut by default because it is too close // to Ctrl+D - which will terminate the session in many cases collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_H); action = collection->addAction(QStringLiteral("detach-tab")); action->setEnabled(true); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach"))); action->setText(i18nc("@action:inmenu", "Detach Current &Tab")); connect(action, &QAction::triggered, this, &ViewManager::detachActiveTab); _multiTabOnlyActions << action; // Ctrl+Shift+D is not used as a shortcut by default because it is too close // to Ctrl+D - which will terminate the session in many cases collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_L); // keyboard shortcut only actions action = new QAction(i18nc("@action Shortcut entry", "Next Tab"), this); const QList nextViewActionKeys{Qt::SHIFT + Qt::Key_Right, Qt::CTRL + Qt::Key_PageDown}; collection->setDefaultShortcuts(action, nextViewActionKeys); collection->addAction(QStringLiteral("next-tab"), action); connect(action, &QAction::triggered, this, &ViewManager::nextView); _multiTabOnlyActions << action; // _viewSplitter->addAction(nextViewAction); action = new QAction(i18nc("@action Shortcut entry", "Previous Tab"), this); const QList previousViewActionKeys{Qt::SHIFT + Qt::Key_Left, Qt::CTRL + Qt::Key_PageUp}; collection->setDefaultShortcuts(action, previousViewActionKeys); collection->addAction(QStringLiteral("previous-tab"), action); connect(action, &QAction::triggered, this, &ViewManager::previousView); _multiTabOnlyActions << action; // _viewSplitter->addAction(previousViewAction); action = new QAction(i18nc("@action Shortcut entry", "Focus Above Terminal"), this); connect(action, &QAction::triggered, this, &ViewManager::focusUp); collection->addAction(QStringLiteral("focus-view-above"), action); collection->setDefaultShortcut(action, Qt::SHIFT + Qt::CTRL + Qt::Key_Up); _viewContainer->addAction(action); _multiSplitterOnlyActions << action; action = new QAction(i18nc("@action Shortcut entry", "Focus Below Terminal"), this); collection->setDefaultShortcut(action, Qt::SHIFT + Qt::CTRL + Qt::Key_Down); collection->addAction(QStringLiteral("focus-view-below"), action); connect(action, &QAction::triggered, this, &ViewManager::focusDown); _multiSplitterOnlyActions << action; _viewContainer->addAction(action); action = new QAction(i18nc("@action Shortcut entry", "Focus Left Terminal"), this); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Konsole::LEFT); connect(action, &QAction::triggered, this, &ViewManager::focusLeft); collection->addAction(QStringLiteral("focus-view-left"), action); _multiSplitterOnlyActions << action; action = new QAction(i18nc("@action Shortcut entry", "Focus Right Terminal"), this); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Konsole::RIGHT); connect(action, &QAction::triggered, this, &ViewManager::focusRight); collection->addAction(QStringLiteral("focus-view-right"), action); _multiSplitterOnlyActions << action; action = new QAction(i18nc("@action Shortcut entry", "Switch to Last Tab"), this); connect(action, &QAction::triggered, this, &ViewManager::lastView); collection->addAction(QStringLiteral("last-tab"), action); _multiTabOnlyActions << action; action = new QAction(i18nc("@action Shortcut entry", "Last Used Tabs"), this); connect(action, &QAction::triggered, this, &ViewManager::lastUsedView); collection->setDefaultShortcut(action, Qt::CTRL + Qt::Key_Tab); collection->addAction(QStringLiteral("last-used-tab"), action); action = new QAction(i18nc("@action Shortcut entry", "Toggle Between Two Tabs"), this); connect(action, &QAction::triggered, this, &Konsole::ViewManager::toggleTwoViews); collection->addAction(QStringLiteral("toggle-two-tabs"), action); _multiTabOnlyActions << action; action = new QAction(i18nc("@action Shortcut entry", "Last Used Tabs (Reverse)"), this); collection->addAction(QStringLiteral("last-used-tab-reverse"), action); collection->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_Tab); connect(action, &QAction::triggered, this, &ViewManager::lastUsedViewReverse); action = new QAction(i18nc("@action Shortcut entry", "Maximize current Terminal"), this); collection->addAction(QStringLiteral("maximize-current-terminal"), action); collection->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_E); connect(action, &QAction::triggered, _viewContainer, &TabbedViewContainer::toggleMaximizeCurrentTerminal); _multiSplitterOnlyActions << action; _viewContainer->addAction(action); action = new QAction(i18nc("@action Shortcut entry", "Move tab to the right"), this); collection->addAction(QStringLiteral("move-tab-to-right"), action); collection->setDefaultShortcut(action, Qt::CTRL + Qt::ALT + Qt::Key_Right); connect(action, &QAction::triggered, _viewContainer, &TabbedViewContainer::moveTabRight); _viewContainer->addAction(action); action = new QAction(i18nc("@action Shortcut entry", "Move tab to the left"), this); collection->addAction(QStringLiteral("move-tab-to-left"), action); collection->setDefaultShortcut(action, Qt::CTRL + Qt::ALT + Qt::Key_Left); connect(action, &QAction::triggered, _viewContainer, &TabbedViewContainer::moveTabLeft); _viewContainer->addAction(action); // _viewSplitter->addAction(lastUsedViewReverseAction); const int SWITCH_TO_TAB_COUNT = 19; for (int i = 0; i < SWITCH_TO_TAB_COUNT; i++) { action = new QAction(i18nc("@action Shortcut entry", "Switch to Tab %1", i + 1), this); connect(action, &QAction::triggered, this, [this, i]() { switchToView(i); }); collection->addAction(QStringLiteral("switch-to-tab-%1").arg(i), action); } connect(_viewContainer, &TabbedViewContainer::viewAdded, this, &ViewManager::toggleActionsBasedOnState); connect(_viewContainer, &TabbedViewContainer::viewRemoved, this, &ViewManager::toggleActionsBasedOnState); connect(_viewContainer, &QTabWidget::currentChanged, this, &ViewManager::toggleActionsBasedOnState); toggleActionsBasedOnState(); } void ViewManager::toggleActionsBasedOnState() { const int count = _viewContainer->count(); foreach(QAction *tabOnlyAction, _multiTabOnlyActions) { tabOnlyAction->setEnabled(count > 1); } if ((_viewContainer != nullptr) && (_viewContainer->activeViewSplitter() != nullptr)) { const int splitCount = _viewContainer ->activeViewSplitter() ->getToplevelSplitter() ->findChildren() .count(); foreach (QAction *action, _multiSplitterOnlyActions) { action->setEnabled(splitCount > 1); } } } void ViewManager::switchToView(int index) { _viewContainer->setCurrentIndex(index); } void ViewManager::switchToTerminalDisplay(Konsole::TerminalDisplay* terminalDisplay) { auto splitter = qobject_cast(terminalDisplay->parentWidget()); auto toplevelSplitter = splitter->getToplevelSplitter(); // Focus the TermialDisplay terminalDisplay->setFocus(); if (_viewContainer->currentWidget() != toplevelSplitter) { // Focus the tab switchToView(_viewContainer->indexOf(toplevelSplitter)); } } void ViewManager::focusUp() { _viewContainer->activeViewSplitter()->focusUp(); } void ViewManager::focusDown() { _viewContainer->activeViewSplitter()->focusDown(); } void ViewManager::focusLeft() { _viewContainer->activeViewSplitter()->focusLeft(); } void ViewManager::focusRight() { _viewContainer->activeViewSplitter()->focusRight(); } void ViewManager::moveActiveViewLeft() { _viewContainer->moveActiveView(TabbedViewContainer::MoveViewLeft); } void ViewManager::moveActiveViewRight() { _viewContainer->moveActiveView(TabbedViewContainer::MoveViewRight); } void ViewManager::nextContainer() { // _viewSplitter->activateNextContainer(); } void ViewManager::nextView() { _viewContainer->activateNextView(); } void ViewManager::previousView() { _viewContainer->activatePreviousView(); } void ViewManager::lastView() { _viewContainer->activateLastView(); } void ViewManager::activateLastUsedView(bool reverse) { if (_terminalDisplayHistory.count() <= 1) { return; } if (_terminalDisplayHistoryIndex == -1) { _terminalDisplayHistoryIndex = reverse ? _terminalDisplayHistory.count() - 1 : 1; } else if (reverse) { if (_terminalDisplayHistoryIndex == 0) { _terminalDisplayHistoryIndex = _terminalDisplayHistory.count() - 1; } else { _terminalDisplayHistoryIndex--; } } else { if (_terminalDisplayHistoryIndex >= _terminalDisplayHistory.count() - 1) { _terminalDisplayHistoryIndex = 0; } else { _terminalDisplayHistoryIndex++; } } switchToTerminalDisplay(_terminalDisplayHistory[_terminalDisplayHistoryIndex]); } void ViewManager::lastUsedView() { activateLastUsedView(false); } void ViewManager::lastUsedViewReverse() { activateLastUsedView(true); } void ViewManager::toggleTwoViews() { if (_terminalDisplayHistory.count() <= 1) { return; } switchToTerminalDisplay(_terminalDisplayHistory.at(1)); } void ViewManager::detachActiveView() { // find the currently active view and remove it from its container if ((_viewContainer->findChildren()).count() > 1) { auto activeSplitter = _viewContainer->activeViewSplitter(); auto terminal = activeSplitter->activeTerminalDisplay(); auto newSplitter = new ViewSplitter(); newSplitter->addTerminalDisplay(terminal, Qt::Horizontal); QHash detachedSessions = forgetAll(newSplitter); emit terminalsDetached(newSplitter, detachedSessions); focusAnotherTerminal(activeSplitter->getToplevelSplitter()); toggleActionsBasedOnState(); } } void ViewManager::detachActiveTab() { const int currentIdx = _viewContainer->currentIndex(); detachTab(currentIdx); } void ViewManager::detachTab(int tabIdx) { ViewSplitter* splitter = _viewContainer->viewSplitterAt(tabIdx); QHash detachedSessions = forgetAll(_viewContainer->viewSplitterAt(tabIdx)); emit terminalsDetached(splitter, detachedSessions); } QHash ViewManager::forgetAll(ViewSplitter* splitter) { splitter->setParent(nullptr); QHash detachedSessions; foreach(TerminalDisplay* terminal, splitter->findChildren()) { Session* session = forgetTerminal(terminal); detachedSessions[terminal] = session; } return detachedSessions; } Session* ViewManager::forgetTerminal(TerminalDisplay* terminal) { disconnect(terminal, &TerminalDisplay::requestToggleExpansion, nullptr, nullptr); removeController(terminal->sessionController()); auto session = _sessionMap.take(terminal); if (session != nullptr) { disconnect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished); } _viewContainer->disconnectTerminalDisplay(terminal); updateTerminalDisplayHistory(terminal, true); return session; } +Session* ViewManager::createSession(const Profile::Ptr &profile, const QString &directory) +{ + Session *session = SessionManager::instance()->createSession(profile); + Q_ASSERT(session); + if (!directory.isEmpty()) { + session->setInitialWorkingDirectory(directory); + } + session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); + return session; +} + void ViewManager::sessionFinished() { // if this slot is called after the view manager's main widget // has been destroyed, do nothing if (_viewContainer.isNull()) { return; } auto *session = qobject_cast(sender()); Q_ASSERT(session); auto view = _sessionMap.key(session); _sessionMap.remove(view); if (SessionManager::instance()->isClosingAllSessions()){ return; } // Before deleting the view, let's unmaximize if it's maximized. auto *splitter = qobject_cast(view->parentWidget()); if (splitter == nullptr) { return; } auto *toplevelSplitter = splitter->getToplevelSplitter(); toplevelSplitter->handleMinimizeMaximize(false); view->deleteLater(); // Only remove the controller from factory() if it's actually controlling // the session from the sender. // This fixes BUG: 348478 - messed up menus after a detached tab is closed if ((!_pluggedController.isNull()) && (_pluggedController->session() == session)) { // This is needed to remove this controller from factory() in // order to prevent BUG: 185466 - disappearing menu popup emit unplugController(_pluggedController); } if (!_sessionMap.empty()) { updateTerminalDisplayHistory(view, true); focusAnotherTerminal(toplevelSplitter); toggleActionsBasedOnState(); } } void ViewManager::focusAnotherTerminal(ViewSplitter *toplevelSplitter) { auto tabTterminalDisplays = toplevelSplitter->findChildren(); if (tabTterminalDisplays.count() == 0) { return; } if (tabTterminalDisplays.count() > 1) { // Give focus to the last used terminal in this tab for (auto *historyItem : _terminalDisplayHistory) { for (auto *terminalDisplay : tabTterminalDisplays) { if (terminalDisplay == historyItem) { terminalDisplay->setFocus(Qt::OtherFocusReason); return; } } } } else if (_terminalDisplayHistory.count() >= 1) { // Give focus to the last used terminal tab switchToTerminalDisplay(_terminalDisplayHistory[0]); } } void ViewManager::viewActivated(TerminalDisplay *view) { Q_ASSERT(view != nullptr); // focus the activated view, this will cause the SessionController // to notify the world that the view has been focused and the appropriate UI // actions will be plugged in. view->setFocus(Qt::OtherFocusReason); } void ViewManager::splitLeftRight() { splitView(Qt::Horizontal); } void ViewManager::splitTopBottom() { splitView(Qt::Vertical); } void ViewManager::splitView(Qt::Orientation orientation) { - // get the currently applied profile and use it to create the new tab. int currentSessionId = currentSession(); // At least one display/session exists if we are splitting Q_ASSERT(currentSessionId >= 0); Session *activeSession = SessionManager::instance()->idToSession(currentSessionId); Q_ASSERT(activeSession); auto profile = SessionManager::instance()->sessionProfile(activeSession); - // Create a new session with the selected profile. - auto *session = SessionManager::instance()->createSession(profile); - const QString directory = profile->startInCurrentSessionDir() ? activeSession->currentWorkingDirectory() : QString(); - if (!directory.isEmpty() && profile->startInCurrentSessionDir()) { - session->setInitialWorkingDirectory(directory); - } - - session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); + auto *session = createSession(profile, directory); auto terminalDisplay = createView(session); _viewContainer->splitView(terminalDisplay, orientation); toggleActionsBasedOnState(); // focus the new container terminalDisplay->setFocus(); } void ViewManager::expandActiveContainer() { _viewContainer->activeViewSplitter()->adjustActiveTerminalDisplaySize(10); } void ViewManager::shrinkActiveContainer() { _viewContainer->activeViewSplitter()->adjustActiveTerminalDisplaySize(-10); } SessionController *ViewManager::createController(Session *session, TerminalDisplay *view) { // create a new controller for the session, and ensure that this view manager // is notified when the view gains the focus auto controller = new SessionController(session, view, this); connect(controller, &Konsole::SessionController::focused, this, &Konsole::ViewManager::controllerChanged); connect(session, &Konsole::Session::destroyed, controller, &Konsole::SessionController::deleteLater); connect(session, &Konsole::Session::primaryScreenInUse, controller, &Konsole::SessionController::setupPrimaryScreenSpecificActions); connect(session, &Konsole::Session::selectionChanged, controller, &Konsole::SessionController::selectionChanged); connect(view, &Konsole::TerminalDisplay::destroyed, controller, &Konsole::SessionController::deleteLater); // if this is the first controller created then set it as the active controller if (_pluggedController.isNull()) { controllerChanged(controller); } return controller; } // should this be handed by ViewManager::unplugController signal void ViewManager::removeController(SessionController* controller) { if (_pluggedController == controller) { _pluggedController.clear(); } controller->deleteLater(); } void ViewManager::controllerChanged(SessionController *controller) { if (controller == _pluggedController) { return; } updateTerminalDisplayHistory(controller->view()); _pluggedController = controller; emit activeViewChanged(controller); } SessionController *ViewManager::activeViewController() const { return _pluggedController; } void ViewManager::attachView(TerminalDisplay *terminal, Session *session) { connect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished, Qt::UniqueConnection); // Disconnect from the other viewcontainer. disconnect(terminal, &TerminalDisplay::requestToggleExpansion, nullptr, nullptr); // reconnect on this container. connect(terminal, &TerminalDisplay::requestToggleExpansion, _viewContainer, &TabbedViewContainer::toggleMaximizeCurrentTerminal, Qt::UniqueConnection); _sessionMap[terminal] = session; createController(session, terminal); toggleActionsBasedOnState(); _terminalDisplayHistory.append(terminal); } TerminalDisplay *ViewManager::createView(Session *session) { // notify this view manager when the session finishes so that its view // can be deleted // // Use Qt::UniqueConnection to avoid duplicate connection connect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished, Qt::UniqueConnection); TerminalDisplay *display = createTerminalDisplay(session); const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); applyProfileToView(display, profile); // set initial size const QSize &preferredSize = session->preferredSize(); display->setSize(preferredSize.width(), preferredSize.height()); createController(session, display); _sessionMap[display] = session; session->addView(display); _terminalDisplayHistory.append(display); // tell the session whether it has a light or dark background session->setDarkBackground(colorSchemeForProfile(profile)->hasDarkBackground()); display->setFocus(Qt::OtherFocusReason); // updateDetachViewState(); return display; } TabbedViewContainer *ViewManager::createContainer() { auto *container = new TabbedViewContainer(this, nullptr); container->setNavigationVisibility(_navigationVisibility); connect(container, &TabbedViewContainer::detachTab, this, &ViewManager::detachTab); // connect signals and slots connect(container, &Konsole::TabbedViewContainer::viewAdded, this, [this, container]() { containerViewsChanged(container); }); connect(container, &Konsole::TabbedViewContainer::viewRemoved, this, [this, container]() { containerViewsChanged(container); }); connect(container, &TabbedViewContainer::newViewRequest, this, &ViewManager::newViewRequest); connect(container, &Konsole::TabbedViewContainer::newViewWithProfileRequest, this, &Konsole::ViewManager::newViewWithProfileRequest); connect(container, &Konsole::TabbedViewContainer::viewRemoved, this, &Konsole::ViewManager::viewDestroyed); connect(container, &Konsole::TabbedViewContainer::activeViewChanged, this, &Konsole::ViewManager::viewActivated); return container; } void ViewManager::setNavigationMethod(NavigationMethod method) { Q_ASSERT(_actionCollection); if (_actionCollection == nullptr) { return; } KActionCollection *collection = _actionCollection; _navigationMethod = method; // FIXME: The following disables certain actions for the KPart that it // doesn't actually have a use for, to avoid polluting the action/shortcut // namespace of an application using the KPart (otherwise, a shortcut may // be in use twice, and the user gets to see an "ambiguous shortcut over- // load" error dialog). However, this approach sucks - it's the inverse of // what it should be. Rather than disabling actions not used by the KPart, // a method should be devised to only enable those that are used, perhaps // by using a separate action collection. const bool enable = (method != NoNavigation); auto enableAction = [&enable, &collection](const QString& actionName) { auto *action = collection->action(actionName); if (action != nullptr) { action->setEnabled(enable); } }; enableAction(QStringLiteral("next-view")); enableAction(QStringLiteral("previous-view")); enableAction(QStringLiteral("last-tab")); enableAction(QStringLiteral("last-used-tab")); enableAction(QStringLiteral("last-used-tab-reverse")); enableAction(QStringLiteral("split-view-left-right")); enableAction(QStringLiteral("split-view-top-bottom")); enableAction(QStringLiteral("rename-session")); enableAction(QStringLiteral("move-view-left")); enableAction(QStringLiteral("move-view-right")); } ViewManager::NavigationMethod ViewManager::navigationMethod() const { return _navigationMethod; } void ViewManager::containerViewsChanged(TabbedViewContainer *container) { Q_UNUSED(container); // TODO: Verify that this is right. emit viewPropertiesChanged(viewProperties()); } void ViewManager::viewDestroyed(QWidget *view) { // Note: the received QWidget has already been destroyed, so // using dynamic_cast<> or qobject_cast<> does not work here // We only need the pointer address to look it up below auto *display = reinterpret_cast(view); // 1. detach view from session // 2. if the session has no views left, close it Session *session = _sessionMap[ display ]; _sessionMap.remove(display); if (session != nullptr) { if (session->views().count() == 0) { session->close(); } } //we only update the focus if the splitter is still alive toggleActionsBasedOnState(); // The below causes the menus to be messed up // Only happens when using the tab bar close button // if (_pluggedController) // emit unplugController(_pluggedController); } TerminalDisplay *ViewManager::createTerminalDisplay(Session *session) { auto display = new TerminalDisplay(nullptr); display->setRandomSeed(session->sessionId() | (qApp->applicationPid() << 10)); connect(display, &TerminalDisplay::requestToggleExpansion, _viewContainer, &TabbedViewContainer::toggleMaximizeCurrentTerminal); return display; } const ColorScheme *ViewManager::colorSchemeForProfile(const Profile::Ptr &profile) { const ColorScheme *colorScheme = ColorSchemeManager::instance()-> findColorScheme(profile->colorScheme()); if (colorScheme == nullptr) { colorScheme = ColorSchemeManager::instance()->defaultColorScheme(); } Q_ASSERT(colorScheme); return colorScheme; } bool ViewManager::profileHasBlurEnabled(const Profile::Ptr &profile) { return colorSchemeForProfile(profile)->blur(); } void ViewManager::applyProfileToView(TerminalDisplay *view, const Profile::Ptr &profile) { Q_ASSERT(profile); view->applyProfile(profile); emit updateWindowIcon(); emit blurSettingChanged(view->colorScheme()->blur()); } void ViewManager::updateViewsForSession(Session *session) { const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); const QList sessionMapKeys = _sessionMap.keys(session); foreach (TerminalDisplay *view, sessionMapKeys) { applyProfileToView(view, profile); } } void ViewManager::profileChanged(const Profile::Ptr &profile) { // update all views associated with this profile QHashIterator iter(_sessionMap); while (iter.hasNext()) { iter.next(); // if session uses this profile, update the display if (iter.key() != nullptr && iter.value() != nullptr && SessionManager::instance()->sessionProfile(iter.value()) == profile) { applyProfileToView(iter.key(), profile); } } } QList ViewManager::viewProperties() const { QList list; TabbedViewContainer *container = _viewContainer; if (container == nullptr) { return {}; } auto terminalContainers = _viewContainer->findChildren(); list.reserve(terminalContainers.size()); for(auto terminalDisplay : _viewContainer->findChildren()) { list.append(terminalDisplay->sessionController()); } return list; } namespace { QJsonObject saveSessionTerminal(TerminalDisplay *terminalDisplay) { QJsonObject thisTerminal; auto terminalSession = terminalDisplay->sessionController()->session(); const int sessionRestoreId = SessionManager::instance()->getRestoreId(terminalSession); thisTerminal.insert(QStringLiteral("SessionRestoreId"), sessionRestoreId); return thisTerminal; } QJsonObject saveSessionsRecurse(QSplitter *splitter) { QJsonObject thisSplitter; thisSplitter.insert( QStringLiteral("Orientation"), splitter->orientation() == Qt::Horizontal ? QStringLiteral("Horizontal") : QStringLiteral("Vertical") ); QJsonArray internalWidgets; for (int i = 0; i < splitter->count(); i++) { auto *widget = splitter->widget(i); auto *maybeSplitter = qobject_cast(widget); auto *maybeTerminalDisplay = qobject_cast(widget); if (maybeSplitter != nullptr) { internalWidgets.append(saveSessionsRecurse(maybeSplitter)); } else if (maybeTerminalDisplay != nullptr) { internalWidgets.append(saveSessionTerminal(maybeTerminalDisplay)); } } thisSplitter.insert(QStringLiteral("Widgets"), internalWidgets); return thisSplitter; } } // namespace void ViewManager::saveSessions(KConfigGroup &group) { QJsonArray rootArray; for(int i = 0; i < _viewContainer->count(); i++) { auto *splitter = qobject_cast(_viewContainer->widget(i)); rootArray.append(saveSessionsRecurse(splitter)); } group.writeEntry("Tabs", QJsonDocument(rootArray).toJson(QJsonDocument::Compact)); group.writeEntry("Active", _viewContainer->currentIndex()); } namespace { ViewSplitter *restoreSessionsSplitterRecurse(const QJsonObject& jsonSplitter, ViewManager *manager) { auto splitterWidgets = jsonSplitter[QStringLiteral("Widgets")].toArray(); auto orientation = (jsonSplitter[QStringLiteral("Orientation")].toString() == QLatin1String("Horizontal")) ? Qt::Horizontal : Qt::Vertical; auto *currentSplitter = new ViewSplitter(); currentSplitter->setOrientation(orientation); for (const auto& widgetJsonValue : splitterWidgets) { const auto widgetJsonObject = widgetJsonValue.toObject(); const auto sessionIterator = widgetJsonObject.constFind(QStringLiteral("SessionRestoreId")); if (sessionIterator != widgetJsonObject.constEnd()) { Session *session = SessionManager::instance()->idToSession(sessionIterator->toInt()); auto newView = manager->createView(session); currentSplitter->addWidget(newView); } else { auto nextSplitter = restoreSessionsSplitterRecurse(widgetJsonObject, manager); currentSplitter->addWidget(nextSplitter); } } return currentSplitter; } } // namespace void ViewManager::restoreSessions(const KConfigGroup &group) { const auto tabList = group.readEntry("Tabs", QByteArray("[]")); const auto jsonTabs = QJsonDocument::fromJson(tabList).array(); for (const auto& jsonSplitter : jsonTabs) { auto topLevelSplitter = restoreSessionsSplitterRecurse(jsonSplitter.toObject(), this); _viewContainer->addSplitter(topLevelSplitter, _viewContainer->count()); } if (!jsonTabs.isEmpty()) return; // Session file is unusable, try older format QList ids = group.readEntry("Sessions", QList()); int activeTab = group.readEntry("Active", 0); TerminalDisplay *display = nullptr; int tab = 1; foreach (int id, ids) { Session *session = SessionManager::instance()->idToSession(id); if (session == nullptr) { qWarning() << "Unable to load session with id" << id; // Force a creation of a default session below ids.clear(); break; } activeContainer()->addView(createView(session)); if (!session->isRunning()) { session->run(); } if (tab++ == activeTab) { display = qobject_cast(activeView()); } } if (display != nullptr) { activeContainer()->setCurrentWidget(display); display->setFocus(Qt::OtherFocusReason); } if (ids.isEmpty()) { // Session file is unusable, start default Profile Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); Session *session = SessionManager::instance()->createSession(profile); activeContainer()->addView(createView(session)); if (!session->isRunning()) { session->run(); } } } TabbedViewContainer *ViewManager::activeContainer() { return _viewContainer; } int ViewManager::sessionCount() { return _sessionMap.size(); } QStringList ViewManager::sessionList() { QStringList ids; QHash::const_iterator i; for (i = _sessionMap.constBegin(); i != _sessionMap.constEnd(); ++i) { ids.append(QString::number(i.value()->sessionId())); } return ids; } int ViewManager::currentSession() { if (_pluggedController) { Q_ASSERT(_pluggedController->session() != nullptr); return _pluggedController->session()->sessionId(); } return -1; } void ViewManager::setCurrentSession(int sessionId) { auto *session = SessionManager::instance()->idToSession(sessionId); if (session == nullptr || session->views().count() == 0) { return; } auto *display = session->views().at(0); if (display != nullptr) { display->setFocus(Qt::OtherFocusReason); } } int ViewManager::newSession() { - Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); - Session *session = SessionManager::instance()->createSession(profile); - - session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); - - auto newView = createView(session); - activeContainer()->addView(newView); - session->run(); - - return session->sessionId(); + return newSession(QString(), QString()); } int ViewManager::newSession(const QString &profile) { - const QList profilelist = ProfileManager::instance()->allProfiles(); - Profile::Ptr profileptr = ProfileManager::instance()->defaultProfile(); - - for (const auto &i : profilelist) { - if (i->name() == profile) { - profileptr = i; - break; - } - } - - Session *session = SessionManager::instance()->createSession(profileptr); - - session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); - - auto newView = createView(session); - activeContainer()->addView(newView); - session->run(); - - return session->sessionId(); + return newSession(profile, QString()); } int ViewManager::newSession(const QString &profile, const QString &directory) { - const QList profilelist = ProfileManager::instance()->allProfiles(); Profile::Ptr profileptr = ProfileManager::instance()->defaultProfile(); + if(!profile.isEmpty()) { + const QList profilelist = ProfileManager::instance()->allProfiles(); - for (const auto &i : profilelist) { - if (i->name() == profile) { - profileptr = i; - break; + for (const auto &i : profilelist) { + if (i->name() == profile) { + profileptr = i; + break; + } } } - Session *session = SessionManager::instance()->createSession(profileptr); - session->setInitialWorkingDirectory(directory); - - session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); + Session *session = createSession(profileptr, directory); auto newView = createView(session); activeContainer()->addView(newView); session->run(); return session->sessionId(); } QString ViewManager::defaultProfile() { return ProfileManager::instance()->defaultProfile()->name(); } QStringList ViewManager::profileList() { return ProfileManager::instance()->availableProfileNames(); } void ViewManager::nextSession() { nextView(); } void ViewManager::prevSession() { previousView(); } void ViewManager::moveSessionLeft() { moveActiveViewLeft(); } void ViewManager::moveSessionRight() { moveActiveViewRight(); } void ViewManager::setTabWidthToText(bool setTabWidthToText) { _viewContainer->tabBar()->setExpanding(!setTabWidthToText); _viewContainer->tabBar()->update(); } void ViewManager::setNavigationVisibility(NavigationVisibility navigationVisibility) { if (_navigationVisibility != navigationVisibility) { _navigationVisibility = navigationVisibility; _viewContainer->setNavigationVisibility(navigationVisibility); } } void ViewManager::updateTerminalDisplayHistory(TerminalDisplay* terminalDisplay, bool remove) { if (terminalDisplay == nullptr) { if (_terminalDisplayHistoryIndex >= 0) { // This is the case when we finished walking through the history // (i.e. when Ctrl-Tab has been released) terminalDisplay = _terminalDisplayHistory[_terminalDisplayHistoryIndex]; _terminalDisplayHistoryIndex = -1; } else { return; } } if (_terminalDisplayHistoryIndex >= 0 && !remove) { // Do not reorder the tab history while we are walking through it return; } for (int i = 0; i < _terminalDisplayHistory.count(); i++) { if (_terminalDisplayHistory[i] == terminalDisplay) { _terminalDisplayHistory.removeAt(i); if (!remove) { _terminalDisplayHistory.prepend(terminalDisplay); } break; } } } diff --git a/src/ViewManager.h b/src/ViewManager.h index 399d13a6..cded6a38 100644 --- a/src/ViewManager.h +++ b/src/ViewManager.h @@ -1,438 +1,447 @@ /* Copyright 2006-2008 by Robert Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef VIEWMANAGER_H #define VIEWMANAGER_H // Qt #include #include #include #include // Konsole #include "Profile.h" class KActionCollection; class KConfigGroup; namespace Konsole { class ColorScheme; class Session; class TerminalDisplay; class TabbedViewContainer; class SessionController; class ViewProperties; class ViewSplitter; class TabbedViewContainer; /** * Manages the terminal display widgets in a Konsole window or part. * * When a view manager is created, it constructs a tab widget ( accessed via * widget() ) to hold one or more view splitters. Each view splitter holds * one or more terminal displays and splitters. * * The view manager provides menu actions ( defined in the 'konsoleui.rc' XML file ) * to manipulate the views and view containers - for example, actions to split the view * left/right or top/bottom, detach a view from the current window and navigate between * views and containers. These actions are added to the collection specified in the * ViewManager's constructor. * * The view manager provides facilities to construct display widgets for a terminal * session and also to construct the SessionController which provides the menus and other * user interface elements specific to each display/session pair. * */ class KONSOLEPRIVATE_EXPORT ViewManager : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.konsole.Window") public: /** * Constructs a new view manager with the specified @p parent. * View-related actions defined in 'konsoleui.rc' are created * and added to the specified @p collection. */ ViewManager(QObject *parent, KActionCollection *collection); ~ViewManager() Q_DECL_OVERRIDE; /** * Creates a new view to display the output from and deliver input to @p session. * Constructs a new container to hold the views if no container has yet been created. */ void createView(TabbedViewContainer *tabWidget, Session *session); /* * Applies the view-specific settings associated with specified @p profile * to the terminal display @p view. */ void applyProfileToView(TerminalDisplay *view, const Profile::Ptr &profile); void toggleActionsBasedOnState(); /** * Return the main widget for the view manager which * holds all of the views managed by this ViewManager instance. */ QWidget *widget() const; /** * Returns the view manager's active view. */ QWidget *activeView() const; /** * Returns the list of view properties for views in the active container. * Each view widget is associated with a ViewProperties instance which * provides access to basic information about the session being * displayed in the view, such as title, current directory and * associated icon. */ QList viewProperties() const; /** * This enum describes the available types of navigation widget * which newly created containers can provide to allow navigation * between open sessions. */ enum NavigationMethod { /** * Each container has a row of tabs (one per session) which the user * can click on to navigate between open sessions. */ TabbedNavigation, /** The container has no navigation widget. */ NoNavigation }; /** * Describes the options for showing or hiding the container's navigation widget. */ enum NavigationVisibility { NavigationNotSet, // Don't rely on this information, Only use the settings. AlwaysShowNavigation, ShowNavigationAsNeeded, AlwaysHideNavigation }; /** * Sets the visibility of the view container's navigation widget. * The ViewContainer subclass is responsible for ensuring that this * setting is respected as views are dded or removed from the container */ void setNavigationVisibility(NavigationVisibility navigationVisibility); /** Returns the current mode for controlling the visibility of the * view container's navigation widget. */ NavigationVisibility navigationVisibility() const; /** * Sets the type of widget provided to navigate between open sessions * in a container. The changes will only apply to newly created containers. * * The default method is TabbedNavigation. To disable navigation widgets, call * setNavigationMethod(ViewManager::NoNavigation) before creating any sessions. */ void setNavigationMethod(NavigationMethod method); /** * Returns the type of navigation widget created in new containers. * See setNavigationMethod() */ NavigationMethod navigationMethod() const; /** * Returns the controller for the active view. activeViewChanged() is * emitted when this changes. */ SessionController *activeViewController() const; /** * Session management */ void saveSessions(KConfigGroup &group); void restoreSessions(const KConfigGroup &group); int managerId() const; /** Returns a list of sessions in this ViewManager */ QList sessions() { return _sessionMap.values(); } /** * Returns whether the @p profile has the blur setting enabled */ static bool profileHasBlurEnabled(const Profile::Ptr &profile); /** returns the active tab from the view */ TabbedViewContainer *activeContainer(); TerminalDisplay *createView(Session *session); void attachView(TerminalDisplay *terminal, Session *session); static const ColorScheme *colorSchemeForProfile(const Profile::Ptr &profile); /** Reorder the terminal display history list */ void updateTerminalDisplayHistory(TerminalDisplay *terminalDisplay = nullptr, bool remove = false); QHash forgetAll(ViewSplitter* splitter); Session* forgetTerminal(TerminalDisplay* terminal); + + /** + * Creates and returns new session + * + * The session has specified @p profile, working @p directory + * and configured environment. + */ + Session* createSession(const Profile::Ptr &profile, const QString &directory = QString()); + Q_SIGNALS: /** Emitted when the last view is removed from the view manager */ void empty(); /** Emitted when a session is detached from a view owned by this ViewManager */ void terminalsDetached(ViewSplitter *splitter, QHash sessionsMap); /** * Emitted when the active view changes. * @param controller The controller associated with the active view */ void activeViewChanged(SessionController *controller); /** * Emitted when the current session needs unplugged from factory(). * @param controller The controller associated with the active view */ void unplugController(SessionController *controller); /** * Emitted when the list of view properties ( as returned by viewProperties() ) changes. * This occurs when views are added to or removed from the active container, or * if the active container is changed. */ void viewPropertiesChanged(const QList &propertiesList); /** * Emitted when menu bar visibility changes because a profile that requires so is * activated. */ void setMenuBarVisibleRequest(bool); void updateWindowIcon(); void blurSettingChanged(bool); /** Requests creation of a new view with the default profile. */ void newViewRequest(); /** Requests creation of a new view, with the selected profile. */ void newViewWithProfileRequest(const Profile::Ptr&); public Q_SLOTS: /** DBus slot that returns the number of sessions in the current view. */ Q_SCRIPTABLE int sessionCount(); /** * DBus slot that returns the unique ids of the sessions in the * current view. The returned list is not sorted. * QList is not printable by qdbus so we use QStringList */ Q_SCRIPTABLE QStringList sessionList(); /** DBus slot that returns the current (active) session window */ Q_SCRIPTABLE int currentSession(); /** DBus slot that sets the current (active) session window */ Q_SCRIPTABLE void setCurrentSession(int sessionId); /** DBus slot that creates a new session in the current view with the associated * default profile and the default working directory */ Q_SCRIPTABLE int newSession(); /** DBus slot that creates a new session in the current view. * @param profile the name of the profile to be used * started. */ Q_SCRIPTABLE int newSession(const QString &profile); /** DBus slot that creates a new session in the current view. * @param profile the name of the profile to be used * @param directory the working directory where the session is * started. */ Q_SCRIPTABLE int newSession(const QString &profile, const QString &directory); // TODO: its semantic is application-wide. Move it to more appropriate place // DBus slot that returns the name of default profile Q_SCRIPTABLE QString defaultProfile(); // TODO: its semantic is application-wide. Move it to more appropriate place // DBus slot that returns a string list of defined (known) profiles Q_SCRIPTABLE QStringList profileList(); /** DBus slot that changes the view port to the next session */ Q_SCRIPTABLE void nextSession(); /** DBus slot that changes the view port to the previous session */ Q_SCRIPTABLE void prevSession(); /** DBus slot that switches the current session (as returned by * currentSession()) with the left (or previous) one in the * navigation tab. */ Q_SCRIPTABLE void moveSessionLeft(); /** DBus slot that Switches the current session (as returned by * currentSession()) with the right (or next) one in the navigation * tab. */ Q_SCRIPTABLE void moveSessionRight(); /** DBus slot that sets ALL tabs' width to match their text */ Q_SCRIPTABLE void setTabWidthToText(bool); private Q_SLOTS: // called when the "Split View Left/Right" menu item is selected void splitLeftRight(); void splitTopBottom(); void expandActiveContainer(); void shrinkActiveContainer(); // called when the "Detach View" menu item is selected void detachActiveView(); void detachActiveTab(); // called when a session terminates - the view manager will delete any // views associated with the session void sessionFinished(); // called when one view has been destroyed void viewDestroyed(QWidget *view); // controller detects when an associated view is given the focus // and emits a signal. ViewManager listens for that signal // and then plugs the action into the UI //void viewFocused( SessionController* controller ); // called when the active view in a ViewContainer changes, so // that we can plug the appropriate actions into the UI void viewActivated(TerminalDisplay *view); void focusUp(); void focusDown(); void focusLeft(); void focusRight(); // called when "Next View" shortcut is activated void nextView(); // called when "Previous View" shortcut is activated void previousView(); // called when "Switch to last tab" shortcut is activated void lastView(); // called when "Switch to last used tab" shortcut is activated void lastUsedView(); // called when "Switch to last used tab (reverse)" shortcut is activated void lastUsedViewReverse(); // called when "Next View Container" shortcut is activated void nextContainer(); // called when "Toggle Two tabs" shortcut is activated void toggleTwoViews(); // called when the views in a container owned by this view manager // changes void containerViewsChanged(TabbedViewContainer *container); // called when a profile changes void profileChanged(const Profile::Ptr &profile); void updateViewsForSession(Session *session); // moves active view to the left void moveActiveViewLeft(); // moves active view to the right void moveActiveViewRight(); // switches to the view at visual position 'index' // in the current container void switchToView(int index); // gives focus and switches the terminal display, changing tab if needed void switchToTerminalDisplay(TerminalDisplay *terminalDisplay); // called when a SessionController gains focus void controllerChanged(SessionController *controller); /* Detaches the tab at index tabIdx */ void detachTab(int tabIdx); private: Q_DISABLE_COPY(ViewManager) void createView(Session *session, TabbedViewContainer *container, int index); void setupActions(); // takes a view from a view container owned by a different manager and places it in // newContainer owned by this manager void takeView(ViewManager *otherManager, TabbedViewContainer *otherContainer, TabbedViewContainer *newContainer, TerminalDisplay *view); void splitView(Qt::Orientation orientation); // creates a new container which can hold terminal displays TabbedViewContainer *createContainer(); // creates a new terminal display // the 'session' is used so that the terminal display's random seed // can be set to something which depends uniquely on that session TerminalDisplay *createTerminalDisplay(Session *session = nullptr); // creates a new controller for a session/display pair which provides the menu // actions associated with that view, and exposes basic information // about the session ( such as title and associated icon ) to the display. SessionController *createController(Session *session, TerminalDisplay *view); void removeController(SessionController* controller); // Activates a different terminal when the TerminalDisplay // closes or is detached and another one should be focused. // It will activate the last used terminal within the same splitView // if possible otherwise it will focus the last used tab void focusAnotherTerminal(ViewSplitter *toplevelSplitter); void activateLastUsedView(bool reverse); private: QPointer _viewContainer; QPointer _pluggedController; QHash _sessionMap; KActionCollection *_actionCollection; NavigationMethod _navigationMethod; NavigationVisibility _navigationVisibility; int _managerId; static int lastManagerId; QList _terminalDisplayHistory; int _terminalDisplayHistoryIndex; // List of actions that should only be enabled when there are multiple view // containers open QList _multiTabOnlyActions; QList _multiSplitterOnlyActions; }; } #endif