#############################################################################
#
# Author: Anna Omelchenko
#
# Copyright: M. Sanner TSRI
#
#############################################################################

#
#$Header: /opt/cvs/Volume/Pvv/vlioptionsCommands.py,v 1.1.1.1 2003/05/23 18:03:19 annao Exp $
#
#$Id: vlioptionsCommands.py,v 1.1.1.1 2003/05/23 18:03:19 annao Exp $
#
"""This module exposes a number of VLI features (gradients, blend mode,
render mode, supersampling factors) that allow the application
to control the rendering context and appearance of the volume"""

from ViewerFramework.VFCommand import Command, CommandGUI
from mglutil.gui.InputForm.Tk.gui import InputFormDescr, InputForm
from mglutil.gui.BasicWidgets.Tk.customizedWidgets import ExtendedSliderWidget
import Tkinter
from DejaVu.ColorChooser import ColorChooser
try:
    from Volume.Renderers.VLI import vli
except:
    pass
volGeom = None

class VLIOptions(Command):
    """ Options include:
    - set or clear the gradient magnitude opacity ,
      gradient magnitude specular illumination and
      gradient magnitude diffuse illumination modulations;
    - set the blend mode to be used during the rendering
      process. There are three options: front to back blending,
      maximum intensity projection and minimum intensity projection;
    - set rendering modes: hexagon (the volume is rendered as a flat hexagon),
      3D (the base plane is projected onto the front faces of the volume
      parallelepiped. This results in a more pleasing display, but does not
      correctly embed geometry inside the volume -- all surface/volume
      interactions are at the outside of the volume);
    - set the supersampling factors for the rendering context
      (the number of sample slices for each voxel slice).
      Supersampling in the x and y directions results in more samples in the
      base plane. Supersampling in the z direction results in more slices
      in the viewing direction.
      Supersampling allows a maximum of 4 additional samples between two
      voxel slices in the X, Y and Z directions;
      Starting with VLI version 2.0 supersampling is limited to
      three for X and Y, and one, two or four for Z;
    - specify the material properties used in lighting:
      -- diffuse reflection coefficient;
      -- specular reflection coefficient: the valid range is from
         0.0 (no reflection) to  1.0 (maximum reflection);
      -- emissive coefficient: the valid range is from 0.0 (no emission)
         to 1.0 (maximum emission);
      -- specular exponent (controls the size and brightness
         of the specular highlight): the valid range is from 0.0 (no highlight)
      to 128.0 (very focused highlight).
    - turn on/off cropping using the mouse;
    - reset the crop box;
      The following mouse button/keyboard key bindings are available to set
      the subvolume (crop box) size:
      -- 'Control' + mouse button 1, 2 or 3 are used to change crop box size
         along X, Y, Z respectively;
      -- 'meta' + mouse button 3 changes the crop box size in all directions;
      -- 'meta' + button 1 moves (translates) the crop box inside the volume
         in all directions (screen coord. trranslation mode);
      -- 'meta' + mouse button 1, 2, or 3 translates the box along volume's
          X, Y, or Z axes respectively(object coord. translation mode).
      The GUI consists of a set of menu buttons used to control gradients,
      blend mode, render mode, sampling factors and mouse crop.
      Pressing a menu button causes the associated menu (containing radio or
      checkbuttons) to be posted under the menubutton.
      A set of sliders is used to specify material properties."""

    def __init__(self):
        Command.__init__(self)
        self.gradOpacity = Tkinter.IntVar()
        self.gradOpacity.set(0)
        self.gradSpecil = Tkinter.IntVar()
        self.gradSpecil.set(0)
        self.gradDiffil = Tkinter.IntVar()
        self.gradDiffil.set(0)
        self.blendMode = Tkinter.StringVar()
        self.blendMode.set('frontToBack')
