from direct.showbase.TkGlobal import *
import Valuator
import Floater
import Slider
import string
import tkColorChooser
import types

class VectorEntry(Pmw.MegaWidget):
    def __init__(self, parent = None, **kw):

        # Default vector size
        DEFAULT_DIM = 3
        # Default value depends on *actual* vector size, test for user input
        DEFAULT_VALUE = [0.0] * kw.get('dim', DEFAULT_DIM)
        DEFAULT_LABELS = map(lambda x: 'v[%d]' % x,
                             range(kw.get('dim', DEFAULT_DIM)))

        # Process options
        INITOPT = Pmw.INITOPT
        optiondefs = (
            ('dim',                 DEFAULT_DIM,    INITOPT),
            ('value',        DEFAULT_VALUE,  INITOPT),
            ('resetValue',          DEFAULT_VALUE,  None),
            ('label_width',         12,             None),
            ('labelIpadx',          2,              None),
            ('command',             None,           None),
            ('entryWidth',          8,              self._updateEntryWidth),
            ('relief',              GROOVE,         self._updateRelief),
            ('bd',                  2,              self._updateBorderWidth),
            ('text',                'Vector:',      self._updateText),
            ('min',                 None,           self._updateValidate),
            ('max',                 None,           self._updateValidate),
            ('numDigits',           2,              self._setSigDigits),
            ('type',                'floater',      None),
            ('state',               'normal',       self._setState),
            )
        self.defineoptions(kw, optiondefs)

        # Initialize superclass
        Pmw.MegaWidget.__init__(self, parent)

        # Initialize value
        # Make sure its a list (and as a byproduct, make a distinct copy)
        self._value = list(self['value'])
        self['resetValue'] = self['value']
        self._floaters = None
        self.entryFormat = '%.2f'

        # Get a handle on the parent container        
        interior = self.interior()

        # This does double duty as a menu button
        self._label = self.createcomponent('label', (), None,
                                           Menubutton, (interior,),
                                           text = self['text'],
                                           activebackground = '#909090')
        self.menu = self._label['menu'] = Menu(self._label)
        self.menu.add_command(label = 'Reset', command = self.reset)
        self.menu.add_command(label = 'Popup sliders', command = self.popupSliders)
        self._label.pack(side = LEFT, fill = X, ipadx = self['labelIpadx'])

        self.variableList = []
        self.entryList = []
        for index in range(self['dim']):
            var = StringVar()
            self.variableList.append(var)
            # To set the configuration of all entrys in a vector use:
            # ve.configure(Entry_XXX = YYY)
            # To configure an individual entryfield's entry use:
            # ve.configure(entry0_XXX = YYY)
            entry = self.createcomponent(
                'entryField%d' % index,
                (('entry%d' % index,
                  'entryField%d_entry' % index),),
                'Entry',
                Pmw.EntryField, (interior,),
                entry_justify = RIGHT,
                entry_textvariable = var,
                command = lambda s = self, i = index: s._entryUpdateAt(i))
            entry.pack(side = LEFT, expand = 1, fill = X)
            self.entryList.append(entry)

        # To configure the floaterGroup use:
        # ve.configure(floaterGroup_XXX = YYY)
        # ve.configure(fGroup_XXX = YYY) or
        # To set the configuration all floaters in a group use:
        # ve.configure(Valuator_XXX = YYY)
        # To configure an individual floater in a group use:
        # ve.configure(floaterGroup_floater0_XXX = YYY) or
        # ve.configure(fGroup_floater0_XXX = YYY)
        self._floaters = self.createcomponent(
            'floaterGroup',
            (('fGroup', 'floaterGroup'),
             ('valuator', 'floaterGroup_valuator'),), None,
            Valuator.ValuatorGroupPanel, (self.interior(),),
            dim = self['dim'],
            #title = self['text'],
            type = self['type'],
            command = self.set)
        # Note: This means the 'X' on the menu bar doesn't really destroy
        # the panel, just withdraws it.  This is to avoid problems which occur
        # if the user kills the floaterGroup and then tries to pop it open again
        self._floaters.userdeletefunc(self._floaters.withdraw)
        self._floaters.withdraw()
                                             

        # Make sure entries are updated
        self.set(self['value'])

        # Record entry color
        self.entryBackground = self.cget('Entry_entry_background')
        
        # Make sure input variables processed 
        self.initialiseoptions(VectorEntry)

    def menu(self):
        return self.menu

    def label(self):
        return self._label

    def entry(self, index):
        return self.entryList[index]

    def entryList(self):
        return self.entryList

    def floaters(self):
        return self._floaters

    def _clearFloaters(self):
        self._floaters.withdraw()
        
    def _updateText(self):
        self._label['text'] = self['text']

    def _updateRelief(self):
        self.interior()['relief'] = self['relief']

    def _updateBorderWidth(self):
        self.interior()['bd'] = self['bd']        

    def _updateEntryWidth(self):
        self['Entry_entry_width'] = self['entryWidth']

    def _setSigDigits(self):
        sd = self['numDigits']
        self.entryFormat = '%.' + '%d' % sd + 'f'
        self.configure(valuator_numDigits = sd)
        # And refresh value to reflect change
        for index in range(self['dim']):
            self._refreshEntry(index)

    def _updateValidate(self):
        # Update entry field to respect new limits
        self.configure(Entry_validate = {
            'validator' : 'real',
            'min' : self['min'],
            'max' : self['max'],
            'minstrict' : 0,
            'maxstrict' : 0})
        # Reflect changes in floaters
        self.configure(valuator_min = self['min'],
                       valuator_max = self['max'])            

    def get(self):
        return self._value

    def getAt(self,index):
        return self._value[index]
                                                                                      
    def set(self, value, fCommand = 1):
        if type(value) in (types.FloatType, types.IntType, types.LongType):
            value = [value] * self['dim']
        for i in range(self['dim']):
            self._value[i] = value[i]
            self.variableList[i].set(self.entryFormat % value[i])
        self.action(fCommand)

    def setAt(self, index, value, fCommand = 1):
        self.variableList[index].set(self.entryFormat % value)
        self._value[index] = value
        self.action(fCommand)
        
    def _entryUpdateAt(self, index):
        entryVar = self.variableList[index]
        # Did we get a valid float?
        try: 
            newVal = string.atof(entryVar.get())
        except ValueError:
            return
        
        # Clamp value
        if self['min'] is not None:
            if newVal < self['min']:
                newVal = self['min']
        if self['max'] is not None:
            if newVal > self['max']:
                newVal = self['max']

        # Update vector's value
        self._value[index] = newVal

        # refresh entry to reflect formatted value
        self._refreshEntry(index)

        # Update the floaters and call the command
        self.action()

    def _refreshEntry(self,index):
        self.variableList[index].set( self.entryFormat % self._value[index] )
        self.entryList[index].checkentry()

    def _refreshFloaters(self):
        if self._floaters:
            self._floaters.set(self._value, 0)
        
    def action(self, fCommand = 1):
        self._refreshFloaters()
        if fCommand and (self['command'] != None):
            self['command'](self._value)        
        
    def reset(self):
        self.set(self['resetValue'])

    def addMenuItem(self, label = '', command = None):
        self.menu.add_command(label = label, command = command)

    def popupSliders(self):
        self._floaters.set(self.get()[:])
        self._floaters.show()

    def _setState(self):
        if self['state'] == 'disabled':
            # Disable entry
            self.configure(Entry_entry_state = 'disabled')
            self.configure(Entry_entry_background = '#C0C0C0')
            # Disable floater Group scale
            self.component('fGroup').configure(
                valuator_state = 'disabled')
            # Disable floater group entry
            self.component('fGroup').configure(
                valuator_entry_state = 'disabled')
            self.component('fGroup').configure(
                valuator_entry_background = '#C0C0C0')
        else:
            # Disable entry
            self.configure(Entry_entry_state = 'normal')
            self.configure(Entry_entry_background = self.entryBackground)
            # Disable floater Group scale
            self.component('fGroup').configure(
                valuator_state = 'normal')
            # Disable floater group entry
            self.component('fGroup').configure(
                valuator_entry_state = 'normal')
            self.component('fGroup').configure(
                valuator_entry_background = self.entryBackground)

