# These are Quisk widgets from __future__ import print_function from __future__ import absolute_import from __future__ import division import sys, re import wx, wx.lib.buttons, wx.lib.stattext # The main script will alter quisk_conf_defaults to include the user's config file. import quisk_conf_defaults as conf import _quisk as QS wxVersion = wx.version()[0] def EmptyBitmap(width, height): if wxVersion in ('2', '3'): return wx.EmptyBitmap(width, height) else: return wx.Bitmap(width, height) def MakeWidgetGlobals(): global button_font, button_uline_font, button_bezel, button_width, button_height, button_text_width, button_text_height global _bitmap_menupop, _bitmap_sliderpop, _bitmap_cyclepop button_bezel = 3 # size of button bezel in pixels button_font = wx.Font(conf.button_font_size, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_NORMAL, False, conf.quisk_typeface) button_uline_font = wx.Font(conf.button_font_size, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_NORMAL, True, conf.quisk_typeface) dc = wx.MemoryDC() dc.SetFont(button_font) tmp_bm = EmptyBitmap(1, 1) # Thanks to NS4Y dc.SelectObject(tmp_bm) button_text_width, button_text_height = dc.GetTextExtent('0') button_width = button_text_width + 2 + 2 * button_bezel # + 4 * int(self.useFocusInd) button_height = button_text_height + 2 + 2 * button_bezel # + 4 * int(self.useFocusInd) # Make a bitmap for the slider pop button height = button_text_height + 2 # button height less bezel width = height _bitmap_sliderpop = EmptyBitmap(height, height) dc.SelectObject(_bitmap_sliderpop) pen = wx.Pen(conf.color_enable, 1) dc.SetPen(pen) brush = wx.Brush(conf.color_btn) dc.SetBackground(brush) dc.Clear() w = width * 5 // 10 w += w % 2 bd = (width - 1 - w) // 2 x1 = bd x2 = x1 + w y1 = 0 y2 = height - y1 - 1 dc.DrawLine(x1, y1, x2, y1) dc.DrawLine(x2, y1, x2, y2) dc.DrawLine(x2, y2, x1, y2) dc.DrawLine(x1, y2, x1, y1) x0 = (x2 + x1) // 2 dc.DrawLine(x0, y1 + 3, x0, y2 - 2) y0 = height * 6 // 10 dc.DrawLine(x0 - 2, y0, x0 + 3, y0) y0 -= 1 color = pen.GetColour() r = color.Red() g = color.Green() b = color.Blue() f = 160 r = min(r + f, 255) g = min(g + f, 255) b = min(b + f, 255) color = wx.Colour(r, g, b) dc.SetPen(wx.Pen(color, 1, wx.SOLID)) dc.DrawLine(x0 - 2, y0, x0 + 3, y0) dc.SelectObject(wx.NullBitmap) # Make a bitmap for the menu pop button _bitmap_menupop = EmptyBitmap(height, height) dc.SelectObject(_bitmap_menupop) dc.SetBackground(brush) dc.Clear() dc.SetPen(wx.Pen(conf.color_enable, 1)) dc.SetBrush(wx.Brush(conf.color_enable)) x = 3 for y in range(2, height - 3, 5): dc.DrawRectangle(x, y, 3, 3) dc.DrawLine(x + 5, y + 1, width - 3, y + 1) dc.SelectObject(wx.NullBitmap) # Make a bitmap for the cycle button _bitmap_cyclepop = EmptyBitmap(height, height) dc.SelectObject(_bitmap_cyclepop) dc.SetBackground(brush) dc.SetFont(button_font) dc.Clear() w, h = dc.GetTextExtent(conf.btn_text_cycle) dc.DrawText(conf.btn_text_cycle, (height - x) // 2, (height - y) // 2) dc.SelectObject(wx.NullBitmap) def FreqFormatter(freq): # Format the string or integer frequency by adding blanks freq = int(freq) if freq >= 0: t = str(freq) minus = '' else: t = str(-freq) minus = '- ' l = len(t) if l > 9: txt = "%s%s %s %s %s" % (minus, t[0:-9], t[-9:-6], t[-6:-3], t[-3:]) elif l > 6: txt = "%s%s %s %s" % (minus, t[0:-6], t[-6:-3], t[-3:]) elif l > 3: txt = "%s%s %s" % (minus, t[0:-3], t[-3:]) else: txt = minus + t return txt class FrequencyDisplay(wx.lib.stattext.GenStaticText): """Create a frequency display widget.""" def __init__(self, frame, width, height): wx.lib.stattext.GenStaticText.__init__(self, frame, -1, '3', style=wx.ALIGN_CENTER|wx.ST_NO_AUTORESIZE) border = 4 for points in range(30, 6, -1): font = wx.Font(points, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_NORMAL, False, conf.quisk_typeface) self.SetFont(font) w, h = self.GetTextExtent('333 444 555 Hz') if w < width and h < height - border * 2: break self.SetSizeHints(w, h, w * 5, h) self.height = h self.points = points border = self.border = (height - self.height) // 2 self.height_and_border = h + border * 2 self.SetBackgroundColour(conf.color_freq) self.SetForegroundColour(conf.color_freq_txt) self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) # Click on a digit changes the frequency self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) self.Bind(wx.EVT_MOUSEWHEEL, self.OnWheel) if sys.platform == 'win32': self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) self.timer = wx.Timer(self) # Holding a digit continuously changes the frequency self.Bind(wx.EVT_TIMER, self.OnTimer) self.repeat_time = 0 # Repeat function is inactive def OnEnter(self, event): if not application.w_phase: self.SetFocus() # Set focus so we get mouse wheel events def Clip(self, clip): """Change color to indicate clipping.""" if clip: self.SetBackgroundColour('deep pink') else: self.SetBackgroundColour(conf.color_freq) self.Refresh() def Display(self, freq): """Set the frequency to be displayed.""" txt = FreqFormatter(freq) self.SetLabel('%s Hz' % txt) def GetIndex(self, event): # Determine which digit is being changed mouse_x, mouse_y = event.GetPosition() width, height = self.GetClientSize().Get() text = self.GetLabel() tw, th = self.GetTextExtent(text) edge = (width - tw) // 2 digit = self.GetTextExtent('0')[0] blank = self.GetTextExtent(' ')[0] if mouse_x < edge - digit: return None x = width - edge - self.GetTextExtent(" Hz")[0] - mouse_x if x < 0: return None #print ('size', width, height, 'mouse', mouse_x, mouse_y, 'digit', digit, 'blank', blank) shift = 0 while x > digit * 3: shift += 1 x -= digit * 3 + blank if x < 0: return None return x // digit + shift * 3 # index of digit being changed def OnLeftDown(self, event): # Click on a digit changes the frequency if self.repeat_time: self.timer.Stop() self.repeat_time = 0 index = self.GetIndex(event) if index is not None: self.index = index mouse_x, mouse_y = event.GetPosition() width, height = self.GetClientSize().Get() if mouse_y < height // 2: self.increase = True else: self.increase = False self.ChangeFreq() self.repeat_time = 300 # first button push self.timer.Start(milliseconds=300, oneShot=True) def OnLeftUp(self, event): self.timer.Stop() self.repeat_time = 0 def ChangeFreq(self): text = self.GetLabel() text = text.replace(' ', '')[:-2] text = text[:len(text)-self.index] + '0' * self.index if self.increase: freq = int(text) + 10 ** self.index else: freq = int(text) - 10 ** self.index if freq <= 0 and self.index > 0: freq = 10 ** (self.index - 1) #print ('X', x, 'N', n, text, 'freq', freq) application.ChangeRxTxFrequency(None, freq) def OnTimer(self, event): if self.repeat_time == 300: # after first push, turn on repeats self.repeat_time = 150 elif self.repeat_time > 20: self.repeat_time -= 5 self.ChangeFreq() self.timer.Start(milliseconds=self.repeat_time, oneShot=True) def OnWheel(self, event): index = self.GetIndex(event) if index is not None: self.index = index if event.GetWheelRotation() > 0: self.increase = True else: self.increase = False self.ChangeFreq() class SliderBoxH: """A horizontal control with a slider and text with a value. The text must have a %d or %f if display is True.""" def __init__(self, parent, text, init, themin, themax, handler, display, pos, width, scale=1): self.text = text self.handler = handler self.display = display self.scale = scale if display: # Display the slider value t1 = self.text % (themin * scale) t2 = self.text % (themax * scale) if len(t1) > len(t2): # set text size to the largest t = t1 else: t = t2 else: t = self.text if pos is None: self.text_ctrl = wx.StaticText(parent, -1, t, style=wx.ST_NO_AUTORESIZE) w2, h2 = self.text_ctrl.GetSize() self.text_ctrl.SetSizeHints(w2, -1, w2) self.slider = wx.Slider(parent, -1, init, themin, themax) else: # Thanks to Stephen Hurd self.text_ctrl = wx.StaticText(parent, -1, t, pos=pos) w2, h2 = self.text_ctrl.GetSize() self.slider = wx.Slider(parent, -1, init, themin, themax) w3, h3 = self.slider.GetSize() p2 = pos[1] if h3 > h2: p2 -= (h3 - h2) / 2 else: p2 += (h2 - h3) / 2 self.slider.SetSize((width - w2, h3)) self.slider.SetPosition((pos[0] + w2, p2)) self.slider.Bind(wx.EVT_SCROLL, self.OnScroll) self.text_ctrl.SetForegroundColour(parent.GetForegroundColour()) self.OnScroll() def OnScroll(self, event=None): if event: event.Skip() if self.handler: self.handler(event) if self.display: t = self.text % (self.slider.GetValue() * self.scale) else: t = self.text self.text_ctrl.SetLabel(t) def GetValue(self): return self.slider.GetValue() def SetValue(self, value): # Set slider visual position; does not call handler self.slider.SetValue(value) self.OnScroll() class SliderBoxHH(SliderBoxH, wx.BoxSizer): """A horizontal control with a slider and text with a value. The text must have a %d if display is True.""" def __init__(self, parent, text, init, themin, themax, handler, display): wx.BoxSizer.__init__(self, wx.HORIZONTAL) SliderBoxH.__init__(self, parent, text, init, themin, themax, handler, display, None, None) #font = wx.Font(10, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_NORMAL, False, conf.quisk_typeface) #self.text_ctrl.SetFont(font) self.Add(self.text_ctrl, 0, wx.ALIGN_CENTER) self.Add(self.slider, 1, wx.ALIGN_CENTER) class SliderBoxV(wx.BoxSizer): """A vertical box containing a slider and a text heading""" # Note: A vertical wx slider has the max value at the bottom. This is # reversed for this control. def __init__(self, parent, text, init, themax, handler, display=False, themin=0): wx.BoxSizer.__init__(self, wx.VERTICAL) self.slider = wx.Slider(parent, -1, init, themin, themax, style=wx.SL_VERTICAL|wx.SL_INVERSE) self.slider.Bind(wx.EVT_SCROLL, handler) sw, sh = self.slider.GetSize() self.text = text self.themin = themin self.themax = themax if display: # Display the slider value when it is thumb'd self.text_ctrl = wx.StaticText(parent, -1, str(themax)) self.text_ctrl.SetFont(button_font) w1, self.text_height = self.text_ctrl.GetSize() # Measure size with max number self.text_ctrl.SetLabel(str(themin)) w3, h3 = self.text_ctrl.GetSize() # Measure size with min number self.text_ctrl.SetLabel(text) w2, h2 = self.text_ctrl.GetSize() # Measure size with text self.width = max(w1, w2, w3, sw) + self.text_ctrl.GetCharWidth() self.text_ctrl.SetSizeHints(self.width, -1, self.width) self.slider.Bind(wx.EVT_SCROLL_THUMBTRACK, self.Change) self.slider.Bind(wx.EVT_SCROLL_THUMBRELEASE, self.ChangeDone) else: self.text_ctrl = wx.StaticText(parent, -1, text) self.text_ctrl.SetFont(button_font) w2, self.text_height = self.text_ctrl.GetSize() # Measure size with text self.width = max(w2, sw) + self.text_ctrl.GetCharWidth() self.text_ctrl.SetSizeHints(self.width, -1, self.width) self.text_ctrl.SetForegroundColour(parent.GetForegroundColour()) self.Add(self.text_ctrl, 0, wx.ALIGN_CENTER_VERTICAL) self.Add(self.slider, 1, wx.ALIGN_CENTER_VERTICAL) def Change(self, event): event.Skip() self.text_ctrl.SetLabel(str(self.slider.GetValue())) def ChangeDone(self, event): event.Skip() self.text_ctrl.SetLabel(self.text) def GetValue(self): return self.slider.GetValue() def SetValue(self, value): # Set slider visual position; does not call handler self.slider.SetValue(value) class QuiskText1(wx.lib.stattext.GenStaticText): # Self-drawn text for QuiskText. def __init__(self, parent, size_text, height, style=0, fixed=False): wx.lib.stattext.GenStaticText.__init__(self, parent, -1, '', pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.ST_NO_AUTORESIZE|style, name = "QuiskText1") self.fixed = fixed self.size_text = size_text self.pen = wx.Pen(conf.color_btn, 2) self.brush = wx.Brush(conf.color_freq) self.SetForegroundColour(conf.color_freq_txt) self.SetSizeHints(1, height, 9999, height) def _MeasureFont(self, dc, width, height): # Set decreasing point size until size_text fits in the space available for points in range(20, 6, -1): if self.fixed: font = wx.Font(points, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.FONTWEIGHT_NORMAL, False, conf.quisk_typeface) else: font = wx.Font(points, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_NORMAL, False, conf.quisk_typeface) dc.SetFont(font) w, h = dc.GetTextExtent(self.size_text) if w < width and h < height: break self.size_text = '' self.SetFont(font) def OnPaint(self, event): dc = wx.PaintDC(self) width, height = self.GetClientSize().Get() if not width or not height: return dc.SetPen(self.pen) dc.SetBrush(self.brush) dc.DrawRectangle(1, 1, width-1, height-1) label = self.GetLabel() if not label: return if self.size_text: self._MeasureFont(dc, width-2, height-2) else: dc.SetFont(self.GetFont()) if self.IsEnabled(): dc.SetTextForeground(self.GetForegroundColour()) else: dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)) style = self.GetWindowStyleFlag() w, h = dc.GetTextExtent(label) y = (height - h) // 2 if y < 0: y = 0 if style & wx.ALIGN_RIGHT: x = width - w - 4 elif style & wx.ALIGN_CENTER: x = (width - w - 1)//2 else: x = 3 dc.DrawText(label, x, y) class QuiskText(wx.BoxSizer): # A one-line text display left/right/center justified and vertically centered. # The height of the control is fixed as "height". The width is expanded. # The font is chosen so size_text fits in the client area. def __init__(self, parent, size_text, height, style=0, fixed=False): wx.BoxSizer.__init__(self, wx.HORIZONTAL) self.TextCtrl = QuiskText1(parent, size_text, height, style, fixed) self.Add(self.TextCtrl, 1, flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL) def SetLabel(self, label): self.TextCtrl.SetLabel(label) # Start of our button classes. They are compatible with wxPython GenButton # buttons. Use the usual methods for access: # GetLabel(self), SetLabel(self, label): Get and set the label # Enable(self, flag), Disable(self), IsEnabled(self): Enable / Disable # GetValue(self), SetValue(self, value): Get / Set check button state True / False # SetIndex(self, index): For cycle buttons, set the label from its index class QuiskButtons: """Base class for special buttons.""" def __init__(self): self.up_brush = wx.Brush(conf.color_btn) r, g, b = self.up_brush.GetColour().Get(False) r, g, b = min(255,r+32), min(255,g+32), min(255,b+32) self.down_brush = wx.Brush(wx.Colour(r, g, b)) self.color_disable = conf.color_disable def InitButtons(self, text, text_color=None): if text_color: self.text_color = text_color else: self.text_color = conf.color_enable self.SetBezelWidth(button_bezel) self.SetBackgroundColour(conf.color_btn) self.SetUseFocusIndicator(False) self.decoration = None self.char_shortcut = '' self.SetFont(button_font) if text: w, h = self.GetTextExtent(text) else: w, h = self.GetTextExtent("OK") self.Disable() # create a size for null text, but Disable() w += button_bezel * 2 + self.GetCharWidth() h = h * 12 // 10 h += button_bezel * 2 self.SetSizeHints(w, h, 999, h, 1, 1) def DrawLabel(self, dc, width, height, dx=0, dy=0): # Override to change Disable text color if self.up: # Clear the background here dc.SetBrush(self.up_brush) else: dc.SetBrush(self.down_brush) dc.SetPen(wx.TRANSPARENT_PEN) bw = self.bezelWidth dc.DrawRectangle(bw, bw, width - bw * 2, height - bw * 2) dc.SetFont(self.GetFont()) label = self.GetLabel() tw, th = dc.GetTextExtent(label) self.label_width = tw dx = dy = self.labelDelta slabel = re.split('('+u'\u25CF'+')', label) # unicode symbol for record: a filled dot for part in slabel: # This code makes the symbol red. Thanks to Christof, DJ4CM. if self.IsEnabled(): if part == u'\u25CF': dc.SetTextForeground('red') else: dc.SetTextForeground(self.text_color) else: dc.SetTextForeground(self.color_disable) if self.char_shortcut: scut = part.split(self.char_shortcut, 1) if len(scut) == 2: # The shortcut character is present in the string dc.DrawText(scut[0], (width-tw)//2+dx, (height-th)//2+dy) dx += dc.GetTextExtent(scut[0])[0] dc.SetFont(button_uline_font) dc.DrawText(self.char_shortcut, (width-tw)//2+dx, (height-th)//2+dy) dx += dc.GetTextExtent(self.char_shortcut)[0] dc.SetFont(self.GetFont()) dc.DrawText(scut[1], (width-tw)//2+dx, (height-th)//2+dy) dx += dc.GetTextExtent(scut[1])[0] else: dc.DrawText(part, (width-tw)//2+dx, (height-th)//2+dy) else: dc.DrawText(part, (width-tw)//2+dx, (height-th)//2+dy) dx += dc.GetTextExtent(part)[0] if self.decoration and conf.decorate_buttons: wd, ht = dc.GetTextExtent(self.decoration) dc.DrawText(self.decoration, width - wd * 15 // 10, (height - ht) // 2) def OnKeyDown(self, event): pass def OnKeyUp(self, event): pass def DrawGlyphCycle(self, dc, width, height): # Add a cycle indicator to the label if not conf.decorate_buttons: return uch = conf.btn_text_cycle wd, ht = dc.GetTextExtent(uch) if wd * 2 + self.label_width > width: # not enough space uch = conf.btn_text_cycle_small wd, ht = dc.GetTextExtent(uch) dc.DrawText(uch, width - wd, (height - ht) // 2) else: dc.DrawText(uch, width - wd * 15 // 10, (height - ht) // 2) def SetColorGray(self): self.SetBackgroundColour(wx.Colour(220, 220, 220)) # This sets the bezel colors self.SetBezelWidth(2) self.text_color = 'black' self.color_disable = 'white' self.up_brush = wx.Brush(wx.Colour(220, 220, 220)) self.down_brush = wx.Brush(wx.Colour(240, 240, 240)) class QuiskBitmapButton(wx.lib.buttons.GenBitmapButton): def __init__(self, parent, command, bitmap, use_right=False): self.command = command self.bitmap = bitmap wx.lib.buttons.GenBitmapButton.__init__(self, parent, -1, bitmap) self.SetFont(button_font) self.SetBezelWidth(button_bezel) self.SetBackgroundColour(conf.color_btn) self.SetUseFocusIndicator(False) self.Bind(wx.EVT_BUTTON, self.OnButton) self.direction = 1 if use_right: self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp) def DoGetBestSize(self): return self.bitmap.GetWidth() + button_bezel * 2, self.bitmap.GetHeight() + button_bezel * 2 def OnButton(self, event): if self.command: self.command(event) def OnRightDown(self, event): if self.GetBitmapLabel() == _bitmap_cyclepop: self.OnLeftDown(event) def OnRightUp(self, event): if self.GetBitmapLabel() == _bitmap_cyclepop: self.direction = -1 self.OnLeftUp(event) self.direction = 1 class QuiskPushbutton(QuiskButtons, wx.lib.buttons.GenButton): """A plain push button widget.""" def __init__(self, parent, command, text, use_right=False, text_color=None, style=0): QuiskButtons.__init__(self) wx.lib.buttons.GenButton.__init__(self, parent, -1, text, style=style) self.command = command self.Bind(wx.EVT_BUTTON, self.OnButton) self.InitButtons(text, text_color) self.direction = 1 if use_right: self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp) def OnButton(self, event): if self.command: self.command(event) def OnRightDown(self, event): self.direction = -1 self.OnLeftDown(event) def OnRightUp(self, event): self.OnLeftUp(event) self.direction = 1 class QuiskRepeatbutton(QuiskButtons, wx.lib.buttons.GenButton): """A push button that repeats when held down.""" def __init__(self, parent, command, text, up_command=None, use_right=False): QuiskButtons.__init__(self) wx.lib.buttons.GenButton.__init__(self, parent, -1, text) self.command = command self.up_command = up_command self.timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.OnTimer) self.Bind(wx.EVT_BUTTON, self.OnButton) self.InitButtons(text) self.repeat_state = 0 # repeater button inactive self.direction = 1 if use_right: self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp) def SendCommand(self, command): if command: event = wx.PyEvent() event.SetEventObject(self) command(event) def OnLeftDown(self, event): if self.IsEnabled(): self.shift = event.ShiftDown() self.control = event.ControlDown() self.SendCommand(self.command) self.repeat_state = 1 # first button push self.timer.Start(milliseconds=300, oneShot=True) wx.lib.buttons.GenButton.OnLeftDown(self, event) def OnLeftUp(self, event): if self.IsEnabled(): self.SendCommand(self.up_command) self.repeat_state = 0 self.timer.Stop() wx.lib.buttons.GenButton.OnLeftUp(self, event) def OnRightDown(self, event): if self.IsEnabled(): self.shift = event.ShiftDown() self.control = event.ControlDown() self.direction = -1 self.OnLeftDown(event) def OnRightUp(self, event): if self.IsEnabled(): self.OnLeftUp(event) self.direction = 1 def OnTimer(self, event): if self.repeat_state == 1: # after first push, turn on repeats self.timer.Start(milliseconds=150, oneShot=False) self.repeat_state = 2 if self.repeat_state: # send commands until button is released self.SendCommand(self.command) def OnButton(self, event): pass # button command not used class QuiskCheckbutton(QuiskButtons, wx.lib.buttons.GenToggleButton): """A button that pops up and down, and changes color with each push.""" # Check button; get the checked state with self.GetValue() def __init__(self, parent, command, text, color=None, use_right=False): QuiskButtons.__init__(self) wx.lib.buttons.GenToggleButton.__init__(self, parent, -1, text) self.InitButtons(text) self.Bind(wx.EVT_BUTTON, self.OnButton) self.button_down = 0 # used for radio buttons self.command = command if color is None: self.down_brush = wx.Brush(conf.color_check_btn) else: self.down_brush = wx.Brush(color) self.direction = 1 if use_right: self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp) def SetValue(self, value, do_cmd=False): wx.lib.buttons.GenToggleButton.SetValue(self, value) self.button_down = value if do_cmd and self.command: event = wx.PyEvent() event.SetEventObject(self) self.command(event) def OnButton(self, event): if self.command: self.command(event) def OnRightDown(self, event): self.direction = -1 self.OnLeftDown(event) def OnRightUp(self, event): self.OnLeftUp(event) self.direction = 1 def Shortcut(self, event): self.SetValue(not self.GetValue(), True) class QuiskBitField(wx.Window): """A control used to set/unset bits.""" def __init__(self, parent, numbits, value, height, command): self.numbits = numbits self.value = value self.height = height self.command = command self.backgroundBrush = wx.Brush('white') self.pen = wx.Pen('light gray', 1) self.font = parent.GetFont() self.charx, self.chary = parent.GetTextExtent('1') self.linex = [] # x pixel of vertical lines self.bitx = [] # x pixel of character for bits space = self.space = max(2, self.charx * 2 // 10) width = 0 for i in range(numbits - 1): width += space + self.charx + space self.linex.append(width) width = space for i in range(numbits): self.bitx.append(width) width += self.charx + space * 2 wx.Window.__init__(self, parent, size=(width + 4, height), style=wx.BORDER_SUNKEN) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) def OnPaint(self, event): dc = wx.PaintDC(self) dc.SetBackground(self.backgroundBrush) dc.Clear() dc.SetFont(self.font) dc.SetPen(self.pen) for x in self.linex: dc.DrawLine(x, 0, x, self.height) for i in range(self.numbits): power = self.numbits - i - 1 x = self.bitx[i] if self.value & (1 << power): dc.DrawText('1', x, 0) def OnLeftDown(self, event): mouse_x, mouse_y = event.GetPosition().Get() for index in range(len(self.linex)): if mouse_x < self.linex[index]: break else: index = self.numbits - 1 power = self.numbits - index - 1 mask = 1 << power if self.value & mask: self.value &= ~ mask else: self.value |= mask self.Refresh() if self.command: self.command(self) class QFilterButtonWindow(wx.Frame): """Create a window with controls for the button""" def __init__(self, wrap, value): self.wrap = wrap l = self.valuelist = [] bw = 10 incr = 10 for i in range(0, 101): l.append(bw) bw += incr if bw == 100: incr = 20 elif bw == 500: incr = 50 elif bw == 1000: incr = 100 elif bw == 5000: incr = 500 elif bw == 10000: incr = 1000 x, y = wrap.GetPosition().Get() x, y = wrap.GetParent().ClientToScreen(wx.Point(x, y)) w, h = wrap.GetSize() height = h * 10 size = (w, height) if sys.platform == 'win32': pos = (x, y - height) t = 'Filter' else: pos = (x, y - height - h) t = '' wx.Frame.__init__(self, wrap.GetParent(), -1, t, pos, size, wx.FRAME_TOOL_WINDOW|wx.FRAME_FLOAT_ON_PARENT|wx.CLOSE_BOX|wx.CAPTION|wx.SYSTEM_MENU) self.SetBackgroundColour(conf.color_freq) self.Bind(wx.EVT_CLOSE, self.OnClose) try: index = self.valuelist.index(value) except ValueError: index = 0 self.wrap.button.slider_value = self.valuelist[0] self.slider = wx.Slider(self, -1, index, 0, 100, (0, 0), (w//2, height), wx.SL_VERTICAL|wx.SL_INVERSE) self.slider.Bind(wx.EVT_SCROLL, self.OnSlider) self.SetTitle("%d" % self.valuelist[index]) self.Show() self.slider.SetFocus() def OnSlider(self, event): index = self.slider.GetValue() value = self.valuelist[index] self.SetTitle("%d" % value) self.wrap.ChangeSlider(value) #self.wrap.SetLabel(str(value)) #self.wrap.SetValue(True, True) #application.filterAdjBw1 = value def OnClose(self, event): self.wrap.adjust = None self.Destroy() class QSliderButtonWindow(wx.Frame): """Create a window with controls for the button""" def __init__(self, button, value): self.button = button x, y = button.GetPosition().Get() x, y = button.GetParent().ClientToScreen(wx.Point(x, y)) w, h = button.GetSize() height = h * 10 size = (w, height) if sys.platform == 'win32': pos = (x, y - height) else: pos = (x, y - height - h) wx.Frame.__init__(self, button.GetParent(), -1, '', pos, size, wx.FRAME_TOOL_WINDOW|wx.FRAME_FLOAT_ON_PARENT|wx.CLOSE_BOX|wx.CAPTION|wx.SYSTEM_MENU) self.SetBackgroundColour(conf.color_freq) self.Bind(wx.EVT_CLOSE, self.OnClose) self.slider = wx.Slider(self, -1, value, self.button.slider_min, self.button.slider_max, (0, 0), (w//2, height), wx.SL_VERTICAL|wx.SL_INVERSE) self.slider.Bind(wx.EVT_SCROLL, self.OnSlider) if self.button.display: value = float(value) / self.button.slider_max self.SetTitle("%6.3f" % value) self.Show() self.slider.SetFocus() def OnSlider(self, event): value = self.slider.GetValue() if self.button.display: v = float(value) / self.button.slider_max self.SetTitle("%6.3f" % v) self.button.ChangeSlider(value) def OnClose(self, event): self.button.adjust = None self.Destroy() # Dual slider widget for bias class QDualSliderButtonWindow(wx.Frame): # Thanks to Steve, KF7O """Create a window with controls for the button""" def __init__(self, button): self.button = button x, y = button.GetPosition().Get() x, y = button.GetParent().ClientToScreen(wx.Point(x, y)) w, h = button.GetSize() w = w * 12 // 10 height = h * 10 size = (w, height) if sys.platform == 'win32': pos = (x, y - height) else: pos = (x, y - height - h) wx.Frame.__init__(self, button.GetParent(), -1, '', pos, size, wx.FRAME_TOOL_WINDOW|wx.FRAME_FLOAT_ON_PARENT|wx.CLOSE_BOX|wx.CAPTION|wx.SYSTEM_MENU) self.SetBackgroundColour(conf.color_freq) self.Bind(wx.EVT_CLOSE, self.OnClose) panel = wx.Panel(self, -1) panel.SetBackgroundColour(conf.color_freq) hbox = wx.BoxSizer(wx.HORIZONTAL) self.lslider = wx.Slider(panel, -1, self.button.lslider_value, self.button.slider_min, self.button.slider_max, (0, 0), (w//2, height), wx.SL_VERTICAL|wx.SL_INVERSE) self.lslider.Bind(wx.EVT_SCROLL, self.OnSlider) hbox.Add(self.lslider, flag=wx.LEFT) self.rslider = wx.Slider(panel, -1, self.button.rslider_value, self.button.slider_min, self.button.slider_max, (0, 0), (w//2, height), wx.SL_VERTICAL|wx.SL_INVERSE) self.rslider.Bind(wx.EVT_SCROLL, self.OnSlider) hbox.Add(self.rslider, flag=wx.RIGHT) panel.SetSizer(hbox) if self.button.display: self.SetTitle("%3d %3d" % (self.button.lslider_value,self.button.rslider_value)) self.Show() self.lslider.SetFocus() def OnSlider(self, event): lvalue = self.lslider.GetValue() rvalue = self.rslider.GetValue() self.button.ChangeSlider(lvalue,rvalue) if self.button.display: self.SetTitle("%3d %3d" % (self.button.lslider_value,self.button.rslider_value)) def OnClose(self, event): self.button.adjust = None self.Destroy() class WrapControl(wx.BoxSizer): def __init__(self): wx.BoxSizer.__init__(self, wx.HORIZONTAL) def Enable(self, value=True): self.button.Enable(value) def SetLabel(self, text=None): if text is not None: self.button.SetLabel(text) def GetParent(self): return self.button.GetParent() def GetValue(self): return self.button.GetValue() def SetValue(self, value, do_cmd=False): self.button.SetValue(value, do_cmd) def GetLabel(self): return self.button.GetLabel() def __getattr__(self, name): return getattr(self.button, name) class WrapPushButton(WrapControl): def __init__(self, button, control): self.button = button WrapControl.__init__(self) self.Add(button, 1, flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL) b = QuiskPushbutton(button.GetParent(), control, conf.btn_text_switch) self.Add(b, 0, flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=2) class WrapMenu(WrapControl): def __init__(self, button, menu, on_open=None): self.button = button self.menu = menu self.on_open = on_open WrapControl.__init__(self) self.Add(button, 1, flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL) b = QuiskBitmapButton(button.GetParent(), self.OnPopButton, _bitmap_menupop) self.Add(b, 0, flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=2) def OnPopButton(self, event): if self.on_open: self.on_open(self.menu) pos = (5, 5) self.button.PopupMenu(self.menu, pos) class WrapSlider(WrapControl): def __init__(self, button, command, slider_value=0, slider_min=0, slider_max=1000, display=False, wintype=''): self.adjust = None self.dual = False # dual means separate slider values for on and off self.button = button self.main_command = button.command button.command = self.OnMainButton self.command = command button.slider_value = slider_value # value for not dual button.slider_value_off = slider_value # value for dual and button up button.slider_value_on = slider_value # value for dual and button down self.slider_min = slider_min self.slider_max = slider_max self.display = display # Display the value at the top self.wintype = wintype WrapControl.__init__(self) self.Add(button, 1, flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL) b = QuiskBitmapButton(button.GetParent(), self.OnPopButton, _bitmap_sliderpop) self.Add(b, 0, flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=2) def SetDual(self, dual): # dual means separate slider values for on and off self.dual = dual if self.adjust: self.adjust.Destroy() self.adjust = None def DeleteSliderWindow(self): if self.adjust: self.adjust.Destroy() self.adjust = None def OnPopButton(self, event): if self.adjust: self.adjust.Destroy() self.adjust = None else: if not self.dual: value = self.button.slider_value elif self.button.GetValue(): value = self.button.slider_value_on else: value = self.button.slider_value_off if self.wintype == 'filter': self.adjust = QFilterButtonWindow(self, value) else: self.adjust = QSliderButtonWindow(self, value) def OnMainButton(self, event): if self.adjust: self.adjust.Destroy() self.adjust = None if self.main_command: self.main_command(event) def ChangeSlider(self, value): if not self.dual: self.button.slider_value = value elif self.button.GetValue(): self.button.slider_value_on = value else: self.button.slider_value_off = value if self.wintype == 'filter': self.button.SetLabel(str(value)) self.button.Refresh() if self.command: event = wx.PyEvent() event.SetEventObject(self.button) self.command(event) def SetSlider(self, value=None, value_off=None, value_on=None): if value is not None: self.button.slider_value = value if value_off is not None: self.button.slider_value_off = value_off if value_on is not None: self.button.slider_value_on = value_on class WrapDualSlider(WrapControl): # Thanks to Steve, KF7O def __init__(self, button, command, lslider_value=0, rslider_value=0, slider_min=0, slider_max=1000, display=0): self.adjust = None self.button = button self.main_command = button.command button.command = self.OnMainButton self.command = command button.lslider_value = lslider_value button.rslider_value = rslider_value self.slider_min = slider_min self.slider_max = slider_max self.display = display # Display the value at the top WrapControl.__init__(self) self.Add(button, 1, flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL) print("Size",self.button.GetSize()) ## This is a hack to get _bitmap_sliderpop ## It would be better if _bitmap_sliderpop were not a global variable ##but a first-class member in another module _bitmap_sliderpop = MakeWidgetGlobals.__globals__['_bitmap_sliderpop'] b = QuiskBitmapButton(button.GetParent(), self.OnPopButton, _bitmap_sliderpop) self.Add(b, 0, flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=2) def OnPopButton(self, event): if self.adjust: self.adjust.Destroy() self.adjust = None else: self.adjust = QDualSliderButtonWindow(self) def OnMainButton(self, event): if self.adjust: self.adjust.Destroy() self.adjust = None if self.main_command: self.main_command(event) def ChangeSlider(self, lvalue, rvalue): self.button.lslider_value = lvalue self.button.rslider_value = rvalue if self.command: event = wx.PyEvent() event.SetEventObject(self.button) self.command(event) class QuiskCycleCheckbutton(QuiskCheckbutton): """A button that cycles through its labels with each push. The button is up for labels[0], down for all other labels. Change to the next label for each push. If you call SetLabel(), the label must be in the list. The self.index is the index of the current label. """ def __init__(self, parent, command, labels, color=None, is_radio=False): self.labels = list(labels) # Be careful if you change this list self.index = 0 # index of selected label 0, 1, ... self.direction = 0 # 1 for up, -1 for down, 0 for no change to index self.is_radio = is_radio # Is this a radio cycle button? if color is None: color = conf.color_cycle_btn QuiskCheckbutton.__init__(self, parent, command, labels[0], color) self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDclick) def SetLabel(self, label, do_cmd=False): self.index = self.labels.index(label) QuiskCheckbutton.SetLabel(self, label) QuiskCheckbutton.SetValue(self, self.index) if do_cmd and self.command: event = wx.PyEvent() event.SetEventObject(self) self.command(event) def SetIndex(self, index, do_cmd=False): self.index = index QuiskCheckbutton.SetLabel(self, self.labels[index]) QuiskCheckbutton.SetValue(self, index) if do_cmd and self.command: event = wx.PyEvent() event.SetEventObject(self) self.command(event) def OnButton(self, event): if not self.is_radio or self.button_down: self.direction = 1 self.index += 1 if self.index >= len(self.labels): self.index = 0 self.SetIndex(self.index) else: self.direction = 0 if self.command: self.command(event) def OnRightDown(self, event): # Move left in the list of labels if not self.is_radio or self.GetValue(): self.index -= 1 if self.index < 0: self.index = len(self.labels) - 1 self.SetIndex(self.index) self.direction = -1 if self.command: self.command(event) def OnLeftDclick(self, event): # Left double-click: Set index zero if not self.is_radio or self.GetValue(): self.index = 0 self.SetIndex(self.index) self.direction = 1 if self.command: self.command(event) def DrawLabel(self, dc, width, height, dx=0, dy=0): QuiskCheckbutton.DrawLabel(self, dc, width, height, dx, dy) self.DrawGlyphCycle(dc, width, height) def Shortcut(self, event): index = self.index + 1 if index >= len(self.labels): index = 0 self.SetIndex(index, True) class RadioButtonGroup: """This class encapsulates a group of radio buttons. This class is not a button! The "labels" is a list of labels for the toggle buttons. An item of labels can be a list/tuple, and the corresponding button will be a cycle button. """ def __init__(self, parent, command, labels, default, shortcuts=()): self.command = command self.buttons = [] self.button = None self.shortcuts = list(shortcuts[:]) self.last_shortcut = 0 i = 0 for text in labels: if isinstance(text, (list, tuple)): b = QuiskCycleCheckbutton(parent, self.OnButton, text, is_radio=True) if shortcuts: b.char_shortcut = shortcuts[i] for t in text: if t == default and self.button is None: b.SetLabel(t) self.button = b else: b = QuiskCheckbutton(parent, self.OnButton, text) if shortcuts: b.char_shortcut = shortcuts[i] if text == default and self.button is None: b.SetValue(True) self.button = b self.buttons.append(b) i += 1 def ReplaceButton(self, index, button): # introduce a specialized button b = self.buttons[index] b.Destroy() self.buttons[index] = button if isinstance(button, WrapSlider): button.main_command = self.OnButton elif isinstance(button, WrapMenu): button.button.command = self.OnButton else: button.command = self.OnButton def SetLabel(self, label, do_cmd=False, direction=None): self.button = None for b in self.buttons: if self.button is not None: b.SetValue(False) elif isinstance(b, QuiskCycleCheckbutton): try: index = b.labels.index(label) except ValueError: b.SetValue(False) continue else: b.SetIndex(index) self.button = b b.SetValue(True) if direction is not None: b.direction = direction elif b.GetLabel() == label: b.SetValue(True) self.button = b else: b.SetValue(False) if do_cmd and self.command and self.button: event = wx.PyEvent() event.SetEventObject(self.button) self.command(event) def GetButtons(self): return self.buttons def OnButton(self, event): win = event.GetEventObject() for b in self.buttons: if b is win or (isinstance(b, WrapControl) and b.button is win): self.button = b b.SetValue(True) else: b.SetValue(False) if self.command: self.command(event) def GetLabel(self): if not self.button: return None return self.button.GetLabel() def GetSelectedButton(self): # return the selected button return self.button def GetIndex(self): # Careful. Some buttons are complex. if not self.button: return None return self.buttons.index(self.button) def Shortcut(self, event): # Multiple buttons can have the same shortcut, so move to the next one. index = self.last_shortcut + 1 length = len(self.shortcuts) if index >= length: index = 0 for i in range(length): shortcut = self.shortcuts[index] if shortcut and wx.GetKeyState(ord(shortcut)): break index += 1 if index >= length: index = 0 else: return self.last_shortcut = index button = self.buttons[index] event = wx.PyEvent() event.SetEventObject(button) button.OnButton(event) class _PopWindow(wx.PopupWindow): def __init__(self, parent, command, labels, default): wx.PopupWindow.__init__(self, parent) self.panel = wx.Panel(self) self.panel.SetBackgroundColour(conf.color_popchoice) self.RbGroup = RadioButtonGroup(self.panel, command, labels, default) x = 5 y = 5 for b in self.RbGroup.buttons: b.SetPosition((x, y)) w, h = b.GetTextExtent(b.GetLabel()) width = w + 2 + 2 * b.bezelWidth + 4 * int(b.useFocusInd) height = h + 2 + 2 * b.bezelWidth + 4 * int(b.useFocusInd) b.SetInitialSize((width, height)) x += width + 5 self.SetSize((x, height + 2 * y)) self.panel.SetSize((x, height + 2 * y)) class RadioBtnPopup: """This class contains a button that pops up a row of radio buttons""" def __init__(self, parent, command, in_labels, default): self.parent = parent self.pop_command = command self.button_data = {} labels = [] for item in in_labels: if isinstance(item, (list, tuple)): labels.append(item[0]) self.button_data[item[0]] = [_bitmap_cyclepop, 0, len(item)] # bitmap, index, max_index else: labels.append(item) self.RbDialog = _PopWindow(parent, self.OnGroupButton, labels, default) self.RbDialog.Hide() self.pop_control = wx.BoxSizer(wx.HORIZONTAL) self.first_button = QuiskPushbutton(parent, self.OnFirstButton, labels[0], text_color=conf.color_popchoice) self.first_button.decoration = u'\u21D2' self.second_button = QuiskBitmapButton(parent, self.OnSecondButton, _bitmap_menupop, use_right=True) self.pop_control.Add(self.first_button, 1, flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL) self.pop_control.Add(self.second_button, 0, flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=2) self.pop_control.Show(self.second_button, False) self.adjust = None self.first_button.index = 0 def GetPopControl(self): return self.pop_control def AddMenu(self, label, menu): self.button_data[label] = (_bitmap_menupop, menu) return self def AddSlider(self, label, command, slider_value=0, slider_min=0, slider_max=1000, display=False, wintype=''): self.button_data[label] = [_bitmap_sliderpop, command, slider_value, slider_min, slider_max, display, wintype] def OnFirstButton(self, event): if self.adjust: # Destroy any slider window self.adjust.Destroy() self.adjust = None if self.RbDialog.IsShown(): self.RbDialog.Hide() return dw, dh = self.RbDialog.GetSize().Get() bw, bh = self.first_button.GetSize().Get() bx, by = self.first_button.ClientToScreen(wx.Point(0, 0)) self.RbDialog.Position(wx.Point(bx + bw * 8 // 10, by + bh // 2 - dh), wx.Size(1, 1)) self.RbDialog.Show() self.RbDialog.SetFocus() def AddSecondButton(self, label): data = self.button_data.get(label, None) if data is None: self.pop_control.Show(self.second_button, False) else: self.second_button.SetBitmapLabel(data[0]) self.pop_control.Show(self.second_button, True) if data[0] == _bitmap_cyclepop: self.first_button.index = data[1] self.pop_control.Layout() def OnSecondButton(self, event): label = self.first_button.GetLabel() data = self.button_data.get(label, None) if data is None: pass elif data[0] == _bitmap_menupop: self.first_button.PopupMenu(data[1], (5, 5)) elif data[0] == _bitmap_sliderpop: if self.adjust: self.adjust.Destroy() self.adjust = None else: bitm, self.command, slider_value, self.slider_min, self.slider_max, self.display, self.wintype = data self.adjust = QSliderButtonWindow(self, slider_value) self.second_data = data elif data[0] == _bitmap_cyclepop: if self.second_button.direction >= 0: data[1] += 1 if data[1] >= data[2]: data[1] = 0 else: data[1] -= 1 if data[1] < 0: data[1] = data[2] - 1 self.first_button.index = data[1] self.first_button.direction = self.second_button.direction if self.pop_command: event = wx.PyEvent() event.SetEventObject(self.first_button) self.pop_command(event) self.first_button.direction = 1 def OnGroupButton(self, event): btn = event.GetEventObject() label = btn.GetLabel() self.first_button.SetLabel(label) self.first_button.Refresh() self.RbDialog.Hide() self.AddSecondButton(label) if self.pop_command: event = wx.PyEvent() event.SetEventObject(self.first_button) self.pop_command(event) def Enable(self, label, enable): for b in self.RbDialog.RbGroup.buttons: if b.GetLabel() == label: b.Enable(enable) break def GetLabel(self): return self.first_button.GetLabel() def SetLabel(self, label, do_cmd=False, direction=None): self.first_button.SetLabel(label) self.AddSecondButton(label) self.RbDialog.RbGroup.SetLabel(label, False) if do_cmd and self.pop_command: event = wx.PyEvent() event.SetEventObject(self.first_button) self.pop_command(event) def Refresh(self): pass def ChangeSlider(self, slider_value): self.second_data[2] = slider_value command = self.second_data[1] if command: event = wx.PyEvent() self.first_button.slider_value = slider_value event.SetEventObject(self.first_button) command(event) def GetPositionTuple(self): return self.first_button.GetPosition().Get() def GetParent(self): return self.parent def GetSize(self): return self.first_button.GetSize() class FreqSetter(wx.TextCtrl): def __init__(self, parent, x, y, label, fmin, fmax, freq, command): self.pos = (x, y) self.label = label self.fmin = fmin self.fmax = fmax self.command = command self.font = wx.Font(16, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_NORMAL, False, conf.quisk_typeface) t = wx.StaticText(parent, -1, label, pos=(x, y)) t.SetFont(self.font) freq_w, freq_h = t.GetTextExtent(" 662 000 000") tw, th = t.GetSize().Get() x += tw + 20 wx.TextCtrl.__init__(self, parent, size=(freq_w, freq_h), pos=(x, y), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER) self.SetFont(self.font) self.Bind(wx.EVT_TEXT, self.OnText) self.Bind(wx.EVT_TEXT_ENTER, self.OnEnter) w, h = self.GetSize().Get() x += w + 1 self.butn = b = wx.SpinButton(parent, size=(freq_h, freq_h), pos=(x, y)) w, h = b.GetSize().Get() self.end_pos = (x + w, y + h) b.Bind(wx.EVT_SPIN, self.OnSpin) # The spin button frequencies are in kHz b.SetMin(fmin // 1000) b.SetMax(fmax // 1000) self.SetValue(freq) def OnText(self, event): self.SetBackgroundColour('pink') def OnEnter(self, event): text = wx.TextCtrl.GetValue(self) text = text.replace(' ', '') if '-' in text: return try: if '.' in text: freq = int(float(text) * 1000000 + 0.5) else: freq = int(text) except: return self.SetValue(freq) self.command(self) def OnSpin(self, event): freq = self.butn.GetValue() * 1000 self.SetValue(freq) self.command(self) def SetValue(self, freq): if freq < self.fmin: freq = self.fmin elif freq > self.fmax: freq = self.fmax self.butn.SetValue(freq // 1000) txt = FreqFormatter(freq) wx.TextCtrl.SetValue(self, txt) self.SetBackgroundColour(conf.color_entry) def GetValue(self): value = wx.TextCtrl.GetValue(self) value = value.replace(' ', '') try: value = int(value) except: value = 7000 return value