Commit f12ee2f7 authored by Artur Grunau's avatar Artur Grunau
Browse files

Support dragging VP widgets back into the MDI area

VisualizationPipelineWidget and PipelineMdiSubwindow have been extended
to report changes in their positions to VisualizationPipelineWrapper.
Based on the information the latter receives, it decides when to undock
MDI subwindows and dock floating pipeline widgets.

As a result, it's now possible to drag visualization pipeline widgets
back into the MDI area and turn them into MDI subwindows.
parent 62373ebf
......@@ -31,40 +31,65 @@
#include <QMdiArea>
#include <QMoveEvent>
#include <QApplication>
namespace campvis {
void PipelineMdiSubWindow::moveEvent(QMoveEvent* event) {
// Special move logic only applies if the event was generated by the user dragging the
// window. If it was caused by Qt laying out widgets before displaying them, ignore it.
if (QApplication::mouseButtons() != Qt::LeftButton) {
return;
PipelineMdiSubWindow::PipelineMdiSubWindow(QWidget* parent /*= 0*/, Qt::WindowFlags flags /*= 0*/)
: QMdiSubWindow(parent, flags)
, _dragActive(false)
, _lastMousePos()
{}
void PipelineMdiSubWindow::stopWindowDrag() {
if (_dragActive) {
_dragActive = false;
releaseMouse();
}
}
const QPoint& pos = event->pos();
QMdiArea* mdiArea = this->mdiArea();
int halfWidth = frameGeometry().width() / 2;
int halfHeight = frameGeometry().height() / 2;
void PipelineMdiSubWindow::mouseMoveEvent(QMouseEvent* event) {
if (event->buttons().testFlag(Qt::LeftButton)) {
const QPoint& mousePos = event->globalPos();
// Eject the subwindow if more than half of it has left the MDI area
if (pos.x() < -halfWidth || pos.y() < -halfHeight ||
pos.x() > mdiArea->width() - halfWidth ||
pos.y() > mdiArea->height() - halfHeight)
{
QWidget *widget = this->widget();
const QPoint& absolutePos = mdiArea->mapToGlobal(pos);
if (!_dragActive) {
_dragActive = true;
_lastMousePos = mousePos;
return QMdiSubWindow::mouseMoveEvent(event);
}
this->setWidget(0);
mdiArea->removeSubWindow(this);
QPoint newPos = pos() + (mousePos - _lastMousePos);
if (widget != 0) {
widget->move(absolutePos);
widget->show();
/*
* Dragging the subwindow upwards out of the MDI area is blocked for 2 reasons:
* - the subwindow can't be detached and focused reliably in such cases, possibly due
* to the main window's title bar being in the way
* - that's how moving subwindows in an MDI area works by default
*/
if (newPos.y() < 0) {
newPos.setY(0);
_lastMousePos.setX(mousePos.x());
}
else {
_lastMousePos = mousePos;
}
emit s_leftMdiArea();
move(newPos);
emit s_positionChanged(newPos);
}
else {
QMdiSubWindow::mouseMoveEvent(event);
}
}
void PipelineMdiSubWindow::mouseReleaseEvent(QMouseEvent* event) {
if (event->button() == Qt::LeftButton) {
stopWindowDrag();
mdiArea()->tileSubWindows();
}
// The default implementation detects clicks on the close, maximize and minimize buttons,
// among other things
QMdiSubWindow::mouseReleaseEvent(event);
}
}
......@@ -37,24 +37,55 @@ namespace campvis {
/**
* QMdiSubWindow subclass for visualization pipeline widgets.
*
* PipelineMdiSubWindow implements special move semantics: once it's been mostly dragged out of
* its MDI area, it disappears and releases its widget, making it a top-level floating window.
* PipelineMdiSubWindow reports changes in its position via the s_positionChanged signal.
* Higher-level components listen to this signal to decide when to detach the subwindow from
* its MDI area. PipelineMdiSubWindow also implements additional methods (stopWindowDrag) that
* should be used to coordinate this detaching with respect to grabbing/releasing the mouse
* input.
*/
class PipelineMdiSubWindow : public QMdiSubWindow {
Q_OBJECT;
Q_OBJECT
public:
/**
* Construct an MDI subwindow for a visualization pipeline.
*
* \param parent the window's parent
* \param flags options customizing the frame of the subwindow
*/
PipelineMdiSubWindow(QWidget* parent = 0, Qt::WindowFlags flags = 0);
/**
* Cancel the dragging of the window.
*
* This method causes the window to release the mouse grab and stop following the cursor.
* It's supposed to be called when the window is detached from the MDI area.
*/
void stopWindowDrag();
signals:
/**
* Emitted when the subwindow is ejected from the MDI area (by dragging).
* Emitted when the subwindow's position changes.
*
* \param newPos the subwindow's new position
*/
void s_leftMdiArea();
void s_positionChanged(const QPoint& newPos);
protected:
/**
* Event handler that receives move events for the window.
* Event handler that receives mouse move events for the widget.
*/
virtual void moveEvent(QMoveEvent* event);
virtual void mouseMoveEvent(QMouseEvent* event);
/**
* Event handler that receives mouse release events for the widget.
*/
virtual void mouseReleaseEvent(QMouseEvent * event);
private:
bool _dragActive; ///< Is the window currently being dragged?
QPoint _lastMousePos; ///< Last reported mouse position
};
}
......
......@@ -38,6 +38,7 @@ namespace campvis {
VisualizationPipelineWidget::VisualizationPipelineWidget(QWidget* canvas, QWidget* parent /*= 0*/)
: QWidget(parent)
, _dragActive(false)
, _lastMousePos()
{
QLayout* layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
......@@ -47,25 +48,36 @@ namespace campvis {
}
void VisualizationPipelineWidget::forceWindowDrag() {
if (!_dragActive) {
if (!_dragActive && parent() == 0) {
_dragActive = true;
_lastMousePos = QCursor::pos();
grabMouse();
}
}
void VisualizationPipelineWidget::stopWindowDrag() {
if (_dragActive) {
_dragActive = false;
releaseMouse();
}
}
void VisualizationPipelineWidget::mouseMoveEvent(QMouseEvent* event) {
const QPoint& mousePos = event->globalPos();
const QPoint& newPos = pos() + (mousePos - _lastMousePos);
move(pos() + (mousePos - _lastMousePos));
move(newPos);
_lastMousePos = mousePos;
}
void VisualizationPipelineWidget::mouseReleaseEvent(QMouseEvent* event) {
if (event->button() == Qt::LeftButton) {
_dragActive = false;
releaseMouse();
stopWindowDrag();
}
}
}
\ No newline at end of file
void VisualizationPipelineWidget::moveEvent(QMoveEvent* /*event*/) {
emit s_positionChanged(frameGeometry().topLeft());
}
}
......@@ -37,32 +37,52 @@ namespace campvis {
/**
* Specialised widget for visualization pipelines.
*
* VisualizationPipelineWidget can be seamlessly used with MDI subwindows, as well as
* a top-level floating window.
* VisualizationPipelineWidget can be seamlessly used with MDI subwindows and as a top-level
* floating window. When detached, it reports changes in its position via the s_positionChanged
* signal. Higher-level components listen to this signal to decide when to dock the widget in
* an MDI area. VisualizationPipelineWidget also implements additional methods (forceWindowDrag,
* stopWindowDrag) that should be used to coordinate the docking with respect to
* grabbing/releasing the mouse input.
*/
class VisualizationPipelineWidget : public QWidget {
Q_OBJECT;
Q_OBJECT
public:
/**
* Construct a floating widget for a visualization pipeline.
* Construct a widget for a visualization pipeline.
*
* \param canvas the pipeline's canvas
* \param parent the widget's parent
*/
VisualizationPipelineWidget(QWidget* canvas, QWidget* parent = 0);
public slots:
/**
* Enter the widget into forced drag mode.
*
* This slot is invoked after the the widget has been detached from an MDI area and become
* a floating window. It lets the user drag the window as if it was still the same widget
* (MdiSubWindow) that has just been "pulled out" of the MDI area.
* This method is to be invoked after the the widget has been detached from an MDI area and
* become a floating window. It causes the widget to grab the mouse input and follow the
* cursor. As a result, the user can seamlessly continue dragging the widget after it has
* been "pulled out" of the MDI area.
*/
void forceWindowDrag();
/**
* Cancel the dragging of the widget.
*
* This method causes the widget to release the mouse grab and stop following the cursor.
* It's supposed to be called when the widget is re-docked in an MDI area.
*/
void stopWindowDrag();
signals:
/**
* Emitted when the widget's position changes.
*
* \param newPos the widget's new position
*/
void s_positionChanged(const QPoint& newPos);
protected:
/**
* Event handler that receives mouse move events for the widget.
......@@ -74,6 +94,11 @@ namespace campvis {
*/
virtual void mouseReleaseEvent(QMouseEvent * event);
/**
* Event handler that receives move events for the window.
*/
virtual void moveEvent(QMoveEvent* event);
private:
bool _dragActive; ///< Is the widget currently being dragged?
QPoint _lastMousePos; ///< Last reported mouse position
......
......@@ -41,14 +41,56 @@ namespace campvis {
_mdiSubWindow->setWidget(_pipelineWidget);
mdiArea->addSubWindow(_mdiSubWindow);
_mdiSubWindow->setWindowTitle(QString::fromStdString(name));
connect(_mdiSubWindow, SIGNAL(s_leftMdiArea()), _pipelineWidget, SLOT(forceWindowDrag()));
connect(_mdiSubWindow, SIGNAL(s_leftMdiArea()), this, SLOT(retileMdiSubWindows()));
const QString& windowTitle = QString::fromStdString(name);
_mdiSubWindow->setWindowTitle(windowTitle);
_pipelineWidget->setWindowTitle(windowTitle);
connect(_mdiSubWindow, SIGNAL(s_positionChanged(const QPoint&)),
this, SLOT(trackMdiSubWindowsPosition(const QPoint&)));
connect(_pipelineWidget, SIGNAL(s_positionChanged(const QPoint&)),
this, SLOT(trackFloatingWindowsPosition(const QPoint&)));
}
void VisualizationPipelineWrapper::retileMdiSubWindows() {
_mdiArea->tileSubWindows();
void VisualizationPipelineWrapper::trackFloatingWindowsPosition(const QPoint& newPos) {
const QRect& widgetGeometry = _pipelineWidget->frameGeometry();
const QRect& mdiAreaRect = _mdiArea->contentsRect();
const QRect mdiAreaGeometry(_mdiArea->mapToGlobal(mdiAreaRect.topLeft()),
_mdiArea->mapToGlobal(mdiAreaRect.bottomRight()));
const QRect& intersection = widgetGeometry & mdiAreaGeometry;
// Dock the widget if at least 60% of it is over the MDI area
if (widgetGeometry.width() * widgetGeometry.height() * 3 <
intersection.width() * intersection.height() * 5) {
_pipelineWidget->stopWindowDrag();
_mdiSubWindow->setWidget(_pipelineWidget);
_mdiArea->addSubWindow(_mdiSubWindow);
_pipelineWidget->show();
_mdiSubWindow->move(_mdiArea->mapFromGlobal(newPos));
_mdiSubWindow->grabMouse();
}
}
void VisualizationPipelineWrapper::trackMdiSubWindowsPosition(const QPoint& newPos) {
const QRect& subWindowGeometry = _mdiSubWindow->frameGeometry();
const QRect& mdiAreaGeometry = _mdiArea->contentsRect();
const QRect& intersection = subWindowGeometry & mdiAreaGeometry;
// Detach the subwindow if at least 60% of it has left the MDI area
if (subWindowGeometry.width() * subWindowGeometry.height() * 2 >
intersection.width() * intersection.height() * 5) {
_mdiSubWindow->stopWindowDrag();
_mdiSubWindow->setWidget(0);
_mdiArea->removeSubWindow(_mdiSubWindow);
_mdiArea->tileSubWindows();
_pipelineWidget->move(_mdiArea->mapToGlobal(newPos));
_pipelineWidget->show();
_pipelineWidget->forceWindowDrag();
}
}
}
\ No newline at end of file
}
......@@ -46,7 +46,7 @@ namespace campvis {
*/
class VisualizationPipelineWrapper : public QObject {
Q_OBJECT;
Q_OBJECT
public:
/**
......@@ -65,11 +65,18 @@ namespace campvis {
private slots:
/**
* Re-tile subwindows in the MDI area.
* Track the position of the pipeline's widget and dock it if necessary.
*
* This slot is invoked whenever the pipeline's MDI subwindow is detached or reattached.
* This slot is invoked when the pipeline's widget is floating and its position changes.
*/
void retileMdiSubWindows();
void trackFloatingWindowsPosition(const QPoint& newPos);
/**
* Track the position of the pipeline's MDI subwindow and detach it if necessary.
*
* This slot is invoked when the position of the pipeline's MDI subwindow changes.
*/
void trackMdiSubWindowsPosition(const QPoint& newPos);
private:
QMdiArea* _mdiArea; ///< The MDI area associated with the widget
......
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