#!/usr/bin/env python
"""Widgets to load and run opscore.actor.ScriptRunner scripts.

ScriptModuleWdg loads a script from a specified module.
ScriptFileWdg loads a script from a python source file
  (i.e. a module, but one that need not be on the python path)

History:
2010-02-17 ROwen    Adapted from RO.Wdg.ScriptWdg.
2010-03-10 ROwen    Commented out a debug print statement.
2010-06-28 ROwen    Removed two duplicate imports (thanks to pychecker).
2015-11-05 ROwen    Ditched obsolete "except (SystemExit, KeyboardInterrupt): raise" code
"""
__all__ = ['BasicScriptWdg', 'ScriptModuleWdg', 'ScriptFileWdg']

import os.path
import Tkinter
import RO.Constants
import RO.AddCallback
import RO.Wdg
import opscore.actor
import StatusBar

# compute _StateSevDict which contains
# state:severity for non-normal severities
_StateSevDict = {}
_StateSevDict[opscore.actor.ScriptRunner.Paused] = RO.Constants.sevWarning
_StateSevDict[opscore.actor.ScriptRunner.Cancelled] = RO.Constants.sevWarning
_StateSevDict[opscore.actor.ScriptRunner.Failed] = RO.Constants.sevError

class _Blank(object):
    def __init__(self):
        object.__init__(self)
        
class _FakeButton:
    def noop(self, *args, **kargs):
        return
    __init__ = noop
    __setitem__ = noop
    pack = noop
    ctxSetConfigFunc = noop

class BasicScriptWdg(RO.AddCallback.BaseMixin):
    """Handles button enable/disable and such for a ScriptRunner.
    You are responsible for creating and displaying the status bar(s)
    and start, pause and cancel buttons.
    
    Inputs:
    - master        master widget; the script functions may pack or grid stuff into this
    - name          script name; used to report status
    - dispatcher    keyword dispatcher; required to use the doCmd and startCmd methods
    - runFunc       run function (run when the start button pressed)
    - statusBar     script status bar, if any
    - startButton   button to start the script
        The following inputs are optional:
    - initFunc      a function run once when the script is first loaded
    - endFunc       a function run when the script ends for any reason; None of undefined)
    - cmdStatusBar  command status bar, if any; may be the same as statusBar
    - pauseButton   button to pause/resume the script
    - cancelButton  button to cancel the script
    - stateFunc     function to call when the script runner changes state.
                    The function receives one argument: the script runner.
    
    Notes:
    - The text of the Pause button is automatically set (to Pause or Resume, as appropriate).
    - You must set the text of the start and cancel buttons.
    - Supports the RO.AddCallback interface for state function callbacks,
      including addCallback and removeCallback
    """
    def __init__(self,
        master,
        name,
        dispatcher,
        statusBar,
        startButton,
        scriptClass = None,
        runFunc = None,
        initFunc = None,
        endFunc = None,
        cmdStatusBar = None,
        pauseButton = None,
        cancelButton = None,
        stateFunc = None,
    ):
        RO.AddCallback.BaseMixin.__init__(self)

        self.name = name
        self.dispatcher = dispatcher
        
        self.scriptRunner = None
        
        if not pauseButton:
            pauseButton = _FakeButton()

        if not cancelButton:
            cancelButton = _FakeButton()
        
        self.scriptStatusBar = statusBar
        self.cmdStatusBar = cmdStatusBar or statusBar
        
        self.startButton = startButton
        self.pauseButton = pauseButton
        self.cancelButton = cancelButton
        
        self.startButton["command"] = self._doStart
        self.pauseButton["command"] = self._doPause
        self.cancelButton["command"] = self._doCancel
    
        self._makeScriptRunner(
            master = master,
            scriptClass = scriptClass,
            initFunc = initFunc,
            runFunc = runFunc,
            endFunc = endFunc,
        )
        
        if stateFunc:
            self.addCallback(stateFunc)
    
    def _makeScriptRunner(self, master, scriptClass=None, initFunc=None, runFunc=None, endFunc=None):
        """Create a new script runner.
        See ScriptRunner for the meaning of the arguments.
        """
        self.scriptRunner = opscore.actor.ScriptRunner(
            name = self.name,
            dispatcher = self.dispatcher,
            master = master,
            scriptClass = scriptClass,
            initFunc = initFunc,
            runFunc = runFunc,
            endFunc = endFunc,
            stateFunc = self._stateFunc,
            statusBar = self.scriptStatusBar,
            cmdStatusBar = self.cmdStatusBar,
        )   

        self._setButtonState()
    
    def _doCancel(self):
        """Cancel the script.
        """
        self.scriptRunner.cancel()
    
    def _doPause(self):
        """Pause or resume script (depending on Pause button's text).
        
        Note: the pause button's text is updated by _stateFunc.
        """
        if self.pauseButton["text"] == "Resume":
            self.scriptRunner.resume()
        else:
            self.scriptRunner.pause()
    
    def _doStart(self):
        """Start script.
        """
        self.scriptRunner.start()
    
    def _setButtonState(self):
        """Set the state of the various buttons.
        """
#        print "_setButtonState(); state=%r; isExecuting=%r" % (self.scriptRunner.state, self.scriptRunner.isExecuting,)
        if self.scriptRunner.isExecuting:
            self.startButton["state"] = "disabled"
            self.pauseButton["state"] = "normal"
            self.cancelButton["state"] = "normal"
        else:
            self.startButton["state"] = "normal"
            self.pauseButton["state"] = "disabled"
            self.cancelButton["state"] = "disabled"

        if self.scriptRunner.isPaused:
            self._setPauseText("Resume")
        else:
            self._setPauseText("Pause")
    
    def _setPauseText(self, text):
        """Set the text and help text of the pause button.
        """
        self.pauseButton["text"] = text
        self.pauseButton.helpText = "%s the script" % text
    
    def _stateFunc(self, *args):
        """Script state function callback.
        """
        state, reason = self.scriptRunner.fullState
        if reason:
            msgStr = "%s: %s" % (state, reason)
        else:
            msgStr = state
        
        severity = _StateSevDict.get(state, RO.Constants.sevNormal)

        self.scriptStatusBar.setMsg(msgStr, severity)
        self._setButtonState()
        
        if self.scriptRunner.isDone:
            if self.scriptRunner.didFail:
                self.scriptStatusBar.playCmdFailed()