Ejemplo n.º 1
0
    def __initData(self,textDocumentObj,settingsObj):
        # 为了代码的可读性,将部分属性值(影响文本显示的属性值)放置到EditorSettings类中
        self.__settings = settingsObj
        self.settings = lambda : self.__settings

        # 为了代码的可读性,将部分数据的操作(关于鼠标选中的文本)放置到EditorSettings类中
        self.__selectedTextManager = SelectedTextManager()
        self.selectedTextManager = lambda : self.__selectedTextManager

        self.__textDocument = textDocumentObj
        self.__textDocument.setFont(self.settings().getFont(),self.settings().getFontMetrics())

        self.__textCursor = TextCursor(self)
        self.__textCursor.cursorPosChangedSignal.connect(self.onCursorPosChanged)
        self.__textCursor.initPos( (0,0) )
        self.__textCursor.setFocus(QtCore.Qt.MouseFocusReason)
        
        self.settings().fontChangedSignal.connect(self.onFontChanged)
        self.settings().startDisLineNumberChangedSignal.connect( self.onStartDisLineNumberChanged )
        self.settings().startDisLetterXOffChangedSignal.connect( self.onStartDisLetterXOffChanged )
        self.settings().lineTextMaxPixelChangedSignal.connect( self.onLineTextMaxPixelChanged )
        self.settings().editableChangedSignal.connect( self.onEditableChanged )
        
        self.selectedTextManager().selectedTextChangedSignal.connect( self.onSelectedTextChanged )
