diff --git a/TabBarSettings.ui b/TabBarSettings.ui new file mode 100644 --- /dev/null +++ b/TabBarSettings.ui @@ -0,0 +1,217 @@ + + + TabBarSettings + + + + 0 + 0 + 503 + 397 + + + + + + + Appearance + + + + + + + 1 + 0 + + + + + Always Show Tab Bar + + + + + Show Tab Bar When Needed + + + + + Always Hide Tab Bar + + + + + + + + Expand Individual Tab Widths to Full Window + + + + + + + Use user-defined stylesheet + + + + + + + Tab bar position: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 2 + 0 + + + + text/css + + + + + + + Tab bar visibility: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Show 'New Tab' and 'Close Tab' buttons + + + + + + + + 1 + 0 + + + + + Above Terminal Area + + + + + Below Terminal Area + + + + + + + + QFrame::Box + + + QFrame::Plain + + + Starting with Qt 5.9, the next two options have different effects. + + + Qt::AlignCenter + + + true + + + + + + + Show the 'Close Tab' button individually + + + + + + + + + + Behavior + + + + + + New tab behavior: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 0 + + + + + Put New Tab At The End + + + + + Put New Tab After Current Tab + + + + + + + + + + + Qt::Vertical + + + + 20 + 4 + + + + + + + + + KComboBox + QComboBox +
kcombobox.h
+
+ + KUrlRequester + QFrame +
kurlrequester.h
+ 1 +
+
+ + +
diff --git a/ViewContainer.cpp b/ViewContainer.cpp new file mode 100644 --- /dev/null +++ b/ViewContainer.cpp @@ -0,0 +1,535 @@ +/* + 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 "ViewContainer.h" +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include + +// Konsole +#include "IncrementalSearchBar.h" +#include "ViewProperties.h" +#include "ProfileList.h" +#include "ViewManager.h" +#include "KonsoleSettings.h" +#include "SessionController.h" +#include "DetachableTabBar.h" +#include "TerminalDisplay.h" + +// TODO Perhaps move everything which is Konsole-specific into different files + +using namespace Konsole; + + +TabbedViewContainer::TabbedViewContainer(ViewManager *connectedViewManager, QWidget *parent) : + QTabWidget(parent), + _connectedViewManager(connectedViewManager), + _newTabButton(new QToolButton()), + _closeTabButton(new QToolButton()), + _contextMenuTabIndex(-1), + _navigationVisibility(ViewManager::NavigationVisibility::NavigationNotSet), + _tabHistoryIndex(-1) +{ + setAcceptDrops(true); + + auto tabBarWidget = new DetachableTabBar(); + setTabBar(tabBarWidget); + setDocumentMode(true); + setMovable(true); + connect(tabBarWidget, &DetachableTabBar::moveTabToWindow, this, &TabbedViewContainer::moveTabToWindow); + tabBar()->setContextMenuPolicy(Qt::CustomContextMenu); + _newTabButton->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); + _newTabButton->setAutoRaise(true); + connect(_newTabButton, &QToolButton::clicked, this, [this]{ + emit newViewRequest(this); + }); + + _closeTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); + _closeTabButton->setAutoRaise(true); + connect(_closeTabButton, &QToolButton::clicked, this, [this]{ + closeCurrentTab(); + }); + + connect(tabBar(), &QTabBar::tabBarDoubleClicked, this, + &Konsole::TabbedViewContainer::tabDoubleClicked); + connect(tabBar(), &QTabBar::customContextMenuRequested, this, + &Konsole::TabbedViewContainer::openTabContextMenu); + connect(tabBarWidget, &DetachableTabBar::detachTab, this, [this](int idx) { + emit detachTab(this, terminalAt(idx)); + }); + connect(this, &TabbedViewContainer::currentChanged, this, &TabbedViewContainer::currentTabChanged); + + // The context menu of tab bar + _contextPopupMenu = new QMenu(tabBar()); + connect(_contextPopupMenu, &QMenu::aboutToHide, this, [this]() { + // Remove the read-only action when the popup closes + for (auto &action : _contextPopupMenu->actions()) { + if (action->objectName() == QStringLiteral("view-readonly")) { + _contextPopupMenu->removeAction(action); + break; + } + } + }); + + connect(tabBar(), &QTabBar::tabCloseRequested, this, &TabbedViewContainer::closeTerminalTab); + +#if defined(ENABLE_DETACHING) + auto detachAction = _contextPopupMenu->addAction( + QIcon::fromTheme(QStringLiteral("tab-detach")), + i18nc("@action:inmenu", "&Detach Tab"), this, + [this] { emit detachTab(this, terminalAt(_contextMenuTabIndex)); } + ); + detachAction->setObjectName(QStringLiteral("tab-detach")); +#endif + + auto editAction = _contextPopupMenu->addAction( + QIcon::fromTheme(QStringLiteral("edit-rename")), + i18nc("@action:inmenu", "&Rename Tab..."), this, + [this]{ renameTab(_contextMenuTabIndex); } + ); + editAction->setObjectName(QStringLiteral("edit-rename")); + + auto closeAction = _contextPopupMenu->addAction( + QIcon::fromTheme(QStringLiteral("tab-close")), + i18nc("@action:inmenu", "Close Tab"), this, + [this] { closeTerminalTab(_contextMenuTabIndex); } + ); + closeAction->setObjectName(QStringLiteral("tab-close")); + + auto profileMenu = new QMenu(); + auto profileList = new ProfileList(false, profileMenu); + profileList->syncWidgetActions(profileMenu, true); + connect(profileList, &Konsole::ProfileList::profileSelected, this, + [this](Profile::Ptr profile) { emit newViewWithProfileRequest(this, profile); }); + _newTabButton->setMenu(profileMenu); + + konsoleConfigChanged(); + connect(KonsoleSettings::self(), &KonsoleSettings::configChanged, this, &TabbedViewContainer::konsoleConfigChanged); +} + +TabbedViewContainer::~TabbedViewContainer() +{ + for(int i = 0, end = count(); i < end; i++) { + auto view = widget(i); + disconnect(view, &QWidget::destroyed, this, &Konsole::TabbedViewContainer::viewDestroyed); + } +} + +TerminalDisplay *TabbedViewContainer::terminalAt(int index) +{ + return qobject_cast(widget(index)); +} + +void TabbedViewContainer::moveTabToWindow(int index, QWidget *window) +{ + const int id = terminalAt(index)->sessionController()->identifier(); + // This one line here will be removed as soon as I finish my new split handling. + // it's hacky but it works. + const auto widgets = window->findChildren(); + const auto currentPos = QCursor::pos(); + for(const auto dropWidget : widgets) { + if (dropWidget->rect().contains(dropWidget->mapFromGlobal(currentPos))) { + emit dropWidget->moveViewRequest(-1, id); + removeView(terminalAt(index)); + } + } +} + +void TabbedViewContainer::konsoleConfigChanged() +{ + // don't show tabs if we are in KParts mode. + // This is a hack, and this needs to be rewritten. + // The container should not be part of the KParts, perhaps just the + // TerminalDisplay should. + + // ASAN issue if using sessionController->isKonsolePart(), just + // duplicate code for now + if (qApp->applicationName() != QLatin1String("konsole")) { + tabBar()->setVisible(false); + } else { + // if we start with --show-tabbar or --hide-tabbar we ignore the preferences. + setTabBarAutoHide(KonsoleSettings::tabBarVisibility() == KonsoleSettings::EnumTabBarVisibility::ShowTabBarWhenNeeded); + if (KonsoleSettings::tabBarVisibility() == KonsoleSettings::EnumTabBarVisibility::AlwaysShowTabBar) { + tabBar()->setVisible(true); + } else if (KonsoleSettings::tabBarVisibility() == KonsoleSettings::EnumTabBarVisibility::AlwaysHideTabBar) { + tabBar()->setVisible(false); + } + } + + setTabPosition((QTabWidget::TabPosition) KonsoleSettings::tabBarPosition()); + + setCornerWidget( KonsoleSettings::showQuickButtons() ? _newTabButton : nullptr, Qt::TopLeftCorner); + setCornerWidget( KonsoleSettings::showQuickButtons() ? _closeTabButton : nullptr, Qt::TopRightCorner); + + tabBar()->setExpanding(KonsoleSettings::expandTabWidth()); + tabBar()->update(); + if (isVisible() && KonsoleSettings::showQuickButtons()) { + _newTabButton->setVisible(true); + _closeTabButton->setVisible(true); + } + + tabBar()->setTabsClosable(KonsoleSettings::showCloseTabIndividually()); + + if (KonsoleSettings::tabBarUseUserStyleSheet()) { + setCssFromFile(KonsoleSettings::tabBarUserStyleSheetFile()); + } else { + setCss(); + } +} + +void TabbedViewContainer::setCss(const QString& styleSheet) +{ + static const QString defaultCss = QStringLiteral("QTabWidget::tab-bar, QTabWidget::pane { margin: 0; }\n"); + setStyleSheet(defaultCss + styleSheet); +} + +void TabbedViewContainer::setCssFromFile(const QUrl &url) +{ + // Let's only deal w/ local files for now + if (!url.isLocalFile()) { + setStyleSheet(KonsoleSettings::tabBarStyleSheet()); + } + + QFile file(url.toLocalFile()); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + setStyleSheet(KonsoleSettings::tabBarStyleSheet()); + } + + QTextStream in(&file); + setCss(in.readAll()); +} + +void TabbedViewContainer::moveActiveView(MoveDirection direction) +{ + if (count() < 2) { // return if only one view + return; + } + const int currentIndex = indexOf(currentWidget()); + int newIndex = direction == MoveViewLeft ? qMax(currentIndex - 1, 0) : qMin(currentIndex + 1, count() - 1); + + auto swappedWidget = terminalAt(newIndex); + auto currentWidget = terminalAt(currentIndex); + auto swappedContext = swappedWidget->sessionController(); + auto currentContext = currentWidget->sessionController(); + + if (newIndex < currentIndex) { + insertTab(newIndex, currentWidget, currentContext->icon(), currentContext->title()); + insertTab(currentIndex, swappedWidget, swappedContext->icon(), swappedContext->title()); + } else { + insertTab(currentIndex, swappedWidget, swappedContext->icon(), swappedContext->title()); + insertTab(newIndex, currentWidget, currentContext->icon(), currentContext->title()); + } + setCurrentIndex(newIndex); +} + +void TabbedViewContainer::addView(TerminalDisplay *view, int index) +{ + auto item = view->sessionController(); + if (index == -1) { + addTab(view, item->icon(), item->title()); + } else { + insertTab(index, view, item->icon(), item->title()); + } + + _tabHistory.append(view); + connect(item, &Konsole::ViewProperties::titleChanged, this, + &Konsole::TabbedViewContainer::updateTitle); + connect(item, &Konsole::ViewProperties::iconChanged, this, + &Konsole::TabbedViewContainer::updateIcon); + connect(item, &Konsole::ViewProperties::activity, this, + &Konsole::TabbedViewContainer::updateActivity); + connect(view, &QWidget::destroyed, this, + &Konsole::TabbedViewContainer::viewDestroyed); + emit viewAdded(view); +} + +void TabbedViewContainer::viewDestroyed(QObject *view) +{ + auto widget = static_cast(view); + const auto idx = indexOf(widget); + + removeTab(idx); + forgetView(widget); +} + +void TabbedViewContainer::forgetView(TerminalDisplay *view) +{ + updateTabHistory(view, true); + emit viewRemoved(view); + if (count() == 0) { + emit empty(this); + } +} + +void TabbedViewContainer::removeView(TerminalDisplay *view) +{ + const int idx = indexOf(view); + disconnect(view, &QWidget::destroyed, this, &Konsole::TabbedViewContainer::viewDestroyed); + removeTab(idx); + forgetView(view); +} + +void TabbedViewContainer::activateNextView() +{ + QWidget *active = currentWidget(); + int index = indexOf(active); + setCurrentIndex(index == count() - 1 ? 0 : index + 1); +} + +void TabbedViewContainer::activateLastView() +{ + setCurrentIndex(count() - 1); +} + +void TabbedViewContainer::activatePreviousView() +{ + QWidget *active = currentWidget(); + int index = indexOf(active); + setCurrentIndex(index == 0 ? count() - 1 : index - 1); +} + +void TabbedViewContainer::activateLastUsedView(bool reverse) +{ + if (_tabHistory.count() <= 1) { + return; + } + + if (_tabHistoryIndex == -1) { + _tabHistoryIndex = reverse ? _tabHistory.count() - 1 : 1; + } else if (reverse) { + if (_tabHistoryIndex == 0) { + _tabHistoryIndex = _tabHistory.count() - 1; + } else { + _tabHistoryIndex--; + } + } else { + if (_tabHistoryIndex >= _tabHistory.count() - 1) { + _tabHistoryIndex = 0; + } else { + _tabHistoryIndex++; + } + } + + int index = indexOf(_tabHistory[_tabHistoryIndex]); + setCurrentIndex(index); +} + +void TabbedViewContainer::keyReleaseEvent(QKeyEvent* event) +{ + if (_tabHistoryIndex != -1 && event->modifiers() == Qt::NoModifier) { + _tabHistoryIndex = -1; + auto *active = qobject_cast(currentWidget()); + if (active != _tabHistory[0]) { + // Update the tab history now that we have ended the walk-through + updateTabHistory(active); + } + } +} + +void TabbedViewContainer::updateTabHistory(TerminalDisplay* view, bool remove) +{ + if (_tabHistoryIndex != -1 && !remove) { + // Do not reorder the tab history while we are walking through it + return; + } + + for (int i = 0; i < _tabHistory.count(); ++i ) { + if (_tabHistory[i] == view) { + _tabHistory.removeAt(i); + if (!remove) { + _tabHistory.prepend(view); + } + break; + } + } +} + +void TabbedViewContainer::closeCurrentTab() +{ + if (currentIndex() != -1) { + closeTerminalTab(currentIndex()); + } +} + +void TabbedViewContainer::tabDoubleClicked(int index) +{ + if (index >= 0) { + renameTab(index); + } else { + emit newViewRequest(this); + } +} + +void TabbedViewContainer::renameTab(int index) +{ + if (index != -1) { + terminalAt(index)->sessionController()->rename(); + } +} + +void TabbedViewContainer::openTabContextMenu(const QPoint &point) +{ + if (point.isNull()) { + return; + } + + _contextMenuTabIndex = tabBar()->tabAt(point); + if (_contextMenuTabIndex < 0) { + return; + } + + //TODO: add a countChanged signal so we can remove this for. + // Detaching in mac causes crashes. +#if defined(ENABLE_DETACHING) + for(auto action : _contextPopupMenu->actions()) { + if (action->objectName() == QStringLiteral("tab-detach")) { + action->setEnabled(count() > 1); + } + } +#endif + + // Add the read-only action + auto controller = terminalAt(_contextMenuTabIndex)->sessionController(); + auto sessionController = qobject_cast(controller); + + if (sessionController != nullptr) { + auto collection = sessionController->actionCollection(); + auto readonlyAction = collection->action(QStringLiteral("view-readonly")); + if (readonlyAction != nullptr) { + const auto readonlyActions = _contextPopupMenu->actions(); + _contextPopupMenu->insertAction(readonlyActions.last(), readonlyAction); + } + + // Disable tab rename + for (auto &action : _contextPopupMenu->actions()) { + if (action->objectName() == QStringLiteral("edit-rename")) { + action->setEnabled(!sessionController->isReadOnly()); + break; + } + } + } + + _contextPopupMenu->exec(tabBar()->mapToGlobal(point)); +} + +void TabbedViewContainer::currentTabChanged(int index) +{ + if (index != -1) { + auto *view = terminalAt(index); + view->setFocus(); + updateTabHistory(view); + emit activeViewChanged(view); + setTabActivity(index, false); + } else { + deleteLater(); + } +} + +void TabbedViewContainer::wheelScrolled(int delta) +{ + if (delta < 0) { + activateNextView(); + } else { + activatePreviousView(); + } +} + +void TabbedViewContainer::setTabActivity(int index, bool activity) +{ + const QPalette &palette = tabBar()->palette(); + KColorScheme colorScheme(palette.currentColorGroup()); + const QColor colorSchemeActive = colorScheme.foreground(KColorScheme::ActiveText).color(); + + const QColor normalColor = palette.text().color(); + const QColor activityColor = KColorUtils::mix(normalColor, colorSchemeActive); + + QColor color = activity ? activityColor : QColor(); + + if (color != tabBar()->tabTextColor(index)) { + tabBar()->setTabTextColor(index, color); + } +} + +void TabbedViewContainer::updateActivity(ViewProperties *item) +{ + auto controller = qobject_cast(item); + auto index = indexOf(controller->view()); + if (index != currentIndex()) { + setTabActivity(index, true); + } +} + +void TabbedViewContainer::updateTitle(ViewProperties *item) +{ + auto controller = qobject_cast(item); + + const int index = indexOf(controller->view()); + QString tabText = item->title(); + + setTabToolTip(index, tabText); + + // To avoid having & replaced with _ (shortcut indicator) + tabText.replace(QLatin1Char('&'), QLatin1String("&&")); + setTabText(index, tabText); +} + +void TabbedViewContainer::updateIcon(ViewProperties *item) +{ + auto controller = qobject_cast(item); + const int index = indexOf(controller->view()); + setTabIcon(index, item->icon()); +} + +void TabbedViewContainer::closeTerminalTab(int idx) { + terminalAt(idx)->sessionController()->closeSession(); +} + +ViewManager *TabbedViewContainer::connectedViewManager() +{ + return _connectedViewManager; +} + +void TabbedViewContainer::setNavigationVisibility(ViewManager::NavigationVisibility navigationVisibility) { + if (navigationVisibility == ViewManager::NavigationNotSet) { + return; + } + + setTabBarAutoHide(navigationVisibility == ViewManager::ShowNavigationAsNeeded); + if (navigationVisibility == ViewManager::AlwaysShowNavigation) { + tabBar()->setVisible(true); + } else if (navigationVisibility == ViewManager::AlwaysHideNavigation) { + tabBar()->setVisible(false); + } +} diff --git a/konsole.kcfg b/konsole.kcfg new file mode 100644 --- /dev/null +++ b/konsole.kcfg @@ -0,0 +1,139 @@ + + + + + + + Show menubar by default in each Konsole window + true + + + + Show window title set by escape sequence on the titlebar + false + + + + Allow users to access top menu through Alt+Key combination + false + + + + Show terminal size in columns and lines in the center of window after resizing + true + + + + The window size will be saved upon exiting Konsole + true + + + + When launching Konsole re-use existing process if possible + false + + + + + + Sets whether the search is case sensitive + false + + + + false + + + + Sets whether matching text should be highlighted + true + + + + Sets whether search should start from the bottom + true + + + + + + + + + + + ShowTabBarWhenNeeded + + + + + + + + Bottom + + + + QTabBar::tab { min-width: 2em ; max-width: 25em } + + + + false + + + + + + + + false + + + + false + + + + + + + + PutNewTabAtTheEnd + + + + false + + + + + + true + + + + true + + + + + + true + + + + false + + + + false + + + + + + +