# $Id: actions.py,v 1.37 2003/11/04 21:23:03 mackstann Exp $

import sys, os
import kahakai
from events import *

class RedirectingLog(object):
    def __init__(self, filename, replace, name):
        self.filename = filename
        self.name = name
        self.prefix = "Python %s: " % name

        self._oldfp = replace
        self._log = file(filename, "a")

        print >> self._log, "Python: %s log opened" % self.name
        self._oldfp.flush()

    def __getattr__(self, attr):
        return getattr(self._oldfp, attr)

    def write(self, text):
        lines = [ line.rstrip() + "\n" for line in
                  filter(str.strip, text.split("\n")) ]

        for line in lines:
            for f in (self._log, self._oldfp):
                f.write("%s%s" % (self.prefix, line))

logfn = os.path.expanduser("~/.kahakai/log")

kahakaiInst = kahakai.Kahakai.Instance()

def screen():
    return kahakaiInst.script_current_screen

_objects   = []

_beendone = []

def execute(screen, command):
    if not os.fork():
        os.setsid()
        os.putenv(*screen.displaystring.split('='));
        os.execl("/bin/sh", "/bin/sh", "-c", command);
        sys.exit(0);

def _bindAction(bindto, actionList, event, function, param):
    assert bindto in ("window","menu")

    action = kahakai.Action()
    _objects.append(action)

    if not event.set(action): return

    if bindto == "window":
        if not isinstance(function, kahakai.WindowFunction):
            function = kahakai.createWindowFunction(function)

        _objects.append(function)
        action.winfunc = function
    else:
        action.menufunc = function

    action.param = param
    actionList.append(action)

bindWindowAction = lambda *args: _bindAction("window", *args)
bindMenuAction = lambda *args: _bindAction("menu", *args)

def _processFunction(function, functionType):
    """
    Function argument parsing code common to all three *Action() functions.

    functionType is either 'window', 'screen' or 'menu'.

    Returns function and the parameter
    """
    assert functionType in ('screen', 'menu', 'window')

    param = ""

    if isinstance(function, tuple):
        function, param = function

        if isinstance(param, int):
            param = str(param)

    if isinstance(function, str):
        if function.startswith('{') and function.endswith('}'):
            command  = function[1:-1]
            function = lambda screen, *args: execute(screen, command)
            return kahakai.createScreenFunction(function), param

        else:
            if functionType in ("window", "menu", "screen"):

                fullname = "%sEv%s" % (functionType.capitalize(), function)
                scrfullname = "ScreenEv%s" % function

                if hasattr(kahakai, fullname):
                    return getattr(kahakai, fullname)(), param

                elif hasattr(kahakai, scrfullname):
                    return getattr(kahakai, scrfullname)(), param

                else:
                    raise ValueError("notice: '%s' is not a valid %s function"
                                     % (function, functionType))


            else:
                raise ValueError("`%s' is not a valid action target"
                                  % functionType)


    classes = {'menu'  : kahakai.MenuFunction,
               'window': kahakai.WindowFunction,
               'screen': kahakai.ScreenFunction}

    wrappers = {'menu'  : kahakai.createMenuFunction,
                'window': kahakai.createWindowFunction,
                'screen': kahakai.createScreenFunction}

    if not isinstance(function, classes[functionType]):
        if callable(function):
            function = wrappers[functionType](function)
        else:
            raise ValueError("Wrong type for a function: %s" % type(function))

    return function, param

def getWindowActionList(screen, name):
    assert name.startswith('window.')

    if name.startswith('window.button_'):
        id = name[len('window.button_'):]
        actionList = screen.config.bacts[id]
        #print "ACTIONLIST: ", actionList
    else:
        actionLists = {
            'frame': screen.config.frameacts,
            'title': screen.config.titleacts,
            'label': screen.config.labelacts,
            'handle': screen.config.handleacts,
            'activeclient': screen.config.awinacts,
            'focused': screen.config.awinacts,
            'passiveclient': screen.config.pwinacts,
            'unfocused': screen.config.pwinacts,
            'leftgrip':  screen.config.lgacts,
            'rightgrip': screen.config.rgacts,
            }
        suffix = name[len('window.'):]
        
        if not suffix.lower() in actionLists:
            warn("Unknown window target %s" % suffix)
            return None
        else:
            actionList = actionLists[suffix.lower()]

    return actionList
    

