# 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):
    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)
      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):
    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
