1383 lines
50 KiB
Python
Executable File
1383 lines
50 KiB
Python
Executable File
# 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
|