1 /***************************************************************************
2     qgsextentwidget.h
3     ---------------------
4     begin                : March 2020
5     copyright            : (C) 2020 by 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 #ifndef QGSEXTENTWIDGET_H
17 #define QGSEXTENTWIDGET_H
18 
19 #include "qgscollapsiblegroupbox.h"
20 #include "qgsmaptool.h"
21 #include "qgsmaptoolextent.h"
22 #include "qgis_sip.h"
23 
24 #include "ui_qgsextentgroupboxwidget.h"
25 
26 #include "qgscoordinatereferencesystem.h"
27 #include "qgsrectangle.h"
28 #include "qgis_gui.h"
29 
30 #include <memory>
31 #include <QRegularExpression>
32 
33 class QgsCoordinateReferenceSystem;
34 class QgsMapLayerModel;
35 class QgsMapLayer;
36 
37 /**
38  * \ingroup gui
39  * \brief A widget for configuration of a map extent.
40  *
41  * Besides allowing the user to enter the extent manually, it comes with options to use
42  * original extent or extent defined by the current view in map canvas.
43  *
44  * When using the widget, make sure to call setOriginalExtent(), setCurrentExtent() and setOutputCrs() during initialization.
45  *
46  * \see QgsExtentGroupBox
47  *
48  * \since QGIS 3.14
49  */
50 class GUI_EXPORT QgsExtentWidget : public QWidget, private Ui::QgsExtentGroupBoxWidget
51 {
52     Q_OBJECT
53 
54   public:
55 
56     //! Available states for the current extent selection in the widget
57     enum ExtentState
58     {
59       OriginalExtent,  //!< Layer's extent
60       CurrentExtent,   //!< Map canvas extent
61       UserExtent,      //!< Extent manually entered/modified by the user
62       ProjectLayerExtent, //!< Extent taken from a layer within the project
63       DrawOnCanvas, //!< Extent taken from a rectangled drawn onto the map canvas
64     };
65 
66     //! Widget styles
67     enum WidgetStyle
68     {
69       CondensedStyle, //!< Shows a compressed widget, for use when available space is minimal
70       ExpandedStyle, //!< Shows an expanded widget, for use when space is not constrained
71     };
72 
73     /**
74      * Constructor for QgsExtentWidget.
75      */
76     explicit QgsExtentWidget( QWidget *parent SIP_TRANSFERTHIS = nullptr, WidgetStyle style = CondensedStyle );
77 
78     ~QgsExtentWidget() override;
79 
80     /**
81      * Sets the original extent and coordinate reference system for the widget. This should be called as part of initialization.
82      * \see originalExtent()
83      * \see originalCrs()
84      */
85     void setOriginalExtent( const QgsRectangle &originalExtent, const QgsCoordinateReferenceSystem &originalCrs );
86 
87     /**
88      * Returns the original extent set for the widget.
89      * \see setOriginalExtent()
90      * \see originalCrs()
91      */
originalExtent()92     QgsRectangle originalExtent() const { return mOriginalExtent; }
93 
94     /**
95      * Returns the original coordinate reference system set for the widget.
96      * \see originalExtent()
97      * \see setOriginalExtent()
98      */
originalCrs()99     QgsCoordinateReferenceSystem originalCrs() const { return mOriginalCrs; }
100 
101     /**
102      * Sets the current extent to show in the widget - should be called as part of initialization (or whenever current extent changes).
103      * The current extent is usually set to match the current map canvas extent.
104      * \see currentExtent()
105      * \see currentCrs()
106      */
107     void setCurrentExtent( const QgsRectangle &currentExtent, const QgsCoordinateReferenceSystem &currentCrs );
108 
109     /**
110      * Returns the current extent set for the widget. The current extent is usually set to match the
111      * current map canvas extent.
112      * \see setCurrentExtent()
113      * \see currentCrs()
114      */
currentExtent()115     QgsRectangle currentExtent() const { return mCurrentExtent; }
116 
117     /**
118      * Returns the coordinate reference system for the current extent set for the widget. The current
119      * extent and CRS usually reflects the map canvas extent and CRS.
120      * \see setCurrentExtent()
121      * \see currentExtent()
122      */
currentCrs()123     QgsCoordinateReferenceSystem currentCrs() const { return mCurrentCrs; }
124 
125     /**
126      * Sets the output CRS - may need to be used for transformation from original/current extent.
127      * Should be called as part of initialization and whenever the the output CRS is changed.
128      * The current extent will be reprojected into the new output CRS.
129      */
130     void setOutputCrs( const QgsCoordinateReferenceSystem &outputCrs );
131 
132     /**
133      * Returns the extent shown in the widget - in output CRS coordinates.
134      * \see outputCrs
135      */
136     QgsRectangle outputExtent() const;
137 
138     /**
139      * Returns the current output CRS, used in the display.
140      * \see outputExtent
141      */
outputCrs()142     QgsCoordinateReferenceSystem outputCrs() const { return mOutputCrs; }
143 
144     /**
145      * Returns the currently selected state for the widget's extent.
146      */
extentState()147     QgsExtentWidget::ExtentState extentState() const { return mExtentState; }
148 
149     /**
150      * Sets the map canvas to enable dragging of extent on a canvas.
151      * \param canvas the map canvas
152      */
153     void setMapCanvas( QgsMapCanvas *canvas );
154 
155     /**
156      * Returns the current fixed aspect ratio to be used when dragging extent onto the canvas.
157      * If the aspect ratio isn't fixed, the width and height will be set to zero.
158      */
ratio()159     QSize ratio() const { return mRatio; }
160 
161     /**
162      * Returns the name of the extent layer.
163      */
164     QString extentLayerName() const;
165 
166     /**
167      * Returns TRUE if the widget is in a valid state, i.e. has an extent set.
168      */
169     bool isValid() const;
170 
171     /**
172      * Sets whether the widget can be set to a "not set" (null) state.
173      *
174      * The specified \a notSetText will be used for showing null values.
175      *
176      * \note This mode only applies to widgets in the condensed state!
177      */
178     void setNullValueAllowed( bool allowed, const QString &notSetText = QString() );
179 
180   public slots:
181 
182     /**
183      * Sets the output extent to be the same as original extent (may be transformed to output CRS).
184      */
185     void setOutputExtentFromOriginal();
186 
187     /**
188      * Sets the output extent to be the same as current extent (may be transformed to output CRS).
189      */
190     void setOutputExtentFromCurrent();
191 
192     /**
193      * Sets the output extent to a custom extent (may be transformed to output CRS).
194      */
195     void setOutputExtentFromUser( const QgsRectangle &extent, const QgsCoordinateReferenceSystem &crs );
196 
197     /**
198      * Sets the output extent to match a \a layer's extent (may be transformed to output CRS).
199      */
200     void setOutputExtentFromLayer( const QgsMapLayer *layer );
201 
202     /**
203      * Sets the output extent by dragging on the canvas.
204      */
205     void setOutputExtentFromDrawOnCanvas();
206 
207     /**
208      * Sets a fixed aspect ratio to be used when dragging extent onto the canvas.
209      * To unset a fixed aspect ratio, set the width and height to zero.
210      * \param ratio aspect ratio's width and height
211      */
setRatio(QSize ratio)212     void setRatio( QSize ratio ) { mRatio = ratio; }
213 
214     /**
215      * Clears the widget, setting it to a null value.
216      */
217     void clear();
218 
219   signals:
220 
221     /**
222      * Emitted when the widget's extent is changed.
223      */
224     void extentChanged( const QgsRectangle &r );
225 
226     /**
227      * Emitted when the widget's validation state changes.
228      */
229     void validationChanged( bool valid );
230 
231     /**
232      * Emitted when the parent dialog visibility must be changed (e.g.
233      * to permit access to the map canvas)
234      */
235     void toggleDialogVisibility( bool visible );
236 
237   protected:
238 
239     void dragEnterEvent( QDragEnterEvent *event ) override;
240     void dragLeaveEvent( QDragLeaveEvent *event ) override;
241     void dropEvent( QDropEvent *event ) override;
242 
243   private slots:
244 
245     void layerMenuAboutToShow();
246 
247     void extentDrawn( const QgsRectangle &extent );
248     void mapToolDeactivated();
249 
250   private:
251     void setOutputExtent( const QgsRectangle &r, const QgsCoordinateReferenceSystem &srcCrs, QgsExtentWidget::ExtentState state );
252     void setOutputExtentFromLineEdit();
253     void setOutputExtentFromCondensedLineEdit();
254 
255     ExtentState mExtentState = OriginalExtent;
256 
257     QgsCoordinateReferenceSystem mOutputCrs;
258 
259     QgsRectangle mCurrentExtent;
260     QgsCoordinateReferenceSystem mCurrentCrs;
261 
262     QgsRectangle mOriginalExtent;
263     QgsCoordinateReferenceSystem mOriginalCrs;
264 
265     QMenu *mMenu = nullptr;
266     QMenu *mLayerMenu = nullptr;
267     QgsMapLayerModel *mMapLayerModel = nullptr;
268     QList< QAction * > mLayerMenuActions;
269     QAction *mUseCanvasExtentAction = nullptr;
270     QAction *mUseCurrentExtentAction = nullptr;
271     QAction *mDrawOnCanvasAction = nullptr;
272 
273     QPointer< const QgsMapLayer > mExtentLayer;
274     QString mExtentLayerName;
275 
276     std::unique_ptr< QgsMapToolExtent > mMapToolExtent;
277     QPointer< QgsMapTool > mMapToolPrevious = nullptr;
278     QgsMapCanvas *mCanvas = nullptr;
279     QSize mRatio;
280 
281     bool mIsValid = false;
282     bool mHasFixedOutputCrs = false;
283 
284     QRegularExpression mCondensedRe;
285     void setValid( bool valid );
286 
287     void setExtentToLayerExtent( const QString &layerId );
288 
289     QgsMapLayer *mapLayerFromMimeData( const QMimeData *data ) const;
290 
291 
292 };
293 
294 #endif // QGSEXTENTWIDGET_H
295