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

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

"""
Module implementing a compatability interface class to QextScintilla.
"""

import sys

from qt import Qt, QKeyEvent, QEvent, qApp, SIGNAL
from qtext import QextScintilla, \
    QSCINTILLA_VERSION as QextQSCINTILLA_VERSION, QSCINTILLA_VERSION_STR


#####################################################################################
# enums below have been added to QScintilla starting with version after 1.6
#####################################################################################
    
if "WrapFlagNone" not in QextScintilla.__dict__:
    QextScintilla.WrapFlagNone = 0
    QextScintilla.WrapFlagByText = 1
    QextScintilla.WrapFlagByBorder = 2
    
if "AcsAll" not in QextScintilla.__dict__:
    QextScintilla.AcsAll = QextScintilla.AcsDocument
    # identical to AcsDocument for older QScintilla versions

#####################################################################################

def QSCINTILLA_VERSION():
    """
    Module function to return the QScintilla version.
    
    If the installed QScintilla is a snapshot version, then assume it is
    of the latest release and return a version number of 0x99999.
    
    @return QScintilla version (integer)
    """
    if QSCINTILLA_VERSION_STR.startswith('snapshot') or \
       QSCINTILLA_VERSION_STR.startswith('1-snapshot'):
        return 0x99999
    else:
        return QextQSCINTILLA_VERSION
    
#####################################################################################