class Vector2Entry(VectorEntry):
    def __init__(self, parent = None, **kw):
        # Initialize options for the class
        optiondefs = (
            ('dim',    2,       Pmw.INITOPT),
            ('fGroup_labels',   ('X','Y','Z'),  None),
            )
        self.defineoptions(kw, optiondefs)
        # Initialize the superclass, make sure dim makes it to superclass
        VectorEntry.__init__(self, parent, dim = self['dim'])
        # Needed because this method checks if self.__class__ is myClass
        # where myClass is the argument passed into inialiseoptions
        self.initialiseoptions(Vector2Entry)

class Vector3Entry(VectorEntry):
    def __init__(self, parent = None, **kw):
        # Initialize options for the class
        optiondefs = (
            ('dim',    3,       Pmw.INITOPT),
            ('fGroup_labels',   ('X','Y','Z'),  None),
            )
        self.defineoptions(kw, optiondefs)
        # Initialize the superclass, make sure dim makes it to superclass
        VectorEntry.__init__(self, parent, dim = self['dim'])
        # Needed because this method checks if self.__class__ is myClass
        # where myClass is the argument passed into inialiseoptions
        self.initialiseoptions(Vector3Entry)

class Vector4Entry(VectorEntry):
    def __init__(self, parent = None, **kw):
        # Initialize options for the class
        optiondefs = (
            ('dim',     4,      Pmw.INITOPT),
            ('fGroup_labels',   ('X','Y','Z','W'),  None),
            )
        self.defineoptions(kw, optiondefs)
        # Initialize the superclass, make sure dim makes it to superclass
        VectorEntry.__init__(self, parent, dim = self['dim'])
        # Needed because this method checks if self.__class__ is myClass
        # where myClass is the argument passed into inialiseoptions
        self.initialiseoptions(Vector4Entry)

