Beispiel #1
0
class XNavigationEdit(XLineEdit):
    """ """
    navigationChanged = Signal()
    
    __designer_icon__ = projexui.resources.find('img/ui/navigate.png')
    
    def __init__( self, parent = None ):
        super(XNavigationEdit, self).__init__( parent )
        
        # define custom properties
        self._separator             = '/'
        self._partsEditingEnabled   = True
        self._originalText          = ''
        self._scrollWidget          = QScrollArea(self)
        self._partsWidget           = QWidget(self._scrollWidget)
        self._buttonGroup           = QButtonGroup(self)
        self._scrollAmount          = 0
        self._navigationModel       = None
        
        # create the completer tree
        palette = self.palette()
        palette.setColor(palette.Base, palette.color(palette.Window))
        palette.setColor(palette.Text, palette.color(palette.WindowText))
        
        bg      = palette.color(palette.Highlight)
        abg     = bg.darker(115)
        fg      = palette.color(palette.HighlightedText)
        sbg     = 'rgb(%s, %s, %s)' % (bg.red(), bg.green(), bg.blue())
        sabg    = 'rgb(%s, %s, %s)' % (abg.red(), abg.green(), abg.blue())
        sfg     = 'rgb(%s, %s, %s)' % (fg.red(), fg.green(), fg.blue())
        style   = 'QTreeView::item:hover { '\
                  '     color: %s;'\
                  '     background: qlineargradient(x1:0,'\
                  '                                 y1:0,'\
                  '                                 x2:0,'\
                  '                                 y2:1,'\
                  '                                 stop: 0 %s,'\
                  '                                 stop: 1 %s);'\
                  '}' % (sfg, sbg, sabg)
        
        self._completerTree = QTreeView(self)
        self._completerTree.setStyleSheet(style)
        self._completerTree.header().hide()
        self._completerTree.setFrameShape(QTreeView.Box)
        self._completerTree.setFrameShadow(QTreeView.Plain)
        self._completerTree.setPalette(palette)
        self._completerTree.setEditTriggers(QTreeView.NoEditTriggers)
        self._completerTree.setWindowFlags(Qt.Popup)
        self._completerTree.installEventFilter(self)
        self._completerTree.setRootIsDecorated(False)
        self._completerTree.setItemsExpandable(False)
        
        # create the editing widget
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addStretch()
        
        self._scrollWidget.setFrameShape( QScrollArea.NoFrame )
        self._scrollWidget.setFocusPolicy(Qt.NoFocus)
        self._scrollWidget.setWidget(self._partsWidget)
        self._scrollWidget.setWidgetResizable(True)
        self._scrollWidget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self._scrollWidget.setAlignment(Qt.AlignTop | Qt.AlignRight)
        self._scrollWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self._scrollWidget.setContentsMargins(0, 0, 0, 0)
        self._scrollWidget.setViewportMargins(0, 0, 0, 0)
        self._scrollWidget.move(2, 2)
        
        self._partsWidget.setLayout(layout)
        self._partsWidget.setCursor(Qt.ArrowCursor)
        self._partsWidget.setAutoFillBackground(True)
        self._partsWidget.setFixedHeight(self.height() - 12)
        
        palette = self._partsWidget.palette()
        palette.setColor(palette.Background, palette.color(palette.Base))
        self._partsWidget.setPalette(palette)
        
        # create connections
        self._completerTree.clicked.connect( self.navigateToIndex )
        self._buttonGroup.buttonClicked.connect( self.handleButtonClick )
        self._scrollWidget.horizontalScrollBar().valueChanged.connect( 
                                                        self.scrollParts )
    
    def acceptEdit( self ):
        """
        Accepts the current text and rebuilds the parts widget.
        """
        
        if ( self._partsWidget.isVisible() ):
            return False
        
        use_completion = self.completer().popup().isVisible()
        completion     = self.completer().currentCompletion()
        
        self._completerTree.hide()
        self.completer().popup().hide()
        
        if ( use_completion ):
            self.setText(completion)
        else:
            self.rebuild()
            
        return True
    
    def cancelEdit( self ):
        """
        Rejects the current edit and shows the parts widget.
        """
        
        if ( self._partsWidget.isVisible() ):
            return False
            
        self._completerTree.hide()
        self.completer().popup().hide()
        
        self.setText(self._originalText)
        return True
    
    def currentItem( self ):
        """
        Returns the current navigation item from the current path.
        
        :return     <XNavigationItem> || None
        """
        model = self.navigationModel()
        if ( not model ):
            return None
        
        return model.itemByPath(self.text())
    
    def eventFilter( self, object, event ):
        """
        Filters the events for the inputed object through this edit.
        
        :param      object | <QObject>
                    event  | <QEvent>
        
        :return     <bool> | consumed
        """
        if ( event.type() == event.KeyPress ):
            if ( event.key() == Qt.Key_Escape ):
                self._completerTree.hide()
                self.completer().popup().hide()
                
                self.cancelEdit()
                
            elif ( event.key() in (Qt.Key_Return, Qt.Key_Enter) ):
                self.acceptEdit()
                return True
                
            elif ( event.key() == Qt.Key_Tab ):
                if ( self.completer().popup().isVisible() ):
                    text   = str(self.completer().currentCompletion())
                    super(XNavigationEdit, self).setText(text)
                    return True
                else:
                    self.acceptEdit()
                    return False
            
        elif ( event.type() == event.MouseButtonPress ):
            if ( not self._completerTree.rect().contains(event.pos()) ):
                self._completerTree.hide()
                self.completer().popup().hide()
                
                self.cancelEdit()
        
        return False
    
    def focusOutEvent( self, event ):
        """
        Overloads the focus out event to cancel editing when the widget loses
        focus.
        
        :param      event | <QFocusEvent>
        """
        super(XNavigationEdit, self).focusOutEvent(event)
        
        self.cancelEdit()
    
    def handleButtonClick( self, button ):
        """
        Handle the event when a user clicks on one of the part buttons.
        
        :param      button | <QToolButton>
        """
        path            = button.property('path')
        is_completer    = button.property('is_completer')
        
        # popup a completion menu
        if ( unwrapVariant(is_completer) ):
            model = self.navigationModel()
            if ( not model ):
                return
            
            sep  = self.separator()
            path = str(unwrapVariant(path))
            item = model.itemByPath(path, includeRoot = True)
            if ( not item ):
                return
            
            curr_path = str(self.text()).strip(self.separator())
            curr_path = curr_path.replace(path, '').strip(self.separator())
            
            child_name = ''
            if ( curr_path ):
                child_name = curr_path.split(self.separator())[0]
            
            index = model.indexFromItem(item)
            
            self._completerTree.move(QCursor.pos())
            self._completerTree.setRootIndex(index)
            self._completerTree.verticalScrollBar().setValue(0)
            
            if ( child_name ):
                child_item = None
                for i in range(item.rowCount()):
                    child = item.child(i)
                    if ( child.text() == child_name ):
                        child_item = child
                        break
                
                if ( child_item ):
                    child_index = model.indexFromItem(child_item)
                    self._completerTree.setCurrentIndex(child_index)
                    self._completerTree.scrollTo(child_index)
            
            self._completerTree.show()
            self._completerTree.setUpdatesEnabled(True)
        else:
            self.setText(unwrapVariant(path))
    
    def keyPressEvent( self, event ):
        """
        Overloads the key press event to listen for escape calls to cancel the
        parts editing.
        
        :param      event | <QKeyPressEvent>
        """
        if ( self.scrollWidget().isHidden() ):
            if ( event.key() == Qt.Key_Escape ):
                self.cancelEdit()
                return
                
            elif ( event.key() in (Qt.Key_Return, Qt.Key_Enter) ):
                self.acceptEdit()
                return
            
        elif ( event.key() == Qt.Key_A and 
               event.modifiers() == Qt.ControlModifier ):
            self.startEdit()
        
        super(XNavigationEdit, self).keyPressEvent(event)
    
    def mouseDoubleClickEvent( self, event ):
        """
        Overloads the system to enable editing when a user double clicks.
        
        :param      event | <QMouseEvent>
        """
        super(XNavigationEdit, self).mouseDoubleClickEvent(event)
        
        self.startEdit()
    
    def navigationModel( self ):
        """
        Returns the navigation model linked with this edit.
        
        :return     <XNavigationModel> || None
        """
        return self._navigationModel
    
    def navigateToIndex( self, index ):
        """
        Navigates to the inputed action's path.
        
        :param      action | <QAction>
        """
        self._completerTree.hide()
        item = self._navigationModel.itemFromIndex(index)
        self.setText(self._navigationModel.itemPath(item))
    
    def parts( self ):
        """
        Returns the parts that are used for this system.
        
        :return     [<str>, ..]
        """
        path = str(self.text()).strip(self.separator())
        if ( not path ):
            return []
        return path.split(self.separator())
    
    def partsWidget( self ):
        """
        Returns the widget that contains the parts system.
        
        :return     <QScrollArea>
        """
        return self._partsWidget
    
    def startEdit( self ):
        """
        Rebuilds the pathing based on the parts.
        """
        self._originalText = self.text()
        self.scrollWidget().hide()
        self.setFocus()
        self.selectAll()
    
    def rebuild( self ):
        """
        Rebuilds the parts widget with the latest text.
        """
        navitem = self.currentItem()
        if ( navitem ):
            navitem.initialize()
            
        self.setUpdatesEnabled(False)
        self.scrollWidget().show()
        self._originalText = ''
        
        partsw = self.partsWidget()
        for button in self._buttonGroup.buttons():
            self._buttonGroup.removeButton(button)
            button.close()
            button.setParent(None)
            button.deleteLater()
        
        # create the root button
        layout = partsw.layout()
        parts  = self.parts()
        
        button = QToolButton(partsw)
        button.setAutoRaise(True)
        button.setMaximumWidth(12)
        button.setArrowType(Qt.RightArrow)
        
        button.setProperty('path',          wrapVariant(''))
        button.setProperty('is_completer',  wrapVariant(True))
        last_button = button
            
        self._buttonGroup.addButton(button)
        layout.insertWidget(0, button)
        
        # check to see if we have a navigation model setup
        if ( self._navigationModel ):
            last_item = self._navigationModel.itemByPath(self.text())
            show_last =  last_item and last_item.rowCount() > 0
        else:
            show_last = False
        
        # load the navigation system
        count = len(parts)
        for i, part in enumerate(parts):
            path = self.separator().join(parts[:i+1])
            
            button = QToolButton(partsw)
            button.setAutoRaise(True)
            button.setText(part)
            
            if ( self._navigationModel ):
                item = self._navigationModel.itemByPath(path)
                if ( item ):
                    button.setIcon(item.icon())
                    button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
            
            button.setProperty('path',         wrapVariant(path))
            button.setProperty('is_completer', wrapVariant(False))
            
            self._buttonGroup.addButton(button)
            layout.insertWidget((i * 2) + 1, button)
            
            # determine if we should show the final button
            if ( show_last or i < (count - 1) ):
                button = QToolButton(partsw)
                button.setAutoRaise(True)
                button.setMaximumWidth(12)
                button.setArrowType(Qt.RightArrow)
                
                button.setProperty('path',          wrapVariant(path))
                button.setProperty('is_completer',  wrapVariant(True))
            
                self._buttonGroup.addButton(button)
                layout.insertWidget((i * 2) + 2, button)
                
                last_button = button
        
        if ( self.scrollWidget().width() < partsw.width() ):
            self.scrollParts(partsw.width() - self.scrollWidget().width())
            
        self.setUpdatesEnabled(True)
        self.navigationChanged.emit()
    
    def resizeEvent( self, event ):
        """
        Resizes the current widget and its parts widget.
        
        :param      event | <QResizeEvent>
        """
        super(XNavigationEdit, self).resizeEvent(event)
        
        w = self.width()
        h = self.height()
        
        self._scrollWidget.resize(w - 4, h - 4)
        
        if ( self._scrollWidget.width() < self._partsWidget.width() ):
           self.scrollParts( self._partsWidget.width() - self._scrollWidget.width() )
    
    def scrollParts( self, amount ):
        """
        Scrolls the parts to offset the scrolling amount.
        
        :param      amount | <int>
        """
        change = self._scrollAmount - amount
        self._partsWidget.scroll(change, 0)
        self._scrollAmount = amount
    
    def scrollWidget( self ):
        """
        Returns the scrolling widget.
        
        :return     <QScrollArea>
        """
        return self._scrollWidget
    
    def separator( self ):
        """
        Returns the separation character that is used for this edit.
        
        :return     <str>
        """
        return self._separator
    
    def setTopLevelItems( self, items ):
        """
        Initializes the navigation system to start with the inputed root \
        item.
        
        :param      item | <XNavigationItem>
        """
        if ( not self._navigationModel ):
            self.setNavigationModel(XNavigationModel(self))
        
        self._navigationModel.setTopLevelItems(items)
    
    def setNavigationModel( self, model ):
        """
        Sets the navigation model for this edit.
        
        :param      model | <XNavigationModel>
        """
        self._navigationModel = model
        self._completerTree.setModel(model)
        
        if ( model ):
            model.setSeparator(self.separator())
            completer = XNavigationCompleter(model, self)
            self.setCompleter(completer)
            completer.popup().installEventFilter(self)
        else:
            self.setCompleter(None)
        
        self.rebuild()
    
    def setParts( self, parts ):
        """
        Sets the path for this edit widget by providing the parts to the path.
        
        :param      parts | [<str>, ..]
        """
        self.setText(self.separator().join(map(str, parts)))
    
    def setSeparator( self, separator ):
        """
        Sets the separator to the inputed character.
        
        :param      separator | <str>
        """
        self._separator = separator
        if ( self._navigationModel ):
            self._navigationModel.setSeparator(separator)
        self.rebuild()
    
    def setText( self, text ):
        """
        Sets the text for this edit to the inputed text.
        
        :param      text | <str>
        """
        super(XNavigationEdit, self).setText(text)
        
        self.scrollWidget().show()
        if ( text == '' or self._originalText != text ):
            self.rebuild()
