'''
LNTextEdit v4.3
Text widget with support for line numbers
http://john.nachtimwald.com/2009/08/19/better-qplaintextedit-with-line-numbers/

mod: by ying - http://shining-lucy.com/wiki
v4.3 (2020.02.14): 
  * add monoFont function
v4.2
  * fix lineWrap cmd typo
v4.1
 * fix qt5 qpallete code in qtgui
v4.0
 * python 3 support
 * pyside, pyside2, pyqt4, pyqt5 support
v3.2
 * re/set/getFontSize and font size add zoom in out and scoll zoom event
v3.1.2
 * add unicode support url path name
v3.1 
 * add readyOnly function
 * add drag and drop function (multiple files)
 * add highlight toggle
 * add text return function
 * clean and update Qt code
 * remove qvariant and update more code to work for both pyside and pyqt
'''

# python 2,3 support unicode function
try:
    UNICODE_EXISTS = bool(type(unicode))
except NameError:
    unicode = lambda s: str(s)

try:
    from PySide import QtGui, QtCore
    import PySide.QtGui as QtWidgets
    print("PySide Try")
    qtMode = 0
except ImportError:
    try:
        from PySide2 import QtCore, QtGui, QtWidgets
        print("PySide2 Try")
        qtMode = 2
    except ImportError:
        try:
            from PyQt4 import QtGui,QtCore
            import PyQt4.QtGui as QtWidgets
            import sip
            qtMode = 1
            print("PyQt4 Try")
        except ImportError:
            from PyQt5 import QtGui,QtCore,QtWidgets
            import sip
            qtMode = 3
            print("PyQt5 Try")
            
