Ejemplo n.º 1
0
    def __updateGeometry(self):
        """
        Update the shadow geometry to fit the widget's changed
        geometry.

        """
        widget = self.__widget
        parent = self.__widgetParent
        radius = self.radius_
        pos = widget.pos()
        if parent != widget.parentWidget():
            pos = widget.parentWidget().mapTo(parent, pos)

        geom = QRect(pos, widget.size())
        geom.adjust(-radius, -radius, radius, radius)
        if geom != self.geometry():
            self.setGeometry(geom)

        # Set the widget mask (punch a hole through to the `widget` instance.
        rect = self.rect()

        mask = QRegion(rect)
        transparent = QRegion(rect.adjusted(radius, radius, -radius, -radius))

        mask = mask.subtracted(transparent)
        self.setMask(mask)
Ejemplo n.º 2
0
class Surface(QWidget):
    
    rightClicked = pyqtSignal(QPoint)
    linkClicked = pyqtSignal(QEvent, page.Page, popplerqt4.Poppler.Link)
    linkHovered = pyqtSignal(page.Page, popplerqt4.Poppler.Link)
    linkLeft = pyqtSignal()
    linkHelpRequested = pyqtSignal(QPoint, page.Page, popplerqt4.Poppler.Link)    
    selectionChanged = pyqtSignal(QRect)
    
    def __init__(self, view):
        super(Surface, self).__init__(view)
        self.setBackgroundRole(QPalette.Dark)
        self._view = weakref.ref(view)
        self._currentLinkId = None
        self._selecting = False
        self._magnifying = False
        self._magnifier = None
        self.setMagnifier(magnifier.Magnifier())
        self.setMagnifierModifiers(Qt.CTRL)
        self._selection = QRect()
        self._rubberBand = CustomRubberBand(self)
        self._scrolling = False
        self._scrollTimer = QTimer(interval=100, timeout=self._scrollTimeout)
        self._pageLayout = None
        self._highlights = weakref.WeakKeyDictionary()
        self.setPageLayout(layout.Layout())
        self.setContextMenuPolicy(Qt.PreventContextMenu)
        self.setLinksEnabled(True)
        self.setSelectionEnabled(True)
        self.setShowUrlTips(True)
        
        self.view().cursorNeedUpdate.connect(self.updateCursor)
 
    def pageLayout(self):
        return self._pageLayout
        
    def setPageLayout(self, layout):
        old, self._pageLayout = self._pageLayout, layout
        if old:
            old.redraw.disconnect(self.redraw)
            old.changed.disconnect(self.updateLayout)
        layout.redraw.connect(self.redraw)
        layout.changed.connect(self.updateLayout)
    
    def view(self):
        """Returns our associated View."""
        return self._view()
    
    def viewportRect(self):
        """Returns the rectangle of us that is visible in the View."""
        return self.view().viewport().rect().translated(-self.pos())
        
    def setSelectionEnabled(self, enabled):
        """Enables or disables selecting rectangular regions."""
        self._selectionEnabled = enabled
        if not enabled:
            self.clearSelection()
            self._rubberBand.hide()
            self._selecting = False
    
    def selectionEnabled(self):
        """Returns True if selecting rectangular regions is enabled."""
        return self._selectionEnabled
        
    def setLinksEnabled(self, enabled):
        """Enables or disables the handling of Poppler.Links in the pages."""
        self._linksEnabled = enabled
    
    def linksEnabled(self):
        """Returns True if the handling of Poppler.Links in the pages is enabled."""
        return self._linksEnabled
    
    def setShowUrlTips(self, enabled):
        """Enables or disables showing the URL in a tooltip when hovering a link.
        
        (Of course also setLinksEnabled(True) if you want this.)
        
        """
        self._showUrlTips = enabled
        
    def showUrlTips(self):
        """Returns True if URLs are shown in a tooltip when hovering a link."""
        return self._showUrlTips
        
    def setMagnifier(self, magnifier):
        """Sets the Magnifier to use (or None to disable the magnifier).
        
        The Surface takes ownership of the Magnifier.
        
        """
        if self._magnifier:
            self._magnifier.setParent(None)
        magnifier.setParent(self)
        self._magnifier = magnifier
    
    def magnifier(self):
        """Returns the currently set magnifier."""
        return self._magnifier
    
    def setMagnifierModifiers(self, modifiers):
        """Sets the modifiers (e.g. Qt.CTRL) to show the magnifier.
        
        Use None to show the magnifier always (instead of dragging).
        
        """
        self._magnifierModifiers = modifiers
    
    def magnifierModifiers(self):
        """Returns the currently set keyboard modifiers (e.g. Qt.CTRL) to show the magnifier."""
        return self._magnifierModifiers
        
    def hasSelection(self):
        """Returns True if there is a selection."""
        return bool(self._selection)
        
    def setSelection(self, rect):
        """Sets the selection rectangle."""
        rect = rect.normalized()
        old, self._selection = self._selection, rect
        self._rubberBand.setGeometry(rect)
        self._rubberBand.setVisible(bool(rect))
        if rect != old:
            self.selectionChanged.emit(rect)
        
    def selection(self):
        """Returns the selection rectangle (normalized) or an invalid QRect()."""
        return QRect(self._selection)
    
    def clearSelection(self):
        """Hides the selection rectangle."""
        self.setSelection(QRect())
        
    def redraw(self, rect):
        """Called when the Layout wants to redraw a rectangle."""
        self.update(rect)
        
    def updateLayout(self):
        """Conforms ourselves to our layout (that must already be updated.)"""
        self.clearSelection()
        self.resize(self._pageLayout.size())
        self.update()
        
    def highlight(self, highlighter, areas, msec=0):
        """Highlights the list of areas using the given highlighter.
        
        Every area is a two-tuple (page, rect), where rect is a rectangle inside (0, 0, 1, 1) like the
        linkArea attribute of a Poppler.Link.
        
        """
        d = weakref.WeakKeyDictionary()
        for page, areas in itertools.groupby(sorted(areas), lambda a: a[0]):
            d[page] = list(area[1] for area in areas)
        if msec:
            def clear(selfref=weakref.ref(self)):
                self = selfref()
                if self:
                    self.clearHighlight(highlighter)
            t = QTimer(singleShot = True, timeout = clear)
            t.start(msec)
        else:
            t = None
        self.clearHighlight(highlighter)
        self._highlights[highlighter] = (d, t)
        self.update(sum((page.rect() for page in d), QRegion()))
        
    def clearHighlight(self, highlighter):
        """Removes the highlighted areas of the given highlighter."""
        try:
            (d, t) = self._highlights[highlighter]
        except KeyError:
            return
        del self._highlights[highlighter]
        self.update(sum((page.rect() for page in d), QRegion()))
    
    def paintEvent(self, ev):
        """Handle PaintEvent on the surface to highlight the selection."""
        painter = QPainter(self)
        pages = list(self.pageLayout().pagesAt(ev.rect()))
        for page in pages:
            page.paint(painter, ev.rect())
        
        for highlighter, (d, t) in self._highlights.items():
            rects = []
            for page in pages:
                try:
                    rects.extend(map(page.linkRect, d[page]))
                except KeyError:
                    continue
            if rects:
                highlighter.paintRects(painter, rects)
    
    def handleMousePressEvent(self, ev):
        """Handle mouse press for various operations
            - links to source,
            - magnifier, 
            - selection highlight,
            
            If event was used, return true to indicate processing should stop.
        """
        
        # As the event comes from the view, we need to map it locally.
        pos = self.mapFromParent(ev.pos())
               
        # selecting?
        if self._selectionEnabled:
            if self.hasSelection():
                edge = selectionEdge(pos, self.selection())
                if edge == _OUTSIDE:
                    self.clearSelection()
                else:
                    if ev.button() != Qt.RightButton or edge != _INSIDE:
                        self._selecting = True
                        self._selectionEdge = edge
                        self._selectionRect = self.selection()
                        self._selectionPos = pos
                        if edge == _INSIDE:
                            self.setCursor(Qt.SizeAllCursor)
                    return True
            if not self._selecting:
                if ev.modifiers() & Qt.ShiftModifier and ev.button() == Qt.LeftButton and self._linksEnabled:
                    page, link = self.pageLayout().linkAt(pos)
                    if link:
                        self.linkClickEvent(ev, page, link)
                        return True
                if ev.button() == Qt.RightButton or int(ev.modifiers()) & _SCAM:
                    if not (int(ev.modifiers()) & _SCAM == self._magnifierModifiers 
                            and  ev.button() == Qt.LeftButton):
                        self._selecting = True
                        self._selectionEdge = _RIGHT | _BOTTOM
                        self._selectionRect = QRect(pos, QSize(0, 0))
                        self._selectionPos = pos
                        return True
        
        # link?
        if self._linksEnabled:
            page, link = self.pageLayout().linkAt(pos)
            if link:
                self.linkClickEvent(ev, page, link)
                return True
        # magnifier?
        if (self._magnifier and
            int(ev.modifiers()) & _SCAM == self._magnifierModifiers and
            ev.button() == Qt.LeftButton):
            self._magnifier.moveCenter(pos)
            self._magnifier.show()
            self._magnifier.raise_()
            self._magnifying = True
            self.setCursor(QCursor(Qt.BlankCursor))
            return True

        return False
        
    def handleMouseReleaseEvent(self, ev):
        """Handle mouse release events for various operations:
            - hide magnifier,
            - selection.
            
            If event was used, return true to indicate processing should stop.
        """
        consumed = False
        if self._magnifying:
            self._magnifier.hide()
            self._magnifying = False
            self.unsetCursor() 
            consumed = True
        elif self._selecting:
            self._selecting = False
            selection = self._selectionRect.normalized()
            if selection.width() < 8 and selection.height() < 8:
                self.clearSelection()
            else:
                self.setSelection(selection)
            if self._scrolling:
                self.stopScrolling()
            self.unsetCursor() 
            consumed = True
        if ev.button() == Qt.RightButton:
            # As the event comes from the view, we need to map it locally.
            self.rightClick(self.mapFromParent(ev.pos()))
            consumed = True
        
        return consumed
            
    def handleMouseMoveEvent(self, ev):
        """Handle mouse move events for various operations:
            - move magnifier,
            - selection extension.
            
            If event was used, return true to indicate processing should stop.
        """
        consumed = False 
        if self._magnifying:
            # As the event comes from the view, we need to map it locally.
            self._magnifier.moveCenter(self.mapFromParent(ev.pos()))
            consumed = True
        elif self._selecting:
            # As the event comes from the view, we need to map it locally.
            pos = self.mapFromParent(ev.pos())
            self._moveSelection(pos)
            self._rubberBand.show()
            # check if we are dragging close to the edge of the view, scroll if needed
            view = self.viewportRect()
            dx = pos.x() - view.left() - 12
            if dx >= 0:
                dx = pos.x() - view.right() + 12
                if dx < 0:
                    dx = 0
            dy = pos.y() - view.top() - 12
            if dy >= 0:
                dy = pos.y() - view.bottom() + 12
                if dy < 0:
                    dy = 0
            if dx or dy:
                self.startScrolling(dx, dy)
            elif self._scrolling:
                self.stopScrolling()
            consumed = True
              
        return consumed
        
    def handleMoveEvent(self, ev):
        """Handle  move events for various operations:
            - move magnifier,
            - selection extension.
            
            If event was used, return true to indicate processing should stop.
        """
        consumed = False
        pos = self.mapFromGlobal(QCursor.pos())
        if self._selecting:
            self._moveSelection(pos)
            consumed = True
        elif self._magnifying:
            self._magnifier.moveCenter(pos)
            consumed = True

        return consumed
        
    def handleHelpEvent(self, ev):
        """Handle help event: show link if any."""
        if self._linksEnabled:
            page, link = self.pageLayout().linkAt(self.mapFromParent(ev.pos()))
            if link:
                self.linkHelpEvent(ev.globalPos(), page, link)
        return True

    def updateKineticCursor(self, active):
        """Cursor handling when kinetic move starts/stops.
        
        - reset the cursor and hide tooltips if visible at start,
        - update the cursor and show the appropriate tooltips at stop.
        
        Used as a slot linked to the kineticStarted() signal.
        """
        if active:
            self.unsetCursor()
            if QToolTip.isVisible():
                QToolTip.hideText()
        else:
            self.updateCursor(QCursor.pos())
            if self._linksEnabled:
                page, link = self.pageLayout().linkAt(self.mapFromGlobal(QCursor.pos()))
                if link:
                    self.linkHelpEvent(QCursor.pos(), page, link)

    def updateCursor(self, evpos):
        """Set the cursor to the right glyph, depending on action""" 
        pos = self.mapFromGlobal(evpos)
        cursor = None
        edge = _OUTSIDE
        if self._selectionEnabled and self.hasSelection():
            edge = selectionEdge(pos, self.selection())
            
        if edge is not _OUTSIDE:
            if edge in (_TOP, _BOTTOM):
                cursor = Qt.SizeVerCursor
            elif edge in (_LEFT, _RIGHT):
                cursor = Qt.SizeHorCursor
            elif edge in (_LEFT | _TOP, _RIGHT | _BOTTOM):
                cursor = Qt.SizeFDiagCursor
            elif edge in (_TOP | _RIGHT, _BOTTOM | _LEFT):
                cursor = Qt.SizeBDiagCursor
            elif edge is _INSIDE:
                cursor = Qt.SizeAllCursor

        elif self._linksEnabled:
            page, link = self.pageLayout().linkAt(pos)
            if link:
                cursor = Qt.PointingHandCursor
                lid = id(link)
            else:
                lid = None
            if lid != self._currentLinkId:
                if self._currentLinkId is not None:
                    self.linkHoverLeave()
                self._currentLinkId = lid
                if link:
                    self.linkHoverEnter(page, link)
        
        self.setCursor(cursor) if cursor else self.unsetCursor()
    
    def linkHelpEvent(self, globalPos, page, link):
        """Called when a QHelpEvent occurs on a link.
        
        The default implementation shows a tooltip if showUrls() is True,
        and emits the linkHelpRequested() signal.
        
        """
        if self._showUrlTips and isinstance(link, popplerqt4.Poppler.LinkBrowse):
            QToolTip.showText(globalPos, link.url(), self, page.linkRect(link.linkArea()))
        self.linkHelpRequested.emit(globalPos, page, link)
        
    def rightClick(self, pos):
        """Called when the right mouse button is released.
        
        (Use this instead of the contextMenuEvent as that one also
        fires when starting a right-button selection.)
        The default implementation emite the rightClicked(pos) signal and also
        sends a ContextMenu event to the View widget.
        
        """
        self.rightClicked.emit(pos)
        QApplication.postEvent(self.view().viewport(), QContextMenuEvent(QContextMenuEvent.Mouse, pos + self.pos()))
        
    def linkClickEvent(self, ev, page, link):
        """Called when a link is clicked.
        
        The default implementation emits the linkClicked(event, page, link) signal.
        
        """
        self.linkClicked.emit(ev, page, link)
        
    def linkHoverEnter(self, page, link):
        """Called when the mouse hovers over a link.
        
        The default implementation emits the linkHovered(page, link) signal.
        
        """
        self.linkHovered.emit(page, link)
        
    def linkHoverLeave(self):
        """Called when the mouse does not hover a link anymore.
        
        The default implementation emits the linkLeft() signal.
        
        """
        self.linkLeft.emit()

    def startScrolling(self, dx, dy):
        """Starts scrolling dx, dy about 10 times a second.
        
        Stops automatically when the end is reached.
        
        """
        self._scrolling = QPoint(dx, dy)
        self._scrollTimer.isActive() or self._scrollTimer.start()
        
    def stopScrolling(self):
        """Stops scrolling."""
        self._scrolling = False
        self._scrollTimer.stop()
        
    def _scrollTimeout(self):
        """(Internal) Called by the _scrollTimer."""
        # change the scrollbars, but check how far they really moved.
        pos = self.pos()
        self.view().fastScrollBy(self._scrolling)
        diff = pos - self.pos()
        if not diff:
            self.stopScrolling()
    
    def _moveSelection(self, pos):
        """(Internal) Moves the dragged selection edge or corner to the given pos (QPoint)."""
        diff = pos - self._selectionPos
        self._selectionPos = pos
        edge = self._selectionEdge
        self._selectionRect.adjust(
            diff.x() if edge & _LEFT   else 0,
            diff.y() if edge & _TOP    else 0,
            diff.x() if edge & _RIGHT  else 0,
            diff.y() if edge & _BOTTOM else 0)
        self._rubberBand.setGeometry(self._selectionRect.normalized())
        if self.cursor().shape() in (Qt.SizeBDiagCursor, Qt.SizeFDiagCursor):
            # we're dragging a corner, use correct diagonal cursor
            bdiag = (edge in (3, 12)) ^ (self._selectionRect.width() * self._selectionRect.height() >= 0)
            self.setCursor(Qt.SizeBDiagCursor if bdiag else Qt.SizeFDiagCursor)
