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