diff --git a/src/ViewContainer.cpp b/src/ViewContainer.cpp index 79c24d5c..d425e0e5 100644 --- a/src/ViewContainer.cpp +++ b/src/ViewContainer.cpp @@ -1,764 +1,750 @@ /* 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 #include // Konsole #include "IncrementalSearchBar.h" #include "ViewProperties.h" #include "ViewContainerTabBar.h" #include "ProfileList.h" #include "ViewManager.h" // TODO Perhaps move everything which is Konsole-specific into different files using namespace Konsole; ViewContainer::ViewContainer(NavigationPosition position , QObject* parent) : QObject(parent) , _navigationVisibility(AlwaysShowNavigation) , _navigationPosition(position) , _searchBar(0) { } ViewContainer::~ViewContainer() { foreach(QWidget * view , _views) { disconnect(view, &QWidget::destroyed, this, &Konsole::ViewContainer::viewDestroyed); } if (_searchBar) { _searchBar->deleteLater(); } emit destroyed(this); } void ViewContainer::moveViewWidget(int , int) {} void ViewContainer::setFeatures(Features features) { _features = features; } ViewContainer::Features ViewContainer::features() const { return _features; } void ViewContainer::moveActiveView(MoveDirection direction) { const int currentIndex = _views.indexOf(activeView()); int newIndex = -1; switch (direction) { case MoveViewLeft: newIndex = qMax(currentIndex - 1 , 0); break; case MoveViewRight: newIndex = qMin(currentIndex + 1 , _views.count() - 1); break; } Q_ASSERT(newIndex != -1); moveViewWidget(currentIndex , newIndex); _views.swap(currentIndex, newIndex); setActiveView(_views[newIndex]); } void ViewContainer::setNavigationVisibility(NavigationVisibility mode) { _navigationVisibility = mode; navigationVisibilityChanged(mode); } ViewContainer::NavigationPosition ViewContainer::navigationPosition() const { return _navigationPosition; } void ViewContainer::setNavigationPosition(NavigationPosition position) { // assert that this position is supported Q_ASSERT(supportedNavigationPositions().contains(position)); _navigationPosition = position; navigationPositionChanged(position); } QList ViewContainer::supportedNavigationPositions() const { return QList() << NavigationPositionTop; } ViewContainer::NavigationVisibility ViewContainer::navigationVisibility() const { return _navigationVisibility; } void ViewContainer::setNavigationTextMode(bool mode) { navigationTextModeChanged(mode); } void ViewContainer::addView(QWidget* view , ViewProperties* item, int index) { if (index == -1) _views.append(view); else _views.insert(index, view); _navigation[view] = item; connect(view, &QWidget::destroyed, this, &Konsole::ViewContainer::viewDestroyed); addViewWidget(view, index); emit viewAdded(view, item); } void ViewContainer::viewDestroyed(QObject* object) { QWidget* widget = static_cast(object); + forgetView(widget); +} - _views.removeAll(widget); - _navigation.remove(widget); - - // FIXME This can result in ViewContainerSubClass::removeViewWidget() being - // called after the widget's parent has been deleted or partially deleted - // in the ViewContainerSubClass instance's destructor. - // - // Currently deleteLater() is used to remove child widgets in the subclass - // constructors to get around the problem, but this is a hack and needs - // to be fixed. - removeViewWidget(widget); +void ViewContainer::forgetView(QWidget* view) +{ + _views.removeAll(view); + _navigation.remove(view); - emit viewRemoved(widget); + emit viewRemoved(view); if (_views.count() == 0) emit empty(this); } + void ViewContainer::removeView(QWidget* view) { - _views.removeAll(view); - _navigation.remove(view); - disconnect(view, &QWidget::destroyed, this, &Konsole::ViewContainer::viewDestroyed); - removeViewWidget(view); - - emit viewRemoved(view); - - if (_views.count() == 0) - emit empty(this); + forgetView(view); } const QList ViewContainer::views() const { return _views; } IncrementalSearchBar* ViewContainer::searchBar() { if (!_searchBar) { _searchBar = new IncrementalSearchBar(0); _searchBar->setVisible(false); connect(_searchBar, &Konsole::IncrementalSearchBar::destroyed, this, &Konsole::ViewContainer::searchBarDestroyed); } return _searchBar; } void ViewContainer::searchBarDestroyed() { _searchBar = 0; } void ViewContainer::activateNextView() { QWidget* active = activeView(); int index = _views.indexOf(active); if (index == -1) return; if (index == _views.count() - 1) index = 0; else index++; setActiveView(_views.at(index)); } void ViewContainer::activateLastView() { setActiveView(_views.at(_views.count() - 1)); } void ViewContainer::activatePreviousView() { QWidget* active = activeView(); int index = _views.indexOf(active); if (index == -1) return; if (index == 0) index = _views.count() - 1; else index--; setActiveView(_views.at(index)); } ViewProperties* ViewContainer::viewProperties(QWidget* widget) const { Q_ASSERT(_navigation.contains(widget)); return _navigation[widget]; } QList ViewContainer::widgetsForItem(ViewProperties* item) const { return _navigation.keys(item); } TabbedViewContainer::TabbedViewContainer(NavigationPosition position, ViewManager* connectedViewManager, QObject* parent) : ViewContainer(position, parent) , _connectedViewManager(connectedViewManager) , _contextMenuTabIndex(0) { _containerWidget = new QWidget; _stackWidget = new QStackedWidget(); + connect(_stackWidget.data(), &QStackedWidget::widgetRemoved, this, &TabbedViewContainer::widgetRemoved); // The tab bar _tabBar = new ViewContainerTabBar(_containerWidget, this); _tabBar->setSupportedMimeType(ViewProperties::mimeType()); connect(_tabBar, &Konsole::ViewContainerTabBar::currentChanged, this, &Konsole::TabbedViewContainer::currentTabChanged); connect(_tabBar, &Konsole::ViewContainerTabBar::tabDoubleClicked, this, &Konsole::TabbedViewContainer::tabDoubleClicked); connect(_tabBar, &Konsole::ViewContainerTabBar::newTabRequest, this, static_cast(&Konsole::TabbedViewContainer::newViewRequest)); connect(_tabBar, &Konsole::ViewContainerTabBar::wheelDelta, this, &Konsole::TabbedViewContainer::wheelScrolled); connect(_tabBar, &Konsole::ViewContainerTabBar::initiateDrag, this, &Konsole::TabbedViewContainer::startTabDrag); connect(_tabBar, &Konsole::ViewContainerTabBar::querySourceIndex, this, &Konsole::TabbedViewContainer::querySourceIndex); connect(_tabBar, &Konsole::ViewContainerTabBar::moveViewRequest, this, &Konsole::TabbedViewContainer::onMoveViewRequest); connect(_tabBar, &Konsole::ViewContainerTabBar::contextMenu, this, &Konsole::TabbedViewContainer::openTabContextMenu); // The context menu of tab bar _contextPopupMenu = new KMenu(_tabBar); #if defined(ENABLE_DETACHING) _contextPopupMenu->addAction(KIcon("tab-detach"), i18nc("@action:inmenu", "&Detach Tab"), this, SLOT(tabContextMenuDetachTab())); #endif _contextPopupMenu->addAction(KIcon("edit-rename"), i18nc("@action:inmenu", "&Rename Tab..."), this, SLOT(tabContextMenuRenameTab())); _contextPopupMenu->addSeparator(); _contextPopupMenu->addAction(KIcon("tab-close"), i18nc("@action:inmenu", "&Close Tab"), this, SLOT(tabContextMenuCloseTab())); // The 'new tab' and 'close tab' button _newTabButton = new QToolButton(_containerWidget); _newTabButton->setFocusPolicy(Qt::NoFocus); _newTabButton->setIcon(KIcon("tab-new")); _newTabButton->setToolTip(i18nc("@info:tooltip", "Create new tab")); _newTabButton->setWhatsThis(i18nc("@info:whatsthis", "Create a new tab. Press and hold to select profile from menu")); _newTabButton->adjustSize(); QMenu* profileMenu = new QMenu(_newTabButton); ProfileList* profileList = new ProfileList(false, profileMenu); profileList->syncWidgetActions(profileMenu, true); connect(profileList, &Konsole::ProfileList::profileSelected, this, static_cast(&Konsole::TabbedViewContainer::newViewRequest)); setNewViewMenu(profileMenu); _closeTabButton = new QToolButton(_containerWidget); _closeTabButton->setFocusPolicy(Qt::NoFocus); _closeTabButton->setIcon(KIcon("tab-close")); _closeTabButton->setToolTip(i18nc("@info:tooltip", "Close tab")); _closeTabButton->setWhatsThis(i18nc("@info:whatsthis", "Close the active tab")); _closeTabButton->adjustSize(); // 'new tab' button is initially hidden. It will be shown when setFeatures() // is called with the QuickNewView flag enabled. The 'close tab' is the same. _newTabButton->setHidden(true); _closeTabButton->setHidden(true); connect(_newTabButton, &QToolButton::clicked, this, static_cast(&Konsole::TabbedViewContainer::newViewRequest)); connect(_closeTabButton, &QToolButton::clicked, this, &Konsole::TabbedViewContainer::closeCurrentTab); // Combine tab bar and 'new/close tab' buttons _tabBarLayout = new QHBoxLayout; _tabBarLayout->setSpacing(0); _tabBarLayout->setContentsMargins(0, 0, 0, 0); _tabBarLayout->addWidget(_newTabButton); _tabBarLayout->addWidget(_tabBar); _tabBarLayout->addWidget(_closeTabButton); // The search bar searchBar()->setParent(_containerWidget); // The overall layout _layout = new QVBoxLayout; _layout->setSpacing(0); _layout->setContentsMargins(0, 0, 0, 0); setNavigationPosition(position); _containerWidget->setLayout(_layout); } void TabbedViewContainer::setNewViewMenu(QMenu* menu) { _newTabButton->setMenu(menu); } ViewContainer::Features TabbedViewContainer::supportedFeatures() const { return QuickNewView | QuickCloseView; } void TabbedViewContainer::setFeatures(Features features) { ViewContainer::setFeatures(features); updateVisibilityOfQuickButtons(); } void TabbedViewContainer::closeCurrentTab() { if (_stackWidget->currentIndex() != -1) { emit closeTab(this, _stackWidget->widget(_stackWidget->currentIndex())); } } void TabbedViewContainer::updateVisibilityOfQuickButtons() { const bool tabBarHidden = _tabBar->isHidden(); _newTabButton->setVisible(!tabBarHidden && (features() & QuickNewView)); _closeTabButton->setVisible(!tabBarHidden && (features() & QuickCloseView)); } void TabbedViewContainer::setTabBarVisible(bool visible) { _tabBar->setVisible(visible); updateVisibilityOfQuickButtons(); } QList TabbedViewContainer::supportedNavigationPositions() const { return QList() << NavigationPositionTop << NavigationPositionBottom; } void TabbedViewContainer::navigationPositionChanged(NavigationPosition position) { // this method assumes that there are three or zero items in the layout Q_ASSERT(_layout->count() == 3 || _layout->count() == 0); // clear all existing items from the layout _layout->removeItem(_tabBarLayout); _tabBarLayout->setParent(0); // suppress the warning of "already has a parent" _layout->removeWidget(_stackWidget); _layout->removeWidget(searchBar()); if (position == NavigationPositionTop) { _layout->insertLayout(-1, _tabBarLayout); _layout->insertWidget(-1, _stackWidget); _layout->insertWidget(-1, searchBar()); _tabBar->setShape(QTabBar::RoundedNorth); } else if (position == NavigationPositionBottom) { _layout->insertWidget(-1, _stackWidget); _layout->insertWidget(-1, searchBar()); _layout->insertLayout(-1, _tabBarLayout); _tabBar->setShape(QTabBar::RoundedSouth); } else { Q_ASSERT(false); // should never reach here } } void TabbedViewContainer::navigationVisibilityChanged(NavigationVisibility mode) { if (mode == AlwaysShowNavigation && _tabBar->isHidden()) setTabBarVisible(true); if (mode == AlwaysHideNavigation && !_tabBar->isHidden()) setTabBarVisible(false); if (mode == ShowNavigationAsNeeded) dynamicTabBarVisibility(); } void TabbedViewContainer::dynamicTabBarVisibility() { if (_tabBar->count() > 1 && _tabBar->isHidden()) setTabBarVisible(true); if (_tabBar->count() < 2 && !_tabBar->isHidden()) setTabBarVisible(false); } void TabbedViewContainer::setStyleSheet(const QString& styleSheet) { _tabBar->setStyleSheet(styleSheet); } void TabbedViewContainer::navigationTextModeChanged(bool useTextWidth) { if (useTextWidth) { _tabBar->setStyleSheet("QTabBar::tab { }"); _tabBar->setExpanding(false); _tabBar->setElideMode(Qt::ElideNone); } else { _tabBar->setStyleSheet("QTabBar::tab { min-width: 2em; max-width: 25em }"); _tabBar->setExpanding(true); _tabBar->setElideMode(Qt::ElideLeft); } } TabbedViewContainer::~TabbedViewContainer() { if (!_containerWidget.isNull()) _containerWidget->deleteLater(); } void TabbedViewContainer::startTabDrag(int tab) { QDrag* drag = new QDrag(_tabBar); const QRect tabRect = _tabBar->tabRect(tab); QPixmap tabPixmap = _tabBar->dragDropPixmap(tab); drag->setPixmap(tabPixmap); // offset the tab position so the tab will follow the cursor exactly // where it was clicked (as opposed to centering on the origin of the pixmap) QPoint mappedPos = _tabBar->mapFromGlobal(QCursor::pos()); mappedPos.rx() -= tabRect.x(); drag->setHotSpot(mappedPos); const int id = viewProperties(views()[tab])->identifier(); QWidget* view = views()[tab]; drag->setMimeData(ViewProperties::createMimeData(id)); // start dragging const Qt::DropAction action = drag->exec(); if (drag->target()) { switch (action) { case Qt::MoveAction: // The MoveAction indicates the widget has been successfully // moved into another tabbar/container, so remove the widget in // current tabbar/container. // // Deleting the view may cause the view container to be deleted, // which will also delete the QDrag object. This can cause a // crash if Qt's internal drag-and-drop handling tries to delete // it later. // // For now set the QDrag's parent to 0 so that it won't be // deleted if this view container is destroyed. // // FIXME: Resolve this properly drag->setParent(0); removeView(view); break; case Qt::IgnoreAction: // The IgnoreAction is used by the tabbar to indicate the // special case of dropping one tab into its existing position. // So nothing need to do here. break; default: break; } } else { // if the tab is dragged onto something that does not accept this // drop(for example, a different application or a different konsole // process), then detach the tab to achieve the effect of "dragging tab // out of current window and into its own window" // // It feels unnatural to do the detach when this is only one tab in the // tabbar if (_tabBar->count() > 1) emit detachTab(this, view); } } void TabbedViewContainer::querySourceIndex(const QDropEvent* event, int& sourceIndex) { const int droppedId = ViewProperties::decodeMimeData(event->mimeData()); const QList viewList = views(); const int count = viewList.count(); int index = -1; for (index = 0; index < count; index++) { const int id = viewProperties(viewList[index])->identifier(); if (id == droppedId) break; } sourceIndex = index; } void TabbedViewContainer::onMoveViewRequest(int index, const QDropEvent* event ,bool& success, TabbedViewContainer* sourceTabbedContainer) { const int droppedId = ViewProperties::decodeMimeData(event->mimeData()); emit moveViewRequest(index, droppedId, success, sourceTabbedContainer); } void TabbedViewContainer::tabDoubleClicked(int index) { renameTab(index); } void TabbedViewContainer::renameTab(int index) { viewProperties(views()[index])->rename(); } void TabbedViewContainer::openTabContextMenu(int index, const QPoint& pos) { _contextMenuTabIndex = index; #if defined(ENABLE_DETACHING) // Enable 'Detach Tab' menu item only if there is more than 1 tab // Note: the code is coupled with that action's position within the menu QAction* detachAction = _contextPopupMenu->actions().first(); detachAction->setEnabled(_tabBar->count() > 1); #endif _contextPopupMenu->exec(pos); } void TabbedViewContainer::tabContextMenuCloseTab() { _tabBar->setCurrentIndex(_contextMenuTabIndex);// Required for this to work emit closeTab(this, _stackWidget->widget(_contextMenuTabIndex)); } void TabbedViewContainer::tabContextMenuDetachTab() { emit detachTab(this, _stackWidget->widget(_contextMenuTabIndex)); } void TabbedViewContainer::tabContextMenuRenameTab() { renameTab(_contextMenuTabIndex); } void TabbedViewContainer::moveViewWidget(int fromIndex , int toIndex) { QString text = _tabBar->tabText(fromIndex); QIcon icon = _tabBar->tabIcon(fromIndex); // FIXME - This will lose properties of the tab other than // their text and icon when moving them _tabBar->removeTab(fromIndex); _tabBar->insertTab(toIndex, icon, text); QWidget* widget = _stackWidget->widget(fromIndex); _stackWidget->removeWidget(widget); _stackWidget->insertWidget(toIndex, widget); } void TabbedViewContainer::currentTabChanged(int index) { _stackWidget->setCurrentIndex(index); if (_stackWidget->widget(index)) emit activeViewChanged(_stackWidget->widget(index)); // clear activity indicators setTabActivity(index, false); } void TabbedViewContainer::wheelScrolled(int delta) { if (delta < 0) activateNextView(); else activatePreviousView(); } QWidget* TabbedViewContainer::containerWidget() const { return _containerWidget; } QWidget* TabbedViewContainer::activeView() const { return _stackWidget->currentWidget(); } void TabbedViewContainer::setActiveView(QWidget* view) { const int index = _stackWidget->indexOf(view); Q_ASSERT(index != -1); _stackWidget->setCurrentWidget(view); _tabBar->setCurrentIndex(index); } void TabbedViewContainer::addViewWidget(QWidget* view , int index) { _stackWidget->insertWidget(index, view); _stackWidget->updateGeometry(); ViewProperties* item = viewProperties(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); _tabBar->insertTab(index , item->icon() , item->title()); if (navigationVisibility() == ShowNavigationAsNeeded) dynamicTabBarVisibility(); } + void TabbedViewContainer::removeViewWidget(QWidget* view) { if (!_stackWidget) return; - const int index = _stackWidget->indexOf(view); + _stackWidget->removeWidget(view); +} +void TabbedViewContainer::widgetRemoved(int index) +{ Q_ASSERT(index != -1); - _stackWidget->removeWidget(view); _tabBar->removeTab(index); if (navigationVisibility() == ShowNavigationAsNeeded) dynamicTabBarVisibility(); } 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) { foreach(QWidget* widget, widgetsForItem(item)) { const int index = _stackWidget->indexOf(widget); if (index != _stackWidget->currentIndex()) { setTabActivity(index, true); } } } void TabbedViewContainer::updateTitle(ViewProperties* item) { foreach(QWidget* widget, widgetsForItem(item)) { const int index = _stackWidget->indexOf(widget); QString tabText = item->title(); _tabBar->setTabToolTip(index , tabText); // To avoid having & replaced with _ (shortcut indicator) tabText.replace('&', "&&"); _tabBar->setTabText(index , tabText); } } void TabbedViewContainer::updateIcon(ViewProperties* item) { foreach(QWidget* widget, widgetsForItem(item)) { const int index = _stackWidget->indexOf(widget); _tabBar->setTabIcon(index , item->icon()); } } ViewManager* TabbedViewContainer::connectedViewManager() { return _connectedViewManager; } StackedViewContainer::StackedViewContainer(QObject* parent) : ViewContainer(NavigationPositionTop, parent) { _containerWidget = new QWidget; QVBoxLayout* layout = new QVBoxLayout(_containerWidget); _stackWidget = new QStackedWidget(_containerWidget); searchBar()->setParent(_containerWidget); layout->addWidget(searchBar()); layout->addWidget(_stackWidget); layout->setContentsMargins(0, 0, 0, 0); } StackedViewContainer::~StackedViewContainer() { if (!_containerWidget.isNull()) _containerWidget->deleteLater(); } QWidget* StackedViewContainer::containerWidget() const { return _containerWidget; } QWidget* StackedViewContainer::activeView() const { return _stackWidget->currentWidget(); } void StackedViewContainer::setActiveView(QWidget* view) { _stackWidget->setCurrentWidget(view); } void StackedViewContainer::addViewWidget(QWidget* view , int) { _stackWidget->addWidget(view); } void StackedViewContainer::removeViewWidget(QWidget* view) { if (!_stackWidget) return; - const int index = _stackWidget->indexOf(view); - - Q_ASSERT(index != -1); - Q_UNUSED(index); - _stackWidget->removeWidget(view); } - diff --git a/src/ViewContainer.h b/src/ViewContainer.h index 60d2bd9e..bd6b5cc5 100644 --- a/src/ViewContainer.h +++ b/src/ViewContainer.h @@ -1,453 +1,456 @@ /* 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. */ #ifndef VIEWCONTAINER_H #define VIEWCONTAINER_H // Qt #include #include #include #include // Konsole #include "Profile.h" class QStackedWidget; class QWidget; class QHBoxLayout; class QVBoxLayout; // TabbedViewContainer // Qt class QPoint; class QToolButton; class QMenu; class QDropEvent; // KDE class KMenu; namespace Konsole { class IncrementalSearchBar; class ViewProperties; class TabbedViewContainer; /** * An interface for container widgets which can hold one or more views. * * The container widget typically displays a list of the views which * it has and provides a means of switching between them. * * Subclasses should reimplement the addViewWidget() and removeViewWidget() functions * to actually add or remove view widgets from the container widget, as well * as updating any navigation aids. */ class ViewContainer : public QObject { Q_OBJECT public: /** * This enum describes the options for positioning the * container's navigation widget. */ enum NavigationPosition { /** Position the navigation widget above the views. */ NavigationPositionTop, /** Position the navigation widget below the views. */ NavigationPositionBottom, /** Position the navigation widget to the left of the views. */ NavigationPositionLeft, /** Position the navigation widget to the right of the views. */ NavigationPositionRight }; /** * Constructs a new view container with the specified parent. * * @param position The initial position of the navigation widget * @param parent The parent object of the container */ ViewContainer(NavigationPosition position , QObject* parent); /** * Called when the ViewContainer is destroyed. When reimplementing this in * subclasses, use object->deleteLater() to delete any widgets or other objects * instead of 'delete object'. */ virtual ~ViewContainer(); /** Returns the widget which contains the view widgets */ virtual QWidget* containerWidget() const = 0; /** * This enum describes the options for showing or hiding the * container's navigation widget. */ enum NavigationVisibility { /** Always show the navigation widget. */ AlwaysShowNavigation, /** Show the navigation widget only when the container has more than one view. */ ShowNavigationAsNeeded, /** Always hide the navigation widget. */ AlwaysHideNavigation }; /* * Sets the visibility of the view container's navigation widget. * * The ViewContainer sub-class is responsible for ensuring that this * setting is respected as views are added or removed from the * container. * * ViewContainer sub-classes should reimplement the * navigationVisibilityChanged() method to respond to changes * of this property. */ void setNavigationVisibility(NavigationVisibility mode); /** * Returns the current mode for controlling the visibility of the * the view container's navigation widget. */ NavigationVisibility navigationVisibility() const; /** * Sets the position of the navigation widget with * respect to the main content area. * * Depending on the ViewContainer subclass, not all * positions from the NavigationPosition enum may be * supported. A list of supported positions can be * obtained by calling supportedNavigationPositions() * * ViewContainer sub-classes should re-implement the * navigationPositionChanged() method to respond * to changes of this property. */ void setNavigationPosition(NavigationPosition position); /** * Returns the position of the navigation widget with * respect to the main content area. */ NavigationPosition navigationPosition() const; /** * Returns the list of supported navigation positions. * The supported positions will depend upon the type of the * navigation widget used by the ViewContainer subclass. * * The base implementation returns one item, NavigationPositionTop */ virtual QList supportedNavigationPositions() const; /** Sets the navigation text mode * If mode is true, use the width of the title; otherwise use the * default width calculations. */ void setNavigationTextMode(bool mode); /** Sets the stylesheet for visual appearance * * The default implementation does nothing. */ virtual void setStyleSheet(const QString& styleSheet) { Q_UNUSED(styleSheet); } /** Adds a new view to the container widget */ void addView(QWidget* view , ViewProperties* navigationItem, int index = -1); /** Removes a view from the container */ void removeView(QWidget* view); /** Returns the ViewProperties instance associated with a particular view in the container */ ViewProperties* viewProperties(QWidget* view) const; /** Returns a list of the contained views */ const QList views() const; /** * Returns the view which currently has the focus or 0 if none * of the child views have the focus. */ virtual QWidget* activeView() const = 0; /** * Changes the focus to the specified view and updates * navigation aids to reflect the change. */ virtual void setActiveView(QWidget* widget) = 0; /** * @return the search widget for this view */ IncrementalSearchBar* searchBar(); /** Changes the active view to the next view */ void activateNextView(); /** Changes the active view to the previous view */ void activatePreviousView(); /** Changes the active view to the last view */ void activateLastView(); /** * This enum describes the directions * in which views can be re-arranged within the container * using the moveActiveView() method. */ enum MoveDirection { /** Moves the view to the left. */ MoveViewLeft, /** Moves the view to the right. */ MoveViewRight }; /** * Moves the active view within the container and * updates the order in which the views are shown * in the container's navigation widget. * * The default implementation does nothing. */ void moveActiveView(MoveDirection direction); /** Enum describing extra UI features which can be * provided by the container. */ enum Feature { /** Provides a button which can be clicked to create new views quickly. * When the button is clicked, a newViewRequest() signal is emitted. */ QuickNewView = 1, /** Provides a button which can be clicked to close views quickly. */ QuickCloseView = 2 }; Q_DECLARE_FLAGS(Features, Feature) /** * Sets which additional features are enabled in this container. * The default implementation does thing. Sub-classes should re-implement this * to hide or show the relevant parts of their UI */ virtual void setFeatures(Features features); /** Returns a bitwise-OR of enabled extra UI features. See setFeatures() */ Features features() const; /** Returns a bitwise-OR of supported extra UI features. The default * implementation returns 0 (no extra features) */ virtual Features supportedFeatures() const { return 0; } /** Sets the menu to be shown when the new view button is clicked. * Only valid if the QuickNewView feature is enabled. * The default implementation does nothing. */ virtual void setNewViewMenu(QMenu* menu) { Q_UNUSED(menu); } signals: /** Emitted when the container is deleted */ void destroyed(ViewContainer* container); /** Emitted when the container has no more children */ void empty(ViewContainer* container); /** Emitted when the user requests to open a new view */ void newViewRequest(); /** Requests creation of a new view, with the selected profile. */ void newViewRequest(Profile::Ptr); /** * Emitted when the user requests to move a view from another container * into this container. If 'success' is set to true by a connected slot * then the original view will be removed. * * @param index Index at which to insert the new view in the container or -1 * to append it. This index should be passed to addView() when the new view * has been created. * @param id The identifier of the view. * @param success The slot handling this signal should set this to true if the * new view was successfully created. * @param sourceContainer Initial move event Tabbed view container. */ void moveViewRequest(int index, int id, bool& success, TabbedViewContainer* sourceContainer); /** Emitted when the active view changes */ void activeViewChanged(QWidget* view); /** Emitted when a view is added to the container. */ void viewAdded(QWidget* view , ViewProperties* properties); /** Emitted when a view is removed from the container. */ void viewRemoved(QWidget* view); protected: /** * Performs the task of adding the view widget * to the container widget. */ virtual void addViewWidget(QWidget* view, int index) = 0; /** * Performs the task of removing the view widget * from the container widget. */ virtual void removeViewWidget(QWidget* view) = 0; /** * Called when the navigation display mode changes. * See setNavigationVisibility */ virtual void navigationVisibilityChanged(NavigationVisibility) {} /** * Called when the navigation position changes to re-layout * the container and place the navigation widget in the * specified position. */ virtual void navigationPositionChanged(NavigationPosition) {} virtual void navigationTextModeChanged(bool) {} /** Returns the widgets which are associated with a particular navigation item */ QList widgetsForItem(ViewProperties* item) const; /** * Rearranges the order of widgets in the container. * * @param fromIndex Current index of the widget to move * @param toIndex New index for the widget */ virtual void moveViewWidget(int fromIndex , int toIndex); private slots: void viewDestroyed(QObject* view); void searchBarDestroyed(); private: + void forgetView(QWidget* view); + NavigationVisibility _navigationVisibility; NavigationPosition _navigationPosition; QList _views; QHash _navigation; Features _features; IncrementalSearchBar* _searchBar; }; Q_DECLARE_OPERATORS_FOR_FLAGS(ViewContainer::Features) class ViewContainerTabBar; class ViewManager; /** * An alternative tabbed view container which uses a QTabBar and QStackedWidget * combination for navigation instead of QTabWidget */ class TabbedViewContainer : public ViewContainer { Q_OBJECT public: /** * Constructs a new tabbed view container. Supported positions * are NavigationPositionTop and NavigationPositionBottom. */ TabbedViewContainer(NavigationPosition position, ViewManager* connectedViewManager, QObject* parent); virtual ~TabbedViewContainer(); virtual QWidget* containerWidget() const; virtual QWidget* activeView() const; virtual void setActiveView(QWidget* view); virtual QList supportedNavigationPositions() const; virtual void setFeatures(Features features); virtual Features supportedFeatures() const; virtual void setNewViewMenu(QMenu* menu); virtual void setStyleSheet(const QString& styleSheet); // return associated view manager ViewManager* connectedViewManager(); protected: virtual void addViewWidget(QWidget* view , int index); virtual void removeViewWidget(QWidget* view); virtual void navigationVisibilityChanged(NavigationVisibility mode); virtual void navigationPositionChanged(NavigationPosition position); virtual void navigationTextModeChanged(bool mode); virtual void moveViewWidget(int fromIndex , int toIndex); private slots: void updateTitle(ViewProperties* item); void updateIcon(ViewProperties* item); void updateActivity(ViewProperties* item); void currentTabChanged(int index); void closeCurrentTab(); void wheelScrolled(int delta); void tabDoubleClicked(int index); void openTabContextMenu(int index, const QPoint& point); void tabContextMenuCloseTab(); void tabContextMenuRenameTab(); void tabContextMenuDetachTab(); void startTabDrag(int index); void querySourceIndex(const QDropEvent* event, int& sourceIndex); void onMoveViewRequest(int index, const QDropEvent* event, bool& success, TabbedViewContainer* sourceTabbedContainer); signals: void detachTab(ViewContainer * self, QWidget * activeView); void closeTab(ViewContainer * self, QWidget * activeView); private: void dynamicTabBarVisibility(); void setTabBarVisible(bool visible); void setTabActivity(int index, bool activity); void renameTab(int index); void updateVisibilityOfQuickButtons(); + void widgetRemoved(int index); ViewContainerTabBar* _tabBar; QPointer _stackWidget; QPointer _containerWidget; ViewManager* _connectedViewManager; QVBoxLayout* _layout; QHBoxLayout* _tabBarLayout; QToolButton* _newTabButton; QToolButton* _closeTabButton; int _contextMenuTabIndex; KMenu* _contextPopupMenu; }; /** A plain view container with no navigation display */ class StackedViewContainer : public ViewContainer { public: explicit StackedViewContainer(QObject* parent); virtual ~StackedViewContainer(); virtual QWidget* containerWidget() const; virtual QWidget* activeView() const; virtual void setActiveView(QWidget* view); protected: virtual void addViewWidget(QWidget* view , int index); virtual void removeViewWidget(QWidget* view); private: QPointer _containerWidget; QPointer _stackWidget; }; } #endif //VIEWCONTAINER_H diff --git a/src/ViewManager.cpp b/src/ViewManager.cpp index 75473e9c..359aa065 100644 --- a/src/ViewManager.cpp +++ b/src/ViewManager.cpp @@ -1,1145 +1,1125 @@ /* Copyright 2006-2008 by Robert Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ // Own #include "ViewManager.h" #include // Qt #include #include #include #include #include // KDE #include #include #include #include // Konsole #include #include "ColorScheme.h" #include "ColorSchemeManager.h" #include "Session.h" #include "TerminalDisplay.h" #include "SessionController.h" #include "SessionManager.h" #include "ProfileManager.h" #include "ViewContainer.h" #include "ViewSplitter.h" #include "Profile.h" #include "Enumeration.h" using namespace Konsole; int ViewManager::lastManagerId = 0; ViewManager::ViewManager(QObject* parent , KActionCollection* collection) : QObject(parent) , _viewSplitter(0) , _actionCollection(collection) , _containerSignalMapper(new QSignalMapper(this)) , _navigationMethod(TabbedNavigation) , _navigationVisibility(ViewContainer::AlwaysShowNavigation) , _navigationPosition(ViewContainer::NavigationPositionTop) , _showQuickButtons(false) , _newTabBehavior(PutNewTabAtTheEnd) , _navigationStyleSheet(QString()) , _managerId(0) { // create main view area _viewSplitter = new ViewSplitter(0); KAcceleratorManager::setNoAccel(_viewSplitter); // the ViewSplitter class supports both recursive and non-recursive splitting, // in non-recursive mode, all containers are inserted into the same top-level splitter // widget, and all the divider lines between the containers have the same orientation // // the ViewManager class is not currently able to handle a ViewSplitter in recursive-splitting // mode _viewSplitter->setRecursiveSplitting(false); _viewSplitter->setFocusPolicy(Qt::NoFocus); // setup actions which are related to the views setupActions(); // emit a signal when all of the views held by this view manager are destroyed connect(_viewSplitter.data() , &Konsole::ViewSplitter::allContainersEmpty , this , &Konsole::ViewManager::empty); connect(_viewSplitter.data() , &Konsole::ViewSplitter::empty , this , &Konsole::ViewManager::empty); // listen for addition or removal of views from associated containers connect(_containerSignalMapper , static_cast(&QSignalMapper::mapped) , this , &Konsole::ViewManager::containerViewsChanged); // listen for profile changes connect(ProfileManager::instance() , &Konsole::ProfileManager::profileChanged , this, &Konsole::ViewManager::profileChanged); connect(SessionManager::instance() , &Konsole::SessionManager::sessionUpdated , this, &Konsole::ViewManager::updateViewsForSession); //prepare DBus communication new WindowAdaptor(this); // TODO: remove this obsolete and bad name QDBusConnection::sessionBus().registerObject(QLatin1String("/Konsole"), this); _managerId = ++lastManagerId; QDBusConnection::sessionBus().registerObject(QLatin1String("/Windows/") + QString::number(_managerId), this); } ViewManager::~ViewManager() { } int ViewManager::managerId() const { return _managerId; } QWidget* ViewManager::activeView() const { ViewContainer* container = _viewSplitter->activeContainer(); if (container) { return container->activeView(); } else { return 0; } } QWidget* ViewManager::widget() const { return _viewSplitter; } void ViewManager::setupActions() { KActionCollection* collection = _actionCollection; QAction* nextViewAction = new QAction(i18nc("@action Shortcut entry", "Next Tab") , this); QAction* previousViewAction = new QAction(i18nc("@action Shortcut entry", "Previous Tab") , this); QAction* lastViewAction = new QAction(i18nc("@action Shortcut entry", "Switch to Last Tab") , this); QAction* nextContainerAction = new QAction(i18nc("@action Shortcut entry", "Next View Container") , this); QAction* moveViewLeftAction = new QAction(i18nc("@action Shortcut entry", "Move Tab Left") , this); QAction* moveViewRightAction = new QAction(i18nc("@action Shortcut entry", "Move Tab Right") , this); // list of actions that should only be enabled when there are multiple view // containers open QList multiViewOnlyActions; multiViewOnlyActions << nextContainerAction; if (collection) { QAction* splitLeftRightAction = new QAction(KIcon("view-split-left-right"), i18nc("@action:inmenu", "Split View Left/Right"), this); splitLeftRightAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_ParenLeft)); collection->addAction("split-view-left-right", splitLeftRightAction); connect(splitLeftRightAction , &QAction::triggered , this , &Konsole::ViewManager::splitLeftRight); QAction* splitTopBottomAction = new QAction(KIcon("view-split-top-bottom") , i18nc("@action:inmenu", "Split View Top/Bottom"), this); splitTopBottomAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_ParenRight)); collection->addAction("split-view-top-bottom", splitTopBottomAction); connect(splitTopBottomAction , &QAction::triggered , this , &Konsole::ViewManager::splitTopBottom); QAction* closeActiveAction = new QAction(i18nc("@action:inmenu Close Active View", "Close Active") , this); closeActiveAction->setIcon(KIcon("view-close")); closeActiveAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_S)); closeActiveAction->setEnabled(false); collection->addAction("close-active-view", closeActiveAction); connect(closeActiveAction , &QAction::triggered , this , &Konsole::ViewManager::closeActiveContainer); multiViewOnlyActions << closeActiveAction; QAction* closeOtherAction = new QAction(i18nc("@action:inmenu Close Other Views", "Close Others") , this); closeOtherAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_O)); closeOtherAction->setEnabled(false); collection->addAction("close-other-views", closeOtherAction); connect(closeOtherAction , &QAction::triggered , this , &Konsole::ViewManager::closeOtherContainers); multiViewOnlyActions << closeOtherAction; // Expand & Shrink Active View QAction* expandActiveAction = new QAction(i18nc("@action:inmenu", "Expand View") , this); expandActiveAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_BracketRight)); expandActiveAction->setEnabled(false); collection->addAction("expand-active-view", expandActiveAction); connect(expandActiveAction , &QAction::triggered , this , &Konsole::ViewManager::expandActiveContainer); multiViewOnlyActions << expandActiveAction; QAction* shrinkActiveAction = new QAction(i18nc("@action:inmenu", "Shrink View") , this); shrinkActiveAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_BracketLeft)); shrinkActiveAction->setEnabled(false); collection->addAction("shrink-active-view", shrinkActiveAction); connect(shrinkActiveAction , &QAction::triggered , this , &Konsole::ViewManager::shrinkActiveContainer); multiViewOnlyActions << shrinkActiveAction; #if defined(ENABLE_DETACHING) QAction* detachViewAction = collection->addAction("detach-view"); detachViewAction->setIcon(KIcon("tab-detach")); detachViewAction->setText(i18nc("@action:inmenu", "D&etach Current Tab")); // Ctrl+Shift+D is not used as a shortcut by default because it is too close // to Ctrl+D - which will terminate the session in many cases detachViewAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_H)); connect(this , &Konsole::ViewManager::splitViewToggle , this , &Konsole::ViewManager::updateDetachViewState); connect(detachViewAction , &QAction::triggered , this , &Konsole::ViewManager::detachActiveView); #endif // Next / Previous View , Next Container collection->addAction("next-view", nextViewAction); collection->addAction("previous-view", previousViewAction); collection->addAction("last-tab", lastViewAction); collection->addAction("next-container", nextContainerAction); collection->addAction("move-view-left", moveViewLeftAction); collection->addAction("move-view-right", moveViewRightAction); // Switch to tab N shortcuts const int SWITCH_TO_TAB_COUNT = 19; QSignalMapper* switchToTabMapper = new QSignalMapper(this); connect(switchToTabMapper, static_cast(&QSignalMapper::mapped), this, &Konsole::ViewManager::switchToView); for (int i = 0; i < SWITCH_TO_TAB_COUNT; i++) { QAction* switchToTabAction = new QAction(i18nc("@action Shortcut entry", "Switch to Tab %1", i + 1), this); switchToTabMapper->setMapping(switchToTabAction, i); connect(switchToTabAction, &QAction::triggered, switchToTabMapper, static_cast(&QSignalMapper::map)); collection->addAction(QString("switch-to-tab-%1").arg(i), switchToTabAction); } } foreach(QAction* action, multiViewOnlyActions) { connect(this , &Konsole::ViewManager::splitViewToggle , action , &QAction::setEnabled); } // keyboard shortcut only actions nextViewAction->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Right)); connect(nextViewAction, &QAction::triggered , this , &Konsole::ViewManager::nextView); _viewSplitter->addAction(nextViewAction); previousViewAction->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Left)); connect(previousViewAction, &QAction::triggered , this , &Konsole::ViewManager::previousView); _viewSplitter->addAction(previousViewAction); nextContainerAction->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Tab)); connect(nextContainerAction , &QAction::triggered , this , &Konsole::ViewManager::nextContainer); _viewSplitter->addAction(nextContainerAction); moveViewLeftAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Left)); connect(moveViewLeftAction , &QAction::triggered , this , &Konsole::ViewManager::moveActiveViewLeft); _viewSplitter->addAction(moveViewLeftAction); moveViewRightAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Right)); connect(moveViewRightAction , &QAction::triggered , this , &Konsole::ViewManager::moveActiveViewRight); _viewSplitter->addAction(moveViewRightAction); connect(lastViewAction, &QAction::triggered , this , &Konsole::ViewManager::lastView); _viewSplitter->addAction(lastViewAction); } void ViewManager::switchToView(int index) { Q_ASSERT(index >= 0); ViewContainer* container = _viewSplitter->activeContainer(); Q_ASSERT(container); QList containerViews = container->views(); if (index >= containerViews.count()) return; container->setActiveView(containerViews.at(index)); } void ViewManager::updateDetachViewState() { if (!_actionCollection) return; const bool splitView = _viewSplitter->containers().count() >= 2; const bool shouldEnable = splitView || _viewSplitter->activeContainer()->views().count() >= 2; QAction* detachAction = _actionCollection->action("detach-view"); if (detachAction && shouldEnable != detachAction->isEnabled()) detachAction->setEnabled(shouldEnable); } void ViewManager::moveActiveViewLeft() { ViewContainer* container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->moveActiveView(ViewContainer::MoveViewLeft); } void ViewManager::moveActiveViewRight() { ViewContainer* container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->moveActiveView(ViewContainer::MoveViewRight); } void ViewManager::nextContainer() { _viewSplitter->activateNextContainer(); } void ViewManager::nextView() { ViewContainer* container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->activateNextView(); } void ViewManager::previousView() { ViewContainer* container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->activatePreviousView(); } void ViewManager::lastView() { ViewContainer* container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->activateLastView(); } void ViewManager::detachActiveView() { // find the currently active view and remove it from its container ViewContainer* container = _viewSplitter->activeContainer(); detachView(container, container->activeView()); } void ViewManager::detachView(ViewContainer* container, QWidget* widgetView) { #if !defined(ENABLE_DETACHING) return; #endif TerminalDisplay * viewToDetach = qobject_cast(widgetView); if (!viewToDetach) return; emit viewDetached(_sessionMap[viewToDetach]); _sessionMap.remove(viewToDetach); // remove the view from this window container->removeView(viewToDetach); viewToDetach->deleteLater(); // if the container from which the view was removed is now empty then it can be deleted, // unless it is the only container in the window, in which case it is left empty // so that there is always an active container if (_viewSplitter->containers().count() > 1 && container->views().count() == 0) { removeContainer(container); } } void ViewManager::sessionFinished() { // if this slot is called after the view manager's main widget // has been destroyed, do nothing if (!_viewSplitter) return; Session* session = qobject_cast(sender()); Q_ASSERT(session); // close attached views QList children = _viewSplitter->findChildren(); foreach(TerminalDisplay* view , children) { if (_sessionMap[view] == session) { _sessionMap.remove(view); view->deleteLater(); } } // This is needed to remove this controller from factory() in // order to prevent BUG: 185466 - disappearing menu popup if (_pluggedController) emit unplugController(_pluggedController); } -void ViewManager::focusActiveView() -{ - // give the active view in a container the focus. this ensures - // that controller associated with that view is activated and the session-specific - // menu items are replaced with the ones for the newly focused view - - // see the viewFocused() method - - ViewContainer* container = _viewSplitter->activeContainer(); - if (container) { - QWidget* activeView = container->activeView(); - if (activeView) { - activeView->setFocus(Qt::MouseFocusReason); - } - } -} - void ViewManager::viewActivated(QWidget* view) { Q_ASSERT(view != 0); // focus the activated view, this will cause the SessionController // to notify the world that the view has been focused and the appropriate UI // actions will be plugged in. view->setFocus(Qt::OtherFocusReason); } void ViewManager::splitLeftRight() { splitView(Qt::Horizontal); } void ViewManager::splitTopBottom() { splitView(Qt::Vertical); } void ViewManager::splitView(Qt::Orientation orientation) { ViewContainer* container = createContainer(); // iterate over each session which has a view in the current active // container and create a new view for that session in a new container foreach(QWidget* view, _viewSplitter->activeContainer()->views()) { Session* session = _sessionMap[qobject_cast(view)]; TerminalDisplay* display = createTerminalDisplay(session); const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); applyProfileToView(display, profile); ViewProperties* properties = createController(session, display); _sessionMap[display] = session; container->addView(display, properties); session->addView(display); } _viewSplitter->addContainer(container, orientation); emit splitViewToggle(_viewSplitter->containers().count() > 0); // focus the new container container->containerWidget()->setFocus(); // ensure that the active view is focused after the split / unsplit ViewContainer* activeContainer = _viewSplitter->activeContainer(); QWidget* activeView = activeContainer ? activeContainer->activeView() : 0; if (activeView) activeView->setFocus(Qt::OtherFocusReason); } void ViewManager::removeContainer(ViewContainer* container) { // remove session map entries for views in this container foreach(QWidget* view , container->views()) { TerminalDisplay* display = qobject_cast(view); Q_ASSERT(display); _sessionMap.remove(display); } _viewSplitter->removeContainer(container); container->deleteLater(); emit splitViewToggle(_viewSplitter->containers().count() > 1); } void ViewManager::expandActiveContainer() { _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(), 10); } void ViewManager::shrinkActiveContainer() { _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(), -10); } void ViewManager::closeActiveContainer() { // only do something if there is more than one container active if (_viewSplitter->containers().count() > 1) { ViewContainer* container = _viewSplitter->activeContainer(); removeContainer(container); // focus next container so that user can continue typing // without having to manually focus it themselves nextContainer(); } } void ViewManager::closeOtherContainers() { ViewContainer* active = _viewSplitter->activeContainer(); foreach(ViewContainer* container, _viewSplitter->containers()) { if (container != active) removeContainer(container); } } SessionController* ViewManager::createController(Session* session , TerminalDisplay* view) { // create a new controller for the session, and ensure that this view manager // is notified when the view gains the focus SessionController* controller = new SessionController(session, view, this); connect(controller , &Konsole::SessionController::focused , this , &Konsole::ViewManager::controllerChanged); connect(session , &Konsole::Session::destroyed , controller , &Konsole::SessionController::deleteLater); connect(session , &Konsole::Session::primaryScreenInUse , controller , &Konsole::SessionController::setupPrimaryScreenSpecificActions); connect(session , &Konsole::Session::selectionChanged , controller , &Konsole::SessionController::selectionChanged); connect(view , &Konsole::TerminalDisplay::destroyed , controller , &Konsole::SessionController::deleteLater); // if this is the first controller created then set it as the active controller if (!_pluggedController) controllerChanged(controller); return controller; } void ViewManager::controllerChanged(SessionController* controller) { if (controller == _pluggedController) return; _viewSplitter->setFocusProxy(controller->view()); _pluggedController = controller; emit activeViewChanged(controller); } SessionController* ViewManager::activeViewController() const { return _pluggedController; } IncrementalSearchBar* ViewManager::searchBar() const { return _viewSplitter->activeSplitter()->activeContainer()->searchBar(); } void ViewManager::createView(Session* session, ViewContainer* container, int index) { // notify this view manager when the session finishes so that its view // can be deleted // // Use Qt::UniqueConnection to avoid duplicate connection connect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished, Qt::UniqueConnection); TerminalDisplay* display = createTerminalDisplay(session); const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); applyProfileToView(display, profile); // set initial size const QSize& preferredSize = session->preferredSize(); // FIXME: +1 is needed here for getting the expected rows // Note that the display shouldn't need to take into account the tabbar. // However, it appears that taking into account the tabbar is needed. // If tabbar is not visible, no +1 is needed here; however, depending on // settings/tabbar style, +2 might be needed. // 1st attempt at fixing the above: // Guess if tabbar will NOT be visible; ignore ShowNavigationAsNeeded int heightAdjustment = 0; if (_navigationVisibility != ViewContainer::AlwaysHideNavigation) { heightAdjustment = 2; } display->setSize(preferredSize.width(), preferredSize.height() + heightAdjustment); ViewProperties* properties = createController(session, display); _sessionMap[display] = session; container->addView(display, properties, index); session->addView(display); // tell the session whether it has a light or dark background session->setDarkBackground(colorSchemeForProfile(profile)->hasDarkBackground()); if (container == _viewSplitter->activeContainer()) { container->setActiveView(display); display->setFocus(Qt::OtherFocusReason); } updateDetachViewState(); } void ViewManager::createView(Session* session) { // create the default container if (_viewSplitter->containers().count() == 0) { ViewContainer* container = createContainer(); _viewSplitter->addContainer(container, Qt::Vertical); emit splitViewToggle(false); } // new tab will be put at the end by default. int index = -1; if (_newTabBehavior == PutNewTabAfterCurrentTab) { QWidget* view = activeView(); if (view) { QList views = _viewSplitter->activeContainer()->views(); index = views.indexOf(view) + 1; } } // iterate over the view containers owned by this view manager // and create a new terminal display for the session in each of them, along with // a controller for the session/display pair foreach(ViewContainer* container, _viewSplitter->containers()) { createView(session, container, index); } } ViewContainer* ViewManager::createContainer() { ViewContainer* container = 0; switch (_navigationMethod) { case TabbedNavigation: { TabbedViewContainer* tabbedContainer = new TabbedViewContainer(_navigationPosition, this, _viewSplitter); container = tabbedContainer; connect(tabbedContainer, &TabbedViewContainer::detachTab, this, &ViewManager::detachView); connect(tabbedContainer, &TabbedViewContainer::closeTab, this, &ViewManager::closeTabFromContainer); } break; case NoNavigation: default: container = new StackedViewContainer(_viewSplitter); } // FIXME: these code feels duplicated container->setNavigationVisibility(_navigationVisibility); container->setNavigationPosition(_navigationPosition); container->setStyleSheet(_navigationStyleSheet); if (_showQuickButtons) { container->setFeatures(container->features() | ViewContainer::QuickNewView | ViewContainer::QuickCloseView); } else { container->setFeatures(container->features() & ~ViewContainer::QuickNewView & ~ViewContainer::QuickCloseView); } // connect signals and slots connect(container , &Konsole::ViewContainer::viewAdded , _containerSignalMapper , static_cast(&QSignalMapper::map)); connect(container , &Konsole::ViewContainer::viewRemoved , _containerSignalMapper , static_cast(&QSignalMapper::map)); _containerSignalMapper->setMapping(container, container); connect(container, static_cast(&Konsole::ViewContainer::newViewRequest), this, static_cast(&Konsole::ViewManager::newViewRequest)); connect(container, static_cast(&Konsole::ViewContainer::newViewRequest), this, static_cast(&Konsole::ViewManager::newViewRequest)); connect(container, &Konsole::ViewContainer::moveViewRequest, this , &Konsole::ViewManager::containerMoveViewRequest); connect(container , &Konsole::ViewContainer::viewRemoved , this , &Konsole::ViewManager::viewDestroyed); connect(container , &Konsole::ViewContainer::activeViewChanged , this , &Konsole::ViewManager::viewActivated); return container; } void ViewManager::containerMoveViewRequest(int index, int id, bool& moved, TabbedViewContainer* sourceTabbedContainer) { ViewContainer* container = qobject_cast(sender()); SessionController* controller = qobject_cast(ViewProperties::propertiesById(id)); if (!controller) return; // do not move the last tab in a split view. if (sourceTabbedContainer) { QPointer sourceContainer = qobject_cast(sourceTabbedContainer); if (_viewSplitter->containers().contains(sourceContainer)) { return; } else { ViewManager* sourceViewManager = sourceTabbedContainer->connectedViewManager(); // do not remove the last tab on the window if (qobject_cast(sourceViewManager->widget())->containers().size() > 1) { return; } } } createView(controller->session(), container, index); controller->session()->refresh(); moved = true; } void ViewManager::setNavigationMethod(NavigationMethod method) { _navigationMethod = method; KActionCollection* collection = _actionCollection; if (collection) { // FIXME: The following disables certain actions for the KPart that it // doesn't actually have a use for, to avoid polluting the action/shortcut // namespace of an application using the KPart (otherwise, a shortcut may // be in use twice, and the user gets to see an "ambiguous shortcut over- // load" error dialog). However, this approach sucks - it's the inverse of // what it should be. Rather than disabling actions not used by the KPart, // a method should be devised to only enable those that are used, perhaps // by using a separate action collection. const bool enable = (_navigationMethod != NoNavigation); QAction* action; action = collection->action("next-view"); if (action) action->setEnabled(enable); action = collection->action("previous-view"); if (action) action->setEnabled(enable); action = collection->action("last-tab"); if (action) action->setEnabled(enable); action = collection->action("split-view-left-right"); if (action) action->setEnabled(enable); action = collection->action("split-view-top-bottom"); if (action) action->setEnabled(enable); action = collection->action("rename-session"); if (action) action->setEnabled(enable); action = collection->action("move-view-left"); if (action) action->setEnabled(enable); action = collection->action("move-view-right"); if (action) action->setEnabled(enable); } } ViewManager::NavigationMethod ViewManager::navigationMethod() const { return _navigationMethod; } void ViewManager::containerViewsChanged(QObject* container) { if (_viewSplitter && container == _viewSplitter->activeContainer()) { emit viewPropertiesChanged(viewProperties()); } } void ViewManager::viewDestroyed(QWidget* view) { // Note: the received QWidget has already been destroyed, so // using dynamic_cast<> or qobject_cast<> does not work here TerminalDisplay* display = static_cast(view); Q_ASSERT(display); // 1. detach view from session // 2. if the session has no views left, close it Session* session = _sessionMap[ display ]; _sessionMap.remove(display); if (session) { - display->deleteLater(); - if (session->views().count() == 0) session->close(); } //we only update the focus if the splitter is still alive if (_viewSplitter) { - focusActiveView(); updateDetachViewState(); } // The below causes the menus to be messed up // Only happens when using the tab bar close button // if (_pluggedController) // emit unplugController(_pluggedController); } TerminalDisplay* ViewManager::createTerminalDisplay(Session* session) { TerminalDisplay* display = new TerminalDisplay(0); display->setRandomSeed(session->sessionId() * 31); return display; } const ColorScheme* ViewManager::colorSchemeForProfile(const Profile::Ptr profile) { const ColorScheme* colorScheme = ColorSchemeManager::instance()-> findColorScheme(profile->colorScheme()); if (!colorScheme) colorScheme = ColorSchemeManager::instance()->defaultColorScheme(); Q_ASSERT(colorScheme); return colorScheme; } void ViewManager::applyProfileToView(TerminalDisplay* view , const Profile::Ptr profile) { Q_ASSERT(profile); emit updateWindowIcon(); // load color scheme ColorEntry table[TABLE_COLORS]; const ColorScheme* colorScheme = colorSchemeForProfile(profile); colorScheme->getColorTable(table , view->randomSeed()); view->setColorTable(table); view->setOpacity(colorScheme->opacity()); view->setWallpaper(colorScheme->wallpaper()); // load font view->setAntialias(profile->antiAliasFonts()); view->setBoldIntense(profile->boldIntense()); view->setVTFont(profile->font()); // set scroll-bar position int scrollBarPosition = profile->property(Profile::ScrollBarPosition); if (scrollBarPosition == Enum::ScrollBarLeft) view->setScrollBarPosition(Enum::ScrollBarLeft); else if (scrollBarPosition == Enum::ScrollBarRight) view->setScrollBarPosition(Enum::ScrollBarRight); else if (scrollBarPosition == Enum::ScrollBarHidden) view->setScrollBarPosition(Enum::ScrollBarHidden); bool scrollFullPage = profile->property(Profile::ScrollFullPage); view->setScrollFullPage(scrollFullPage); // show hint about terminal size after resizing view->setShowTerminalSizeHint(profile->showTerminalSizeHint()); // terminal features view->setBlinkingCursorEnabled(profile->blinkingCursorEnabled()); view->setBlinkingTextEnabled(profile->blinkingTextEnabled()); int tripleClickMode = profile->property(Profile::TripleClickMode); view->setTripleClickMode(Enum::TripleClickModeEnum(tripleClickMode)); view->setAutoCopySelectedText(profile->autoCopySelectedText()); view->setUnderlineLinks(profile->underlineLinksEnabled()); view->setControlDrag(profile->property(Profile::CtrlRequiredForDrag)); view->setBidiEnabled(profile->bidiRenderingEnabled()); view->setLineSpacing(profile->lineSpacing()); view->setTrimTrailingSpaces(profile->property(Profile::TrimTrailingSpacesInSelectedText)); view->setOpenLinksByDirectClick(profile->property(Profile::OpenLinksByDirectClickEnabled)); int middleClickPasteMode = profile->property(Profile::MiddleClickPasteMode); if (middleClickPasteMode == Enum::PasteFromX11Selection) view->setMiddleClickPasteMode(Enum::PasteFromX11Selection); else if (middleClickPasteMode == Enum::PasteFromClipboard) view->setMiddleClickPasteMode(Enum::PasteFromClipboard); // margin/center - these are hard-fixed ATM view->setMargin(1); view->setCenterContents(false); // cursor shape int cursorShape = profile->property(Profile::CursorShape); if (cursorShape == Enum::BlockCursor) view->setKeyboardCursorShape(Enum::BlockCursor); else if (cursorShape == Enum::IBeamCursor) view->setKeyboardCursorShape(Enum::IBeamCursor); else if (cursorShape == Enum::UnderlineCursor) view->setKeyboardCursorShape(Enum::UnderlineCursor); // cursor color if (profile->useCustomCursorColor()) { const QColor& cursorColor = profile->customCursorColor(); view->setKeyboardCursorColor(cursorColor); } else { // an invalid QColor is used to inform the view widget to // draw the cursor using the default color( matching the text) view->setKeyboardCursorColor(QColor()); } // word characters view->setWordCharacters(profile->wordCharacters()); // bell mode view->setBellMode(profile->property(Profile::BellMode)); // mouse wheel zoom view->setMouseWheelZoom(profile->mouseWheelZoomEnabled()); } void ViewManager::updateViewsForSession(Session* session) { const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); foreach(TerminalDisplay* view, _sessionMap.keys(session)) { applyProfileToView(view, profile); } } void ViewManager::profileChanged(Profile::Ptr profile) { // update all views associated with this profile QHashIterator iter(_sessionMap); while (iter.hasNext()) { iter.next(); // if session uses this profile, update the display if (iter.key() != 0 && iter.value() != 0 && SessionManager::instance()->sessionProfile(iter.value()) == profile) { applyProfileToView(iter.key(), profile); } } } QList ViewManager::viewProperties() const { QList list; ViewContainer* container = _viewSplitter->activeContainer(); Q_ASSERT(container); foreach(QWidget* view, container->views()) { ViewProperties* properties = container->viewProperties(view); Q_ASSERT(properties); list << properties; } return list; } void ViewManager::saveSessions(KConfigGroup& group) { // find all unique session restore IDs QList ids; QHash unique; // first: sessions in the active container, preserving the order ViewContainer* container = _viewSplitter->activeContainer(); Q_ASSERT(container); TerminalDisplay* activeview = qobject_cast(container->activeView()); QListIterator viewIter(container->views()); int tab = 1; while (viewIter.hasNext()) { TerminalDisplay* view = qobject_cast(viewIter.next()); Q_ASSERT(view); Session* session = _sessionMap[view]; ids << SessionManager::instance()->getRestoreId(session); if (view == activeview) group.writeEntry("Active", tab); unique.insert(session, 1); tab++; } // second: all other sessions, in random order // we don't want to have sessions restored that are not connected foreach(Session * session, _sessionMap) { if (!unique.contains(session)) { ids << SessionManager::instance()->getRestoreId(session); unique.insert(session, 1); } } group.writeEntry("Sessions", ids); } void ViewManager::restoreSessions(const KConfigGroup& group) { QList ids = group.readEntry("Sessions", QList()); int activeTab = group.readEntry("Active", 0); TerminalDisplay* display = 0; int tab = 1; foreach(int id, ids) { Session* session = SessionManager::instance()->idToSession(id); createView(session); if (!session->isRunning()) session->run(); if (tab++ == activeTab) display = qobject_cast(activeView()); } if (display) { _viewSplitter->activeContainer()->setActiveView(display); display->setFocus(Qt::OtherFocusReason); } if (ids.isEmpty()) { // Session file is unusable, start default Profile Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); Session* session = SessionManager::instance()->createSession(profile); createView(session); if (!session->isRunning()) session->run(); } } uint qHash(QPointer display) { return qHash((TerminalDisplay*)display); } int ViewManager::sessionCount() { return this->_sessionMap.size(); } int ViewManager::currentSession() { QHash::iterator i; for (i = this->_sessionMap.begin(); i != this->_sessionMap.end(); ++i) if (i.key()->isVisible()) return i.value()->sessionId(); return -1; } int ViewManager::newSession() { Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); Session* session = SessionManager::instance()->createSession(profile); this->createView(session); session->run(); return session->sessionId(); } int ViewManager::newSession(QString profile, QString directory) { const QList profilelist = ProfileManager::instance()->allProfiles(); Profile::Ptr profileptr = ProfileManager::instance()->defaultProfile(); for (int i = 0; i < profilelist.size(); ++i) { if (profilelist.at(i)->name() == profile) { profileptr = profilelist.at(i); break; } } Session* session = SessionManager::instance()->createSession(profileptr); session->setInitialWorkingDirectory(directory); this->createView(session); session->run(); return session->sessionId(); } QString ViewManager::defaultProfile() { return ProfileManager::instance()->defaultProfile()->name(); } QStringList ViewManager::profileList() { return ProfileManager::instance()->availableProfileNames(); } void ViewManager::nextSession() { this->nextView(); } void ViewManager::prevSession() { this->previousView(); } void ViewManager::moveSessionLeft() { this->moveActiveViewLeft(); } void ViewManager::moveSessionRight() { this->moveActiveViewRight(); } void ViewManager::setTabWidthToText(bool useTextWidth) { ViewContainer* container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->setNavigationTextMode(useTextWidth); } void ViewManager::closeTabFromContainer(ViewContainer* container, QWidget* tab) { SessionController* controller = qobject_cast(container->viewProperties(tab)); Q_ASSERT(controller); if (controller) controller->closeSession(); } void ViewManager::setNavigationVisibility(int visibility) { _navigationVisibility = static_cast(visibility); foreach(ViewContainer* container, _viewSplitter->containers()) { container->setNavigationVisibility(_navigationVisibility); } } void ViewManager::setNavigationPosition(int position) { _navigationPosition = static_cast(position); foreach(ViewContainer* container, _viewSplitter->containers()) { Q_ASSERT(container->supportedNavigationPositions().contains(_navigationPosition)); container->setNavigationPosition(_navigationPosition); } } void ViewManager::setNavigationStyleSheet(const QString& styleSheet) { _navigationStyleSheet = styleSheet; foreach(ViewContainer* container, _viewSplitter->containers()) { container->setStyleSheet(_navigationStyleSheet); } } void ViewManager::setShowQuickButtons(bool show) { _showQuickButtons = show; foreach(ViewContainer* container, _viewSplitter->containers()) { if (_showQuickButtons) { container->setFeatures(container->features() | ViewContainer::QuickNewView | ViewContainer::QuickCloseView); } else { container->setFeatures(container->features() & ~ViewContainer::QuickNewView & ~ViewContainer::QuickCloseView); } } } void ViewManager::setNavigationBehavior(int behavior) { _newTabBehavior = static_cast(behavior); } diff --git a/src/ViewManager.h b/src/ViewManager.h index 047b7ee6..ee864718 100644 --- a/src/ViewManager.h +++ b/src/ViewManager.h @@ -1,393 +1,392 @@ /* Copyright 2006-2008 by Robert Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef VIEWMANAGER_H #define VIEWMANAGER_H // Qt #include #include #include // Konsole #include "Profile.h" #include "ViewContainer.h" class QSignalMapper; class KActionCollection; class KConfigGroup; namespace Konsole { class ColorScheme; class IncrementalSearchBar; class Session; class TerminalDisplay; class SessionController; class ViewProperties; class ViewSplitter; /** * Manages the terminal display widgets in a Konsole window or part. * * When a view manager is created, it constructs a splitter widget ( accessed via * widget() ) to hold one or more view containers. Each view container holds * one or more terminal displays and a navigation widget ( eg. tabs or a list ) * to allow the user to navigate between the displays in that container. * * The view manager provides menu actions ( defined in the 'konsoleui.rc' XML file ) * to manipulate the views and view containers - for example, actions to split the view * left/right or top/bottom, detach a view from the current window and navigate between * views and containers. These actions are added to the collection specified in the * ViewManager's constructor. * * The view manager provides facilities to construct display widgets for a terminal * session and also to construct the SessionController which provides the menus and other * user interface elements specific to each display/session pair. * */ class KONSOLEPRIVATE_EXPORT ViewManager : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.konsole.Window") public: /** * Constructs a new view manager with the specified @p parent. * View-related actions defined in 'konsoleui.rc' are created * and added to the specified @p collection. */ ViewManager(QObject* parent , KActionCollection* collection); ~ViewManager(); /** * Creates a new view to display the output from and deliver input to @p session. * Constructs a new container to hold the views if no container has yet been created. */ void createView(Session* session); /** * Applies the view-specific settings associated with specified @p profile * to the terminal display @p view. */ void applyProfileToView(TerminalDisplay* view , const Profile::Ptr profile); /** * Return the main widget for the view manager which * holds all of the views managed by this ViewManager instance. */ QWidget* widget() const; /** * Returns the view manager's active view. */ QWidget* activeView() const; /** * Returns the list of view properties for views in the active container. * Each view widget is associated with a ViewProperties instance which * provides access to basic information about the session being * displayed in the view, such as title, current directory and * associated icon. */ QList viewProperties() const; /** * This enum describes the available types of navigation widget * which newly created containers can provide to allow navigation * between open sessions. */ enum NavigationMethod { /** * Each container has a row of tabs (one per session) which the user * can click on to navigate between open sessions. */ TabbedNavigation, /** The container has no navigation widget. */ NoNavigation }; /** * This enum describes where newly created tab should be placed. */ enum NewTabBehavior { /** Put newly created tab at the end. */ PutNewTabAtTheEnd = 0, /** Put newly created tab right after current tab. */ PutNewTabAfterCurrentTab = 1 }; /** * Sets the type of widget provided to navigate between open sessions * in a container. The changes will only apply to newly created containers. * * The default method is TabbedNavigation. To disable navigation widgets, call * setNavigationMethod(ViewManager::NoNavigation) before creating any sessions. */ void setNavigationMethod(NavigationMethod method); /** * Returns the type of navigation widget created in new containers. * See setNavigationMethod() */ NavigationMethod navigationMethod() const; /** * Returns the controller for the active view. activeViewChanged() is * emitted when this changes. */ SessionController* activeViewController() const; /** * Returns the search bar. */ IncrementalSearchBar* searchBar() const; /** * Session management */ void saveSessions(KConfigGroup& group); void restoreSessions(const KConfigGroup& group); void setNavigationVisibility(int visibility); void setNavigationPosition(int position); void setNavigationBehavior(int behavior); void setNavigationStyleSheet(const QString& styleSheet); void setShowQuickButtons(bool show); int managerId() const; /** Returns a list of sessions in this ViewManager */ QList sessions() { return _sessionMap.values(); } signals: /** Emitted when the last view is removed from the view manager */ void empty(); /** Emitted when a session is detached from a view owned by this ViewManager */ void viewDetached(Session* session); /** * Emitted when the active view changes. * @param controller The controller associated with the active view */ void activeViewChanged(SessionController* controller); /** * Emitted when the current session needs unplugged from factory(). * @param controller The controller associated with the active view */ void unplugController(SessionController* controller); /** * Emitted when the list of view properties ( as returned by viewProperties() ) changes. * This occurs when views are added to or removed from the active container, or * if the active container is changed. */ void viewPropertiesChanged(const QList& propertiesList); /** * Emitted when the number of views containers changes. This is used to disable or * enable menu items which can only be used when there are one or multiple containers * visible. * * @param multipleViews True if there are multiple view containers open or false if there is * just a single view. */ void splitViewToggle(bool multipleViews); /** * Emitted when menu bar visibility changes because a profile that requires so is * activated. */ void setMenuBarVisibleRequest(bool); void updateWindowIcon(); /** Requests creation of a new view with the default profile. */ void newViewRequest(); /** Requests creation of a new view, with the selected profile. */ void newViewRequest(Profile::Ptr); public slots: /** DBus slot that returns the number of sessions in the current view. */ Q_SCRIPTABLE int sessionCount(); /** DBus slot that returns the current (active) session window */ Q_SCRIPTABLE int currentSession(); /** DBus slot that creates a new session in the current view. * @param profile the name of the profile to be used * @param directory the working directory where the session is * started. */ Q_SCRIPTABLE int newSession(QString profile, QString directory); // TODO: its semantic is application-wide. Move it to more appropriate place // DBus slot that returns the name of default profile Q_SCRIPTABLE QString defaultProfile(); // TODO: its semantic is application-wide. Move it to more appropriate place // DBus slot that returns a string list of defined (known) profiles Q_SCRIPTABLE QStringList profileList(); /** DBus slot that creates a new session in the current view with the associated * default profile and the default working directory */ Q_SCRIPTABLE int newSession(); /** DBus slot that changes the view port to the next session */ Q_SCRIPTABLE void nextSession(); /** DBus slot that changes the view port to the previous session */ Q_SCRIPTABLE void prevSession(); /** DBus slot that switches the current session (as returned by * currentSession()) with the left (or previous) one in the * navigation tab. */ Q_SCRIPTABLE void moveSessionLeft(); /** DBus slot that Switches the current session (as returned by * currentSession()) with the right (or next) one in the navigation * tab. */ Q_SCRIPTABLE void moveSessionRight(); /** DBus slot that sets ALL tabs' width to match their text */ Q_SCRIPTABLE void setTabWidthToText(bool); private slots: // called when the "Split View Left/Right" menu item is selected void splitLeftRight(); void splitTopBottom(); void closeActiveContainer(); void closeOtherContainers(); void expandActiveContainer(); void shrinkActiveContainer(); // called when the "Detach View" menu item is selected void detachActiveView(); void updateDetachViewState(); // called when a session terminates - the view manager will delete any // views associated with the session void sessionFinished(); // called when one view has been destroyed void viewDestroyed(QWidget* widget); // controller detects when an associated view is given the focus // and emits a signal. ViewManager listens for that signal // and then plugs the action into the UI //void viewFocused( SessionController* controller ); // called when the active view in a ViewContainer changes, so // that we can plug the appropriate actions into the UI void viewActivated(QWidget* view); // called when "Next View" shortcut is activated void nextView(); // called when "Previous View" shortcut is activated void previousView(); // called when "Switch to last tab" shortcut is activated void lastView(); // called when "Next View Container" shortcut is activated void nextContainer(); // called when the views in a container owned by this view manager // changes void containerViewsChanged(QObject* container); // called when a profile changes void profileChanged(Profile::Ptr profile); void updateViewsForSession(Session* session); // moves active view to the left void moveActiveViewLeft(); // moves active view to the right void moveActiveViewRight(); // switches to the view at visual position 'index' // in the current container void switchToView(int index); // called when a SessionController gains focus void controllerChanged(SessionController* controller); // called when a ViewContainer requests a view be // moved void containerMoveViewRequest(int index, int id, bool& success, TabbedViewContainer* sourceTabbedContainer); void detachView(ViewContainer* container, QWidget* view); void closeTabFromContainer(ViewContainer* container, QWidget* view); private: void createView(Session* session, ViewContainer* container, int index); static const ColorScheme* colorSchemeForProfile(const Profile::Ptr profile); void setupActions(); - void focusActiveView(); // takes a view from a view container owned by a different manager and places it in // newContainer owned by this manager void takeView(ViewManager* otherManager , ViewContainer* otherContainer, ViewContainer* newContainer, TerminalDisplay* view); void splitView(Qt::Orientation orientation); // creates a new container which can hold terminal displays ViewContainer* createContainer(); // removes a container and emits appropriate signals void removeContainer(ViewContainer* container); // creates a new terminal display // the 'session' is used so that the terminal display's random seed // can be set to something which depends uniquely on that session TerminalDisplay* createTerminalDisplay(Session* session = 0); // creates a new controller for a session/display pair which provides the menu // actions associated with that view, and exposes basic information // about the session ( such as title and associated icon ) to the display. SessionController* createController(Session* session , TerminalDisplay* display); private: QPointer _viewSplitter; QPointer _pluggedController; QHash _sessionMap; KActionCollection* _actionCollection; QSignalMapper* _containerSignalMapper; NavigationMethod _navigationMethod; ViewContainer::NavigationVisibility _navigationVisibility; ViewContainer::NavigationPosition _navigationPosition; bool _showQuickButtons; NewTabBehavior _newTabBehavior; QString _navigationStyleSheet; int _managerId; static int lastManagerId; }; } #endif diff --git a/src/ViewSplitter.cpp b/src/ViewSplitter.cpp index bfc727e5..d558ca3f 100644 --- a/src/ViewSplitter.cpp +++ b/src/ViewSplitter.cpp @@ -1,259 +1,264 @@ /* 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 // Konsole #include "ViewContainer.h" using Konsole::ViewSplitter; using Konsole::ViewContainer; ViewSplitter::ViewSplitter(QWidget* parent) : QSplitter(parent) , _recursiveSplitting(true) { } void ViewSplitter::childEmpty(ViewSplitter* splitter) { delete splitter; if (count() == 0) emit empty(this); } void ViewSplitter::adjustContainerSize(ViewContainer* container , int percentage) { int containerIndex = indexOf(container->containerWidget()); Q_ASSERT(containerIndex != -1); QList containerSizes = sizes(); const int oldSize = containerSizes[containerIndex]; const int newSize = static_cast(oldSize * (1.0 + percentage / 100.0)); const int perContainerDelta = (count() == 1) ? 0 : ((newSize - oldSize) / (count() - 1)) * (-1); for (int i = 0 ; i < containerSizes.count() ; i++) { if (i == containerIndex) containerSizes[i] = newSize; else containerSizes[i] = containerSizes[i] + perContainerDelta; } setSizes(containerSizes); } ViewSplitter* ViewSplitter::activeSplitter() { QWidget* widget = focusWidget() ? focusWidget() : this; ViewSplitter* splitter = 0; while (!splitter && widget) { splitter = qobject_cast(widget); widget = widget->parentWidget(); } Q_ASSERT(splitter); return splitter; } void ViewSplitter::registerContainer(ViewContainer* container) { _containers << container; - connect(container , static_cast(&Konsole::ViewContainer::destroyed) , this , &Konsole::ViewSplitter::containerDestroyed); - connect(container , &Konsole::ViewContainer::empty , this , &Konsole::ViewSplitter::containerEmpty); + // Connecting to container::destroyed() using the new-style connection + // syntax causes a crash at exit. I don't know why. Using the old-style + // syntax works. + //connect(container , static_cast(&Konsole::ViewContainer::destroyed) , this , &Konsole::ViewSplitter::containerDestroyed); + //connect(container , &Konsole::ViewContainer::empty , this , &Konsole::ViewSplitter::containerEmpty); + connect(container , SIGNAL(destroyed(ViewContainer*)) , this , SLOT(containerDestroyed(ViewContainer*))); + connect(container , SIGNAL(empty(ViewContainer*)) , this , SLOT(containerEmpty(ViewContainer*))); } void ViewSplitter::unregisterContainer(ViewContainer* container) { _containers.removeAll(container); disconnect(container , 0 , this , 0); } void ViewSplitter::updateSizes() { int space; if (orientation() == Qt::Horizontal) { space = width() / count(); } else { space = height() / count(); } QList widgetSizes; for (int i = 0; i < count(); i++) widgetSizes << space; setSizes(widgetSizes); } void ViewSplitter::setRecursiveSplitting(bool recursive) { _recursiveSplitting = recursive; } bool ViewSplitter::recursiveSplitting() const { return _recursiveSplitting; } void ViewSplitter::removeContainer(ViewContainer* container) { Q_ASSERT(containers().contains(container)); unregisterContainer(container); } void ViewSplitter::addContainer(ViewContainer* container , Qt::Orientation containerOrientation) { ViewSplitter* splitter = activeSplitter(); if (splitter->count() < 2 || containerOrientation == splitter->orientation() || !_recursiveSplitting) { splitter->registerContainer(container); splitter->addWidget(container->containerWidget()); if (splitter->orientation() != containerOrientation) splitter->setOrientation(containerOrientation); splitter->updateSizes(); } else { ViewSplitter* newSplitter = new ViewSplitter(this); connect(newSplitter , &Konsole::ViewSplitter::empty , splitter , &Konsole::ViewSplitter::childEmpty); ViewContainer* oldContainer = splitter->activeContainer(); const int oldContainerIndex = splitter->indexOf(oldContainer->containerWidget()); splitter->unregisterContainer(oldContainer); newSplitter->registerContainer(oldContainer); newSplitter->registerContainer(container); newSplitter->addWidget(oldContainer->containerWidget()); newSplitter->addWidget(container->containerWidget()); newSplitter->setOrientation(containerOrientation); newSplitter->updateSizes(); newSplitter->show(); splitter->insertWidget(oldContainerIndex, newSplitter); } } void ViewSplitter::containerEmpty(ViewContainer* /*container*/) { int children = 0; foreach(ViewContainer* container, _containers) { children += container->views().count(); } if (children == 0) emit allContainersEmpty(); } void ViewSplitter::containerDestroyed(ViewContainer* container) { Q_ASSERT(_containers.contains(container)); _containers.removeAll(container); if (count() == 0) { emit empty(this); } } void ViewSplitter::activateNextContainer() { ViewContainer* active = activeContainer(); int index = _containers.indexOf(active); if (index == -1) return; if (index == _containers.count() - 1) index = 0; else index++; setActiveContainer(_containers.at(index)); } void ViewSplitter::activatePreviousContainer() { ViewContainer* active = activeContainer(); int index = _containers.indexOf(active); if (index == 0) index = _containers.count() - 1; else index--; setActiveContainer(_containers.at(index)); } void ViewSplitter::setActiveContainer(ViewContainer* container) { QWidget* activeView = container->activeView(); if (activeView) activeView->setFocus(Qt::OtherFocusReason); } ViewContainer* ViewSplitter::activeContainer() const { if (QWidget* focusW = focusWidget()) { ViewContainer* focusContainer = 0; while (focusW != 0) { foreach(ViewContainer* container, _containers) { if (container->containerWidget() == focusW) { focusContainer = container; break; } } focusW = focusW->parentWidget(); } if (focusContainer) return focusContainer; } QList splitters = findChildren(); if (splitters.count() > 0) { return splitters.last()->activeContainer(); } else { if (_containers.count() > 0) return _containers.last(); else return 0; } }