# --------------------------------------------------------------------------------- # # PYFAGAUGE wxPython IMPLEMENTATION # # Darriele, @ 08/30/2010 # Updated: 09/07/2010 # Based on AWG : pygauge code # --------------------------------------------------------------------------------- # """ PyfaGauge is a generic Gauge implementation tailored for PYFA (Python Fitting Assistant) It uses the easeOutQuad equation from caurina.transitions.Tweener to do the animation stuff """ import wx import copy import math from gui.utils import colorUtils import gui.utils.drawUtils as drawUtils import gui.utils.animEffects as animEffects import gui.utils.fonts as fonts class PyGauge(wx.PyWindow): """ This class provides a visual alternative for `wx.Gauge`. It currently only support determinant mode (see SetValue and SetRange) """ def __init__(self, parent, id=wx.ID_ANY, range=100, pos=wx.DefaultPosition, size=(-1,30), style=0): """ Default class constructor. :param `parent`: parent window. Must not be ``None``; :param `id`: window identifier. A value of -1 indicates a default value; :param `pos`: the control position. A value of (-1, -1) indicates a default position, chosen by either the windowing system or wxPython, depending on platform; :param `size`: the control size. A value of (-1, -1) indicates a default size, chosen by either the windowing system or wxPython, depending on platform. """ wx.PyWindow.__init__(self, parent, id, pos, size, style) self._size = size self._border_colour = wx.BLACK self._barColour = self._barColourSorted = [wx.Colour(212,228,255)] self._barGradient = self._barGradientSorted = None self._border_padding = 0 self._range = range self._value = 0 self._fractionDigits = 0 self._timerId = wx.NewId() self._timer = None self._oldValue = 0 self._animDuration = 500 self._animStep = 0 self._period = 20 self._animValue = 0 self._animDirection = 0 self.animEffect = animEffects.OUT_QUAD self.transitionsColors = [( wx.Colour(191, 191, 191, 255) , wx.Colour(96, 191, 0, 255) ), ( wx.Colour(191, 167, 96, 255) , wx.Colour(255, 191, 0, 255) ), ( wx.Colour(255, 191, 0, 255) , wx.Colour(255, 128, 0, 255) ), ( wx.Colour(255, 128, 0, 255) , wx.Colour(255, 0, 0, 255) )] self.gradientEffect = -35 self._percentage = 0 self._oldPercentage = 0 self._showRemaining = False self.font = wx.Font(fonts.NORMAL, wx.SWISS, wx.NORMAL, wx.NORMAL, False) self.SetBarGradient((wx.Colour(119,119,119),wx.Colour(153,153,153))) self.SetBackgroundColour(wx.Colour(51,51,51)) self._tooltip = wx.ToolTip("") self.SetToolTip(self._tooltip) self._tooltip.SetTip("0.00/100.00") self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) self.Bind(wx.EVT_TIMER, self.OnTimer) self.Bind(wx.EVT_ENTER_WINDOW, self.OnWindowEnter) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnWindowLeave) def OnWindowEnter(self, event): self._showRemaining = True self.Refresh() def OnWindowLeave(self, event): self._showRemaining = False self.Refresh() def DoGetBestSize(self): """ Overridden base class virtual. Determines the best size of the button based on the label and bezel size. """ return wx.Size(self._size[0], self._size[1]) def GetBorderColour(self): return self._border_colour def SetBorderColour(self, colour): self._border_colour = colour SetBorderColor = SetBorderColour GetBorderColor = GetBorderColour def GetBarColour(self): return self._barColour[0] def SetBarColour(self, colour): if type(colour) != type([]): self._barColour = [colour] else: self._barColour = list(colour) SetBarColor = SetBarColour GetBarColor = GetBarColour def SetFractionDigits(self, digits): self._fractionDigits=digits def GetBarGradient(self): """ Returns a tuple containing the gradient start and end colours. """ if self._barGradient == None: return None return self._barGradient[0] def SetBarGradient(self, gradient = None): """ Sets the bar gradient. This overrides the BarColour. :param `gradient`: a tuple containing the gradient start and end colours. """ if gradient == None: self._barGradient = None else: if type(gradient) != type([]): self._barGradient = [gradient] else: self._barGradient = list(gradient) def GetBorderPadding(self): """ Gets the border padding. """ return self._border_padding def SetBorderPadding(self, padding): """ Sets the border padding. :param `padding`: pixels between the border and the progress bar. """ self._border_padding = padding def GetRange(self): """ Returns the maximum value of the gauge. """ return self._range def Animate(self): if not self._timer: self._timer = wx.Timer(self, self._timerId) self._animStep = 0 self._timer.Start(self._period) def SetRange(self, range, reinit = False): """ Sets the range of the gauge. The gauge length is its value as a proportion of the range. :param `range`: The maximum value of the gauge. """ if self._range == range: return range = float(range) if range <= 0: self._range = 0.01 else: self._range = range if reinit is False: self._oldPercentage = self._percentage self._percentage = (self._value/self._range) * 100 else: self._oldPercentage = self._percentage self._percentage = 0 self._value = 0 self.Animate() self._tooltip.SetTip("%.2f/%.2f" % (self._value, self._range if self._range >0.01 else 0)) def GetValue(self): """ Returns the current position of the gauge. """ return self._value def SetValue(self, value): """ Sets the current position of the gauge. """ if self._value == value: return value = float(value) self._oldPercentage = self._percentage self._value = value if value < 0: self._value = 0 self._percentage = (self._value/self._range) * 100 self.Animate() self._tooltip.SetTip("%.2f/%.2f" % (self._value, self._range)) def SetValueRange(self, value, range, reinit = False): if self._value == value and self._range == range: return range = float(range) if range <= 0: self._range = 0.01 else: self._range = range value = float(value) self._value = value if value < 0: self._value = float(0) if reinit is False: self._oldPercentage = self._percentage self._percentage = (self._value/self._range) * 100 else: self._oldPercentage = self._percentage self._percentage = 0 self.Animate() self._tooltip.SetTip("%.2f/%.2f" % (self._value, self._range if self._range >0.01 else 0)) def OnEraseBackground(self, event): """ Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{PyGauge}. :param `event`: a `wx.EraseEvent` event to be processed. :note: This method is intentionally empty to reduce flicker. """ pass def OnPaint(self, event): """ Handles the ``wx.EVT_PAINT`` event for L{PyGauge}. :param `event`: a `wx.PaintEvent` event to be processed. """ dc = wx.BufferedPaintDC(self) rect = self.GetClientRect() dc.SetBackground(wx.Brush(self.GetBackgroundColour())) dc.Clear() colour = self.GetBackgroundColour() dc.SetBrush(wx.Brush(colour)) dc.SetPen(wx.Pen(colour)) dc.DrawRectangleRect(rect) value = self._percentage if self._timer: if self._timer.IsRunning(): value = self._animValue if self._border_colour: dc.SetPen(wx.Pen(self.GetBorderColour())) dc.DrawRectangleRect(rect) pad = 1 + self.GetBorderPadding() rect.Deflate(pad,pad) if self.GetBarGradient(): if value > 100: w = rect.width else: w = rect.width * (float(value) / 100) r = copy.copy(rect) r.width = w pv = value xv=1 transition = 0 if pv <= 100: xv = pv/100 transition = 0 elif pv <=101: xv = pv -100 transition = 1 elif pv <= 103: xv = (pv -101)/2 transition = 2 elif pv <= 105: xv = (pv -103)/2 transition = 3 else: pv = 106 xv = pv -100 transition = -1 if transition != -1: colorS,colorE = self.transitionsColors[transition] color = colorUtils.CalculateTransitionColor(colorS, colorE, xv) else: color = wx.Colour(191,48,48) if self.gradientEffect > 0: gcolor = colorUtils.BrightenColor(color, float(self.gradientEffect) / 100) gMid = colorUtils.BrightenColor(color, float(self.gradientEffect/2) / 100) else: gcolor = colorUtils.DarkenColor(color, float(-self.gradientEffect) / 100) gMid = colorUtils.DarkenColor(color, float(-self.gradientEffect/2) / 100) gBmp = drawUtils.DrawGradientBar(r.width, r.height, gMid, color, gcolor) dc.DrawBitmap(gBmp,r.left, r.top) else: colour=self.GetBarColour() dc.SetBrush(wx.Brush(colour)) dc.SetPen(wx.Pen(colour)) if value > 100: w = rect.width else: w = rect.width * (float(value) / 100) r = copy.copy(rect) r.width = w dc.DrawRectangleRect(r) dc.SetFont(self.font) r = copy.copy(rect) r.left +=1 r.top +=1 if self._range == 0.01 and self._value > 0: formatStr = u'\u221e' dc.SetTextForeground(wx.Colour(80,80,80)) dc.DrawLabel(formatStr, r, wx.ALIGN_CENTER) dc.SetTextForeground(wx.Colour(255,255,255)) dc.DrawLabel(formatStr, rect, wx.ALIGN_CENTER) else: if self.GetBarGradient() and self._showRemaining: range = self._range if self._range > 0.01 else 0 value = range - self._value if value < 0: label = "over" value = -value else: label = "left" formatStr = "{0:." + str(self._fractionDigits) + "f} " + label else: formatStr = "{0:." + str(self._fractionDigits) + "f}%" dc.SetTextForeground(wx.Colour(80,80,80)) dc.DrawLabel(formatStr.format(value), r, wx.ALIGN_CENTER) dc.SetTextForeground(wx.Colour(255,255,255)) dc.DrawLabel(formatStr.format(value), rect, wx.ALIGN_CENTER) def OnTimer(self,event): """ Handles the ``wx.EVT_TIMER`` event for L{PyfaGauge}. :param `event`: a timer event """ oldValue=self._oldPercentage value=self._percentage start = 0 direction = 1 if oldValue < value else -1 end = direction * (value - oldValue) self._animDirection = direction step = self.animEffect(self._animStep, start, end, self._animDuration) self._animStep += self._period if self._timerId == event.GetId(): stop_timer = False if self._animStep > self._animDuration: stop_timer = True if direction == 1: if (oldValue+step) < value: self._animValue = oldValue+step else: stop_timer = True else: if (oldValue-step) > value: self._animValue = oldValue-step else: stop_timer = True if stop_timer: self._timer.Stop() self.Refresh()