diff --git a/src/ViewSplitter.cpp b/src/ViewSplitter.cpp index 0ed6afa5..89198335 100644 --- a/src/ViewSplitter.cpp +++ b/src/ViewSplitter.cpp @@ -1,358 +1,362 @@ /* This file is part of the Konsole Terminal. Copyright 2006-2008 Robert Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ // Own #include "ViewSplitter.h" // Qt #include #include #include #include #include #include #include // Konsole #include "ViewContainer.h" #include "TerminalDisplay.h" using Konsole::ViewSplitter; using Konsole::TerminalDisplay; //TODO: Connect the TerminalDisplay destroyed signal here. ViewSplitter::ViewSplitter(QWidget *parent) : QSplitter(parent) { setAcceptDrops(true); } /* This function is called on the toplevel splitter, we need to look at the actual ViewSplitter inside it */ void ViewSplitter::adjustActiveTerminalDisplaySize(int percentage) { auto focusedTerminalDisplay = activeTerminalDisplay(); Q_ASSERT(focusedTerminalDisplay); auto parentSplitter = qobject_cast(focusedTerminalDisplay->parent()); const int containerIndex = parentSplitter->indexOf(activeTerminalDisplay()); Q_ASSERT(containerIndex != -1); QList containerSizes = parentSplitter->sizes(); const int oldSize = containerSizes[containerIndex]; const auto newSize = static_cast(oldSize * (1.0 + percentage / 100.0)); const int perContainerDelta = (count() == 1) ? 0 : ((newSize - oldSize) / (count() - 1)) * (-1); for (int& size : containerSizes) { size += perContainerDelta; } containerSizes[containerIndex] = newSize; parentSplitter->setSizes(containerSizes); } // Get the first splitter that's a parent of the current focused widget. ViewSplitter *ViewSplitter::activeSplitter() { QWidget *widget = focusWidget() != nullptr ? focusWidget() : this; ViewSplitter *splitter = nullptr; while ((splitter == nullptr) && (widget != nullptr)) { splitter = qobject_cast(widget); widget = widget->parentWidget(); } Q_ASSERT(splitter); return splitter; } void ViewSplitter::updateSizes() { const int space = (orientation() == Qt::Horizontal ? width() : height()) / count(); setSizes(QVector(count(), space).toList()); } void ViewSplitter::addTerminalDisplay(TerminalDisplay *terminalDisplay, Qt::Orientation containerOrientation, AddBehavior behavior) { ViewSplitter *splitter = activeSplitter(); const int currentIndex = splitter->activeTerminalDisplay() == nullptr ? splitter->count() : splitter->indexOf(splitter->activeTerminalDisplay()); if (splitter->count() < 2) { splitter->insertWidget(behavior == AddBehavior::AddBefore ? currentIndex : currentIndex + 1, terminalDisplay); splitter->setOrientation(containerOrientation); } else if (containerOrientation == splitter->orientation()) { splitter->insertWidget(currentIndex, terminalDisplay); } else { auto newSplitter = new ViewSplitter(); TerminalDisplay *oldTerminalDisplay = splitter->activeTerminalDisplay(); const int oldContainerIndex = splitter->indexOf(oldTerminalDisplay); newSplitter->addWidget(behavior == AddBehavior::AddBefore ? terminalDisplay : oldTerminalDisplay); newSplitter->addWidget(behavior == AddBehavior::AddBefore ? oldTerminalDisplay : terminalDisplay); newSplitter->setOrientation(containerOrientation); newSplitter->updateSizes(); newSplitter->show(); splitter->insertWidget(oldContainerIndex, newSplitter); } splitter->updateSizes(); } void ViewSplitter::childEvent(QChildEvent *event) { QSplitter::childEvent(event); if (event->removed()) { if (count() == 0) { deleteLater(); } if (findChild() == nullptr) { deleteLater(); } } auto terminals = getToplevelSplitter()->findChildren(); if (terminals.size() == 1) { terminals.at(0)->headerBar()->setVisible(false); } else { for(auto terminal : terminals) { terminal->headerBar()->setVisible(true); } } } void ViewSplitter::handleFocusDirection(Qt::Orientation orientation, int direction) { auto terminalDisplay = activeTerminalDisplay(); auto parentSplitter = qobject_cast(terminalDisplay->parentWidget()); auto topSplitter = parentSplitter->getToplevelSplitter(); // Find the theme's splitter width + extra space to find valid terminal // See https://bugs.kde.org/show_bug.cgi?id=411387 for more info const auto handleWidth = parentSplitter->handleWidth() + 3; const auto start = QPoint(terminalDisplay->x(), terminalDisplay->y()); const auto startMapped = parentSplitter->mapTo(topSplitter, start); const int newX = orientation != Qt::Horizontal ? startMapped.x() + handleWidth : direction == 1 ? startMapped.x() + terminalDisplay->width() + handleWidth : startMapped.x() - handleWidth; const int newY = orientation != Qt::Vertical ? startMapped.y() + handleWidth : direction == 1 ? startMapped.y() + terminalDisplay->height() + handleWidth : startMapped.y() - handleWidth; const auto newPoint = QPoint(newX, newY); auto child = topSplitter->childAt(newPoint); TerminalDisplay *focusTerminal = nullptr; if (auto* terminal = qobject_cast(child)) { focusTerminal = terminal; } else if (qobject_cast(child) != nullptr) { auto targetSplitter = qobject_cast(child->parent()); focusTerminal = qobject_cast(targetSplitter->widget(0)); } else if (qobject_cast(child) != nullptr) { while(child != nullptr && focusTerminal == nullptr) { focusTerminal = qobject_cast(child->parentWidget()); child = child->parentWidget(); } } if (focusTerminal != nullptr) { focusTerminal->setFocus(Qt::OtherFocusReason); } } void ViewSplitter::focusUp() { handleFocusDirection(Qt::Vertical, -1); } void ViewSplitter::focusDown() { handleFocusDirection(Qt::Vertical, +1); } void ViewSplitter::focusLeft() { handleFocusDirection(Qt::Horizontal, -1); } void ViewSplitter::focusRight() { handleFocusDirection(Qt::Horizontal, +1); } TerminalDisplay *ViewSplitter::activeTerminalDisplay() const { auto focusedWidget = qobject_cast(focusWidget()); return focusedWidget != nullptr ? focusedWidget : findChild(); } void ViewSplitter::toggleMaximizeCurrentTerminal() { m_terminalMaximized = !m_terminalMaximized; handleMinimizeMaximize(m_terminalMaximized); } namespace { void restoreAll(QList&& terminalDisplays, QList&& splitters) { for (auto splitter : splitters) { splitter->setVisible(true); } for (auto terminalDisplay : terminalDisplays) { terminalDisplay->setVisible(true); } } } bool ViewSplitter::hideRecurse(TerminalDisplay *currentTerminalDisplay) { bool allHidden = true; for(int i = 0, end = count(); i < end; i++) { if (auto *maybeSplitter = qobject_cast(widget(i))) { allHidden = maybeSplitter->hideRecurse(currentTerminalDisplay) && allHidden; continue; } if (auto maybeTerminalDisplay = qobject_cast(widget(i))) { if (maybeTerminalDisplay == currentTerminalDisplay) { allHidden = false; } else { maybeTerminalDisplay->setVisible(false); } } } if (allHidden) { setVisible(false); } return allHidden; } void ViewSplitter::handleMinimizeMaximize(bool maximize) { auto topLevelSplitter = getToplevelSplitter(); auto currentTerminalDisplay = topLevelSplitter->activeTerminalDisplay(); if (maximize) { for (int i = 0, end = topLevelSplitter->count(); i < end; i++) { auto widgetAt = topLevelSplitter->widget(i); if (auto *maybeSplitter = qobject_cast(widgetAt)) { maybeSplitter->hideRecurse(currentTerminalDisplay); } if (auto maybeTerminalDisplay = qobject_cast(widgetAt)) { if (maybeTerminalDisplay != currentTerminalDisplay) { maybeTerminalDisplay->setVisible(false); } } } } else { restoreAll(topLevelSplitter->findChildren(), topLevelSplitter->findChildren()); } } ViewSplitter *ViewSplitter::getToplevelSplitter() { ViewSplitter *current = this; while(qobject_cast(current->parentWidget()) != nullptr) { current = qobject_cast(current->parentWidget()); } return current; } namespace { TerminalDisplay *currentDragTarget = nullptr; } void Konsole::ViewSplitter::dragEnterEvent(QDragEnterEvent* ev) { const auto dragId = QStringLiteral("konsole/terminal_display"); if (ev->mimeData()->hasFormat(dragId)) { auto other_pid = ev->mimeData()->data(dragId).toInt(); // don't accept the drop if it's another instance of konsole if (qApp->applicationPid() != other_pid) { return; } if (getToplevelSplitter()->terminalMaximized()) { return; } ev->accept(); } } void Konsole::ViewSplitter::dragMoveEvent(QDragMoveEvent* ev) { auto currentWidget = childAt(ev->pos()); if (auto terminal = qobject_cast(currentWidget)) { if ((currentDragTarget != nullptr) && currentDragTarget != terminal) { currentDragTarget->hideDragTarget(); } if (terminal == ev->source()) { return; } currentDragTarget = terminal; auto localPos = currentDragTarget->mapFromParent(ev->pos()); currentDragTarget->showDragTarget(localPos); } } void Konsole::ViewSplitter::dragLeaveEvent(QDragLeaveEvent* event) { Q_UNUSED(event); if (currentDragTarget != nullptr) { currentDragTarget->hideDragTarget(); currentDragTarget = nullptr; } } void Konsole::ViewSplitter::dropEvent(QDropEvent* ev) { if (ev->mimeData()->hasFormat(QStringLiteral("konsole/terminal_display"))) { if (getToplevelSplitter()->terminalMaximized()) { return; } if (currentDragTarget != nullptr) { currentDragTarget->hideDragTarget(); auto source = qobject_cast(ev->source()); source->setVisible(false); source->setParent(nullptr); currentDragTarget->setFocus(Qt::OtherFocusReason); const auto droppedEdge = currentDragTarget->droppedEdge(); AddBehavior behavior = droppedEdge == Qt::LeftEdge || droppedEdge == Qt::TopEdge ? AddBehavior::AddBefore : AddBehavior::AddAfter; Qt::Orientation orientation = droppedEdge == Qt::LeftEdge || droppedEdge == Qt::RightEdge ? Qt::Horizontal : Qt::Vertical; // topLevel is the splitter that's connected with the ViewManager // that in turn can call the SessionController. emit getToplevelSplitter()->terminalDisplayDropped(source); addTerminalDisplay(source, orientation, behavior); source->setVisible(true); currentDragTarget = nullptr; } } } - +void Konsole::ViewSplitter::showEvent(QShowEvent *) +{ + // Fixes lost focus in background mode. + setFocusProxy(activeSplitter()->activeTerminalDisplay()); +} diff --git a/src/ViewSplitter.h b/src/ViewSplitter.h index 3649292b..72f4638e 100644 --- a/src/ViewSplitter.h +++ b/src/ViewSplitter.h @@ -1,146 +1,147 @@ /* 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 VIEWSPLITTER_H #define VIEWSPLITTER_H // Qt #include // Konsole #include "konsoleprivate_export.h" class QDragMoveEvent; class QDragEnterEvent; class QDropEvent; class QDragLeaveEvent; namespace Konsole { class TerminalDisplay; /** * A splitter which holds a number of ViewContainer objects and allows * the user to control the size of each view container by dragging a splitter * bar between them. * * Each splitter can also contain child ViewSplitter widgets, allowing * for a hierarchy of view splitters and containers. * * The addContainer() method is used to split the existing view and * insert a new view container. * Containers can only be removed from the hierarchy by deleting them. */ class KONSOLEPRIVATE_EXPORT ViewSplitter : public QSplitter { Q_OBJECT public: explicit ViewSplitter(QWidget *parent = nullptr); enum class AddBehavior {AddBefore, AddAfter}; /** * Locates the child ViewSplitter widget which currently has the focus * and inserts the container into it. * * @param terminalDisplay The container to insert * @param containerOrientation Specifies whether the view should be split * horizontally or vertically. If the orientation * is the same as the ViewSplitter into which the * container is to be inserted, or if the splitter * has fewer than two child widgets then the container * will be added to that splitter. If the orientation * is different, then a new child splitter * will be created, into which the container will * be inserted. * @param behavior Specifies whether to add new terminal after current * tab or at end. */ void addTerminalDisplay(TerminalDisplay* terminalDisplay, Qt::Orientation containerOrientation, AddBehavior behavior = AddBehavior::AddAfter); /** Returns the child ViewSplitter widget which currently has the focus */ ViewSplitter *activeSplitter(); /** * Returns the container which currently has the focus or 0 if none * of the immediate child containers have the focus. This does not * search through child splitters. activeSplitter() can be used * to search recursively through child splitters for the splitter * which currently has the focus. * * To find the currently active container, use * mySplitter->activeSplitter()->activeTerminalDisplay() where * mySplitter is the ViewSplitter widget at the top of the hierarchy. */ TerminalDisplay *activeTerminalDisplay() const; /** Makes the current TerminalDisplay expanded to 100% of the view */ void toggleMaximizeCurrentTerminal(); void handleMinimizeMaximize(bool maximize); /** returns the splitter that has no splitter as a parent. */ ViewSplitter *getToplevelSplitter(); /** * Changes the size of the specified @p container by a given @p percentage. * @p percentage may be positive ( in which case the size of the container * is increased ) or negative ( in which case the size of the container * is decreased ). * * The sizes of the remaining containers are increased or decreased * uniformly to maintain the width of the splitter. */ void adjustActiveTerminalDisplaySize(int percentage); void focusUp(); void focusDown(); void focusLeft(); void focusRight(); void handleFocusDirection(Qt::Orientation orientation, int direction); void childEvent(QChildEvent* event) override; bool terminalMaximized() const { return m_terminalMaximized; } protected: void dragEnterEvent(QDragEnterEvent *ev) override; void dragMoveEvent(QDragMoveEvent *ev) override; void dragLeaveEvent(QDragLeaveEvent * event) override; void dropEvent(QDropEvent *ev) override; + void showEvent(QShowEvent *) override; Q_SIGNALS: void terminalDisplayDropped(TerminalDisplay *terminalDisplay); private: /** recursively walks the object tree looking for Splitters and * TerminalDisplays, hidding the ones that should be hidden. * If a terminal display is not hidden in a subtree, we cannot * hide the whole tree. * * @p currentTerminalDisplay the only terminal display that will still be visible. */ bool hideRecurse(TerminalDisplay *currentTerminalDisplay); void updateSizes(); bool m_terminalMaximized = false; }; } #endif //VIEWSPLITTER_H