QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgsscalecombobox.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsscalecombobox.h
3 ------------------------
4 begin : January 7, 2012
5 copyright : (C) 2012 by Alexander Bruy
6 email : alexander dot bruy at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgsscalecombobox.h"
19
20#include "qgis.h"
21#include "qgsmathutils.h"
24
25#include <QAbstractItemView>
26#include <QLineEdit>
27#include <QLocale>
28#include <QString>
29
30#include "moc_qgsscalecombobox.cpp"
31
32using namespace Qt::StringLiterals;
33
35 : QComboBox( parent )
36{
38
39 setEditable( true );
40 setInsertPolicy( QComboBox::NoInsert );
41 setCompleter( nullptr );
42 connect( this, qOverload<int>( &QComboBox::activated ), this, &QgsScaleComboBox::fixupScale );
43 connect( lineEdit(), &QLineEdit::editingFinished, this, &QgsScaleComboBox::fixupScale );
44 fixupScale();
45}
46
47void QgsScaleComboBox::updateScales( const QStringList &scales )
48{
49 QStringList scalesList;
50 const QString oldScale = currentText();
51
52 if ( scales.isEmpty() )
53 {
55 }
56 else
57 {
58 scalesList = scales;
59 }
60
61 QStringList cleanedScalesList;
62 for ( const QString &scale : std::as_const( scalesList ) )
63 {
64 const QStringList parts = scale.split( ':' );
65 if ( parts.size() < 2 )
66 continue;
67
68 bool ok = false;
69 const double denominator = QLocale().toDouble( parts[1], &ok );
70 if ( ok )
71 {
72 cleanedScalesList.push_back( toString( denominator ) );
73 }
74 else
75 {
76 const double denominator = parts[1].toDouble( &ok );
77 if ( ok )
78 {
79 cleanedScalesList.push_back( toString( denominator ) );
80 }
81 }
82 }
83
84 blockSignals( true );
85 clear();
86 addItems( cleanedScalesList );
87 setScaleString( oldScale );
88 blockSignals( false );
89}
90
91void QgsScaleComboBox::setPredefinedScales( const QVector<double> &scales )
92{
93 if ( scales.isEmpty() )
94 {
96 return;
97 }
98
99 const QString oldScale = currentText();
100
101 QStringList scalesStringList;
102 scalesStringList.reserve( scales.size() );
103 for ( double denominator : scales )
104 {
105 scalesStringList.push_back( toString( denominator ) );
106 }
107
108 blockSignals( true );
109 clear();
110 addItems( scalesStringList );
111 setScaleString( oldScale );
112 blockSignals( false );
113}
114
116{
117 QComboBox::showPopup();
118
119 if ( !currentText().contains( ':' ) )
120 {
121 return;
122 }
123 QStringList parts = currentText().split( ':' );
124 bool ok;
125 int idx = 0;
126 int min = 999999;
127 const long currScale = parts.at( 1 ).toLong( &ok );
128 long nextScale, delta;
129 for ( int i = 0; i < count(); i++ )
130 {
131 parts = itemText( i ).split( ':' );
132 nextScale = parts.at( 1 ).toLong( &ok );
133 delta = std::labs( currScale - nextScale );
134 if ( delta < min )
135 {
136 min = delta;
137 idx = i;
138 }
139 }
140
141 blockSignals( true );
142 view()->setCurrentIndex( model()->index( idx, 0 ) );
143 blockSignals( false );
144 view()->setMinimumWidth( view()->sizeHintForColumn( 0 ) );
145}
146
148{
149 return toString( mScale, mMode );
150}
151
152bool QgsScaleComboBox::setScaleString( const QString &string )
153{
154 const double oldScale = mScale;
155 if ( mAllowNull && string.trimmed().isEmpty() )
156 {
157 mScale = std::numeric_limits<double>::quiet_NaN();
158 setEditText( toString( mScale ) );
159 clearFocus();
160 if ( !std::isnan( oldScale ) )
161 {
162 emit scaleChanged( mScale );
163 }
164 return true;
165 }
166
167 bool ok;
168 double newScale = toDouble( string, &ok );
169 if ( newScale > mMinScale && newScale != 0 && mMinScale != 0 )
170 {
171 newScale = mMinScale;
172 }
173 if ( !ok )
174 {
175 return false;
176 }
177 else
178 {
179 mScale = newScale;
180 setEditText( toString( mScale, mMode ) );
181 clearFocus();
182 if ( mScale != oldScale )
183 {
184 emit scaleChanged( mScale );
185 }
186 return true;
187 }
188}
189
191{
192 return mScale;
193}
194
196{
197 return std::isnan( mScale );
198}
199
201{
202 setScaleString( toString( scale, mMode ) );
203}
204
205void QgsScaleComboBox::fixupScale()
206{
207 if ( mAllowNull && currentText().trimmed().isEmpty() )
208 {
209 setScale( std::numeric_limits<double>::quiet_NaN() );
210 return;
211 }
212
213 const QStringList txtList = currentText().split( ':' );
214 const bool userSetScale = txtList.size() != 2;
215
216 bool ok;
217 double newScale = toDouble( currentText(), &ok );
218
219 // Valid string representation
220 if ( ok )
221 {
222 switch ( mMode )
223 {
225 {
226 // if a user types scale = 2345, we transform to 1:2345
227 if ( userSetScale && newScale < 1.0 && !qgsDoubleNear( newScale, 0.0 ) )
228 {
229 newScale = 1 / newScale;
230 }
231 break;
232 }
234 break;
235 }
236
237 setScale( newScale );
238 }
239 else
240 {
241 setScale( mScale );
242 }
243}
244
249
251{
252 if ( mode == mMode )
253 return;
254
255 mMode = mode;
256 setScale( mScale );
257 emit ratioModeChanged( mMode );
258}
259
261{
262 if ( std::isnan( scale ) )
263 {
264 return QString();
265 }
266 if ( scale == 0 )
267 {
268 return u"0"_s;
269 }
270
271 switch ( mode )
272 {
274 if ( scale <= 1 )
275 {
276 return u"%1:1"_s.arg( QLocale().toString( static_cast<int>( std::round( 1.0 / scale ) ) ) );
277 }
278 else
279 {
280 return u"1:%1"_s.arg( QLocale().toString( static_cast<float>( std::round( scale ) ), 'f', 0 ) );
281 }
282
284 {
285 qlonglong numerator = 0;
286 qlonglong denominator = 0;
287 QgsMathUtils::doubleToRational( 1.0 / scale, numerator, denominator, 0.01 );
288 return u"%1:%2"_s.arg(
289 QLocale().toString( numerator ),
290 QLocale().toString( denominator )
291 );
292 }
293 }
294 return QString();
295}
296
297double QgsScaleComboBox::toDouble( const QString &scaleString, bool *returnOk )
298{
299 bool ok = false;
300 QString scaleTxt( scaleString );
301
302 const double denominator = qgsPermissiveToDouble( scaleTxt, ok );
303 double scale = !qgsDoubleNear( denominator, 0.0 ) ? 1.0 / denominator : 0.0;
304 if ( ok )
305 {
306 // Create a text version and set that text and rescan
307 // Idea is to get the same rounding.
308 scaleTxt = toString( scale );
309 }
310 else
311 {
312 // It is now either X:Y or not valid
313 QStringList txtList = scaleTxt.split( ':' );
314 if ( 2 == txtList.size() )
315 {
316 bool okX = false;
317 bool okY = false;
318 const int x = qgsPermissiveToInt( txtList[0], okX );
319 const int y = qgsPermissiveToInt( txtList[1], okY );
320 if ( okX && okY && x != 0 )
321 {
322 // Scale is fraction of x and y
323 scale = static_cast<double>( y ) / static_cast<double>( x );
324 ok = true;
325 }
326 }
327 }
328
329 // Set up optional return flag
330 if ( returnOk )
331 {
332 *returnOk = ok;
333 }
334 return scale;
335}
336
338{
339 mAllowNull = allowNull;
340 lineEdit()->setClearButtonEnabled( allowNull );
341 updateScales();
342}
343
345{
346 return mAllowNull;
347}
348
350{
351 mMinScale = scale;
352 if ( mScale > mMinScale && mScale != 0 && mMinScale != 0 )
353 {
354 setScale( mMinScale );
355 }
356}
357
359{
360 if ( allowNull() )
361 setScale( std::numeric_limits<double>::quiet_NaN() );
362}
static Q_INVOKABLE void doubleToRational(double value, qlonglong &numerator, qlonglong &denominator, double tolerance=1.0e-9, int maxIterations=100)
Converts a double value to a rational fraction.
void setPredefinedScales(const QVector< double > &scales)
Sets the list of predefined scales to show in the combobox.
void updateScales(const QStringList &scales=QStringList())
Sets the list of predefined scales to show in the combobox.
QString scaleString() const
Returns the selected scale as a string, e.g.
bool setScaleString(const QString &string)
Set the selected scale from a string, e.g.
void setAllowNull(bool allowNull)
Sets whether the scale combobox can be set to a NULL value.
QgsScaleComboBox(QWidget *parent=nullptr)
Constructor for QgsScaleComboBox.
bool isNull() const
Returns true if the combo box is currently set to a "null" value.
bool allowNull() const
Returns true if the combobox can be set to a NULL value.
static double toDouble(const QString &string, bool *ok=nullptr)
Helper function to convert a scale string to double.
void setScale(double scale)
Set the selected scale from a double.
void ratioModeChanged(QgsScaleComboBox::RatioMode mode)
Emitted when the ratio mode for the widget is changed.
void showPopup() override
void setNull()
Sets the combo box to the null value.
RatioMode
Scale ratio modes.
@ Flexible
Allows numerator values other than 1, e.g: "2:3".
@ ForceUnitNumerator
Default mode, forces the scale numerator to be 1, e.g. "1:1000".
void setRatioMode(QgsScaleComboBox::RatioMode mode)
Sets the ratio mode for the scale.
void setMinScale(double scale)
Set the minimum allowed scale.
static QString toString(double scale, QgsScaleComboBox::RatioMode mode=QgsScaleComboBox::RatioMode::ForceUnitNumerator)
Helper function to convert a scale double to scale string.
void scaleChanged(double scale)
Emitted when user has finished editing/selecting a new scale.
static const QgsSettingsEntryStringList * settingsMapScales
double qgsPermissiveToDouble(QString string, bool &ok)
Converts a string to a double in a permissive way, e.g., allowing for incorrect numbers of digits bet...
Definition qgis.cpp:91
int qgsPermissiveToInt(QString string, bool &ok)
Converts a string to an integer in a permissive way, e.g., allowing for incorrect numbers of digits b...
Definition qgis.cpp:98
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6900