# vim: sts=4 sw=4 et
# GladeVcp Widgets
#
# Copyright (c) 2010  Pavel Shramov <shramov@mexmat.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

import gi
gi.require_version("Gtk","3.0")
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GObject
import cairo
import math

# This creates the custom LED widget

if __name__ == "__main__":
    from hal_widgets import _HalWidgetBase, hal, hal_pin_changed_signal
else:
    from .hal_widgets import _HalWidgetBase, hal, hal_pin_changed_signal

MAX_INT = 0x7fffffff

def Gdk_color_tuple(c):
    if not c:
        return 0, 0, 0
    return c.red_float, c.green_float, c.blue_float

class HAL_Bar(Gtk.DrawingArea, _HalWidgetBase):
    __gtype_name__ = 'HAL_Bar'
    __gsignals__ = dict([hal_pin_changed_signal])
    __gproperties__ = {
        'invert' : ( GObject.TYPE_BOOLEAN, 'Inverted', 'Invert min-max direction',
                    False, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'show_limits' : ( GObject.TYPE_BOOLEAN, 'Show Limits', 'Display upper and lower limit text',
                    True, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'min' : ( GObject.TYPE_FLOAT, 'Min', 'Minimum value',
                    -MAX_INT, MAX_INT, 0, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'max'  : ( GObject.TYPE_FLOAT, 'Max', 'Maximum value',
                    -MAX_INT, MAX_INT, 100, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'zero' : ( GObject.TYPE_FLOAT, 'Zero', 'Zero value',
                    -MAX_INT, MAX_INT, 0, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'value' : ( GObject.TYPE_FLOAT, 'Value', 'Current bar value (for glade testing)',
                    -MAX_INT, MAX_INT, 0, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'target_value' : ( GObject.TYPE_FLOAT, 'Target_Value', 'Target value (for glade testing)',
                    -MAX_INT, MAX_INT, 0, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'target_width' : ( GObject.TYPE_FLOAT, 'Target Width', 'Target pixel width',
                    1, 5, 2, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'z0_color' : ( Gdk.Color.__gtype__, 'Zone 0 color', "Set color for first zone",
                        GObject.ParamFlags.READWRITE),
        'z1_color' : ( Gdk.Color.__gtype__, 'Zone 1 color', "Set color for second zone",
                        GObject.ParamFlags.READWRITE),
        'z2_color' : ( Gdk.Color.__gtype__, 'Zone 2 color', "Set color for third zone",
                        GObject.ParamFlags.READWRITE),
        'target_color' : ( Gdk.Color.__gtype__, 'Target color', "Set color for target indicator",
                        GObject.ParamFlags.READWRITE),
        'z0_border' : ( GObject.TYPE_FLOAT, 'Zone 0 up limit', 'Up limit (fraction) of zone 0',
                    0, 1, 1, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'z1_border' : ( GObject.TYPE_FLOAT, 'Zone 1 up limit', 'Up limit (fraction) of zone 1',
                    0, 1, 1, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'bg_color' : ( Gdk.Color.__gtype__, 'Background', "Choose background color",
                        GObject.ParamFlags.READWRITE),
        'force_width' : ( GObject.TYPE_INT, 'Forced width', 'Force bar width not dependent on widget size. -1 to disable',
                    -1, MAX_INT, -1, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'force_height' : ( GObject.TYPE_INT, 'Forced height', 'Force bar height not dependent on widget size. -1 to disable',
                    -1, MAX_INT, -1, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'text_template' : ( GObject.TYPE_STRING, 'Text template',
                'Text template to display. Python formatting may be used for one variable',
                "%s", GObject.ParamFlags.READWRITE|GObject.ParamFlags.CONSTRUCT),
        'shiny' : ( GObject.TYPE_BOOLEAN, 'Shiny', 'Makes the bar shiny',
                    False, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
    }
    __gproperties = __gproperties__
    _size_request = (20, 20)

    def __init__(self):
        super(HAL_Bar, self).__init__()

        self.bg_color = Gdk.Color.parse('gray')[1]
        self.z0_color = Gdk.Color.parse('green')[1]
        self.z1_color = Gdk.Color.parse('yellow')[1]
        self.z2_color = Gdk.Color.parse('red')[1]
        self.target_color = Gdk.Color.parse('purple')[1]
        self.target_width = 2
        self.force_width = self._size_request[0]
        self.force_height = self._size_request[1]
        self.set_size_request(*self._size_request)

        self.connect("draw", self.expose)

    def text_at(self, cr, text, x, y, xalign='center', yalign='center'):
        xbearing, ybearing, width, height, xadvance, yadvance = cr.text_extents(text)
        #print xbearing, ybearing, width, height, xadvance, yadvance
        if xalign == 'center':
            x = x - width/2
        elif xalign == 'right':
            x = x - width
        if yalign == 'center':
            y = y + height/2
        elif yalign == 'top':
            y = y + height
        cr.move_to(x, y)
        cr.show_text(text)

    def _expose_prepare(self, widget):
        if self.is_sensitive():
            alpha = 1
        else:
            alpha = 0.3

        w = self.get_allocated_width()
        h = self.get_allocated_height()

        fw = self.force_width
        fh = self.force_height

        aw = max(w, fw)
        ah = max(h, fh)

        #self.set_size_request(aw, ah)

        if fw != -1: w = fw
        if fh != -1: h = fh

        cr = widget.get_property('window').cairo_create()
        def set_color(c):
            return cr.set_source_rgba(c.red_float, c.green_float, c.blue_float, alpha)

        cr.set_line_width(2)
        set_color(Gdk.Color.parse('black')[1])

        #print w, h, aw, ah, fw, fh
        cr.translate((aw - w) / 2, (ah - h) / 2)
        cr.rectangle(0, 0, w, h)
        cr.clip_preserve()
        cr.stroke()

        cr.translate(1, 1)
        w, h = w - 2, h - 2

        cr.set_line_width(1)
        set_color(self.bg_color)
        cr.rectangle(0, 0, w, h)
        cr.stroke_preserve()
        cr.fill()
        return cr, (w, h), set_color, alpha

    def _load_gradient(self, lg, alpha):
        z0 = Gdk_color_tuple(self.z0_color) + (alpha,)
        z1 = Gdk_color_tuple(self.z1_color) + (alpha,)
        z2 = Gdk_color_tuple(self.z2_color) + (alpha,)
        delta = 0.025
        z0b = self.z0_border
        z1b = max(z0b, self.z1_border)

        lg.add_color_stop_rgba(0.00, *z0)
        if z0b + delta > 1:
            lg.add_color_stop_rgba(1.00, *z0)
            return

        lg.add_color_stop_rgba(max(0, z0b - delta), *z0)
        lg.add_color_stop_rgba(min(1, z0b + delta), *z1)
        if z0b + delta > z1b:
            lg.add_color_stop_rgba(1.00, *z1)
            return

        lg.add_color_stop_rgba(max(0, z1b - delta), *z1)
        lg.add_color_stop_rgba(min(1, z1b + delta), *z2)
        lg.add_color_stop_rgba(1, *z2)

    def do_get_property(self, property):
        name = property.name.replace('-', '_')
        if name in list(self.__gproperties.keys()):
            return getattr(self, name)
        else:
            raise AttributeError('unknown property %s' % property.name)

    def do_set_property(self, property, value):
        name = property.name.replace('-', '_')

        if name == 'text_template':
            try:
                v = value % 0.0
            except Exception as e:
                print("Invalid format string '%s': %s" % (value, e))
                return False

        if name in list(self.__gproperties.keys()):
            setattr(self, name, value)
            self.queue_draw()
        else:
            raise AttributeError('unknown property %s' % property.name)

        if name in ['force_width', 'force_height']:
            #print "Forcing size request %s" % name
            self.set_size_request(self.force_width, self.force_height)

        self.queue_draw()
        return True

    def set_value(self, value):
        self.value = value
        self.queue_draw()

    def set_target_value(self, value):
        self.target_value = value
        self.queue_draw()

    def get_value_diff(self, value):
        value = max(self.min, min(value, self.max))
        return (value - self.min) / (self.max - self.min)

    def _hal_init(self):
        _HalWidgetBase._hal_init(self)
        self.hal_pin = self.hal.newpin(self.hal_name, hal.HAL_FLOAT, hal.HAL_IN)
        self.hal_pin.connect('value-changed', lambda p: self.set_value(p.value))
        self.hal_pin.connect('value-changed', lambda s: self.emit('hal-pin-changed', s))

class HAL_HBar(HAL_Bar):
    __gtype_name__ = 'HAL_HBar'
    _size_request = (-1, 20)

    def expose(self, widget, event):
        cr, (w, h), set_color, alpha = self._expose_prepare(widget)

        # make bar
        set_color(Gdk.Color.parse('black')[1])
        cr.save()
        zv = w * self.get_value_diff(self.zero)
        wv = w * self.get_value_diff(self.value)
        if not self.invert:
            cr.rectangle(zv, 0, wv - zv, h)
        else:
            cr.rectangle(w - wv, 0, wv - zv, h)
        cr.clip_preserve()
        cr.stroke_preserve()
        bi_flag = bool((self.min == (- self.max)) and self.value < 0)
        if self.invert or bi_flag:
            lg = cairo.LinearGradient(w, 0, 0, 0)
        else:
            lg = cairo.LinearGradient(0, 0, w, 0)
        self._load_gradient(lg, alpha)
        cr.set_source(lg)
        cr.fill()
        cr.restore()

        # now make it shiny
        if self.shiny:
            cr.rectangle(0, 0, w, h)
            lg = cairo.LinearGradient(0, 0, 0, h)
            lg.add_color_stop_rgba(0, 0, 0, 0, .5)
            lg.add_color_stop_rgba(.16, 1, 1, 1, .25)
            lg.add_color_stop_rgba(.33, 1, 1, 1, .75)
            lg.add_color_stop_rgba(.66, 1, 1, 1, .25)
            lg.add_color_stop_rgba(1, 0, 0, 0, .5)
            cr.set_source(lg)
            cr.fill()

        # make target line
        if self.target_value > 0:
            set_color(self.target_color)
            if self.target_value > self.max:
                tvalue = self.max
            else:
                tvalue = self.target_value
            wv = w * self.get_value_diff(tvalue)
            cr.set_line_width(self.target_width)
            if not self.invert:
                cr.move_to(wv,0)
                cr.rel_line_to(0,h)
            else:
                cr.move_to(w-wv,0)
                cr.rel_line_to(0,h)
            cr.stroke()

        # write text
        set_color(Gdk.Color.parse('black')[1])
        tmpl = lambda s: self.text_template % s
        if self.show_limits:
            if not self.invert:
                self.text_at(cr, tmpl(self.min), 5, h/2, 'left')
                self.text_at(cr, tmpl(self.max), w-5, h/2, 'right')
            else:
                self.text_at(cr, tmpl(self.max), 5, h/2, 'left')
                self.text_at(cr, tmpl(self.min), w-5, h/2, 'right')
        self.text_at(cr, tmpl(self.value), w/2, h/2, 'center')

        return False

class HAL_VBar(HAL_Bar):
    __gtype_name__ = 'HAL_VBar'
    _size_request = (25, -1)

    def expose(self, widget, event):
        cr, (w, h), set_color, alpha = self._expose_prepare(widget)

        # make bar
        cr.save()
        set_color(Gdk.Color.parse('black')[1])
        zv = h * self.get_value_diff(self.zero)
        wv = h * self.get_value_diff(self.value)
        if not self.invert:
            cr.rectangle(0, h - wv, w, wv - zv)
        else:
            cr.rectangle(0, zv, w, wv - zv)
        cr.clip_preserve()
        cr.stroke_preserve()

        bi_flag = bool((self.min == (- self.max)) and self.value < 0)
        if self.invert or bi_flag:
            lg = cairo.LinearGradient(0, 0, 0, h)
        else:
            lg = cairo.LinearGradient(0, h, 0, 0)
        self._load_gradient(lg, alpha)
        cr.set_source(lg)
        cr.fill()
        cr.restore()
        
        # now make it shiny
        if self.shiny:
            cr.rectangle(0, 0, w, h)
            lg = cairo.LinearGradient(0, 0, w, 0)
            lg.add_color_stop_rgba(0, 0, 0, 0, .5)
            lg.add_color_stop_rgba(.16, 1, 1, 1, .25)
            lg.add_color_stop_rgba(.33, 1, 1, 1, .75)
            lg.add_color_stop_rgba(.66, 1, 1, 1, .25)
            lg.add_color_stop_rgba(1, 0, 0, 0, .5)
            cr.set_source(lg)
            cr.fill()

        # make target line
        if self.target_value > 0:
            set_color(self.target_color)
            if self.target_value > self.max:
                tvalue = self.max
            else:
                tvalue = self.target_value
            wv = h * self.get_value_diff(tvalue)
            cr.set_line_width(self.target_width)
            if not self.invert:
                cr.move_to(0,h - wv)
                cr.rel_line_to(w,0)
            else:
                #cr.rectangle(w - wv, 0, wv - zv, h)
                cr.move_to(0,zv + wv)
                cr.rel_line_to(w,0)
            cr.stroke()

        # make text
        set_color(Gdk.Color.parse('black')[1])
        tmpl = lambda s: self.text_template % s
        if self.show_limits:
            if not self.invert:
                self.text_at(cr, tmpl(self.max), w/2, 5,  yalign='top')
                self.text_at(cr, tmpl(self.min), w/2, h-5, yalign='bottom')
            else:
                self.text_at(cr, tmpl(self.min), w/2, 5,  yalign='top')
                self.text_at(cr, tmpl(self.max), w/2, h-5, yalign='bottom')
        self.text_at(cr, tmpl(self.value), w/2, h/2)

        return False
