# Part of the A-A-P GUI IDE: The GUI console window

# Copyright (C) 2002-2003 Stichting NLnet Labs
# Permission to copy and use this file is specified in the file COPYING.
# If this file is missing you can find it here: http://www.a-a-p.org/COPYING

import os
import sys
import string
from wxPython.wx import *

import Tool

# message translation
def _(x):
    return Tool._gettext(x)

#
# The console window.
# It's a simple text control with support for Copy.  No Cut or Paste!
# The user may close the console window.  It's opened again when another
# message is printed.
#
class Console(wxFrame):

    def _init_ctrls(self, prnt):
        from GUItop import toplevel_xpos, toplevel_ypos, \
                toplevel_height, border_height, \
                console_width, console_height
        wxFrame.__init__(self, id=wxNewId(), name='', parent=prnt,
              pos=wxPoint(toplevel_xpos,
                              toplevel_ypos + toplevel_height + border_height),
              size=wxSize(console_width, console_height),
              style=wxDEFAULT_FRAME_STYLE, title='Agide console')

        # The main part of the window is the text control.
        self.textctrl = wxTextCtrl(self, wxNewId(), '',
                                              style = wxTE_MULTILINE|wxTE_RICH)
        EVT_RIGHT_DOWN(self.textctrl, self.OnRightDown)
        EVT_RIGHT_UP(self.textctrl, self.OnRightUp)
        EVT_LEFT_DCLICK(self.textctrl, self.OnDclick)

    def __init__(self, parent, consout, topmodel):
        self.consout = consout
        self.topmodel = topmodel

        self._init_ctrls(parent)
        EVT_CLOSE(self, self.OnWindowClose)
        self.positions = {}

        self.prevline = ''      # unfinished line, after last '\n'.
        self.curdir = None      # deteced current directory

    def OnWindowClose(self, event):
        """Called when the console window is closed."""
        # Let the ConsoleOut know our window was closed.
        self.consout.console = None
        self.Destroy()
        self.positions = {}

    def OnRightDown(self, event):
        """Right mouse button down: ignore to avoid it changes the selection."""
        pass

    def OnRightUp(self, event):
        """Right mouse button up: popup context menu."""
        # Create popup menu
        menu = wxMenu()

        # popup menu item: copy
        id = wxNewId()
        menu.Append(id, _("&Copy"), _("&Copy the selected text"))
        EVT_MENU(self, id, self.OnPmenuCopy)
        menu.Enable(id, self.textctrl.CanCopy())

        self.PopupMenu(menu, event.GetPosition())

    def OnDclick(self, event):
        """Handle double click: If cursor is on an error, display it."""
        t = self.textctrl.PositionToXY(self.textctrl.GetInsertionPoint())
        # The documentation says there are two elements, but in practice we get
        # three?!?!
        if len(t) > 2:
            x, c, l = t
        else:
            c, l = t
        l = l + 1
        if self.positions.get(l):
            fname = self.positions[l]["fname"]
            lnum = self.positions[l]["lnum"]
            Tool.gotoFile(self.topmodel, None, fname, lnum = lnum)

    def OnPmenuCopy(self, event):
        """Popup menu copy handler."""
        self.textctrl.Copy()

    def write(self, msg):
        """Add a message to the console."""

        # Split it up in lines and handle each line.
        linestart = 0
        while linestart < len(msg):
            fname = None
            lnum = 0

            nli = string.find(msg, "\n", linestart)
            if nli < 0:
                # No newline, use the rest of the message.
                text = msg[linestart:]
                linestart = len(msg)
                # Keep the text until the newline is found.
                self.prevline = self.prevline + text
            else:
                # Use the part upto and including the next newline.
                text = msg[linestart:nli + 1]
                linestart = nli + 1
            
                # Only check for error message when the terminating NL was
                # found.
                # XXX Error handling should be flexible.

                # Concatenate with the text of the line so far.
                line = self.prevline + text

                # Recognize Aap and GCC changedir messages:
                #   Entering directory "/home/mool/aaptest"
                i = string.find(line, 'Entering directory `')
                if i >= 0:
                    s = i + 20
                    i = string.find(line, "'", s)
                    if i > 0:
                        self.curdir = line[s:i]

                # Reset "curdir" when building is done:
                #   Finished building hello  (/home/mool/tmp).
                i = string.find(line, 'Finished building ')
                if i >= 0:
                    self.curdir = None

                # Recognize a Python error message:
                #   File "./GUItop.py", line 86, in OnInit
                #   File "./foobar.py", line 123
                i = string.find(line, 'File "')
                if i >= 0:
                    s = i + 6
                    i = string.find(line, '", line ', s)
                    if i > 0:
                        fname = line[s:i]
                        s = i + 8
                        i = s
                        while i < len(line) and line[i] in string.digits:
                            i = i + 1
                        if i > s:
                            lnum = int(line[s:i])

                # Recognize GCC style error:
                #   try.c:11: `j' undeclared (first use in this function)
                if not fname or not lnum:
                    i = string.find(line, ':')
                    if i > 0:
                        e = string.find(line, ':', i + 1)
                        if e > 0:
                            try:
                                lnum = int(line[i + 1:e])
                                fname = line[:i]
                            except:
                                pass

                # Recognize MSVC style error:
                #   try.c(11) : error C234: `j' : undeclared identifier
                if not fname or not lnum:
                    i = string.find(line, '(')
                    if i > 0:
                        e = string.find(line, ')', i + 1)
                        if e > 0:
                            try:
                                lnum = int(line[i + 1:e])
                                fname = line[:i]
                            except:
                                pass

            if fname and lnum:
                if self.prevline:
                    # delete the text that was already displayed
                    p = self.textctrl.GetLastPosition()
                    self.textctrl.Remove(p - len(self.prevline), p)

                l = self.textctrl.GetNumberOfLines()
                if self.curdir:
                    fname = os.path.join(self.curdir, fname)
                self.positions[l] = {"fname" : fname, "lnum" : lnum}
                # style = self.textctrl.GetDefaultStyle()
                self.textctrl.SetDefaultStyle(wxTextAttr("RED"))
                self.textctrl.AppendText(line)
                # This apperently doesn't work...
                # self.textctrl.SetDefaultStyle(style)
                self.textctrl.SetDefaultStyle(wxTextAttr("BLACK"))
            else:
                self.textctrl.AppendText(text)

            # Found a newline, clear prevline.
            if nli >= 0:
                self.prevline = ''


