hal_lightbutton.py
1 # GladeVcp HAL lighted button 2 # 3 # Copyright (c) 2015 Moses McKnight <moses@texband.net> 4 # 5 # This program is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU General Public License as published by 7 # the Free Software Foundation, either version 2 of the License, or 8 # (at your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU General Public License for more details. 14 15 import gtk 16 import gobject 17 import cairo 18 import pango 19 import math 20 import gtk.glade 21 22 # This creates the custom lighted button widget 23 # A lighted button has a HAL_OUT or HAL_IO pin for the button. This pin indicates if the button is pressed or toggled or not. 24 # If it is an HAL_IO pin, the pin is set True when the button is clicked and must be set False externally. 25 # This gives an external component a chance to "see" the pin state. 26 # If the pin is a HAL_OUT pin, the button acts as a toggle button and the pin state is toggled with each click. 27 # A HAL pin to enable/disable the button can be created. One use is to connect it to halui.machine.is-on to enable a button 28 # when the machine is on and disable it when the machine is off. 29 # There is also a HAL IN pin for the light, which operates independently of the button state. 30 # 31 # If has_hal_pins is true, the button creates several pins with the base name being the name of the widget 32 # widgetname-button OUT or IO - indicates button state 33 # widgetname-button-not OUT - only created if 'button_halio' is NOT true, inverse of widgetname-button 34 # widgetname-enable IN - only created if 'create_enable_pin' is true, allows button to be enabled and disabled by a HAL signal 35 # widgetname-light IN - controls light on/off 36 # 37 # If 'has_hal_pins' is false, no hal pins are created and the button can be completely controlled in code. It emits the 'clicked' 38 # signal when pressed. You can also leave 'has_hal_pins' true, and connect the -light pin to a HAL signal while handling the 39 # button 'clicked' signal in code. For this use you want to make sure 'button_halio_pin' is false or the button will remain 'active' 40 # after the first click, and calls to get_active() will always return True. 41 42 from hal_widgets import _HalWidgetBase, hal, hal_pin_changed_signal 43 44 clicked_signal = ('clicked', (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,))) 45 46 class HAL_LightButton(gtk.DrawingArea, _HalWidgetBase): 47 __gtype_name__ = 'HAL_LightButton' 48 __gsignals__ = dict([clicked_signal]) 49 __gproperties__ = { 50 'has_hal_pins' : ( gobject.TYPE_BOOLEAN, 'Has HAL pins', 'Set false if this button will not be controlled using HAL', 51 True, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 52 'is_momentary' : ( gobject.TYPE_BOOLEAN, 'Is momentary', 53 'Set True if this button will be momentary rather then toggle', 54 False, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 55 'button_halio_pin' : ( gobject.TYPE_BOOLEAN, 'Button pin is HAL_IO', 'If HAL_IO, pin is set true on button press; if HAL_OUT, pin state is toggled by button press', 56 True, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 57 'create_enable_pin' : ( gobject.TYPE_BOOLEAN, 'Create enable pin', 'Creates an enable pin which enables the button if True, and disables when False', 58 False, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 59 'dual_color' : ( gobject.TYPE_BOOLEAN, 'Dual Color Light', 'If true, light is on always, but changes color for OFF state', 60 False, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 61 #'use_bitmaps' : ( gobject.TYPE_BOOLEAN, 'Use Bitmaps', 'If true, you must select bitmaps for each button state', 62 # False, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 63 'light_is_on' : ( gobject.TYPE_BOOLEAN, 'Light is on', 'Turns light on - for testing in glade', 64 False, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 65 'light_on_color' : ( gtk.gdk.Color.__gtype__, 'Light ON color', "Set color for light ON state", 66 gobject.PARAM_READWRITE), 67 'light_off_color' : ( gtk.gdk.Color.__gtype__, 'Light OFF color', "Set color for light OFF state", 68 gobject.PARAM_READWRITE), 69 'border_width' : ( gobject.TYPE_INT, 'Border width', 'Number of pixels extra border around label', 70 0, 50, 6, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 71 'corner_radius' : ( gobject.TYPE_INT, 'Corner radius', 'Radius of the button corners', 72 1, 20, 4, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 73 'button_text' : ( gobject.TYPE_STRING, 'Button default/off text', 'Text shown when light is off, or all the time if \"Button ON text\" is blank', 74 "Button", gobject.PARAM_READWRITE|gobject.PARAM_CONSTRUCT), 75 'button_on_text' : ( gobject.TYPE_STRING, 'Button ON text', 'If not blank, this text will be shown on the button when light is on', 76 "", gobject.PARAM_READWRITE|gobject.PARAM_CONSTRUCT), 77 'font_on_color' : ( gtk.gdk.Color.__gtype__, 'Font ON color', "Set color for button text when light is ON", 78 gobject.PARAM_READWRITE), 79 'font_off_color' : ( gtk.gdk.Color.__gtype__, 'Font OFF color', "Set color for button text when light is OFF", 80 gobject.PARAM_READWRITE), 81 'font_face' : ( gobject.TYPE_STRING, 'Font name', 'Button text', 82 "Sans", gobject.PARAM_READWRITE|gobject.PARAM_CONSTRUCT), 83 'font_bold' : ( gobject.TYPE_BOOLEAN, 'Bold font', 'Set button font to bold', 84 False, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 85 'font_size' : ( gobject.TYPE_INT, 'Font size', 'Number of pixels extra border around label', 86 0, 100, 10, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 87 } 88 __gproperties = __gproperties__ 89 _size_request = (35, 35) 90 91 def __init__(self): 92 super(HAL_LightButton, self).__init__() 93 94 self.has_hal_pins = True 95 96 # When True, button_pin is HAL_IO. 97 # Pressing the button sets the pin True, and it must be reset to False externally 98 # When False, button_pin is HAL_OUT, and it's state is toggled by a button press, 99 # which means this button becomes a togglebutton. 100 self.button_halio_pin = True 101 102 self.dual_color = False 103 self.use_bitmaps = False #this feature is not implemented yet 104 self.light_on_color = gtk.gdk.Color('green') 105 self.light_off_color = gtk.gdk.Color('gray') 106 self.border_width = 6 107 self.corner_radius = 4 108 self.button_text = 'Button' 109 self.button_on_text = '' 110 self.font_face = 'Sans' 111 self.font_bold = False 112 self.font_size = 10 113 self.font_on_color = gtk.gdk.Color('black') 114 self.font_off_color = gtk.gdk.Color('black') 115 self.create_enable_pin = False 116 117 self.active = False 118 self.light_is_on = False 119 self.mouseover = False 120 121 self.default_pangolayout = self.create_pango_layout(self.button_text) 122 self.on_pangolayout = self.create_pango_layout(self.button_on_text) 123 124 self.set_size_request(*self._size_request) 125 self.set_events(gtk.gdk.EXPOSURE_MASK 126 | gtk.gdk.ENTER_NOTIFY_MASK 127 | gtk.gdk.LEAVE_NOTIFY_MASK 128 | gtk.gdk.BUTTON_PRESS_MASK 129 | gtk.gdk.BUTTON_RELEASE_MASK 130 | gtk.gdk.POINTER_MOTION_MASK 131 | gtk.gdk.POINTER_MOTION_HINT_MASK) 132 133 self.connect("expose-event", self.expose) 134 #self.connect('button-press-event', self.pressed) 135 #self.connect('button-release-event', self.released) 136 #self.connect('state-changed', self._on_state_changed) 137 138 def do_enter_notify_event(self, event): 139 self.mouseover = True 140 self.queue_draw() 141 def do_leave_notify_event(self, event): 142 self.mouseover = False 143 self.queue_draw() 144 145 def do_button_press_event(self, event): 146 if event.button == 1: 147 if self.is_momentary: 148 active = True 149 else: 150 active = not self.active 151 152 if (self.has_hal_pins): 153 if (self.button_halio_pin): 154 self.set_active(True) 155 try: 156 self.button_pin.set(True) 157 except: 158 pass 159 else: 160 self.set_active(active) 161 try: 162 self.button_pin.set(active) 163 self.button_pin_not.set(not active) 164 except: 165 pass 166 167 else: 168 self.set_active(active) 169 170 self.emit("clicked", self) 171 return True 172 173 def do_button_release_event(self, event): 174 if event.button == 1: 175 if not self.is_momentary: 176 return True 177 if (self.has_hal_pins): 178 self.set_active(False) 179 try: 180 self.button_pin.set(False) 181 self.button_pin_not.set(True) 182 except: 183 pass 184 185 else: 186 self.set_active(False) 187 188 self.emit("clicked", self) 189 return True 190 191 192 def expose(self, widget, event): 193 if (self.flags() & gtk.PARENT_SENSITIVE) and (self.flags() & gtk.SENSITIVE): 194 alpha = 1 195 else: 196 alpha = 0.3 197 198 w = self.allocation.width 199 h = self.allocation.height 200 201 cr = widget.window.cairo_create() 202 def set_color(c): 203 return cr.set_source_rgba(c.red_float, c.green_float, c.blue_float, alpha) 204 205 if (self.use_bitmaps == False): 206 linewidth = 4 207 x = y = linewidth/2 208 w2 = w - linewidth 209 h2 = h - linewidth 210 x_center = x + w2/2 211 y_center = y + h2/2 212 degrees = math.pi / 180.0 213 214 cr.new_sub_path() 215 cr.arc(x + w2 - self.corner_radius, y + self.corner_radius, self.corner_radius, -90 * degrees, 0 * degrees) 216 cr.arc(x + w2 - self.corner_radius, y + h2 - self.corner_radius, self.corner_radius, 0 * degrees, 90 * degrees) 217 cr.arc(x + self.corner_radius, y + h2 - self.corner_radius, self.corner_radius, 90 * degrees, 180 * degrees) 218 cr.arc(x + self.corner_radius, y + self.corner_radius, self.corner_radius, 180 * degrees, 270 * degrees) 219 cr.close_path() 220 221 if (self.light_is_on): 222 color = self.light_on_color 223 if self.mouseover: 224 color = gtk.gdk.color_from_hsv(color.hue, color.saturation * .5, color.value * 1.5) 225 color2 = gtk.gdk.color_from_hsv(color.hue, color.saturation * .25, color.value) 226 linecolor = gtk.gdk.color_from_hsv(color.hue, color.saturation, color.value * .95) 227 else: 228 color = self.light_off_color 229 if self.mouseover: 230 color = gtk.gdk.color_from_hsv(color.hue, color.saturation * .5, color.value * 1.5) 231 if (self.dual_color): 232 color2 = gtk.gdk.color_from_hsv(color.hue, color.saturation * .25, color.value) 233 linecolor = gtk.gdk.color_from_hsv(color.hue, color.saturation, color.value * .95) 234 else: 235 color1 = gtk.gdk.color_from_hsv(color.hue, color.saturation * .50, color.value * 2) 236 color2 = gtk.gdk.color_from_hsv(color.hue, color.saturation, color.value * .50) 237 linecolor = gtk.gdk.color_from_hsv(color.hue, color.saturation * .50, color.value * .75) 238 239 cr.set_line_width(linewidth) 240 set_color(linecolor) 241 cr.stroke_preserve() 242 243 if (self.light_is_on == True) or (self.dual_color == True): 244 gradient_radius = (w2 + h2) 245 g1 = cairo.RadialGradient(x_center, y_center, 0, x_center, y_center, gradient_radius) 246 g1.add_color_stop_rgb(0.0, color2.red_float, color2.green_float, color2.blue_float) 247 g1.add_color_stop_rgb(1.0, color.red_float, color.green_float, color.blue_float) 248 else: 249 g1 = cairo.LinearGradient(x, y, x, y + h2) 250 g1.add_color_stop_rgb(0.0, color1.red_float, color1.green_float, color1.blue_float) 251 g1.add_color_stop_rgb(0.0, color.red_float, color.green_float, color.blue_float) 252 g1.add_color_stop_rgb(1.0, color2.red_float, color2.green_float, color2.blue_float) 253 cr.set_source(g1) 254 cr.fill() 255 256 #Using bitmaps is not implemented. Bitmaps should not be scaled unless you figure out a way to make them scale nicely 257 #The main reason to use bitmaps would be if someone wants a different look from the default one 258 #The code below is the basic way to do it. update_widget_size() should use the button size instead of text size 259 #and there should be properties to set image filenames for each state (light on, light off, mouseover, etc) 260 else: 261 cr.save() 262 image = cairo.ImageSurface.create_from_png('resources/k_green.png') 263 img_w = image.get_width() 264 img_h = image.get_height() 265 print float(w)/img_w, float(h)/img_h 266 cr.set_source_surface(image, 0, 0) 267 cr.paint() 268 cr.restore() 269 270 # write text 271 _layout = self.default_pangolayout 272 if (self.light_is_on): 273 set_color(self.font_on_color) 274 if (not self.button_on_text == ""): 275 _layout = self.on_pangolayout 276 else: 277 set_color(self.font_off_color) 278 279 fontw, fonth = _layout.get_pixel_size() 280 cr.move_to((w - fontw)/2, (h - fonth)/2) 281 cr.update_layout(_layout) 282 cr.show_layout(_layout) 283 return False 284 285 def update_font(self): 286 if self.font_bold == True: 287 fontweight = "bold" 288 else: 289 fontweight = "" 290 self.default_pangolayout.set_font_description(pango.FontDescription(self.font_face + ' ' + fontweight + ' ' + str(self.font_size))) 291 self.on_pangolayout.set_font_description(pango.FontDescription(self.font_face + ' ' + fontweight + ' ' + str(self.font_size))) 292 self.update_widget_size() 293 294 def update_widget_size(self): 295 w1, h1 = self.default_pangolayout.get_pixel_size() 296 w2, h2 = self.on_pangolayout.get_pixel_size() 297 width = max(w1 + self.border_width*2, w2 + self.border_width*2) 298 height = max(h1 + self.border_width*2, h2 + self.border_width*2) 299 self.set_size_request(int(width), int(height)) 300 301 # These set_* functions are called by the hal pin callbacks. 302 # Set self.has_hal_pins = False if you want to control state with these functions directly 303 #************************************************************** 304 305 def set_active(self, active): 306 self.active = active 307 self.queue_draw() 308 def get_active(self): 309 return self.active 310 311 def set_light_on(self, state): 312 self.light_is_on = state 313 self.queue_draw() 314 def get_light_on(self): 315 return self.light_is_on 316 317 def set_text(self, text): 318 self.button_text = text 319 self.default_pangolayout.set_text(text) 320 self.update_widget_size() 321 322 def set_on_text(self, text): 323 self.button_on_text = text 324 self.on_pangolayout.set_text(text) 325 self.update_widget_size() 326 327 #************************************************************** 328 329 def do_get_property(self, property): 330 name = property.name.replace('-', '_') 331 if name in self.__gproperties.keys(): 332 return getattr(self, name) 333 else: 334 raise AttributeError('unknown property %s' % property.name) 335 336 def do_set_property(self, property, value): 337 name = property.name.replace('-', '_') 338 if name == 'button_text': 339 self.set_text(value) 340 elif name == 'button_on_text': 341 self.set_on_text(value) 342 elif name in self.__gproperties.keys(): 343 setattr(self, name, value) 344 if name in ['font_face', 'font_bold', 'font_size']: 345 self.update_font() 346 if name == 'border_width': 347 self.update_widget_size() 348 self.queue_draw() 349 else: 350 raise AttributeError('unknown property %s' % property.name) 351 return True 352 353 def _hal_init(self): 354 if (self.has_hal_pins): 355 _HalWidgetBase._hal_init(self) 356 357 self.set_active(False) 358 if (self.button_halio_pin): 359 self.button_pin = self.hal.newpin(self.hal_name+'-button', hal.HAL_BIT, hal.HAL_IO) 360 self.button_pin.connect('value-changed', self.button_pin_update) 361 else: 362 self.button_pin = self.hal.newpin(self.hal_name+'-button', hal.HAL_BIT, hal.HAL_OUT) 363 self.button_pin_not = self.hal.newpin(self.hal_name+'-button-not', hal.HAL_BIT, hal.HAL_OUT) 364 if (self.create_enable_pin): 365 self.enable_pin = self.hal.newpin(self.hal_name+'-enable', hal.HAL_BIT, hal.HAL_IN) 366 self.enable_pin.connect('value-changed', self.enable_pin_update) 367 368 self.light_pin = self.hal.newpin(self.hal_name+'-light', hal.HAL_BIT, hal.HAL_IN) 369 self.light_pin.connect('value-changed', self.light_pin_update) 370 371 def button_pin_update(self, hal_pin, data=None): 372 self.set_active(bool(self.button_pin.get())) 373 def enable_pin_update(self, hal_pin, data=None): 374 self.set_sensitive(hal_pin.get()) 375 def light_pin_update(self, hal_pin, data=None): 376 self.set_light_on(bool(self.light_pin.get())) 377 378 gobject.type_register(HAL_LightButton) 379