def windowAction(screen, name, event, function):
    try:
        function, param = _processFunction(function, 'window')
    except (), err:
        print "warning: actions.windowAction() caught exception:"
        print err
        return

    _objects.append(function)

    if name.startswith('window.'):
        bindWindowAction(getWindowActionList(screen, name),
                         event, function, param)
    else:
        warn("Unsupported window action target %s" % name)

def getMenuActionList(screen, name):
    assert name.startswith('menu.'), name
    
    actionLists = {
        'title': screen.config.mtacts,
        'item':  screen.config.miacts,
        'sub':   screen.config.msacts,
        'checkbox': screen.config.mcbacts}
    
    suffix = name[len('menu.'):]
    
    if not suffix.lower() in actionLists:
        warn("Unknown menu target %s" % suffix)
        return None
    else:
        return actionLists[suffix.lower()]

def menuAction(screen, name, event, function):
    action = kahakai.Action()
    _objects.append(action)

    if not event.set(action): return

    try:
        function, param = _processFunction(function, 'menu')
    except (), err:
        print "warning: actions.menuAction() caught exception:"
        print err
        return

    _objects.append(function)

    if name.startswith('menu.'):
        actionList = getMenuActionList(screen, name)
        
        bindMenuAction(actionList, event, function, param)
    else:
        warn("Unsupported menu action target %s" % name)
        
def screenAction(screen, event, function, edge=None):
    action = kahakai.Action()
    _objects.append(action)

    if not event.set(action): return

    param = ""

    try:
        function, param = _processFunction(function, 'screen')
    except (), err:
        print "warning: actions.screenAction() caught exception:"
        print err
        return

    _objects.append(function)

    action.rootfunc = function
    action.param    = param

    screen.config.rootacts.append(action)

def edgeAction(screen, event, function, edge):
    action = kahakai.Action()
    _objects.append(action)

    if not event.set(action): return

    param = ""

    try:
        function, param = _processFunction(function, 'screen')
    except (), err:
        print "warning: actions.edgeAction() caught exception:"
        print err
        return

    _objects.append(function)

    action.rootfunc = function
    action.param    = param

    edge = edge.lower()

    if   edge == 'n': screen.config.neacts.append(action)
    elif edge == 's': screen.config.seacts.append(action)
    elif edge == 'e': screen.config.eeacts.append(action)
    elif edge == 'w': screen.config.weacts.append(action)
    else:
        warn("Wrong edge: %s (should be n, s, e, w)" % edge)

# screen

_delayedScreenActs = []

def doDelayedScreenActs():
    global _delayedScreenActs
    _screenActions(_delayedScreenActs)
    _delayedScreenActs = []

def screenActions(actions):
    _delayedScreenActs.extend(actions)

def _screenActions(actions):    
    for event, function in actions:
        screenAction(screen(), event, function)

# edges

_delayedEdgeActs = {}

def doDelayedEdgeActs():
    global _delayedEdgeActs
    for edge, actions in _delayedEdgeActs.items():
        _edgeActions(actions, edge)
    _delayedEdgeActs = {}

def edgeActions(actions, edge):
    if not _delayedEdgeActs.has_key(edge):
        _delayedEdgeActs[edge] = []

    _delayedEdgeActs[edge].extend(actions)

def _edgeActions(actions, edge):
    for event, function in actions:
        edgeAction(screen(), event, function, edge)

# windows

_delayedWinActs = {}

def doDelayedWinActs():
    global _delayedWinActs
    
    for name, actions in _delayedWinActs.items():
        _windowActions(name, actions)

    _delayedWinActs = {}

def windowActions(name, actions):
    """Not the real thing; appends the actions to the hash containing delayed
    actions, for later execution by doDelayedWinActs"""
    if not _delayedWinActs.has_key(name):
        _delayedWinActs[name] = []

    _delayedWinActs[name].extend(actions)

