diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index eefb3110..30df398d 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1,877 +1,878 @@ /* 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 #include // KDE #include #include #include #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 "ViewManager.h" #include "SessionManager.h" #include "ProfileManager.h" #include "KonsoleSettings.h" #include "WindowSystemInfo.h" #include "TerminalDisplay.h" #include "settings/FileLocationSettings.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, static_cast(&Konsole::ViewManager::newViewRequest), this, &Konsole::MainWindow::newFromProfile); connect(_viewManager, static_cast(&Konsole::ViewManager::newViewRequest), this, &Konsole::MainWindow::newTab); connect(_viewManager, &Konsole::ViewManager::viewDetached, this, &Konsole::MainWindow::viewDetached); 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); } } void MainWindow::activeViewChanged(SessionController *controller) { // 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(" "); } if (KonsoleSettings::showAppNameOnTitleBar()) { setCaption(caption); } else { setPlainCaption(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, &Konsole::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, &Konsole::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(); Q_FOREACH (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())); // 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 _viewManager->createView(session); 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 _viewManager->createView(session); 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(), true); } int result; if (processesRunning.count() > 0) { 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(), QStringLiteral("CloseAllTabs")); } else { result = KMessageBox::warningYesNoCancel(this, i18nc("@info", "There are %1 open tabs 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(), QStringLiteral("CloseAllEmptyTabs")); } switch (result) { case KMessageBox::Yes: return true; case KMessageBox::No: if ((!_pluggedController.isNull()) && (!_pluggedController->session().isNull())) { disconnectController(_pluggedController); _pluggedController->session()->closeInNormalWay(); } 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()) { MainWindow *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(Profile::Ptr profile) { createSession(profile, activeSessionDir()); } void MainWindow::showManageProfilesDialog() { showSettingsDialog(true); } void MainWindow::showSettingsDialog(const bool showProfilePage) { if (KConfigDialog::showDialog(QStringLiteral("settings"))) { return; } KConfigDialog *settingsDialog = new KConfigDialog(this, QStringLiteral("settings"), KonsoleSettings::self()); settingsDialog->setFaceType(KPageDialog::Tabbed); auto generalSettings = new GeneralSettings(settingsDialog); settingsDialog->addPage(generalSettings, i18nc("@title Preferences page name", "General"), QStringLiteral("utilities-terminal")); auto profileSettings = new ProfileSettings(settingsDialog); KPageWidgetItem *profilePage = settingsDialog->addPage(profileSettings, i18nc("@title Preferences page name", "Profiles"), QStringLiteral("configure")); auto tabBarSettings = new TabBarSettings(settingsDialog); settingsDialog->addPage(tabBarSettings, i18nc("@title Preferences page name", "TabBar"), QStringLiteral("system-run")); auto fileLocationSettings = new FileLocationSettings(settingsDialog); settingsDialog->addPage(fileLocationSettings, i18nc("@title Preferences page name", "File Location"), QStringLiteral("configure")); if (showProfilePage) { settingsDialog->setCurrentPage(profilePage); } settingsDialog->show(); } void MainWindow::applyKonsoleSettings() { setMenuBarInitialVisibility(KonsoleSettings::showMenuBarByDefault()); if (KonsoleSettings::allowMenuAccelerators()) { restoreMenuAccelerators(); } else { removeMenuAccelerators(); } + _viewManager->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::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 (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 7a43d9d6..acdcba23 100644 --- a/src/ViewManager.cpp +++ b/src/ViewManager.cpp @@ -1,1124 +1,1134 @@ /* Copyright 2006-2008 by Robert Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ // Own #include "ViewManager.h" #include // Qt #include #include // KDE #include #include #include #include // Konsole #include #include "ColorScheme.h" #include "ColorSchemeManager.h" #include "Session.h" #include "TerminalDisplay.h" #include "SessionController.h" #include "SessionManager.h" #include "ProfileManager.h" #include "ViewSplitter.h" #include "Enumeration.h" #include "ViewContainer.h" using namespace Konsole; int ViewManager::lastManagerId = 0; ViewManager::ViewManager(QObject *parent, KActionCollection *collection) : QObject(parent), _viewSplitter(nullptr), _pluggedController(nullptr), _sessionMap(QHash()), _actionCollection(collection), _navigationVisibility(NavigationNotSet), + _newTabBehavior(PutNewTabAtTheEnd), _managerId(0) { // create main view area _viewSplitter = new ViewSplitter(nullptr); KAcceleratorManager::setNoAccel(_viewSplitter); // the ViewSplitter class supports both recursive and non-recursive splitting, // in non-recursive mode, all containers are inserted into the same top-level splitter // widget, and all the divider lines between the containers have the same orientation // // the ViewManager class is not currently able to handle a ViewSplitter in recursive-splitting // mode _viewSplitter->setRecursiveSplitting(false); _viewSplitter->setFocusPolicy(Qt::NoFocus); // setup actions which are related to the views setupActions(); // emit a signal when all of the views held by this view manager are destroyed connect(_viewSplitter.data(), &Konsole::ViewSplitter::allContainersEmpty, this, &Konsole::ViewManager::empty); connect(_viewSplitter.data(), &Konsole::ViewSplitter::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 { TabbedViewContainer *container = _viewSplitter->activeContainer(); if (container != nullptr) { return container->currentWidget(); } else { return nullptr; } } QWidget *ViewManager::widget() const { return _viewSplitter; } void ViewManager::setupActions() { Q_ASSERT(_actionCollection); if (_actionCollection == nullptr) { return; } KActionCollection *collection = _actionCollection; QAction *nextViewAction = new QAction(i18nc("@action Shortcut entry", "Next Tab"), this); QAction *previousViewAction = new QAction(i18nc("@action Shortcut entry", "Previous Tab"), this); QAction *lastViewAction = new QAction(i18nc("@action Shortcut entry", "Switch to Last Tab"), this); QAction *nextContainerAction = new QAction(i18nc("@action Shortcut entry", "Next View Container"), this); QAction *moveViewLeftAction = new QAction(i18nc("@action Shortcut entry", "Move Tab Left"), this); QAction *moveViewRightAction = new QAction(i18nc("@action Shortcut entry", "Move Tab Right"), this); // list of actions that should only be enabled when there are multiple view // containers open QList multiViewOnlyActions; multiViewOnlyActions << nextContainerAction; QAction *splitLeftRightAction = new QAction(QIcon::fromTheme(QStringLiteral("view-split-left-right")), i18nc("@action:inmenu", "Split View Left/Right"), this); collection->setDefaultShortcut(splitLeftRightAction, Konsole::ACCEL + Qt::Key_ParenLeft); collection->addAction(QStringLiteral("split-view-left-right"), splitLeftRightAction); connect(splitLeftRightAction, &QAction::triggered, this, &Konsole::ViewManager::splitLeftRight); QAction *splitTopBottomAction = new QAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")), i18nc("@action:inmenu", "Split View Top/Bottom"), this); collection->setDefaultShortcut(splitTopBottomAction, Konsole::ACCEL + Qt::Key_ParenRight); collection->addAction(QStringLiteral("split-view-top-bottom"), splitTopBottomAction); connect(splitTopBottomAction, &QAction::triggered, this, &Konsole::ViewManager::splitTopBottom); QAction *closeActiveAction = new QAction(i18nc("@action:inmenu Close Active View", "Close Active"), this); closeActiveAction->setIcon(QIcon::fromTheme(QStringLiteral("view-close"))); collection->setDefaultShortcut(closeActiveAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_X); closeActiveAction->setEnabled(false); collection->addAction(QStringLiteral("close-active-view"), closeActiveAction); connect(closeActiveAction, &QAction::triggered, this, &Konsole::ViewManager::closeActiveContainer); multiViewOnlyActions << closeActiveAction; QAction *closeOtherAction = new QAction(i18nc("@action:inmenu Close Other Views", "Close Others"), this); collection->setDefaultShortcut(closeOtherAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_O); closeOtherAction->setEnabled(false); collection->addAction(QStringLiteral("close-other-views"), closeOtherAction); connect(closeOtherAction, &QAction::triggered, this, &Konsole::ViewManager::closeOtherContainers); multiViewOnlyActions << closeOtherAction; // Expand & Shrink Active View QAction *expandActiveAction = new QAction(i18nc("@action:inmenu", "Expand View"), this); collection->setDefaultShortcut(expandActiveAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketRight); expandActiveAction->setEnabled(false); collection->addAction(QStringLiteral("expand-active-view"), expandActiveAction); connect(expandActiveAction, &QAction::triggered, this, &Konsole::ViewManager::expandActiveContainer); multiViewOnlyActions << expandActiveAction; QAction *shrinkActiveAction = new QAction(i18nc("@action:inmenu", "Shrink View"), this); collection->setDefaultShortcut(shrinkActiveAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketLeft); shrinkActiveAction->setEnabled(false); collection->addAction(QStringLiteral("shrink-active-view"), shrinkActiveAction); connect(shrinkActiveAction, &QAction::triggered, this, &Konsole::ViewManager::shrinkActiveContainer); multiViewOnlyActions << shrinkActiveAction; // Crashes on Mac. #if defined(ENABLE_DETACHING) QAction *detachViewAction = collection->addAction(QStringLiteral("detach-view")); detachViewAction->setEnabled(true); detachViewAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach"))); detachViewAction->setText(i18nc("@action:inmenu", "D&etach Current Tab")); // Ctrl+Shift+D is not used as a shortcut by default because it is too close // to Ctrl+D - which will terminate the session in many cases collection->setDefaultShortcut(detachViewAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_H); connect(this, &Konsole::ViewManager::splitViewToggle, this, &Konsole::ViewManager::updateDetachViewState); connect(detachViewAction, &QAction::triggered, this, &Konsole::ViewManager::detachActiveView); #endif // Next / Previous View , Next Container collection->addAction(QStringLiteral("next-view"), nextViewAction); collection->addAction(QStringLiteral("previous-view"), previousViewAction); collection->addAction(QStringLiteral("last-tab"), lastViewAction); collection->addAction(QStringLiteral("next-container"), nextContainerAction); collection->addAction(QStringLiteral("move-view-left"), moveViewLeftAction); collection->addAction(QStringLiteral("move-view-right"), moveViewRightAction); // Switch to tab N shortcuts const int SWITCH_TO_TAB_COUNT = 19; for (int i = 0; i < SWITCH_TO_TAB_COUNT; i++) { QAction *switchToTabAction = new QAction(i18nc("@action Shortcut entry", "Switch to Tab %1", i + 1), this); connect(switchToTabAction, &QAction::triggered, this, [this, i]() { switchToView(i); }); collection->addAction(QStringLiteral("switch-to-tab-%1").arg(i), switchToTabAction); } foreach (QAction *action, multiViewOnlyActions) { connect(this, &Konsole::ViewManager::splitViewToggle, action, &QAction::setEnabled); } // keyboard shortcut only actions const QList nextViewActionKeys{Qt::SHIFT + Qt::Key_Right, Qt::CTRL + Qt::Key_PageDown}; collection->setDefaultShortcuts(nextViewAction, nextViewActionKeys); connect(nextViewAction, &QAction::triggered, this, &Konsole::ViewManager::nextView); _viewSplitter->addAction(nextViewAction); const QList previousViewActionKeys{Qt::SHIFT + Qt::Key_Left, Qt::CTRL + Qt::Key_PageUp}; collection->setDefaultShortcuts(previousViewAction, previousViewActionKeys); connect(previousViewAction, &QAction::triggered, this, &Konsole::ViewManager::previousView); _viewSplitter->addAction(previousViewAction); collection->setDefaultShortcut(nextContainerAction, Qt::SHIFT + Qt::Key_Tab); connect(nextContainerAction, &QAction::triggered, this, &Konsole::ViewManager::nextContainer); _viewSplitter->addAction(nextContainerAction); #ifdef Q_OS_MACOS collection->setDefaultShortcut(moveViewLeftAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketLeft); #else collection->setDefaultShortcut(moveViewLeftAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Left); #endif connect(moveViewLeftAction, &QAction::triggered, this, &Konsole::ViewManager::moveActiveViewLeft); _viewSplitter->addAction(moveViewLeftAction); #ifdef Q_OS_MACOS collection->setDefaultShortcut(moveViewRightAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketRight); #else collection->setDefaultShortcut(moveViewRightAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Right); #endif connect(moveViewRightAction, &QAction::triggered, this, &Konsole::ViewManager::moveActiveViewRight); _viewSplitter->addAction(moveViewRightAction); connect(lastViewAction, &QAction::triggered, this, &Konsole::ViewManager::lastView); _viewSplitter->addAction(lastViewAction); } void ViewManager::switchToView(int index) { _viewSplitter->activeContainer()->setCurrentIndex(index); } void ViewManager::updateDetachViewState() { Q_ASSERT(_actionCollection); if (_actionCollection == nullptr) { return; } const bool splitView = _viewSplitter->containers().count() >= 2; auto activeContainer = _viewSplitter->activeContainer(); const bool shouldEnable = splitView || ((activeContainer != nullptr) && activeContainer->count() >= 2); QAction *detachAction = _actionCollection->action(QStringLiteral("detach-view")); if ((detachAction != nullptr) && shouldEnable != detachAction->isEnabled()) { detachAction->setEnabled(shouldEnable); } } void ViewManager::moveActiveViewLeft() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->moveActiveView(TabbedViewContainer::MoveViewLeft); } void ViewManager::moveActiveViewRight() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->moveActiveView(TabbedViewContainer::MoveViewRight); } void ViewManager::nextContainer() { _viewSplitter->activateNextContainer(); } void ViewManager::nextView() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->activateNextView(); } void ViewManager::previousView() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->activatePreviousView(); } void ViewManager::lastView() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->activateLastView(); } void ViewManager::detachActiveView() { // find the currently active view and remove it from its container TabbedViewContainer *container = _viewSplitter->activeContainer(); detachView(container, container->currentWidget()); } void ViewManager::detachView(TabbedViewContainer *container, QWidget *view) { #if !defined(ENABLE_DETACHING) return; #endif TerminalDisplay *viewToDetach = qobject_cast(view); if (viewToDetach == nullptr) { return; } // BR390736 - some instances are sending invalid session to viewDetached() Session *sessionToDetach = _sessionMap[viewToDetach]; if (sessionToDetach == nullptr) { return; } emit viewDetached(sessionToDetach); _sessionMap.remove(viewToDetach); // remove the view from this window container->removeView(viewToDetach); viewToDetach->deleteLater(); // if the container from which the view was removed is now empty then it can be deleted, // unless it is the only container in the window, in which case it is left empty // so that there is always an active container if (_viewSplitter->containers().count() > 1 && container->count() == 0) { removeContainer(container); } } void ViewManager::sessionFinished() { // if this slot is called after the view manager's main widget // has been destroyed, do nothing if (_viewSplitter.isNull()) { return; } Session *session = qobject_cast(sender()); Q_ASSERT(session); // close attached views QList children = _viewSplitter->findChildren(); foreach (TerminalDisplay *view, children) { if (_sessionMap[view] == session) { _sessionMap.remove(view); view->deleteLater(); } } // Only remove the controller from factory() if it's actually controlling // the session from the sender. // This fixes BUG: 348478 - messed up menus after a detached tab is closed if ((!_pluggedController.isNull()) && (_pluggedController->session() == session)) { // This is needed to remove this controller from factory() in // order to prevent BUG: 185466 - disappearing menu popup emit unplugController(_pluggedController); } } void ViewManager::viewActivated(QWidget *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) { TabbedViewContainer *container = createContainer(); // iterate over each session which has a view in the current active // container and create a new view for that session in a new container for(int i = 0, end = _viewSplitter->activeContainer()->count(); i < end; i++) { auto view = _viewSplitter->activeContainer()->widget(i); Session *session = _sessionMap[qobject_cast(view)]; TerminalDisplay *display = createTerminalDisplay(session); const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); applyProfileToView(display, profile); ViewProperties *properties = createController(session, display); _sessionMap[display] = session; container->addView(display, properties); session->addView(display); } _viewSplitter->addContainer(container, orientation); emit splitViewToggle(_viewSplitter->containers().count() > 0); // focus the new container container->currentWidget()->setFocus(); // ensure that the active view is focused after the split / unsplit TabbedViewContainer *activeContainer = _viewSplitter->activeContainer(); QWidget *activeView = activeContainer != nullptr ? activeContainer->currentWidget() : nullptr; if (activeView != nullptr) { activeView->setFocus(Qt::OtherFocusReason); } } void ViewManager::removeContainer(TabbedViewContainer *container) { // remove session map entries for views in this container for(int i = 0, end = container->count(); i < end; i++) { auto view = container->widget(i); TerminalDisplay *display = qobject_cast(view); Q_ASSERT(display); _sessionMap.remove(display); } _viewSplitter->removeContainer(container); container->deleteLater(); emit splitViewToggle(_viewSplitter->containers().count() > 1); } void ViewManager::expandActiveContainer() { _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(), 10); } void ViewManager::shrinkActiveContainer() { _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(), -10); } void ViewManager::closeActiveContainer() { // only do something if there is more than one container active if (_viewSplitter->containers().count() > 1) { TabbedViewContainer *container = _viewSplitter->activeContainer(); removeContainer(container); // focus next container so that user can continue typing // without having to manually focus it themselves nextContainer(); } } void ViewManager::closeOtherContainers() { TabbedViewContainer *active = _viewSplitter->activeContainer(); foreach (TabbedViewContainer *container, _viewSplitter->containers()) { if (container != active) { removeContainer(container); } } } 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; } void ViewManager::controllerChanged(SessionController *controller) { if (controller == _pluggedController) { return; } _viewSplitter->setFocusProxy(controller->view()); _pluggedController = controller; emit activeViewChanged(controller); } SessionController *ViewManager::activeViewController() const { return _pluggedController; } void ViewManager::createView(Session *session, TabbedViewContainer *container, int index) { // 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()); ViewProperties *properties = createController(session, display); _sessionMap[display] = session; container->addView(display, properties, index); session->addView(display); // tell the session whether it has a light or dark background session->setDarkBackground(colorSchemeForProfile(profile)->hasDarkBackground()); if (container == _viewSplitter->activeContainer()) { container->setCurrentWidget(display); display->setFocus(Qt::OtherFocusReason); } updateDetachViewState(); } void ViewManager::createView(Session *session) { // create the default container if (_viewSplitter->containers().count() == 0) { TabbedViewContainer *container = createContainer(); _viewSplitter->addContainer(container, Qt::Vertical); emit splitViewToggle(false); } // new tab will be put at the end by default. int index = -1; + if (_newTabBehavior == PutNewTabAfterCurrentTab) { + index = _viewSplitter->activeContainer()->currentIndex() + 1; + } + // iterate over the view containers owned by this view manager // and create a new terminal display for the session in each of them, along with // a controller for the session/display pair foreach (TabbedViewContainer *container, _viewSplitter->containers()) { createView(session, container, index); } } TabbedViewContainer *ViewManager::createContainer() { auto *container = new TabbedViewContainer(this, _viewSplitter); container->setNavigationVisibility(_navigationVisibility); //TODO: Fix Detaching. connect(container, &TabbedViewContainer::detachTab, this, &ViewManager::detachView); // 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, static_cast(&Konsole::TabbedViewContainer::newViewRequest), this, static_cast(&Konsole::ViewManager::newViewRequest)); connect(container, static_cast(&Konsole::TabbedViewContainer::newViewRequest), this, static_cast(&Konsole::ViewManager::newViewRequest)); connect(container, &Konsole::TabbedViewContainer::moveViewRequest, this, &Konsole::ViewManager::containerMoveViewRequest); connect(container, &Konsole::TabbedViewContainer::viewRemoved, this, &Konsole::ViewManager::viewDestroyed); connect(container, &Konsole::TabbedViewContainer::activeViewChanged, this, &Konsole::ViewManager::viewActivated); return container; } void ViewManager::containerMoveViewRequest(int index, int id, TabbedViewContainer *sourceTabbedContainer) { TabbedViewContainer *container = qobject_cast(sender()); SessionController *controller = qobject_cast(ViewProperties::propertiesById(id)); if (controller == nullptr) { return; } // do not move the last tab in a split view. if (sourceTabbedContainer != nullptr) { QPointer sourceContainer = qobject_cast(sourceTabbedContainer); if (_viewSplitter->containers().contains(sourceContainer)) { return; } else { ViewManager *sourceViewManager = sourceTabbedContainer->connectedViewManager(); // do not remove the last tab on the window if (qobject_cast(sourceViewManager->widget())->containers().size() > 1) { return; } } } createView(controller->session(), container, index); controller->session()->refresh(); } void ViewManager::setNavigationMethod(NavigationMethod method) { Q_ASSERT(_actionCollection); if (_actionCollection == nullptr) { return; } KActionCollection *collection = _actionCollection; // 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("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) { if ((!_viewSplitter.isNull()) && container == _viewSplitter->activeContainer()) { 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 TerminalDisplay *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 if (!_viewSplitter.isNull()) { updateDetachViewState(); } // The below causes the menus to be messed up // Only happens when using the tab bar close button // if (_pluggedController) // emit unplugController(_pluggedController); } TerminalDisplay *ViewManager::createTerminalDisplay(Session *session) { auto display = new TerminalDisplay(nullptr); display->setRandomSeed(session->sessionId() * 31); return display; } const ColorScheme *ViewManager::colorSchemeForProfile(const Profile::Ptr profile) { const ColorScheme *colorScheme = ColorSchemeManager::instance()-> findColorScheme(profile->colorScheme()); if (colorScheme == nullptr) { colorScheme = ColorSchemeManager::instance()->defaultColorScheme(); } Q_ASSERT(colorScheme); return colorScheme; } bool ViewManager::profileHasBlurEnabled(const Profile::Ptr profile) { return colorSchemeForProfile(profile)->blur(); } void ViewManager::applyProfileToView(TerminalDisplay *view, const Profile::Ptr profile) { Q_ASSERT(profile); emit updateWindowIcon(); // load color scheme ColorEntry table[TABLE_COLORS]; const ColorScheme *colorScheme = colorSchemeForProfile(profile); colorScheme->getColorTable(table, view->randomSeed()); view->setColorTable(table); view->setOpacity(colorScheme->opacity()); view->setWallpaper(colorScheme->wallpaper()); emit blurSettingChanged(colorScheme->blur()); // load font view->setAntialias(profile->antiAliasFonts()); view->setBoldIntense(profile->boldIntense()); view->setUseFontLineCharacters(profile->useFontLineCharacters()); view->setVTFont(profile->font()); // set scroll-bar position view->setScrollBarPosition(Enum::ScrollBarPositionEnum(profile->property(Profile::ScrollBarPosition))); view->setScrollFullPage(profile->property(Profile::ScrollFullPage)); // show hint about terminal size after resizing view->setShowTerminalSizeHint(profile->showTerminalSizeHint()); // terminal features view->setBlinkingCursorEnabled(profile->blinkingCursorEnabled()); view->setBlinkingTextEnabled(profile->blinkingTextEnabled()); view->setTripleClickMode(Enum::TripleClickModeEnum(profile->property(Profile::TripleClickMode))); view->setAutoCopySelectedText(profile->autoCopySelectedText()); view->setControlDrag(profile->property(Profile::CtrlRequiredForDrag)); view->setDropUrlsAsText(profile->property(Profile::DropUrlsAsText)); view->setBidiEnabled(profile->bidiRenderingEnabled()); view->setLineSpacing(profile->lineSpacing()); view->setTrimLeadingSpaces(profile->property(Profile::TrimLeadingSpacesInSelectedText)); view->setTrimTrailingSpaces(profile->property(Profile::TrimTrailingSpacesInSelectedText)); view->setOpenLinksByDirectClick(profile->property(Profile::OpenLinksByDirectClickEnabled)); view->setUrlHintsModifiers(profile->property(Profile::UrlHintsModifiers)); view->setReverseUrlHintsEnabled(profile->property(Profile::ReverseUrlHints)); view->setMiddleClickPasteMode(Enum::MiddleClickPasteModeEnum(profile->property(Profile::MiddleClickPasteMode))); view->setCopyTextAsHTML(profile->property(Profile::CopyTextAsHTML)); // margin/center view->setMargin(profile->property(Profile::TerminalMargin)); view->setCenterContents(profile->property(Profile::TerminalCenter)); // cursor shape view->setKeyboardCursorShape(Enum::CursorShapeEnum(profile->property(Profile::CursorShape))); // cursor color // an invalid QColor is used to inform the view widget to // draw the cursor using the default color( matching the text) view->setKeyboardCursorColor(profile->useCustomCursorColor() ? profile->customCursorColor() : QColor()); // word characters view->setWordCharacters(profile->wordCharacters()); // bell mode view->setBellMode(profile->property(Profile::BellMode)); // mouse wheel zoom view->setMouseWheelZoom(profile->mouseWheelZoomEnabled()); view->setAlternateScrolling(profile->property(Profile::AlternateScrolling)); } 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(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 = _viewSplitter->activeContainer(); if (container == nullptr) { return {}; } list.reserve(container->count()); for(int i = 0, end = container->count(); i < end; i++) { auto view = container->widget(i); ViewProperties *properties = container->viewProperties(view); Q_ASSERT(properties); list << properties; } return list; } void ViewManager::saveSessions(KConfigGroup &group) { // find all unique session restore IDs QList ids; QSet unique; int tab = 1; TabbedViewContainer *container = _viewSplitter->activeContainer(); // first: sessions in the active container, preserving the order Q_ASSERT(container); if (container == nullptr) { return; } ids.reserve(container->count()); TerminalDisplay *activeview = qobject_cast(container->currentWidget()); for (int i = 0, end = container->count(); i < end; i++) { TerminalDisplay *view = qobject_cast(container->widget(i)); Q_ASSERT(view); Session *session = _sessionMap[view]; ids << SessionManager::instance()->getRestoreId(session); unique.insert(session); if (view == activeview) { group.writeEntry("Active", tab); } tab++; } // second: all other sessions, in random order // we don't want to have sessions restored that are not connected foreach (Session *session, _sessionMap) { if (!unique.contains(session)) { ids << SessionManager::instance()->getRestoreId(session); unique.insert(session); } } group.writeEntry("Sessions", ids); } void ViewManager::restoreSessions(const KConfigGroup &group) { 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; } createView(session); if (!session->isRunning()) { session->run(); } if (tab++ == activeTab) { display = qobject_cast(activeView()); } } if (display != nullptr) { _viewSplitter->activeContainer()->setCurrentWidget(display); display->setFocus(Qt::OtherFocusReason); } if (ids.isEmpty()) { // Session file is unusable, start default Profile Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); Session *session = SessionManager::instance()->createSession(profile); createView(session); if (!session->isRunning()) { session->run(); } } } int ViewManager::sessionCount() { return _sessionMap.size(); } QStringList ViewManager::sessionList() { QStringList ids; QHash::const_iterator i; for (i = _sessionMap.constBegin(); i != _sessionMap.constEnd(); ++i) { ids.append(QString::number(i.value()->sessionId())); } return ids; } int ViewManager::currentSession() { QHash::const_iterator i; for (i = _sessionMap.constBegin(); i != _sessionMap.constEnd(); ++i) { if (i.key()->isVisible()) { return i.value()->sessionId(); } } return -1; } void ViewManager::setCurrentSession(int sessionId) { QHash::const_iterator i; for (i = _sessionMap.constBegin(); i != _sessionMap.constEnd(); ++i) { if (i.value()->sessionId() == sessionId) { TabbedViewContainer *container = _viewSplitter->activeContainer(); if (container != nullptr) { container->setCurrentWidget(i.key()); } } } } int ViewManager::newSession() { Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); Session *session = SessionManager::instance()->createSession(profile); session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); createView(session); session->run(); return session->sessionId(); } int ViewManager::newSession(const QString &profile) { const QList profilelist = ProfileManager::instance()->allProfiles(); Profile::Ptr profileptr = ProfileManager::instance()->defaultProfile(); for (const auto &i : profilelist) { if (i->name() == profile) { profileptr = i; break; } } Session *session = SessionManager::instance()->createSession(profileptr); session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); createView(session); session->run(); return session->sessionId(); } int ViewManager::newSession(const QString &profile, const QString &directory) { const QList profilelist = ProfileManager::instance()->allProfiles(); Profile::Ptr profileptr = ProfileManager::instance()->defaultProfile(); for (const auto &i : profilelist) { if (i->name() == profile) { profileptr = i; break; } } Session *session = SessionManager::instance()->createSession(profileptr); session->setInitialWorkingDirectory(directory); session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); createView(session); session->run(); return session->sessionId(); } QString ViewManager::defaultProfile() { return ProfileManager::instance()->defaultProfile()->name(); } QStringList ViewManager::profileList() { return ProfileManager::instance()->availableProfileNames(); } void ViewManager::nextSession() { nextView(); } void ViewManager::prevSession() { previousView(); } void ViewManager::moveSessionLeft() { moveActiveViewLeft(); } void ViewManager::moveSessionRight() { moveActiveViewRight(); } void ViewManager::setTabWidthToText(bool setTabWidthToText) { for(auto container : _viewSplitter->containers()) { container->tabBar()->setExpanding(!setTabWidthToText); container->tabBar()->update(); } } void ViewManager::setNavigationVisibility(NavigationVisibility navigationVisibility) { if (_navigationVisibility != navigationVisibility) { _navigationVisibility = navigationVisibility; for(auto *container : _viewSplitter->containers()) { container->setNavigationVisibility(navigationVisibility); } } } + +void ViewManager::setNavigationBehavior(int behavior) +{ + _newTabBehavior = static_cast(behavior); +} diff --git a/src/ViewManager.h b/src/ViewManager.h index 6d09f6a7..1b676621 100644 --- a/src/ViewManager.h +++ b/src/ViewManager.h @@ -1,421 +1,423 @@ /* 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 // Konsole #include "Profile.h" class KActionCollection; class KConfigGroup; namespace Konsole { class ColorScheme; class Session; class TerminalDisplay; class TabbedViewContainer; class SessionController; class ViewProperties; class ViewSplitter; /** * Manages the terminal display widgets in a Konsole window or part. * * When a view manager is created, it constructs a splitter widget ( accessed via * widget() ) to hold one or more view containers. Each view container holds * one or more terminal displays and a navigation widget ( eg. tabs or a list ) * to allow the user to navigate between the displays in that container. * * 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(Session *session); /** * Applies the view-specific settings associated with specified @p profile * to the terminal display @p view. */ void applyProfileToView(TerminalDisplay *view, const Profile::Ptr profile); /** * Return the main widget for the view manager which * holds all of the views managed by this ViewManager instance. */ 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 }; /** * This enum describes where newly created tab should be placed. */ enum NewTabBehavior { /** Put newly created tab at the end. */ PutNewTabAtTheEnd = 0, /** Put newly created tab right after current tab. */ PutNewTabAfterCurrentTab = 1 }; /** * 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); + void setNavigationBehavior(int behavior); 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); Q_SIGNALS: /** Emitted when the last view is removed from the view manager */ void empty(); /** Emitted when a session is detached from a view owned by this ViewManager */ void viewDetached(Session *session); /** * 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 the number of views containers changes. This is used to disable or * enable menu items which can only be used when there are one or multiple containers * visible. * * @param multipleViews True if there are multiple view containers open or false if there is * just a single view. */ void splitViewToggle(bool multipleViews); /** * Emitted when menu bar visibility changes because a profile that requires so is * activated. */ 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 newViewRequest(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. * @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 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 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 closeActiveContainer(); void closeOtherContainers(); void expandActiveContainer(); void shrinkActiveContainer(); // called when the "Detach View" menu item is selected void detachActiveView(); void updateDetachViewState(); // 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(QWidget *view); // 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 "Next View Container" shortcut is activated void nextContainer(); // called when the views in a container owned by this view manager // changes void containerViewsChanged(TabbedViewContainer *container); // called when a profile changes void profileChanged(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); // called when a SessionController gains focus void controllerChanged(SessionController *controller); // called when a ViewContainer requests a view be // moved void containerMoveViewRequest(int index, int id, TabbedViewContainer *sourceTabbedContainer); void detachView(TabbedViewContainer *container, QWidget *view); private: Q_DISABLE_COPY(ViewManager) void createView(Session *session, TabbedViewContainer *container, int index); static const ColorScheme *colorSchemeForProfile(const Profile::Ptr profile); 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(); // removes a container and emits appropriate signals void removeContainer(TabbedViewContainer *container); // creates a new terminal display // the 'session' is used so that the terminal display's random seed // 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); private: QPointer _viewSplitter; QPointer _pluggedController; QHash _sessionMap; KActionCollection *_actionCollection; NavigationMethod _navigationMethod; NavigationVisibility _navigationVisibility; + NewTabBehavior _newTabBehavior; QString _navigationStyleSheet; int _managerId; static int lastManagerId; }; } #endif