class ConsoleOut:
    """Class that can be used to replace stdout and stderr.
       Displays the text in the console window."""
    def __init__(self, parent, topmodel):
        self.console = None
        self.parent = parent
        self.topmodel = topmodel

        # Open a logfile for debugging if desired.
        fname = os.environ.get("AGIDE_LOGFILE")
        if fname:
            self.logfile = open(fname, "w")
        else:
            self.logfile = None

    def openConsole(self):
        """Open the console window if it currently doesn't exist."""
        if not self.console:
            self.console = Console(None, self, self.topmodel)
            self.console.Show()
            # Call wxYield() to show the window right now.
            wxYield()

    def writelines(self, lines):
        self.openConsole()
        map(self.write, lines)

    def write(self, msg):
        self.openConsole()
        self.console.write(msg)
        if self.logfile:
            self.logfile.write(msg)

    def flush(self):
        pass

    def doRaise(self):
        """Raise the console window, if it exists."""
        if self.console:
            self.console.Raise()

    def shutdown(self):
        """Shutdown: close the console window."""
        if self.console:
            self.console.Destroy()
            self.console = None


class ConsoleErr:
    """Class that can be used to replace stderr.  Same as ConsoleOut, but also
       echoes the text to the original sys.__stderr__."""
    def __init__(self, consoleout):
        self.consoleout = consoleout

    def writelines(self, lines):
        map(self.write, lines)

    def write(self, msg):
        sys.__stderr__.write(msg)
        self.consoleout.write(msg)

    def flush(self):
        sys.__stderr__.flush()


# vim: set sw=4 et sts=4 tw=79 fo+=l: