diff --git a/src/SessionController.cpp b/src/SessionController.cpp index 9cd422b1..c801455c 100644 --- a/src/SessionController.cpp +++ b/src/SessionController.cpp @@ -1,1799 +1,1799 @@ /* Copyright 2006-2008 by Robert Knight Copyright 2009 by Thomas Dreibholz 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 "SessionController.h" #include "ProfileManager.h" #include "konsoledebug.h" // Qt #include #include #include #include #include #include #include #include #include #include #include #include // KDE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Konsole #include "EditProfileDialog.h" #include "CopyInputDialog.h" #include "Emulation.h" #include "Filter.h" #include "History.h" #include "HistorySizeDialog.h" #include "IncrementalSearchBar.h" #include "RenameTabDialog.h" #include "ScreenWindow.h" #include "Session.h" #include "ProfileList.h" #include "TerminalDisplay.h" #include "SessionManager.h" #include "Enumeration.h" #include "PrintOptions.h" #include "SaveHistoryTask.h" #include "SearchHistoryTask.h" // For Unix signal names #include using namespace Konsole; // TODO - Replace the icon choices below when suitable icons for silence and // activity are available Q_GLOBAL_STATIC_WITH_ARGS(QIcon, _activityIcon, (QIcon::fromTheme(QLatin1String("dialog-information")))) Q_GLOBAL_STATIC_WITH_ARGS(QIcon, _silenceIcon, (QIcon::fromTheme(QLatin1String("dialog-information")))) Q_GLOBAL_STATIC_WITH_ARGS(QIcon, _bellIcon, (QIcon::fromTheme(QLatin1String("preferences-desktop-notification-bell")))) Q_GLOBAL_STATIC_WITH_ARGS(QIcon, _broadcastIcon, (QIcon::fromTheme(QLatin1String("emblem-important")))) QSet SessionController::_allControllers; int SessionController::_lastControllerId; SessionController::SessionController(Session* session , TerminalDisplay* view, QObject* parent) : ViewProperties(parent) , KXMLGUIClient() , _session(session) , _view(view) , _copyToGroup(nullptr) , _profileList(nullptr) , _sessionIcon(QIcon()) , _sessionIconName(QString()) , _previousState(-1) , _searchFilter(nullptr) , _urlFilter(nullptr) , _fileFilter(nullptr) , _copyInputToAllTabsAction(nullptr) , _findAction(nullptr) , _findNextAction(nullptr) , _findPreviousAction(nullptr) , _interactionTimer(nullptr) , _searchStartLine(0) , _prevSearchResultLine(0) , _codecAction(nullptr) , _switchProfileMenu(nullptr) , _webSearchMenu(nullptr) , _listenForScreenWindowUpdates(false) , _preventClose(false) , _keepIconUntilInteraction(false) , _selectedText(QString()) , _showMenuAction(nullptr) , _bookmarkValidProgramsToClear(QStringList()) , _isSearchBarEnabled(false) , _editProfileDialog(nullptr) , _searchBar(view->searchBar()) { Q_ASSERT(session); Q_ASSERT(view); // handle user interface related to session (menus etc.) if (isKonsolePart()) { setComponentName(QStringLiteral("konsole"), i18n("Konsole")); setXMLFile(QStringLiteral("partui.rc")); setupCommonActions(); } else { setXMLFile(QStringLiteral("sessionui.rc")); setupCommonActions(); setupExtraActions(); } actionCollection()->addAssociatedWidget(view); foreach(QAction * action, actionCollection()->actions()) { action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } setIdentifier(++_lastControllerId); sessionAttributeChanged(); view->installEventFilter(this); view->setSessionController(this); // install filter on the view to highlight URLs and files updateFilterList(SessionManager::instance()->sessionProfile(_session)); // listen for changes in session, we might need to change the enabled filters connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged, this, &Konsole::SessionController::updateFilterList); // listen for session resize requests connect(_session.data(), &Konsole::Session::resizeRequest, this, &Konsole::SessionController::sessionResizeRequest); // listen for popup menu requests connect(_view.data(), &Konsole::TerminalDisplay::configureRequest, this, &Konsole::SessionController::showDisplayContextMenu); // move view to newest output when keystrokes occur connect(_view.data(), &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::SessionController::trackOutput); // listen to activity / silence notifications from session connect(_session.data(), &Konsole::Session::stateChanged, this, &Konsole::SessionController::sessionStateChanged); // listen to title and icon changes connect(_session.data(), &Konsole::Session::sessionAttributeChanged, this, &Konsole::SessionController::sessionAttributeChanged); connect(_session.data(), &Konsole::Session::readOnlyChanged, this, &Konsole::SessionController::sessionReadOnlyChanged); connect(this, &Konsole::SessionController::tabRenamedByUser, _session, &Konsole::Session::tabTitleSetByUser); connect(_session.data() , &Konsole::Session::currentDirectoryChanged , this , &Konsole::SessionController::currentDirectoryChanged); // listen for color changes connect(_session.data(), &Konsole::Session::changeBackgroundColorRequest, _view.data(), &Konsole::TerminalDisplay::setBackgroundColor); connect(_session.data(), &Konsole::Session::changeForegroundColorRequest, _view.data(), &Konsole::TerminalDisplay::setForegroundColor); // update the title when the session starts connect(_session.data(), &Konsole::Session::started, this, &Konsole::SessionController::snapshot); // listen for output changes to set activity flag connect(_session->emulation(), &Konsole::Emulation::outputChanged, this, &Konsole::SessionController::fireActivity); // listen for detection of ZModem transfer connect(_session.data(), &Konsole::Session::zmodemDownloadDetected, this, &Konsole::SessionController::zmodemDownload); connect(_session.data(), &Konsole::Session::zmodemUploadDetected, this, &Konsole::SessionController::zmodemUpload); // listen for flow control status changes connect(_session.data(), &Konsole::Session::flowControlEnabledChanged, _view.data(), &Konsole::TerminalDisplay::setFlowControlWarningEnabled); _view->setFlowControlWarningEnabled(_session->flowControlEnabled()); // take a snapshot of the session state every so often when // user activity occurs // // the timer is owned by the session so that it will be destroyed along // with the session _interactionTimer = new QTimer(_session); _interactionTimer->setSingleShot(true); _interactionTimer->setInterval(500); connect(_interactionTimer, &QTimer::timeout, this, &Konsole::SessionController::snapshot); connect(_view.data(), &Konsole::TerminalDisplay::focusGained, this, &Konsole::SessionController::interactionHandler); connect(_view.data(), &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::SessionController::interactionHandler); // take a snapshot of the session state periodically in the background auto backgroundTimer = new QTimer(_session); backgroundTimer->setSingleShot(false); backgroundTimer->setInterval(2000); connect(backgroundTimer, &QTimer::timeout, this, &Konsole::SessionController::snapshot); backgroundTimer->start(); // xterm '11;?' request connect(_session.data(), &Konsole::Session::getBackgroundColor, this, &Konsole::SessionController::sendBackgroundColor); _allControllers.insert(this); // A list of programs that accept Ctrl+C to clear command line used // before outputting bookmark. _bookmarkValidProgramsToClear << QStringLiteral("bash") << QStringLiteral("fish") << QStringLiteral("sh"); _bookmarkValidProgramsToClear << QStringLiteral("tcsh") << QStringLiteral("zsh"); setupSearchBar(); _searchBar->setVisible(_isSearchBarEnabled); } SessionController::~SessionController() { _allControllers.remove(this); if (!_editProfileDialog.isNull()) { delete _editProfileDialog.data(); } } void SessionController::trackOutput(QKeyEvent* event) { Q_ASSERT(_view->screenWindow()); // Qt has broken something, so we can't rely on just checking if certain // keys are passed as modifiers anymore. const int key = event->key(); const bool shouldNotTriggerScroll = key == Qt::Key_Super_L || key == Qt::Key_Super_R || key == Qt::Key_Hyper_L || key == Qt::Key_Hyper_R || key == Qt::Key_Shift || key == Qt::Key_Control || key == Qt::Key_Meta || key == Qt::Key_Alt || key == Qt::Key_AltGr || key == Qt::Key_CapsLock || key == Qt::Key_NumLock || key == Qt::Key_ScrollLock; // Only jump to the bottom if the user actually typed something in, // not if the user e. g. just pressed a modifier. - if (event->text().isEmpty() && (event->modifiers() || shouldNotTriggerScroll)) { + if (event->text().isEmpty() && ((event->modifiers() != 0u) || shouldNotTriggerScroll)) { return; } _view->screenWindow()->setTrackOutput(true); } void SessionController::interactionHandler() { // This flag is used to make sure those special icons indicating interest // events (activity/silence/bell?) remain in the tab until user interaction // happens. Otherwise, those special icons will quickly be replaced by // normal icon when ::snapshot() is triggered _keepIconUntilInteraction = false; _interactionTimer->start(); } void SessionController::snapshot() { Q_ASSERT(!_session.isNull()); QString title = _session->getDynamicTitle(); title = title.simplified(); // Visualize that the session is broadcasting to others if ((_copyToGroup != nullptr) && _copyToGroup->sessions().count() > 1) { title.append(QLatin1Char('*')); } // use the fallback title if needed if (title.isEmpty()) { title = _session->title(Session::NameRole); } // apply new title _session->setTitle(Session::DisplayedTitleRole, title); // do not forget icon updateSessionIcon(); } QString SessionController::currentDir() const { return _session->currentWorkingDirectory(); } QUrl SessionController::url() const { return _session->getUrl(); } void SessionController::rename() { renameSession(); } void SessionController::openUrl(const QUrl& url) { // Clear shell's command line if (!_session->isForegroundProcessActive() && _bookmarkValidProgramsToClear.contains(_session->foregroundProcessName())) { _session->sendTextToTerminal(QChar(0x03), QLatin1Char('\n')); // Ctrl+C } // handle local paths if (url.isLocalFile()) { QString path = url.toLocalFile(); _session->sendTextToTerminal(QStringLiteral("cd ") + KShell::quoteArg(path), QLatin1Char('\r')); } else if (url.scheme().isEmpty()) { // QUrl couldn't parse what the user entered into the URL field // so just dump it to the shell QString command = url.toDisplayString(); if (!command.isEmpty()) { _session->sendTextToTerminal(command, QLatin1Char('\r')); } } else if (url.scheme() == QLatin1String("ssh")) { 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')); } else if (url.scheme() == QLatin1String("telnet")) { QString telnetCommand = QStringLiteral("telnet "); if (!url.userName().isEmpty()) { telnetCommand += QStringLiteral("-l %1 ").arg(url.userName()); } if (!url.host().isEmpty()) { telnetCommand += (url.host() + QLatin1Char(' ')); } if (url.port() > -1) { telnetCommand += QString::number(url.port()); } _session->sendTextToTerminal(telnetCommand, QLatin1Char('\r')); } else { //TODO Implement handling for other Url types KMessageBox::sorry(_view->window(), i18n("Konsole does not know how to open the bookmark: ") + url.toDisplayString()); qCDebug(KonsoleDebug) << "Unable to open bookmark at url" << url << ", I do not know" << " how to handle the protocol " << url.scheme(); } } void SessionController::setupPrimaryScreenSpecificActions(bool use) { KActionCollection* collection = actionCollection(); QAction* clearAction = collection->action(QStringLiteral("clear-history")); QAction* resetAction = collection->action(QStringLiteral("clear-history-and-reset")); QAction* selectAllAction = collection->action(QStringLiteral("select-all")); QAction* selectLineAction = collection->action(QStringLiteral("select-line")); // these actions are meaningful only when primary screen is used. clearAction->setEnabled(use); resetAction->setEnabled(use); selectAllAction->setEnabled(use); selectLineAction->setEnabled(use); } void SessionController::selectionChanged(const QString& selectedText) { _selectedText = selectedText; updateCopyAction(selectedText); } void SessionController::updateCopyAction(const QString& selectedText) { QAction* copyAction = actionCollection()->action(QStringLiteral("edit_copy")); // copy action is meaningful only when some text is selected. copyAction->setEnabled(!selectedText.isEmpty()); } void SessionController::updateWebSearchMenu() { // reset _webSearchMenu->setVisible(false); _webSearchMenu->menu()->clear(); if (_selectedText.isEmpty()) { return; } QString searchText = _selectedText; searchText = searchText.replace(QLatin1Char('\n'), QLatin1Char(' ')).replace(QLatin1Char('\r'), QLatin1Char(' ')).simplified(); if (searchText.isEmpty()) { return; } KUriFilterData filterData(searchText); filterData.setSearchFilteringOptions(KUriFilterData::RetrievePreferredSearchProvidersOnly); if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::NormalTextFilter)) { const QStringList searchProviders = filterData.preferredSearchProviders(); if (!searchProviders.isEmpty()) { _webSearchMenu->setText(i18n("Search for '%1' with", KStringHandler::rsqueeze(searchText, 16))); QAction* action = nullptr; foreach(const QString& searchProvider, searchProviders) { action = new QAction(searchProvider, _webSearchMenu); action->setIcon(QIcon::fromTheme(filterData.iconNameForPreferredSearchProvider(searchProvider))); action->setData(filterData.queryForPreferredSearchProvider(searchProvider)); connect(action, &QAction::triggered, this, &Konsole::SessionController::handleWebShortcutAction); _webSearchMenu->addAction(action); } _webSearchMenu->addSeparator(); action = new QAction(i18n("Configure Web Shortcuts..."), _webSearchMenu); action->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect(action, &QAction::triggered, this, &Konsole::SessionController::configureWebShortcuts); _webSearchMenu->addAction(action); _webSearchMenu->setVisible(true); } } } void SessionController::handleWebShortcutAction() { auto * action = qobject_cast(sender()); if (action == nullptr) { return; } KUriFilterData filterData(action->data().toString()); if (KUriFilter::self()->filterUri(filterData, QStringList() << QStringLiteral("kurisearchfilter"))) { const QUrl& url = filterData.uri(); new KRun(url, QApplication::activeWindow()); } } void SessionController::configureWebShortcuts() { KToolInvocation::kdeinitExec(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("webshortcuts")); } void SessionController::sendSignal(QAction* action) { const auto signal = action->data().toInt(); _session->sendSignal(signal); } void SessionController::sendBackgroundColor() { const QColor c = _view->getBackgroundColor(); _session->reportBackgroundColor(c); } void SessionController::toggleReadOnly() { auto *action = qobject_cast(sender()); if (action != nullptr) { bool readonly = !isReadOnly(); _session->setReadOnly(readonly); } } bool SessionController::eventFilter(QObject* watched , QEvent* event) { if (event->type() == QEvent::FocusIn && watched == _view) { // notify the world that the view associated with this session has been focused // used by the view manager to update the title of the MainWindow widget containing the view emit focused(this); // when the view is focused, set bell events from the associated session to be delivered // by the focused view // first, disconnect any other views which are listening for bell signals from the session disconnect(_session.data(), &Konsole::Session::bellRequest, nullptr, nullptr); // second, connect the newly focused view to listen for the session's bell signal connect(_session.data(), &Konsole::Session::bellRequest, _view.data(), &Konsole::TerminalDisplay::bell); if ((_copyInputToAllTabsAction != nullptr) && _copyInputToAllTabsAction->isChecked()) { // A session with "Copy To All Tabs" has come into focus: // Ensure that newly created sessions are included in _copyToGroup! copyInputToAllTabs(); } } return Konsole::ViewProperties::eventFilter(watched, event); } void SessionController::removeSearchFilter() { if (_searchFilter == nullptr) { return; } _view->filterChain()->removeFilter(_searchFilter); delete _searchFilter; _searchFilter = nullptr; } void SessionController::setupSearchBar() { connect(_searchBar.data(), &Konsole::IncrementalSearchBar::unhandledMovementKeyPressed, this, &Konsole::SessionController::movementKeyFromSearchBarReceived); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::closeClicked, this, &Konsole::SessionController::searchClosed); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchFromClicked, this, &Konsole::SessionController::searchFrom); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::findNextClicked, this, &Konsole::SessionController::findNextInHistory); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::findPreviousClicked, this, &Konsole::SessionController::findPreviousInHistory); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::highlightMatchesToggled , this , &Konsole::SessionController::highlightMatches); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::matchCaseToggled, this, &Konsole::SessionController::changeSearchMatch); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::matchRegExpToggled, this, &Konsole::SessionController::changeSearchMatch); } void SessionController::setShowMenuAction(QAction* action) { _showMenuAction = action; } void SessionController::setupCommonActions() { KActionCollection* collection = actionCollection(); // Close Session QAction* action = collection->addAction(QStringLiteral("close-session"), this, SLOT(closeSession())); action->setText(i18n("&Close Session")); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_W); // Open Browser action = collection->addAction(QStringLiteral("open-browser"), this, SLOT(openBrowser())); action->setText(i18n("Open File Manager")); action->setIcon(QIcon::fromTheme(QStringLiteral("system-file-manager"))); // Copy and Paste action = KStandardAction::copy(this, SLOT(copy()), collection); #ifdef Q_OS_MACOS // Don't use the Konsole::ACCEL const here, we really want the Command key (Qt::META) // TODO: check what happens if we leave it to Qt to assign the default? collection->setDefaultShortcut(action, Qt::META + Qt::Key_C); #else collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_C); #endif // disabled at first, since nothing has been selected now action->setEnabled(false); action = KStandardAction::paste(this, SLOT(paste()), collection); QList pasteShortcut; #ifdef Q_OS_MACOS pasteShortcut.append(QKeySequence(Qt::META + Qt::Key_V)); // No Insert key on Mac keyboards #else pasteShortcut.append(QKeySequence(Konsole::ACCEL + Qt::SHIFT + Qt::Key_V)); pasteShortcut.append(QKeySequence(Qt::SHIFT + Qt::Key_Insert)); #endif collection->setDefaultShortcuts(action, pasteShortcut); action = collection->addAction(QStringLiteral("paste-selection"), this, SLOT(pasteFromX11Selection())); action->setText(i18n("Paste Selection")); #ifdef Q_OS_MACOS collection->setDefaultShortcut(action, Qt::META + Qt::SHIFT + Qt::Key_V); #else collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Insert); #endif _webSearchMenu = new KActionMenu(i18n("Web Search"), this); _webSearchMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-web-browser-shortcuts"))); _webSearchMenu->setVisible(false); collection->addAction(QStringLiteral("web-search"), _webSearchMenu); action = collection->addAction(QStringLiteral("select-all"), this, SLOT(selectAll())); action->setText(i18n("&Select All")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-select-all"))); action = collection->addAction(QStringLiteral("select-line"), this, SLOT(selectLine())); action->setText(i18n("Select &Line")); action = KStandardAction::saveAs(this, SLOT(saveHistory()), collection); action->setText(i18n("Save Output &As...")); #ifdef Q_OS_MACOS action->setShortcut(QKeySequence(Qt::META + Qt::Key_S)); #endif action = KStandardAction::print(this, SLOT(print_screen()), collection); action->setText(i18n("&Print Screen...")); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_P); action = collection->addAction(QStringLiteral("adjust-history"), this, SLOT(showHistoryOptions())); action->setText(i18n("Adjust Scrollback...")); action->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); action = collection->addAction(QStringLiteral("clear-history"), this, SLOT(clearHistory())); action->setText(i18n("Clear Scrollback")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history"))); action = collection->addAction(QStringLiteral("clear-history-and-reset"), this, SLOT(clearHistoryAndReset())); action->setText(i18n("Clear Scrollback and Reset")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_K); // Profile Options action = collection->addAction(QStringLiteral("edit-current-profile"), this, SLOT(editCurrentProfile())); action->setText(i18n("Edit Current Profile...")); action->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); _switchProfileMenu = new KActionMenu(i18n("Switch Profile"), this); collection->addAction(QStringLiteral("switch-profile"), _switchProfileMenu); connect(_switchProfileMenu->menu(), &QMenu::aboutToShow, this, &Konsole::SessionController::prepareSwitchProfileMenu); // History _findAction = KStandardAction::find(this, SLOT(searchBarEvent()), collection); _findNextAction = KStandardAction::findNext(this, SLOT(findNextInHistory()), collection); _findNextAction->setEnabled(false); _findPreviousAction = KStandardAction::findPrev(this, SLOT(findPreviousInHistory()), collection); _findPreviousAction->setEnabled(false); #ifdef Q_OS_MACOS collection->setDefaultShortcut(_findAction, Qt::META + Qt::Key_F); collection->setDefaultShortcut(_findNextAction, Qt::META + Qt::Key_G); collection->setDefaultShortcut(_findPreviousAction, Qt::META + Qt::SHIFT + Qt::Key_G); #else collection->setDefaultShortcut(_findAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_F); collection->setDefaultShortcut(_findNextAction, Qt::Key_F3); collection->setDefaultShortcut(_findPreviousAction, Qt::SHIFT + Qt::Key_F3); #endif // Character Encoding _codecAction = new KCodecAction(i18n("Set &Encoding"), this); _codecAction->setIcon(QIcon::fromTheme(QStringLiteral("character-set"))); collection->addAction(QStringLiteral("set-encoding"), _codecAction); connect(_codecAction->menu(), &QMenu::aboutToShow, this, &Konsole::SessionController::updateCodecAction); connect(_codecAction, QOverload::of(&KCodecAction::triggered), this, &Konsole::SessionController::changeCodec); // Read-only action = collection->addAction(QStringLiteral("view-readonly"), this, SLOT(toggleReadOnly())); action->setText(i18nc("@item:inmenu A read only (locked) session", "Read-only")); action->setCheckable(true); updateReadOnlyActionStates(); } void SessionController::setupExtraActions() { KActionCollection* collection = actionCollection(); // Rename Session QAction* action = collection->addAction(QStringLiteral("rename-session"), this, SLOT(renameSession())); action->setText(i18n("&Rename Tab...")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::ALT + Qt::Key_S); // Copy input to ==> all tabs auto* copyInputToAllTabsAction = collection->add(QStringLiteral("copy-input-to-all-tabs")); copyInputToAllTabsAction->setText(i18n("&All Tabs in Current Window")); copyInputToAllTabsAction->setData(CopyInputToAllTabsMode); // this action is also used in other place, so remember it _copyInputToAllTabsAction = copyInputToAllTabsAction; // Copy input to ==> selected tabs auto* copyInputToSelectedTabsAction = collection->add(QStringLiteral("copy-input-to-selected-tabs")); copyInputToSelectedTabsAction->setText(i18n("&Select Tabs...")); collection->setDefaultShortcut(copyInputToSelectedTabsAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Period); copyInputToSelectedTabsAction->setData(CopyInputToSelectedTabsMode); // Copy input to ==> none auto* copyInputToNoneAction = collection->add(QStringLiteral("copy-input-to-none")); copyInputToNoneAction->setText(i18nc("@action:inmenu Do not select any tabs", "&None")); collection->setDefaultShortcut(copyInputToNoneAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Slash); copyInputToNoneAction->setData(CopyInputToNoneMode); copyInputToNoneAction->setChecked(true); // the default state // The "Copy Input To" submenu // The above three choices are represented as combo boxes auto* copyInputActions = collection->add(QStringLiteral("copy-input-to")); copyInputActions->setText(i18n("Copy Input To")); copyInputActions->addAction(copyInputToAllTabsAction); copyInputActions->addAction(copyInputToSelectedTabsAction); copyInputActions->addAction(copyInputToNoneAction); connect(copyInputActions, QOverload::of(&KSelectAction::triggered), this, &Konsole::SessionController::copyInputActionsTriggered); action = collection->addAction(QStringLiteral("zmodem-upload"), this, SLOT(zmodemUpload())); action->setText(i18n("&ZModem Upload...")); action->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::ALT + Qt::Key_U); // Monitor KToggleAction* toggleAction = new KToggleAction(i18n("Monitor for &Activity"), this); collection->setDefaultShortcut(toggleAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_A); action = collection->addAction(QStringLiteral("monitor-activity"), toggleAction); connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorActivity); toggleAction = new KToggleAction(i18n("Monitor for &Silence"), this); collection->setDefaultShortcut(toggleAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_I); action = collection->addAction(QStringLiteral("monitor-silence"), toggleAction); connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorSilence); // Text Size action = collection->addAction(QStringLiteral("enlarge-font"), this, SLOT(increaseFontSize())); action->setText(i18n("Enlarge Font")); action->setIcon(QIcon::fromTheme(QStringLiteral("format-font-size-more"))); QList enlargeFontShortcut; enlargeFontShortcut.append(QKeySequence(Konsole::ACCEL + Qt::Key_Plus)); enlargeFontShortcut.append(QKeySequence(Konsole::ACCEL + Qt::Key_Equal)); collection->setDefaultShortcuts(action, enlargeFontShortcut); action = collection->addAction(QStringLiteral("shrink-font"), this, SLOT(decreaseFontSize())); action->setText(i18n("Shrink Font")); action->setIcon(QIcon::fromTheme(QStringLiteral("format-font-size-less"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::Key_Minus); action = collection->addAction(QStringLiteral("reset-font-size"), this, SLOT(resetFontSize())); action->setText(i18n("Reset Font Size")); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::ALT + Qt::Key_0); // Send signal auto* sendSignalActions = collection->add(QStringLiteral("send-signal")); sendSignalActions->setText(i18n("Send Signal")); connect(sendSignalActions, QOverload::of(&KSelectAction::triggered), this, &Konsole::SessionController::sendSignal); action = collection->addAction(QStringLiteral("sigstop-signal")); action->setText(i18n("&Suspend Task") + QStringLiteral(" (STOP)")); action->setData(SIGSTOP); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigcont-signal")); action->setText(i18n("&Continue Task") + QStringLiteral(" (CONT)")); action->setData(SIGCONT); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sighup-signal")); action->setText(i18n("&Hangup") + QStringLiteral(" (HUP)")); action->setData(SIGHUP); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigint-signal")); action->setText(i18n("&Interrupt Task") + QStringLiteral(" (INT)")); action->setData(SIGINT); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigterm-signal")); action->setText(i18n("&Terminate Task") + QStringLiteral(" (TERM)")); action->setData(SIGTERM); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigkill-signal")); action->setText(i18n("&Kill Task") + QStringLiteral(" (KILL)")); action->setData(SIGKILL); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigusr1-signal")); action->setText(i18n("User Signal &1") + QStringLiteral(" (USR1)")); action->setData(SIGUSR1); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigusr2-signal")); action->setText(i18n("User Signal &2") + QStringLiteral(" (USR2)")); action->setData(SIGUSR2); sendSignalActions->addAction(action); } void SessionController::switchProfile(const Profile::Ptr &profile) { SessionManager::instance()->setSessionProfile(_session, profile); updateFilterList(profile); } void SessionController::prepareSwitchProfileMenu() { if (_switchProfileMenu->menu()->isEmpty()) { _profileList = new ProfileList(false, this); connect(_profileList, &Konsole::ProfileList::profileSelected, this, &Konsole::SessionController::switchProfile); } _switchProfileMenu->menu()->clear(); _switchProfileMenu->menu()->addActions(_profileList->actions()); } void SessionController::updateCodecAction() { _codecAction->setCurrentCodec(QString::fromUtf8(_session->codec())); } void SessionController::changeCodec(QTextCodec* codec) { _session->setCodec(codec); } EditProfileDialog* SessionController::profileDialogPointer() { return _editProfileDialog.data(); } void SessionController::editCurrentProfile() { // Searching for Edit profile dialog opened with the same profile const QList allSessionsControllers = _allControllers.values(); foreach (SessionController* session, allSessionsControllers) { if ((session->profileDialogPointer() != nullptr) && session->profileDialogPointer()->isVisible() && session->profileDialogPointer()->lookupProfile() == SessionManager::instance()->sessionProfile(_session)) { session->profileDialogPointer()->close(); } } // NOTE bug311270: For to prevent the crash, the profile must be reset. if (!_editProfileDialog.isNull()) { // exists but not visible delete _editProfileDialog.data(); } _editProfileDialog = new EditProfileDialog(QApplication::activeWindow()); _editProfileDialog.data()->setProfile(SessionManager::instance()->sessionProfile(_session)); _editProfileDialog.data()->show(); } void SessionController::renameSession() { const QString &sessionLocalTabTitleFormat = _session->tabTitleFormat(Session::LocalTabTitle); const QString &sessionRemoteTabTitleFormat = _session->tabTitleFormat(Session::RemoteTabTitle); QScopedPointer dialog(new RenameTabDialog(QApplication::activeWindow())); dialog->setTabTitleText(sessionLocalTabTitleFormat); dialog->setRemoteTabTitleText(sessionRemoteTabTitleFormat); if (_session->isRemote()) { dialog->focusRemoteTabTitleText(); } else { dialog->focusTabTitleText(); } QPointer guard(_session); int result = dialog->exec(); if (guard.isNull()) { return; } if (result != 0) { const QString &tabTitle = dialog->tabTitleText(); const QString &remoteTabTitle = dialog->remoteTabTitleText(); if (tabTitle != sessionLocalTabTitleFormat) { _session->setTabTitleFormat(Session::LocalTabTitle, tabTitle); emit tabRenamedByUser(true); // trigger an update of the tab text snapshot(); } if(remoteTabTitle != sessionRemoteTabTitleFormat) { _session->setTabTitleFormat(Session::RemoteTabTitle, remoteTabTitle); emit tabRenamedByUser(true); snapshot(); } } } bool SessionController::confirmClose() const { if (_session->isForegroundProcessActive()) { QString title = _session->foregroundProcessName(); // hard coded for now. In future make it possible for the user to specify which programs // are ignored when considering whether to display a confirmation QStringList ignoreList; ignoreList << QString::fromUtf8(qgetenv("SHELL")).section(QLatin1Char('/'), -1); if (ignoreList.contains(title)) { return true; } QString question; if (title.isEmpty()) { question = i18n("A program is currently running in this session." " Are you sure you want to close it?"); } else { question = i18n("The program '%1' is currently running in this session." " Are you sure you want to close it?", title); } int result = KMessageBox::warningYesNo(_view->window(), question, i18n("Confirm Close")); return result == KMessageBox::Yes; } return true; } bool SessionController::confirmForceClose() const { if (_session->isRunning()) { QString title = _session->program(); // hard coded for now. In future make it possible for the user to specify which programs // are ignored when considering whether to display a confirmation QStringList ignoreList; ignoreList << QString::fromUtf8(qgetenv("SHELL")).section(QLatin1Char('/'), -1); if (ignoreList.contains(title)) { return true; } QString question; if (title.isEmpty()) { question = i18n("A program in this session would not die." " Are you sure you want to kill it by force?"); } else { question = i18n("The program '%1' is in this session would not die." " Are you sure you want to kill it by force?", title); } int result = KMessageBox::warningYesNo(_view->window(), question, i18n("Confirm Close")); return result == KMessageBox::Yes; } return true; } void SessionController::closeSession() { if (_preventClose) { return; } if (confirmClose()) { if (_session->closeInNormalWay()) { return; } else if (confirmForceClose()) { if (_session->closeInForceWay()) { return; } else { qCDebug(KonsoleDebug) << "Konsole failed to close a session in any way."; } } } } // Trying to open a remote Url may produce unexpected results. // Therefore, if a remote url, open the user's home path. // TODO consider: 1) disable menu upon remote session // 2) transform url to get the desired result (ssh -> sftp, etc) void SessionController::openBrowser() { const QUrl currentUrl = url(); if (currentUrl.isLocalFile()) { new KRun(currentUrl, QApplication::activeWindow(), true); } else { new KRun(QUrl::fromLocalFile(QDir::homePath()), QApplication::activeWindow(), true); } } void SessionController::copy() { _view->copyToClipboard(); } void SessionController::paste() { _view->pasteFromClipboard(); } void SessionController::pasteFromX11Selection() { _view->pasteFromX11Selection(); } void SessionController::selectAll() { _view->selectAll(); } void SessionController::selectLine() { _view->selectCurrentLine(); } static const KXmlGuiWindow* findWindow(const QObject* object) { // Walk up the QObject hierarchy to find a KXmlGuiWindow. while (object != nullptr) { const auto* window = qobject_cast(object); if (window != nullptr) { return(window); } object = object->parent(); } return(nullptr); } static bool hasTerminalDisplayInSameWindow(const Session* session, const KXmlGuiWindow* window) { // Iterate all TerminalDisplays of this Session ... foreach(const TerminalDisplay* terminalDisplay, session->views()) { // ... and check whether a TerminalDisplay has the same // window as given in the parameter if (window == findWindow(terminalDisplay)) { return(true); } } return(false); } void SessionController::copyInputActionsTriggered(QAction* action) { const auto mode = action->data().toInt(); switch (mode) { case CopyInputToAllTabsMode: copyInputToAllTabs(); break; case CopyInputToSelectedTabsMode: copyInputToSelectedTabs(); break; case CopyInputToNoneMode: copyInputToNone(); break; default: Q_ASSERT(false); } } void SessionController::copyInputToAllTabs() { if (_copyToGroup == nullptr) { _copyToGroup = new SessionGroup(this); } // Find our window ... const KXmlGuiWindow* myWindow = findWindow(_view); QSet group = QSet::fromList(SessionManager::instance()->sessions()); for (auto session : group) { // First, ensure that the session is removed // (necessary to avoid duplicates on addSession()!) _copyToGroup->removeSession(session); // Add current session if it is displayed our window if (hasTerminalDisplayInSameWindow(session, myWindow)) { _copyToGroup->addSession(session); } } _copyToGroup->setMasterStatus(_session, true); _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll); snapshot(); } void SessionController::copyInputToSelectedTabs() { if (_copyToGroup == nullptr) { _copyToGroup = new SessionGroup(this); _copyToGroup->addSession(_session); _copyToGroup->setMasterStatus(_session, true); _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll); } QPointer dialog = new CopyInputDialog(_view); dialog->setMasterSession(_session); QSet currentGroup = QSet::fromList(_copyToGroup->sessions()); currentGroup.remove(_session); dialog->setChosenSessions(currentGroup); QPointer guard(_session); int result = dialog->exec(); if (guard.isNull()) { return; } if (result == QDialog::Accepted) { QSet newGroup = dialog->chosenSessions(); newGroup.remove(_session); QSet completeGroup = newGroup | currentGroup; foreach(Session * session, completeGroup) { if (newGroup.contains(session) && !currentGroup.contains(session)) { _copyToGroup->addSession(session); } else if (!newGroup.contains(session) && currentGroup.contains(session)) { _copyToGroup->removeSession(session); } } _copyToGroup->setMasterStatus(_session, true); _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll); snapshot(); } } void SessionController::copyInputToNone() { if (_copyToGroup == nullptr) { // No 'Copy To' is active return; } QSet group = QSet::fromList(SessionManager::instance()->sessions()); for (auto iterator : group) { Session* session = iterator; if (session != _session) { _copyToGroup->removeSession(iterator); } } delete _copyToGroup; _copyToGroup = nullptr; snapshot(); } void SessionController::searchClosed() { _isSearchBarEnabled = false; searchHistory(false); } void SessionController::updateFilterList(Profile::Ptr profile) { if (profile != SessionManager::instance()->sessionProfile(_session)) { return; } bool underlineFiles = profile->underlineFilesEnabled(); if (!underlineFiles && (_fileFilter != nullptr)) { _view->filterChain()->removeFilter(_fileFilter); delete _fileFilter; _fileFilter = nullptr; } else if (underlineFiles && (_fileFilter == nullptr)) { _fileFilter = new FileFilter(_session); _view->filterChain()->addFilter(_fileFilter); } bool underlineLinks = profile->underlineLinksEnabled(); if (!underlineLinks && (_urlFilter != nullptr)) { _view->filterChain()->removeFilter(_urlFilter); delete _urlFilter; _urlFilter = nullptr; } else if (underlineLinks && (_urlFilter == nullptr)) { _urlFilter = new UrlFilter(); _view->filterChain()->addFilter(_urlFilter); } } void SessionController::setSearchStartToWindowCurrentLine() { setSearchStartTo(-1); } void SessionController::setSearchStartTo(int line) { _searchStartLine = line; _prevSearchResultLine = line; } void SessionController::listenForScreenWindowUpdates() { if (_listenForScreenWindowUpdates) { return; } connect(_view->screenWindow(), &Konsole::ScreenWindow::outputChanged, this, &Konsole::SessionController::updateSearchFilter); connect(_view->screenWindow(), &Konsole::ScreenWindow::scrolled, this, &Konsole::SessionController::updateSearchFilter); connect(_view->screenWindow(), &Konsole::ScreenWindow::currentResultLineChanged, _view.data(), QOverload<>::of(&Konsole::TerminalDisplay::update)); _listenForScreenWindowUpdates = true; } void SessionController::updateSearchFilter() { if ((_searchFilter != nullptr) && (!_searchBar.isNull())) { _view->processFilters(); } } void SessionController::searchBarEvent() { QString selectedText = _view->screenWindow()->selectedText(Screen::PreserveLineBreaks | Screen::TrimLeadingWhitespace | Screen::TrimTrailingWhitespace); if (!selectedText.isEmpty()) { _searchBar->setSearchText(selectedText); } if (_searchBar->isVisible()) { _searchBar->focusLineEdit(); } else { searchHistory(true); _isSearchBarEnabled = true; } } void SessionController::enableSearchBar(bool showSearchBar) { if (_searchBar.isNull()) { return; } if (showSearchBar && !_searchBar->isVisible()) { setSearchStartToWindowCurrentLine(); } _searchBar->setVisible(showSearchBar); if (showSearchBar) { connect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchChanged, this, &Konsole::SessionController::searchTextChanged); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchReturnPressed, this, &Konsole::SessionController::findPreviousInHistory); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchShiftPlusReturnPressed, this, &Konsole::SessionController::findNextInHistory); } else { disconnect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchChanged, this, &Konsole::SessionController::searchTextChanged); disconnect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchReturnPressed, this, &Konsole::SessionController::findPreviousInHistory); disconnect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchShiftPlusReturnPressed, this, &Konsole::SessionController::findNextInHistory); if ((!_view.isNull()) && (_view->screenWindow() != nullptr)) { _view->screenWindow()->setCurrentResultLine(-1); } } } bool SessionController::reverseSearchChecked() const { Q_ASSERT(_searchBar); QBitArray options = _searchBar->optionsChecked(); return options.at(IncrementalSearchBar::ReverseSearch); } QRegularExpression SessionController::regexpFromSearchBarOptions() const { QBitArray options = _searchBar->optionsChecked(); QString text(_searchBar->searchText()); QRegularExpression regExp; if (options.at(IncrementalSearchBar::RegExp)) { regExp.setPattern(text); } else { regExp.setPattern(QRegularExpression::escape(text)); } if (!options.at(IncrementalSearchBar::MatchCase)) { regExp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); } return regExp; } // searchHistory() may be called either as a result of clicking a menu item or // as a result of changing the search bar widget void SessionController::searchHistory(bool showSearchBar) { enableSearchBar(showSearchBar); if (!_searchBar.isNull()) { if (showSearchBar) { removeSearchFilter(); listenForScreenWindowUpdates(); _searchFilter = new RegExpFilter(); _searchFilter->setRegExp(regexpFromSearchBarOptions()); _view->filterChain()->addFilter(_searchFilter); _view->processFilters(); setFindNextPrevEnabled(true); } else { setFindNextPrevEnabled(false); removeSearchFilter(); _view->setFocus(Qt::ActiveWindowFocusReason); } } } void SessionController::setFindNextPrevEnabled(bool enabled) { _findNextAction->setEnabled(enabled); _findPreviousAction->setEnabled(enabled); } void SessionController::searchTextChanged(const QString& text) { Q_ASSERT(_view->screenWindow()); if (_searchText == text) { return; } _searchText = text; if (text.isEmpty()) { _view->screenWindow()->clearSelection(); _view->screenWindow()->scrollTo(_searchStartLine); } // update search. this is called even when the text is // empty to clear the view's filters beginSearch(text , reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch); } void SessionController::searchCompleted(bool success) { _prevSearchResultLine = _view->screenWindow()->currentResultLine(); if (!_searchBar.isNull()) { _searchBar->setFoundMatch(success); } } void SessionController::beginSearch(const QString& text, Enum::SearchDirection direction) { Q_ASSERT(_searchBar); Q_ASSERT(_searchFilter); QRegularExpression regExp = regexpFromSearchBarOptions(); _searchFilter->setRegExp(regExp); if (_searchStartLine < 0 || _searchStartLine > _view->screenWindow()->lineCount()) { if (direction == Enum::ForwardsSearch) { setSearchStartTo(_view->screenWindow()->currentLine()); } else { setSearchStartTo(_view->screenWindow()->currentLine() + _view->screenWindow()->windowLines()); } } if (!regExp.pattern().isEmpty()) { _view->screenWindow()->setCurrentResultLine(-1); auto task = new SearchHistoryTask(this); connect(task, &Konsole::SearchHistoryTask::completed, this, &Konsole::SessionController::searchCompleted); task->setRegExp(regExp); task->setSearchDirection(direction); task->setAutoDelete(true); task->setStartLine(_searchStartLine); task->addScreenWindow(_session , _view->screenWindow()); task->execute(); } else if (text.isEmpty()) { searchCompleted(false); } _view->processFilters(); } void SessionController::highlightMatches(bool highlight) { if (highlight) { _view->filterChain()->addFilter(_searchFilter); _view->processFilters(); } else { _view->filterChain()->removeFilter(_searchFilter); } _view->update(); } void SessionController::searchFrom() { Q_ASSERT(_searchBar); Q_ASSERT(_searchFilter); if (reverseSearchChecked()) { setSearchStartTo(_view->screenWindow()->lineCount()); } else { setSearchStartTo(0); } beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch); } void SessionController::findNextInHistory() { Q_ASSERT(_searchBar); Q_ASSERT(_searchFilter); setSearchStartTo(_prevSearchResultLine); beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch); } void SessionController::findPreviousInHistory() { Q_ASSERT(_searchBar); Q_ASSERT(_searchFilter); setSearchStartTo(_prevSearchResultLine); beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::ForwardsSearch : Enum::BackwardsSearch); } void SessionController::changeSearchMatch() { Q_ASSERT(_searchBar); Q_ASSERT(_searchFilter); // reset Selection for new case match _view->screenWindow()->clearSelection(); beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch); } void SessionController::showHistoryOptions() { QScopedPointer dialog(new HistorySizeDialog(QApplication::activeWindow())); const HistoryType& currentHistory = _session->historyType(); if (currentHistory.isEnabled()) { if (currentHistory.isUnlimited()) { dialog->setMode(Enum::UnlimitedHistory); } else { dialog->setMode(Enum::FixedSizeHistory); dialog->setLineCount(currentHistory.maximumLineCount()); } } else { dialog->setMode(Enum::NoHistory); } QPointer guard(_session); int result = dialog->exec(); if (guard.isNull()) { return; } if (result != 0) { scrollBackOptionsChanged(dialog->mode(), dialog->lineCount()); } } void SessionController::sessionResizeRequest(const QSize& size) { ////qDebug() << "View resize requested to " << size; _view->setSize(size.width(), size.height()); } void SessionController::scrollBackOptionsChanged(int mode, int lines) { switch (mode) { case Enum::NoHistory: _session->setHistoryType(HistoryTypeNone()); break; case Enum::FixedSizeHistory: _session->setHistoryType(CompactHistoryType(lines)); break; case Enum::UnlimitedHistory: _session->setHistoryType(HistoryTypeFile()); break; } } void SessionController::print_screen() { QPrinter printer; QPointer dialog = new QPrintDialog(&printer, _view); auto options = new PrintOptions(); dialog->setOptionTabs(QList() << options); dialog->setWindowTitle(i18n("Print Shell")); connect(dialog.data(), QOverload<>::of(&QPrintDialog::accepted), options, &Konsole::PrintOptions::saveSettings); if (dialog->exec() != QDialog::Accepted) { return; } QPainter painter; painter.begin(&printer); KConfigGroup configGroup(KSharedConfig::openConfig(), "PrintOptions"); if (configGroup.readEntry("ScaleOutput", true)) { double scale = qMin(printer.pageRect().width() / static_cast(_view->width()), printer.pageRect().height() / static_cast(_view->height())); painter.scale(scale, scale); } _view->printContent(painter, configGroup.readEntry("PrinterFriendly", true)); } void SessionController::saveHistory() { SessionTask* task = new SaveHistoryTask(this); task->setAutoDelete(true); task->addSession(_session); task->execute(); } void SessionController::clearHistory() { _session->clearHistory(); _view->updateImage(); // To reset view scrollbar _view->repaint(); } void SessionController::clearHistoryAndReset() { Profile::Ptr profile = SessionManager::instance()->sessionProfile(_session); QByteArray name = profile->defaultEncoding().toUtf8(); Emulation* emulation = _session->emulation(); emulation->reset(); _session->refresh(); _session->setCodec(QTextCodec::codecForName(name)); clearHistory(); } void SessionController::increaseFontSize() { _view->increaseFontSize(); } void SessionController::decreaseFontSize() { _view->decreaseFontSize(); } void SessionController::resetFontSize() { _view->resetFontSize(); } void SessionController::monitorActivity(bool monitor) { _session->setMonitorActivity(monitor); } void SessionController::monitorSilence(bool monitor) { _session->setMonitorSilence(monitor); } void SessionController::updateSessionIcon() { // If the default profile icon is being used, don't put it on the tab // Only show the icon if the user specifically chose one if (_session->iconName() == QStringLiteral("utilities-terminal")) { _sessionIconName = QString(); } else { _sessionIconName = _session->iconName(); } _sessionIcon = QIcon::fromTheme(_sessionIconName); // Visualize that the session is broadcasting to others if ((_copyToGroup != nullptr) && _copyToGroup->sessions().count() > 1) { // Master Mode: set different icon, to warn the user to be careful setIcon(*_broadcastIcon); } else { if (!_keepIconUntilInteraction) { // Not in Master Mode: use normal icon setIcon(_sessionIcon); } } } void SessionController::updateReadOnlyActionStates() { bool readonly = isReadOnly(); QAction *readonlyAction = actionCollection()->action(QStringLiteral("view-readonly")); Q_ASSERT(readonlyAction != nullptr); readonlyAction->setIcon(QIcon::fromTheme(readonly ? QStringLiteral("object-locked") : QStringLiteral("object-unlocked"))); readonlyAction->setChecked(readonly); auto updateActionState = [this, readonly](const QString &name) { QAction *action = actionCollection()->action(name); if (action != nullptr) { action->setEnabled(!readonly); } }; updateActionState(QStringLiteral("edit_paste")); updateActionState(QStringLiteral("clear-history")); updateActionState(QStringLiteral("clear-history-and-reset")); updateActionState(QStringLiteral("edit-current-profile")); updateActionState(QStringLiteral("switch-profile")); updateActionState(QStringLiteral("adjust-history")); updateActionState(QStringLiteral("send-signal")); updateActionState(QStringLiteral("zmodem-upload")); _codecAction->setEnabled(!readonly); // Without the timer, when detaching a tab while the message widget is visible, // the size of the terminal becomes really small... QTimer::singleShot(0, this, [this, readonly]() { _view->updateReadOnlyState(readonly); }); } bool SessionController::isReadOnly() const { if (!_session.isNull()) { return _session->isReadOnly(); } else { return false; } } void SessionController::sessionAttributeChanged() { if (_sessionIconName != _session->iconName()) { updateSessionIcon(); } QString title = _session->title(Session::DisplayedTitleRole); // special handling for the "%w" marker which is replaced with the // window title set by the shell title.replace(QLatin1String("%w"), _session->userTitle()); // special handling for the "%#" marker which is replaced with the // number of the shell title.replace(QLatin1String("%#"), QString::number(_session->sessionId())); if (title.isEmpty()) { title = _session->title(Session::NameRole); } setTitle(title); emit rawTitleChanged(); } void SessionController::sessionReadOnlyChanged() { // Trigger icon update sessionAttributeChanged(); updateReadOnlyActionStates(); // Update all views foreach (TerminalDisplay* view, session()->views()) { if (view != _view.data()) { view->updateReadOnlyState(isReadOnly()); } } } void SessionController::showDisplayContextMenu(const QPoint& position) { // needed to make sure the popup menu is available, even if a hosting // application did not merge our GUI. if (factory() == nullptr) { if (clientBuilder() == nullptr) { setClientBuilder(new KXMLGUIBuilder(_view)); } auto factory = new KXMLGUIFactory(clientBuilder(), this); factory->addClient(this); ////qDebug() << "Created xmlgui factory" << factory; } QPointer popup = qobject_cast(factory()->container(QStringLiteral("session-popup-menu"), this)); if (!popup.isNull()) { updateReadOnlyActionStates(); // prepend content-specific actions such as "Open Link", "Copy Email Address" etc. QList contentActions = _view->filterActions(position); auto contentSeparator = new QAction(popup); contentSeparator->setSeparator(true); contentActions << contentSeparator; popup->insertActions(popup->actions().value(0, nullptr), contentActions); // always update this submenu before showing the context menu, // because the available search services might have changed // since the context menu is shown last time updateWebSearchMenu(); _preventClose = true; if (_showMenuAction != nullptr) { if ( _showMenuAction->isChecked() ) { popup->removeAction( _showMenuAction); } else { popup->insertAction(_switchProfileMenu, _showMenuAction); } } QAction* chosen = popup->exec(_view->mapToGlobal(position)); // check for validity of the pointer to the popup menu if (!popup.isNull()) { delete contentSeparator; } _preventClose = false; if ((chosen != nullptr) && chosen->objectName() == QLatin1String("close-session")) { chosen->trigger(); } } else { qCDebug(KonsoleDebug) << "Unable to display popup menu for session" << _session->title(Session::NameRole) << ", no GUI factory available to build the popup."; } } void SessionController::movementKeyFromSearchBarReceived(QKeyEvent *event) { QCoreApplication::sendEvent(_view, event); setSearchStartToWindowCurrentLine(); } void SessionController::sessionStateChanged(int state) { if (state == _previousState) { return; } if (state == NOTIFYACTIVITY) { setIcon(*_activityIcon); _keepIconUntilInteraction = true; } else if (state == NOTIFYSILENCE) { setIcon(*_silenceIcon); _keepIconUntilInteraction = true; } else if (state == NOTIFYBELL) { setIcon(*_bellIcon); _keepIconUntilInteraction = true; } else if (state == NOTIFYNORMAL) { updateSessionIcon(); } _previousState = state; } void SessionController::zmodemDownload() { QString zmodem = QStandardPaths::findExecutable(QStringLiteral("rz")); if (zmodem.isEmpty()) { zmodem = QStandardPaths::findExecutable(QStringLiteral("lrz")); } if (!zmodem.isEmpty()) { const QString path = QFileDialog::getExistingDirectory(_view, i18n("Save ZModem Download to..."), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (!path.isEmpty()) { _session->startZModem(zmodem, path, QStringList()); return; } } else { KMessageBox::error(_view, i18n("

A ZModem file transfer attempt has been detected, " "but no suitable ZModem software was found on this system.

" "

You may wish to install the 'rzsz' or 'lrzsz' package.

")); } _session->cancelZModem(); } void SessionController::zmodemUpload() { if (_session->isZModemBusy()) { KMessageBox::sorry(_view, i18n("

The current session already has a ZModem file transfer in progress.

")); return; } QString zmodem = QStandardPaths::findExecutable(QStringLiteral("sz")); if (zmodem.isEmpty()) { zmodem = QStandardPaths::findExecutable(QStringLiteral("lsz")); } if (zmodem.isEmpty()) { KMessageBox::sorry(_view, i18n("

No suitable ZModem software was found on this system.

" "

You may wish to install the 'rzsz' or 'lrzsz' package.

")); return; } QStringList files = QFileDialog::getOpenFileNames(_view, i18n("Select Files for ZModem Upload"), QDir::homePath()); if (!files.isEmpty()) { _session->startZModem(zmodem, QString(), files); } } bool SessionController::isKonsolePart() const { // Check to see if we are being called from Konsole or a KPart return !(qApp->applicationName() == QLatin1String("konsole")); } QString SessionController::userTitle() const { if (!_session.isNull()) { return _session->userTitle(); } else { return QString(); } } diff --git a/src/TerminalHeaderBar.cpp b/src/TerminalHeaderBar.cpp index e917ecd9..e8fb37f1 100644 --- a/src/TerminalHeaderBar.cpp +++ b/src/TerminalHeaderBar.cpp @@ -1,184 +1,184 @@ /* * This file is part of Konsole, a terminal emulator for KDE. * * Copyright 2012 Frederik Gladhorn * * 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. */ #include "TerminalHeaderBar.h" #include "TerminalDisplay.h" #include "SessionController.h" #include "ViewProperties.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Konsole { TerminalHeaderBar::TerminalHeaderBar(QWidget *parent) : QWidget(parent) { m_closeBtn = new QToolButton(this); m_closeBtn->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); m_closeBtn->setToolTip(i18nc("@info:tooltip", "Close terminal")); m_closeBtn->setText(i18nc("@info:tooltip", "Close terminal")); m_closeBtn->setObjectName(QStringLiteral("close-terminal-button")); m_closeBtn->setAutoRaise(true); m_toggleExpandedMode = new QToolButton(this); m_toggleExpandedMode->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); // fake 'expand' icon. VDG input? m_toggleExpandedMode->setAutoRaise(true); m_toggleExpandedMode->setCheckable(true); m_toggleExpandedMode->setToolTip(i18nc("@info:tooltip", "Maximize terminal")); m_terminalTitle = new QLabel(this); m_terminalTitle->setFont(QApplication::font()); m_terminalIcon = new QLabel(this); m_terminalActivity = new QLabel(this); m_boxLayout = new QBoxLayout(QBoxLayout::LeftToRight); m_boxLayout->setSpacing(0); m_boxLayout->setMargin(0); // Layout Setup m_boxLayout->addStretch(); m_boxLayout->addWidget(m_terminalIcon); m_boxLayout->addWidget(m_terminalTitle); m_boxLayout->addWidget(m_terminalActivity); m_boxLayout->addStretch(); m_boxLayout->addWidget(m_toggleExpandedMode); m_boxLayout->addWidget(m_closeBtn); setLayout(m_boxLayout); setAutoFillBackground(true); terminalFocusOut(); connect(m_toggleExpandedMode, &QToolButton::clicked, this, &TerminalHeaderBar::requestToggleExpansion); } // Hack untill I can detangle the creation of the TerminalViews void TerminalHeaderBar::finishHeaderSetup(ViewProperties *properties) { auto controller = dynamic_cast(properties); connect(properties, &Konsole::ViewProperties::titleChanged, this, [this, properties]{ m_terminalTitle->setText(properties->title()); }); connect(properties, &Konsole::ViewProperties::iconChanged, this, [this, properties] { m_terminalIcon->setPixmap(properties->icon().pixmap(QSize(22,22))); }); connect(properties, &Konsole::ViewProperties::activity, this, [this]{ m_terminalActivity->setPixmap(QPixmap()); }); connect(m_closeBtn, &QToolButton::clicked, controller, &SessionController::closeSession); } void TerminalHeaderBar::paintEvent(QPaintEvent *paintEvent) { /* Try to get the widget that's 10px above this one. * If the widget is something else than a TerminalWidget, a TabBar or a QSplitter, * draw a 1px line to separate it from the others. */ const auto globalPos = parentWidget()->mapToGlobal(pos()); auto *widget = qApp->widgetAt(globalPos.x() + 10, globalPos.y() - 10); - const bool isTabbar = qobject_cast(widget); - const bool isTerminalWidget = qobject_cast(widget); - const bool isSplitter = qobject_cast(widget) || qobject_cast(widget); - if (widget && !isTabbar && !isTerminalWidget && !isSplitter) { + const bool isTabbar = qobject_cast(widget) != nullptr; + const bool isTerminalWidget = qobject_cast(widget) != nullptr; + const bool isSplitter = (qobject_cast(widget) != nullptr) || (qobject_cast(widget) != nullptr); + if ((widget != nullptr) && !isTabbar && !isTerminalWidget && !isSplitter) { QStyleOptionTabBarBase optTabBase; QStylePainter p(this); optTabBase.init(this); optTabBase.shape = QTabBar::Shape::RoundedSouth; optTabBase.documentMode = false; p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase); } QWidget::paintEvent(paintEvent); if (!m_terminalIsFocused) { auto p = qApp->palette(); auto shadowColor = p.color(QPalette::ColorRole::Shadow); shadowColor.setAlphaF( 0.2f * shadowColor.alphaF() ); // same as breeze. QPainter painter(this); painter.setPen(Qt::NoPen); painter.setBrush(shadowColor); painter.drawRect(rect()); } } void TerminalHeaderBar::mouseMoveEvent(QMouseEvent* ev) { if (m_toggleExpandedMode->isChecked()) { return; } auto point = ev->pos() - m_startDrag; if (point.manhattanLength() > 10) { auto drag = new QDrag(parent()); auto mimeData = new QMimeData(); QByteArray payload; payload.setNum(qApp->applicationPid()); mimeData->setData(QStringLiteral("konsole/terminal_display"), payload); drag->setMimeData(mimeData); drag->start(); } } void TerminalHeaderBar::mousePressEvent(QMouseEvent* ev) { m_startDrag = ev->pos(); } void TerminalHeaderBar::mouseReleaseEvent(QMouseEvent* ev) { Q_UNUSED(ev); } void TerminalHeaderBar::terminalFocusIn() { m_terminalIsFocused = true; update(); } void TerminalHeaderBar::terminalFocusOut() { m_terminalIsFocused = false; update(); } } diff --git a/src/ViewSplitter.cpp b/src/ViewSplitter.cpp index a5b5433f..bb316e63 100644 --- a/src/ViewSplitter.cpp +++ b/src/ViewSplitter.cpp @@ -1,357 +1,357 @@ /* This file is part of the Konsole Terminal. Copyright 2006-2008 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 "ViewSplitter.h" // Qt #include #include #include #include #include #include #include #include #include #include // Konsole #include "ViewContainer.h" #include "TerminalDisplay.h" using Konsole::ViewSplitter; using Konsole::TerminalDisplay; //TODO: Connect the TerminalDisplay destroyed signal here. ViewSplitter::ViewSplitter(QWidget *parent) : QSplitter(parent) { setAcceptDrops(true); } /* This function is called on the toplevel splitter, we need to look at the actual ViewSplitter inside it */ void ViewSplitter::adjustActiveTerminalDisplaySize(int percentage) { auto focusedTerminalDisplay = activeTerminalDisplay(); Q_ASSERT(focusedTerminalDisplay); auto parentSplitter = qobject_cast(focusedTerminalDisplay->parent()); const int containerIndex = parentSplitter->indexOf(activeTerminalDisplay()); Q_ASSERT(containerIndex != -1); QList containerSizes = parentSplitter->sizes(); const int oldSize = containerSizes[containerIndex]; const auto newSize = static_cast(oldSize * (1.0 + percentage / 100.0)); const int perContainerDelta = (count() == 1) ? 0 : ((newSize - oldSize) / (count() - 1)) * (-1); for (int& size : containerSizes) { size += perContainerDelta; } containerSizes[containerIndex] = newSize; parentSplitter->setSizes(containerSizes); } // Get the first splitter that's a parent of the current focused widget. ViewSplitter *ViewSplitter::activeSplitter() { QWidget *widget = focusWidget() != nullptr ? focusWidget() : this; ViewSplitter *splitter = nullptr; while ((splitter == nullptr) && (widget != nullptr)) { splitter = qobject_cast(widget); widget = widget->parentWidget(); } Q_ASSERT(splitter); return splitter; } void ViewSplitter::updateSizes() { const int space = (orientation() == Qt::Horizontal ? width() : height()) / count(); setSizes(QVector(count(), space).toList()); } void ViewSplitter::addTerminalDisplay(TerminalDisplay *terminalDisplay, Qt::Orientation containerOrientation, AddBehavior behavior) { ViewSplitter *splitter = activeSplitter(); - const int currentIndex = !splitter->activeTerminalDisplay() ? splitter->count() + const int currentIndex = splitter->activeTerminalDisplay() == nullptr ? splitter->count() : splitter->indexOf(splitter->activeTerminalDisplay()); if (splitter->count() < 2) { splitter->insertWidget(behavior == AddBehavior::AddBefore ? currentIndex : currentIndex + 1, terminalDisplay); splitter->setOrientation(containerOrientation); } else if (containerOrientation == splitter->orientation()) { splitter->insertWidget(currentIndex, terminalDisplay); } else { auto newSplitter = new ViewSplitter(); TerminalDisplay *oldTerminalDisplay = splitter->activeTerminalDisplay(); const int oldContainerIndex = splitter->indexOf(oldTerminalDisplay); newSplitter->addWidget(behavior == AddBehavior::AddBefore ? terminalDisplay : oldTerminalDisplay); newSplitter->addWidget(behavior == AddBehavior::AddBefore ? oldTerminalDisplay : terminalDisplay); newSplitter->setOrientation(containerOrientation); newSplitter->updateSizes(); newSplitter->show(); splitter->insertWidget(oldContainerIndex, newSplitter); } splitter->updateSizes(); } void ViewSplitter::childEvent(QChildEvent *event) { QSplitter::childEvent(event); if (event->removed()) { if (count() == 0) { deleteLater(); } - if (!findChild()) { + if (findChild() == nullptr) { deleteLater(); } } auto terminals = getToplevelSplitter()->findChildren(); if (terminals.size() == 1) { terminals.at(0)->headerBar()->setVisible(false); } else { for(auto terminal : terminals) { terminal->headerBar()->setVisible(true); } } } void ViewSplitter::handleFocusDirection(Qt::Orientation orientation, int direction) { auto terminalDisplay = activeTerminalDisplay(); auto parentSplitter = qobject_cast(terminalDisplay->parentWidget()); auto topSplitter = parentSplitter->getToplevelSplitter(); const auto handleWidth = parentSplitter->handleWidth() <= 1 ? 4 : parentSplitter->handleWidth(); const auto start = QPoint(terminalDisplay->x(), terminalDisplay->y()); const auto startMapped = parentSplitter->mapTo(topSplitter, start); const int newX = orientation != Qt::Horizontal ? startMapped.x() + handleWidth : direction == 1 ? startMapped.x() + terminalDisplay->width() + handleWidth : startMapped.x() - handleWidth; const int newY = orientation != Qt::Vertical ? startMapped.y() + handleWidth : direction == 1 ? startMapped.y() + terminalDisplay->height() + handleWidth : startMapped.y() - handleWidth; const auto newPoint = QPoint(newX, newY); auto child = topSplitter->childAt(newPoint); if (auto* terminal = qobject_cast(child)) { terminal->setFocus(Qt::OtherFocusReason); - } else if (qobject_cast(child)) { + } else if (qobject_cast(child) != nullptr) { auto targetSplitter = qobject_cast(child->parent()); auto splitterTerminal = qobject_cast(targetSplitter->widget(0)); splitterTerminal->setFocus(Qt::OtherFocusReason); - } else if (qobject_cast(child)) { + } else if (qobject_cast(child) != nullptr) { TerminalDisplay *terminalParent = nullptr; - while(!terminalParent) { + while(terminalParent == nullptr) { terminalParent = qobject_cast(child->parentWidget()); child = child->parentWidget(); } terminalParent->setFocus(Qt::OtherFocusReason); } } void ViewSplitter::focusUp() { handleFocusDirection(Qt::Vertical, -1); } void ViewSplitter::focusDown() { handleFocusDirection(Qt::Vertical, +1); } void ViewSplitter::focusLeft() { handleFocusDirection(Qt::Horizontal, -1); } void ViewSplitter::focusRight() { handleFocusDirection(Qt::Horizontal, +1); } TerminalDisplay *ViewSplitter::activeTerminalDisplay() const { auto focusedWidget = qobject_cast(focusWidget()); - return focusedWidget ? focusedWidget : findChild(); + return focusedWidget != nullptr ? focusedWidget : findChild(); } void ViewSplitter::toggleMaximizeCurrentTerminal() { m_terminalMaximized = !m_terminalMaximized; handleMinimizeMaximize(m_terminalMaximized); } namespace { void restoreAll(QList&& terminalDisplays, QList&& splitters) { for (auto splitter : splitters) { splitter->setVisible(true); } for (auto terminalDisplay : terminalDisplays) { terminalDisplay->setVisible(true); } } } bool ViewSplitter::hideRecurse(TerminalDisplay *currentTerminalDisplay) { bool allHidden = true; for(int i = 0, end = count(); i < end; i++) { if (auto *maybeSplitter = qobject_cast(widget(i))) { allHidden = maybeSplitter->hideRecurse(currentTerminalDisplay) && allHidden; continue; } if (auto maybeTerminalDisplay = qobject_cast(widget(i))) { if (maybeTerminalDisplay == currentTerminalDisplay) { allHidden = false; } else { maybeTerminalDisplay->setVisible(false); } } } if (allHidden) { setVisible(false); } return allHidden; } void ViewSplitter::handleMinimizeMaximize(bool maximize) { auto topLevelSplitter = getToplevelSplitter(); auto currentTerminalDisplay = topLevelSplitter->activeTerminalDisplay(); if (maximize) { for (int i = 0, end = topLevelSplitter->count(); i < end; i++) { auto widgetAt = topLevelSplitter->widget(i); if (auto *maybeSplitter = qobject_cast(widgetAt)) { maybeSplitter->hideRecurse(currentTerminalDisplay); } if (auto maybeTerminalDisplay = qobject_cast(widgetAt)) { if (maybeTerminalDisplay != currentTerminalDisplay) { maybeTerminalDisplay->setVisible(false); } } } } else { restoreAll(topLevelSplitter->findChildren(), topLevelSplitter->findChildren()); } } ViewSplitter *ViewSplitter::getToplevelSplitter() { ViewSplitter *current = this; - while(qobject_cast(current->parentWidget())) { + while(qobject_cast(current->parentWidget()) != nullptr) { current = qobject_cast(current->parentWidget()); } return current; } namespace { TerminalDisplay *currentDragTarget = nullptr; } void Konsole::ViewSplitter::dragEnterEvent(QDragEnterEvent* ev) { const auto dragId = QStringLiteral("konsole/terminal_display"); if (ev->mimeData()->hasFormat(dragId)) { auto other_pid = ev->mimeData()->data(dragId).toInt(); // don't accept the drop if it's another instance of konsole if (qApp->applicationPid() != other_pid) { return; } if (getToplevelSplitter()->terminalMaximized()) { return; } ev->accept(); } } void Konsole::ViewSplitter::dragMoveEvent(QDragMoveEvent* ev) { auto currentWidget = childAt(ev->pos()); if (auto terminal = qobject_cast(currentWidget)) { - if (currentDragTarget && currentDragTarget != terminal) { + if ((currentDragTarget != nullptr) && currentDragTarget != terminal) { currentDragTarget->hideDragTarget(); } if (terminal == ev->source()) { return; } currentDragTarget = terminal; currentDragTarget->showDragTarget(); } } void Konsole::ViewSplitter::dragLeaveEvent(QDragLeaveEvent* event) { Q_UNUSED(event); - if (currentDragTarget) { + if (currentDragTarget != nullptr) { currentDragTarget->hideDragTarget(); currentDragTarget = nullptr; } } void Konsole::ViewSplitter::dropEvent(QDropEvent* ev) { if (ev->mimeData()->hasFormat(QStringLiteral("konsole/terminal_display"))) { if (getToplevelSplitter()->terminalMaximized()) { return; } - if (currentDragTarget) { + if (currentDragTarget != nullptr) { currentDragTarget->hideDragTarget(); auto source = qobject_cast(ev->source()); source->setVisible(false); source->setParent(nullptr); currentDragTarget->setFocus(Qt::OtherFocusReason); const auto droppedEdge = currentDragTarget->droppedEdge(); AddBehavior behavior = droppedEdge == Qt::LeftEdge || droppedEdge == Qt::TopEdge ? AddBehavior::AddBefore : AddBehavior::AddAfter; Qt::Orientation orientation = droppedEdge == Qt::LeftEdge || droppedEdge == Qt::RightEdge ? Qt::Horizontal : Qt::Vertical; // topLevel is the splitter that's connected with the ViewManager // that in turn can call the SessionController. emit getToplevelSplitter()->terminalDisplayDropped(source); addTerminalDisplay(source, orientation, behavior); source->setVisible(true); currentDragTarget = nullptr; } } } diff --git a/src/autotests/CharacterColorTest.cpp b/src/autotests/CharacterColorTest.cpp index 5c2600ac..4a4bef61 100644 --- a/src/autotests/CharacterColorTest.cpp +++ b/src/autotests/CharacterColorTest.cpp @@ -1,212 +1,212 @@ /* Copyright 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 "CharacterColorTest.h" // Qt #include #include // KDE #include using namespace Konsole; const ColorEntry CharacterColorTest::DefaultColorTable[TABLE_COLORS] = { ColorEntry(0x00, 0x00, 0x00), // Dfore ColorEntry(0xFF, 0xFF, 0xFF), // Dback ColorEntry(0x00, 0x00, 0x00), // Black ColorEntry(0xB2, 0x18, 0x18), // Red ColorEntry(0x18, 0xB2, 0x18), // Green ColorEntry(0xB2, 0x68, 0x18), // Yellow ColorEntry(0x18, 0x18, 0xB2), // Blue ColorEntry(0xB2, 0x18, 0xB2), // Magenta ColorEntry(0x18, 0xB2, 0xB2), // Cyan ColorEntry(0xB2, 0xB2, 0xB2), // White // intensive versions ColorEntry(0x00, 0x00, 0x00), ColorEntry(0xFF, 0xFF, 0xFF), ColorEntry(0x68, 0x68, 0x68), ColorEntry(0xFF, 0x54, 0x54), ColorEntry(0x54, 0xFF, 0x54), ColorEntry(0xFF, 0xFF, 0x54), ColorEntry(0x54, 0x54, 0xFF), ColorEntry(0xFF, 0x54, 0xFF), ColorEntry(0x54, 0xFF, 0xFF), ColorEntry(0xFF, 0xFF, 0xFF), // Here are faint intensities, which may not be good. // faint versions ColorEntry(0x00, 0x00, 0x00), ColorEntry(0xFF, 0xFF, 0xFF), ColorEntry(0x00, 0x00, 0x00), ColorEntry(0x65, 0x00, 0x00), ColorEntry(0x00, 0x65, 0x00), ColorEntry(0x65, 0x5E, 0x00), ColorEntry(0x00, 0x00, 0x65), ColorEntry(0x65, 0x00, 0x65), ColorEntry(0x00, 0x65, 0x65), ColorEntry(0x65, 0x65, 0x65) }; void CharacterColorTest::init() { } void CharacterColorTest::cleanup() { } void CharacterColorTest::testColorEntry() { ColorEntry black = ColorEntry(0x00, 0x00, 0x00); ColorEntry white = ColorEntry(0xFF, 0xFF, 0xFF); ColorEntry red = ColorEntry(0xB2, 0x18, 0x18); ColorEntry green = ColorEntry(0x18, 0xB2, 0x18); // Test operator== operator!= QCOMPARE(black == white, false); QCOMPARE(black != white, true); QCOMPARE(red == green, false); QCOMPARE(red != green, true); QCOMPARE(red == green, false); QCOMPARE(red != green, true); // Test operator= ColorEntry tmpColorEntry; tmpColorEntry = red; QCOMPARE(tmpColorEntry == red, true); QCOMPARE(red == tmpColorEntry, true); // Test ColorEntry() ColorEntry defaultColorEntry = ColorEntry(); QCOMPARE(defaultColorEntry != green, true); QCOMPARE(defaultColorEntry != black, true); QCOMPARE(defaultColorEntry.isValid(), false); } void CharacterColorTest::testDummyConstructor() { CharacterColor charColor; QCOMPARE(charColor.isValid(), false); } void CharacterColorTest::testColorSpaceDefault_data() { QTest::addColumn("colorValue"); QTest::addColumn("expected"); QTest::newRow("color 0") << 0 << DefaultColorTable[0]; QTest::newRow("color 1") << 1 << DefaultColorTable[1]; } void CharacterColorTest::testColorSpaceDefault() { QFETCH(int, colorValue); QFETCH(QColor, expected); CharacterColor charColor(COLOR_SPACE_DEFAULT, colorValue); const QColor result = charColor.color(DefaultColorTable); QCOMPARE(result, expected); } void CharacterColorTest::testColorSpaceSystem_data() { QTest::addColumn("colorValue"); QTest::addColumn("expected"); QTest::newRow("color 0") << 0 << DefaultColorTable[2 + 0]; QTest::newRow("color 1") << 1 << DefaultColorTable[2 + 1]; QTest::newRow("color 7") << 7 << DefaultColorTable[2 + 7]; } void CharacterColorTest::testColorSpaceSystem() { QFETCH(int, colorValue); QFETCH(QColor, expected); CharacterColor charColor(COLOR_SPACE_SYSTEM, colorValue); const QColor result = charColor.color(DefaultColorTable); QCOMPARE(result, expected); } void CharacterColorTest::testColorSpaceRGB_data() { QTest::addColumn("colorValue"); QTest::addColumn("expected"); // Pick colors to test or test all if needed for (const int i : {0, 1, 64, 127, 128, 215, 255}) { const QString name = QStringLiteral("color %1").arg(i); QTest::newRow(qPrintable(name)) << i << QColor(i >> 16, i >> 8, i); } } void CharacterColorTest::testColorSpaceRGB() { QFETCH(int, colorValue); QFETCH(QColor, expected); CharacterColor charColor(COLOR_SPACE_RGB, colorValue); const QColor result = charColor.color(DefaultColorTable); QCOMPARE(result, expected); } void CharacterColorTest::testColor256_data() { QTest::addColumn("colorValue"); QTest::addColumn("expected"); // This might be overkill for (int i = 0; i < 8; ++i) { const QString name = QStringLiteral("color256 color %1").arg(i); QTest::newRow(qPrintable(name)) << i << DefaultColorTable[i + 2]; } for (int i = 8; i < 16; ++i) { const QString name = QStringLiteral("color256 color %1").arg(i); QTest::newRow(qPrintable(name)) << i << DefaultColorTable[i + 2 + 10 - 8]; } for (int i = 16; i < 232; ++i) { const QString name = QStringLiteral("color256 color %1").arg(i); const auto u = i - 16; - const auto color = QColor(((u / 36) % 6) ? (40 * ((u / 36) % 6) + 55) : 0, - ((u / 6) % 6) ? (40 * ((u / 6) % 6) + 55) : 0, - ((u / 1) % 6) ? (40 * ((u / 1) % 6) + 55) : 0); + const auto color = QColor(((u / 36) % 6) != 0 ? (40 * ((u / 36) % 6) + 55) : 0, + ((u / 6) % 6) != 0 ? (40 * ((u / 6) % 6) + 55) : 0, + ((u / 1) % 6) != 0 ? (40 * ((u / 1) % 6) + 55) : 0); QTest::newRow(qPrintable(name)) << i << color; } for (int i = 232; i < 256; ++i) { const QString name = QStringLiteral("color256 color %1").arg(i); const auto gray = (i - 232) * 10 + 8; QTest::newRow(qPrintable(name)) << i << QColor(gray, gray, gray); } } void CharacterColorTest::testColor256() { QFETCH(int, colorValue); QFETCH(QColor, expected); const QColor result = color256(colorValue, DefaultColorTable); QCOMPARE(result, expected); } QTEST_GUILESS_MAIN(CharacterColorTest)