class ColorEntry(VectorEntry):
    def __init__(self, parent = None, **kw):
        # Initialize options for the class (overriding some superclass options)
        optiondefs = (
            ('dim',                     4,                  Pmw.INITOPT),
            ('type',                    'slider',           Pmw.INITOPT),
            ('fGroup_labels',           ('R','G','B','A'),  None),
            ('min',                     0.0,                None),
            ('max',                     255.0,              None),
            ('nuDigits',                0,                  None),
            ('valuator_resolution',     1.0,                None),
            )
        self.defineoptions(kw, optiondefs)

        # Initialize the superclass, make sure dim makes it to superclass
        VectorEntry.__init__(self, parent, dim = self['dim'])
        # Add menu item to popup color picker
        self.addMenuItem(
            'Popup color picker',
            command = lambda s = self: s.popupColorPicker())
        # Needed because this method checks if self.__class__ is myClass
        # where myClass is the argument passed into inialiseoptions
        self.initialiseoptions(ColorEntry)

    def popupColorPicker(self):
        # Can pass in current color with: color = (255, 0, 0)
        color = tkColorChooser.askcolor(
            parent = self.interior(),
            # Initialize it to current color
            initialcolor = tuple(self.get()[:3]))[0]
        if color:
            self.set((color[0], color[1], color[2], self.getAt(3)))

if __name__ == '__main__':
    root = Toplevel()
    root.title('Vector Widget demo')

    ve = VectorEntry(root); ve.pack()
    v3e = Vector3Entry(root); v3e.pack()
    v4e = Vector4Entry(root); v4e.pack()
    ce = ColorEntry(root); ce.pack()
