Skip to content

Commit 5da2513

Browse files
committed
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.
1 parent f85d24e commit 5da2513

File tree

6 files changed

+514
-0
lines changed

6 files changed

+514
-0
lines changed

python/gui/gui.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
%Include qgsfilewidget.sip
8080
%Include qgsfiledownloader.sip
8181
%Include qgsfilterlineedit.sip
82+
%Include qgsfloatingwidget.sip
8283
%Include qgsfocuswatcher.sip
8384
%Include qgsformannotationitem.sip
8485
%Include qgsgenericprojectionselector.sip

src/gui/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ SET(QGIS_GUI_SRCS
226226
qgsfiledropedit.cpp
227227
qgsfilewidget.cpp
228228
qgsfilterlineedit.cpp
229+
qgsfloatingwidget.cpp
229230
qgsfocuswatcher.cpp
230231
qgsformannotationitem.cpp
231232
qgsgenericprojectionselector.cpp
@@ -393,6 +394,7 @@ SET(QGIS_GUI_MOC_HDRS
393394
qgsfiledropedit.h
394395
qgsfilewidget.h
395396
qgsfilterlineedit.h
397+
qgsfloatingwidget.h
396398
qgsfocuswatcher.h
397399
qgsformannotationitem.h
398400
qgsgenericprojectionselector.h

src/gui/qgsfloatingwidget.cpp

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/***************************************************************************
2+
qgsfloatingwidget.cpp
3+
---------------------
4+
begin : April 2016
5+
copyright : (C) Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsfloatingwidget.h"
17+
#include <QEvent>
18+
19+
20+
//
21+
// QgsFloatingWidget
22+
//
23+
24+
QgsFloatingWidget::QgsFloatingWidget( QWidget *parent )
25+
: QWidget( parent )
26+
, mAnchorWidget( nullptr )
27+
, mParentEventFilter( nullptr )
28+
, mAnchorEventFilter( nullptr )
29+
, mFloatAnchorPoint( BottomMiddle )
30+
, mAnchorWidgetAnchorPoint( TopMiddle )
31+
{
32+
if ( parent )
33+
{
34+
mParentEventFilter = new QgsFloatingWidgetEventFilter( parent );
35+
parent->installEventFilter( mParentEventFilter );
36+
connect( mParentEventFilter, SIGNAL( anchorPointChanged() ), this, SLOT( anchorPointChanged() ) );
37+
}
38+
}
39+
40+
void QgsFloatingWidget::setAnchorWidget( QWidget *widget )
41+
{
42+
// remove existing event filter
43+
if ( mAnchorWidget )
44+
{
45+
mAnchorWidget->removeEventFilter( mAnchorEventFilter );
46+
delete mAnchorEventFilter;
47+
mAnchorEventFilter = nullptr;
48+
}
49+
50+
mAnchorWidget = widget;
51+
if ( mAnchorWidget )
52+
{
53+
mAnchorEventFilter = new QgsFloatingWidgetEventFilter( mAnchorWidget );
54+
mAnchorWidget->installEventFilter( mAnchorEventFilter );
55+
connect( mAnchorEventFilter, SIGNAL( anchorPointChanged() ), this, SLOT( anchorPointChanged() ) );
56+
}
57+
58+
anchorPointChanged();
59+
}
60+
61+
QWidget *QgsFloatingWidget::anchorWidget()
62+
{
63+
return mAnchorWidget;
64+
}
65+
66+
void QgsFloatingWidget::showEvent( QShowEvent *e )
67+
{
68+
anchorPointChanged();
69+
QWidget::showEvent( e );
70+
}
71+
72+
void QgsFloatingWidget::anchorPointChanged()
73+
{
74+
if ( mAnchorWidget )
75+
{
76+
QPoint anchorWidgetOrigin;
77+
78+
switch ( mAnchorWidgetAnchorPoint )
79+
{
80+
case TopLeft:
81+
anchorWidgetOrigin = QPoint( 0, 0 );
82+
break;
83+
case TopMiddle:
84+
anchorWidgetOrigin = QPoint( mAnchorWidget->width() / 2, 0 );
85+
break;
86+
case TopRight:
87+
anchorWidgetOrigin = QPoint( mAnchorWidget->width(), 0 );
88+
break;
89+
case MiddleLeft:
90+
anchorWidgetOrigin = QPoint( 0, mAnchorWidget->height() / 2 );
91+
break;
92+
case Middle:
93+
anchorWidgetOrigin = QPoint( mAnchorWidget->width() / 2, mAnchorWidget->height() / 2 );
94+
break;
95+
case MiddleRight:
96+
anchorWidgetOrigin = QPoint( mAnchorWidget->width(), mAnchorWidget->height() / 2 );
97+
break;
98+
case BottomLeft:
99+
anchorWidgetOrigin = QPoint( 0, mAnchorWidget->height() );
100+
break;
101+
case BottomMiddle:
102+
anchorWidgetOrigin = QPoint( mAnchorWidget->width() / 2, mAnchorWidget->height() );
103+
break;
104+
case BottomRight:
105+
anchorWidgetOrigin = QPoint( mAnchorWidget->width(), mAnchorWidget->height() );
106+
break;
107+
}
108+
109+
anchorWidgetOrigin = mAnchorWidget->mapTo( parentWidget(), anchorWidgetOrigin );
110+
int anchorX = anchorWidgetOrigin.x();
111+
int anchorY = anchorWidgetOrigin.y();
112+
113+
switch ( mFloatAnchorPoint )
114+
{
115+
case TopLeft:
116+
break;
117+
case TopMiddle:
118+
anchorX = anchorX - width() / 2;
119+
break;
120+
case TopRight:
121+
anchorX = anchorX - width();
122+
break;
123+
case MiddleLeft:
124+
anchorY = anchorY - height() / 2;
125+
break;
126+
case Middle:
127+
anchorY = anchorY - height() / 2;
128+
anchorX = anchorX - width() / 2;
129+
break;
130+
case MiddleRight:
131+
anchorX = anchorX - width();
132+
anchorY = anchorY - height() / 2;
133+
break;
134+
case BottomLeft:
135+
anchorY = anchorY - height();
136+
break;
137+
case BottomMiddle:
138+
anchorX = anchorX - width() / 2;
139+
anchorY = anchorY - height();
140+
break;
141+
case BottomRight:
142+
anchorX = anchorX - width();
143+
anchorY = anchorY - height();
144+
break;
145+
}
146+
147+
// constrain x so that widget floats within parent widget
148+
anchorX = qBound( 0, anchorX, parentWidget()->width() - width() );
149+
150+
move( anchorX, anchorY );
151+
}
152+
}
153+
154+
//
155+
// QgsFloatingWidgetEventFilter
156+
//
157+
158+
/// @cond PRIVATE
159+
QgsFloatingWidgetEventFilter::QgsFloatingWidgetEventFilter( QWidget *parent )
160+
: QObject( parent )
161+
{
162+
163+
}
164+
165+
bool QgsFloatingWidgetEventFilter::eventFilter( QObject *object, QEvent *event )
166+
{
167+
Q_UNUSED( object );
168+
switch ( event->type() )
169+
{
170+
case QEvent::Move:
171+
case QEvent::Resize:
172+
emit anchorPointChanged();
173+
return true;
174+
default:
175+
return false;
176+
}
177+
}
178+
179+
///@endcond

src/gui/qgsfloatingwidget.h

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/***************************************************************************
2+
qgsfloatingwidget.h
3+
-------------------
4+
begin : April 2016
5+
copyright : (C) Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
#ifndef QGSFLOATINGWIDGET_H
16+
#define QGSFLOATINGWIDGET_H
17+
18+
#include <QWidget>
19+
20+
21+
/** \ingroup gui
22+
* \class QgsFloatingWidget
23+
* A QWidget subclass for creating widgets which float outside of the normal Qt layout
24+
* system. Floating widgets use an "anchor widget" to determine how they are anchored
25+
* within their parent widget.
26+
* \note Added in version 2.16
27+
*/
28+
29+
class GUI_EXPORT QgsFloatingWidget: public QWidget
30+
{
31+
Q_OBJECT
32+
33+
public:
34+
35+
//! Reference points for anchoring widget position
36+
enum AnchorPoint
37+
{
38+
TopLeft, //!< Top-left of widget
39+
TopMiddle, //!< Top center of widget
40+
TopRight, //!< Top-right of widget
41+
MiddleLeft, //!< Middle left of widget
42+
Middle, //!< Middle of widget
43+
MiddleRight, //!< Middle right of widget
44+
BottomLeft, //!< Bottom-left of widget
45+
BottomMiddle, //!< Bottom center of widget
46+
BottomRight, //!< Bottom-right of widget
47+
};
48+
49+
QgsFloatingWidget( QWidget* parent = nullptr );
50+
51+
/** Sets the widget to "anchor" the floating widget to. The floating widget will be repositioned whenever the
52+
* anchor widget moves or is resized so that it maintains the same relative position to the anchor widget.
53+
* @param widget anchor widget. Both the floating widget and the anchor widget must share some common parent.
54+
* @see anchorWidget()
55+
*/
56+
void setAnchorWidget( QWidget* widget );
57+
58+
/** Returns the widget that the floating widget is "anchored" tto. The floating widget will be repositioned whenever the
59+
* anchor widget moves or is resized so that it maintains the same relative position to the anchor widget.
60+
* @see setAnchorWidget()
61+
*/
62+
QWidget* anchorWidget();
63+
64+
/** Returns the floating widget's anchor point, which corresponds to the point on the widget which should remain
65+
* fixed in the same relative position whenever the widget's parent is resized or moved.
66+
* @see setAnchorPoint()
67+
*/
68+
AnchorPoint anchorPoint() const { return mFloatAnchorPoint; }
69+
70+
/** Sets the floating widget's anchor point, which corresponds to the point on the widget which should remain
71+
* fixed in the same relative position whenever the widget's parent is resized or moved.
72+
* @param point anchor point
73+
* @see anchorPoint()
74+
*/
75+
void setAnchorPoint( AnchorPoint point ) { mFloatAnchorPoint = point; anchorPointChanged(); }
76+
77+
/** Returns the anchor widget's anchor point, which corresponds to the point on the anchor widget which
78+
* the floating widget should "attach" to. The floating widget should remain fixed in the same relative position
79+
* to this anchor widget whenever the widget's parent is resized or moved.
80+
* @see setAnchorWidgetPoint()
81+
*/
82+
AnchorPoint anchorWidgetPoint() const { return mAnchorWidgetAnchorPoint; }
83+
84+
/** Returns the anchor widget's anchor point, which corresponds to the point on the anchor widget which
85+
* the floating widget should "attach" to. The floating widget should remain fixed in the same relative position
86+
* to this anchor widget whenever the widget's parent is resized or moved.
87+
* @see setAnchorWidgetPoint()
88+
*/
89+
void setAnchorWidgetPoint( AnchorPoint point ) { mAnchorWidgetAnchorPoint = point; anchorPointChanged(); }
90+
91+
protected:
92+
void showEvent( QShowEvent* e ) override;
93+
94+
private slots:
95+
96+
//! Repositions the floating widget to a changed anchor point
97+
void anchorPointChanged();
98+
99+
private:
100+
101+
QWidget* mAnchorWidget;
102+
QObject* mParentEventFilter;
103+
QObject* mAnchorEventFilter;
104+
AnchorPoint mFloatAnchorPoint;
105+
AnchorPoint mAnchorWidgetAnchorPoint;
106+
107+
};
108+
109+
/// @cond PRIVATE
110+
111+
class QgsFloatingWidgetEventFilter: public QObject
112+
{
113+
Q_OBJECT
114+
115+
public:
116+
117+
QgsFloatingWidgetEventFilter( QWidget* parent = nullptr );
118+
119+
virtual bool eventFilter( QObject* object, QEvent* event ) override;
120+
121+
signals:
122+
123+
//! Emitted when the filter's parent is moved or resized
124+
void anchorPointChanged();
125+
126+
};
127+
128+
/// @endcond
129+
130+
#endif // QGSFLOATINGWIDGET_H

tests/src/python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ ADD_PYTHON_TEST(PyQgsFeatureIterator test_qgsfeatureiterator.py)
4949
ADD_PYTHON_TEST(PyQgsField test_qgsfield.py)
5050
ADD_PYTHON_TEST(PyQgsFieldModel test_qgsfieldmodel.py)
5151
ADD_PYTHON_TEST(PyQgsFilterLineEdit test_qgsfilterlineedit.py)
52+
ADD_PYTHON_TEST(PyQgsFloatingWidget test_qgsfloatingwidget.py)
5253
ADD_PYTHON_TEST(PyQgsFontUtils test_qgsfontutils.py)
5354
ADD_PYTHON_TEST(PyQgsGeometryAvoidIntersections test_qgsgeometry_avoid_intersections.py)
5455
ADD_PYTHON_TEST(PyQgsGeometryGeneratorSymbolLayer test_qgsgeometrygeneratorsymbollayer.py)

0 commit comments

Comments
 (0)