##          self.currentValues = {'matProp':(0.0, 0.0, 1.0, 7.0),
##                                'frontToBack':1, 'opacity':0}
        self.renderMode = Tkinter.StringVar()
        self.renderMode.set('hexagon')
        self.transMode = Tkinter.StringVar()
        self.transMode.set('screen')
        self.mcSwitch = Tkinter.IntVar()
        
        self.materialValues = (0.0, 0.0, 1.0, 7.0)
        self.LightColor = Tkinter.IntVar()
        
        self.samplingValues = [1,1,1]
        self.xsampl = Tkinter.StringVar()
        self.xsampl.set('1')
        self.ysampl = Tkinter.StringVar()
        self.ysampl.set('1')
        self.zsampl = Tkinter.StringVar()
        self.zsampl.set('1')
        
        self.form = None
        self.isDisplayed = 0

    def onAddCmdToViewer(self):
        """Called when the command is loaded(added) to the viewer."""

        self.blend_modes = {'frontToBack':vli.kVLIBlendFTB,
                            'maxIntensity':vli.kVLIBlendMIP,
                            'minIntensity':vli.kVLIBlendMINIP}
        c = volGeom.context
        if not c: return
        
        self.materialValues = list(c.GetReflectionProperties())
        self.colorChooser = None

    def guiCallback(self):
        """Builds the input form (GUI)."""
        if self.isDisplayed:
            self.form.lift()
            return
        self.isDisplayed=1
        if self.form:
            self.form.deiconify()
            return
        ifd = self.ifd = InputFormDescr(title = 'VLI Options')
        ifd.append( {'name': 'Gradient',
                     'widgetType':Tkinter.Menubutton,
                     'wcfg':{'text': 'Gradient Modulation',
                             'relief': Tkinter.RAISED,
                             'direction': Tkinter.RIGHT},
                     'gridcfg':{'columnspan':3,'sticky':'we'}})
        ifd.append( {'name': 'Blending',
                     'widgetType':Tkinter.Menubutton,
                     'wcfg':{'text': 'Blend Mode',
                             'relief': Tkinter.RAISED,
                             'direction': Tkinter.RIGHT},
                     'gridcfg':{'columnspan':3, 'sticky':'we'}})
        ifd.append( {'name': 'Rendermode',
                     'widgetType':Tkinter.Menubutton,
                     'wcfg':{'text': 'Render Mode',
                             'relief': Tkinter.RAISED,
                             'direction': Tkinter.RIGHT},
                     'gridcfg':{'columnspan':3, 'sticky':'we'}})
        ifd.append( {'name': 'mouseCrop',
                     'widgetType':Tkinter.Menubutton,
                     'tip': "'Control'+B1, B2 or B3 crop volume along X, Y, Z;  'meta'+B3 crops volume in all directions",
                      'wcfg':{'text': 'Mouse Crop',
                             'relief': Tkinter.RAISED},
                             #'direction': Tkinter.RIGHT},
                     'gridcfg':{'columnspan':3,'sticky':'we'}})
        
        ifd.append({'widgetType': Tkinter.Label,
                    'text': 'SuperSampling:',
                    'gridcfg':{'columnspan':3, 'sticky':'w', 'pady': 5}})
        ifd.append( {'name': 'xSampling',
                     'widgetType':Tkinter.Menubutton,
                     'wcfg':{'text': 'X: '+str(self.samplingValues[0]),
                             'relief': Tkinter.RAISED},
                     'gridcfg':{'column':0,'sticky':'we'}})
        ifd.append( {'name': 'ySampling',
                     'widgetType':Tkinter.Menubutton,
                     'wcfg':{'text': 'Y: '+str(self.samplingValues[1]),
                             'relief': Tkinter.RAISED},
                     'gridcfg':{'row':-1,'column':1,'sticky':'we'}})
        ifd.append( {'name': 'zSampling',
                     'widgetType':Tkinter.Menubutton,
                     'wcfg':{'text': 'Z: '+str(self.samplingValues[2]),
                             'relief': Tkinter.RAISED},
                     'gridcfg':{'row':-1,'column':2,'sticky':'we'}})
        
        ifd.append({'widgetType': Tkinter.Label,
                    'text': 'Material Properties:',
                    'gridcfg':{'columnspan':3, 'sticky':'w', 'pady': 5}})
        ifd.append( {'name': 'diffuse',
                       'widgetType':ExtendedSliderWidget,
                       'type':float,
                       'wcfg':{'label': 'Diffuse         ',
                               'immediate':1,
                               'minval':0.0,'maxval':1.0,
                               'incr': 0.1, 'init':self.materialValues[0],
                               'labelsCursorFormat':'%3.1f',
                               'sliderType':'float',
                               'entrypackcfg':{'side':'right'},
                               'command':self.getMaterialVal_cb},
                       'gridcfg':{'columnspan':3,'sticky':'we'}
                       })
        
        ifd.append( {'name': 'specular',
                     'widgetType':ExtendedSliderWidget,
                     'type':float,
                     'wcfg':{'label': 'Specular        ',
                             'immediate':1,
                             'minval':0.0,'maxval':1.0,
                             'incr': 0.1, 'init':self.materialValues[1],
                             'labelsCursorFormat':'%3.1f',
                             'sliderType':'float',
                             'entrypackcfg':{'side':'right'},
                             'command':self.getMaterialVal_cb},
                     'gridcfg':{'columnspan':3,'sticky':'we'}
                     })
          
        ifd.append( {'name': 'emissive',
                     'widgetType':ExtendedSliderWidget,
                     'type':float,
                     'wcfg':{'label': 'Emissive        ',
                             'immediate':1,
                             'minval':0.0,'maxval':1.0,
                             'incr': 0.1, 'init':self.materialValues[2] ,
                             'labelsCursorFormat':'%3.1f',
                             'sliderType':'float',
                             'entrypackcfg':{'side':'right'},
                             'command':self.getMaterialVal_cb},
                     'gridcfg':{'columnspan':3,'sticky':'we'}
                     })
          
        ifd.append( {'name': 'specexp',
                     'widgetType':ExtendedSliderWidget,
                     'type':float,
                     'wcfg':{'label': 'SpecExponent',
                             'immediate':1,
                             'minval':0.0,'maxval':128.0,
                             'incr': 1.0, 'init':self.materialValues[3] ,
                             'labelsCursorFormat':'%5.1f',
                             'sliderType':'float',
                             'entrypackcfg':{'side':'right'},
                             'command':self.getMaterialVal_cb},
                     'gridcfg':{'columnspan':3,'sticky':'we'}
                     })
        self.LightColor.set(0)
        ifd.append({'widgetType':Tkinter.Checkbutton,
                    'name': 'lightcolor',
                    'wcfg':{'text': 'Edit Specular Color',
                            'variable':self.LightColor,
                            'onvalue': 1, 'offvalue':0,
                            'command':self.setSpecularColor_cb},
                    'gridcfg':{'columnspan':3, 'sticky':'w'}
                               #'padx':5, 'pady':5}
                        })
        ifd.append({'widgetType':Tkinter.Button,
                    'wcfg':{'text': 'Dismiss',
                            'relief' : Tkinter.RAISED,
                            'command':self.dismiss_cb},
                    'gridcfg':{'columnspan':3,'sticky':'we'}
                    })
          
        self.form = self.vf.getUserInput(ifd, modal = 0, blocking=0)
        wnames = ['diffuse', 'specular', 'emissive','specexp']
        labelfnt = ('arial', 12, 'bold')
        numfnt = ('arial', 8, 'bold')
        for name in wnames:
            w = ifd.entryByName[name]['widget']