Beispiel #2
0
class PylintViewer( QWidget ):
    " Pylint tab widget "

    # Limits to colorize the final score
    BadLimit = 8.5
    GoodLimit = 9.5

    # Options of providing a report
    SingleFile     = 0
    DirectoryFiles = 1
    ProjectFiles   = 2
    SingleBuffer   = 3

    updatePylintTooltip = pyqtSignal( str )

    def __init__( self, parent = None ):
        QWidget.__init__( self, parent )

        self.__reportUUID = ""
        self.__reportFileName = ""
        self.__reportOption = -1
        self.__reportShown = False
        self.__report = None

        self.__widgets = []

        # Prepare members for reuse
        if GlobalData().pylintAvailable:
            self.__noneLabel = QLabel( "\nNo results available" )
        else:
            self.__noneLabel = QLabel( "\nPylint is not available" )
        self.__noneLabel.setAutoFillBackground( True )
        noneLabelPalette = self.__noneLabel.palette()
        noneLabelPalette.setColor( QPalette.Background,
                                   GlobalData().skin.nolexerPaper )
        self.__noneLabel.setPalette( noneLabelPalette )

        self.__noneLabel.setFrameShape( QFrame.StyledPanel )
        self.__noneLabel.setAlignment( Qt.AlignHCenter )
        self.__headerFont = self.__noneLabel.font()
        self.__headerFont.setPointSize( self.__headerFont.pointSize() + 4 )
        self.__noneLabel.setFont( self.__headerFont )

        self.__createLayout( parent )

        self.__updateButtonsStatus()
        self.resizeEvent()
        return

    def __createLayout( self, parent ):
        " Creates the toolbar and layout "

        # Buttons
        self.printButton = QAction( PixmapCache().getIcon( 'printer.png' ),
                                    'Print', self )
        #printButton.setShortcut( 'Ctrl+' )
        self.printButton.triggered.connect( self.__onPrint )
        self.printButton.setVisible( False )

        self.printPreviewButton = QAction(
                PixmapCache().getIcon( 'printpreview.png' ),
                'Print preview', self )
        #printPreviewButton.setShortcut( 'Ctrl+' )
        self.printPreviewButton.triggered.connect( self.__onPrintPreview )
        self.printPreviewButton.setVisible( False )

        spacer = QWidget()
        spacer.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding )

        self.clearButton = QAction(
            PixmapCache().getIcon( 'trash.png' ),
            'Clear', self )
        self.clearButton.triggered.connect( self.__clear )

        # The toolbar
        self.toolbar = QToolBar( self )
        self.toolbar.setOrientation( Qt.Vertical )
        self.toolbar.setMovable( False )
        self.toolbar.setAllowedAreas( Qt.RightToolBarArea )
        self.toolbar.setIconSize( QSize( 16, 16 ) )
        self.toolbar.setFixedWidth( 28 )
        self.toolbar.setContentsMargins( 0, 0, 0, 0 )

        self.toolbar.addAction( self.printPreviewButton )
        self.toolbar.addAction( self.printButton )
        self.toolbar.addWidget( spacer )
        self.toolbar.addAction( self.clearButton )

        self.__vLayout = QVBoxLayout()
        self.__vLayout.setContentsMargins( 5, 5, 5, 5 )
        self.__vLayout.setSpacing( 0 )
        self.__vLayout.setSizeConstraint( QLayout.SetFixedSize )

        self.__bodyFrame = QFrame( self )