class QextScintillaCompat(QextScintilla):
    """
    Class implementing a compatability interface to QextScintilla.
    
    This class implements all the functions, that were added to
    QextScintilla incrementally. This class ensures compatibility
    to older versions of QextScintilla.
    """
    def __init__(self, parent=None, name=None, flags=0):
        """
        Constructor
        
        @param parent parent widget (QWidget)
        @param name name of this instance (string or QString)
        @param flags window flags
        """
        QextScintilla.__init__(self, parent, name, flags)
        self.zoom = 0
        
        # compatibility code for QScintilla < 1.7
        if QSCINTILLA_VERSION < 0x010700:
            self.autoCompleteFromAll = self.autoCompleteFromDocument
        
        # workaround for a bug in version <= 1.5.1
        self._modified = 0
        self.connect(self, SIGNAL('modificationChanged(bool)'), 
                    self._handleModificationChanged)
        
        # workaround for an initialization bug in version <= 1.4
        self.clearStyles()
    
    # workaround for a bug in version <= 1.5.1
    def _handleModificationChanged(self, m):
        """
        Private slot to handle the modificationChanged signal. 
        
        It emits the signal modificationStatusChanged with parameters
        m and self.
        
        @param m modification status
        """
        self._modified = m
        
    def isModified(self):
        """
        Public method to retrieve the modification status.
        
        @return flag indicating the modification status (boolean)
        """
        return self._modified
        
    def setModified(self, m):
        """
        Public method to set the modification status.
        
        @param m flag indicating the new modification status
        """
        self._modified = m
        QextScintilla.setModified(self, m)
    
    # workaround for a bug in setLexer() in version <= 1.4
    def setLexer(self, lex = None):
        """
        Public method to set the lexer.
        
        @param lex the lexer to be set or None to reset it.
        """
        QextScintilla.setLexer(self, lex)
        if lex is None:
            self.clearStyles()
        
    def clearStyles(self):
        """
        Public method to set the styles according the selected Qt style.
        """
        colorGroup = qApp.palette().active()
        self.SendScintilla(QextScintilla.SCI_STYLESETFORE,
            QextScintilla.STYLE_DEFAULT, colorGroup.text())
        self.SendScintilla(QextScintilla.SCI_STYLESETBACK,
            QextScintilla.STYLE_DEFAULT, colorGroup.base())
        self.SendScintilla(QextScintilla.SCI_STYLECLEARALL)
        self.SendScintilla(QextScintilla.SCI_CLEARDOCUMENTSTYLE)
        
        self.setMarginsForegroundColor(colorGroup.foreground())
        self.setMarginsBackgroundColor(colorGroup.background())
        self.setFoldMarginColors(colorGroup.mid(),
                                 colorGroup.mid())
        
    def monospacedStyles(self, font):
        """
        Public method to set the current style to be monospaced.
        
        @param font font to be used (QFont)
        """
        try:
            rangeLow = range(self.STYLE_DEFAULT)
        except AttributeError:
            rangeLow = range(32)
        try:
            rangeHigh = range(self.STYLE_LASTPREDEFINED + 1,
                              self.STYLE_MAX + 1)
        except AttributeError:
            rangeHigh = range(40, 128)
            
        f = font.family().latin1()
        ps = font.pointSize()
        for style in rangeLow + rangeHigh:
            self.SendScintilla(QextScintilla.SCI_STYLESETFONT, style, f)
            self.SendScintilla(QextScintilla.SCI_STYLESETSIZE, style, ps)
        
    def lineAt(self, pos):
        """
        Public method to calculate the line at a position.
        
        This variant is able to calculate the line for positions in the
        margins and for empty lines.
        
        @param pos position to calculate the line for (QPoint)
        @return linenumber at position or -1, if there is no line at pos
            (integer, zero based)
        """
        scipos = self.SendScintilla(QextScintilla.SCI_POSITIONFROMPOINT, pos.x(), pos.y())
        line = self.SendScintilla(QextScintilla.SCI_LINEFROMPOSITION, scipos)
        if line >= self.lines():
            line = -1
        return line
    
    #####################################################################################
    # methods below are missing from QScintilla
    #####################################################################################

    def zoomIn(self, zoom = 1):
        """
        Public method used to increase the zoom factor.
        
        @param zoom zoom factor increment
        """
        self.zoom += zoom
        QextScintilla.zoomIn(self, zoom)
        
    def zoomOut(self, zoom = 1):
        """
        Public method used to decrease the zoom factor.
        
        @param zoom zoom factor decrement
        """
        self.zoom -= zoom
        QextScintilla.zoomOut(self, zoom)
        
    def zoomTo(self, zoom):
        """
        Public method used to zoom to a specific zoom factor.
        
        @param zoom zoom factor
        """
        self.zoom = zoom
        QextScintilla.zoomTo(self, zoom)
        
    def getZoom(self):
        """
        Public method used to retrieve the current zoom factor.
        
        @return zoom factor (int)
        """
        return self.zoom
        
    def editorCommand(self, cmd):
        """
        Public method to perform a simple editor command.
        
        @param cmd the scintilla command to be performed
        """
        self.SendScintilla(cmd)
        
    def scrollVertical(self, lines):
        """
        Public method to scroll the text area.
        
        @param lines number of lines to scroll (negative scrolls up,
            positive scrolls down) (integer)
        """
        self.SendScintilla(QextScintilla.SCI_LINESCROLL, 0, lines)
        
    def moveCursorToEOL(self):
        """
        Public method to move the cursor to the end of line.
        """
        self.SendScintilla(QextScintilla.SCI_LINEEND)
        
    def moveCursorLeft(self):
        """
        Public method to move the cursor left.
        """
        self.SendScintilla(QextScintilla.SCI_CHARLEFT)
        
    def moveCursorRight(self):
        """
        Public method to move the cursor right.
        """
        self.SendScintilla(QextScintilla.SCI_CHARRIGHT)
        
    def moveCursorWordLeft(self):
        """
        Public method to move the cursor left one word.
        """
        self.SendScintilla(QextScintilla.SCI_WORDLEFT)
        
    def moveCursorWordRight(self):
        """
        Public method to move the cursor right one word.
        """
        self.SendScintilla(QextScintilla.SCI_WORDRIGHT)
        
    def deleteBack(self):
        """
        Public method to delete the character to the left of the cursor.
        """
        self.SendScintilla(QextScintilla.SCI_DELETEBACK)
        
    def delete(self):
        """
        Public method to delete the character to the right of the cursor.
        """
        self.SendScintilla(QextScintilla.SCI_CLEAR)
        
    def deleteWordLeft(self):
        """
        Public method to delete the word to the left of the cursor.
        """
        self.SendScintilla(QextScintilla.SCI_DELWORDLEFT)
        
    def deleteWordRight(self):
        """
        Public method to delete the word to the right of the cursor.
        """
        self.SendScintilla(QextScintilla.SCI_DELWORDRIGHT)
        
    def deleteLineLeft(self):
        """
        Public method to delete the line to the left of the cursor.
        """
        self.SendScintilla(QextScintilla.SCI_DELLINELEFT)
        
    def deleteLineRight(self):
        """
        Public method to delete the line to the right of the cursor.
        """
        self.SendScintilla(QextScintilla.SCI_DELLINERIGHT)
        
    def extendSelectionLeft(self):
        """
        Public method to extend the selection one character to the left.
        """
        self.SendScintilla(QextScintilla.SCI_CHARLEFTEXTEND)
        
    def extendSelectionRight(self):
        """
        Public method to extend the selection one character to the right.
        """
        self.SendScintilla(QextScintilla.SCI_CHARRIGHTEXTEND)
        
    def extendSelectionWordLeft(self):
        """
        Public method to extend the selection one word to the left.
        """
        self.SendScintilla(QextScintilla.SCI_WORDLEFTEXTEND)
        
    def extendSelectionWordRight(self):
        """
        Public method to extend the selection one word to the right.
        """
        self.SendScintilla(QextScintilla.SCI_WORDRIGHTEXTEND)
        
    def extendSelectionToBOL(self):
        """
        Public method to extend the selection to the beginning of the line.
        """
        self.SendScintilla(QextScintilla.SCI_VCHOMEEXTEND)
        
    def extendSelectionToEOL(self):
        """
        Public method to extend the selection to the end of the line.
        """
        self.SendScintilla(QextScintilla.SCI_LINEENDEXTEND)
    
    #####################################################################################
    # methods below have been added to QScintilla starting with version after 1.6
    #####################################################################################
    
    if "setAutoCompletionFillupsEnabled" not in QextScintilla.__dict__:
        def setAutoCompletionFillupsEnabled(self, on):
            """
            Public method to enable the use of autocompletion fillup characters.
            
            @param on flag indicating the usage of autocompletion fillup 
                characters (boolean)
            """
            if on:
                self.setAutoCompletionFillups("(")
            else:
                self.setAutoCompletionFillups("")
        
    if "isCalltipActive" not in QextScintilla.__dict__:
        def isCallTipActive(self):
            """
            Public method to check, if a call tip is active.
            
            @return flag indicating call tip active state (boolean)
            """
            return self.SendScintilla(QextScintilla.SCI_CALLTIPACTIVE)
        
    if "isListActive" not in QextScintilla.__dict__:
        def isListActive(self):
            """
            Public method to check, if autocompletion or user-defined list is active.
            
            @return flag indicating autocompletion active state (boolean)
            """
            return self.SendScintilla(QextScintilla.SCI_AUTOCACTIVE)
        
    if "cancelList" not in QextScintilla.__dict__:
        def cancelList(self):
            """
            Public method to cancel autocompletion list or user-defined list.
            """
            self.SendScintilla(QextScintilla.SCI_AUTOCCANCEL)
        
    if "showUserList" not in QextScintilla.__dict__:
        def showUserList(self, id, userlist):
            """
            Public method to show a user list.
            
            @param id id of the list to be shown (integer)
            @param userlist list of items to be shown (QStringList)
            """
            if id <= 0:
                return
            
            liststring = str(userlist.join(' '))
            self.SendScintilla(QextScintilla.SCI_USERLISTSHOW, id, liststring)
        
    if "firstVisibleLine" not in QextScintilla.__dict__:
        def firstVisibleLine(self):
            """
            Public method to get the line number of the first visible line.
            
            @return number of the first visible line (integer)
            """
            return self.SendScintilla(QextScintilla.SCI_GETFIRSTVISIBLELINE)
        
    if "textHeight" not in QextScintilla.__dict__:
        def textHeight(self, linenr):
            """
            Public method to get the text height of a line.
            
            @param linenr number of the line (integer)
            @return text height (integer)
            """
            return self.SendScintilla(QextScintilla.SCI_TEXTHEIGHT, linenr)
        
    if "setWrapVisualFlags" not in QextScintilla.__dict__:
        def setWrapVisualFlags(self, eflag, sflag = QextScintilla.WrapFlagNone,
            sindent = 0):
            """
            Public method to set the visual flags for wrapped lines.
            
            @param eflag determines if and where the flag at the end of a line
                is displayed
            @param sflag determines if and where the flag at the start of a line
                is displayed
            @param sindent is the number of characters a wrapped line is indented by
            """
            flags = QextScintilla.SC_WRAPVISUALFLAG_NONE
            loc = QextScintilla.SC_WRAPVISUALFLAGLOC_DEFAULT
            
            if eflag == QextScintilla.WrapFlagByText:
                flags |= QextScintilla.SC_WRAPVISUALFLAG_END
                loc |= QextScintilla.SC_WRAPVISUALFLAGLOC_END_BY_TEXT
            elif eflag == QextScintilla.WrapFlagByBorder:
                flags |= QextScintilla.SC_WRAPVISUALFLAG_END
            
            if sflag == QextScintilla.WrapFlagByText:
                flags |= QextScintilla.SC_WRAPVISUALFLAG_START
                loc |= QextScintilla.SC_WRAPVISUALFLAGLOC_START_BY_TEXT
            elif sflag == QextScintilla.WrapFlagByBorder:
                flags |= QextScintilla.SC_WRAPVISUALFLAG_START
            
            self.SendScintilla(QextScintilla.SCI_SETWRAPVISUALFLAGS, flags)
            self.SendScintilla(QextScintilla.SCI_SETWRAPVISUALFLAGSLOCATION, loc)
            self.SendScintilla(QextScintilla.SCI_SETWRAPSTARTINDENT, sindent)
            
        def setLayoutCache(self, cacheMode):
            """
            Public method to set the layout cache mode.
            
            @param cacheMode layout cache mode to be set
            """
            self.SendScintilla(QextScintilla.SCI_SETLAYOUTCACHE, cacheMode)
    else:
        def setLayoutCache(self, cacheMode):
            """
            Public method to set the layout cache mode.
            
            @param cacheMode layout cache mode to be set
            """
            # do nothing, it is handled by QextScintilla
            pass
    
    #####################################################################################
    # methods below have been added to QScintilla starting with version after 1.3
    #####################################################################################
    
    if "setEdgeMode" not in QextScintilla.__dict__:
        def setEdgeMode(self, edgeMode):
            """
            Public method to set the edge mode.
            
            @param edgeMode mode to be set
            """
            self.SendScintilla(QextScintilla.SCI_SETEDGEMODE, edgeMode)
        
    if "setEdgeColumn" not in QextScintilla.__dict__:
        def setEdgeColumn(self, column):
            """
            Public method to set the edge column.
            
            @param column column number to be set for the edge mode (integer)
            """
            self.SendScintilla(QextScintilla.SCI_SETEDGECOLUMN, column)
        
    if "setEdgeColor" not in QextScintilla.__dict__:
        def setEdgeColor(self, colour):
            """
            Public method to set the color of the edge mode highlight.
            
            @param colour colour to be set (QColor)
            """
            self.SendScintilla(QextScintilla.SCI_SETEDGECOLOUR, colour)
        
    if "setWrapMode" not in QextScintilla.__dict__:
        def setWrapMode(self, mode):
            """
            Public slot to set the wrap mode of the text display.
            
            @param mode wrap mode (QextScintilla.WrapNone or
                QextScintilla.WrapWord)
            """
            self.SendScintilla(QextScintilla.SCI_SETWRAPMODE, mode)
        
    if "resetSelectionForegroundColor" not in QextScintilla.__dict__:
        def resetSelectionForegroundColor(self):
            """
            Public method to reset the foreground color of the selection.
            """
            self.SendScintilla(QextScintilla.SCI_SETSELFORE, 0)
        
        # compatibility method to correct a bug under win32
        if sys.platform == "win32":
            def keyPressEvent(self, event):
                """
                Workaround for QScintilla <= 1.3 bug w/ keyboard layouts 
                having a AltGr key in win32: 
                AltGr is reported as state == Qt.ControlButton | Qt.AltButton,
                but QScintilla expects (Unix-style) Qt.MetaButton. Just 
                translate states for infected QScintilla versions.
                
                Reimplemented from QextScintilla.keyPressEvent
                
                @param event reference to the key press event (QKeyEvent)
                @author Torsten Marek, 2004-08-22
                """
                if event.state() == Qt.AltButton | Qt.ControlButton:
                    newevent = QKeyEvent(QEvent.KeyPress,
                                         event.key(),
                                         event.ascii(),
                                         Qt.MetaButton,
                                         event.text())
                    event = newevent
                QextScintilla.keyPressEvent(self, event)

    #####################################################################################
    # methods below have been added to QScintilla starting with version after 1.2
    #####################################################################################
    
    if "setSelectionBackgroundColor" not in QextScintilla.__dict__:
        def setSelectionBackgroundColor(self, colour):
            """
            Public method to set the background color of the selection.
            
            @param colour colour to be set (QColor)
            """
            self.SendScintilla(QextScintilla.SCI_SETSELBACK, 1, colour)
        
    if "setSelectionForegroundColor" not in QextScintilla.__dict__:
        def setSelectionForegroundColor(self, colour):
            """
            Public method to set the foreground color of the selection.
            
            @param colour colour to be set (QColor)
            """
            self.SendScintilla(QextScintilla.SCI_SETSELFORE, 1, colour)
        
    if "setCaretForegroundColor" not in QextScintilla.__dict__:
        def setCaretForegroundColor(self, colour):
            """
            Public method to set the foreground color of the caret.
            
            @param colour colour to be set (QColor)
            """
            self.SendScintilla(QextScintilla.SCI_SETCARETFORE, colour)
        
    if "setCaretLineBackgroundColor" not in QextScintilla.__dict__:
        def setCaretLineBackgroundColor(self, colour):
            """
            Public method to set the background color of the caret line.
            
            @param colour colour to be set (QColor)
            """
            self.SendScintilla(QextScintilla.SCI_SETCARETLINEBACK, colour)
        
    if "setCaretLineVisible" not in QextScintilla.__dict__:
        def setCaretLineVisible(self, enable):
            """
            Public method to enable the highlighting of the caret line.
            
            @param enable flag indicating the highlighting state to be set (boolean)
            """
            self.SendScintilla(QextScintilla.SCI_SETCARETLINEVISIBLE, enable)
        
    if "setCaretWidth" not in QextScintilla.__dict__:
        def setCaretWidth(self, width):
            """
            Public method to enable the highlighting of the caret line.
            
            @param width width of the caret (integer - 0, 1, 2 or 3)
            """
            if width < 0:
                width = 0
            elif width > 3:
                width = 3
            self.SendScintilla(QextScintilla.SCI_SETCARETWIDTH, width)
        
    if "recolor" not in QextScintilla.__dict__:
        def recolor(self, start = 0, end = -1):
            """
            Public method to recolor the given region.
            
            @param start linenumber where to start recolourisation (integer)
            @param end linenumber where to end recolourisation (integer)
            """
            self.SendScintilla(QextScintilla.SCI_COLOURISE, start, end)

    #####################################################################################
    # interface methods to the standard keyboard command set
    #####################################################################################
    
    def clearAlternateKeys(self):
        """
        Protected method to clear the alternate key commands.
        """
        if "clearAlternateKeys" not in self.standardCommands().__dict__:
            # clear each alternate key for QScintilla versions <= 1.3
            for cmd in self.standardCommands().commands():
                cmd.setAlternateKey(0)
        else:
            # call into the QextScintillaCommandSet
            self.standardCommands().clearAlternateKeys()
