def on_sectionResized(self, logicalIndex: int): if self.isSectionHidden(logicalIndex): return leafIndex = QModelIndex(self._pd.leafIndex(logicalIndex)) if leafIndex.isValid(): leafsList = QModelIndexList( self._pd.leafs(self._pd.findRootIndex(leafIndex))) for n in range(leafsList.indexOf(leafIndex), 0, -1): logicalIndex -= 1 w = self.viewport().width() h = self.viewport().height() pos = self.sectionViewportPosition(logicalIndex) r = QRect(pos, 0, w - pos, h) if self.orientation() == Qt.Horizontal: if self.isRightToLeft(): r.setRect(0, 0, pos + self.sectionSize(logicalIndex), h) else: r.setRect(0, pos, w, h - pos) self.viewport().update(r.normalized())
def __selectionForArea(self, area): r1, c1 = self._cellAt(area.topLeft()) r2, c2 = self._cellAt(area.bottomRight()) selarea = QRect(c1, r1, c2 - c1 + 1, r2 - r1 + 1) return selarea.normalized()
class SelectArea(QLabel): doneSignal = pyqtSignal(QPixmap) def __init__(self, *args, **kwargs): super(SelectArea, self).__init__(*args, **kwargs) self.pixmap = take_screenshot() self.setPixmap(self.pixmap) self.setCursor(Qt.CrossCursor) self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint) self.showFullScreen() self.move(0, 0) self.resize(self.pixmap.size()) self.selected = QRect(0, 0, 0, 0) self.first_point = None def paintEvent(self, event): super(SelectArea, self).paintEvent(event) painter = QPainter(self) drawRegion = QRegion(self.pixmap.rect()) drawRegion = drawRegion.subtract(QRegion(self.selected)) painter.setClipRegion(drawRegion) painter.setBrush(QBrush(QColor(0, 0, 0, 120))) painter.drawRect(self.pixmap.rect()) painter.setClipping(False) if self.selected: # направляющие линии painter.setPen(QPen(QColor(255, 255, 255))) painter.setBrush(QBrush(QColor(0, 0, 0, 0))) vertical = self.selected.normalized() vertical.setLeft(-1) vertical.setRight(self.pixmap.rect().right() + 1) horizontal = self.selected.normalized() horizontal.setTop(-1) horizontal.setBottom(self.pixmap.rect().bottom() + 1) painter.drawRects( vertical, horizontal ) # координаты начала bound = self.pixmap.rect() bound.setBottomRight(self.selected.topLeft() - QPoint(5, 5)) painter.drawText( bound, Qt.AlignBottom | Qt.AlignRight, '(%d, %d)' % (self.selected.topLeft().x(), self.selected.topLeft().y()) ) # ширина/высота bound = self.pixmap.rect() bound.setTopLeft(self.selected.bottomRight() + QPoint(5, 5)) painter.drawText( bound, Qt.AlignTop | Qt.AlignLeft, '(%d, %d)' % (self.selected.width(), self.selected.height()) ) def keyPressEvent(self, event): key = event.key() if key == Qt.Key_Escape: self.close() def mousePressEvent(self, event): self.first_point = event.pos() self.selected.setTopLeft(self.first_point) def mouseMoveEvent(self, event): self.move_selection(event.pos()) def mouseReleaseEvent(self, event): self.move_selection(event.pos()) self.finish() def move_selection(self, now_point): self.selected = QRect(self.first_point, now_point).normalized() self.update() def finish(self): self.doneSignal.emit(self.pixmap.copy(self.selected)) self.close()
class ImageAreaSelector (QtGui.QWidget): '''This widget provides means to visually crop a portion of an image by selecting it''' # pylint: disable=W0612 NAME = 'ImageAreaSelector' DESCRIPTION = 'A widget used to select part of an image' AUTHOR = 'Gabriele "Whisky" Visconti' WEBSITE = '' # pylint: enable=W0612 selection_changed = QtCore.pyqtSignal() def __init__(self, pixmap, parent=None): '''Constructor''' QtGui.QWidget.__init__(self, parent) self._pixmap = pixmap self._selection_rect = QRect() self._image_origin = QPoint() self._resize_start = None self._drag_start = None self._handle_size = QSize(-10, -10) self._painter = QtGui.QPainter() self._hl_color1 = QtGui.QPalette().color(QtGui.QPalette.Highlight) self._hl_color2 = QtGui.QPalette().color(QtGui.QPalette.Highlight) self._hl_color2.setAlpha(150) self._zoom = 1.0 self.adjust_minum_size() self.setBackgroundRole(QtGui.QPalette.Dark) self.setMouseTracking(True) self.setCursor(Qt.CrossCursor) # -------------------- [BEGIN] QT_OVERRIDE def mousePressEvent (self, event): '''Overrides QWidget's mousePressEvent. Handles starting a new selection, starting a drag operation''' # pylint: disable=C0103 mouse_pos = event.pos() / self._zoom sel_rect = self._selection_rect if not event.button() == Qt.LeftButton: return if (not sel_rect.isNull()) and sel_rect.contains(mouse_pos, True): handle_rect = QRect(sel_rect.bottomRight(), self._handle_size) if handle_rect.contains(mouse_pos): self._resize_start = mouse_pos else: self._drag_start = mouse_pos else: self._resize_start = mouse_pos sel_rect.setTopLeft (mouse_pos) self._selection_rect.setSize(QSize(0, 0)) def mouseMoveEvent (self, event): '''Overrides QWidget's mouseMoveEvent. Handles resizing and dragging operations on selection''' # pylint: disable=C0103 sel_rect = self._selection_rect if self._resize_start: resize_end = event.pos() / self._zoom sel_rect.setBottomRight(sel_rect.bottomRight() + (resize_end - self._resize_start)) self._resize_start = resize_end self.make_selection_square() self.update() elif self._drag_start is not None: drag_end = event.pos() / self._zoom sel_rect.translate(drag_end - self._drag_start) self._drag_start = drag_end self.update() # cursor shape: mouse_pos = event.pos() / self._zoom if (not sel_rect.isNull()) and sel_rect.contains(mouse_pos, True): handle_rect = QRect(sel_rect.bottomRight(), self._handle_size) if handle_rect.contains(mouse_pos): self.setCursor(Qt.SizeFDiagCursor) else: self.setCursor(Qt.OpenHandCursor) else: self.setCursor(Qt.CrossCursor) def mouseReleaseEvent (self, event): '''Overrides QWidget's mouseReleaseEvent. Handles ending a resizing or draggin operation on the selection''' # pylint: disable=C0103 self._selection_rect = self._selection_rect.normalized() self._resize_start = None self._drag_start = None self.update() if not self._selection_rect.isNull(): self.selection_changed.emit() def paintEvent(self, event): '''Overrides QWidtget's paintEvent.''' # pylint: disable=C0103 QtGui.QWidget.paintEvent(self, event) self._painter.begin(self) pixmap_dest_rect = QRect(self._image_origin * self._zoom, self._pixmap.size()*self._zoom) self._painter.drawPixmap(pixmap_dest_rect, self._pixmap) if not self._selection_rect.isNull(): # preparing the darkened frame: sel_rect = self._selection_rect.normalized() frame = QtGui.QPixmap(event.rect().size()) frame.fill(QtGui.QColor(0, 0, 0, 127)) frame_painter = QtGui.QPainter(frame) # erase the selected area from the frame: frame_painter.setCompositionMode( QtGui.QPainter.CompositionMode_DestinationIn) sel_rect_scaled = QRect(sel_rect.topLeft() * self._zoom, sel_rect.size() * self._zoom) frame_painter.fillRect(sel_rect_scaled, QtGui.QColor(0, 0, 0, 0)) # draw selection border : frame_painter.setCompositionMode( QtGui.QPainter.CompositionMode_SourceOver) frame_painter.setPen(self._hl_color1) frame_painter.drawRect(sel_rect_scaled) # draw the resize grip (if possible) if sel_rect_scaled.width() > 20 and sel_rect_scaled.height() > 20: handle_rect = QRect(sel_rect_scaled.bottomRight(), self._handle_size) frame_painter.fillRect(handle_rect, self._hl_color2) frame_painter.drawRect(handle_rect) frame_painter.end() # painting the darkened frame: self._painter.drawPixmap(0, 0, frame) self._painter.end() def resizeEvent(self, event): '''Overrides QWidget's resizeEvent. Handles image centering.''' # pylint: disable=C0103 self.adjust_image_origin() # -------------------- [END] QT_OVERRIDE def adjust_image_origin(self): '''Recalculates the top left corner's image position, so the image is painted centered''' # pylint: disable=C0103 new_size = self.size() / self._zoom pix_size = self._pixmap.size() dx = (new_size.width() - pix_size.width() ) /2 dy = (new_size.height() - pix_size.height()) /2 new_image_origin = QPoint(dx, dy) self._selection_rect.translate(new_image_origin - self._image_origin) self._image_origin = new_image_origin log.info('image origin: %s' % new_image_origin) def select_unscaled(self): '''Selects, if possible, a 96 x 96 square centered around the original image. In this way the image won't be scaled but won't take up all the 96 x 96 area.''' # pylint: disable=C0103 pix_size = self._pixmap.size() if pix_size.width() <= 96 and pix_size.height() <= 96: viewport_size = self.size() x = (viewport_size.width () - 96) / 2 y = (viewport_size.height() - 96) / 2 self._selection_rect.setTopLeft(QPoint(x, y)) self._selection_rect.setSize(QSize(96, 96)) self.update() self.selection_changed.emit() def select_all(self): '''Selects the whole image. Currently broken for images taller than wide.''' # TODO: make me work! self._selection_rect.setTopLeft(self._image_origin) self._selection_rect.setSize(self._pixmap.size()) self.update() self.selection_changed.emit() def rotate_left(self): '''Rotates the image counterclockwise.''' self._pixmap = self._pixmap.transformed(QtGui.QTransform().rotate(-90)) self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def rotate_right(self): '''Rotates the image clockwise''' self._pixmap = self._pixmap.transformed(QtGui.QTransform().rotate(90)) self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def adjust_minum_size(self): '''Sets the new minimum size, calculated upon the image size and the _zoom factor.''' pixmap = self._pixmap if pixmap.width() < 96 or pixmap.height() < 96: min_size = QSize(96, 96) else: min_size = pixmap.size() self.setMinimumSize(min_size*self._zoom) def make_selection_square(self): '''Modify the selected area making it square''' wid = self._selection_rect.width () self._selection_rect.setSize(QSize(wid, wid)) def set_zoom(self, zoomlevel): '''Sets the specified zoomlevel''' self._zoom = zoomlevel self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def fit_zoom(self): '''Chooses a zoomlevel that makes visible the entire image. Currently broken.''' widget_wid = self.size().width () widget_hei = self.size().height() pixmap_wid = self._pixmap.width () pixmap_hei = self._pixmap.height() self._zoom = (min(widget_wid, widget_hei) / min(pixmap_wid, pixmap_hei)) self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def get_selected_pixmap(self): '''Returns the pixmap contained in the selection rect. Currently doesn't handle transparency correctly''' sel_rect_scaled = QRect(self._selection_rect.topLeft() * self._zoom, self._selection_rect.size() * self._zoom) return QtGui.QPixmap.grabWidget(self, sel_rect_scaled)
def on_sectionResized(self, logicalIndex: int): if self.isSectionHidden(logicalIndex): return leafIndex = QModelIndex(self._pd.leafIndex(logicalIndex)) if leafIndex.isValid(): leafsList = QModelIndexList(self._pd.leafs(self._pd.findRootIndex(leafIndex))) for n in range(leafsList.indexOf(leafIndex), 0, -1): logicalIndex-=1 w = self.viewport().width() h = self.viewport().height() pos = self.sectionViewportPosition(logicalIndex) r = QRect(pos, 0, w - pos, h) if self.orientation() == Qt.Horizontal: if self.isRightToLeft(): r.setRect(0, 0, pos + self.sectionSize(logicalIndex), h) else: r.setRect(0, pos, w, h - pos) self.viewport().update(r.normalized()) def setModel(self, model): super().setModel(model) model.layoutChanged.connect(self.layoutChanged) self.layoutChanged() def layoutChanged(self): if self.model(): self._pd.initFromNewModel(self.orientation(), self.model()) axis = ("column", "row")[self.orientation()!=Qt.Horizontal] cnt = getattr(self.model(), axis+"Count")(QModelIndex()) if cnt: self.initializeSections(0, cnt-1) MultiIndexHeaderView=HierarchicalHeaderView class DataFrameModel(QtCore.QAbstractTableModel): #na_values:least|greatest - for sorting options = {"striped": True, "stripesColor": "#fafafa", "na_values": "least", "tooltip_min_len": 21} def __init__(self, dataframe=None): super().__init__() self.setDataFrame(dataframe if dataframe is not None else pd.DataFrame()) def setDataFrame(self, dataframe): self.df = dataframe.copy() # self.df_full = self.df self.layoutChanged.emit() def rowCount(self, parent): return len(self.df) def columnCount(self, parent): return len(self.df.columns) def readLevel(self, y=0, xs=0, xe=None, orient=None): c = getattr(self.df, ("columns", "index")[orient!=HorizontalHeaderDataRole]) if not hasattr(c, "levels"): #not MultiIndex return [QtGui.QStandardItem(str(i)) for i in c] sibl = [] section_start, v, xe = xs, None, xe or len(c) for i in range(xs, xe): label = c.labels[y][i] if label!=v: if y+1<len(c.levels) and i>xs: children = self.readLevel(y+1, section_start, i, orient=orient) sibl[-1].appendRow(children) item = QtGui.QStandardItem(str(c.levels[y][label])) sibl.append(item) section_start = i v=label if y+1<len(c.levels): children = self.readLevel(y+1, section_start, orient=orient) sibl[-1].appendRow(children) return sibl def data(self, index, role): row, col = index.row(), index.column() if role in (Qt.DisplayRole, Qt.ToolTipRole): ret = self.df.iat[row, col] if ret is not None and ret==ret: #convert to str except for None, NaN, NaT if isinstance(ret, float): ret = "{:n}".format(ret) elif isinstance(ret, datetime.date): #FIXME: show microseconds optionally ret = ret.strftime(("%x", "%c")[isinstance(ret, datetime.datetime)]) else: ret = str(ret) if role == Qt.ToolTipRole: if len(ret)<self.options["tooltip_min_len"]: ret = "" return ret elif role == Qt.BackgroundRole: if self.options["striped"] and row%2: return QBrush(QColor(self.options["stripesColor"])) elif role in (HorizontalHeaderDataRole, VerticalHeaderDataRole): hm = QtGui.QStandardItemModel() hm.appendRow(self.readLevel(orient=role)) return hm def reorder(self, oldIndex, newIndex, orientation): "Reorder columns / rows" horizontal = orientation==Qt.Horizontal cols = list(self.df.columns if horizontal else self.df.index) cols.insert(newIndex, cols.pop(oldIndex)) self.df = self.df[cols] if horizontal else self.df.T[cols].T return True # def filter(self, filt=None): # self.df = self.df_full if filt is None else self.df[filt] # self.layoutChanged.emit() def headerData(self, section, orientation, role): if role != Qt.DisplayRole: return label = getattr(self.df, ("columns", "index")[orientation!=Qt.Horizontal])[section] # return label if type(label) is tuple else label return ("\n", " | ")[orientation!=Qt.Horizontal].join(str(i) for i in label) if type(label) is tuple else str(label) def dataFrame(self): return self.df def sort(self, column, order): # print("sort", column, order) #FIXME: double sort after setSortingEnabled(True) if len(self.df): asc = order==Qt.AscendingOrder na_pos = 'first' if (self.options["na_values"]=="least")==asc else 'last' self.df.sort_values(self.df.columns[column], ascending=asc, inplace=True, na_position=na_pos) self.layoutChanged.emit() if __name__=="__main__": import sys, locale locale.setlocale(locale.LC_ALL, '') #system locale settings app = QtGui.QApplication(sys.argv) form = QtGui.QWidget() form.setAttribute(Qt.WA_DeleteOnClose) #http://stackoverflow.com/a/27178019/1119602 form.setMinimumSize(700, 260) view = QtGui.QTableView() QtGui.QVBoxLayout(form).addWidget(view) form.show() #Prepare data tuples=[('bar', 'one', 'q'), ('bar', 'two', 'q'), ('baz', 'one', 'q'), ('baz', 'two', 'q'), ('foo', 'one', 'q'), ('foo', 'two', 'q'), ('qux', 'one', 'q'), ('qux', 'two', 'q')] index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second', 'third']) df=pd.DataFrame(pd.np.random.randn(6, 6), index=index[:6], columns=index[:6]) print("DataFrame:\n%s"%df) #Prepare view # oldh, oldv = view.horizontalHeader(), view.verticalHeader() # oldh.setParent(form), oldv.setParent(form) #Save old headers for some reason MultiIndexHeaderView(Qt.Horizontal, view) MultiIndexHeaderView(Qt.Vertical, view) view.horizontalHeader().setMovable(True) #reorder DataFrame columns manually #Set data view.setModel(DataFrameModel(df)) view.resizeColumnsToContents() view.resizeRowsToContents() #Set sorting enabled (after setting model) view.setSortingEnabled(True) sys.exit(app.exec())
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)
class ImageAreaSelector(QtGui.QWidget): '''This widget provides means to visually crop a portion of an image by selecting it''' # pylint: disable=W0612 NAME = 'ImageAreaSelector' DESCRIPTION = 'A widget used to select part of an image' AUTHOR = 'Gabriele "Whisky" Visconti' WEBSITE = '' # pylint: enable=W0612 selection_changed = QtCore.pyqtSignal() def __init__(self, pixmap, parent=None): '''Constructor''' QtGui.QWidget.__init__(self, parent) self._pixmap = pixmap self._selection_rect = QRect() self._image_origin = QPoint() self._resize_start = None self._drag_start = None self._handle_size = QSize(-10, -10) self._painter = QtGui.QPainter() self._hl_color1 = QtGui.QPalette().color(QtGui.QPalette.Highlight) self._hl_color2 = QtGui.QPalette().color(QtGui.QPalette.Highlight) self._hl_color2.setAlpha(150) self._zoom = 1.0 self.adjust_minum_size() self.setBackgroundRole(QtGui.QPalette.Dark) self.setMouseTracking(True) self.setCursor(Qt.CrossCursor) # -------------------- [BEGIN] QT_OVERRIDE def mousePressEvent(self, event): '''Overrides QWidget's mousePressEvent. Handles starting a new selection, starting a drag operation''' # pylint: disable=C0103 mouse_pos = event.pos() / self._zoom sel_rect = self._selection_rect if not event.button() == Qt.LeftButton: return if (not sel_rect.isNull()) and sel_rect.contains(mouse_pos, True): handle_rect = QRect(sel_rect.bottomRight(), self._handle_size) if handle_rect.contains(mouse_pos): self._resize_start = mouse_pos else: self._drag_start = mouse_pos else: self._resize_start = mouse_pos sel_rect.setTopLeft(mouse_pos) self._selection_rect.setSize(QSize(0, 0)) def mouseMoveEvent(self, event): '''Overrides QWidget's mouseMoveEvent. Handles resizing and dragging operations on selection''' # pylint: disable=C0103 sel_rect = self._selection_rect if self._resize_start: resize_end = event.pos() / self._zoom sel_rect.setBottomRight(sel_rect.bottomRight() + (resize_end - self._resize_start)) self._resize_start = resize_end self.make_selection_square() self.update() elif self._drag_start is not None: drag_end = event.pos() / self._zoom sel_rect.translate(drag_end - self._drag_start) self._drag_start = drag_end self.update() # cursor shape: mouse_pos = event.pos() / self._zoom if (not sel_rect.isNull()) and sel_rect.contains(mouse_pos, True): handle_rect = QRect(sel_rect.bottomRight(), self._handle_size) if handle_rect.contains(mouse_pos): self.setCursor(Qt.SizeFDiagCursor) else: self.setCursor(Qt.OpenHandCursor) else: self.setCursor(Qt.CrossCursor) def mouseReleaseEvent(self, event): '''Overrides QWidget's mouseReleaseEvent. Handles ending a resizing or draggin operation on the selection''' # pylint: disable=C0103 self._selection_rect = self._selection_rect.normalized() self._resize_start = None self._drag_start = None self.update() if not self._selection_rect.isNull(): self.selection_changed.emit() def paintEvent(self, event): '''Overrides QWidtget's paintEvent.''' # pylint: disable=C0103 QtGui.QWidget.paintEvent(self, event) self._painter.begin(self) pixmap_dest_rect = QRect(self._image_origin * self._zoom, self._pixmap.size() * self._zoom) self._painter.drawPixmap(pixmap_dest_rect, self._pixmap) if not self._selection_rect.isNull(): # preparing the darkened frame: sel_rect = self._selection_rect.normalized() frame = QtGui.QPixmap(event.rect().size()) frame.fill(QtGui.QColor(0, 0, 0, 127)) frame_painter = QtGui.QPainter(frame) # erase the selected area from the frame: frame_painter.setCompositionMode( QtGui.QPainter.CompositionMode_DestinationIn) sel_rect_scaled = QRect(sel_rect.topLeft() * self._zoom, sel_rect.size() * self._zoom) frame_painter.fillRect(sel_rect_scaled, QtGui.QColor(0, 0, 0, 0)) # draw selection border : frame_painter.setCompositionMode( QtGui.QPainter.CompositionMode_SourceOver) frame_painter.setPen(self._hl_color1) frame_painter.drawRect(sel_rect_scaled) # draw the resize grip (if possible) if sel_rect_scaled.width() > 20 and sel_rect_scaled.height() > 20: handle_rect = QRect(sel_rect_scaled.bottomRight(), self._handle_size) frame_painter.fillRect(handle_rect, self._hl_color2) frame_painter.drawRect(handle_rect) frame_painter.end() # painting the darkened frame: self._painter.drawPixmap(0, 0, frame) self._painter.end() def resizeEvent(self, event): '''Overrides QWidget's resizeEvent. Handles image centering.''' # pylint: disable=C0103 self.adjust_image_origin() # -------------------- [END] QT_OVERRIDE def adjust_image_origin(self): '''Recalculates the top left corner's image position, so the image is painted centered''' # pylint: disable=C0103 new_size = self.size() / self._zoom pix_size = self._pixmap.size() dx = (new_size.width() - pix_size.width()) / 2 dy = (new_size.height() - pix_size.height()) / 2 new_image_origin = QPoint(dx, dy) self._selection_rect.translate(new_image_origin - self._image_origin) self._image_origin = new_image_origin log.info('image origin: %s' % new_image_origin) def select_unscaled(self): '''Selects, if possible, a 96 x 96 square centered around the original image. In this way the image won't be scaled but won't take up all the 96 x 96 area.''' # pylint: disable=C0103 pix_size = self._pixmap.size() if pix_size.width() <= 96 and pix_size.height() <= 96: viewport_size = self.size() x = (viewport_size.width() - 96) / 2 y = (viewport_size.height() - 96) / 2 self._selection_rect.setTopLeft(QPoint(x, y)) self._selection_rect.setSize(QSize(96, 96)) self.update() self.selection_changed.emit() def select_all(self): '''Selects the whole image. Currently broken for images taller than wide.''' # TODO: make me work! self._selection_rect.setTopLeft(self._image_origin) self._selection_rect.setSize(self._pixmap.size()) self.update() self.selection_changed.emit() def rotate_left(self): '''Rotates the image counterclockwise.''' self._pixmap = self._pixmap.transformed(QtGui.QTransform().rotate(-90)) self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def rotate_right(self): '''Rotates the image clockwise''' self._pixmap = self._pixmap.transformed(QtGui.QTransform().rotate(90)) self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def adjust_minum_size(self): '''Sets the new minimum size, calculated upon the image size and the _zoom factor.''' pixmap = self._pixmap if pixmap.width() < 96 or pixmap.height() < 96: min_size = QSize(96, 96) else: min_size = pixmap.size() self.setMinimumSize(min_size * self._zoom) def make_selection_square(self): '''Modify the selected area making it square''' wid = self._selection_rect.width() self._selection_rect.setSize(QSize(wid, wid)) def set_zoom(self, zoomlevel): '''Sets the specified zoomlevel''' self._zoom = zoomlevel self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def fit_zoom(self): '''Chooses a zoomlevel that makes visible the entire image. Currently broken.''' widget_wid = self.size().width() widget_hei = self.size().height() pixmap_wid = self._pixmap.width() pixmap_hei = self._pixmap.height() self._zoom = (min(widget_wid, widget_hei) / min(pixmap_wid, pixmap_hei)) self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def get_selected_pixmap(self): '''Returns the pixmap contained in the selection rect. Currently doesn't handle transparency correctly''' sel_rect_scaled = QRect(self._selection_rect.topLeft() * self._zoom, self._selection_rect.size() * self._zoom) return QtGui.QPixmap.grabWidget(self, sel_rect_scaled)
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)