##              w.label.configure(font=labelfnt)
##              w.draw.itemconfig(w.valueLabel, font=numfnt)
            w.configure(labelfont=labelfnt, cursorfont=numfnt)
        self.buildGradientMenu()
        self.buildBlendMenu()
        self.buildRenderModeMenu()
        self.buildMouseCropMenu()
        self.buildSamplingMenus()

    def buildGradientMenu(self):
        gradMb = self.ifd.entryByName['Gradient']['widget']
        gradMb.menu = Tkinter.Menu(gradMb)
        gradMb['menu'] = gradMb.menu
        gradMb.menu.add_checkbutton(label='Opacity',
                    var=self.gradOpacity, command=self.setGradOpacity_cb )
        gradMb.menu.add_checkbutton(label='Specular illumination',
                    var=self.gradSpecil, command=self.setGradSpecIllum_cb )
        gradMb.menu.add_checkbutton(label='Diffuse illumination ',
                    var=self.gradDiffil, command=self.setGradDiffIllum_cb )

    def buildBlendMenu(self):
        blendMb = self.ifd.entryByName['Blending']['widget']
        blendMb.menu = Tkinter.Menu(blendMb)
        blendMb['menu'] = blendMb.menu
        mode = [('Front To Back', 'frontToBack'),
                ('Max Intensity', 'maxIntensity'),
                ('Min Intensity','minIntensity')]
        for l, v in mode:
            blendMb.menu.add_radiobutton(label=l, var=self.blendMode,
                                         value=v, command=self.setblendmode_cb)

    def buildRenderModeMenu(self):
        renderMb = self.ifd.entryByName['Rendermode']['widget']
        renderMb.menu = Tkinter.Menu(renderMb)
        renderMb['menu'] = renderMb.menu
        mode = ['hexagon','3D']
        
        for m in mode:
            renderMb.menu.add_radiobutton(label=m, value=m,
                                          var=self.renderMode, 
                                          command=self.setrendermode_cb)

    def buildMouseCropMenu(self):
        mouseCropMb = self.ifd.entryByName['mouseCrop']['widget']
        mouseCropMb.menu = Tkinter.Menu(mouseCropMb)
        mouseCropMb['menu'] = mouseCropMb.menu
        self.mcSwitch.set(1)
        mouseCropMb.menu.add_checkbutton(label='MouseCrop on/off',
                            var=self.mcSwitch, command=self.crop_onoff_cb)
        mouseCropMb.menu.add_command(label="Reset Crop",
                                             command=self.resetCrop_cb)
        mouseCropMb.menu.choices = Tkinter.Menu(mouseCropMb.menu)
        mode = [('screen coord. space', 'screen'),
                ('volume coord. space', 'object')]
        for l,m in mode:
            mouseCropMb.menu.choices.add_radiobutton(label=l, value=m,
                                                     var=self.transMode, 
                                                 command=self.settransmode_cb)
        mouseCropMb.menu.add_cascade(label='Translation Mode',
                                     menu = mouseCropMb.menu.choices)

    def buildSamplingMenus(self):

        if vli.kVLIMajorVersion >=2:
            menus = [('xSampling', self.xsampl, ('1', '2','3')),
                     ('ySampling', self.ysampl, ('1', '2','3')),
                     ('zSampling', self.zsampl, ('1', '2','4'))]
        else:
            menus = [('xSampling', self.xsampl, ('1', '2','4','8')),
                     ('ySampling', self.ysampl, ('1', '2','4','8')),
                     ('zSampling', self.zsampl, ('1', '2','4','8'))]
        for m, var, val in menus:
            samplMb = self.ifd.entryByName[m]['widget']
            samplMb.menu = Tkinter.Menu(samplMb)
            samplMb['menu'] = samplMb.menu
            for v in val:
                samplMb.menu.add_radiobutton(label=v, value=v, var=var,
                                             command=self.setSuperSampling_cb)
        
    def setGradOpacity_cb(self):
        """Sets or clears the gradient magnitude opacity modulation"""
        val = self.gradOpacity.get()
        kw = {'opacity': val}
        apply(self.doitWrapper, (), kw)

    def setGradSpecIllum_cb(self):
        """Sets or clears gradient magnitude specular illumination
        modulation"""
        val = self.gradSpecil.get()
        kw = {'specillum': val}
        apply(self.doitWrapper, (), kw)

    def setGradDiffIllum_cb(self):
        """Sets or clears gradient magnitude diffuse illumination modulation"""
        val = self.gradDiffil.get()
        kw = {'diffillum': val}
        apply(self.doitWrapper, (), kw)
    
    def setblendmode_cb(self):
        """Sets the blend mode to be used during the rendering process"""
        val = self.blendMode.get()
        kw = {val: 1}
        apply(self.doitWrapper, (), kw)

    def setrendermode_cb(self):
        """Sets rendering mode 'hexagon' or '3D' """
        kw = {'renderMode': self.renderMode.get()}
        apply(self.doitWrapper, (), kw)
    
    def getMaterialVal_cb(self, value):
        """Sets the material properties used in lighting"""
        val =  self.form.checkValues()
        diff = val['diffuse']
        spec = val['specular']
        ems = val['emissive']
        spex = val['specexp']
        kw = {'matProp':(diff,spec,ems,spex)}
        apply(self.doitWrapper, (), kw)

    def settransmode_cb(self):
        """Sets mouse crop translation mode"""
        kw = {'mouseCropTrans': self.transMode.get()}
        apply(self.doitWrapper, (), kw)

    def resetCrop_cb(self):
        """Resets the crop box to the volume size"""
        cropBox = volGeom.cropBox
        cropBox.xmin = 0
        cropBox.xmax = cropBox.volSize[0]
        cropBox.ymin = 0
        cropBox.ymax = cropBox.volSize[1]
        cropBox.zmin = 0
        cropBox.zmax = cropBox.volSize[2]
        cropBox.update()

    def crop_onoff_cb(self):
        """Switches mouse crop on/off """
        crop = volGeom.cropBox.crop
        if self.mcSwitch.get() == 0:
            flag = "off"
            crop.SetFlags(crop.kDisable)
        else:
            flag = "on"
            crop.SetFlags(crop.kSubVolume)
        if 'vliCrop' in self.vf.commands.keys():
            if self.vf.Crop.isDisplayed:
                self.vf.Crop.idf.entryByName['switch']['variable'].set(flag)
        self.vf.GUI.VIEWER.Redraw()

    def setSuperSampling_cb(self):
        """Sets the supersampling factors"""
        vals = ( int(self.xsampl.get()), int(self.ysampl.get()),
                 int(self.zsampl.get()) )
        kw = {'sampling':vals}
        apply(self.doitWrapper, (), kw)

    def setSpecularColor_cb(self):
          if self.LightColor.get():
               if not self.colorChooser:
                    self.colorChooser = ColorChooser(self.form.root)
                    self.colorChooser.frame.forget()
                    self.colorChooser.AddCallback(self.SetSpecularColor)
               self.colorChooser.frame.pack(side=Tkinter.BOTTOM)
          else :
               if self.colorChooser:
                    self.colorChooser.frame.forget()

    def SetSpecularColor(self, val):
          volGeom.context.SetSpecularColor(val[0],val[1],val[2])
          self.vf.GUI.VIEWER.Redraw()
        

    def __call__(self, **kw):
        """None<-VLIOptions(key = value) Keywords, values:
        -'opacity', 'diffillum', 'specillum' - set(value = 1) or
         clear(value=0) gradient opacity modulation, gradient specular
         illumination and gradient diffuse illumination;
        -'frontToBack','maxIntensity','minIntensity' - set blend mode(value=1);
        -'matProp' - sets diffuse, specular, emissive coefficients
         (valid range 0.0...1.0) and specular exponent(0.0...128.0) - 
         (value - tuple (diff,spec,em,spex);
        -'renderMode'- can be 'hexagon' or '3D';
        -'mouseCropTrans' - sets mouse crop translation mode, (value: either
         'screen' or 'object');
        -'sampling' - sets supersampling factors (value - tuple (X,Y,Z),
         valid values : 1,2,3 for X and Y, and  1,2,4 for Z)"""
        
        if not volGeom.context:
            print "Context has not been created"
            return
        op = kw.get('opacity')
        if op is not None:
            if op == 0  or op == 1:
                self.gradOpacity.set(op)
            else:
                raise ValueError("expected values for 'opacity': 1 or 0")
        di = kw.get('diffillum')
        if di is not None:
            if di == 0 or di == 1:
                self.gradDiffil.set(di)
            else:
                raise ValueError("expected values for 'diffillum': 1 or 0")
        si = kw.get('specillum')
        if si is not None:
            if si == 0 or si == 1:
                self.gradSpecil.set(si)
            else:
                raise ValueError("expected values for 'specillum': 1 or 0")
            
        ftb = kw.get('frontToBack')
        if ftb is not None:
            self.blendMode.set('frontToBack')
        maxint = kw.get('maxIntensity')
        if maxint is not None:
            self.blendMode.set('maxIntensity')
        minint = kw.get('minIntensity')
        if minint is not None:
            self.blendMode.set('minIntensity')
            
        rm = kw.get('renderMode')
        if rm is not None:
            if rm == 'hexagon' or rm == '3D':
                self.renderMode.set(rm)
            else:
                raise ValueError("expected 'hexagon' or '3D' for 'renderMode'")
            
        tm = kw.get('mouseCropTrans')
        if tm is not None:
            if tm == 'screen' or tm == 'object':
                self.transMode.set(tm)

        ss = kw.get('sampling')
        if ss is not None:
            assert len(ss) == 3
            x,y,z = ss
            error = 0
            if x not in [1,2,3]:
                error = 1
            if y not in [1,2,3]:
                error = 1
            if z not in [1,2,4]:
                error = 1
            if error:
                raise ValueError("'sampling' values expected: 1, 2, or 3 for X and Y, and 1, 2, or 4 for Z")
            else:
                self.xsampl.set(str(x))
                self.ysampl.set(str(y))
                self.zsampl.set(str(z))
        apply(self.doitWrapper, (), kw)
        