Ejemplo n.º 2
0
class BaseTextWidget(QWidget):
    @staticmethod
    def generateIDStrByObjAndEvent(obj,eventType):
        return '%s__%s'%( str(id(obj)),str(eventType) )
    
    
    # 在每次重绘的时候,该函数都将被调用,以此通知显示行号的模块(LineNumberWidget)应该显示哪些行号,显示的位置如何
    visibleLineYOffInfoChangedSignal = QtCore.pyqtSignal( list )
        
    def showLineNumberAsTop(self,lineNumber,update=True):
        suitedLineNumber = FUF.calcMidNumberByRange( 0,lineNumber,self.document().getLineCount()-1 )
        self.settings().setStartDisLineNumber(suitedLineNumber,update)
        
    def showLeftXOffAsLeft(self,xOff,update=True):
        self.settings().setStartDisLetterXOff( 0 if xOff < 0 else xOff,update )
    
    
    
    
    def onSelectedTextChanged(self):
        self.update()
    
    def onFontChanged(self,newFontObj):
        self.__textDocument.setFont(self.settings().getFont(), self.settings().getFontMetrics())
        self.update()
    
    # 当光标的位置改变时,需要刷新原来的行以及新行
    def onCursorPosChanged(self):
        self.update()
    
    def onStartDisLineNumberChanged(self,curV):
        self.update()

    def onStartDisLetterXOffChanged(self,curV):
        self.update()

    def onLineTextMaxPixelChanged(self,curV):
        self.update()

    def onEditableChanged(self,curState):
        self.update()
    
    def onGlobalEvents(self,obj,event):
        dispacherInfo = self.settings().getUserDataByKey('eventDispacherInfo',{})
        func = dispacherInfo.get( BaseTextWidget.generateIDStrByObjAndEvent(obj, event.type()) )        
        if func != None:
            return func(obj,event)
    
    # 注册事件转换器,将obj上发生的eventType类型交给func处理
    # 信息存储时的数据结构:所有信息存储在一个dict中
    #     key:BaseTextWidget.generateIDStrByObjAndEvent计算得到
    #     value:func
    def regEventDispacher(self,obj,eventType,func):
        dispacherInfo = self.settings().getUserDataByKey('eventDispacherInfo',{})
        key = BaseTextWidget.generateIDStrByObjAndEvent(obj, eventType)
        if dispacherInfo.get(key) == None:
            dispacherInfo[key] = func
        self.settings().setUserDataByKey( 'eventDispacherInfo',dispacherInfo )

    def dispacherOnInputMethod(self,obj,event):
        self.insertStr(event.commitString())
        return True
    
    def __installEventDispacher(self):
        self.regEventDispacher(self, QtCore.QEvent.InputMethod, self.dispacherOnInputMethod)
        self.regEventDispacher(self.__textCursor, QtCore.QEvent.InputMethod, self.dispacherOnInputMethod)
        

    
    def __init__(self,textDocumentObj = None,settingsObj = None,parent=None):
        QWidget.__init__(self,parent)
        self.setCursor( QtCore.Qt.IBeamCursor )
        
        textDocumentObj = TextDocument() if textDocumentObj == None else textDocumentObj 
        settingsObj = EditorSettings() if settingsObj == None else settingsObj
        self.__initData( textDocumentObj,settingsObj )
        
        self.__globalEventFilter = __EventFilter__( self.onGlobalEvents )
        self.__installEventDispacher()
        QtWidgets.qApp.installEventFilter( self.__globalEventFilter )
        
        
        self.setText = self.__textDocument.setText
        self.getText = self.__textDocument.getText
        self.document = lambda : self.__textDocument
        self.cursor = lambda : self.__textCursor
    
    def __initData(self,textDocumentObj,settingsObj):
        # 为了代码的可读性,将部分属性值(影响文本显示的属性值)放置到EditorSettings类中
        self.__settings = settingsObj
        self.settings = lambda : self.__settings

        # 为了代码的可读性,将部分数据的操作(关于鼠标选中的文本)放置到EditorSettings类中
        self.__selectedTextManager = SelectedTextManager()
        self.selectedTextManager = lambda : self.__selectedTextManager

        self.__textDocument = textDocumentObj
        self.__textDocument.setFont(self.settings().getFont(),self.settings().getFontMetrics())

        self.__textCursor = TextCursor(self)
        self.__textCursor.cursorPosChangedSignal.connect(self.onCursorPosChanged)
        self.__textCursor.initPos( (0,0) )
        self.__textCursor.setFocus(QtCore.Qt.MouseFocusReason)
        
        self.settings().fontChangedSignal.connect(self.onFontChanged)
        self.settings().startDisLineNumberChangedSignal.connect( self.onStartDisLineNumberChanged )
        self.settings().startDisLetterXOffChangedSignal.connect( self.onStartDisLetterXOffChanged )
        self.settings().lineTextMaxPixelChangedSignal.connect( self.onLineTextMaxPixelChanged )
        self.settings().editableChangedSignal.connect( self.onEditableChanged )
        
        self.selectedTextManager().selectedTextChangedSignal.connect( self.onSelectedTextChanged )
    

    



                    

    # 以当前的设置为准,更新第lineIndex对应的矩形区域
    def updateLineIndexRect(self,lineIndex,height):
        for item in self.calcAnyVisibleYOff():
            if item['lineIndex'] == lineIndex:
                self.update( 0, item['lineYOff'],self.width(),height )
                break

    # 根据光标的全局位置,计算出当前视口下光标的实际位置,返回tuple
    def transGloPixelPosToCurPixelPos(self,xyGloPixelPosTuple):
        return (xyGloPixelPosTuple[0] - self.settings().getStartDisLetterXOff(), \
                xyGloPixelPosTuple[1] - self.settings().getFontMetrics().lineSpacing() * self.settings().getStartDisLineNumber() )
        
    # 根据光标在当前视口下的位置,计算出光标的全局位置,返回tuple
    def transCurPixelPosToGloPixelPos(self,xyCurPixelPosTuple):
        return (xyCurPixelPosTuple[0] + self.settings().getStartDisLetterXOff(), \
                xyCurPixelPosTuple[1] + self.settings().getFontMetrics().lineSpacing() * self.settings().getStartDisLineNumber() )        
   
    # 根据全局的indexPos,得到全局的pixelPos
    def transGloIndexPosToGloPixelPos(self,xyIndexPosTuple):
        xIndexPos,yIndexPos = xyIndexPosTuple
        charWidthInfoArr = self.__textDocument.getLineCharWidthArrayByIndex(yIndexPos)
               
        yPixelPos = yIndexPos*self.settings().getFontMetrics().lineSpacing() + CEGD.TextYOff
        xPixelPos = 0
        for i in range(xIndexPos):
            xPixelPos += CEGD.CharDistancePixel + charWidthInfoArr[i]
        return (xPixelPos,yPixelPos)
        
    # 根据用户点击的位置,计算出光标应该处于的位置
    def transUserClickedPixelPosToIndexPos(self,xyClickedPixelPosTuple):
        x,y = xyClickedPixelPosTuple
        x = max([x,0])
        lineIndex = self.settings().getStartDisLineNumber() + int((y-CEGD.TextYOff)/self.settings().getFontMetrics().lineSpacing())
        lineIndex = FUF.calcMidNumberByRange(0, lineIndex, self.__textDocument.getLineCount()-1)
        
        charWidthArray = self.__textDocument.getLineCharWidthArrayByIndex(lineIndex)
                
        startX = 0 - self.settings().getStartDisLetterXOff()
        xIndex = 0
        while xIndex < len(charWidthArray):
            charWidth = charWidthArray[xIndex]
            startX += charWidth + CEGD.CharDistancePixel
            xIndex += 1
            if startX >= x:
                break
        if startX > x:
            if ( startX - (charWidth + CEGD.CharDistancePixel)/2 > x ):
                startX -= (charWidth + CEGD.CharDistancePixel)
                xIndex -= 1
        return RetuInfo.info( indexPos = ( xIndex,lineIndex ) , offset = x-startX )



    # 计算一共可以显示多少行
    def calcDisLineNumber(self):
        return int((self.height()-CEGD.TextYOff) / self.settings().getFontMetrics().lineSpacing())
    
    def calcMaxStartDisLineNumber(self):
        return max([ 0,self.__textDocument.getLineCount()-1-(self.calcDisLineNumber()-1) ])


    # 计算每行文本的y偏移(行号和文本的y偏移都一样)
    def calcAnyVisibleYOff(self):
        yOffArray = []
        for i in range( self.settings().getStartDisLineNumber(),self.__textDocument.getLineCount() ):
            curY = CEGD.TextYOff + self.settings().getFontMetrics().lineSpacing() * (i-self.settings().getStartDisLineNumber())
            if curY > self.height():
                break
            yOffArray.append( {'lineIndex':i,'lineYOff':curY} )
        return yOffArray
    
    
    # 子类通过重写beforeDraw、afterDraw来实现对界面的修改,而不要直接重写paintEvent
    # 之所以子类尽量不要重写paintEvent,是因为子类中大部分情况是为了高亮某段文本
    #     如果先绘制文本,再用半透明的高亮画刷绘制高亮区域,会导致之前绘制的文本的颜色大幅度变浅,影响用户查看
    #     而如果先用高亮画刷绘制高亮区域,再绘制文本即可解决上述问题
    # 如果子类的绘制可以晚于文本的绘制,并不会造成不利影响,那么子类重写paintEvent也是可以的
    def beforePaint(self,painter):
        pass
    
    def afterPaint(self,painter):
        pass

    def paintEvent(self,event):
        visibleLineYOffInfoArray = self.calcAnyVisibleYOff()
        self.visibleLineYOffInfoChangedSignal.emit( visibleLineYOffInfoArray )
        
        painter = QtGui.QPainter(self)
        painter.setFont(self.settings().getFont())
        painter.setPen(QtCore.Qt.NoPen)
        painter.setBrush(CEGD.WhiteOpaqueBrush)
        painter.drawRect(0,0,self.width(),self.height())
        
        painter.save()
        self.beforePaint(painter)
        painter.restore()
        
        painter.save()
        self.__drawLineText(painter,visibleLineYOffInfoArray,event.rect())
        painter.restore()
        
        painter.save()
        self.__refreshCursor(painter,visibleLineYOffInfoArray)
        painter.restore()

        painter.save()
        self.afterPaint(painter)
        painter.restore()

                
        
    # 绘制每行文本
    def __drawLineText(self,painter,visibleLineYOffInfoArray,redrawRect):
        for item in visibleLineYOffInfoArray:
            lineYOff = item['lineYOff']
            lineIndex = item['lineIndex']
            pixmapObj = self.__textDocument.getLinePixmapByIndex(lineIndex)
            if lineYOff >= redrawRect.y() and lineYOff <= redrawRect.y() + redrawRect.height():            
                painter.drawPixmap( - self.settings().getStartDisLetterXOff() , lineYOff , pixmapObj )
        self.settings().setLineTextMaxPixel(self.__textDocument.getMaxLineWidth())


    # 绘制鼠标相关
    def __refreshCursor(self,painter,visibleLineYOffInfoArray):
        drawCursorSign = False
        for item in visibleLineYOffInfoArray:
            if item['lineIndex'] == self.__textCursor.getCursorIndexPos()[1]:     
                drawCursorSign = True
                break
        
        if drawCursorSign == False:
            self.__textCursor.hide()
            return 
        
        # 绘制光标
        cursorPos = self.transGloPixelPosToCurPixelPos( self.transGloIndexPosToGloPixelPos(self.__textCursor.getCursorIndexPos()) )
        self.__textCursor.setGeometry(cursorPos[0],cursorPos[1],CEGD.CursorWidth,self.settings().getFontMetrics().lineSpacing())

        # 绘制光标所在行高亮
        if self.selectedTextManager().getSelectedTextIndexPos() == None:
            lineTextRect = QtCore.QRect( 0,cursorPos[1],self.width(),self.settings().getFontMetrics().lineSpacing() )
            painter.fillRect( lineTextRect ,CEGD.LineSelectedBKBrush)