class LNTextEdit(QtWidgets.QFrame):
 
    class NumberBar(QtWidgets.QWidget):
 
        def __init__(self, edit):
            QtWidgets.QWidget.__init__(self, edit)
            
            self.edit = edit
            self.adjustWidth(1)
            
        def paintEvent(self, event):
            self.edit.numberbarPaint(self, event)
            QtWidgets.QWidget.paintEvent(self, event)
 
        def adjustWidth(self, count):
            width = self.fontMetrics().width(unicode(count))
            if self.width() != width:
                self.setFixedWidth(width)
 
        def updateContents(self, rect, scroll):
            if scroll:
                self.scroll(0, scroll)
            else:
                # It would be nice to do
                # self.update(0, rect.y(), self.width(), rect.height())
                # But we can't because it will not remove the bold on the
                # current line if word wrap is enabled and a new block is
                # selected.
                self.update()
                
    class PlainTextEdit(QtWidgets.QPlainTextEdit):
        def __init__(self, *args):
            QtWidgets.QPlainTextEdit.__init__(self, *args)
            self.setFrameStyle(QtWidgets.QFrame.NoFrame)
            self.zoomWheelEnabled = 0
            self.highlight()
            #self.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap)
            self.cursorPositionChanged.connect(self.highlight)
        
        def dragEnterEvent( self, event ):
            data = event.mimeData()
            urls = data.urls()
            if ( urls and urls[0].scheme() == 'file' ):
                event.acceptProposedAction()

        def dragMoveEvent( self, event ):
            data = event.mimeData()
            urls = data.urls()
            if ( urls and urls[0].scheme() == 'file' ):
                event.acceptProposedAction()

        def dropEvent( self, event ):
            data = event.mimeData()
            urls = data.urls()
            if ( urls and urls[0].scheme() == 'file' ):
                txt = "\n".join( [unicode(url.path())[1:] for url in urls] ) # remove 1st / char
                self.insertPlainText( txt ) 
        def zoom_in(self):
            font = self.document().defaultFont()
            size = font.pointSize()
            if size < 28:
                size += 2
                font.setPointSize(size)
            self.setFont(font)
        def zoom_out(self):
            font = self.document().defaultFont()
            size = font.pointSize()
            if size > 6:
                size -= 2
                font.setPointSize(size)
            self.setFont(font)
        def wheelEvent(self, event, forward=True):
            if event.modifiers() == QtCore.Qt.ControlModifier:
                if self.zoomWheelEnabled == 1:
                    if event.delta() == 120:
                        self.zoom_in()
                    elif event.delta() == -120:
                        self.zoom_out()
                event.ignore()
            QtWidgets.QPlainTextEdit.wheelEvent(self, event)
            
        def highlight(self):
            hi_selection = QtWidgets.QTextEdit.ExtraSelection()
 
            hi_selection.format.setBackground(self.palette().alternateBase())
            hi_selection.format.setProperty(QtGui.QTextFormat.FullWidthSelection, 1) #QtCore.QVariant(True)
            hi_selection.cursor = self.textCursor()
            hi_selection.cursor.clearSelection()
 
            self.setExtraSelections([hi_selection])
 
        def numberbarPaint(self, number_bar, event):
            font_metrics = self.fontMetrics()
            current_line = self.document().findBlock(self.textCursor().position()).blockNumber() + 1
 
            block = self.firstVisibleBlock()
            line_count = block.blockNumber()
            painter = QtGui.QPainter(number_bar)
            painter.fillRect(event.rect(), self.palette().base())
 
            # Iterate over all visible text blocks in the document.
            while block.isValid():
                line_count += 1
                block_top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top()
 
                # Check if the position of the block is out side of the visible
                # area.
                if not block.isVisible() or block_top >= event.rect().bottom():
                    break
 
                # We want the line number for the selected line to be bold.
                if line_count == current_line:
                    font = painter.font()
                    font.setBold(True)
                    painter.setFont(font)
                else:
                    font = painter.font()
                    font.setBold(False)
                    painter.setFont(font)
 
                # Draw the line number right justified at the position of the line.
                paint_rect = QtCore.QRect(0, block_top, number_bar.width(), font_metrics.height())
                painter.drawText(paint_rect, QtCore.Qt.AlignRight, unicode(line_count))
 
                block = block.next()
 
            painter.end()
 
    def __init__(self, *args):
        QtWidgets.QFrame.__init__(self, *args)
 
        self.setFrameStyle(QtWidgets.QFrame.StyledPanel | QtWidgets.QFrame.Sunken)
 
        self.edit = self.PlainTextEdit()
        self.number_bar = self.NumberBar(self.edit)
 
        hbox = QtWidgets.QHBoxLayout(self)
        hbox.setSpacing(0)
        hbox.setContentsMargins(0,0,0,0) # setMargin
        hbox.addWidget(self.number_bar)
        hbox.addWidget(self.edit)
 
        self.edit.blockCountChanged.connect(self.number_bar.adjustWidth)
        self.edit.updateRequest.connect(self.number_bar.updateContents)
    
    def text(self):
        return unicode(self.edit.toPlainText())
    def clear(self):
        self.setText('')
    def getText(self):
        return unicode(self.edit.toPlainText())
    def setText(self, text):
        self.edit.setPlainText(text)
    def insertText(self, text):
        self.edit.insertPlainText(text)
    def addText(self, text):
        self.edit.setPlainText(self.edit.toPlainText()+text)
    def insertPlainText(self, text):
        self.insertText(text)
    def isModified(self):
        return self.edit.document().isModified() 
    def setModified(self, modified):
        self.edit.document().setModified(modified)
    
    def setLineWrapMode(self, mode):
        self.setWrap(mode)
    def setWrap(self, state):
        if state == 0:
            self.edit.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap)
        else:
            self.edit.setLineWrapMode(QtWidgets.QPlainTextEdit.WidgetWidth)
    def setReadOnly(self, state):
        self.edit.setReadOnly(state)
    def setReadOnlyStyle(self, state):
        if state == 1:
            mainWindowBgColor = QtGui.QPalette().color(QtGui.QPalette.Window)
            self.setStyleSheet('QPlainTextEdit[readOnly="true"] { background-color: %s;} QFrame {border: 0px}' % mainWindowBgColor.name() )
            self.setHighlight(0)
        else:
            self.setStyleSheet('')
            self.setHighlight(1)
    def setFontSize(self, value):
        font = self.edit.document().defaultFont()
        if value > 6 and value < 28:
            font.setPointSize(value)
            self.edit.setFont(font)
    def getFontSize(self):
        font = self.edit.document().defaultFont()
        size = font.pointSize()
        return size
    def resetFontSize(self):
        font = self.edit.document().defaultFont()
        font.setPointSize(8)
        self.edit.setFont(font)
    def monoFont(self, state):
        if state == 1:
            self.edit.setStyleSheet('QPlainTextEdit {font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;}')
        else:
            self.edit.setStyleSheet('')
    def setZoom(self,mode):
        if mode == 0:
            self.edit.zoomWheelEnabled = 0
        else:
            self.edit.zoomWheelEnabled = 1
    def setHighlight(self, state):
        txtEdit = self.edit
        if state == 0:
            txtEdit.cursorPositionChanged.disconnect()
            txtEdit.setExtraSelections([])
        else:
            txtEdit.cursorPositionChanged.connect(txtEdit.highlight)