11#!/usr/bin/python
2+ import re
3+
24from Qt import QtWidgets , QtCore , QtGui
35
6+ _NUMB_REGEX = re .compile (r'^((?:\-)*\d+)*([\.,])*(\d+(?:[eE](?:[\-\+])*\d+)*)*' )
7+
48
59class _NumberValueMenu (QtWidgets .QMenu ):
610
@@ -83,16 +87,17 @@ def __init__(self, parent=None, data_type=float):
8387 self ._previous_x = None
8488 self ._previous_value = None
8589 self ._step = 1
86- self ._speed = 0.1
90+ self ._speed = 0.05
8791 self ._data_type = float
92+ self ._min = None
93+ self ._max = None
8894
8995 self ._menu = _NumberValueMenu ()
9096 self ._menu .mouseMove .connect (self .mouseMoveEvent )
9197 self ._menu .mouseRelease .connect (self .mouseReleaseEvent )
9298 self ._menu .stepChange .connect (self ._reset_previous_x )
93- self ._menu .set_steps ([0.001 , 0.01 , 0.1 , 1 , 10 , 100 , 1000 ])
9499
95- self .editingFinished .connect (self ._on_text_changed )
100+ self .editingFinished .connect (self ._on_editing_finished )
96101
97102 self .set_data_type (data_type )
98103
@@ -113,7 +118,7 @@ def mouseMoveEvent(self, event):
113118 value = self ._previous_value
114119 value = value + int (delta * self ._speed ) * self ._step
115120 self .set_value (value )
116- self ._on_text_changed ()
121+ self ._on_mmb_mouse_move ()
117122 super (_NumberValueEdit , self ).mouseMoveEvent (event )
118123
119124 def mousePressEvent (self , event ):
@@ -140,15 +145,37 @@ def keyPressEvent(self, event):
140145 def _reset_previous_x (self ):
141146 self ._previous_x = None
142147
143- def _on_text_changed (self ):
148+ def _on_mmb_mouse_move (self ):
149+ self .value_changed .emit (self .get_value ())
150+
151+ def _on_editing_finished (self ):
152+ if self ._data_type is float :
153+ match = _NUMB_REGEX .match (self .text ())
154+ if match :
155+ val1 , point , val2 = match .groups ()
156+ if point :
157+ val1 = val1 or '0'
158+ val2 = val2 or '0'
159+ self .setText (val1 + point + val2 )
144160 self .value_changed .emit (self .get_value ())
145161
146162 def _convert_text (self , text ):
147- # int("1.0") will return error
148- # so we use int(float("1.0"))
149- try :
150- value = float (text )
151- except :
163+ """
164+ Convert text to int or float.
165+
166+ Args:
167+ text (str): input text.
168+
169+ Returns:
170+ int or float: converted value.
171+ """
172+ match = _NUMB_REGEX .match (text )
173+ if match :
174+ val1 , _ , val2 = match .groups ()
175+ val1 = val1 or '0'
176+ val2 = val2 or '0'
177+ value = float (val1 + '.' + val2 )
178+ else :
152179 value = 0.0
153180 if self ._data_type is int :
154181 value = int (value )
@@ -157,26 +184,90 @@ def _convert_text(self, text):
157184 # public
158185
159186 def set_data_type (self , data_type ):
187+ """
188+ Sets the line edit to either display value in float or int.
189+
190+ Args:
191+ data_type(int or float): int or float data type object.
192+ """
160193 self ._data_type = data_type
161- self ._menu .set_data_type (data_type )
162194 if data_type is int :
163- self .setValidator (QtGui .QIntValidator ())
195+ regexp = QtCore .QRegExp (r'\d+' )
196+ validator = QtGui .QRegExpValidator (regexp , self )
197+ steps = [1 , 10 , 100 , 1000 ]
164198 elif data_type is float :
165- self .setValidator (QtGui .QDoubleValidator ())
199+ regexp = QtCore .QRegExp (r'\d+[\.,]\d+(?:[eE](?:[\-\+]|)\d+)*' )
200+ validator = QtGui .QRegExpValidator (regexp , self )
201+ steps = [0.001 , 0.01 , 0.1 , 1 ]
202+ self .setValidator (validator )
203+ if not self ._menu .steps :
204+ self ._menu .set_steps (steps )
205+ self ._menu .set_data_type (data_type )
166206
167207 def set_steps (self , steps = None ):
168- steps = steps or [0.001 , 0.01 , 0.1 , 1 , 10 , 100 , 1000 ]
208+ """
209+ Sets the step items in the MMB context menu.
210+
211+ Args:
212+ steps (list[int] or list[float]): list of ints or floats.
213+ """
214+ step_types = {
215+ int : [1 , 10 , 100 , 1000 ],
216+ float : [0.001 , 0.01 , 0.1 , 1 ]
217+ }
218+ steps = steps or step_types .get (self ._data_type )
169219 self ._menu .set_steps (steps )
170220
221+ def set_min (self , value = None ):
222+ """
223+ Set the minimum range for the input field.
224+
225+ Args:
226+ value (int or float): minimum range value.
227+ """
228+ if self ._data_type is int :
229+ self ._min = int (value )
230+ elif self ._data_type is float :
231+ self ._min = float (value )
232+ else :
233+ self ._min = value
234+
235+ def set_max (self , value = None ):
236+ """
237+ Set the maximum range for the input field.
238+
239+ Args:
240+ value (int or float): maximum range value.
241+ """
242+ if self ._data_type is int :
243+ self ._max = int (value )
244+ elif self ._data_type is float :
245+ self ._max = float (value )
246+ else :
247+ self ._max = value
248+
171249 def get_value (self ):
172- if self .text ().startswith ('.' ):
173- text = '0' + self .text ()
174- self .setText (text )
175- return self ._convert_text (self .text ())
250+ value = self ._convert_text (self .text ())
251+ return value
176252
177253 def set_value (self , value ):
178- if value != self .get_value ():
179- self .setText (str (self ._convert_text (value )))
254+ text = str (value )
255+ converted = self ._convert_text (text )
256+ current = self .get_value ()
257+ if converted == current :
258+ return
259+ point = None
260+ if isinstance (converted , float ):
261+ point = _NUMB_REGEX .match (str (value )).groups (2 )
262+ if self ._min is not None and converted < self ._min :
263+ text = str (self ._min )
264+ if point and point not in text :
265+ text = str (self ._min ).replace ('.' , point )
266+ if self ._max is not None and converted > self ._max :
267+ text = str (self ._max )
268+ if point and point not in text :
269+ text = text .replace ('.' , point )
270+ self .setText (text )
180271
181272
182273class IntValueEdit (_NumberValueEdit ):
0 commit comments