Commit fb8890c1 authored by Kurt Hindenburg's avatar Kurt Hindenburg

Merge branch 'gitlab/drag_drop_terminal_split' into 'master'

Gitlab/drag drop terminal split

See merge request kde/konsole!7
parents 01e23151 2fd8cef0
......@@ -23,6 +23,7 @@
#include <QMouseEvent>
#include <QApplication>
#include <QMimeData>
namespace Konsole {
......@@ -32,6 +33,7 @@ DetachableTabBar::DetachableTabBar(QWidget *parent) :
_originalCursor(cursor()),
tabId(-1)
{
setAcceptDrops(true);
setUsesScrollButtons(false);
setElideMode(Qt::TextElideMode::ElideMiddle);
}
......@@ -115,4 +117,24 @@ void DetachableTabBar::mouseReleaseEvent(QMouseEvent *event)
}
}
void DetachableTabBar::dragEnterEvent(QDragEnterEvent* event)
{
const auto dragId = QStringLiteral("konsole/terminal_display");
if (event->mimeData()->hasFormat(dragId)) {
auto other_pid = event->mimeData()->data(dragId).toInt();
// don't accept the drop if it's another instance of konsole
if (qApp->applicationPid() != other_pid)
return;
event->accept();
}
}
void DetachableTabBar::dragMoveEvent(QDragMoveEvent* event)
{
int tabIdx = tabAt(event->pos());
if (tabIdx != -1) {
setCurrentIndex(tabIdx);
}
}
}
......@@ -41,6 +41,8 @@ protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent*event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent * event) override;
private:
DragType dragType;
......
......@@ -485,6 +485,7 @@ TerminalDisplay::TerminalDisplay(QWidget* parent)
, _searchBar(new IncrementalSearchBar(this))
, _headerBar(new TerminalHeaderBar(this))
, _searchResultRect(QRect())
, _drawOverlay(false)
{
// terminal applications are not designed with Right-To-Left in mind,
// so the layout is forced to Left-To-Right
......@@ -541,7 +542,6 @@ TerminalDisplay::TerminalDisplay(QWidget* parent)
_verticalLayout->setSpacing(0);
_verticalLayout->setMargin(0);
setLayout(_verticalLayout);
new AutoScrollHandler(this);
#ifndef QT_NO_ACCESSIBILITY
QAccessible::installFactory(Konsole::accessibleInterfaceFactory);
......@@ -562,6 +562,35 @@ TerminalDisplay::~TerminalDisplay()
_outputSuspendedMessageWidget = nullptr;
}
void TerminalDisplay::hideDragTarget()
{
_drawOverlay = false;
update();
}
void TerminalDisplay::showDragTarget()
{
auto cursorPos = mapFromGlobal(QCursor::pos());
using EdgeDistance = std::pair<int, Qt::Edge>;
auto closerToEdge = std::min<EdgeDistance>(
{
{cursorPos.x(), Qt::LeftEdge},
{cursorPos.y(), Qt::TopEdge},
{width() - cursorPos.x(), Qt::RightEdge},
{height() - cursorPos.y(), Qt::BottomEdge}
},
[](const EdgeDistance& left, const EdgeDistance& right) -> bool {
return left.first < right.first;
}
);
if (_overlayEdge == closerToEdge.second) {
return;
}
_overlayEdge = closerToEdge.second;
_drawOverlay = true;
update();
}
/* ------------------------------------------------------------------------- */
/* */
/* Display Operations */
......@@ -928,6 +957,13 @@ void TerminalDisplay::scrollImage(int lines , const QRect& screenWindowRegion)
Q_ASSERT(linesToMove > 0);
Q_ASSERT(bytesToMove > 0);
scrollRect.setTop( lines > 0 ? top : top + abs(lines) * _fontHeight);
scrollRect.setHeight(linesToMove * _fontHeight);
if (!scrollRect.isValid() || scrollRect.isEmpty()) {
return;
}
//scroll internal image
if (lines > 0) {
// check that the memory areas that we are going to move are valid
......@@ -938,9 +974,6 @@ void TerminalDisplay::scrollImage(int lines , const QRect& screenWindowRegion)
//scroll internal image down
memmove(firstCharPos , lastCharPos , bytesToMove);
//set region of display to scroll
scrollRect.setTop(top);
} else {
// check that the memory areas that we are going to move are valid
Q_ASSERT((char*)firstCharPos + bytesToMove <
......@@ -948,13 +981,7 @@ void TerminalDisplay::scrollImage(int lines , const QRect& screenWindowRegion)
//scroll internal image up
memmove(lastCharPos , firstCharPos , bytesToMove);
//set region of the display to scroll
scrollRect.setTop(top + abs(lines) * _fontHeight);
}
scrollRect.setHeight(linesToMove * _fontHeight);
Q_ASSERT(scrollRect.isValid() && !scrollRect.isEmpty());
//scroll the display vertically to match internal _image
scroll(0 , _fontHeight * (-lines) , scrollRect);
......@@ -1271,6 +1298,19 @@ void TerminalDisplay::paintEvent(QPaintEvent* pe)
paint.fillRect(rect, dimColor);
}
}
if (_drawOverlay) {
const auto y = _headerBar->isVisible() ? _headerBar->height() : 0;
const auto rect = _overlayEdge == Qt::LeftEdge ? QRect(0, y, width() / 2, height())
: _overlayEdge == Qt::TopEdge ? QRect(0, y, width(), height() / 2)
: _overlayEdge == Qt::RightEdge ? QRect(width() - width() / 2, y, width() / 2, height())
: QRect(0, height() - height() / 2, width(), height() / 2);
paint.setRenderHint(QPainter::Antialiasing);
paint.setPen(Qt::NoPen);
paint.setBrush(QColor(100,100,100, 127));
paint.drawRect(rect);
}
}
void TerminalDisplay::printContent(QPainter& painter, bool friendly)
......
......@@ -74,6 +74,9 @@ public:
explicit TerminalDisplay(QWidget *parent = nullptr);
~TerminalDisplay() Q_DECL_OVERRIDE;
void showDragTarget();
void hideDragTarget();
void applyProfile(const Profile::Ptr& profile);
/** Returns the terminal color palette used by the display. */
......@@ -483,6 +486,10 @@ public Q_SLOTS:
return _scrollbarLocation;
}
Qt::Edge droppedEdge() const {
return _overlayEdge;
}
// Used to show/hide the message widget
void updateReadOnlyState(bool readonly);
IncrementalSearchBar *searchBar() const;
......@@ -852,6 +859,9 @@ private:
TerminalHeaderBar *_headerBar;
QRect _searchResultRect;
friend class TerminalDisplayAccessible;
bool _drawOverlay;
Qt::Edge _overlayEdge;
};
class AutoScrollHandler : public QObject
......
......@@ -41,6 +41,8 @@
#include <QSplitter>
#include <QStyleOptionTabBarBase>
#include <QStylePainter>
#include <QDrag>
#include <QMimeData>
namespace Konsole {
......@@ -80,22 +82,19 @@ TerminalHeaderBar::TerminalHeaderBar(QWidget *parent)
setAutoFillBackground(true);
terminalFocusOut();
connect(m_toggleExpandedMode, &QToolButton::clicked,
this, &TerminalHeaderBar::requestToggleExpansion);
}
// Hack untill I can detangle the creation of the TerminalViews
void TerminalHeaderBar::finishHeaderSetup(ViewProperties *properties)
{
//TODO: Fix ViewProperties signals.
connect(properties, &Konsole::ViewProperties::titleChanged, this,
[this, properties]{
auto controller = dynamic_cast<SessionController*>(properties);
connect(properties, &Konsole::ViewProperties::titleChanged, this, [this, properties]{
m_terminalTitle->setText(properties->title());
});
connect(m_closeBtn, &QToolButton::clicked, this, [properties]{
auto controller = qobject_cast<SessionController*>(properties);
controller->closeSession();
});
connect(properties, &Konsole::ViewProperties::iconChanged, this, [this, properties] {
m_terminalIcon->setPixmap(properties->icon().pixmap(QSize(22,22)));
});
......@@ -104,8 +103,7 @@ void TerminalHeaderBar::finishHeaderSetup(ViewProperties *properties)
m_terminalActivity->setPixmap(QPixmap());
});
connect(m_toggleExpandedMode, &QToolButton::clicked,
this, &TerminalHeaderBar::requestToggleExpansion);
connect(m_closeBtn, &QToolButton::clicked, controller, &SessionController::closeSession);
}
void TerminalHeaderBar::paintEvent(QPaintEvent *paintEvent)
......@@ -145,12 +143,24 @@ void TerminalHeaderBar::paintEvent(QPaintEvent *paintEvent)
void TerminalHeaderBar::mouseMoveEvent(QMouseEvent* ev)
{
Q_UNUSED(ev);
if (m_toggleExpandedMode->isChecked()) {
return;
}
auto point = ev->pos() - m_startDrag;
if (point.manhattanLength() > 10) {
auto drag = new QDrag(parent());
auto mimeData = new QMimeData();
QByteArray payload;
payload.setNum(qApp->applicationPid());
mimeData->setData(QStringLiteral("konsole/terminal_display"), payload);
drag->setMimeData(mimeData);
drag->start();
}
}
void TerminalHeaderBar::mousePressEvent(QMouseEvent* ev)
{
Q_UNUSED(ev);
m_startDrag = ev->pos();
}
void TerminalHeaderBar::mouseReleaseEvent(QMouseEvent* ev)
......
......@@ -23,6 +23,7 @@
#define TERMINAL_HEADER_BAR_H
#include <QWidget>
#include <QPoint>
class QLabel;
class QToolButton;
......@@ -59,6 +60,7 @@ private:
QToolButton *m_closeBtn;
QToolButton *m_toggleExpandedMode;
bool m_terminalIsFocused;
QPoint m_startDrag;
};
} // namespace Konsole
......
......@@ -267,6 +267,12 @@ void TabbedViewContainer::moveActiveView(MoveDirection direction)
setCurrentIndex(newIndex);
}
void TabbedViewContainer::terminalDisplayDropped(TerminalDisplay *terminalDisplay) {
Session* terminalSession = terminalDisplay->sessionController()->session();
terminalDisplay->sessionController()->deleteLater();
connectedViewManager()->attachView(terminalDisplay, terminalSession);
}
void TabbedViewContainer::addSplitter(ViewSplitter *viewSplitter, int index) {
if (index == -1) {
index = addTab(viewSplitter, QString());
......@@ -274,6 +280,10 @@ void TabbedViewContainer::addSplitter(ViewSplitter *viewSplitter, int index) {
insertTab(index, viewSplitter, QString());
}
connect(viewSplitter, &ViewSplitter::destroyed, this, &TabbedViewContainer::viewDestroyed);
disconnect(viewSplitter, &ViewSplitter::terminalDisplayDropped, nullptr, nullptr);
connect(viewSplitter, &ViewSplitter::terminalDisplayDropped, this, &TabbedViewContainer::terminalDisplayDropped);
auto terminalDisplays = viewSplitter->findChildren<TerminalDisplay*>();
foreach(TerminalDisplay* terminal, terminalDisplays) {
connectTerminalDisplay(terminal);
......@@ -298,6 +308,8 @@ void TabbedViewContainer::addView(TerminalDisplay *view)
connectTerminalDisplay(view);
connect(viewSplitter, &ViewSplitter::destroyed, this, &TabbedViewContainer::viewDestroyed);
connect(viewSplitter, &ViewSplitter::terminalDisplayDropped, this, &TabbedViewContainer::terminalDisplayDropped);
setCurrentIndex(index);
emit viewAdded(view);
}
......
......@@ -161,6 +161,7 @@ public:
};
void setNavigationBehavior(int behavior);
void terminalDisplayDropped(TerminalDisplay* terminalDisplay);
Q_SIGNALS:
/** Emitted when the container has no more children */
......@@ -172,6 +173,8 @@ Q_SIGNALS:
/** Requests creation of a new view, with the selected profile. */
void newViewWithProfileRequest(const Profile::Ptr&);
/** a terminalDisplay was dropped in a child Splitter */
/**
* 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
......
......@@ -253,7 +253,6 @@ void ViewManager::setupActions()
collection->addAction(QStringLiteral("switch-to-tab-%1").arg(i), action);
}
connect(_viewContainer, &TabbedViewContainer::viewAdded, this, &ViewManager::toggleActionsBasedOnState);
connect(_viewContainer, &TabbedViewContainer::viewRemoved, this, &ViewManager::toggleActionsBasedOnState);
connect(_viewContainer, &QTabWidget::currentChanged, this, &ViewManager::toggleActionsBasedOnState);
......@@ -433,6 +432,8 @@ QHash<TerminalDisplay*, Session*> ViewManager::forgetAll(ViewSplitter* splitter)
Session* ViewManager::forgetTerminal(TerminalDisplay* terminal)
{
disconnect(terminal, &TerminalDisplay::requestToggleExpansion, nullptr, nullptr);
removeController(terminal->sessionController());
auto session = _sessionMap.take(terminal);
if (session != nullptr) {
......@@ -591,10 +592,8 @@ SessionController *ViewManager::createController(Session *session, TerminalDispl
// should this be handed by ViewManager::unplugController signal
void ViewManager::removeController(SessionController* controller)
{
disconnect(controller, &Konsole::SessionController::focused, this,
&Konsole::ViewManager::controllerChanged);
if (_pluggedController == controller) {
_pluggedController = nullptr;
_pluggedController.clear();
}
controller->deleteLater();
}
......@@ -620,6 +619,14 @@ void ViewManager::attachView(TerminalDisplay *terminal, Session *session)
{
connect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished,
Qt::UniqueConnection);
// Disconnect from the other viewcontainer.
disconnect(terminal, &TerminalDisplay::requestToggleExpansion, nullptr, nullptr);
// reconnect on this container.
connect(terminal, &TerminalDisplay::requestToggleExpansion,
_viewContainer, &TabbedViewContainer::toggleMaximizeCurrentTerminal, Qt::UniqueConnection);
_sessionMap[terminal] = session;
createController(session, terminal);
toggleActionsBasedOnState();
......
......@@ -26,6 +26,14 @@
#include <QDebug>
#include <QChildEvent>
#include <QScrollBar>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QDragMoveEvent>
#include <QMimeType>
#include <QMimeData>
#include <QApplication>
#include <memory>
// Konsole
#include "ViewContainer.h"
......@@ -39,14 +47,20 @@ using Konsole::TerminalDisplay;
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)
{
const int containerIndex = indexOf(activeTerminalDisplay());
auto focusedTerminalDisplay = activeTerminalDisplay();
Q_ASSERT(focusedTerminalDisplay);
auto parentSplitter = qobject_cast<ViewSplitter*>(focusedTerminalDisplay->parent());
const int containerIndex = parentSplitter->indexOf(activeTerminalDisplay());
Q_ASSERT(containerIndex != -1);
QList<int> containerSizes = sizes();
QList<int> containerSizes = parentSplitter->sizes();
const int oldSize = containerSizes[containerIndex];
const int newSize = static_cast<int>(oldSize * (1.0 + percentage / 100.0));
......@@ -57,7 +71,7 @@ void ViewSplitter::adjustActiveTerminalDisplaySize(int percentage)
}
containerSizes[containerIndex] = newSize;
setSizes(containerSizes);
parentSplitter->setSizes(containerSizes);
}
// Get the first splitter that's a parent of the current focused widget.
......@@ -82,31 +96,26 @@ void ViewSplitter::updateSizes()
setSizes(QVector<int>(count(), space).toList());
}
void ViewSplitter::addTerminalDisplay(TerminalDisplay *terminalDisplay, Qt::Orientation containerOrientation)
void ViewSplitter::addTerminalDisplay(TerminalDisplay *terminalDisplay, Qt::Orientation containerOrientation, AddBehavior behavior)
{
ViewSplitter *splitter = activeSplitter();
const int currentIndex = !splitter->activeTerminalDisplay() ? splitter->count()
: splitter->indexOf(splitter->activeTerminalDisplay());
if (splitter->count() < 2) {
splitter->addWidget(terminalDisplay);
splitter->insertWidget(behavior == AddBehavior::AddBefore ? currentIndex : currentIndex + 1, terminalDisplay);
splitter->setOrientation(containerOrientation);
} else if (containerOrientation == splitter->orientation()) {
auto activeDisplay = splitter->activeTerminalDisplay();
if (!activeDisplay) {
splitter->addWidget(terminalDisplay);
} else {
const int currentIndex = splitter->indexOf(activeDisplay);
splitter->insertWidget(currentIndex, terminalDisplay);
}
splitter->insertWidget(currentIndex, terminalDisplay);
} else {
auto newSplitter = new ViewSplitter();
TerminalDisplay *oldTerminalDisplay = splitter->activeTerminalDisplay();
const int oldContainerIndex = splitter->indexOf(oldTerminalDisplay);
newSplitter->addWidget(oldTerminalDisplay);
newSplitter->addWidget(terminalDisplay);
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();
......@@ -269,3 +278,78 @@ ViewSplitter *ViewSplitter::getToplevelSplitter()
}
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<TerminalDisplay*>(currentWidget)) {
if (currentDragTarget && currentDragTarget != terminal) {
currentDragTarget->hideDragTarget();
}
if (terminal == ev->source()) {
return;
}
currentDragTarget = terminal;
currentDragTarget->showDragTarget();
}
}
void Konsole::ViewSplitter::dragLeaveEvent(QDragLeaveEvent* event)
{
if (currentDragTarget) {
currentDragTarget->hideDragTarget();
currentDragTarget = nullptr;
}
}
void Konsole::ViewSplitter::dropEvent(QDropEvent* ev)
{
if (ev->mimeData()->hasFormat(QStringLiteral("konsole/terminal_display"))) {
if (getToplevelSplitter()->terminalMaximized()) {
return;
}
if (currentDragTarget) {
currentDragTarget->hideDragTarget();
auto source = qobject_cast<TerminalDisplay*>(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.
getToplevelSplitter()->terminalDisplayDropped(source);
addTerminalDisplay(source, orientation, behavior);
source->setVisible(true);
currentDragTarget = nullptr;
}
}
}
......@@ -30,6 +30,10 @@
#include "konsoleprivate_export.h"
class QFocusEvent;
class QDragMoveEvent;
class QDragEnterEvent;
class QDropEvent;
class QDragLeaveEvent;
namespace Konsole {
class TerminalDisplay;
......@@ -52,7 +56,7 @@ class KONSOLEPRIVATE_EXPORT ViewSplitter : public QSplitter
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.
......@@ -68,10 +72,7 @@ public:
* will be created, into which the container will
* be inserted.
*/
void addTerminalDisplay(TerminalDisplay *terminalDisplay, Qt::Orientation orientation);
/** Removes a container from the splitter. The container is not deleted. */
void removeTerminalDisplay(TerminalDisplay *terminalDisplay);
void addTerminalDisplay(TerminalDisplay* terminalDisplay, Qt::Orientation containerOrientation, AddBehavior behavior = AddBehavior::AddAfter);
/** Returns the child ViewSplitter widget which currently has the focus */
ViewSplitter *activeSplitter();
......@@ -98,11 +99,6 @@ public:
/** returns the splitter that has no splitter as a parent. */
ViewSplitter *getToplevelSplitter();
/**
* Gives the focus to the active view in the specified container
*/
void setActiveTerminalDisplay(TerminalDisplay *container);
/**
* 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
......@@ -122,6 +118,16 @@ public:
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;
Q_SIGNALS:
void terminalDisplayDropped(TerminalDisplay *terminalDisplay);
private:
/** recursively walks the object tree looking for Splitters and
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment