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

0001 /*
0002     SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // Own
0008 #include "MainWindow.h"
0009 
0010 // Qt
0011 #include <QMenu>
0012 #include <QMenuBar>
0013 #include <QMouseEvent>
0014 #include <QScreen>
0015 #include <QWindow>
0016 
0017 // KDE
0018 #include <KAcceleratorManager>
0019 #include <KActionCollection>
0020 #include <KActionMenu>
0021 #include <KColorSchemeManager>
0022 #include <KColorSchemeMenu>
0023 #include <KCrash>
0024 #include <KHamburgerMenu>
0025 #include <KIconUtils>
0026 #include <KLocalizedString>
0027 #include <KMessageBox>
0028 #include <KNotifyConfigWidget>
0029 #include <KShortcutsDialog>
0030 #include <KStandardAction>
0031 #include <KStandardGuiItem>
0032 #include <KToolBar>
0033 #include <KWindowConfig>
0034 #include <KWindowEffects>
0035 #include <KWindowSystem>
0036 #include <KXMLGUIFactory>
0037 
0038 #if HAVE_X11
0039 #include <KX11Extras>
0040 #endif
0041 
0042 // Konsole
0043 #include "BookmarkHandler.h"
0044 #include "KonsoleSettings.h"
0045 #include "ViewManager.h"
0046 #include "WindowSystemInfo.h"
0047 
0048 #include "profile/ProfileList.h"
0049 #include "profile/ProfileManager.h"
0050 
0051 #include "session/Session.h"
0052 #include "session/SessionController.h"
0053 #include "session/SessionManager.h"
0054 
0055 #include "settings/ConfigurationDialog.h"
0056 #include "settings/GeneralSettings.h"
0057 #include "settings/MemorySettings.h"
0058 #include "settings/ProfileSettings.h"
0059 #include "settings/TabBarSettings.h"
0060 #include "settings/TemporaryFilesSettings.h"
0061 #include "settings/ThumbnailsSettings.h"
0062 
0063 #include "terminalDisplay/TerminalDisplay.h"
0064 #include "widgets/ViewContainer.h"
0065 
0066 #include <konsoledebug.h>
0067 
0068 using namespace Konsole;
0069 
0070 MainWindow::MainWindow()
0071     : KXmlGuiWindow()
0072     , _viewManager(nullptr)
0073     , _bookmarkHandler(nullptr)
0074     , _toggleMenuBarAction(nullptr)
0075     , _newTabMenuAction(nullptr)
0076     , _pluggedController(nullptr)
0077 {
0078     // Set the WA_NativeWindow attribute to force the creation of the QWindow.
0079     // Without this QWidget::windowHandle() returns 0.
0080     // See https://phabricator.kde.org/D23108
0081     setAttribute(Qt::WA_NativeWindow);
0082 
0083     updateUseTransparency();
0084 
0085     // create actions for menus
0086     setupActions();
0087 
0088     // create view manager
0089     _viewManager = new ViewManager(this, actionCollection());
0090     connect(_viewManager, &Konsole::ViewManager::empty, this, &QWidget::close);
0091     // QueuedConnection so that KHamburgerMenu showed up properly on the first tab
0092     connect(_viewManager, &Konsole::ViewManager::activeViewChanged, this, &Konsole::MainWindow::activeViewChanged, Qt::QueuedConnection);
0093     connect(_viewManager, &Konsole::ViewManager::unplugController, this, &Konsole::MainWindow::disconnectController);
0094     connect(_viewManager, &Konsole::ViewManager::viewPropertiesChanged, bookmarkHandler(), &Konsole::BookmarkHandler::setViews);
0095     connect(_viewManager, &Konsole::ViewManager::blurSettingChanged, this, &Konsole::MainWindow::setBlur);
0096 
0097     connect(_viewManager, &Konsole::ViewManager::updateWindowIcon, this, &Konsole::MainWindow::updateWindowIcon);
0098     connect(_viewManager, &Konsole::ViewManager::newViewWithProfileRequest, this, &Konsole::MainWindow::newFromProfile);
0099     connect(_viewManager, &Konsole::ViewManager::newViewRequest, this, &Konsole::MainWindow::newTab);
0100     connect(_viewManager, &Konsole::ViewManager::terminalsDetached, this, &Konsole::MainWindow::terminalsDetached);
0101     connect(_viewManager, &Konsole::ViewManager::activationRequest, this, &Konsole::MainWindow::activationRequest);
0102 
0103     setCentralWidget(_viewManager->widget());
0104 
0105     // disable automatically generated accelerators in top-level
0106     // menu items - to avoid conflicting with Alt+[Letter] shortcuts
0107     // in terminal applications
0108     KAcceleratorManager::setNoAccel(menuBar());
0109 
0110     constexpr KXmlGuiWindow::StandardWindowOptions guiOpts = ToolBar | Keys | Save | Create;
0111     const QString xmlFile = componentName() + QLatin1String("ui.rc"); // Typically "konsoleui.rc"
0112     // The "Create" flag will make it call createGUI()
0113     setupGUI(guiOpts, xmlFile);
0114 
0115     // remember the original menu accelerators for later use
0116     rememberMenuAccelerators();
0117 
0118     // replace standard shortcuts which cannot be used in a terminal
0119     // emulator (as they are reserved for use by terminal applications)
0120     correctStandardShortcuts();
0121 
0122     setProfileList(new ProfileList(true, this));
0123 
0124     // this must come at the end
0125     applyKonsoleSettings();
0126     connect(KonsoleSettings::self(), &Konsole::KonsoleSettings::configChanged, this, &Konsole::MainWindow::applyKonsoleSettings);
0127 
0128     KCrash::initialize();
0129 }
0130 
0131 bool MainWindow::wasWindowGeometrySaved() const
0132 {
0133     KSharedConfigPtr konsoleConfig = KSharedConfig::openConfig(QStringLiteral("konsolerc"));
0134     KConfigGroup cg = konsoleConfig->group(QStringLiteral("MainWindow"));
0135     if (!cg.exists()) { // First run, no existing konsolerc?
0136         return false;
0137     }
0138 
0139     return KWindowConfig::hasSavedWindowSize(cg) || KWindowConfig::hasSavedWindowPosition(cg);
0140 }
0141 
0142 void MainWindow::updateUseTransparency()
0143 {
0144     if (!WindowSystemInfo::HAVE_TRANSPARENCY) {
0145         return;
0146     }
0147 
0148     bool useTranslucency = WindowSystemInfo::compositingActive();
0149 
0150     setAttribute(Qt::WA_TranslucentBackground, useTranslucency);
0151     setAttribute(Qt::WA_NoSystemBackground, false);
0152     WindowSystemInfo::HAVE_TRANSPARENCY = useTranslucency;
0153 }
0154 
0155 void MainWindow::activationRequest(const QString &xdgActivationToken)
0156 {
0157     KWindowSystem::setCurrentXdgActivationToken(xdgActivationToken);
0158 
0159     if (KWindowSystem::isPlatformX11()) {
0160 #if HAVE_X11
0161         KX11Extras::forceActiveWindow(winId());
0162 #endif
0163     } else {
0164         KWindowSystem::activateWindow(windowHandle());
0165     }
0166 }
0167 
0168 void MainWindow::rememberMenuAccelerators()
0169 {
0170     const QList<QAction *> actions = menuBar()->actions();
0171     for (QAction *menuItem : actions) {
0172         QString itemText = menuItem->text();
0173         menuItem->setData(itemText);
0174     }
0175 }
0176 
0177 // remove accelerators for standard menu items (eg. &File, &View, &Edit)
0178 // etc. which are defined in kdelibs/kdeui/xmlgui/ui_standards.rc, again,
0179 // to avoid conflicting with Alt+[Letter] terminal shortcuts
0180 //
0181 // TODO - Modify XMLGUI so that it allows the text for standard actions
0182 // defined in ui_standards.rc to be re-defined in the local application
0183 // XMLGUI file (konsoleui.rc in this case) - the text for standard items
0184 // can then be redefined there to exclude the standard accelerators
0185 void MainWindow::removeMenuAccelerators()
0186 {
0187     const QList<QAction *> actions = menuBar()->actions();
0188     for (QAction *menuItem : actions) {
0189         menuItem->setText(menuItem->text().replace(QLatin1Char('&'), QString()));
0190     }
0191 }
0192 
0193 void MainWindow::restoreMenuAccelerators()
0194 {
0195     const QList<QAction *> actions = menuBar()->actions();
0196     for (QAction *menuItem : actions) {
0197         QString itemText = menuItem->data().toString();
0198         menuItem->setText(itemText);
0199     }
0200 }
0201 
0202 void MainWindow::correctStandardShortcuts()
0203 {
0204     // replace F1 shortcut for help contents
0205     QAction *helpAction = actionCollection()->action(QStringLiteral("help_contents"));
0206     if (helpAction != nullptr) {
0207         actionCollection()->setDefaultShortcut(helpAction, QKeySequence());
0208     }
0209 }
0210 
0211 ViewManager *MainWindow::viewManager() const
0212 {
0213     return _viewManager;
0214 }
0215 
0216 void MainWindow::disconnectController(SessionController *controller)
0217 {
0218     disconnect(controller, &Konsole::SessionController::titleChanged, this, &Konsole::MainWindow::activeViewTitleChanged);
0219     disconnect(controller, &Konsole::SessionController::rawTitleChanged, this, &Konsole::MainWindow::updateWindowCaption);
0220     disconnect(controller, &Konsole::SessionController::iconChanged, this, &Konsole::MainWindow::updateWindowIcon);
0221 
0222     if (auto view = controller->view()) {
0223         view->removeEventFilter(this);
0224     }
0225 
0226     // KXmlGuiFactory::removeClient() will try to access actions associated
0227     // with the controller internally, which may not be valid after the controller
0228     // itself is no longer valid (after the associated session and or view have
0229     // been destroyed)
0230     if (controller->isValid()) {
0231         guiFactory()->removeClient(controller);
0232     }
0233 
0234     if (_pluggedController == controller) {
0235         _pluggedController = nullptr;
0236     }
0237 }
0238 
0239 void MainWindow::activeViewChanged(SessionController *controller)
0240 {
0241     if (!SessionManager::instance()->sessionProfile(controller->session())) {
0242         return;
0243     }
0244     // associate bookmark menu with current session
0245     bookmarkHandler()->setActiveView(controller);
0246     disconnect(bookmarkHandler(), &Konsole::BookmarkHandler::openUrl, nullptr, nullptr);
0247     connect(bookmarkHandler(), &Konsole::BookmarkHandler::openUrl, controller, &Konsole::SessionController::openUrl);
0248 
0249     if (!_pluggedController.isNull()) {
0250         disconnectController(_pluggedController);
0251     }
0252 
0253     Q_ASSERT(controller);
0254     _pluggedController = controller;
0255     _pluggedController->view()->installEventFilter(this);
0256 
0257     setBlur(ViewManager::profileHasBlurEnabled(SessionManager::instance()->sessionProfile(_pluggedController->session())));
0258 
0259     // listen for title changes from the current session
0260     connect(controller, &Konsole::SessionController::titleChanged, this, &Konsole::MainWindow::activeViewTitleChanged);
0261     connect(controller, &Konsole::SessionController::rawTitleChanged, this, &Konsole::MainWindow::updateWindowCaption);
0262     connect(controller, &Konsole::SessionController::iconChanged, this, &Konsole::MainWindow::updateWindowIcon);
0263 
0264     // to prevent shortcuts conflict
0265     if (auto hamburgerMenu = _hamburgerMenu->menu()) {
0266         hamburgerMenu->clear();
0267     }
0268 
0269     controller->setShowMenuAction(_toggleMenuBarAction);
0270     guiFactory()->addClient(controller);
0271 
0272     // update session title to match newly activated session
0273     activeViewTitleChanged(controller);
0274 
0275     // Update window icon to newly activated session's icon
0276     updateWindowIcon();
0277 
0278     for (IKonsolePlugin *plugin : _plugins) {
0279         plugin->activeViewChanged(controller, this);
0280     }
0281 }
0282 
0283 void MainWindow::activeViewTitleChanged(ViewProperties *properties)
0284 {
0285     Q_UNUSED(properties)
0286     updateWindowCaption();
0287 }
0288 
0289 void MainWindow::updateWindowCaption()
0290 {
0291     if (_pluggedController.isNull()) {
0292         return;
0293     }
0294 
0295     const QString &title = _pluggedController->title();
0296     const QString &userTitle = _pluggedController->userTitle();
0297 
0298     // use tab title as caption by default
0299     QString caption = title;
0300 
0301     // use window title as caption when this setting is enabled
0302     // if the userTitle is empty, use a blank space (using an empty string
0303     // removes the dash — before the application name; leaving the dash
0304     // looks better)
0305     if (KonsoleSettings::showWindowTitleOnTitleBar()) {
0306         !userTitle.isEmpty() ? caption = userTitle : caption = QStringLiteral(" ");
0307     }
0308 
0309     setCaption(caption);
0310 }
0311 
0312 void MainWindow::updateWindowIcon()
0313 {
0314     if ((!_pluggedController.isNull()) && !_pluggedController->icon().isNull()) {
0315         setWindowIcon(_pluggedController->icon());
0316     }
0317 }
0318 
0319 void MainWindow::setupActions()
0320 {
0321     KActionCollection *collection = actionCollection();
0322 
0323     // File Menu
0324     _newTabMenuAction = new KActionMenu(QIcon::fromTheme(QStringLiteral("tab-new")), i18nc("@action:inmenu", "&New Tab"), collection);
0325     collection->setDefaultShortcut(_newTabMenuAction, Konsole::ACCEL | Qt::Key_T);
0326     collection->setShortcutsConfigurable(_newTabMenuAction, true);
0327     _newTabMenuAction->setAutoRepeat(false);
0328     connect(_newTabMenuAction, &KActionMenu::triggered, this, &MainWindow::newTab);
0329     collection->addAction(QStringLiteral("new-tab"), _newTabMenuAction);
0330     collection->setShortcutsConfigurable(_newTabMenuAction, true);
0331 
0332     QAction *menuAction = collection->addAction(QStringLiteral("clone-tab"));
0333     menuAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-duplicate")));
0334     menuAction->setText(i18nc("@action:inmenu", "&Clone Tab"));
0335     collection->setDefaultShortcut(menuAction, QKeySequence());
0336     menuAction->setAutoRepeat(false);
0337     connect(menuAction, &QAction::triggered, this, &Konsole::MainWindow::cloneTab);
0338 
0339     menuAction = collection->addAction(QStringLiteral("new-window"));
0340     menuAction->setIcon(QIcon::fromTheme(QStringLiteral("window-new")));
0341     menuAction->setText(i18nc("@action:inmenu", "New &Window"));
0342     collection->setDefaultShortcut(menuAction, Konsole::ACCEL | Qt::Key_N);
0343     menuAction->setAutoRepeat(false);
0344     connect(menuAction, &QAction::triggered, this, &Konsole::MainWindow::newWindow);
0345 
0346     menuAction = collection->addAction(QStringLiteral("close-window"));
0347     menuAction->setIcon(QIcon::fromTheme(QStringLiteral("window-close")));
0348     menuAction->setText(i18nc("@action:inmenu", "Close Window"));
0349     collection->setDefaultShortcut(menuAction, Konsole::ACCEL | Qt::Key_Q);
0350     connect(menuAction, &QAction::triggered, this, &Konsole::MainWindow::close);
0351 
0352     // Bookmark Menu
0353     KActionMenu *bookmarkMenu = new KActionMenu(i18nc("@title:menu", "&Bookmarks"), collection);
0354     bookmarkMenu->setPopupMode(QToolButton::InstantPopup); // Don't require press+hold
0355     _bookmarkHandler = new BookmarkHandler(collection, bookmarkMenu->menu(), true, this);
0356     collection->addAction(QStringLiteral("bookmark"), bookmarkMenu);
0357     connect(_bookmarkHandler, &Konsole::BookmarkHandler::openUrls, this, &Konsole::MainWindow::openUrls);
0358 
0359     // Settings Menu
0360     _toggleMenuBarAction = KStandardAction::showMenubar(menuBar(), &QMenuBar::setVisible, collection);
0361     collection->setDefaultShortcut(_toggleMenuBarAction, Konsole::ACCEL | Qt::Key_M);
0362     connect(_toggleMenuBarAction, &QAction::triggered, [=] {
0363         // Remove menubar icons set for the hamburger menu, so they don't override
0364         // the text when they appear in the in-window menubar
0365         collection->action(QStringLiteral("bookmark"))->setIcon(QIcon());
0366         static_cast<QMenu *>(factory()->container(QStringLiteral("plugins"), this))->setIcon(QIcon());
0367     });
0368 
0369     // Set up themes
0370     auto *manager = new KColorSchemeManager(actionCollection());
0371     manager->setAutosaveChanges(true);
0372     KActionMenu *selectionMenu = KColorSchemeMenu::createMenu(manager, this);
0373     auto winColorSchemeMenu = new QAction(this);
0374     winColorSchemeMenu->setMenu(selectionMenu->menu());
0375     winColorSchemeMenu->menu()->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-color")));
0376     winColorSchemeMenu->menu()->setTitle(i18n("&Window Color Scheme"));
0377     actionCollection()->addAction(QStringLiteral("window-colorscheme-menu"), winColorSchemeMenu);
0378 
0379     // Full Screen
0380     menuAction = KStandardAction::fullScreen(this, &MainWindow::viewFullScreen, this, collection);
0381     collection->setDefaultShortcut(menuAction, Qt::Key_F11);
0382 
0383     KStandardAction::configureNotifications(this, &MainWindow::configureNotifications, collection);
0384     KStandardAction::keyBindings(this, &MainWindow::showShortcutsDialog, collection);
0385     KStandardAction::preferences(this, &MainWindow::showSettingsDialog, collection);
0386 
0387     menuAction = collection->addAction(QStringLiteral("manage-profiles"));
0388     menuAction->setText(i18nc("@action:inmenu", "Manage Profiles..."));
0389     menuAction->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
0390     connect(menuAction, &QAction::triggered, this, &Konsole::MainWindow::showManageProfilesDialog);
0391 
0392     // Set up an shortcut-only action for activating menu bar.
0393     menuAction = collection->addAction(QStringLiteral("activate-menu"));
0394     menuAction->setText(i18nc("@item", "Activate Menu"));
0395     collection->setDefaultShortcut(menuAction, Konsole::ACCEL | Qt::Key_F10);
0396     connect(menuAction, &QAction::triggered, this, &Konsole::MainWindow::activateMenuBar);
0397 
0398     auto action = collection->addAction(QStringLiteral("save-layout"));
0399     action->setEnabled(true);
0400     action->setText(i18nc("@action:inmenu", "Save Tab Layout..."));
0401     connect(action, &QAction::triggered, this, [this]() {
0402         if (viewManager()) {
0403             viewManager()->saveLayoutFile();
0404         }
0405     });
0406 
0407     action = collection->addAction(QStringLiteral("load-layout"));
0408     action->setEnabled(true);
0409     action->setText(i18nc("@action:inmenu", "Load Tab Layout..."));
0410     connect(action, &QAction::triggered, this, [this]() {
0411         if (viewManager()) {
0412             viewManager()->loadLayoutFile();
0413         }
0414     });
0415 
0416     // Hamburger menu for when the menubar is hidden
0417     _hamburgerMenu = KStandardAction::hamburgerMenu(nullptr, nullptr, collection);
0418     _hamburgerMenu->setShowMenuBarAction(_toggleMenuBarAction);
0419     _hamburgerMenu->setMenuBar(menuBar());
0420     connect(_hamburgerMenu, &KHamburgerMenu::aboutToShowMenu, this, &MainWindow::updateHamburgerMenu);
0421 }
0422 
0423 void MainWindow::updateHamburgerMenu()
0424 {
0425     KActionCollection *collection = actionCollection();
0426     KActionCollection *controllerCollection = _pluggedController->actionCollection();
0427 
0428     QMenu *menu = _hamburgerMenu->menu();
0429     if (!menu) {
0430         menu = new QMenu(widget());
0431         _hamburgerMenu->setMenu(menu);
0432     } else {
0433         menu->clear();
0434     }
0435 
0436     menu->addAction(collection->action(QStringLiteral("new-window")));
0437     menu->addAction(collection->action(QStringLiteral("new-tab")));
0438     menu->addAction(controllerCollection->action(QStringLiteral("file_save_as")));
0439 
0440     menu->addSeparator();
0441 
0442     menu->addAction(controllerCollection->action(QStringLiteral("edit_copy")));
0443     menu->addAction(controllerCollection->action(QStringLiteral("edit_paste")));
0444     menu->addAction(controllerCollection->action(QStringLiteral("edit_find")));
0445 
0446     menu->addSeparator();
0447 
0448     menu->addAction(collection->action(KStandardAction::name(KStandardAction::FullScreen)));
0449     menu->addAction(collection->action(QStringLiteral("split-view")));
0450     menu->addAction(controllerCollection->action(QStringLiteral("clear-history-and-reset")));
0451     menu->addAction(controllerCollection->action(QStringLiteral("enlarge-font")));
0452     menu->addAction(controllerCollection->action(QStringLiteral("reset-font-size")));
0453     menu->addAction(controllerCollection->action(QStringLiteral("shrink-font")));
0454 
0455     menu->addSeparator();
0456 
0457     menu->addAction(controllerCollection->action(QStringLiteral("edit-current-profile")));
0458     menu->addAction(controllerCollection->action(QStringLiteral("switch-profile")));
0459 
0460     menu->addSeparator();
0461 
0462     auto monitorMenu =
0463         menu->addMenu(QIcon::fromTheme(QStringLiteral("visibility")), static_cast<QMenu *>(factory()->container(QStringLiteral("view"), nullptr))->title());
0464     monitorMenu->addAction(controllerCollection->action(QStringLiteral("monitor-silence")));
0465     monitorMenu->addAction(controllerCollection->action(QStringLiteral("monitor-activity")));
0466     monitorMenu->addAction(controllerCollection->action(QStringLiteral("monitor-process-finish")));
0467     menu->addMenu(monitorMenu);
0468     _hamburgerMenu->hideActionsOf(monitorMenu);
0469 
0470     auto bookmarkMenu = collection->action(QStringLiteral("bookmark"));
0471     bookmarkMenu->setIcon(QIcon::fromTheme(QStringLiteral("bookmarks"))); // Icon will be removed again when the menu bar is enabled.
0472     menu->addAction(bookmarkMenu);
0473 
0474     auto pluginsMenu = static_cast<QMenu *>(factory()->container(QStringLiteral("plugins"), this));
0475     pluginsMenu->setIcon(QIcon::fromTheme(QStringLiteral("plugins"))); // Icon will be removed again when the menu bar is enabled.
0476     menu->addMenu(pluginsMenu);
0477 
0478     auto configureMenu =
0479         menu->addMenu(QIcon::fromTheme(QStringLiteral("configure")), static_cast<QMenu *>(factory()->container(QStringLiteral("settings"), nullptr))->title());
0480     configureMenu->addAction(toolBarMenuAction());
0481     configureMenu->addSeparator();
0482     configureMenu->addAction(collection->action(KStandardAction::name(KStandardAction::SwitchApplicationLanguage)));
0483     configureMenu->addAction(collection->action(KStandardAction::name(KStandardAction::KeyBindings)));
0484     configureMenu->addAction(collection->action(KStandardAction::name(KStandardAction::ConfigureToolbars)));
0485     configureMenu->addAction(collection->action(KStandardAction::name(KStandardAction::ConfigureNotifications)));
0486     configureMenu->addAction(collection->action(KStandardAction::name(KStandardAction::Preferences)));
0487     _hamburgerMenu->hideActionsOf(configureMenu);
0488 
0489     _hamburgerMenu->hideActionsOf(toolBar());
0490 }
0491 
0492 void MainWindow::viewFullScreen(bool fullScreen)
0493 {
0494     if (fullScreen) {
0495         setWindowState(windowState() | Qt::WindowFullScreen);
0496     } else {
0497         setWindowState(windowState() & ~Qt::WindowFullScreen);
0498     }
0499 }
0500 
0501 void MainWindow::applyMainWindowSettings(const KConfigGroup &config)
0502 {
0503     KMainWindow::applyMainWindowSettings(config);
0504 
0505     // Override the menubar state from the config file
0506     if (_windowArgsMenuBarVisible.enabled) {
0507         menuBar()->setVisible(_windowArgsMenuBarVisible.showMenuBar);
0508     }
0509 
0510     _toggleMenuBarAction->setChecked(menuBar()->isVisibleTo(this));
0511 }
0512 
0513 BookmarkHandler *MainWindow::bookmarkHandler() const
0514 {
0515     return _bookmarkHandler;
0516 }
0517 
0518 void MainWindow::setProfileList(ProfileList *list)
0519 {
0520     profileListChanged(list->actions());
0521 
0522     connect(list, &Konsole::ProfileList::profileSelected, this, &MainWindow::newFromProfile);
0523     connect(list, &Konsole::ProfileList::actionsChanged, this, &Konsole::MainWindow::profileListChanged);
0524 }
0525 
0526 void MainWindow::profileListChanged(const QList<QAction *> &sessionActions)
0527 {
0528     // Update the 'New Tab' KActionMenu
0529     _newTabMenuAction->menu()->clear();
0530     for (QAction *sessionAction : sessionActions) {
0531         _newTabMenuAction->menu()->addAction(sessionAction);
0532 
0533         auto setActionFontBold = [sessionAction](bool isBold) {
0534             QFont actionFont = sessionAction->font();
0535             actionFont.setBold(isBold);
0536             sessionAction->setFont(actionFont);
0537         };
0538 
0539         Profile::Ptr profile = ProfileManager::instance()->defaultProfile();
0540         if (profile && profile->name() == sessionAction->text().remove(QLatin1Char('&'))) {
0541             QIcon icon = KIconUtils::addOverlay(QIcon::fromTheme(profile->icon()), QIcon::fromTheme(QStringLiteral("emblem-favorite")), Qt::BottomRightCorner);
0542             sessionAction->setIcon(icon);
0543             setActionFontBold(true);
0544         } else {
0545             setActionFontBold(false);
0546         }
0547     }
0548 }
0549 
0550 QString MainWindow::activeSessionDir() const
0551 {
0552     if (!_pluggedController.isNull()) {
0553         if (Session *session = _pluggedController->session()) {
0554             // For new tabs to get the correct working directory,
0555             // force the updating of the currentWorkingDirectory.
0556             session->getDynamicTitle();
0557         }
0558         return _pluggedController->currentDir();
0559     }
0560     return QString();
0561 }
0562 
0563 void MainWindow::openUrls(const QList<QUrl> &urls)
0564 {
0565     Profile::Ptr defaultProfile = ProfileManager::instance()->defaultProfile();
0566 
0567     for (const auto &url : urls) {
0568         if (url.isLocalFile()) {
0569             createSession(defaultProfile, url.path());
0570         } else if (url.scheme() == QLatin1String("ssh")) {
0571             createSSHSession(defaultProfile, url);
0572         }
0573     }
0574 }
0575 
0576 void MainWindow::newTab()
0577 {
0578     Profile::Ptr defaultProfile = ProfileManager::instance()->defaultProfile();
0579     createSession(defaultProfile, activeSessionDir());
0580 }
0581 
0582 void MainWindow::setPluginsActions(const QList<QAction *> &actions)
0583 {
0584     _pluginsActions = actions;
0585     unplugActionList(QStringLiteral("plugin-submenu"));
0586     plugActionList(QStringLiteral("plugin-submenu"), _pluginsActions);
0587 }
0588 
0589 void MainWindow::addPlugin(IKonsolePlugin *plugin)
0590 {
0591     Q_ASSERT(std::find(_plugins.cbegin(), _plugins.cend(), plugin) == _plugins.cend());
0592     _plugins.push_back(plugin);
0593 }
0594 
0595 void MainWindow::cloneTab()
0596 {
0597     Q_ASSERT(_pluggedController);
0598 
0599     Session *session = _pluggedController->session();
0600     Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);
0601     if (profile) {
0602         createSession(profile, activeSessionDir());
0603     } else {
0604         // something must be wrong: every session should be associated with profile
0605         Q_ASSERT(false);
0606         newTab();
0607     }
0608 }
0609 
0610 Session *MainWindow::createSession(Profile::Ptr profile, const QString &directory)
0611 {
0612     if (!profile) {
0613         profile = ProfileManager::instance()->defaultProfile();
0614     }
0615 
0616     const QString newSessionDirectory = profile->startInCurrentSessionDir() ? directory : QString();
0617     Session *session = _viewManager->createSession(profile, newSessionDirectory);
0618 
0619     // create view before starting the session process so that the session
0620     // doesn't suffer a change in terminal size right after the session
0621     // starts.  Some applications such as GNU Screen and Midnight Commander
0622     // don't like this happening
0623     auto newView = _viewManager->createView(session);
0624     _viewManager->activeContainer()->addView(newView);
0625 
0626     _viewManager->activeViewController()->actionCollection()->addActions({_hamburgerMenu});
0627 
0628     return session;
0629 }
0630 
0631 Session *MainWindow::createSSHSession(Profile::Ptr profile, const QUrl &url)
0632 {
0633     if (!profile) {
0634         profile = ProfileManager::instance()->defaultProfile();
0635     }
0636 
0637     Session *session = SessionManager::instance()->createSession(profile);
0638 
0639     QString sshCommand = QStringLiteral("ssh ");
0640     if (url.port() > -1) {
0641         sshCommand += QStringLiteral("-p %1 ").arg(url.port());
0642     }
0643     if (!url.userName().isEmpty()) {
0644         sshCommand += (url.userName() + QLatin1Char('@'));
0645     }
0646     if (!url.host().isEmpty()) {
0647         sshCommand += url.host();
0648     }
0649 
0650     session->sendTextToTerminal(sshCommand, QLatin1Char('\r'));
0651 
0652     // create view before starting the session process so that the session
0653     // doesn't suffer a change in terminal size right after the session
0654     // starts.  some applications such as GNU Screen and Midnight Commander
0655     // don't like this happening
0656     auto newView = _viewManager->createView(session);
0657     _viewManager->activeContainer()->addView(newView);
0658     return session;
0659 }
0660 
0661 void MainWindow::setFocus()
0662 {
0663     _viewManager->activeView()->setFocus();
0664 }
0665 
0666 void MainWindow::newWindow()
0667 {
0668     Profile::Ptr defaultProfile = ProfileManager::instance()->defaultProfile();
0669     Q_EMIT newWindowRequest(defaultProfile, activeSessionDir());
0670 }
0671 
0672 bool MainWindow::queryClose()
0673 {
0674     // Do not ask for confirmation during log out and power off
0675     // TODO: rework the dealing of this case to make it has its own confirmation
0676     // dialog.
0677     if (qApp->isSavingSession()) {
0678         return true;
0679     }
0680 
0681     // Check what processes are running, excluding the shell
0682     QStringList processesRunning;
0683     // Need to make a local copy so the begin() and end() point to the same QList
0684     const QList<Session *> sessionList = _viewManager->sessions();
0685     const QSet<Session *> uniqueSessions(sessionList.begin(), sessionList.end());
0686 
0687     for (Session *session : uniqueSessions) {
0688         if ((session == nullptr) || !session->isForegroundProcessActive()) {
0689             continue;
0690         }
0691 
0692         const QString defaultProc = session->program().split(QLatin1Char('/')).last();
0693         const QString currentProc = session->foregroundProcessName().split(QLatin1Char('/')).last();
0694 
0695         if (currentProc.isEmpty()) {
0696             continue;
0697         }
0698 
0699         if (defaultProc != currentProc) {
0700             processesRunning.append(currentProc);
0701         }
0702     }
0703 
0704     // Get number of open tabs
0705     const int openTabs = _viewManager->viewProperties().count();
0706 
0707     // If no processes running (except the shell) and no extra tabs, just close
0708     if (processesRunning.count() == 0 && openTabs < 2) {
0709         return true;
0710     }
0711 
0712     // NOTE: Some, if not all, of the below KWindowSystem calls are only
0713     //       implemented under x11 (KDE4.8 kdelibs/kdeui/windowmanagement).
0714 
0715 #if HAVE_X11
0716     // make sure the window is shown on current desktop and is not minimized
0717     KX11Extras::setOnDesktop(winId(), KX11Extras::currentDesktop());
0718 #endif
0719     int result;
0720 
0721     if (!processesRunning.isEmpty()) {
0722         if (openTabs == 1) {
0723             result = KMessageBox::warningTwoActionsList(this,
0724                                                         i18ncp("@info",
0725                                                                "There is a process running in this window. "
0726                                                                "Do you still want to quit?",
0727                                                                "There are %1 processes running in this window. "
0728                                                                "Do you still want to quit?",
0729                                                                processesRunning.count()),
0730                                                         processesRunning,
0731                                                         i18nc("@title", "Confirm Close"),
0732                                                         KGuiItem(i18nc("@action:button", "Close &Window"), QStringLiteral("window-close")),
0733                                                         KStandardGuiItem::cancel(),
0734                                                         // don't ask again name is wrong but I can't update.
0735                                                         // this is not about tabs anymore. it's about empty tabs *or* splits.
0736                                                         QStringLiteral("CloseAllTabs"));
0737             if (result == KMessageBox::SecondaryAction) // SecondaryAction is equal to cancel closing
0738                 result = KMessageBox::Cancel;
0739         } else {
0740             result = KMessageBox::warningTwoActionsCancelList(this,
0741                                                               i18ncp("@info",
0742                                                                      "There is a process running in this window. "
0743                                                                      "Do you still want to quit?",
0744                                                                      "There are %1 processes running in this window. "
0745                                                                      "Do you still want to quit?",
0746                                                                      processesRunning.count()),
0747                                                               processesRunning,
0748                                                               i18nc("@title", "Confirm Close"),
0749                                                               KGuiItem(i18nc("@action:button", "Close &Window"), QStringLiteral("window-close")),
0750                                                               KGuiItem(i18nc("@action:button", "Close Current &Tab"), QStringLiteral("tab-close")),
0751                                                               KStandardGuiItem::cancel(),
0752                                                               // don't ask again name is wrong but I can't update.
0753                                                               // this is not about tabs anymore. it's about empty tabs *or* splits.
0754                                                               QStringLiteral("CloseAllTabs"));
0755         }
0756     } else {
0757         result = KMessageBox::warningTwoActionsCancel(this,
0758                                                       i18nc("@info",
0759                                                             "There are %1 open terminals in this window. "
0760                                                             "Do you still want to quit?",
0761                                                             openTabs),
0762                                                       i18nc("@title", "Confirm Close"),
0763                                                       KGuiItem(i18nc("@action:button", "Close &Window"), QStringLiteral("window-close")),
0764                                                       KGuiItem(i18nc("@action:button", "Close Current &Tab"), QStringLiteral("tab-close")),
0765                                                       KStandardGuiItem::cancel(),
0766                                                       // don't ask again name is wrong but I can't update.
0767                                                       // this is not about tabs anymore. it's about empty tabs *or* splits.
0768                                                       QStringLiteral("CloseAllEmptyTabs"));
0769     }
0770 
0771     switch (result) {
0772     case KMessageBox::PrimaryAction:
0773         return true;
0774     case KMessageBox::SecondaryAction:
0775         if ((!_pluggedController.isNull()) && (!_pluggedController->session().isNull())) {
0776             if (!(_pluggedController->session()->closeInNormalWay())) {
0777                 if (_pluggedController->confirmForceClose()) {
0778                     _pluggedController->session()->closeInForceWay();
0779                 }
0780             }
0781         }
0782         return false;
0783     case KMessageBox::Cancel:
0784         return false;
0785     }
0786 
0787     return true;
0788 }
0789 
0790 void MainWindow::saveProperties(KConfigGroup &group)
0791 {
0792     _viewManager->saveSessions(group);
0793 }
0794 
0795 void MainWindow::readProperties(const KConfigGroup &group)
0796 {
0797     _viewManager->restoreSessions(group);
0798 }
0799 
0800 void MainWindow::saveGlobalProperties(KConfig *config)
0801 {
0802     SessionManager::instance()->saveSessions(config);
0803 }
0804 
0805 void MainWindow::readGlobalProperties(KConfig *config)
0806 {
0807     SessionManager::instance()->restoreSessions(config);
0808 }
0809 
0810 void MainWindow::syncActiveShortcuts(KActionCollection *dest, const KActionCollection *source)
0811 {
0812     const QList<QAction *> actionsList = source->actions();
0813     for (QAction *qAction : actionsList) {
0814         if (QAction *destQAction = dest->action(qAction->objectName())) {
0815             destQAction->setShortcut(qAction->shortcut());
0816         }
0817     }
0818 }
0819 
0820 void MainWindow::showShortcutsDialog()
0821 {
0822     KShortcutsDialog dialog(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsDisallowed, this);
0823 
0824     // add actions from this window and the current session controller
0825     const QList<KXMLGUIClient *> clientsList = guiFactory()->clients();
0826     for (KXMLGUIClient *client : clientsList) {
0827         dialog.addCollection(client->actionCollection());
0828     }
0829 
0830     if (dialog.configure()) {
0831         // sync shortcuts for non-session actions (defined in "konsoleui.rc") in other main windows
0832         const QList<QWidget *> widgets = QApplication::topLevelWidgets();
0833         for (QWidget *mainWindowWidget : widgets) {
0834             auto *mainWindow = qobject_cast<MainWindow *>(mainWindowWidget);
0835             if ((mainWindow != nullptr) && mainWindow != this) {
0836                 syncActiveShortcuts(mainWindow->actionCollection(), actionCollection());
0837             }
0838         }
0839         // sync shortcuts for session actions (defined in "sessionui.rc") in other session controllers.
0840         // Controllers which are currently plugged in (ie. their actions are part of the current menu)
0841         // must be updated immediately via syncActiveShortcuts().  Other controllers will be updated
0842         // when they are plugged into a main window.
0843         const QSet<SessionController *> allControllers = SessionController::allControllers();
0844         for (SessionController *controller : allControllers) {
0845             controller->reloadXML();
0846             if ((controller->factory() != nullptr) && controller != _pluggedController) {
0847                 syncActiveShortcuts(controller->actionCollection(), _pluggedController->actionCollection());
0848             }
0849         }
0850     }
0851 }
0852 
0853 void MainWindow::newFromProfile(const Profile::Ptr &profile)
0854 {
0855     createSession(profile, activeSessionDir());
0856 }
0857 
0858 void MainWindow::showManageProfilesDialog()
0859 {
0860     showSettingsDialog(true);
0861 }
0862 
0863 void MainWindow::showSettingsDialog(const bool showProfilePage)
0864 {
0865     ConfigurationDialog *confDialog = findChild<ConfigurationDialog *>();
0866     const QString profilePageName = i18nc("@title Preferences page name", "Profiles");
0867 
0868     if (confDialog != nullptr) {
0869         if (showProfilePage) {
0870             for (auto page : confDialog->findChildren<KPageWidgetItem *>()) {
0871                 if (page->name().contains(profilePageName)) {
0872                     confDialog->setCurrentPage(page);
0873                     break;
0874                 }
0875             }
0876         }
0877         confDialog->show();
0878         return;
0879     }
0880 
0881     confDialog = new ConfigurationDialog(this, KonsoleSettings::self());
0882 
0883     const QString generalPageName = i18nc("@title Preferences page name", "General");
0884     auto *generalPage = new KPageWidgetItem(new GeneralSettings(confDialog), generalPageName);
0885     generalPage->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal")));
0886     confDialog->addPage(generalPage, true);
0887 
0888     auto *profileSettings = new ProfileSettings(confDialog);
0889     auto *profilePage = new KPageWidgetItem(profileSettings, profilePageName);
0890     profilePage->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-theme")));
0891     confDialog->addPage(profilePage, true);
0892     connect(confDialog, &QDialog::accepted, profileSettings, &ProfileSettings::slotAccepted);
0893 
0894 #if defined(Q_OS_LINUX)
0895     const QString memoryPageName = i18nc("@title Preferences page name", "Memory");
0896     auto memoryPage = new KPageWidgetItem(new MemorySettings(confDialog), memoryPageName);
0897     memoryPage->setIcon(QIcon::fromTheme(QStringLiteral("memory")));
0898     confDialog->addPage(memoryPage, true);
0899 #endif
0900 
0901     const QString tabBarPageName = i18nc("@title Preferences page name", "Tab Bar / Splitters");
0902     auto tabBarPage = new KPageWidgetItem(new TabBarSettings(confDialog), tabBarPageName);
0903     tabBarPage->setIcon(QIcon::fromTheme(QStringLiteral("preferences-tabs")));
0904     confDialog->addPage(tabBarPage, true);
0905 
0906     const QString temporaryFilesPageName = i18nc("@title Preferences page name", "Temporary Files");
0907     auto temporaryFilesPage = new KPageWidgetItem(new TemporaryFilesSettings(confDialog), temporaryFilesPageName);
0908     temporaryFilesPage->setIcon(QIcon::fromTheme(QStringLiteral("folder-temp")));
0909     confDialog->addPage(temporaryFilesPage, true);
0910 
0911     const QString thumbnailPageName = i18nc("@title Preferences page name", "Thumbnails");
0912     auto thumbnailPage = new KPageWidgetItem(new ThumbnailsSettings(confDialog), thumbnailPageName);
0913     thumbnailPage->setIcon(QIcon::fromTheme(QStringLiteral("image-jpeg")));
0914     confDialog->addPage(thumbnailPage, true);
0915 
0916     if (showProfilePage) {
0917         confDialog->setCurrentPage(profilePage);
0918     }
0919 
0920     confDialog->show();
0921 }
0922 
0923 void MainWindow::applyKonsoleSettings()
0924 {
0925     setRemoveWindowTitleBarAndFrame(KonsoleSettings::removeWindowTitleBarAndFrame());
0926     if (KonsoleSettings::allowMenuAccelerators()) {
0927         restoreMenuAccelerators();
0928     } else {
0929         removeMenuAccelerators();
0930     }
0931 
0932     _viewManager->activeContainer()->setNavigationBehavior(KonsoleSettings::newTabBehavior());
0933 
0934     // Save the toolbar/menu/dockwidget states and the window geometry
0935     setAutoSaveSettings();
0936 
0937     updateWindowCaption();
0938 }
0939 
0940 void MainWindow::activateMenuBar()
0941 {
0942     const QList<QAction *> menuActions = menuBar()->actions();
0943 
0944     if (menuActions.isEmpty()) {
0945         return;
0946     }
0947 
0948     // Show menubar if it is hidden at the moment
0949     if (menuBar()->isHidden()) {
0950         menuBar()->setVisible(true);
0951         _toggleMenuBarAction->setChecked(true);
0952     }
0953 
0954     // First menu action should be 'File'
0955     QAction *menuAction = menuActions.first();
0956 
0957     // TODO: Handle when menubar is top level (MacOS)
0958     menuBar()->setActiveAction(menuAction);
0959 }
0960 
0961 void MainWindow::configureNotifications()
0962 {
0963     KNotifyConfigWidget::configure(this);
0964 }
0965 
0966 void MainWindow::setBlur(bool blur)
0967 {
0968     if (_pluggedController.isNull()) {
0969         return;
0970     }
0971 
0972     // Saves 70-100ms when starting
0973     if (blur == _blurEnabled) {
0974         return;
0975     }
0976     _blurEnabled = blur;
0977 
0978     if (!_pluggedController->isKonsolePart()) {
0979         if (QWindow *window = windowHandle()) {
0980             KWindowEffects::enableBlurBehind(window, blur);
0981         } else {
0982             qCWarning(KonsoleDebug) << "Blur effect couldn't be enabled.";
0983         }
0984     }
0985 }
0986 
0987 void MainWindow::setMenuBarInitialVisibility(bool showMenuBar)
0988 {
0989     _windowArgsMenuBarVisible.enabled = true;
0990     _windowArgsMenuBarVisible.showMenuBar = showMenuBar;
0991 }
0992 
0993 void MainWindow::setRemoveWindowTitleBarAndFrame(bool frameless)
0994 {
0995     Qt::WindowFlags newFlags = frameless ? Qt::FramelessWindowHint : Qt::Window;
0996 
0997     // The window is not yet visible
0998     if (!isVisible()) {
0999         setWindowFlags(newFlags);
1000 
1001         // The window is visible and the setting changed
1002     } else if (windowFlags().testFlag(Qt::FramelessWindowHint) != frameless) {
1003         if (KWindowSystem::isPlatformX11()) {
1004 #if HAVE_X11
1005             const auto oldGeometry = saveGeometry();
1006             // This happens for every Konsole window. It depends on
1007             // the fact that every window is processed in single thread
1008             const auto oldActiveWindow = KX11Extras::activeWindow();
1009 
1010             setWindowFlags(newFlags);
1011 
1012             // The setWindowFlags() has hidden the window. Show it again
1013             // with previous geometry
1014             restoreGeometry(oldGeometry);
1015             setVisible(true);
1016             KX11Extras::activateWindow(oldActiveWindow);
1017 #endif
1018         } else {
1019             // Restoring geometry ourselves doesn't work on Wayland
1020             setWindowFlags(newFlags);
1021             // The setWindowFlags() has hidden the window. Show it again
1022             setVisible(true);
1023         }
1024     }
1025 }
1026 
1027 void MainWindow::showEvent(QShowEvent *event)
1028 {
1029     // Apply this code on first show only
1030     if (_firstShowEvent) {
1031         _firstShowEvent = false;
1032 
1033         if (!KonsoleSettings::rememberWindowSize() || !wasWindowGeometrySaved()) {
1034             // Delay resizing to here, so that the other parts of the UI
1035             // (ViewManager, TabbedViewContainer, TerminalDisplay ... etc)
1036             // have been created and TabbedViewContainer::sizeHint() returns
1037             // a usable size.
1038 
1039             // Remove the WindowMaximized state to override the Window-Maximized
1040             // config key
1041             if (QWindow *window = windowHandle()) {
1042                 window->setWindowStates(window->windowStates() & ~Qt::WindowMaximized);
1043             }
1044 
1045             resize(sizeHint());
1046         }
1047     }
1048 
1049     // Call parent method
1050     KXmlGuiWindow::showEvent(event);
1051 }
1052 
1053 void MainWindow::triggerAction(const QString &name) const
1054 {
1055     if (auto action = actionCollection()->action(name)) {
1056         if (action->isEnabled()) {
1057             action->trigger();
1058         }
1059     }
1060 }
1061 
1062 bool MainWindow::eventFilter(QObject *obj, QEvent *event)
1063 {
1064     if (!_pluggedController.isNull() && obj == _pluggedController->view()) {
1065         switch (event->type()) {
1066         case QEvent::MouseButtonPress:
1067         case QEvent::MouseButtonDblClick:
1068             switch (static_cast<QMouseEvent *>(event)->button()) {
1069             case Qt::ForwardButton:
1070                 triggerAction(QStringLiteral("next-view"));
1071                 break;
1072             case Qt::BackButton:
1073                 triggerAction(QStringLiteral("previous-view"));
1074                 break;
1075             default:;
1076             }
1077         default:;
1078         }
1079     }
1080 
1081     return KXmlGuiWindow::eventFilter(obj, event);
1082 }
1083 
1084 bool MainWindow::focusNextPrevChild(bool v)
1085 {
1086     if (qobject_cast<TerminalDisplay *>(focusWidget())) {
1087         return false;
1088     }
1089 
1090     return QMainWindow::focusNextPrevChild(v);
1091 }
1092 
1093 void MainWindow::saveNewToolbarConfig()
1094 {
1095     KXmlGuiWindow::saveNewToolbarConfig();
1096 
1097     unplugActionList(QStringLiteral("plugin-submenu"));
1098     plugActionList(QStringLiteral("plugin-submenu"), _pluginsActions);
1099 }
1100 
1101 #include "moc_MainWindow.cpp"