Skip to content
Permalink
Browse files
New QgsFloatingWidget widget for easy creation of widgets which "float"
above a layout.

Supports setting another widget as a anchor point for the widget, eg
the floating widget could be set so that it's always placed to the
top-right of the anchor widget.
  • Loading branch information
nyalldawson committed Dec 5, 2016
1 parent f85d24e commit 5da25136a61c3f05292cc5391fab062b29f95b2e
Showing with 514 additions and 0 deletions.
  1. +1 −0 python/gui/gui.sip
  2. +2 −0 src/gui/CMakeLists.txt
  3. +179 −0 src/gui/qgsfloatingwidget.cpp
  4. +130 −0 src/gui/qgsfloatingwidget.h
  5. +1 −0 tests/src/python/CMakeLists.txt
  6. +201 −0 tests/src/python/test_qgsfloatingwidget.py
@@ -79,6 +79,7 @@
%Include qgsfilewidget.sip
%Include qgsfiledownloader.sip
%Include qgsfilterlineedit.sip
%Include qgsfloatingwidget.sip
%Include qgsfocuswatcher.sip
%Include qgsformannotationitem.sip
%Include qgsgenericprojectionselector.sip
@@ -226,6 +226,7 @@ SET(QGIS_GUI_SRCS
qgsfiledropedit.cpp
qgsfilewidget.cpp
qgsfilterlineedit.cpp
qgsfloatingwidget.cpp
qgsfocuswatcher.cpp
qgsformannotationitem.cpp
qgsgenericprojectionselector.cpp
@@ -393,6 +394,7 @@ SET(QGIS_GUI_MOC_HDRS
qgsfiledropedit.h
qgsfilewidget.h
qgsfilterlineedit.h
qgsfloatingwidget.h
qgsfocuswatcher.h
qgsformannotationitem.h
qgsgenericprojectionselector.h
@@ -0,0 +1,179 @@
/***************************************************************************
qgsfloatingwidget.cpp
---------------------
begin : April 2016
copyright : (C) Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* 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. *
* *
***************************************************************************/

#include "qgsfloatingwidget.h"
#include <QEvent>


//
// QgsFloatingWidget
//

QgsFloatingWidget::QgsFloatingWidget( QWidget *parent )
: QWidget( parent )
, mAnchorWidget( nullptr )
, mParentEventFilter( nullptr )
, mAnchorEventFilter( nullptr )
, mFloatAnchorPoint( BottomMiddle )
, mAnchorWidgetAnchorPoint( TopMiddle )
{
if ( parent )
{
mParentEventFilter = new QgsFloatingWidgetEventFilter( parent );
parent->installEventFilter( mParentEventFilter );
connect( mParentEventFilter, SIGNAL( anchorPointChanged() ), this, SLOT( anchorPointChanged() ) );
}
}

void QgsFloatingWidget::setAnchorWidget( QWidget *widget )
{
// remove existing event filter
if ( mAnchorWidget )
{
mAnchorWidget->removeEventFilter( mAnchorEventFilter );
delete mAnchorEventFilter;
mAnchorEventFilter = nullptr;
}

mAnchorWidget = widget;
if ( mAnchorWidget )
{
mAnchorEventFilter = new QgsFloatingWidgetEventFilter( mAnchorWidget );
mAnchorWidget->installEventFilter( mAnchorEventFilter );
connect( mAnchorEventFilter, SIGNAL( anchorPointChanged() ), this, SLOT( anchorPointChanged() ) );
}

anchorPointChanged();
}

QWidget *QgsFloatingWidget::anchorWidget()
{
return mAnchorWidget;
}

void QgsFloatingWidget::showEvent( QShowEvent *e )
{
anchorPointChanged();
QWidget::showEvent( e );
}

void QgsFloatingWidget::anchorPointChanged()
{
if ( mAnchorWidget )
{
QPoint anchorWidgetOrigin;

switch ( mAnchorWidgetAnchorPoint )
{
case TopLeft:
anchorWidgetOrigin = QPoint( 0, 0 );
break;
case TopMiddle:
anchorWidgetOrigin = QPoint( mAnchorWidget->width() / 2, 0 );
break;
case TopRight:
anchorWidgetOrigin = QPoint( mAnchorWidget->width(), 0 );
break;
case MiddleLeft:
anchorWidgetOrigin = QPoint( 0, mAnchorWidget->height() / 2 );
break;
case Middle:
anchorWidgetOrigin = QPoint( mAnchorWidget->width() / 2, mAnchorWidget->height() / 2 );
break;
case MiddleRight:
anchorWidgetOrigin = QPoint( mAnchorWidget->width(), mAnchorWidget->height() / 2 );
break;
case BottomLeft:
anchorWidgetOrigin = QPoint( 0, mAnchorWidget->height() );
break;
case BottomMiddle:
anchorWidgetOrigin = QPoint( mAnchorWidget->width() / 2, mAnchorWidget->height() );
break;
case BottomRight:
anchorWidgetOrigin = QPoint( mAnchorWidget->width(), mAnchorWidget->height() );
break;
}

anchorWidgetOrigin = mAnchorWidget->mapTo( parentWidget(), anchorWidgetOrigin );
int anchorX = anchorWidgetOrigin.x();
int anchorY = anchorWidgetOrigin.y();

switch ( mFloatAnchorPoint )
{
case TopLeft:
break;
case TopMiddle:
anchorX = anchorX - width() / 2;
break;
case TopRight:
anchorX = anchorX - width();
break;
case MiddleLeft:
anchorY = anchorY - height() / 2;
break;
case Middle:
anchorY = anchorY - height() / 2;
anchorX = anchorX - width() / 2;
break;
case MiddleRight:
anchorX = anchorX - width();
anchorY = anchorY - height() / 2;
break;
case BottomLeft:
anchorY = anchorY - height();
break;
case BottomMiddle:
anchorX = anchorX - width() / 2;
anchorY = anchorY - height();
break;
case BottomRight:
anchorX = anchorX - width();
anchorY = anchorY - height();
break;
}

// constrain x so that widget floats within parent widget
anchorX = qBound( 0, anchorX, parentWidget()->width() - width() );

move( anchorX, anchorY );
}
}

//
// QgsFloatingWidgetEventFilter
//

/// @cond PRIVATE
QgsFloatingWidgetEventFilter::QgsFloatingWidgetEventFilter( QWidget *parent )
: QObject( parent )
{

}

bool QgsFloatingWidgetEventFilter::eventFilter( QObject *object, QEvent *event )
{
Q_UNUSED( object );
switch ( event->type() )
{
case QEvent::Move:
case QEvent::Resize:
emit anchorPointChanged();
return true;
default:
return false;
}
}

///@endcond
@@ -0,0 +1,130 @@
/***************************************************************************
qgsfloatingwidget.h
-------------------
begin : April 2016
copyright : (C) Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef QGSFLOATINGWIDGET_H
#define QGSFLOATINGWIDGET_H

#include <QWidget>


/** \ingroup gui
* \class QgsFloatingWidget
* A QWidget subclass for creating widgets which float outside of the normal Qt layout
* system. Floating widgets use an "anchor widget" to determine how they are anchored
* within their parent widget.
* \note Added in version 2.16
*/

class GUI_EXPORT QgsFloatingWidget: public QWidget
{
Q_OBJECT

public:

//! Reference points for anchoring widget position
enum AnchorPoint
{
TopLeft, //!< Top-left of widget
TopMiddle, //!< Top center of widget
TopRight, //!< Top-right of widget
MiddleLeft, //!< Middle left of widget
Middle, //!< Middle of widget
MiddleRight, //!< Middle right of widget
BottomLeft, //!< Bottom-left of widget
BottomMiddle, //!< Bottom center of widget
BottomRight, //!< Bottom-right of widget
};

QgsFloatingWidget( QWidget* parent = nullptr );

/** Sets the widget to "anchor" the floating widget to. The floating widget will be repositioned whenever the
* anchor widget moves or is resized so that it maintains the same relative position to the anchor widget.
* @param widget anchor widget. Both the floating widget and the anchor widget must share some common parent.
* @see anchorWidget()
*/
void setAnchorWidget( QWidget* widget );

/** Returns the widget that the floating widget is "anchored" tto. The floating widget will be repositioned whenever the
* anchor widget moves or is resized so that it maintains the same relative position to the anchor widget.
* @see setAnchorWidget()
*/
QWidget* anchorWidget();

/** Returns the floating widget's anchor point, which corresponds to the point on the widget which should remain
* fixed in the same relative position whenever the widget's parent is resized or moved.
* @see setAnchorPoint()
*/
AnchorPoint anchorPoint() const { return mFloatAnchorPoint; }

/** Sets the floating widget's anchor point, which corresponds to the point on the widget which should remain
* fixed in the same relative position whenever the widget's parent is resized or moved.
* @param point anchor point
* @see anchorPoint()
*/
void setAnchorPoint( AnchorPoint point ) { mFloatAnchorPoint = point; anchorPointChanged(); }

/** Returns the anchor widget's anchor point, which corresponds to the point on the anchor widget which
* the floating widget should "attach" to. The floating widget should remain fixed in the same relative position
* to this anchor widget whenever the widget's parent is resized or moved.
* @see setAnchorWidgetPoint()
*/
AnchorPoint anchorWidgetPoint() const { return mAnchorWidgetAnchorPoint; }

/** Returns the anchor widget's anchor point, which corresponds to the point on the anchor widget which
* the floating widget should "attach" to. The floating widget should remain fixed in the same relative position
* to this anchor widget whenever the widget's parent is resized or moved.
* @see setAnchorWidgetPoint()
*/
void setAnchorWidgetPoint( AnchorPoint point ) { mAnchorWidgetAnchorPoint = point; anchorPointChanged(); }

protected:
void showEvent( QShowEvent* e ) override;

private slots:

//! Repositions the floating widget to a changed anchor point
void anchorPointChanged();

private:

QWidget* mAnchorWidget;
QObject* mParentEventFilter;
QObject* mAnchorEventFilter;
AnchorPoint mFloatAnchorPoint;
AnchorPoint mAnchorWidgetAnchorPoint;

};

/// @cond PRIVATE

class QgsFloatingWidgetEventFilter: public QObject
{
Q_OBJECT

public:

QgsFloatingWidgetEventFilter( QWidget* parent = nullptr );

virtual bool eventFilter( QObject* object, QEvent* event ) override;

signals:

//! Emitted when the filter's parent is moved or resized
void anchorPointChanged();

};

/// @endcond

#endif // QGSFLOATINGWIDGET_H
@@ -49,6 +49,7 @@ ADD_PYTHON_TEST(PyQgsFeatureIterator test_qgsfeatureiterator.py)
ADD_PYTHON_TEST(PyQgsField test_qgsfield.py)
ADD_PYTHON_TEST(PyQgsFieldModel test_qgsfieldmodel.py)
ADD_PYTHON_TEST(PyQgsFilterLineEdit test_qgsfilterlineedit.py)
ADD_PYTHON_TEST(PyQgsFloatingWidget test_qgsfloatingwidget.py)
ADD_PYTHON_TEST(PyQgsFontUtils test_qgsfontutils.py)
ADD_PYTHON_TEST(PyQgsGeometryAvoidIntersections test_qgsgeometry_avoid_intersections.py)
ADD_PYTHON_TEST(PyQgsGeometryGeneratorSymbolLayer test_qgsgeometrygeneratorsymbollayer.py)
Loading

0 comments on commit 5da2513

Please sign in to comment.