diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -72,6 +72,7 @@ KeyBindingEditor.cpp KeyboardTranslator.cpp KeyboardTranslatorManager.cpp + ListTabbedSessionsPopup.cpp ProcessInfo.cpp Profile.cpp ProfileList.cpp diff --git a/src/ListTabbedSessionsPopup.h b/src/ListTabbedSessionsPopup.h new file mode 100644 --- /dev/null +++ b/src/ListTabbedSessionsPopup.h @@ -0,0 +1,62 @@ +/* + Copyright 2007-2008 by Robert Knight + Copyright 1997,1998 by Lars Doelle + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + + +#ifndef LISTTABBEDSESSIONSPOPUP_H +#define LISTTABBEDSESSIONSPOPUP_H + +#include +#include + +class QFocusEvent; +class QShowEvent; +class QKeyEvent; + +namespace Konsole { + +class ListTabbedSessionsPopup : public QListView +{ + Q_OBJECT +public: + ListTabbedSessionsPopup(QWidget *parent); + void setForwardSequence(QKeySequence forwardAccelerator); + void setBackwardSequence(QKeySequence backwardAccelerator); + +protected: + void showEvent(QShowEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + bool event(QEvent *event) override; + +private: + void focusNext(); + void focusPrev(); + void refreshModel(); + void selectFirstItem(); + void calculateSizeAndPosition(); + void bypassPrevNextParentShortcut(QKeyEvent *event); + + QShortcut *_forward; + QShortcut *_backward; +}; + +} +#endif diff --git a/src/ListTabbedSessionsPopup.cpp b/src/ListTabbedSessionsPopup.cpp new file mode 100644 --- /dev/null +++ b/src/ListTabbedSessionsPopup.cpp @@ -0,0 +1,195 @@ +/* + Copyright 2007-2008 by Robert Knight + Copyright 1997,1998 by Lars Doelle + + 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 "ListTabbedSessionsPopup.h" + +#include "ViewSplitter.h" +#include "ViewContainer.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace Konsole { + +ListTabbedSessionsPopup::ListTabbedSessionsPopup(QWidget *parent) : QListView(parent) +{ + auto *currentTabWidget = qobject_cast(parent); + + connect(this, &QListView::activated, this, [=](const QModelIndex& idx) { + if (idx.isValid()) { + currentTabWidget->setCurrentIndex(idx.row()); + hide(); + } + }); + + _forward = new QShortcut(this); + _forward->setContext(Qt::WidgetShortcut); + connect(_forward, &QShortcut::activated, this, &ListTabbedSessionsPopup::focusNext); + + _backward = new QShortcut(this); + _backward->setContext(Qt::WidgetShortcut); + connect(_backward, &QShortcut::activated, this, &ListTabbedSessionsPopup::focusPrev); + + hide(); +} + +void ListTabbedSessionsPopup::setForwardSequence(QKeySequence forwardSequence) +{ + _forward->setKey(forwardSequence); +} + +void ListTabbedSessionsPopup::setBackwardSequence(QKeySequence backwardSequence) +{ + _backward->setKey(backwardSequence); +} + +void ListTabbedSessionsPopup::focusNext() +{ + const int currentIdx = (currentIndex().row() + 1) % model()->rowCount(); + setCurrentIndex(model()->index(currentIdx, 0)); +} + +void ListTabbedSessionsPopup::focusPrev() +{ + const int currentIdx = currentIndex().row() == 0 ? model()->rowCount() - 1 : currentIndex().row() - 1; + setCurrentIndex(model()->index(currentIdx, 0)); +} + +void ListTabbedSessionsPopup::refreshModel() +{ + if (model()) { + model()->deleteLater(); + } + + auto *currentTabWidget = qobject_cast(parent()); + auto *itemModel = new QStandardItemModel(); + const auto count = currentTabWidget->count(); + for (int i = 0; i < count; i++) { + QString tabText = currentTabWidget->tabText(i); + tabText.remove(QLatin1Char('&')); + + auto item = new QStandardItem( + currentTabWidget->tabIcon(i), + tabText + ); + itemModel->appendRow(item); + } + setModel(itemModel); +} + +void ListTabbedSessionsPopup::calculateSizeAndPosition() +{ + auto *w = window(); + resize(w->width() / 4, w->height() / 5); + + move(w->width() / 2 - width() / 2, + w->height() / 2 - height() /2); +} + +void ListTabbedSessionsPopup::selectFirstItem() +{ + if (!currentIndex().isValid()) { + if (model()->rowCount() > 0) { + setCurrentIndex(model()->index(0,0)); + } + } +} + +void ListTabbedSessionsPopup::showEvent(QShowEvent *event) +{ + refreshModel(); + selectFirstItem(); + calculateSizeAndPosition(); + setFocus(Qt::OtherFocusReason); + QListView::showEvent(event); +} + +void ListTabbedSessionsPopup::focusOutEvent(QFocusEvent *event) +{ + Q_UNUSED(event); +} + +void ListTabbedSessionsPopup::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape) + { + clearFocus(); + hide(); + event->accept(); + } + QListView::keyPressEvent(event); +} + +void ListTabbedSessionsPopup::keyReleaseEvent(QKeyEvent *event) +{ + if(event->modifiers() == Qt::NoModifier) { + activated(currentIndex()); + clearFocus(); + hide(); + event->accept(); + setFocus(); + } + QListView::keyReleaseEvent(event); +} + +void ListTabbedSessionsPopup::bypassPrevNextParentShortcut(QKeyEvent *event) +{ + QKeyEvent *keyEvent = static_cast(event); + + int key = keyEvent->key(); + Qt::KeyboardModifiers modifiers = keyEvent->modifiers(); + QString keyText = keyEvent->text(); + + QList modifiersList; + if(modifiers & Qt::ShiftModifier) + key += Qt::SHIFT; + if(modifiers & Qt::ControlModifier) + key += Qt::CTRL; + if(modifiers & Qt::AltModifier) + key += Qt::ALT; + if(modifiers & Qt::MetaModifier) + key += Qt::META; + + if (_backward->key().matches(key)) { + event->accept(); + _backward->activated(); + } else if (_forward->key().matches(key)) { + event->accept(); + _forward->activated(); + } +} + +bool ListTabbedSessionsPopup::event(QEvent *event) +{ + if (event->type() != QEvent::ShortcutOverride) { + return QListView::event(event); + } + + bypassPrevNextParentShortcut(static_cast(event)); + return QListView::event(event); +} + +} diff --git a/src/ViewManager.h b/src/ViewManager.h --- a/src/ViewManager.h +++ b/src/ViewManager.h @@ -40,7 +40,7 @@ class SessionController; class ViewProperties; class ViewSplitter; -class TabbedViewContainer; +class ListTabbedSessionsPopup; /** * Manages the terminal display widgets in a Konsole window or part. @@ -206,6 +206,11 @@ QHash forgetAll(ViewSplitter* splitter); Session* forgetTerminal(TerminalDisplay* terminal); + + /** Behaves like a Alt + Tab application switcher + * */ + void displayTabChangeModalDialog(); + Q_SIGNALS: /** Emitted when the last view is removed from the view manager */ void empty(); @@ -436,14 +441,17 @@ NewTabBehavior _newTabBehavior; int _managerId; static int lastManagerId; + QList _terminalDisplayHistory; int _terminalDisplayHistoryIndex; // List of actions that should only be enabled when there are multiple view // containers open QList _multiTabOnlyActions; QList _multiSplitterOnlyActions; + + QPointer _altTabSwitcher; }; } diff --git a/src/ViewManager.cpp b/src/ViewManager.cpp --- a/src/ViewManager.cpp +++ b/src/ViewManager.cpp @@ -45,6 +45,7 @@ #include "ViewSplitter.h" #include "Enumeration.h" #include "ViewContainer.h" +#include "ListTabbedSessionsPopup.h" using namespace Konsole; @@ -78,6 +79,10 @@ connect(SessionManager::instance(), &Konsole::SessionManager::sessionUpdated, this, &Konsole::ViewManager::updateViewsForSession); + _altTabSwitcher = new ListTabbedSessionsPopup(_viewContainer); + _altTabSwitcher->setForwardSequence(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_O)); + _altTabSwitcher->setBackwardSequence(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_I)); + //prepare DBus communication new WindowAdaptor(this); @@ -246,7 +251,12 @@ _multiSplitterOnlyActions << action; _viewContainer->addAction(action); - // _viewSplitter->addAction(lastUsedViewReverseAction); + action = new QAction(i18nc("@action Shortcut entry", "Display Tab List"), this); + collection->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_O); + connect(action, &QAction::triggered, this, &Konsole::ViewManager::displayTabChangeModalDialog); + _viewContainer->addAction(action); + _multiTabOnlyActions << action; + const int SWITCH_TO_TAB_COUNT = 19; for (int i = 0; i < SWITCH_TO_TAB_COUNT; i++) { action = new QAction(i18nc("@action Shortcut entry", "Switch to Tab %1", i + 1), this); @@ -262,7 +272,6 @@ }; connect(_viewContainer, &TabbedViewContainer::viewAdded, this, handleMultiTabActionsLambda); connect(_viewContainer, &TabbedViewContainer::viewRemoved, this, handleMultiTabActionsLambda); - connect(_viewContainer, &QTabWidget::currentChanged, this, &ViewManager::updateDetachViewState); // Initial state @@ -289,6 +298,16 @@ } } +void ViewManager::displayTabChangeModalDialog() +{ + if (_viewContainer->count() == 1) { + return; + } + + _altTabSwitcher->show(); + _altTabSwitcher->setFocus(); +} + void ViewManager::updateDetachViewState() { if (_viewContainer && _viewContainer->activeViewSplitter()) {