QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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
29#include "moc_qgsscalecombobox.cpp"
30
32 : QComboBox( parent )
33{
35
36 setEditable( true );
37 setInsertPolicy( QComboBox::NoInsert );
38 setCompleter( nullptr );
39 connect( this, qOverload<int>( &QComboBox::activated ), this, &QgsScaleComboBox::fixupScale );
40 connect( lineEdit(), &QLineEdit::editingFinished, this, &QgsScaleComboBox::fixupScale );
41 fixupScale();
42}
43
44void QgsScaleComboBox::updateScales( const QStringList &scales )
45{
46 QStringList scalesList;
47 const QString oldScale = currentText();
48
49 if ( scales.isEmpty() )
50 {
52 }
53 else
54 {
55 scalesList = scales;
56 }
57
58 QStringList cleanedScalesList;
59 for ( const QString &scale : std::as_const( scalesList ) )
60 {
61 const QStringList parts = scale.split( ':' );
62 if ( parts.size() < 2 )
63 continue;
64
65 bool ok = false;
66 const double denominator = QLocale().toDouble( parts[1], &ok );
67 if ( ok )
68 {
69 cleanedScalesList.push_back( toString( denominator ) );
70 }
71 else
72 {
73 const double denominator = parts[1].toDouble( &ok );
74 if ( ok )
75 {
76 cleanedScalesList.push_back( toString( denominator ) );
77 }
78 }
79 }
80
81 blockSignals( true );
82 clear();
83 addItems( cleanedScalesList );
84 setScaleString( oldScale );
85 blockSignals( false );
86}
87
88void QgsScaleComboBox::setPredefinedScales( const QVector<double> &scales )
89{
90 if ( scales.isEmpty() )
91 {
93 return;
94 }
95
96 const QString oldScale = currentText();
97
98 QStringList scalesStringList;
99 scalesStringList.reserve( scales.size() );
100 for ( double denominator : scales )
101 {
102 scalesStringList.push_back( toString( denominator ) );
103 }
104
105 blockSignals( true );
106 clear();
107 addItems( scalesStringList );
108 setScaleString( oldScale );
109 blockSignals( false );
110}
111
113{
114 QComboBox::showPopup();
115
116 if ( !currentText().contains( ':' ) )
117 {
118 return;
119 }
120 QStringList parts = currentText().split( ':' );
121 bool ok;
122 int idx = 0;
123 int min = 999999;
124 const long currScale = parts.at( 1 ).toLong( &ok );
125 long nextScale, delta;
126 for ( int i = 0; i < count(); i++ )
127 {
128 parts = itemText( i ).split( ':' );
129 nextScale = parts.at( 1 ).toLong( &ok );
130 delta = std::labs( currScale - nextScale );
131 if ( delta < min )
132 {
133 min = delta;
134 idx = i;
135 }
136 }
137
138 blockSignals( true );
139 view()->setCurrentIndex( model()->index( idx, 0 ) );
140 blockSignals( false );
141 view()->setMinimumWidth( view()->sizeHintForColumn( 0 ) );
142}
143
145{
146 return toString( mScale, mMode );
147}
148
149bool QgsScaleComboBox::setScaleString( const QString &string )
150{
151 const double oldScale = mScale;
152 if ( mAllowNull && string.trimmed().isEmpty() )
153 {
154 mScale = std::numeric_limits<double>::quiet_NaN();
155 setEditText( toString( mScale ) );
156 clearFocus();
157 if ( !std::isnan( oldScale ) )
158 {
159 emit scaleChanged( mScale );
160 }
161 return true;
162 }
163
164 bool ok;
165 double newScale = toDouble( string, &ok );
166 if ( newScale > mMinScale && newScale != 0 && mMinScale != 0 )
167 {
168 newScale = mMinScale;
169 }
170 if ( !ok )
171 {
172 return false;
173 }
174 else
175 {
176 mScale = newScale;
177 setEditText( toString( mScale, mMode ) );
178 clearFocus();
179 if ( mScale != oldScale )
180 {
181 emit scaleChanged( mScale );
182 }
183 return true;
184 }
185}
186
188{
189 return mScale;
190}
191
193{
194 return std::isnan( mScale );
195}
196
198{
199 setScaleString( toString( scale, mMode ) );
200}
201
202void QgsScaleComboBox::fixupScale()
203{
204 if ( mAllowNull && currentText().trimmed().isEmpty() )
205 {
206 setScale( std::numeric_limits<double>::quiet_NaN() );
207 return;
208 }
209
210 const QStringList txtList = currentText().split( ':' );
211 const bool userSetScale = txtList.size() != 2;
212
213 bool ok;
214 double newScale = toDouble( currentText(), &ok );
215
216 // Valid string representation
217 if ( ok )
218 {
219 switch ( mMode )
220 {
222 {
223 // if a user types scale = 2345, we transform to 1:2345
224 if ( userSetScale && newScale < 1.0 && !qgsDoubleNear( newScale, 0.0 ) )
225 {
226 newScale = 1 / newScale;
227 }
228 break;
229 }
231 break;
232 }
233
234 setScale( newScale );
235 }
236 else
237 {
238 setScale( mScale );
239 }
240}
241
246
248{
249 if ( mode == mMode )
250 return;
251
252 mMode = mode;
253 setScale( mScale );
254 emit ratioModeChanged( mMode );
255}
256
258{
259 if ( std::isnan( scale ) )
260 {
261 return QString();
262 }
263 if ( scale == 0 )
264 {
265 return QStringLiteral( "0" );
266 }
267
268 switch ( mode )
269 {
271 if ( scale <= 1 )
272 {
273 return QStringLiteral( "%1:1" ).arg( QLocale().toString( static_cast<int>( std::round( 1.0 / scale ) ) ) );
274 }
275 else
276 {
277 return QStringLiteral( "1:%1" ).arg( QLocale().toString( static_cast<float>( std::round( scale ) ), 'f', 0 ) );
278 }
279
281 {
282 qlonglong numerator = 0;
283 qlonglong denominator = 0;
284 QgsMathUtils::doubleToRational( 1.0 / scale, numerator, denominator, 0.01 );
285 return QStringLiteral( "%1:%2" ).arg(
286 QLocale().toString( numerator ),
287 QLocale().toString( denominator )
288 );
289 }
290 }
291 return QString();
292}
293
294double QgsScaleComboBox::toDouble( const QString &scaleString, bool *returnOk )
295{
296 bool ok = false;
297 QString scaleTxt( scaleString );
298
299 const double denominator = qgsPermissiveToDouble( scaleTxt, ok );
300 double scale = !qgsDoubleNear( denominator, 0.0 ) ? 1.0 / denominator : 0.0;
301 if ( ok )
302 {
303 // Create a text version and set that text and rescan
304 // Idea is to get the same rounding.
305 scaleTxt = toString( scale );
306 }
307 else
308 {
309 // It is now either X:Y or not valid
310 QStringList txtList = scaleTxt.split( ':' );
311 if ( 2 == txtList.size() )
312 {
313 bool okX = false;
314 bool okY = false;
315 const int x = qgsPermissiveToInt( txtList[0], okX );
316 const int y = qgsPermissiveToInt( txtList[1], okY );
317 if ( okX && okY && x != 0 )
318 {
319 // Scale is fraction of x and y
320 scale = static_cast<double>( y ) / static_cast<double>( x );
321 ok = true;
322 }
323 }
324 }
325
326 // Set up optional return flag
327 if ( returnOk )
328 {
329 *returnOk = ok;
330 }
331 return scale;
332}
333
335{
336 mAllowNull = allowNull;
337 lineEdit()->setClearButtonEnabled( allowNull );
338 updateScales();
339}
340
342{
343 return mAllowNull;
344}
345
347{
348 mMinScale = scale;
349 if ( mScale > mMinScale && mScale != 0 && mMinScale != 0 )
350 {
351 setScale( mMinScale );
352 }
353}
354
356{
357 if ( allowNull() )
358 setScale( std::numeric_limits<double>::quiet_NaN() );
359}
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:83
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:90
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607