# Styles.py

from javax.swing.text import StyleContext, StyleConstants, TabSet, TabStop
import string
class Styles:
    def __init__(self, context=None):
        if context is None:
            context = StyleContext()
        self.context = context
        self.default = self.context.getStyle(StyleContext.DEFAULT_STYLE)

    def add(self, name, parent=None, tabsize=None, **keywords):
        if parent is None:
            parent = self.default
        style = self.context.addStyle(name, parent)

        for key, value in keywords.items():
            key = string.upper(key[0])+key[1:]
            meth = getattr(StyleConstants, "set"+key)
            meth(style, value)

        if tabsize is not None:
            charWidth = StyleConstants.getFontSize(style)
            tabs = []
            for i in range(20):
                tabs.append(TabStop(i*tabsize*charWidth))
            StyleConstants.setTabSet(style, TabSet(tabs))
        return style

    def get(self, stylename):
        return self.context.getStyle(stylename)

    def __tojava__(self, c):
        if isinstance(self.context, c):
            return self.context


# Action.py
import string
from javax import swing
class Action(swing.AbstractAction):
    def __init__(self, name, action=None, icon=None, description=None, needEvent=0):
        if action is None:
            action = name
            name = action.__name__

        #swing.AbstractAction.__init__(self, name)
        self.name = name
        self.icon = icon
        if icon:
            self.setIcon(swing.Action.SMALL_ICON, icon)
        if description:
            self.setText(swing.Action.SHORT_DESCRIPTION, description)
            self.description = description
        else:
            self.description = name
        self.action = action

        self.enabled = 1
        self.needEvent = needEvent

    def actionPerformed(self, event):
        if self.needEvent:
            self.action(event)
        else:
            self.action()

    def createMenuItem(self):
        mi = swing.JMenuItem(
            self.name, actionListener=self, enabled=self.enabled)
        return mi


class TargetAction(Action):
    def actionPerformed(self, event):
        if self.needEvent:
            self.action(self.getTarget(), event)
        else:
            self.action(self.getTarget())

# KeyMap.py
from java.awt.event import KeyEvent, InputEvent
from javax.swing import KeyStroke, text
_keynames = {}


def getKeyStroke(key):
    if len(_keynames) == 0:
        for name in dir(KeyEvent):
            if name[:3] == 'VK_':
                _keynames[string.lower(name[3:])] = getattr(KeyEvent, name)

    if key is None:
        return KeyStroke.getKeyStroke(KeyEvent.CHAR_UNDEFINED)

    if len(key) == 1:
        return KeyStroke.getKeyStroke(key)

    fields = string.split(key, '-')
    key = fields[-1]
    mods = fields[:-1]

    modifiers = 0
    for mod in mods:
        if mod == 'C':
            modifiers = modifiers | InputEvent.CTRL_MASK
        elif mod == 'S':
            modifiers = modifiers | InputEvent.SHIFT_MASK
        # Meta and Alt don't currently work right
        elif mod == 'M':
            modifiers = modifiers | InputEvent.META_MASK
        elif mod == 'A':
            modifiers = modifiers | InputEvent.ALT_MASK
        else:
            raise ValueError, 'Invalid modifier in '+key

    return KeyStroke.getKeyStroke(_keynames[key], modifiers)


def makeAction(o):
    if isinstance(o, Action):
        return o
    if callable(o):
        return Action(o)


class Keymap:
    __keynames = {}
    __defaultKeymap = text.JTextComponent.getKeymap(
        text.JTextComponent.DEFAULT_KEYMAP)

    def __init__(self, bindings={}, parent=__defaultKeymap):
        self.keymap = text.JTextComponent.addKeymap(None, parent)
        for key, action in bindings.items():
            self.bind(key, action)

    def bind(self, key, action):
        self.keymap.addActionForKeyStroke(
            getKeyStroke(key), makeAction(action))

    def __tojava__(self, c):
        if isinstance(self.keymap, c):
            return self.keymap


from pawt import swing, colors
from java.awt.event.KeyEvent import VK_UP, VK_DOWN
from java.awt.event import ActionEvent
from java.lang import Thread, System
from code import compile_command
import string
import sys
import re


class OutputBuffer:
    def __init__(self, console, stylename):
        self.console = console
        self.stylename = stylename

    def flush(self):
        pass

    def write(self, text):
        self.console.write(text, self.stylename)


