# -*- coding: utf-8 -*-

# Copyright (c) 2003 - 2006 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the scripting component of the eric3 IDE.
"""

#
# Code inspired (and partly taken) from Boudewijn Rempts kalam application.
# Original code copyright: (C) 2001, Boudewijn Rempt
#

import sys
import os

from qt import *

from KdeQt import KQInputDialog

class ScriptError(Exception):
    """
    Base class for all scripting related exceptions.
    """
    pass
    
class NoSuchScriptError(ScriptError):
    """
    Class implementing an exception, which is raised, if a script couldn't be found.
    """
    def __init__(self, script):
        """
        Constructor
        """
        ERR = qApp.translate("ScriptError", "Script %1 is not installed")
        self.errorMessage = ERR.arg(script)
        
    def __repr__(self):
        """
        Private method returning a representation of the exception.
        
        @return string representing the error message
        """
        return unicode(self.errorMessage)
        
    def __str__(self):
        """
        Private method returning a string representation of the exception.
        
        @return string representing the error message
        """
        return str(self.errorMessage)
        
class CompilationError(ScriptError):
    """
    Class implementing an exception, which is raised, if a script couldn't be compiled.
    """
    def __init__(self, error):
        """
        Constructor
        """
        ERR = qApp.translate("ScriptError", "Script could not be compiled. Reason: %1")
        self.errorMessage = ERR.arg(unicode(error))
        
    def __repr__(self):
        """
        Private method returning a representation of the exception.
        
        @return string representing the error message
        """
        return unicode(self.errorMessage)
        
    def __str__(self):
        """
        Private method returning a string representation of the exception.
        
        @return string representing the error message
        """
        return str(self.errorMessage)
        
class ExecutionError(ScriptError):
    """
    Class implementing an exception, which is raised, if a script couldn't be executed.
    """
    def __init__(self, error):
        """
        Constructor
        """
        ERR = qApp.translate("ScriptError", "Script could not be executed. Reason: %1")
        self.errorMessage = ERR.arg(unicode(error))
        
    def __repr__(self):
        """
        Private method returning a representation of the exception.
        
        @return string representing the error message
        """
        return unicode(self.errorMessage)
        
    def __str__(self):
        """
        Private method returning a string representation of the exception.
        
        @return string representing the error message
        """
        return str(self.errorMessage)
        
class ScriptAction(QAction):
    """
    Class implementing the script action.
    
    This class is subclassed from QAction to have the possibility to
    attach it to menus and toolbars.
    
    @signal activated signal emitted when this script action is activated
    @exception CompilationError raised, if script compilation fails
    @exception ExecutionError raised, if script execution failed
    """
    def __init__(self, code, *args):
        """
        Constructor
        
        @param code the script code (string or QString)
        @param *args arguments passed on to QAction
        """
        QAction.__init__(*(self,) + args)
        self.code = unicode(code)
        self.bytecode = self.__compile(self.code)
        self.locations = []
        self.connect(self, SIGNAL("activated()"), self.activated)
        
    def activated(self):
        """
        Private method connected to the QAction activated signal.
        """
        self.emit(PYSIGNAL("activated"), (self,))
        
    def addTo(self, widget):
        """
        Public method to add this action to a widget.
        
        Overloaded from QAction in order to keep a list of widgets
        we are added to.
        
        @param widget widget to add this action to (QWidget)
        """
        QAction.addTo(self, widget)
        self.locations.append(widget)
        
    def removeFrom(self, widget):
        """
        Public method to remove this action from a widget.
        
        Overloaded from QAction in order to keep a list of widgets
        we are added to.
        
        @param widget widget to remove this action from (QWidget)
        """
        QAction.removeFrom(self, widget)
        self.locations.remove(widget)
        
    def remove(self):
        """
        Public method to remove this action.
        """
        locs = self.locations[:]
        for widget in locs:
            self.removeFrom(widget)
            
    def __compile(self, code):
        """
        Private method to compile the code string.
        
        @param ode string containing the code (string)
        @exception CompilationError raised, if compilation fails
        @return the compiled bytecode as a string
        """
        # change code to adhere to the form expected by the compile builtin function
        if os.linesep == "\r\n":
            code = code.replace("\r\n", "\n")
        elif os.linesep == "\r":
            code = code.replace("\r", "\n")
        code = "%s\n" % code
        
        try:
            bytecode = compile(code, "<string>", "exec")
            return bytecode
        except Exception, e:
            raise CompilationError(e)
            
    def execute(self, out, err, globals, locals):
        """
        Public method to execute this script.
        
        @param out redirect for sys.stdout
        @param err redirect for sys.stderr
        @param globals dictionary containing global scope
        @param locals dictionary containing local scope
        """
        try:
            __stdout = sys.stdout
            __stderr = sys.stderr
            sys.stdout = out
            sys.stderr = err
            exec self.bytecode in globals, locals
            sys.stdout = __stdout
            sys.stderr = __stderr
        except Exception, e:
            print e
            print sys.exc_info
            sys.stdout = __stdout
            sys.stderr = __stderr
            raise ExecutionError(e)
            
class ScriptManager(QObject):
    """
    Class implementing the script manager.
    
    @signal firstScriptAdded emitted after the first script was added
    @signal lastScriptDeleted emitted after the last script was deleted
    """
    def __init__(self, parent = None, g = None, l = None, *args):
        """
        Constructor
        
        @param parent parent of this scriptmanager (QObject)
        @param g dictionary for global scope
        @param l dictionary for local scope
        @param *args arguments passed on to QObject
        """
        QObject.__init__(*(self, parent) + args)
        
        self.scriptObjects = {}
        
        if g is None:
            self.globals = globals()
        else:
            self.globals = g
            
        if l is None:
            self.locals = locals()
        else:
            self.locals = l
            
    def deleteScript(self, scriptName):
        """
        Public method to delete a script.
        
        @param scriptName name of the script to be deleted (string or QString)
        """
        if not self.scriptObjects.has_key(unicode(scriptName)):
            raise NoSuchScriptError(scriptName)
        else:
            self.scriptObjects[unicode(scriptName)].remove()
            del self.scriptObjects[unicode(scriptName)]
            if not self.scriptObjects:
                self.emit(PYSIGNAL("lastScriptDeleted"), ())
        
    def addScript(self, scriptName, scriptString):
        """
        Public method to add a new script.
        
        @param scriptName name of the script to be added (string or QString)
        @param scriptString the script code (string or QString)
        @return a ScriptAction object
        """
        action = ScriptAction(unicode(scriptString), self.parent())
        if self.scriptObjects.has_key(unicode(scriptName)):
            # remove a previously loaded script with same name
            self.deleteScript(scriptName)
        if not self.scriptObjects:
            self.emit(PYSIGNAL("firstScriptAdded"), ())
        self.scriptObjects[unicode(scriptName)] = action
        self.connect(action, PYSIGNAL("activated"), self.executeAction)
        return action
        
    def executeAction(self, action):
        """
        Public slot to execute a script action.
        
        @param action script action to be executed (ScriptAction)
        """
        action.execute(sys.stdout, sys.stderr, self.globals, self.locals)
        
    def executeScript(self, scriptName, out = sys.stdout, err = sys.stderr):
        """
        Public method to execute a script.
        
        @param scriptName name of the script to be executed (string or QString)
        @param out redirect for stdout
        @param err redirect for stderr
        @exception NoSuchScriptError raised, if the named script cannot be found
        """
        if not self.scriptObjects.has_key(unicode(scriptName)):
            raise NoSuchScriptError(scriptName)
        else:
            self.scriptObjects[unicode(scriptName)].execute(out, err, self.globals, self.locals)
            
    def getScriptNames(self):
        """
        Public method to retrieve the names of all scripts.
        
        @return list of all script names (list of strings)
        """
        return self.scriptObjects.keys()
        
    def getScriptName(self):
        """
        Private method to select a script name from the list of scripts.
        
        @return Tuple of script name and a flag, indicating, if the user pressed ok or
            canceled the operation. (QString, boolean)
        """
        qs = QStringList()
        for s in self.scriptObjects.keys():
            qs.append(s)
        qs.sort()
        return KQInputDialog.getItem(\
            self.trUtf8("Script Name"),
            self.trUtf8("Select a script name:"),
            qs, 0, 0, self.parent())
        