#        self.__bodyFrame.setFrameShape( QFrame.StyledPanel )
        self.__bodyFrame.setFrameShape( QFrame.NoFrame )

#        self.__bodyFrame.setSizePolicy( QSizePolicy.Maximum,
#                                        QSizePolicy.Expanding )
        self.__bodyFrame.setLayout( self.__vLayout )
        self.bodyWidget = QScrollArea( self )
        self.bodyWidget.setFocusPolicy( Qt.NoFocus )
        self.bodyWidget.setWidget( self.__bodyFrame )
        self.bodyWidget.hide()

        self.__hLayout = QHBoxLayout()
        self.__hLayout.setContentsMargins( 0, 0, 0, 0 )
        self.__hLayout.setSpacing( 0 )
        self.__hLayout.addWidget( self.toolbar )
        self.__hLayout.addWidget( self.__noneLabel )
        self.__hLayout.addWidget( self.bodyWidget )

        self.setLayout( self.__hLayout )
        return

    def __updateButtonsStatus( self ):
        " Updates the buttons status "
        self.printButton.setEnabled( self.__reportShown )
        self.printPreviewButton.setEnabled( self.__reportShown )
        self.clearButton.setEnabled( self.__reportShown )
        return

    def __onPrint( self ):
        " Triggered when the print button is pressed "
        pass

    def __onPrintPreview( self ):
        " triggered when the print preview button is pressed "
        pass

    def setFocus( self ):
        " Overridden setFocus "
        self.__vLayout.setFocus()
        return

    def __clear( self ):
        " Clears the content of the vertical layout "
        if not self.__reportShown:
            return

        self.__removeAll()
        self.bodyWidget.hide()
        self.__noneLabel.show()

        self.__report = None
        self.__reportShown = False
        self.__updateButtonsStatus()
        self.resizeEvent()

        self.__updateTooltip()
        return

    def __removeAll( self ):
        " Removes all the items from the report "
        for item in self.__widgets:
            item.hide()
            self.__vLayout.removeWidget( item )
            del item

        self.__widgets = []
        return

    def __createScoreLabel( self, score, previousScore,
                            showFileName, fileName ):
        " Creates the score label "

        txt = "Score: " + str( score )
        if previousScore != "":
            txt += " / Previous score: " + str( previousScore )
        if not showFileName:
            txt += " for " + os.path.basename( fileName )

        scoreLabel = QLabel( txt )
        scoreLabel.setFrameShape( QFrame.StyledPanel )
        scoreLabel.setFont( self.__headerFont )
        scoreLabel.setAutoFillBackground( True )
        palette = scoreLabel.palette()

        if score < self.BadLimit:
            palette.setColor( QPalette.Background, QColor( 255, 127, 127 ) )
            palette.setColor( QPalette.Foreground, QColor( 0, 0, 0 ) )
        elif score > self.GoodLimit:
            palette.setColor( QPalette.Background, QColor( 220, 255, 220 ) )
            palette.setColor( QPalette.Foreground, QColor( 0, 0, 0 ) )
        else:
            palette.setColor( QPalette.Background, QColor( 255, 255, 127 ) )
            palette.setColor( QPalette.Foreground, QColor( 0, 0, 0 ) )

        scoreLabel.setPalette( palette )
        return scoreLabel

    @staticmethod
    def __setTableHeight( table ):
        " Auxiliary function to set the table height "

        # Height - it is ugly and approximate however I am tired of
        # calculating the proper height. Why is this so hard, huh?
        lastRowHeight = table.itemDelegate().lastHeight
        height = lastRowHeight * ( table.topLevelItemCount() + 1 ) + 10
        table.setFixedHeight( height )
        return

    @staticmethod
    def __shouldShowFileName( messages ):
        " Decides if the file name column should be supressed "
        if len( messages ) == 0:
            return False
        firstName = messages[ 0 ].fileName
        for index in range( 1, len( messages ) ):
            if firstName != messages[ index ].fileName:
                return True
        return False

    def __addErrorsTable( self, messages, showFileName ):
        " Creates the messages table "

        errTable = QTreeWidget( self.bodyWidget )
        errTable.setAlternatingRowColors( True )
        errTable.setRootIsDecorated( False )
        errTable.setItemsExpandable( False )
        errTable.setSortingEnabled( True )
        errTable.setItemDelegate( NoOutlineHeightDelegate( 4 ) )
        errTable.setUniformRowHeights( True )
        errTable.itemActivated.connect( self.__errorActivated )

        headerLabels = [ "File name", "Line", "Message ID", "Object", "Message" ]
        errTable.setHeaderLabels( headerLabels )

        for item in messages:
            if item.position is None:
                lineNumber = str( item.lineNumber )
            else:
                lineNumber = str( item.lineNumber ) + ":" + str( item.position )
            values = [ item.fileName, lineNumber, item.messageID,
                       item.objectName, item.message ]
            errTable.addTopLevelItem( ErrorTableItem( values, 1 ) )

        # Hide the file name column if required
        if not showFileName:
            errTable.setColumnHidden( 0, True )

        # Resizing
        errTable.header().resizeSections( QHeaderView.ResizeToContents )
        errTable.header().setStretchLastSection( True )

        # Sort indicator
        if showFileName:
            sortIndex = 0   # By file names
        else:
            sortIndex = 1   # By line number because this is from the same file
        errTable.header().setSortIndicator( sortIndex, Qt.AscendingOrder )
        errTable.sortItems( sortIndex, errTable.header().sortIndicatorOrder() )

        # Height
        self.__setTableHeight( errTable )

        self.__vLayout.addWidget( errTable )
        self.__widgets.append( errTable )
        return

    def __addSimilarity( self, similarity, titleText ):
        " Adds a similarity "

        # Label
        title = QLabel( titleText )
        title.setFont( self.__headerFont )

        self.__vLayout.addWidget( title )
        self.__widgets.append( title )

        # List of files
        simTable = QTreeWidget( self.bodyWidget )
        simTable.setAlternatingRowColors( True )
        simTable.setRootIsDecorated( False )
        simTable.setItemsExpandable( False )
        simTable.setSortingEnabled( False )
        simTable.setItemDelegate( NoOutlineHeightDelegate( 4 ) )
        simTable.setUniformRowHeights( True )
        simTable.itemActivated.connect( self.__similarityActivated )
        simTable.setHeaderLabels( [ "File name", "Line" ] )

        for item in similarity.files:
            values = [ item[ 0 ], str( item[ 1 ] ) ]
            simTable.addTopLevelItem( QTreeWidgetItem( values )  )

        # Resizing
        simTable.header().resizeSections( QHeaderView.ResizeToContents )
        simTable.header().setStretchLastSection( True )

        # Height
        self.__setTableHeight( simTable )

        self.__vLayout.addWidget( simTable )
        self.__widgets.append( simTable )

        # The fragment itself
        if len( similarity.fragment ) > 10:
            # Take first 9 lines
            text = "\n".join( similarity.fragment[ : 9 ] ) + "\n ..."
            toolTip = "\n".join( similarity.fragment )
        else:
            text = "\n".join( similarity.fragment )
            toolTip = ""
        fragmentLabel = QLabel( "<pre>" + self.__htmlEncode( text ) + "</pre>" )
        if toolTip != "":
            fragmentLabel.setToolTip( "<pre>" + self.__htmlEncode( toolTip ) +
                                      "</pre>" )
        palette = fragmentLabel.palette()
        palette.setColor( QPalette.Background, QColor( 250, 250, 175 ) )
        palette.setColor( QPalette.Foreground, QColor( 0, 0, 0 ) )
        fragmentLabel.setPalette( palette )
        fragmentLabel.setFrameShape( QFrame.StyledPanel )
        fragmentLabel.setAutoFillBackground( True )

        labelFont = fragmentLabel.font()
        labelFont.setFamily( GlobalData().skin.baseMonoFontFace )
        fragmentLabel.setFont( labelFont )

        self.__vLayout.addWidget( fragmentLabel )
        self.__widgets.append( fragmentLabel )
        return

    @staticmethod
    def __htmlEncode( string ):
        " Encodes HTML "
        return string.replace( "&", "&amp;" ) \
                     .replace( ">", "&gt;" ) \
                     .replace( "<", "&lt;" )

    def __addSectionSpacer( self ):
        " Adds a fixed height spacer to the VBox layout "
        spacer = QWidget()
        spacer.setFixedHeight( 10 )
        self.__vLayout.addWidget( spacer )
        self.__widgets.append( spacer )
        return

    def __addGenericTable( self, table ):
        " Adds a generic table to the report "

        theTable = QTreeWidget( self.bodyWidget )
        theTable.setAlternatingRowColors( True )
        theTable.setRootIsDecorated( False )
        theTable.setItemsExpandable( False )
        theTable.setSortingEnabled( False )
        theTable.setItemDelegate( NoOutlineHeightDelegate( 4 ) )
        theTable.setUniformRowHeights( True )

        headerLabels = []
        for index in range( 0, len( table.header ) ):
            headerLabels.append( table.header[ index ] )
        theTable.setHeaderLabels( headerLabels )

        for item in table.body:
            row = []
            for index in range( 0, len( table.header ) ):
                row.append( item[ index ] )
            theTable.addTopLevelItem( QTreeWidgetItem( row ) )

        theTable.setFocusPolicy( Qt.NoFocus )

        # Resizing
        theTable.header().resizeSections( QHeaderView.ResizeToContents )
        theTable.header().setStretchLastSection( True )

        # Height
        self.__setTableHeight( theTable )

        self.__vLayout.addWidget( theTable )
        self.__widgets.append( theTable )
        return

    def __addGenericTableTitle( self, table ):
        " Adds a generic table title "
        tableTitle = QLabel( table.title )
        tableTitle.setFont( self.__headerFont )

        self.__vLayout.addWidget( tableTitle )
        self.__widgets.append( tableTitle )
        return

    def __updateTooltip( self ):
        " Generates a signal with appropriate string message "
        if not self.__reportShown:
            tooltip = "No results available"
        elif self.__reportOption == self.DirectoryFiles:
            tooltip = "Report generated for directory: " + \
                      self.__reportFileName
        elif self.__reportOption == self.ProjectFiles:
            tooltip = "Report generated for the whole project"
        elif self.__reportOption == self.SingleFile:
            tooltip = "Report generated for file: " + self.__reportFileName
        elif self.__reportOption == self.SingleBuffer:
            tooltip = "Report generated for unsaved file: " + \
                      self.__reportFileName
        else:
            tooltip = ""
        self.updatePylintTooltip.emit( tooltip )
        return

    def showReport( self, lint, reportOption, fileName, uuid ):
        " Shows the pylint results "
        self.__removeAll()
        self.__noneLabel.hide()

        self.__report = lint
        self.__reportUUID = uuid
        self.__reportFileName = fileName
        self.__reportOption = reportOption

        showFileName = self.__shouldShowFileName( lint.errorMessages )

        scoreLabel = self.__createScoreLabel( lint.score, lint.previousScore,
                                              showFileName, fileName )
        self.__vLayout.addWidget( scoreLabel )
        self.__widgets.append( scoreLabel )

        if len( lint.errorMessages ) > 0:
            self.__addSectionSpacer()
            self.__addErrorsTable( lint.errorMessages, showFileName )

        index = 0
        for similarity in lint.similarities:
            self.__addSectionSpacer()
            self.__addSimilarity( similarity, "Similarity #" + str( index ) )
            index += 1

        for table in lint.tables:
            self.__addSectionSpacer()
            self.__addGenericTableTitle( table )
            self.__addGenericTable( table )

        self.bodyWidget.show()
        self.bodyWidget.ensureVisible( 0, 0, 0, 0 )
        self.__reportShown = True
        self.__updateButtonsStatus()
        self.__updateTooltip()

        # It helps, but why do I have flickering?
        QApplication.processEvents()
        self.__resizeBodyFrame()
        return

    def __errorActivated( self, item, column ):
        " Handles the double click (or Enter) on the item "

        linePos = str( item.text( 1 ) )
        if ":" in linePos:
            parts = linePos.split( ":" )
            lineNumber = int( parts[ 0 ] )
            pos = int( parts[ 1 ] )
        else:
            lineNumber = int( linePos )
            pos = 0

        if self.__reportOption in [ self.SingleFile, self.DirectoryFiles,
                                    self.ProjectFiles ]:
            fileName = str( item.text( 0 ) )
        else:
            # SingleBuffer
            if self.__reportFileName != "":
                if os.path.isabs( self.__reportFileName ):
                    fileName = self.__reportFileName
                else:
                    # Could be unsaved buffer, so try to search by the
                    mainWindow = GlobalData().mainWindow
                    widget = mainWindow.getWidgetByUUID( self.__reportUUID )
                    if widget is None:
                        logging.error( "The unsaved buffer has been closed" )
                        return
                    # The widget was found, so jump to the required
                    editor = widget.getEditor()
                    editor.gotoLine( lineNumber, pos )
                    editor.setFocus()
                    return

        GlobalData().mainWindow.openFile( fileName, lineNumber, pos )
        return

    def __resizeBodyFrame( self ):
        " Resizing the frame to occupy all available width "
        size = self.bodyWidget.maximumViewportSize()
        self.__bodyFrame.setMinimumWidth( size.width() - 16 )
        self.__bodyFrame.setMinimumHeight( size.height() )
        return

    def showEvent( self, showEv = None ):
        " Called when the widget is shown "
        self.__resizeBodyFrame()
        return

    def resizeEvent( self, resizeEv = None ):
        " Called when the main window gets resized "
        self.__resizeBodyFrame()
        return

    def onFileUpdated( self, fileName, uuid ):
        " Called when a buffer is saved or saved as "

        if not self.__reportShown:
            return
        if self.__reportUUID != uuid:
            return

        # Currently shown report is for the saved buffer
        # File name is expected being absolute
        self.__reportFileName = fileName
        self.updatePylintTooltip.emit( "Report generated for buffer saved as " +
                                       fileName )
        return

    def __similarityActivated( self, item, column ):
        " Triggered when a similarity is activated "
        fileName = str( item.text( 0 ) )
        lineNumber = int( item.text( 1 ) )
        GlobalData().mainWindow.openFile( fileName, lineNumber )
        return
