/ lib / python / gladevcp / hal_bar.py
hal_bar.py
  1  # vim: sts=4 sw=4 et
  2  # GladeVcp Widgets
  3  #
  4  # Copyright (c) 2010  Pavel Shramov <shramov@mexmat.net>
  5  #
  6  # This program is free software: you can redistribute it and/or modify
  7  # it under the terms of the GNU General Public License as published by
  8  # the Free Software Foundation, either version 2 of the License, or
  9  # (at your option) any later version.
 10  #
 11  # This program is distributed in the hope that it will be useful,
 12  # but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  # GNU General Public License for more details.
 15  
 16  import gtk
 17  import gobject
 18  import cairo
 19  import math
 20  import gtk.glade
 21  
 22  # This creates the custom LED widget
 23  
 24  from hal_widgets import _HalWidgetBase, hal, hal_pin_changed_signal
 25  
 26  MAX_INT = 0x7fffffff
 27  
 28  def gdk_color_tuple(c):
 29      if not c:
 30          return 0, 0, 0
 31      return c.red_float, c.green_float, c.blue_float
 32  
 33  class HAL_Bar(gtk.DrawingArea, _HalWidgetBase):
 34      __gtype_name__ = 'HAL_Bar'
 35      __gsignals__ = dict([hal_pin_changed_signal])
 36      __gproperties__ = {
 37          'invert' : ( gobject.TYPE_BOOLEAN, 'Inverted', 'Invert min-max direction',
 38                      False, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 39          'show_limits' : ( gobject.TYPE_BOOLEAN, 'Show Limits', 'Display upper and lower limit text',
 40                      True, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 41          'min' : ( gobject.TYPE_FLOAT, 'Min', 'Minimum value',
 42                      -MAX_INT, MAX_INT, 0, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 43          'max'  : ( gobject.TYPE_FLOAT, 'Max', 'Maximum value',
 44                      -MAX_INT, MAX_INT, 100, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 45          'zero' : ( gobject.TYPE_FLOAT, 'Zero', 'Zero value',
 46                      -MAX_INT, MAX_INT, 0, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 47          'value' : ( gobject.TYPE_FLOAT, 'Value', 'Current bar value (for glade testing)',
 48                      -MAX_INT, MAX_INT, 0, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 49          'target_value' : ( gobject.TYPE_FLOAT, 'Target_Value', 'Target value (for glade testing)',
 50                      -MAX_INT, MAX_INT, 0, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 51          'target_width' : ( gobject.TYPE_FLOAT, 'Target Width', 'Target pixel width',
 52                      1, 5, 2, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 53          'z0_color' : ( gtk.gdk.Color.__gtype__, 'Zone 0 color', "Set color for first zone",
 54                          gobject.PARAM_READWRITE),
 55          'z1_color' : ( gtk.gdk.Color.__gtype__, 'Zone 1 color', "Set color for second zone",
 56                          gobject.PARAM_READWRITE),
 57          'z2_color' : ( gtk.gdk.Color.__gtype__, 'Zone 2 color', "Set color for third zone",
 58                          gobject.PARAM_READWRITE),
 59          'target_color' : ( gtk.gdk.Color.__gtype__, 'Target color', "Set color for target indicator",
 60                          gobject.PARAM_READWRITE),
 61          'z0_border' : ( gobject.TYPE_FLOAT, 'Zone 0 up limit', 'Up limit (fraction) of zone 0',
 62                      0, 1, 1, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 63          'z1_border' : ( gobject.TYPE_FLOAT, 'Zone 1 up limit', 'Up limit (fraction) of zone 1',
 64                      0, 1, 1, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 65          'bg_color' : ( gtk.gdk.Color.__gtype__, 'Background', "Choose background color",
 66                          gobject.PARAM_READWRITE),
 67          'force_width' : ( gobject.TYPE_INT, 'Forced width', 'Force bar width not dependent on widget size. -1 to disable',
 68                      -1, MAX_INT, -1, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 69          'force_height' : ( gobject.TYPE_INT, 'Forced height', 'Force bar height not dependent on widget size. -1 to disable',
 70                      -1, MAX_INT, -1, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 71          'text_template' : ( gobject.TYPE_STRING, 'Text template',
 72                  'Text template to display. Python formatting may be used for one variable',
 73                  "%s", gobject.PARAM_READWRITE|gobject.PARAM_CONSTRUCT),
 74          'shiny' : ( gobject.TYPE_BOOLEAN, 'Shiny', 'Makes the bar shiny',
 75                      False, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 76      }
 77      __gproperties = __gproperties__
 78      _size_request = (20, 20)
 79  
 80      def __init__(self):
 81          super(HAL_Bar, self).__init__()
 82  
 83          self.bg_color = gtk.gdk.Color('gray')
 84          self.z0_color = gtk.gdk.Color('green')
 85          self.z1_color = gtk.gdk.Color('yellow')
 86          self.z2_color = gtk.gdk.Color('red')
 87          self.target_color = gtk.gdk.Color('purple')
 88          self.target_width = 2
 89          self.force_width = self._size_request[0]
 90          self.force_height = self._size_request[1]
 91          self.set_size_request(*self._size_request)
 92  
 93          self.connect("expose-event", self.expose)
 94  
 95      def text_at(self, cr, text, x, y, xalign='center', yalign='center'):
 96          xbearing, ybearing, width, height, xadvance, yadvance = cr.text_extents(text)
 97          #print xbearing, ybearing, width, height, xadvance, yadvance
 98          if xalign == 'center':
 99              x = x - width/2
100          elif xalign == 'right':
101              x = x - width
102          if yalign == 'center':
103              y = y + height/2
104          elif yalign == 'top':
105              y = y + height
106          cr.move_to(x, y)
107          cr.show_text(text)
108  
109      def _expose_prepare(self, widget):
110          if self.flags() & gtk.PARENT_SENSITIVE:
111              alpha = 1
112          else:
113              alpha = 0.3
114  
115          w = self.allocation.width
116          h = self.allocation.height
117  
118          fw = self.force_width
119          fh = self.force_height
120  
121          aw = max(w, fw)
122          ah = max(h, fh)
123  
124          #self.set_size_request(aw, ah)
125  
126          if fw != -1: w = fw
127          if fh != -1: h = fh
128  
129          cr = widget.window.cairo_create()
130          def set_color(c):
131              return cr.set_source_rgba(c.red_float, c.green_float, c.blue_float, alpha)
132  
133          cr.set_line_width(2)
134          set_color(gtk.gdk.Color('black'))
135  
136          #print w, h, aw, ah, fw, fh
137          cr.translate((aw - w) / 2, (ah - h) / 2)
138          cr.rectangle(0, 0, w, h)
139          cr.clip_preserve()
140          cr.stroke()
141  
142          cr.translate(1, 1)
143          w, h = w - 2, h - 2
144  
145          cr.set_line_width(1)
146          set_color(self.bg_color)
147          cr.rectangle(0, 0, w, h)
148          cr.stroke_preserve()
149          cr.fill()
150          return cr, (w, h), set_color, alpha
151  
152      def _load_gradient(self, lg, alpha):
153          z0 = gdk_color_tuple(self.z0_color) + (alpha,)
154          z1 = gdk_color_tuple(self.z1_color) + (alpha,)
155          z2 = gdk_color_tuple(self.z2_color) + (alpha,)
156          delta = 0.025
157          z0b = self.z0_border
158          z1b = max(z0b, self.z1_border)
159  
160          lg.add_color_stop_rgba(0.00, *z0)
161          if z0b + delta > 1:
162              lg.add_color_stop_rgba(1.00, *z0)
163              return
164  
165          lg.add_color_stop_rgba(max(0, z0b - delta), *z0)
166          lg.add_color_stop_rgba(min(1, z0b + delta), *z1)
167          if z0b + delta > z1b:
168              lg.add_color_stop_rgba(1.00, *z1)
169              return
170  
171          lg.add_color_stop_rgba(max(0, z1b - delta), *z1)
172          lg.add_color_stop_rgba(min(1, z1b + delta), *z2)
173          lg.add_color_stop_rgba(1, *z2)
174  
175      def do_get_property(self, property):
176          name = property.name.replace('-', '_')
177          if name in self.__gproperties.keys():
178              return getattr(self, name)
179          else:
180              raise AttributeError('unknown property %s' % property.name)
181  
182      def do_set_property(self, property, value):
183          name = property.name.replace('-', '_')
184  
185          if name == 'text_template':
186              try:
187                  v = value % 0.0
188              except Exception, e:
189                  print "Invalid format string '%s': %s" % (value, e)
190                  return False
191  
192          if name in self.__gproperties.keys():
193              setattr(self, name, value)
194              self.queue_draw()
195          else:
196              raise AttributeError('unknown property %s' % property.name)
197  
198          if name in ['force_width', 'force_height']:
199              #print "Forcing size request %s" % name
200              self.set_size_request(self.force_width, self.force_height)
201  
202          self.queue_draw()
203          return True
204  
205      def set_value(self, value):
206          self.value = value
207          self.queue_draw()
208  
209      def set_target_value(self, value):
210          self.target_value = value
211          self.queue_draw()
212  
213      def get_value_diff(self, value):
214          value = max(self.min, min(value, self.max))
215          return (value - self.min) / (self.max - self.min)
216  
217      def _hal_init(self):
218          _HalWidgetBase._hal_init(self)
219          self.hal_pin = self.hal.newpin(self.hal_name, hal.HAL_FLOAT, hal.HAL_IN)
220          self.hal_pin.connect('value-changed', lambda p: self.set_value(p.value))
221          self.hal_pin.connect('value-changed', lambda s: self.emit('hal-pin-changed', s))
222  
223  class HAL_HBar(HAL_Bar):
224      __gtype_name__ = 'HAL_HBar'
225      _size_request = (-1, 20)
226  
227      def expose(self, widget, event):
228          cr, (w, h), set_color, alpha = self._expose_prepare(widget)
229  
230          # make bar
231          set_color(gtk.gdk.Color('black'))
232          cr.save()
233          zv = w * self.get_value_diff(self.zero)
234          wv = w * self.get_value_diff(self.value)
235          if not self.invert:
236              cr.rectangle(zv, 0, wv - zv, h)
237          else:
238              cr.rectangle(w - wv, 0, wv - zv, h)
239          cr.clip_preserve()
240          cr.stroke_preserve()
241          bi_flag = bool((self.min == (- self.max)) and self.value < 0)
242          if self.invert or bi_flag:
243              lg = cairo.LinearGradient(w, 0, 0, 0)
244          else:
245              lg = cairo.LinearGradient(0, 0, w, 0)
246          self._load_gradient(lg, alpha)
247          cr.set_source(lg)
248          cr.fill()
249          cr.restore()
250  
251          # now make it shiny
252          if self.shiny:
253              cr.rectangle(0, 0, w, h)
254              lg = cairo.LinearGradient(0, 0, 0, h)
255              lg.add_color_stop_rgba(0, 0, 0, 0, .5)
256              lg.add_color_stop_rgba(.16, 1, 1, 1, .25)
257              lg.add_color_stop_rgba(.33, 1, 1, 1, .75)
258              lg.add_color_stop_rgba(.66, 1, 1, 1, .25)
259              lg.add_color_stop_rgba(1, 0, 0, 0, .5)
260              cr.set_source(lg)
261              cr.fill()
262  
263          # make target line
264          if self.target_value > 0:
265              set_color(self.target_color)
266              if self.target_value > self.max:
267                  tvalue = self.max
268              else:
269                  tvalue = self.target_value
270              wv = w * self.get_value_diff(tvalue)
271              cr.set_line_width(self.target_width)
272              if not self.invert:
273                  cr.move_to(wv,0)
274                  cr.rel_line_to(0,h)
275              else:
276                  cr.move_to(w-wv,0)
277                  cr.rel_line_to(0,h)
278              cr.stroke()
279  
280          # write text
281          set_color(gtk.gdk.Color('black'))
282          tmpl = lambda s: self.text_template % s
283          if self.show_limits:
284              if not self.invert:
285                  self.text_at(cr, tmpl(self.min), 5, h/2, 'left')
286                  self.text_at(cr, tmpl(self.max), w-5, h/2, 'right')
287              else:
288                  self.text_at(cr, tmpl(self.max), 5, h/2, 'left')
289                  self.text_at(cr, tmpl(self.min), w-5, h/2, 'right')
290          self.text_at(cr, tmpl(self.value), w/2, h/2, 'center')
291  
292          return False
293  
294  class HAL_VBar(HAL_Bar):
295      __gtype_name__ = 'HAL_VBar'
296      _size_request = (25, -1)
297  
298      def expose(self, widget, event):
299          cr, (w, h), set_color, alpha = self._expose_prepare(widget)
300  
301          # make bar
302          cr.save()
303          set_color(gtk.gdk.Color('black'))
304          zv = h * self.get_value_diff(self.zero)
305          wv = h * self.get_value_diff(self.value)
306          if not self.invert:
307              cr.rectangle(0, h - wv, w, wv - zv)
308          else:
309              cr.rectangle(0, zv, w, wv - zv)
310          cr.clip_preserve()
311          cr.stroke_preserve()
312  
313          bi_flag = bool((self.min == (- self.max)) and self.value < 0)
314          if self.invert or bi_flag:
315              lg = cairo.LinearGradient(0, 0, 0, h)
316          else:
317              lg = cairo.LinearGradient(0, h, 0, 0)
318          self._load_gradient(lg, alpha)
319          cr.set_source(lg)
320          cr.fill()
321          cr.restore()
322          
323          # now make it shiny
324          if self.shiny:
325              cr.rectangle(0, 0, w, h)
326              lg = cairo.LinearGradient(0, 0, w, 0)
327              lg.add_color_stop_rgba(0, 0, 0, 0, .5)
328              lg.add_color_stop_rgba(.16, 1, 1, 1, .25)
329              lg.add_color_stop_rgba(.33, 1, 1, 1, .75)
330              lg.add_color_stop_rgba(.66, 1, 1, 1, .25)
331              lg.add_color_stop_rgba(1, 0, 0, 0, .5)
332              cr.set_source(lg)
333              cr.fill()
334  
335          # make target line
336          if self.target_value > 0:
337              set_color(self.target_color)
338              if self.target_value > self.max:
339                  tvalue = self.max
340              else:
341                  tvalue = self.target_value
342              wv = h * self.get_value_diff(tvalue)
343              cr.set_line_width(self.target_width)
344              if not self.invert:
345                  cr.move_to(0,h - wv)
346                  cr.rel_line_to(w,0)
347              else:
348                  #cr.rectangle(w - wv, 0, wv - zv, h)
349                  cr.move_to(0,zv + wv)
350                  cr.rel_line_to(w,0)
351              cr.stroke()
352  
353          # make text
354          set_color(gtk.gdk.Color('black'))
355          tmpl = lambda s: self.text_template % s
356          if self.show_limits:
357              if not self.invert:
358                  self.text_at(cr, tmpl(self.max), w/2, 5,  yalign='top')
359                  self.text_at(cr, tmpl(self.min), w/2, h-5, yalign='bottom')
360              else:
361                  self.text_at(cr, tmpl(self.min), w/2, 5,  yalign='top')
362                  self.text_at(cr, tmpl(self.max), w/2, h-5, yalign='bottom')
363          self.text_at(cr, tmpl(self.value), w/2, h/2)
364  
365          return False