class Console:
    def __init__(self, styles=None, keymap=None):
        if styles is None:
            styles = Styles()
            basic = styles.add('normal', tabsize=3,
                               fontSize=12, fontFamily="Courier")
            styles.add('error', parent=basic, foreground=colors.red)
            styles.add('output', parent=basic, foreground=colors.blue)
            styles.add('input', parent=basic, foreground=colors.black)
            styles.add('prompt', parent=basic, foreground=colors.purple)
        self.styles = styles

        # This is a hack to get at an inner class
        # This will not be required in JPython-1.1
        ForegroundAction = getattr(
            swing.text, 'StyledEditorKit$ForegroundAction')
        self.inputAction = ForegroundAction("start input", colors.black)

        if keymap is None:
            keymap = Keymap()
        keymap.bind('enter', self.enter)
        keymap.bind('tab', self.tab)
        keymap.bind('escape', self.escape)
        keymap.bind('up', self.uphistory)
        keymap.bind('down', self.downhistory)

        self.keymap = keymap

        self.document = swing.text.DefaultStyledDocument(self.styles)
        self.document.setLogicalStyle(0, self.styles.get('normal'))

        self.textpane = swing.JTextPane(self.document)
        self.textpane.keymap = self.keymap

        self.history = []
        self.oldHistoryLength = 0
        self.historyPosition = 0

        self.command = []
        self.locals = {}

    def write(self, text, stylename='normal'):
        style = self.styles.get(stylename)
        self.document.insertString(self.document.length, text, style)

    def beep(self):
        self.textpane.toolkit.beep()

    def startUserInput(self, prompt=None):
        if prompt is not None:
            self.write(prompt, 'prompt')
        self.startInput = self.document.createPosition(self.document.length-1)
        #self.document.setCharacterAttributes(self.document.length-1, 1, self.styles.get('input'), 1)
        self.textpane.caretPosition = self.document.length
        ae = ActionEvent(
            self.textpane, ActionEvent.ACTION_PERFORMED, 'start input')
        self.inputAction.actionPerformed(ae)

    def getinput(self):
        offset = self.startInput.offset
        line = self.document.getText(offset+1, self.document.length-offset)
        return string.rstrip(line)

    def replaceinput(self, text):
        offset = self.startInput.offset + 1
        self.document.remove(offset, self.document.length-offset)
        self.write(text, 'input')

    def enter(self):
        line = self.getinput()
        self.write('\n', 'input')

        self.history.append(line)
        self.handleLine(line)

    def gethistory(self, direction):
        historyLength = len(self.history)
        if self.oldHistoryLength < historyLength:
            # new line was entered after last call
            self.oldHistoryLength = historyLength
            if self.history[self.historyPosition] != self.history[-1]:
                self.historyPosition = historyLength

        pos = self.historyPosition + direction

        if 0 <= pos < historyLength:
            self.historyPosition = pos
            self.replaceinput(self.history[pos])
        else:
            self.beep()

    def uphistory(self):
        self.gethistory(-1)

    def downhistory(self):
        self.gethistory(1)

    def tab(self):
        self.write('\t', 'input')

    def escape(self):
        if (not hasattr(self, 'pythonThread') or self.pythonThread is None or
                not self.pythonThread.alive):
            self.beep()
            return

        self.pythonThread.stopPython()

    def capturePythonOutput(self, stdoutStyle='output', stderrStyle='error'):
        import sys
        sys.stdout = OutputBuffer(self, stdoutStyle)
        sys.stderr = OutputBuffer(self, stderrStyle)

    def handleLine(self, text):
        self.command.append(text)

        try:
            code = compile_command(string.join(self.command, '\n'))
        except SyntaxError:
            traceback.print_exc(0)
            self.command = []
            self.startUserInput(str(sys.ps1)+'\t')
            return

        if code is None:
            self.startUserInput(str(sys.ps2)+'\t')
            return

        self.command = []

        pt = PythonThread(code, self)
        self.pythonThread = pt
        pt.start()

    def newInput(self):
        self.startUserInput(str(sys.ps1)+'\t')


import traceback


class PythonThread(Thread):
    def __init__(self, code, console):
        self.code = code
        self.console = console
        self.locals = console.locals

    def run(self):
        try:
            exec self.code in self.locals

        # Include these lines to actually exit on a sys.exit() call
        # except SystemExit, value:
        #	raise SystemExit, value

        except:
            exc_type, exc_value, exc_traceback = sys.exc_info()
            l = len(traceback.extract_tb(sys.exc_traceback))
            try:
                1/0
            except:
                m = len(traceback.extract_tb(sys.exc_traceback))
            traceback.print_exception(exc_type, exc_value, exc_traceback, l-m)

        self.console.newInput()

    def stopPython(self):
        # Should spend 2 seconds trying to kill thread in nice Python style first...
        self.stop()


header = """\
JPython %(version)s on %(platform)s
%(copyright)s
""" % {'version': sys.version, 'platform': sys.platform, 'copyright': sys.copyright}

#from javax.swing import JFrame
#if __name__ == '__main__':
c = Console()
pane = swing.JScrollPane(c.textpane)
#frame = JFrame(size=(500, 400), name='JPython Console')
#frame.add(pane)
#frame.setVisible(True)
#frame.pack()
swing.test(pane, size=(500, 400), name='JPython Console')
c.write(header, 'output')
c.capturePythonOutput()
c.textpane.requestFocus()
c.newInput()