Ejemplo n.º 3
0
 def lineSelectionRect(self, line):
     rect = QRect(line.p1(), line.p2()).normalized()
     if rect.isValid():
         rect.adjust(-2, -2, 2, 2)
     return rect
Ejemplo n.º 4
0
 def lineSelectionRect(self, line):
     rect = QRect(line.p1(), line.p2()).normalized()
     if rect.isValid():
         rect.adjust(-2, -2, 2, 2)
     return rect
Ejemplo n.º 5
0
    def paint (self, painter, option, index):
        '''
        QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index
        '''

        orntnDelegate = self.chooseOrientationDelegate()
        
        painter.save()

        if option.state & QStyle.State_Selected:
            borderPen = QPen(Qt.blue)
            borderPen.setWidth(3)
            nameBgColors = [Qt.white, Qt.yellow]
        else:
            borderPen = QPen(Qt.lightGray)
            lightBlue = QColor(0, 0, 255).lighter(180)
            nameBgColors = [Qt.white, lightBlue]

        # Set default font and color
        itemRect = option.rect
        painter.fillRect(itemRect, Qt.white)
        painter.setFont(orntnDelegate.getNameFont())
        painter.setPen(Qt.black)

        m = index.model()

        ticker = m.data(index, PositionsModel.ROLE_TICKER)
        companyName = m.data(index, Qt.DisplayRole)
        line2 = m.data(index, PositionsModel.ROLE_CURRENT_PRICE)
        line3 = m.data(index, PositionsModel.ROLE_CHANGE)

        fontMetricsCompanyName = QFontMetrics(orntnDelegate.getNameFont())
        fontMetricsTicker = QFontMetrics(orntnDelegate.getTickerFont())
        fontMetricsCurrentPrice = QFontMetrics(orntnDelegate.getCurrentPriceFont())
        fontMetricsChange = QFontMetrics(orntnDelegate.getChangeFont())
        lineSp1 = fontMetricsCompanyName.lineSpacing()
        lineSp2 = fontMetricsCurrentPrice.lineSpacing()
        lineSp3 = fontMetricsChange.lineSpacing()

        # Company Name    (EXCHANGE:SYMBOL)
        textRectShade = QRect(itemRect.left(),
                              itemRect.top(),
                              itemRect.width(),
                              self.MARGIN + lineSp1 + self.MARGIN)
        gradient = self.createLinearGradient(textRectShade,
                                             nameBgColors[0],
                                             nameBgColors[1])
        painter.fillRect(textRectShade, gradient)
        textRect = QRect(itemRect.left() + self.MARGIN, itemRect.top() + self.MARGIN,
                         itemRect.width() - 2 * self.MARGIN, lineSp1 + self.MARGIN)
        painter.setFont(orntnDelegate.getNameFont())
        # Shorten the company name such that long company names are not written on top of the ticker
        tickerTextW = fontMetricsTicker.width(ticker)
        companyNameTextW = textRect.width() - tickerTextW - 2 * self.MARGIN
        companyName = fontMetricsCompanyName.elidedText(companyName, Qt.ElideRight, companyNameTextW)
        painter.drawText(textRect, Qt.AlignVCenter | Qt.AlignLeft, companyName)
        painter.setFont(orntnDelegate.getTickerFont())
        painter.drawText(textRect, Qt.AlignVCenter | Qt.AlignRight, "(%s)" % (ticker))

        # Current price
        painter.setFont(orntnDelegate.getCurrentPriceFont())
        textRect.adjust(0, lineSp1 + self.MARGIN, 0, lineSp2 + self.MARGIN)
        painter.drawText(textRect, Qt.AlignTop | Qt.AlignLeft, line2)

        rightColRect = QRect(textRect.left(), textRect.top(),
                             textRect.width(), textRect.height())

        # Change
        ccol = m.data(index, PositionsModel.ROLE_CHANGE_COLOR)
        if ccol == "chg":
            painter.setPen(QPen(Qt.darkGreen))
        elif ccol == "chr":
            painter.setPen(QPen(Qt.red))
        painter.setFont(orntnDelegate.getChangeFont())
        textRect.adjust(0, lineSp2 + self.MARGIN, 0, lineSp3 + self.MARGIN)
        painter.drawText(textRect, Qt.AlignTop | Qt.AlignLeft, line3)

        '''
        Right Column
        '''

        orntnDelegate.paintRightCol(index, painter, rightColRect)

        painter.setPen(borderPen)
        painter.drawRect(itemRect)

        painter.restore()
