1 /*
2  * Stellarium
3  * Copyright (C) 2008 Matthew Gates
4  * Copyright (C) 2015 Georg Zotti (min/max limits)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
19  */
20 
21 #ifndef ANGLESPINBOX_HPP
22 #define ANGLESPINBOX_HPP
23 
24 #include <QAbstractSpinBox>
25 #include <QString>
26 #include <cmath>
27 
28 //! @class AngleSpinBox
29 //! A spin box for displaying/entering angular values.
30 //! This class can accept angles in various formats commonly used in astronomy
31 //! including decimal degrees, DMS and HMS.
32 //! You should set upper and lower limits (maximum, minimum) and
33 //! decide whether the values wrap around or are blocked at the limits (wrapping).
34 class AngleSpinBox : public QAbstractSpinBox
35 {
36 	Q_OBJECT
37 
38 public:
39 	//! @enum DisplayFormat
40 	//! Used to decide how to display the angle.
41 	enum DisplayFormat
42 	{
43 		DMSLetters,		//!< Degrees, minutes and seconds, e.g. 180d 4m 8s, with negative values, [-360..360d]
44 		DMSSymbols,		//!< Degrees, minutes and seconds, e.g. 180° 4' 8", with negative values, [-360..360°]
45 		DMSLettersUnsigned,	//!< Degrees, minutes and seconds, e.g. 180d 4m 8s, [0..360d]
46 		DMSSymbolsUnsigned,     //!< Degrees, minutes and seconds, e.g. 180° 4' 8", [0..360°]
47 		HMSLetters,		//!< Hours, minutes and seconds, e.g. 12h 4m 6s
48 		HMSSymbols,		//!< Hours, minutes and seconds, e.g. 12h 4' 6s"
49 		DecimalDeg		//!< Decimal degrees, e.g. 180.06888
50 	};
51 
52 	//! @enum PrefixType
53 	//! Determines how positive and negative values are indicated.
54 	enum PrefixType
55 	{
56 		Normal,			//!< negative values have '-' prefix
57 		NormalPlus,		//!< positive values have '+' prefix, negative values have '-' prefix.
58 		Longitude,		//!< positive values have 'E' prefix, negative values have 'W' prefix.
59 		Latitude,		//!< positive values have 'N' prefix, negative values have 'S' prefix.
60  		Unknown
61 	};
62 
63 	AngleSpinBox(QWidget* parent=Q_NULLPTR, DisplayFormat format=DMSSymbols, PrefixType prefix=Normal);
64 	~AngleSpinBox() Q_DECL_OVERRIDE;
65 
66 	// QAbstractSpinBox virtual members
67 	virtual void stepBy(int steps) Q_DECL_OVERRIDE;
68 	virtual QValidator::State validate(QString& input, int& pos) const Q_DECL_OVERRIDE;
69 
70 	//! Get the angle held in the AngleSpinBox
71 	//! @return the angle in radians
72 	double valueRadians() const;
73 	//! Get the angle held in the AngleSpinBox
74 	//! @return the angle in degrees
75 	double valueDegrees() const;
76 
77 	//! Set the number of decimal places to express float values to (e.g. seconds in DMSLetters format).
78 	//! @param places the number of decimal places to use.
setDecimals(int places)79 	void setDecimals(int places) { decimalPlaces = places; }
80 
81 	//! Get the number of decimal places to express float values to (e.g. seconds in DMSLetters format).
82 	//! @return the number of decimal places used.
decimals() const83 	int decimals() const { return decimalPlaces; }
84 
85 	//! Set the display format.
86 	//! @param format the new format to use.
setDisplayFormat(DisplayFormat format)87 	void setDisplayFormat(DisplayFormat format) { angleSpinBoxFormat=format; formatText(); }
88 
89 	//! Get the current display format.
90 	//! @return the current DisplayFormat.
displayFormat() const91 	DisplayFormat displayFormat() const { return angleSpinBoxFormat; }
92 
93 	//! Set the prefix type.
94 	//! @param prefix the new prefix type to use.
setPrefixType(PrefixType prefix)95 	void setPrefixType(PrefixType prefix) { currentPrefixType=prefix; formatText(); }
96 
97 	//! Get the current display format.
98 	//! @return the current DisplayFormat.
prefixType() const99 	PrefixType prefixType() const { return currentPrefixType; }
100 
101 	//! Set the minimum value.
102 	//! @param min the new minimum value
103 	//! @param isDegrees true if the new minimum value is given in degrees, else min is understood as radians.
setMinimum(const double min,const bool isDegrees=false)104 	void setMinimum(const double min, const bool isDegrees=false) {minRad = min * (isDegrees? M_PI/180.0 : 1.); }
105 	//! Get the minimum value.
106 	//! @return the current minimum value
107 	//! @param isDegrees true if the minimum value is required in degrees, else min is returned as radians.
getMinimum(const bool isDegrees) const108 	double getMinimum(const bool isDegrees) const { return minRad * (isDegrees ? 180.0/M_PI : 1.0); }
109 
110 	//! Set the maximum value.
111 	//! @param max the new maximum value
112 	//! @param isDegrees true if the new maximum value is given in degrees, else max is understood as radians.
setMaximum(const double max,const bool isDegrees=false)113 	void setMaximum(const double max, const bool isDegrees=false) {maxRad = max * (isDegrees? M_PI/180.0 : 1.); }
114 	//! Get the maximum value.
115 	//! @return the current maximum value
116 	//! @param isDegrees true if the maximum value is required in degrees, else max is returned as radians.
getMaximum(const bool isDegrees) const117 	double getMaximum(const bool isDegrees) const { return maxRad * (isDegrees ? 180.0/M_PI : 1.0); }
118 
119 public slots:
120 	//! Set the value to default 0 angle.
121 	virtual void clear() Q_DECL_OVERRIDE;
122 
123 	//! Set the value of the spin box in radians.
124 	//! @param radians the value to set, in radians.
125 	void setRadians(double radians);
126 
127 	//! Set the value of the spin box in decimal degrees.
128 	//! @param degrees the value to set, in decimal degrees.
129 	void setDegrees(double degrees);
130 
131 signals:
132 	//! Emitted when the value changes.
133 	void valueChanged();
134 	void valueChangedDeg(double);
135 	void valueChangedRad(double);
136 
137 protected:
138 	virtual StepEnabled stepEnabled() const Q_DECL_OVERRIDE;
139 
140 private slots:
141 	//! Updates radAngle (internal representation of the angle) and calls formatText
142 	void updateValue(void);
143 
144 private:
145 	//! Convert a string value to an angle in radians.
146 	//! This function can be used to validate a string as expressing an angle. Accepted
147 	//! are any formats which the AngleSpinBox understands.
148 	//! @param input the string value to be converted / validated.
149 	//! @param state a pointer to a QValidator::State value which is set according to the validation.
150 	//! @param prefix the kind of prefix to use for conversion.
151 	//! @return the value of the angle expressed in input in radians.
152 	double stringToDouble(QString input, QValidator::State* state, PrefixType prefix=Unknown) const;
153 
154 	//! @enum AngleSpinboxSection
155 	enum AngleSpinboxSection
156 	{
157 		SectionPrefix,			//! Section of the S/W or E/W or +/-
158   		SectionDegreesHours,	//! Section of the degree or hours
159   		SectionMinutes,			//! Section of the minutes (of degree or of hours)
160   		SectionSeconds,			//! Section of the seconds (of degree or of hours)
161   		SectionNone				//! No matching section, e.g. between 2 sections
162 	};
163 
164 	//! Get the current section in which the line edit cursor is.
165 	AngleSpinboxSection getCurrentSection() const;
166 
167 	//! Reformats the input according to the current value of angleSpinBoxFormat/
168 	//! This is called whenever an editingFinished() signal is emitted,
169 	//! e.g. when RETURN is pressed.
170 	void formatText(void);
171 
172 	static const QString positivePrefix(PrefixType prefix);
173 	static const QString negativePrefix(PrefixType prefix);
174 
175 	DisplayFormat angleSpinBoxFormat;
176 	PrefixType currentPrefixType;
177 	int decimalPlaces;
178 	double radAngle;
179 	// min/max angles (radians), users should not be able to enter more/less.
180 	// Use together with the wrapping() property!
181 	double minRad;
182 	double maxRad;
183 };
184 
185 #endif // ANGLESPINBOX_HPP
186