def _windowActions(name, actions):
    """Formerly known as windowActions, now hidden due to caching"""
    actionList = getWindowActionList(screen(), name)
    
    for event, function in actions:
        function, param = _processFunction(function, 'window')
        _objects.append(function)

        bindWindowAction(actionList, event, function, param)

# menus

_delayedMenuActs = {}

def doDelayedMenuActs():
    global _delayedMenuActs
    for name, actions in _delayedMenuActs.items():
        _menuActions(name, actions)

    _delayedMenuActs = {}

def menuActions(name, actions):
    if not _delayedMenuActs.has_key(name):
        _delayedMenuActs[name] = []

    _delayedMenuActs[name].extend(actions)

def _menuActions(name, actions):
    actionList = getMenuActionList(screen(), name)

    for event, function in actions:
        function, param = _processFunction(function, 'menu')
        _objects.append(function)

        bindMenuAction(actionList, event, function, param)


def _handleMapRequest(config, window, event, action):
    winClass = window.classhint.res_class
    winName  = window.classhint.res_name
    
    config.newWindow(window, winClass, winName, event, action)

def init():
    """This should always be run.  Any code that absolutely needs to be in
    effect for everyone should go here."""

    try:
      stdout = RedirectingLog(logfn, sys.stdout, "stdout")
    except (), err:
      print "error in opening RedirectingLog for stdout:"
      print err
    else:
      sys.stdout = stdout

    try:
      stderr = RedirectingLog(logfn, sys.stderr, "stderr")
    except (), err:
      print "error in opening RedirectingLog for stderr:"
      print err
    else:
      sys.stderr = stderr

    # close task switcher on release of alt
    allMenuActions([ (KeyRelease('Alt_L'), "UnmapTree"),
                     (KeyRelease('Alt_L'), "Func") ],
                   ["item"])

    _beendone.append("init")

def finish():
    if not "init" in _beendone:
        print "You didn't call init()!  This is required!"

    doDelayedWinActs()
    doDelayedMenuActs()
    doDelayedEdgeActs()
    doDelayedScreenActs()

    _beendone.append("finish")

def install(config):

    _callable = lambda method: callable(getattr(config, method))

    for method in filter(_callable, dir(config)):

        # window parts (inc. buttons)

        if method.startswith("window_"):
            windowpart = method.replace("window_","window.")

            # doh
            windowpart = windowpart.replace("unfocused", "passiveclient")
            windowpart = windowpart.replace("focused", "activeclient")

            windowActions(windowpart, getattr(config, method)())

        # menus

        elif method.startswith("menu_"):
            menuActions(method.replace("menu_","menu."),
                        getattr(config, method)())

        # edges

        elif method.endswith("Edge"):
            edgeActions(getattr(config, method)(), method[0])

        # misc


        elif method == "globals":
            config.globals()

        elif method == "globalKeyBindings":
            globalActions(config.globalKeyBindings())

        elif method == "root":
            screenActions(config.root())

        elif method == "defaultAllWindows":
            allWindowActions(config.defaultAllWindows())

        elif method == "defaultAllMenus":
            allMenuActions(config.defaultAllMenus())

        elif method == "defaultAllTitle":
            allWindowActions(config.defaultAllTitle(),
                             ["title","label"])

        elif method == "defaultAllDecor":
            allWindowActions(config.defaultAllWindows(),
                             ["title","label","handle","leftgrip","rightgrip"])

        elif method == "newWindow":
            allWindowActions([(MapRequest(),
                             lambda *args: _handleMapRequest(config, *args))])

    finish()

allWindowTargets = """frame activeclient passiveclient
title label leftgrip rightgrip handle""".split()

allMenuTargets = "title item sub checkbox".split()

def _allActions(prefix, actionsfunc, actions, targets):
    for target in targets:
        actionsfunc("%s%s"%(prefix,target), actions)

def allWindowActions(actions, selective=allWindowTargets):
    _allActions("window.",windowActions, actions, selective)
def allMenuActions(actions, selective=allMenuTargets):
    _allActions("menu.", menuActions, actions, selective)

def globalActions(actions):
    allWindowActions(actions)
    screenActions(actions)

    allMenuActions(actions)