Ejemplo n.º 6
0
    def paint (self, painter, option, index):
        '''
        @param painter QPainter
        @param option    QStyleOptionViewItem
        @param index   QModelIndex
        '''

        itemRect = option.rect

        textW = self.columnWidths[index.column()]

        if index.column() is StockMatchTableModel.COL_TICKER:

            painter.save()

            m = index.model()
            line1 = m.data(index, Qt.DisplayRole).toString()
            line1 = option.fontMetrics.elidedText(line1, Qt.ElideLeft, textW)           

            # Paint the background first and then set the pen color
            if option.state & QStyle.State_Selected:
                painter.fillRect(itemRect, Qt.white)
                self.setPenColor(painter, Qt.darkBlue)
            else:
                linearGradient = self.createLinearGradient(itemRect, Qt.black, Qt.blue)
                painter.fillRect(itemRect, linearGradient)
                self.setPenColor(painter, Qt.white)

            textRect = itemRect
            painter.drawText(textRect, Qt.AlignVCenter | Qt.AlignCenter, line1)

            painter.restore()

        elif index.column() is StockMatchTableModel.COL_NAME:
           # Name column
            
            painter.save()

            m = index.model()
            line1 = m.data(index, Qt.DisplayRole).toString()
            line1 = option.fontMetrics.elidedText(line1, Qt.ElideRight, textW)
            line2 = m.data(index, StockMatchTableModel.ROLE_SUBTEXT1).toString()

            # Paint the background first and then set the pen color
            if option.state & QStyle.State_Selected:
                linearGradient = self.createLinearGradient(itemRect, Qt.white, Qt.yellow)
                painter.fillRect(itemRect, linearGradient)
                self.setPenColor(painter, Qt.darkBlue)
            else:
                linearGradient = self.createLinearGradient(itemRect, Qt.blue, Qt.darkBlue)
                painter.fillRect(itemRect, linearGradient)
                self.setPenColor(painter, Qt.white)

            lineSp = option.fontMetrics.lineSpacing()
            numLines = 2

            leftMargin = self.MARGIN
            textRect = QRect(itemRect.left() + leftMargin, itemRect.top() + self.MARGIN,
                             itemRect.width(), numLines * lineSp)
            painter.drawText(textRect, Qt.AlignTop | Qt.AlignLeft, line1)

            textRect.adjust(0, option.fontMetrics.lineSpacing(), 0, 0)

            # Draw the subtext
            subTextColor = painter.pen().color()
            subTextColor.setAlphaF(0.5)
            painter.setPen(subTextColor)
            painter.drawText(textRect, Qt.AlignTop | Qt.AlignLeft, line2)


            painter.restore()
        else:
            QStyledItemDelegate.paint(self, painter, option, index)