Beispiel #3
0
class PylintViewer(QWidget):
    " Pylint tab widget "

    # Limits to colorize the final score
    BadLimit = 8.5
    GoodLimit = 9.5

    # Options of providing a report
    SingleFile = 0
    DirectoryFiles = 1
    ProjectFiles = 2
    SingleBuffer = 3

    updatePylintTooltip = pyqtSignal(str)

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self.__reportUUID = ""
        self.__reportFileName = ""
        self.__reportOption = -1
        self.__reportShown = False
        self.__report = None

        self.__widgets = []

        # Prepare members for reuse
        if GlobalData().pylintAvailable:
            self.__noneLabel = QLabel("\nNo results available")
        else:
            self.__noneLabel = QLabel("\nPylint is not available")
        self.__noneLabel.setAutoFillBackground(True)
        noneLabelPalette = self.__noneLabel.palette()
        noneLabelPalette.setColor(QPalette.Background,
                                  GlobalData().skin.nolexerPaper)
        self.__noneLabel.setPalette(noneLabelPalette)

        self.__noneLabel.setFrameShape(QFrame.StyledPanel)
        self.__noneLabel.setAlignment(Qt.AlignHCenter)
        self.__headerFont = self.__noneLabel.font()
        self.__headerFont.setPointSize(self.__headerFont.pointSize() + 4)
        self.__noneLabel.setFont(self.__headerFont)

        self.__createLayout(parent)

        self.__updateButtonsStatus()
        self.resizeEvent()
        return

    def __createLayout(self, parent):
        " Creates the toolbar and layout "

        # Buttons
        self.printButton = QAction(PixmapCache().getIcon('printer.png'),
                                   'Print', self)
        #printButton.setShortcut( 'Ctrl+' )
        self.printButton.triggered.connect(self.__onPrint)
        self.printButton.setVisible(False)

        self.printPreviewButton = QAction(
            PixmapCache().getIcon('printpreview.png'), 'Print preview', self)
        #printPreviewButton.setShortcut( 'Ctrl+' )
        self.printPreviewButton.triggered.connect(self.__onPrintPreview)
        self.printPreviewButton.setVisible(False)

        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.clearButton = QAction(PixmapCache().getIcon('trash.png'), 'Clear',
                                   self)
        self.clearButton.triggered.connect(self.__clear)

        # The toolbar
        self.toolbar = QToolBar(self)
        self.toolbar.setOrientation(Qt.Vertical)
        self.toolbar.setMovable(False)
        self.toolbar.setAllowedAreas(Qt.RightToolBarArea)
        self.toolbar.setIconSize(QSize(16, 16))
        self.toolbar.setFixedWidth(28)
        self.toolbar.setContentsMargins(0, 0, 0, 0)

        self.toolbar.addAction(self.printPreviewButton)
        self.toolbar.addAction(self.printButton)
        self.toolbar.addWidget(spacer)
        self.toolbar.addAction(self.clearButton)

        self.__vLayout = QVBoxLayout()
        self.__vLayout.setContentsMargins(5, 5, 5, 5)
        self.__vLayout.setSpacing(0)
        self.__vLayout.setSizeConstraint(QLayout.SetFixedSize)

        self.__bodyFrame = QFrame(self)
        #        self.__bodyFrame.setFrameShape( QFrame.StyledPanel )
        self.__bodyFrame.setFrameShape(QFrame.NoFrame)

        #        self.__bodyFrame.setSizePolicy( QSizePolicy.Maximum,
        #                                        QSizePolicy.Expanding )
        self.__bodyFrame.setLayout(self.__vLayout)
        self.bodyWidget = QScrollArea(self)
        self.bodyWidget.setFocusPolicy(Qt.NoFocus)
        self.bodyWidget.setWidget(self.__bodyFrame)
        self.bodyWidget.hide()

        self.__hLayout = QHBoxLayout()
        self.__hLayout.setContentsMargins(0, 0, 0, 0)
        self.__hLayout.setSpacing(0)
        self.__hLayout.addWidget(self.toolbar)
        self.__hLayout.addWidget(self.__noneLabel)
        self.__hLayout.addWidget(self.bodyWidget)

        self.setLayout(self.__hLayout)
        return

    def __updateButtonsStatus(self):
        " Updates the buttons status "
        self.printButton.setEnabled(self.__reportShown)
        self.printPreviewButton.setEnabled(self.__reportShown)
        self.clearButton.setEnabled(self.__reportShown)
        return

    def __onPrint(self):
        " Triggered when the print button is pressed "
        pass

    def __onPrintPreview(self):
        " triggered when the print preview button is pressed "
        pass

    def setFocus(self):
        " Overridden setFocus "
        self.__vLayout.setFocus()
        return

    def __clear(self):
        " Clears the content of the vertical layout "
        if not self.__reportShown:
            return

        self.__removeAll()
        self.bodyWidget.hide()
        self.__noneLabel.show()

        self.__report = None
        self.__reportShown = False
        self.__updateButtonsStatus()
        self.resizeEvent()

        self.__updateTooltip()
        return

    def __removeAll(self):
        " Removes all the items from the report "
        for item in self.__widgets:
            item.hide()
            self.__vLayout.removeWidget(item)
            del item

        self.__widgets = []
        return

    def __createScoreLabel(self, score, previousScore, showFileName, fileName):
        " Creates the score label "

        txt = "Score: " + str(score)
        if previousScore != "":
            txt += " / Previous score: " + str(previousScore)
        if not showFileName:
            txt += " for " + os.path.basename(fileName)

        scoreLabel = QLabel(txt)
        scoreLabel.setFrameShape(QFrame.StyledPanel)
        scoreLabel.setFont(self.__headerFont)
        scoreLabel.setAutoFillBackground(True)
        palette = scoreLabel.palette()

        if score < self.BadLimit:
            palette.setColor(QPalette.Background, QColor(255, 127, 127))
            palette.setColor(QPalette.Foreground, QColor(0, 0, 0))
        elif score > self.GoodLimit:
            palette.setColor(QPalette.Background, QColor(220, 255, 220))
            palette.setColor(QPalette.Foreground, QColor(0, 0, 0))
        else:
            palette.setColor(QPalette.Background, QColor(255, 255, 127))
            palette.setColor(QPalette.Foreground, QColor(0, 0, 0))

        scoreLabel.setPalette(palette)
        return scoreLabel

    @staticmethod
    def __setTableHeight(table):
        " Auxiliary function to set the table height "

        # Height - it is ugly and approximate however I am tired of
        # calculating the proper height. Why is this so hard, huh?
        lastRowHeight = table.itemDelegate().lastHeight
        height = lastRowHeight * (table.topLevelItemCount() + 1) + 10
        table.setFixedHeight(height)
        return

    @staticmethod
    def __shouldShowFileName(messages):
        " Decides if the file name column should be supressed "
        if len(messages) == 0:
            return False
        firstName = messages[0].fileName
        for index in range(1, len(messages)):
            if firstName != messages[index].fileName:
                return True
        return False

    def __addErrorsTable(self, messages, showFileName):
        " Creates the messages table "

        errTable = QTreeWidget(self.bodyWidget)
        errTable.setAlternatingRowColors(True)
        errTable.setRootIsDecorated(False)
        errTable.setItemsExpandable(False)
        errTable.setSortingEnabled(True)
        errTable.setItemDelegate(NoOutlineHeightDelegate(4))
        errTable.setUniformRowHeights(True)
        errTable.itemActivated.connect(self.__errorActivated)

        headerLabels = ["File name", "Line", "Message ID", "Object", "Message"]
        errTable.setHeaderLabels(headerLabels)

        for item in messages:
            if item.position is None:
                lineNumber = str(item.lineNumber)
            else:
                lineNumber = str(item.lineNumber) + ":" + str(item.position)
            values = [
                item.fileName, lineNumber, item.messageID, item.objectName,
                item.message
            ]
            errTable.addTopLevelItem(ErrorTableItem(values, 1))

        # Hide the file name column if required
        if not showFileName:
            errTable.setColumnHidden(0, True)

        # Resizing
        errTable.header().resizeSections(QHeaderView.ResizeToContents)
        errTable.header().setStretchLastSection(True)

        # Sort indicator
        if showFileName:
            sortIndex = 0  # By file names
        else:
            sortIndex = 1  # By line number because this is from the same file
        errTable.header().setSortIndicator(sortIndex, Qt.AscendingOrder)
        errTable.sortItems(sortIndex, errTable.header().sortIndicatorOrder())

        # Height
        self.__setTableHeight(errTable)

        self.__vLayout.addWidget(errTable)
        self.__widgets.append(errTable)
        return

    def __addSimilarity(self, similarity, titleText):
        " Adds a similarity "

        # Label
        title = QLabel(titleText)
        title.setFont(self.__headerFont)

        self.__vLayout.addWidget(title)
        self.__widgets.append(title)

        # List of files
        simTable = QTreeWidget(self.bodyWidget)
        simTable.setAlternatingRowColors(True)
        simTable.setRootIsDecorated(False)
        simTable.setItemsExpandable(False)
        simTable.setSortingEnabled(False)
        simTable.setItemDelegate(NoOutlineHeightDelegate(4))
        simTable.setUniformRowHeights(True)
        simTable.itemActivated.connect(self.__similarityActivated)
        simTable.setHeaderLabels(["File name", "Line"])

        for item in similarity.files:
            values = [item[0], str(item[1])]
            simTable.addTopLevelItem(QTreeWidgetItem(values))

        # Resizing
        simTable.header().resizeSections(QHeaderView.ResizeToContents)
        simTable.header().setStretchLastSection(True)

        # Height
        self.__setTableHeight(simTable)

        self.__vLayout.addWidget(simTable)
        self.__widgets.append(simTable)

        # The fragment itself
        if len(similarity.fragment) > 10:
            # Take first 9 lines
            text = "\n".join(similarity.fragment[:9]) + "\n ..."
            toolTip = "\n".join(similarity.fragment)
        else:
            text = "\n".join(similarity.fragment)
            toolTip = ""
        fragmentLabel = QLabel("<pre>" + self.__htmlEncode(text) + "</pre>")
        if toolTip != "":
            fragmentLabel.setToolTip("<pre>" + self.__htmlEncode(toolTip) +
                                     "</pre>")
        palette = fragmentLabel.palette()
        palette.setColor(QPalette.Background, QColor(250, 250, 175))
        palette.setColor(QPalette.Foreground, QColor(0, 0, 0))
        fragmentLabel.setPalette(palette)
        fragmentLabel.setFrameShape(QFrame.StyledPanel)
        fragmentLabel.setAutoFillBackground(True)

        labelFont = fragmentLabel.font()
        labelFont.setFamily(GlobalData().skin.baseMonoFontFace)
        fragmentLabel.setFont(labelFont)

        self.__vLayout.addWidget(fragmentLabel)
        self.__widgets.append(fragmentLabel)
        return

    @staticmethod
    def __htmlEncode(string):
        " Encodes HTML "
        return string.replace( "&", "&amp;" ) \
                     .replace( ">", "&gt;" ) \
                     .replace( "<", "&lt;" )

    def __addSectionSpacer(self):
        " Adds a fixed height spacer to the VBox layout "
        spacer = QWidget()
        spacer.setFixedHeight(10)
        self.__vLayout.addWidget(spacer)
        self.__widgets.append(spacer)
        return

    def __addGenericTable(self, table):
        " Adds a generic table to the report "

        theTable = QTreeWidget(self.bodyWidget)
        theTable.setAlternatingRowColors(True)
        theTable.setRootIsDecorated(False)
        theTable.setItemsExpandable(False)
        theTable.setSortingEnabled(False)
        theTable.setItemDelegate(NoOutlineHeightDelegate(4))
        theTable.setUniformRowHeights(True)

        headerLabels = []
        for index in range(0, len(table.header)):
            headerLabels.append(table.header[index])
        theTable.setHeaderLabels(headerLabels)

        for item in table.body:
            row = []
            for index in range(0, len(table.header)):
                row.append(item[index])
            theTable.addTopLevelItem(QTreeWidgetItem(row))

        theTable.setFocusPolicy(Qt.NoFocus)

        # Resizing
        theTable.header().resizeSections(QHeaderView.ResizeToContents)
        theTable.header().setStretchLastSection(True)

        # Height
        self.__setTableHeight(theTable)

        self.__vLayout.addWidget(theTable)
        self.__widgets.append(theTable)
        return

    def __addGenericTableTitle(self, table):
        " Adds a generic table title "
        tableTitle = QLabel(table.title)
        tableTitle.setFont(self.__headerFont)

        self.__vLayout.addWidget(tableTitle)
        self.__widgets.append(tableTitle)
        return

    def __updateTooltip(self):
        " Generates a signal with appropriate string message "
        if not self.__reportShown:
            tooltip = "No results available"
        elif self.__reportOption == self.DirectoryFiles:
            tooltip = "Report generated for directory: " + \
                      self.__reportFileName
        elif self.__reportOption == self.ProjectFiles:
            tooltip = "Report generated for the whole project"
        elif self.__reportOption == self.SingleFile:
            tooltip = "Report generated for file: " + self.__reportFileName
        elif self.__reportOption == self.SingleBuffer:
            tooltip = "Report generated for unsaved file: " + \
                      self.__reportFileName
        else:
            tooltip = ""
        self.updatePylintTooltip.emit(tooltip)
        return

    def showReport(self, lint, reportOption, fileName, uuid):
        " Shows the pylint results "
        self.__removeAll()
        self.__noneLabel.hide()

        self.__report = lint
        self.__reportUUID = uuid
        self.__reportFileName = fileName
        self.__reportOption = reportOption

        showFileName = self.__shouldShowFileName(lint.errorMessages)

        scoreLabel = self.__createScoreLabel(lint.score, lint.previousScore,
                                             showFileName, fileName)
        self.__vLayout.addWidget(scoreLabel)
        self.__widgets.append(scoreLabel)

        if len(lint.errorMessages) > 0:
            self.__addSectionSpacer()
            self.__addErrorsTable(lint.errorMessages, showFileName)

        index = 0
        for similarity in lint.similarities:
            self.__addSectionSpacer()
            self.__addSimilarity(similarity, "Similarity #" + str(index))
            index += 1

        for table in lint.tables:
            self.__addSectionSpacer()
            self.__addGenericTableTitle(table)
            self.__addGenericTable(table)

        self.bodyWidget.show()
        self.bodyWidget.ensureVisible(0, 0, 0, 0)
        self.__reportShown = True
        self.__updateButtonsStatus()
        self.__updateTooltip()

        # It helps, but why do I have flickering?
        QApplication.processEvents()
        self.__resizeBodyFrame()
        return

    def __errorActivated(self, item, column):
        " Handles the double click (or Enter) on the item "

        linePos = str(item.text(1))
        if ":" in linePos:
            parts = linePos.split(":")
            lineNumber = int(parts[0])
            pos = int(parts[1])
        else:
            lineNumber = int(linePos)
            pos = 0

        if self.__reportOption in [
                self.SingleFile, self.DirectoryFiles, self.ProjectFiles
        ]:
            fileName = str(item.text(0))
        else:
            # SingleBuffer
            if self.__reportFileName != "":
                if os.path.isabs(self.__reportFileName):
                    fileName = self.__reportFileName
                else:
                    # Could be unsaved buffer, so try to search by the
                    mainWindow = GlobalData().mainWindow
                    widget = mainWindow.getWidgetByUUID(self.__reportUUID)
                    if widget is None:
                        logging.error("The unsaved buffer has been closed")
                        return
                    # The widget was found, so jump to the required
                    editor = widget.getEditor()
                    editor.gotoLine(lineNumber, pos)
                    editor.setFocus()
                    return

        GlobalData().mainWindow.openFile(fileName, lineNumber, pos)
        return

    def __resizeBodyFrame(self):
        " Resizing the frame to occupy all available width "
        size = self.bodyWidget.maximumViewportSize()
        self.__bodyFrame.setMinimumWidth(size.width() - 16)
        self.__bodyFrame.setMinimumHeight(size.height())
        return

    def showEvent(self, showEv=None):
        " Called when the widget is shown "
        self.__resizeBodyFrame()
        return

    def resizeEvent(self, resizeEv=None):
        " Called when the main window gets resized "
        self.__resizeBodyFrame()
        return

    def onFileUpdated(self, fileName, uuid):
        " Called when a buffer is saved or saved as "

        if not self.__reportShown:
            return
        if self.__reportUUID != uuid:
            return

        # Currently shown report is for the saved buffer
        # File name is expected being absolute
        self.__reportFileName = fileName
        self.updatePylintTooltip.emit("Report generated for buffer saved as " +
                                      fileName)
        return

    def __similarityActivated(self, item, column):
        " Triggered when a similarity is activated "
        fileName = str(item.text(0))
        lineNumber = int(item.text(1))
        GlobalData().mainWindow.openFile(fileName, lineNumber)
        return