##      def setupUndoBefore(self, **kw):
##          com = self.vf.VLIOptions
##          self.addUndoCall((), self.currentValues, com.name)
        
    def doit(self, **kw):
        """Sets the vli options."""

        if not volGeom.context:
            print "Context has not been created"
            return
        op = kw.get('opacity')
        if op is not None:
            volGeom.context.SetGradientOpacityModulation(op)
        di = kw.get('diffillum')
        if di is not None:
            volGeom.context.SetGradientDiffuseIlluminationModulation(di)
        si = kw.get('specillum')
        if si is not None:
            volGeom.context.SetGradientSpecularIlluminationModulation(si)
            
        ftb = kw.get('frontToBack')
        if ftb is not None:
            volGeom.context.SetBlendMode(self.blend_modes['frontToBack'])
        maxint = kw.get('maxIntensity')
        if maxint is not None:
            volGeom.context.SetBlendMode(self.blend_modes['maxIntensity'])
        minint = kw.get('minIntensity')
        if minint is not None:
            volGeom.context.SetBlendMode(self.blend_modes['minIntensity'])

        mp = kw.get('matProp')
        if mp is not None:
            assert len(mp)==4
            volGeom.context.SetReflectionProperties(mp[0],mp[1],mp[2],mp[3])
            self.materialValues = mp

        rm = kw.get('renderMode')
        if rm is not None:
            volGeom.renderMode = rm

        tm = kw.get('mouseCropTrans')
        if tm is not None:
            if tm == 'screen':
                volGeom.BindMouseToCropTransAll()
            elif tm == 'object':
                volGeom.BindMouseToCropTranslate()
            else:
                raise ValueError \
                      ("expected 'screen' or 'object' for'mouseCropTrans'")
        ss = kw.get('sampling')
        if ss is not None:
            x,y,z = ss
            status= volGeom.context.SetSuperSamplingFactor(1./x, 1./y, 1./z)
            if status != 0:
                print "Status in SetSuperSamplingFactor: %d" % (status,)
                x,y,z = (1,1,1)
                status = volGeom.context.SetSuperSamplingFactor(1./x,
                                                                1./y,1./z)
                print "supsampling: x,y,z: %d, %d, %d, status: %d" % \
                      (x,y,z,status)
            if status == 0:
                if self.isDisplayed:
                    ebn = self.ifd.entryByName
                    if x != self.samplingValues[0]:
                        ebn['xSampling']['widget'].configure(text= \
                                                "X: %d"%x)
                    if y != self.samplingValues[1]:
                        ebn['ySampling']['widget'].configure(text= \
                                                "Y: %d"%y)
                    if z != self.samplingValues[2]:
                        ebn['zSampling']['widget'].configure(text= \
                                                     "Z: %d"%z)
                self.samplingValues = [x,y,z]
        self.vf.GUI.VIEWER.Redraw()
        #self.currentValues = kw
        
            
    def dismiss_cb(self):
        """Withdraws the GUI."""
        self.form.withdraw()
        self.isDisplayed = 0

    


commandGUI = CommandGUI()
menuName = 'VLIOptions'
menuIndex = 9
commandNames = {'vli':' VLIOptions', 'utvolren':None}
commandList = [{'name':'', 'cmd':None, 'gui':commandGUI}]

def initModule(viewer):
    for dict in commandList:
        if dict['name']:
            viewer.addCommand(dict['cmd'], dict['name'], dict['gui'])