Ejemplo n.º 7
0
class Surface(QWidget):

    rightClicked = pyqtSignal(QPoint)
    linkClicked = pyqtSignal(QEvent, page.Page, popplerqt4.Poppler.Link)
    linkHovered = pyqtSignal(page.Page, popplerqt4.Poppler.Link)
    linkLeft = pyqtSignal()
    linkHelpRequested = pyqtSignal(QPoint, page.Page, popplerqt4.Poppler.Link)
    selectionChanged = pyqtSignal(QRect)

    def __init__(self, view):
        super(Surface, self).__init__(view)
        self.setBackgroundRole(QPalette.Dark)
        self._view = weakref.ref(view)
        self._currentLinkId = None
        self._selecting = False
        self._magnifying = False
        self._magnifier = None
        self.setMagnifier(magnifier.Magnifier())
        self.setMagnifierModifiers(Qt.CTRL)
        self._selection = QRect()
        self._rubberBand = CustomRubberBand(self)
        self._scrolling = False
        self._scrollTimer = QTimer(interval=100, timeout=self._scrollTimeout)
        self._pageLayout = None
        self._highlights = weakref.WeakKeyDictionary()
        self.setPageLayout(layout.Layout())
        self.setContextMenuPolicy(Qt.PreventContextMenu)
        self.setLinksEnabled(True)
        self.setSelectionEnabled(True)
        self.setShowUrlTips(True)

        self.view().cursorNeedUpdate.connect(self.updateCursor)

    def pageLayout(self):
        return self._pageLayout

    def setPageLayout(self, layout):
        old, self._pageLayout = self._pageLayout, layout
        if old:
            old.redraw.disconnect(self.redraw)
            old.changed.disconnect(self.updateLayout)
        layout.redraw.connect(self.redraw)
        layout.changed.connect(self.updateLayout)

    def view(self):
        """Returns our associated View."""
        return self._view()

    def viewportRect(self):
        """Returns the rectangle of us that is visible in the View."""
        return self.view().viewport().rect().translated(-self.pos())

    def setSelectionEnabled(self, enabled):
        """Enables or disables selecting rectangular regions."""
        self._selectionEnabled = enabled
        if not enabled:
            self.clearSelection()
            self._rubberBand.hide()
            self._selecting = False

    def selectionEnabled(self):
        """Returns True if selecting rectangular regions is enabled."""
        return self._selectionEnabled

    def setLinksEnabled(self, enabled):
        """Enables or disables the handling of Poppler.Links in the pages."""
        self._linksEnabled = enabled

    def linksEnabled(self):
        """Returns True if the handling of Poppler.Links in the pages is enabled."""
        return self._linksEnabled

    def setShowUrlTips(self, enabled):
        """Enables or disables showing the URL in a tooltip when hovering a link.
        
        (Of course also setLinksEnabled(True) if you want this.)
        
        """
        self._showUrlTips = enabled

    def showUrlTips(self):
        """Returns True if URLs are shown in a tooltip when hovering a link."""
        return self._showUrlTips

    def setMagnifier(self, magnifier):
        """Sets the Magnifier to use (or None to disable the magnifier).
        
        The Surface takes ownership of the Magnifier.
        
        """
        if self._magnifier:
            self._magnifier.setParent(None)
        magnifier.setParent(self)
        self._magnifier = magnifier

    def magnifier(self):
        """Returns the currently set magnifier."""
        return self._magnifier

    def setMagnifierModifiers(self, modifiers):
        """Sets the modifiers (e.g. Qt.CTRL) to show the magnifier.
        
        Use None to show the magnifier always (instead of dragging).
        
        """
        self._magnifierModifiers = modifiers

    def magnifierModifiers(self):
        """Returns the currently set keyboard modifiers (e.g. Qt.CTRL) to show the magnifier."""
        return self._magnifierModifiers

    def hasSelection(self):
        """Returns True if there is a selection."""
        return bool(self._selection)

    def setSelection(self, rect):
        """Sets the selection rectangle."""
        rect = rect.normalized()
        old, self._selection = self._selection, rect
        self._rubberBand.setGeometry(rect)
        self._rubberBand.setVisible(bool(rect))
        if rect != old:
            self.selectionChanged.emit(rect)

    def selection(self):
        """Returns the selection rectangle (normalized) or an invalid QRect()."""
        return QRect(self._selection)

    def clearSelection(self):
        """Hides the selection rectangle."""
        self.setSelection(QRect())

    def redraw(self, rect):
        """Called when the Layout wants to redraw a rectangle."""
        self.update(rect)

    def updateLayout(self):
        """Conforms ourselves to our layout (that must already be updated.)"""
        self.clearSelection()
        self.resize(self._pageLayout.size())
        self.update()

    def highlight(self, highlighter, areas, msec=0):
        """Highlights the list of areas using the given highlighter.
        
        Every area is a two-tuple (page, rect), where rect is a rectangle inside (0, 0, 1, 1) like the
        linkArea attribute of a Poppler.Link.
        
        """
        d = weakref.WeakKeyDictionary()
        for page, areas in itertools.groupby(sorted(areas), lambda a: a[0]):
            d[page] = list(area[1] for area in areas)
        if msec:

            def clear(selfref=weakref.ref(self)):
                self = selfref()
                if self:
                    self.clearHighlight(highlighter)

            t = QTimer(singleShot=True, timeout=clear)
            t.start(msec)
        else:
            t = None
        self.clearHighlight(highlighter)
        self._highlights[highlighter] = (d, t)
        self.update(sum((page.rect() for page in d), QRegion()))

    def clearHighlight(self, highlighter):
        """Removes the highlighted areas of the given highlighter."""
        try:
            (d, t) = self._highlights[highlighter]
        except KeyError:
            return
        del self._highlights[highlighter]
        self.update(sum((page.rect() for page in d), QRegion()))

    def paintEvent(self, ev):
        """Handle PaintEvent on the surface to highlight the selection."""
        painter = QPainter(self)
        pages = list(self.pageLayout().pagesAt(ev.rect()))
        for page in pages:
            page.paint(painter, ev.rect())

        for highlighter, (d, t) in self._highlights.items():
            rects = []
            for page in pages:
                try:
                    rects.extend(map(page.linkRect, d[page]))
                except KeyError:
                    continue
            if rects:
                highlighter.paintRects(painter, rects)

    def handleMousePressEvent(self, ev):
        """Handle mouse press for various operations
            - links to source,
            - magnifier, 
            - selection highlight,
            
            If event was used, return true to indicate processing should stop.
        """

        # As the event comes from the view, we need to map it locally.
        pos = self.mapFromParent(ev.pos())

        # selecting?
        if self._selectionEnabled:
            if self.hasSelection():
                edge = selectionEdge(pos, self.selection())
                if edge == _OUTSIDE:
                    self.clearSelection()
                else:
                    if ev.button() != Qt.RightButton or edge != _INSIDE:
                        self._selecting = True
                        self._selectionEdge = edge
                        self._selectionRect = self.selection()
                        self._selectionPos = pos
                        if edge == _INSIDE:
                            self.setCursor(Qt.SizeAllCursor)
                    return True
            if not self._selecting:
                if ev.modifiers() & Qt.ShiftModifier and ev.button(
                ) == Qt.LeftButton and self._linksEnabled:
                    page, link = self.pageLayout().linkAt(pos)
                    if link:
                        self.linkClickEvent(ev, page, link)
                        return True
                if ev.button() == Qt.RightButton or int(
                        ev.modifiers()) & _SCAM:
                    if not (int(ev.modifiers()) & _SCAM
                            == self._magnifierModifiers
                            and ev.button() == Qt.LeftButton):
                        self._selecting = True
                        self._selectionEdge = _RIGHT | _BOTTOM
                        self._selectionRect = QRect(pos, QSize(0, 0))
                        self._selectionPos = pos
                        return True

        # link?
        if self._linksEnabled:
            page, link = self.pageLayout().linkAt(pos)
            if link:
                self.linkClickEvent(ev, page, link)
                return True
        # magnifier?
        if (self._magnifier
                and int(ev.modifiers()) & _SCAM == self._magnifierModifiers
                and ev.button() == Qt.LeftButton):
            self._magnifier.moveCenter(pos)
            self._magnifier.show()
            self._magnifier.raise_()
            self._magnifying = True
            self.setCursor(QCursor(Qt.BlankCursor))
            return True

        return False

    def handleMouseReleaseEvent(self, ev):
        """Handle mouse release events for various operations:
            - hide magnifier,
            - selection.
            
            If event was used, return true to indicate processing should stop.
        """
        consumed = False
        if self._magnifying:
            self._magnifier.hide()
            self._magnifying = False
            self.unsetCursor()
            consumed = True
        elif self._selecting:
            self._selecting = False
            selection = self._selectionRect.normalized()
            if selection.width() < 8 and selection.height() < 8:
                self.clearSelection()
            else:
                self.setSelection(selection)
            if self._scrolling:
                self.stopScrolling()
            self.unsetCursor()
            consumed = True
        if ev.button() == Qt.RightButton:
            # As the event comes from the view, we need to map it locally.
            self.rightClick(self.mapFromParent(ev.pos()))
            consumed = True

        return consumed

    def handleMouseMoveEvent(self, ev):
        """Handle mouse move events for various operations:
            - move magnifier,
            - selection extension.
            
            If event was used, return true to indicate processing should stop.
        """
        consumed = False
        if self._magnifying:
            # As the event comes from the view, we need to map it locally.
            self._magnifier.moveCenter(self.mapFromParent(ev.pos()))
            consumed = True
        elif self._selecting:
            # As the event comes from the view, we need to map it locally.
            pos = self.mapFromParent(ev.pos())
            self._moveSelection(pos)
            self._rubberBand.show()
            # check if we are dragging close to the edge of the view, scroll if needed
            view = self.viewportRect()
            dx = pos.x() - view.left() - 12
            if dx >= 0:
                dx = pos.x() - view.right() + 12
                if dx < 0:
                    dx = 0
            dy = pos.y() - view.top() - 12
            if dy >= 0:
                dy = pos.y() - view.bottom() + 12
                if dy < 0:
                    dy = 0
            if dx or dy:
                self.startScrolling(dx, dy)
            elif self._scrolling:
                self.stopScrolling()
            consumed = True

        return consumed

    def handleMoveEvent(self, ev):
        """Handle  move events for various operations:
            - move magnifier,
            - selection extension.
            
            If event was used, return true to indicate processing should stop.
        """
        consumed = False
        pos = self.mapFromGlobal(QCursor.pos())
        if self._selecting:
            self._moveSelection(pos)
            consumed = True
        elif self._magnifying:
            self._magnifier.moveCenter(pos)
            consumed = True

        return consumed

    def handleHelpEvent(self, ev):
        """Handle help event: show link if any."""
        if self._linksEnabled:
            page, link = self.pageLayout().linkAt(self.mapFromParent(ev.pos()))
            if link:
                self.linkHelpEvent(ev.globalPos(), page, link)
        return True

    def updateKineticCursor(self, active):
        """Cursor handling when kinetic move starts/stops.
        
        - reset the cursor and hide tooltips if visible at start,
        - update the cursor and show the appropriate tooltips at stop.
        
        Used as a slot linked to the kineticStarted() signal.
        """
        if active:
            self.unsetCursor()
            if QToolTip.isVisible():
                QToolTip.hideText()
        else:
            self.updateCursor(QCursor.pos())
            if self._linksEnabled:
                page, link = self.pageLayout().linkAt(
                    self.mapFromGlobal(QCursor.pos()))
                if link:
                    self.linkHelpEvent(QCursor.pos(), page, link)

    def updateCursor(self, evpos):
        """Set the cursor to the right glyph, depending on action"""
        pos = self.mapFromGlobal(evpos)
        cursor = None
        edge = _OUTSIDE
        if self._selectionEnabled and self.hasSelection():
            edge = selectionEdge(pos, self.selection())

        if edge is not _OUTSIDE:
            if edge in (_TOP, _BOTTOM):
                cursor = Qt.SizeVerCursor
            elif edge in (_LEFT, _RIGHT):
                cursor = Qt.SizeHorCursor
            elif edge in (_LEFT | _TOP, _RIGHT | _BOTTOM):
                cursor = Qt.SizeFDiagCursor
            elif edge in (_TOP | _RIGHT, _BOTTOM | _LEFT):
                cursor = Qt.SizeBDiagCursor
            elif edge is _INSIDE:
                cursor = Qt.SizeAllCursor

        elif self._linksEnabled:
            page, link = self.pageLayout().linkAt(pos)
            if link:
                cursor = Qt.PointingHandCursor
                lid = id(link)
            else:
                lid = None
            if lid != self._currentLinkId:
                if self._currentLinkId is not None:
                    self.linkHoverLeave()
                self._currentLinkId = lid
                if link:
                    self.linkHoverEnter(page, link)

        self.setCursor(cursor) if cursor else self.unsetCursor()

    def linkHelpEvent(self, globalPos, page, link):
        """Called when a QHelpEvent occurs on a link.
        
        The default implementation shows a tooltip if showUrls() is True,
        and emits the linkHelpRequested() signal.
        
        """
        if self._showUrlTips and isinstance(link,
                                            popplerqt4.Poppler.LinkBrowse):
            QToolTip.showText(globalPos, link.url(), self,
                              page.linkRect(link.linkArea()))
        self.linkHelpRequested.emit(globalPos, page, link)

    def rightClick(self, pos):
        """Called when the right mouse button is released.
        
        (Use this instead of the contextMenuEvent as that one also
        fires when starting a right-button selection.)
        The default implementation emite the rightClicked(pos) signal and also
        sends a ContextMenu event to the View widget.
        
        """
        self.rightClicked.emit(pos)
        QApplication.postEvent(
            self.view().viewport(),
            QContextMenuEvent(QContextMenuEvent.Mouse, pos + self.pos()))

    def linkClickEvent(self, ev, page, link):
        """Called when a link is clicked.
        
        The default implementation emits the linkClicked(event, page, link) signal.
        
        """
        self.linkClicked.emit(ev, page, link)

    def linkHoverEnter(self, page, link):
        """Called when the mouse hovers over a link.
        
        The default implementation emits the linkHovered(page, link) signal.
        
        """
        self.linkHovered.emit(page, link)

    def linkHoverLeave(self):
        """Called when the mouse does not hover a link anymore.
        
        The default implementation emits the linkLeft() signal.
        
        """
        self.linkLeft.emit()

    def startScrolling(self, dx, dy):
        """Starts scrolling dx, dy about 10 times a second.
        
        Stops automatically when the end is reached.
        
        """
        self._scrolling = QPoint(dx, dy)
        self._scrollTimer.isActive() or self._scrollTimer.start()

    def stopScrolling(self):
        """Stops scrolling."""
        self._scrolling = False
        self._scrollTimer.stop()

    def _scrollTimeout(self):
        """(Internal) Called by the _scrollTimer."""
        # change the scrollbars, but check how far they really moved.
        pos = self.pos()
        self.view().fastScrollBy(self._scrolling)
        diff = pos - self.pos()
        if not diff:
            self.stopScrolling()

    def _moveSelection(self, pos):
        """(Internal) Moves the dragged selection edge or corner to the given pos (QPoint)."""
        diff = pos - self._selectionPos
        self._selectionPos = pos
        edge = self._selectionEdge
        self._selectionRect.adjust(diff.x() if edge & _LEFT else 0,
                                   diff.y() if edge & _TOP else 0,
                                   diff.x() if edge & _RIGHT else 0,
                                   diff.y() if edge & _BOTTOM else 0)
        self._rubberBand.setGeometry(self._selectionRect.normalized())
        if self.cursor().shape() in (Qt.SizeBDiagCursor, Qt.SizeFDiagCursor):
            # we're dragging a corner, use correct diagonal cursor
            bdiag = (edge in (3, 12)) ^ (self._selectionRect.width() *
                                         self._selectionRect.height() >= 0)
            self.setCursor(Qt.SizeBDiagCursor if bdiag else Qt.SizeFDiagCursor)