class MyApp(QWidget): def __init__(self): super().__init__() # self.window_width, self.window_height = 1200, 800 # layout = QVBoxLayout() # self.setLayout(layout) self.pix = QPixmap(self.rect().size()) # print(self.rect().size()) self.begin, self.destination = QPoint(), QPoint() self.image = QPixmap(imgPath) def paintEvent(self, event): painter = QPainter(self) painter.drawPixmap(QPoint(), self.pix) painter.drawPixmap(0, 0, int(43), int(43), self.image) # painter.drawPixmap(self.rect(), self.image) print(self.rect()) if not self.begin.isNull() and not self.destination.isNull(): rect = QRect(self.begin, self.destination) painter.drawRect(rect.normalized()) def mousePressEvent(self, event): if event.buttons() & Qt.LeftButton: self.begin = event.pos() self.destination = self.begin self.update() def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton: self.destination = event.pos() # print(event.pos()) self.update()
class Window(QLabel): def __init__(self, parent=None): QLabel.__init__(self, parent) self.rubberBand = QRubberBand(QRubberBand.Rectangle, self) self.origin = QPoint() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.origin = QPoint(event.pos()) self.rubberBand.setGeometry(QRect(self.origin, QSize())) self.rubberBand.show() def mouseMoveEvent(self, event): if not self.origin.isNull(): self.rubberBand.setGeometry( QRect(self.origin, event.pos()).normalized()) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: self.rubberBand.hide()
def showContextMenu(self, point: QPoint): if point.isNull() or (self.manager.isMainWrapper(self.parentWidget()) and self.tabBar().count() <= 1): return singleTabFrame = cast(self.currentWidget(), QToolWindowSingleTabAreaFrame) if singleTabFrame and singleTabFrame.caption and singleTabFrame.caption.contentsRect( ).contains(point) and not self.manager.isMainWrapper( self.parentWidget()): menu = QMenu(self) menu.addAction("Close").triggered.connect( lambda b: singleTabFrame.close()) menu.exec(self.mapToGlobal(QPoint(point.x(), point.y()))) return tabIndex = self.tabBar().tabAt(point) if tabIndex >= 0: def closeTab(): self.closeTab(tabIndex) menu = QMenu(self) menu.addAction("Close").triggered.connect(lambda b: closeTab()) menu.exec(self.mapToGlobal(QPoint(point.x(), point.y())))
class Window(QLabel): def __init__(self, pixmap, parent=None): QLabel.__init__(self, parent) self.rubberBand = QRubberBand(QRubberBand.Rectangle, self) self.origin = QPoint() self.pixmap = pixmap def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.origin = QPoint(event.pos()) self.rubberBand.setGeometry(QRect(self.origin, QSize())) self.rubberBand.show() def mouseMoveEvent(self, event): if not self.origin.isNull(): self.rubberBand.setGeometry( QRect(self.origin, event.pos()).normalized()) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: self.rubberBand.hide() print(self.rubberBand.geometry()) def resizeEvent(self, event): self.updateScreenshotLabel() # scaledSize = self.pixmap.size() # scaledSize.scale(self.screenshotLabel.size(), Qt.KeepAspectRatio) # if not self.screenshotLabel.pixmap() or scaledSize != self.screenshotLabel.pixmap().size(): # self.updateScreenshotLabel() def updateScreenshotLabel(self): self.setPixmap( self.pixmap.scaled(self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
class MyLabel(QLabel): def __init__(self, parent=None, func=None): self.func = func QLabel.__init__(self, parent) self.rubberBand = QRubberBand(QRubberBand.Rectangle, self) self.origin = QPoint() self.slitpos = 250 self.focus = (333, 666, 333, 666) #xs,xe,ys,ye self.geometry = (0, 0, 1000, 1000) #x,y,w,h def paintEvent(self, event): QLabel.paintEvent(self, event) painter = QPainter(self) painter.setPen(Qt.red) x, y, w, h = self.geometry d = 20 painter.drawLine(x + w // 2 - self.slitpos * w // 1000, y, x + w // 2 - self.slitpos * w // 1000, y + h) painter.drawLine(x + w // 2 - self.slitpos * w // 1000 - d, y + h / 2, x + w // 2 - self.slitpos * w // 1000, y + h / 2 - d) painter.drawLine(x + w // 2 - self.slitpos * w // 1000 - d, y + h / 2, x + w // 2 - self.slitpos * w // 1000, y + h / 2 + d) painter.drawLine(x + w // 2 + self.slitpos * w // 1000, y, x + w // 2 + self.slitpos * w // 1000, y + h) painter.drawLine(x + w // 2 + self.slitpos * w // 1000 + d, y + h // 2, x + w // 2 + self.slitpos * w // 1000, y + h // 2 - d) painter.drawLine(x + w // 2 + self.slitpos * w // 1000 + d, y + h // 2, x + w // 2 + self.slitpos * w // 1000, y + h // 2 + d) painter.setPen(Qt.green) painter.drawRect(x + w * self.focus[0] // 1000, y + h * self.focus[2] // 1000, w * (self.focus[1] - self.focus[0]) // 1000, h * (self.focus[3] - self.focus[2]) // 1000) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.origin = QPoint(event.pos()) self.rubberBand.setGeometry(QRect(self.origin, QSize())) self.rubberBand.show() def mouseMoveEvent(self, event): if not self.origin.isNull(): self.rubberBand.setGeometry( QRect(self.origin, event.pos()).normalized()) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: self.rubberBand.hide() self.region = QRect(self.origin, event.pos()).normalized() if self.func is not None: self.func(self.region)
class MyLabel(QLabel): def __init__(self, parent = None, func=None): self.func = func QLabel.__init__(self, parent) self.rubberBand = QRubberBand(QRubberBand.Rectangle, self) self.origin = QPoint() self.slitpos = 250 self.focus = (333,666,333,666) #xs,xe,ys,ye self.geometry = (0,0,1000,1000) #x,y,w,h def paintEvent(self, event): QLabel.paintEvent(self, event) painter = QPainter(self) painter.setPen(Qt.red) x,y,w,h = self.geometry d = 20 painter.drawLine(x+w//2-self.slitpos*w//1000,y, x+w//2-self.slitpos*w//1000,y+h) painter.drawLine(x+w//2-self.slitpos*w//1000-d,y+h/2, x+w//2-self.slitpos*w//1000, y+h/2-d) painter.drawLine(x+w//2-self.slitpos*w//1000-d,y+h/2, x+w//2-self.slitpos*w//1000, y+h/2+d) painter.drawLine(x+w//2+self.slitpos*w//1000,y, x+w//2+self.slitpos*w//1000,y+h) painter.drawLine(x+w//2+self.slitpos*w//1000+d,y+h//2, x+w//2+self.slitpos*w//1000, y+h//2-d) painter.drawLine(x+w//2+self.slitpos*w//1000+d,y+h//2, x+w//2+self.slitpos*w//1000, y+h//2+d) painter.setPen(Qt.green) painter.drawRect(x+w*self.focus[0]//1000,y+h*self.focus[2]//1000,w*(self.focus[1]-self.focus[0])//1000,h*(self.focus[3]-self.focus[2])//1000) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.origin = QPoint(event.pos()) self.rubberBand.setGeometry(QRect(self.origin, QSize())) self.rubberBand.show() def mouseMoveEvent(self, event): if not self.origin.isNull(): self.rubberBand.setGeometry(QRect(self.origin, event.pos()).normalized()) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: self.rubberBand.hide() self.region = QRect(self.origin, event.pos()).normalized() if self.func is not None: self.func(self.region)
class Window(QWidget): def __init__(self): super().__init__() self.setGeometry(150, 250, 500, 500) self.setWindowTitle("Ammyyy") self.setWindowIcon(QIcon("a.jpeg")) self.begin = QPoint() self.end = QPoint() self.rectangles = [] def paintEvent(self, event): qp = QPainter(self) qp.setPen(QPen(Qt.black, 2, Qt.SolidLine)) for rectangle in self.rectangles: qp.drawRect(rectangle) if not self.begin.isNull() and not self.end.isNull(): qp.drawRect(QRect(self.begin, self.end).normalized()) def mousePressEvent(self, event): self.begin = self.end = event.pos() self.update() super().mousePressEvent(event) def mouseMoveEvent(self, event): self.end = event.pos() self.update() super().mouseMoveEvent(event) def mouseReleaseEvent(self, event): r = QRect(self.begin, self.end).normalized() self.rectangles.append(r) self.begin = self.end = QPoint() self.update() super().mouseReleaseEvent(event)
class QLabelSelectable(QLabel): selectionFinished = pyqtSignal() def __init__(self, parent=None): QLabel.__init__(self, parent) self.rubberBand = QRubberBand(QRubberBand.Rectangle, self) self.origin = QPoint() self.startSelection = False self.endSelection = False def reset(self): self.startSelection = False self.endSelection = False def mousePressEvent(self, event): if event.button( ) == Qt.LeftButton and self.startSelection and not self.endSelection: self.origin = QPoint(event.pos()) self.rubberBand.setGeometry(QRect(self.origin, QSize())) self.rubberBand.show() def mouseMoveEvent(self, event): if not self.origin.isNull( ) and self.startSelection and not self.endSelection: self.rubberBand.setGeometry( QRect(self.origin, event.pos()).normalized()) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton and self.startSelection: #print(self.rubberBand.geometry()) self.drawRectangle() self.rubberBand.hide() self.endSelection = True def drawRectangle(self): # create painter instance with pixmap self.painterInstance = QPainter(self.pixmap()) # set rectangle color and thickness self.penRectangle = QPen(Qt.red) self.penRectangle.setWidth(3) # draw rectangle on painter self.painterInstance.setPen(self.penRectangle) self.painterInstance.drawRect(self.rubberBand.geometry()) self.selectionFinished.emit()
class QLabelRect(QLabel): def __init__(self, parent=None): QLabel.__init__(self, parent) self.setStyleSheet( "border:1px solid rgb(0, 255, 0); border-radius: 2px") self.rubberBand = QRubberBand(QRubberBand.Rectangle, self) self.origin = None def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.origin = QPoint(event.pos()) self.rubberBand.setGeometry(QRect(self.origin, QSize())) self.rubberBand.show() def mouseMoveEvent(self, event): if not self.origin is None and not self.origin.isNull(): self.rubberBand.setGeometry( QRect(self.origin, event.pos()).normalized()) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: if not self.origin is None and not self.parent().img is None: r = Range(self.rubberBand, self.parent().aspect, self.parent().img) self.parent().list_srect.addItem(r) self.origin = None self.rubberBand = QRubberBand(QRubberBand.Rectangle, self) elif not self.parent() is None: self.parent().display_pixel(event.pos().x() * self.parent().aspect, event.pos().y() * self.parent().aspect) def select_all(self): self.rubberBand = QRubberBand(QRubberBand.Rectangle, self) self.rubberBand.setGeometry(0, 0, self.parent().img_label.width(), self.parent().img_label.height()) self.rubberBand.show() r = Range(self.rubberBand, self.parent().aspect, self.parent().img) self.parent().list_srect.addItem(r) self.origin = None self.rubberBand = QRubberBand(QRubberBand.Rectangle, self)
class Image(QLabel): # taken from https://wiki.python.org/moin/PyQt/Selecting%20a%20region%20of%20a%20widget def __init__(self, parent=None): QLabel.__init__(self, parent) self.selection = QRubberBand(QRubberBand.Rectangle, self) self.selection_origin = None self.selection_end = None self.image_path = None def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.selection_origin = QPoint(event.pos()) self.selection.setGeometry(QRect(self.selection_origin, QSize())) self.selection.show() def mouseMoveEvent(self, event): if not self.selection_origin.isNull(): self.selection.setGeometry(QRect(self.selection_origin, event.pos()).normalized()) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: self.selection_end = QPoint(event.pos())
class StatusLabel(QLabel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__press_pos = QPoint() self.initUI() def initUI(self): self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.WindowCloseButtonHint) self.setAttribute(Qt.WA_TranslucentBackground) self.setText("Drag me...") self.setFont(QFont("Times", 10, QFont.Bold)) self.setStyleSheet('color: yellow') self.adjustSize() self.setGeometry( QStyle.alignedRect( Qt.LeftToRight, Qt.AlignCenter, self.size(), QApplication.instance().desktop().availableGeometry() ) ) print("popup") self.keep_getting_incubators = False def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.__press_pos = event.pos() if event.button() == Qt.MiddleButton: sys.exit() def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: self.__press_pos = QPoint() def mouseMoveEvent(self, event): if not self.__press_pos.isNull(): self.move(self.pos() + (event.pos() - self.__press_pos))
class ImgShowWidget(QtWidgets.QLabel): initBB = None def __init__(self, image, parent=None): super().__init__(parent) self.image = image self._red = (0, 0, 255) self._width = 2 self._min_size = (30, 30) self.rubberBand = QRubberBand(QRubberBand.Rectangle, self) self.origin = QPoint() pixmap01 = QtGui.QPixmap.fromImage(self.image) pixmap_image = QtGui.QPixmap(pixmap01) self.setPixmap(pixmap_image) self.setAlignment(QtCore.Qt.AlignCenter) self.setScaledContents(True) self.setMinimumSize(1, 1) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.origin = QPoint(event.pos()) self.rubberBand.setGeometry(QRect(self.origin, QSize())) self.rubberBand.show() def mouseMoveEvent(self, event): if not self.origin.isNull(): self.rubberBand.setGeometry( QRect(self.origin, event.pos()).normalized()) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: self.rubberBand.show() x = self.origin.x() y = self.origin.y()
class SelectNewClip(QMainWindow): def __init__(self, parent=None, reference_img=None, callback=None): QMainWindow.__init__(self, parent) self.rubberBand = QRubberBand(QRubberBand.Rectangle, self) self.origin = QPoint() self.callback = callback wid = QWidget() hbox = QHBoxLayout() hbox.setSpacing(0) hbox.setContentsMargins(0, 0, 0, 0) image_frame = QLabel() image_frame.setPixmap(convert_to_qt(reference_img)) hbox.addWidget(image_frame) wid.setLayout(hbox) self.setCentralWidget(wid) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.origin = QPoint(event.pos()) self.rubberBand.setGeometry(QRect(self.origin, QSize())) self.rubberBand.show() def mouseMoveEvent(self, event): if not self.origin.isNull(): self.rubberBand.setGeometry( QRect(self.origin, event.pos()).normalized()) def mouseReleaseEvent(self, event): self.callback(self.rubberBand.geometry()) if event.button() == Qt.LeftButton: self.rubberBand.hide() self.close()
class Viewer(QGraphicsView): def __init__(self, gridsize_label, position_label, help_label): QGraphicsView.__init__(self) self.gridsize_label = gridsize_label self.position_label = position_label self.help_label = help_label # Create a QGraphicsScene which this view looks at self.scene = QGraphicsScene(self) self.scene.setSceneRect(QRectF()) self.setScene(self.scene) # Customize QGraphicsView self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setInteractive(False) self.scale(1, -1) # Flips around the Y axis # Use OpenGL http://ralsina.me/stories/BBS53.html # self.setViewport(QtOpenGL.QGLWidget()) self.rubberBand = QRubberBand(QRubberBand.Rectangle, self) self.pen = QPen(QtCore.Qt.black, 0) self.portpen = QPen(PORT_COLOR, 3) self.portpen.setCosmetic(True) # Makes constant width self.portfont = QtGui.QFont('Arial', pointSize=14) self.portfontcolor = PORT_COLOR self.subportpen = QPen(SUBPORT_COLOR, 3) self.subportpen.setCosmetic(True) # Makes constant width self.subportfont = QtGui.QFont('Arial', pointSize=14) self.subportfontcolor = SUBPORT_COLOR # Tracking ports # Various status variables self._mousePressed = None self._rb_origin = QPoint() self.zoom_factor_total = 1 # Grid variables self.gridpen = QPen(QtCore.Qt.black, 0) self.gridpen.setStyle(QtCore.Qt.DotLine) self.gridpen.setDashPattern([1, 4]) self.gridpen.setColor(QtGui.QColor(0, 0, 0, 125)) # self.gridpen = QPen(QtCore.Qt.black, 1) # self.gridpen.setCosmetic(True) # Makes constant width self.scene_polys = [] self.initialize() def add_polygons(self, polygons, color='#A8F22A', alpha=1): qcolor = QColor() qcolor.setNamedColor(color) qcolor.setAlphaF(alpha) for points in polygons: qpoly = QPolygonF([QPointF(p[0], p[1]) for p in points]) scene_poly = self.scene.addPolygon(qpoly) scene_poly.setBrush(qcolor) scene_poly.setPen(self.pen) self.scene_polys.append(scene_poly) # Update custom bounding box sr = scene_poly.sceneBoundingRect() if len(self.scene_polys) == 1: self.scene_xmin = sr.left() self.scene_xmax = sr.right() self.scene_ymin = sr.top() self.scene_ymax = sr.bottom() else: self.scene_xmin = min(self.scene_xmin, sr.left()) self.scene_xmax = max(self.scene_xmax, sr.right()) self.scene_ymin = min(self.scene_ymin, sr.top()) self.scene_ymax = max(self.scene_ymax, sr.bottom()) def reset_view(self): # The SceneRect controls how far you can pan, make it larger than # just the bounding box so middle-click panning works panning_rect = QRectF(self.scene_bounding_rect) panning_rect_center = panning_rect.center() panning_rect_size = max(panning_rect.width(), panning_rect.height()) * 3 panning_rect.setSize(QSizeF(panning_rect_size, panning_rect_size)) panning_rect.moveCenter(panning_rect_center) self.setSceneRect(panning_rect) self.fitInView(self.scene_bounding_rect, Qt.KeepAspectRatio) self.zoom_view(0.8) self.update_grid() def add_port(self, port, is_subport=False): if (port.width is None) or (port.width == 0): x, y = port.midpoint cs = 1 # cross size pn = QPointF(x, y + cs) ps = QPointF(x, y - cs) pe = QPointF(x + cs, y) pw = QPointF(x - cs, y) qline1 = self.scene.addLine(QLineF(pn, ps)) qline2 = self.scene.addLine(QLineF(pw, pe)) port_shapes = [qline1, qline2] else: point1, point2 = port.endpoints point1 = QPointF(point1[0], point1[1]) point2 = QPointF(point2[0], point2[1]) qline = self.scene.addLine(QLineF(point1, point2)) arrow_points = np.array([[0, 0], [10, 0], [6, 4], [6, 2], [0, 2] ]) / (40) * port.width arrow_qpoly = QPolygonF( [QPointF(p[0], p[1]) for p in arrow_points]) port_scene_poly = self.scene.addPolygon(arrow_qpoly) port_scene_poly.setRotation(port.orientation) port_scene_poly.moveBy(port.midpoint[0], port.midpoint[1]) port_shapes = [qline, port_scene_poly] qtext = self.scene.addText(str(port.name), self.portfont) port_items = port_shapes + [qtext] rad = port.orientation * np.pi / 180 x, y = port.endpoints[0] * 1 / 4 + port.endpoints[ 1] * 3 / 4 + np.array([np.cos(rad), np.sin(rad)]) * port.width / 8 # x,y = port.midpoint[0], port.midpoint[1] # x,y = x - qtext.boundingRect().width()/2, y - qtext.boundingRect().height()/2 qtext.setPos(QPointF(x, y)) qtext.setFlag(QGraphicsItem.ItemIgnoresTransformations) if not is_subport: [shape.setPen(self.portpen) for shape in port_shapes] qtext.setDefaultTextColor(self.portfontcolor) self.portitems += port_items else: [shape.setPen(self.subportpen) for shape in port_shapes] qtext.setDefaultTextColor(self.subportfontcolor) self.subportitems += port_items # self.portlabels.append(qtext) def add_aliases(self, aliases): for name, ref in aliases.items(): qtext = self.scene.addText(str(name), self.portfont) x, y = ref.center qtext.setPos(QPointF(x, y)) qtext.setFlag(QGraphicsItem.ItemIgnoresTransformations) self.aliasitems += [qtext] def set_port_visibility(self, visible=True): for item in self.portitems: item.setVisible(visible) self.ports_visible = visible def set_subport_visibility(self, visible=True): for item in self.subportitems: item.setVisible(visible) self.subports_visible = visible def set_alias_visibility(self, visible=True): for item in self.aliasitems: item.setVisible(visible) self.aliases_visible = visible def initialize(self): self.scene.clear() self.polygons = {} self.portitems = [] self.subportitems = [] self.aliasitems = [] self.aliases_visible = True self.ports_visible = True self.subports_visible = True self.mouse_position = [0, 0] self.grid_size_snapped = 0 self.setMouseTracking(True) self.scene_bounding_rect = None self.scene_polys = [] self.scene_xmin = 0 self.scene_xmax = 1 self.scene_ymin = 0 self.scene_ymax = 1 def finalize(self): self.scene_bounding_rect = QRectF( QPointF(self.scene_xmin, self.scene_ymin), QPointF(self.scene_xmax, self.scene_ymax)) # self.scene_center = [self.scene_bounding_rect.center().x(), self.scene_bounding_rect.center().y()] self.scene_size = [ self.scene_bounding_rect.width(), self.scene_bounding_rect.height() ] self.create_grid() self.update_grid() #============================================================================== # Grid creation #============================================================================== def update_grid(self): grid_pixels = 50 grid_snaps = [1, 2, 4] # Number of pixels in the viewer view_width, view_height = self.rect().width(), self.rect().height() # Rectangle of viewport in terms of scene coordinates r = self.mapToScene(self.rect()).boundingRect() width, height = r.width(), r.height() xmin, ymin, xmax, ymax = r.x(), r.y(), r.x() + width, r.y() + height grid_size = grid_pixels * (width / view_width) exponent = np.floor(np.log10(grid_size)) digits = round(grid_size / 10**(exponent), 2) digits_snapped = min(grid_snaps, key=lambda x: abs(x - digits)) grid_size_snapped = digits_snapped * 10**(exponent) # Starting coordinates for gridlines x = round((xmin - 2 * width) / grid_size_snapped) * grid_size_snapped y = round((ymin - 2 * height) / grid_size_snapped) * grid_size_snapped for gl in self.gridlinesx: gl.setLine(x, -1e10, x, 1e10) x += grid_size_snapped for gl in self.gridlinesy: gl.setLine(-1e10, y, 1e10, y) y += grid_size_snapped self.grid_size_snapped = grid_size_snapped self.update_gridsize_label() def update_gridsize_label(self): self.gridsize_label.setText('grid size = ' + str(self.grid_size_snapped)) self.gridsize_label.move(QPoint(5, self.height() - 25)) def update_mouse_position_label(self): self.position_label.setText( 'X = %0.4f / Y = %0.4f' % (self.mouse_position[0], self.mouse_position[1])) self.position_label.move(QPoint(self.width() - 250, self.height() - 25)) def update_help_label(self): self.help_label.setText('Press "?" key for help') self.help_label.move(QPoint(self.width() - 175, 0)) def create_grid(self): self.gridlinesx = [ self.scene.addLine(-10, -10, 10, 10, self.gridpen) for n in range(300) ] self.gridlinesy = [ self.scene.addLine(-10, -10, 10, 10, self.gridpen) for n in range(300) ] self.update_grid() #============================================================================== # Mousewheel zoom, taken from http://stackoverflow.com/a/29026916 #============================================================================== def wheelEvent(self, event): # Zoom Factor zoom_percentage = 1.4 # Set Anchors self.setTransformationAnchor(QGraphicsView.NoAnchor) self.setResizeAnchor(QGraphicsView.NoAnchor) # Save the scene pos oldPos = self.mapToScene(event.pos()) # Zoom mousewheel_rotation = event.angleDelta().y( ) # Typically = 120 on most mousewheels zoom_factor = zoom_percentage**(mousewheel_rotation / 120) zoom_factor = np.clip(zoom_factor, 0.5, 2.0) # Check to make sure we're not overzoomed min_width = 0.01 min_height = 0.01 window_width = self.rect().width() window_height = self.rect().height() scene_upper_left_corner = self.mapToScene(QPoint(0, 0)) scene_bottom_right_corner = self.mapToScene( QPoint(window_width, window_height)) scene_width = (scene_bottom_right_corner - scene_upper_left_corner).x() scene_height = (scene_upper_left_corner - scene_bottom_right_corner).y() max_width = self.scene_bounding_rect.width() * 3 max_height = self.scene_bounding_rect.height() * 3 if ((scene_width > max_width) and (scene_height > max_height)) and (zoom_factor < 1): pass elif ((scene_width < min_width) and (scene_height < min_height)) and (zoom_factor > 1): pass else: self.zoom_view(zoom_factor) # Get the new position and move scene to old position newPos = self.mapToScene(event.pos()) delta = newPos - oldPos self.translate(delta.x(), delta.y()) self.update_grid() def zoom_view(self, zoom_factor): old_center = self.mapToScene(self.rect().center()) self.scale(zoom_factor, zoom_factor) self.centerOn(old_center) self.zoom_factor_total *= zoom_factor def resizeEvent(self, event): super(QGraphicsView, self).resizeEvent(event) if self.scene_bounding_rect is not None: self.reset_view() self.update_gridsize_label() self.update_mouse_position_label() self.update_help_label() def mousePressEvent(self, event): super(QGraphicsView, self).mousePressEvent(event) #============================================================================== # Zoom to rectangle, from # https://wiki.python.org/moin/PyQt/Selecting%20a%20region%20of%20a%20widget #============================================================================== if event.button() == Qt.RightButton: self._mousePressed = Qt.RightButton self._rb_origin = QPoint(event.pos()) self.rubberBand.setGeometry(QRect(self._rb_origin, QSize())) self.rubberBand.show() #============================================================================== # Mouse panning, taken from # http://stackoverflow.com/a/15043279 #============================================================================== elif event.button() == Qt.MidButton: self._mousePressed = Qt.MidButton self._mousePressedPos = event.pos() self.setCursor(QtCore.Qt.ClosedHandCursor) self._dragPos = event.pos() def mouseMoveEvent(self, event): super(QGraphicsView, self).mouseMoveEvent(event) # # Useful debug # try: # self.debug_label.setText(str(itemsBoundingRect_nogrid().width())) # except: # print('Debug statement failed') # Update the X,Y label indicating where the mouse is on the geometry mouse_position = self.mapToScene(event.pos()) self.mouse_position = [mouse_position.x(), mouse_position.y()] self.update_mouse_position_label() if not self._rb_origin.isNull( ) and self._mousePressed == Qt.RightButton: self.rubberBand.setGeometry( QRect(self._rb_origin, event.pos()).normalized()) # Middle-click-to-pan if self._mousePressed == Qt.MidButton: newPos = event.pos() diff = newPos - self._dragPos self._dragPos = newPos self.horizontalScrollBar().setValue( self.horizontalScrollBar().value() - diff.x()) self.verticalScrollBar().setValue( self.verticalScrollBar().value() - diff.y()) # event.accept() def mouseReleaseEvent(self, event): if event.button() == Qt.RightButton: self.rubberBand.hide() rb_rect = QRect(self._rb_origin, event.pos()) rb_center = rb_rect.center() rb_size = rb_rect.size() if abs(rb_size.width()) > 3 and abs(rb_size.height()) > 3: viewport_size = self.viewport().geometry().size() zoom_factor_x = abs(viewport_size.width() / rb_size.width()) zoom_factor_y = abs(viewport_size.height() / rb_size.height()) new_center = self.mapToScene(rb_center) zoom_factor = min(zoom_factor_x, zoom_factor_y) self.zoom_view(zoom_factor) self.centerOn(new_center) self.update_grid() if event.button() == Qt.MidButton: self.setCursor(Qt.ArrowCursor) self._mousePressed = None self.update_grid() def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.reset_view() if event.key() == Qt.Key_F1: self.set_alias_visibility(not self.aliases_visible) if event.key() == Qt.Key_F2: self.set_port_visibility(not self.ports_visible) if event.key() == Qt.Key_F3: self.set_subport_visibility(not self.subports_visible) if event.key() == Qt.Key_Question: help_str = """ Mouse control: Mousewheel: Zoom in and out Right-click & drag: Zoom to rectangle Middle-click & drag: Pan Keyboard shortcuts: Esc: Reset view F1: Show/hide alias names F2: Show/hide ports F3: Show/hide subports (ports in underlying references) """ msg = QMessageBox.about(self, 'PHIDL Help', help_str) msg.raise_()
class SvnLogBrowserDialog(QWidget, SvnDialogMixin, Ui_SvnLogBrowserDialog): """ Class implementing a dialog to browse the log history. """ def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(SvnLogBrowserDialog, self).__init__(parent) self.setupUi(self) SvnDialogMixin.__init__(self) self.__position = QPoint() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.filesTree.headerItem().setText(self.filesTree.columnCount(), "") self.filesTree.header().setSortIndicator(0, Qt.AscendingOrder) self.vcs = vcs self.__initData() self.fromDate.setDisplayFormat("yyyy-MM-dd") self.toDate.setDisplayFormat("yyyy-MM-dd") self.__resetUI() self.__messageRole = Qt.UserRole self.__changesRole = Qt.UserRole + 1 self.flags = { 'A': self.tr('Added'), 'D': self.tr('Deleted'), 'M': self.tr('Modified'), 'R': self.tr('Replaced'), } self.client = self.vcs.getClient() self.client.callback_cancel = \ self._clientCancelCallback self.client.callback_get_login = \ self._clientLoginCallback self.client.callback_ssl_server_trust_prompt = \ self._clientSslServerTrustPromptCallback def __initData(self): """ Private method to (re-)initialize some data. """ self.__maxDate = QDate() self.__minDate = QDate() self.__filterLogsEnabled = True self.diff = None self.__lastRev = 0 def closeEvent(self, e): """ Protected slot implementing a close event handler. @param e close event (QCloseEvent) """ self.__position = self.pos() e.accept() def show(self): """ Public slot to show the dialog. """ if not self.__position.isNull(): self.move(self.__position) self.__resetUI() super(SvnLogBrowserDialog, self).show() def __resetUI(self): """ Private method to reset the user interface. """ self.fromDate.setDate(QDate.currentDate()) self.toDate.setDate(QDate.currentDate()) self.fieldCombo.setCurrentIndex(self.fieldCombo.findText( self.tr("Message"))) self.limitSpinBox.setValue(self.vcs.getPlugin().getPreferences( "LogLimit")) self.stopCheckBox.setChecked(self.vcs.getPlugin().getPreferences( "StopLogOnCopy")) self.logTree.clear() self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) def _reset(self): """ Protected method to reset the internal state of the dialog. """ SvnDialogMixin._reset(self) self.cancelled = False self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) QApplication.processEvents() def __resizeColumnsLog(self): """ Private method to resize the log tree columns. """ self.logTree.header().resizeSections(QHeaderView.ResizeToContents) self.logTree.header().setStretchLastSection(True) def __resortLog(self): """ Private method to resort the log tree. """ self.logTree.sortItems( self.logTree.sortColumn(), self.logTree.header().sortIndicatorOrder()) def __resizeColumnsFiles(self): """ Private method to resize the changed files tree columns. """ self.filesTree.header().resizeSections(QHeaderView.ResizeToContents) self.filesTree.header().setStretchLastSection(True) def __resortFiles(self): """ Private method to resort the changed files tree. """ sortColumn = self.filesTree.sortColumn() self.filesTree.sortItems( 1, self.filesTree.header().sortIndicatorOrder()) self.filesTree.sortItems( sortColumn, self.filesTree.header().sortIndicatorOrder()) def __generateLogItem(self, author, date, message, revision, changedPaths): """ Private method to generate a log tree entry. @param author author info (string) @param date date info (integer) @param message text of the log message (string) @param revision revision info (string or pysvn.opt_revision_kind) @param changedPaths list of pysvn dictionary like objects containing info about the changed files/directories @return reference to the generated item (QTreeWidgetItem) """ if revision == "": rev = "" self.__lastRev = 0 else: rev = revision.number self.__lastRev = revision.number if date == "": dt = "" else: dt = formatTime(date) itm = QTreeWidgetItem(self.logTree) itm.setData(0, Qt.DisplayRole, rev) itm.setData(1, Qt.DisplayRole, author) itm.setData(2, Qt.DisplayRole, dt) itm.setData(3, Qt.DisplayRole, " ".join(message.splitlines())) changes = [] for changedPath in changedPaths: if changedPath["copyfrom_path"] is None: copyPath = "" else: copyPath = changedPath["copyfrom_path"] if changedPath["copyfrom_revision"] is None: copyRev = "" else: copyRev = "{0:7d}".format( changedPath["copyfrom_revision"].number) change = { "action": changedPath["action"], "path": changedPath["path"], "copyfrom_path": copyPath, "copyfrom_revision": copyRev, } changes.append(change) itm.setData(0, self.__messageRole, message) itm.setData(0, self.__changesRole, changes) itm.setTextAlignment(0, Qt.AlignRight) itm.setTextAlignment(1, Qt.AlignLeft) itm.setTextAlignment(2, Qt.AlignLeft) itm.setTextAlignment(3, Qt.AlignLeft) itm.setTextAlignment(4, Qt.AlignLeft) return itm def __generateFileItem(self, action, path, copyFrom, copyRev): """ Private method to generate a changed files tree entry. @param action indicator for the change action ("A", "D" or "M") @param path path of the file in the repository (string) @param copyFrom path the file was copied from (None, string) @param copyRev revision the file was copied from (None, string) @return reference to the generated item (QTreeWidgetItem) """ itm = QTreeWidgetItem( self.filesTree, [self.flags[action], path, copyFrom, copyRev] ) itm.setTextAlignment(3, Qt.AlignRight) return itm def __getLogEntries(self, startRev=None): """ Private method to retrieve log entries from the repository. @param startRev revision number to start from (integer, string) """ fetchLimit = 10 self._reset() QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() limit = self.limitSpinBox.value() if startRev is None: start = pysvn.Revision(pysvn.opt_revision_kind.head) else: try: start = pysvn.Revision(pysvn.opt_revision_kind.number, int(startRev)) except TypeError: start = pysvn.Revision(pysvn.opt_revision_kind.head) locker = QMutexLocker(self.vcs.vcsExecutionMutex) cwd = os.getcwd() os.chdir(self.dname) try: nextRev = 0 fetched = 0 logs = [] while fetched < limit: flimit = min(fetchLimit, limit - fetched) if fetched == 0: revstart = start else: revstart = pysvn.Revision( pysvn.opt_revision_kind.number, nextRev) allLogs = self.client.log( self.fname, revision_start=revstart, discover_changed_paths=True, limit=flimit + 1, strict_node_history=self.stopCheckBox.isChecked()) if len(allLogs) <= flimit or self._clientCancelCallback(): logs.extend(allLogs) break else: logs.extend(allLogs[:-1]) nextRev = allLogs[-1]["revision"].number fetched += fetchLimit locker.unlock() for log in logs: author = log["author"] message = log["message"] if sys.version_info[0] == 2: author = author.decode('utf-8') message = message.decode('utf-8') self.__generateLogItem( author, log["date"], message, log["revision"], log['changed_paths']) dt = dateFromTime_t(log["date"]) if not self.__maxDate.isValid() and \ not self.__minDate.isValid(): self.__maxDate = dt self.__minDate = dt else: if self.__maxDate < dt: self.__maxDate = dt if self.__minDate > dt: self.__minDate = dt if len(logs) < limit and not self.cancelled: self.nextButton.setEnabled(False) self.limitSpinBox.setEnabled(False) self.__filterLogsEnabled = False self.fromDate.setMinimumDate(self.__minDate) self.fromDate.setMaximumDate(self.__maxDate) self.fromDate.setDate(self.__minDate) self.toDate.setMinimumDate(self.__minDate) self.toDate.setMaximumDate(self.__maxDate) self.toDate.setDate(self.__maxDate) self.__filterLogsEnabled = True self.__resizeColumnsLog() self.__resortLog() self.__filterLogs() except pysvn.ClientError as e: locker.unlock() self.__showError(e.args[0]) os.chdir(cwd) self.__finish() def start(self, fn, isFile=False): """ Public slot to start the svn log command. @param fn filename to show the log for (string) @keyparam isFile flag indicating log for a file is to be shown (boolean) """ self.sbsCheckBox.setEnabled(isFile) self.sbsCheckBox.setVisible(isFile) self.__initData() self.filename = fn self.dname, self.fname = self.vcs.splitPath(fn) self.activateWindow() self.raise_() self.logTree.clear() self.__getLogEntries() self.logTree.setCurrentItem(self.logTree.topLevelItem(0)) def __finish(self): """ Private slot called when the user pressed the button. """ QApplication.restoreOverrideCursor() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self._cancel() def __diffRevisions(self, rev1, rev2, peg_rev): """ Private method to do a diff of two revisions. @param rev1 first revision number (integer) @param rev2 second revision number (integer) @param peg_rev revision number to use as a reference (integer) """ if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked(): self.vcs.svnSbsDiff(self.filename, revisions=(str(rev1), str(rev2))) else: if self.diff is None: from .SvnDiffDialog import SvnDiffDialog self.diff = SvnDiffDialog(self.vcs) self.diff.show() self.diff.raise_() QApplication.processEvents() self.diff.start(self.filename, [rev1, rev2], pegRev=peg_rev) def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.cancelled = True self.__finish() @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_logTree_currentItemChanged(self, current, previous): """ Private slot called, when the current item of the log tree changes. @param current reference to the new current item (QTreeWidgetItem) @param previous reference to the old current item (QTreeWidgetItem) """ if current is not None: self.messageEdit.setPlainText(current.data(0, self.__messageRole)) self.filesTree.clear() changes = current.data(0, self.__changesRole) if len(changes) > 0: for change in changes: self.__generateFileItem( change["action"], change["path"], change["copyfrom_path"], change["copyfrom_revision"]) self.__resizeColumnsFiles() self.__resortFiles() self.diffPreviousButton.setEnabled( current != self.logTree.topLevelItem( self.logTree.topLevelItemCount() - 1)) @pyqtSlot() def on_logTree_itemSelectionChanged(self): """ Private slot called, when the selection has changed. """ self.diffRevisionsButton.setEnabled( len(self.logTree.selectedItems()) == 2) @pyqtSlot() def on_nextButton_clicked(self): """ Private slot to handle the Next button. """ if self.__lastRev > 1: self.__getLogEntries(self.__lastRev - 1) @pyqtSlot() def on_diffPreviousButton_clicked(self): """ Private slot to handle the Diff to Previous button. """ itm = self.logTree.topLevelItem(0) if itm is None: self.diffPreviousButton.setEnabled(False) return peg_rev = int(itm.text(0)) itm = self.logTree.currentItem() if itm is None: self.diffPreviousButton.setEnabled(False) return rev2 = int(itm.text(0)) itm = self.logTree.topLevelItem( self.logTree.indexOfTopLevelItem(itm) + 1) if itm is None: self.diffPreviousButton.setEnabled(False) return rev1 = int(itm.text(0)) self.__diffRevisions(rev1, rev2, peg_rev) @pyqtSlot() def on_diffRevisionsButton_clicked(self): """ Private slot to handle the Compare Revisions button. """ items = self.logTree.selectedItems() if len(items) != 2: self.diffRevisionsButton.setEnabled(False) return rev2 = int(items[0].text(0)) rev1 = int(items[1].text(0)) itm = self.logTree.topLevelItem(0) if itm is None: self.diffPreviousButton.setEnabled(False) return peg_rev = int(itm.text(0)) self.__diffRevisions(min(rev1, rev2), max(rev1, rev2), peg_rev) def __showError(self, msg): """ Private slot to show an error message. @param msg error message to show (string) """ E5MessageBox.critical( self, self.tr("Subversion Error"), msg) @pyqtSlot(QDate) def on_fromDate_dateChanged(self, date): """ Private slot called, when the from date changes. @param date new date (QDate) """ self.__filterLogs() @pyqtSlot(QDate) def on_toDate_dateChanged(self, date): """ Private slot called, when the from date changes. @param date new date (QDate) """ self.__filterLogs() @pyqtSlot(str) def on_fieldCombo_activated(self, txt): """ Private slot called, when a new filter field is selected. @param txt text of the selected field (string) """ self.__filterLogs() @pyqtSlot(str) def on_rxEdit_textChanged(self, txt): """ Private slot called, when a filter expression is entered. @param txt filter expression (string) """ self.__filterLogs() def __filterLogs(self): """ Private method to filter the log entries. """ if self.__filterLogsEnabled: from_ = self.fromDate.date().toString("yyyy-MM-dd") to_ = self.toDate.date().addDays(1).toString("yyyy-MM-dd") txt = self.fieldCombo.currentText() if txt == self.tr("Author"): fieldIndex = 1 searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive) elif txt == self.tr("Revision"): fieldIndex = 0 txt = self.rxEdit.text() if txt.startswith("^"): searchRx = QRegExp( "^\s*{0}".format(txt[1:]), Qt.CaseInsensitive) else: searchRx = QRegExp(txt, Qt.CaseInsensitive) else: fieldIndex = 3 searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive) currentItem = self.logTree.currentItem() for topIndex in range(self.logTree.topLevelItemCount()): topItem = self.logTree.topLevelItem(topIndex) if topItem.text(2) <= to_ and topItem.text(2) >= from_ and \ searchRx.indexIn(topItem.text(fieldIndex)) > -1: topItem.setHidden(False) if topItem is currentItem: self.on_logTree_currentItemChanged(topItem, None) else: topItem.setHidden(True) if topItem is currentItem: self.messageEdit.clear() self.filesTree.clear() @pyqtSlot(bool) def on_stopCheckBox_clicked(self, checked): """ Private slot called, when the stop on copy/move checkbox is clicked. @param checked flag indicating the check box state (boolean) """ self.vcs.getPlugin().setPreferences("StopLogOnCopy", int(self.stopCheckBox.isChecked())) self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True)
class SvnLogBrowserDialog(QWidget, SvnDialogMixin, Ui_SvnLogBrowserDialog): """ Class implementing a dialog to browse the log history. """ def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(SvnLogBrowserDialog, self).__init__(parent) self.setupUi(self) SvnDialogMixin.__init__(self) self.__position = QPoint() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.filesTree.headerItem().setText(self.filesTree.columnCount(), "") self.filesTree.header().setSortIndicator(0, Qt.AscendingOrder) self.vcs = vcs self.__initData() self.fromDate.setDisplayFormat("yyyy-MM-dd") self.toDate.setDisplayFormat("yyyy-MM-dd") self.__resetUI() self.__messageRole = Qt.UserRole self.__changesRole = Qt.UserRole + 1 self.flags = { 'A': self.tr('Added'), 'D': self.tr('Deleted'), 'M': self.tr('Modified'), 'R': self.tr('Replaced'), } self.client = self.vcs.getClient() self.client.callback_cancel = \ self._clientCancelCallback self.client.callback_get_login = \ self._clientLoginCallback self.client.callback_ssl_server_trust_prompt = \ self._clientSslServerTrustPromptCallback def __initData(self): """ Private method to (re-)initialize some data. """ self.__maxDate = QDate() self.__minDate = QDate() self.__filterLogsEnabled = True self.diff = None self.__lastRev = 0 def closeEvent(self, e): """ Protected slot implementing a close event handler. @param e close event (QCloseEvent) """ self.__position = self.pos() e.accept() def show(self): """ Public slot to show the dialog. """ if not self.__position.isNull(): self.move(self.__position) self.__resetUI() super(SvnLogBrowserDialog, self).show() def __resetUI(self): """ Private method to reset the user interface. """ self.fromDate.setDate(QDate.currentDate()) self.toDate.setDate(QDate.currentDate()) self.fieldCombo.setCurrentIndex( self.fieldCombo.findText(self.tr("Message"))) self.limitSpinBox.setValue( self.vcs.getPlugin().getPreferences("LogLimit")) self.stopCheckBox.setChecked( self.vcs.getPlugin().getPreferences("StopLogOnCopy")) self.logTree.clear() self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) def _reset(self): """ Protected method to reset the internal state of the dialog. """ SvnDialogMixin._reset(self) self.cancelled = False self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) QApplication.processEvents() def __resizeColumnsLog(self): """ Private method to resize the log tree columns. """ self.logTree.header().resizeSections(QHeaderView.ResizeToContents) self.logTree.header().setStretchLastSection(True) def __resortLog(self): """ Private method to resort the log tree. """ self.logTree.sortItems(self.logTree.sortColumn(), self.logTree.header().sortIndicatorOrder()) def __resizeColumnsFiles(self): """ Private method to resize the changed files tree columns. """ self.filesTree.header().resizeSections(QHeaderView.ResizeToContents) self.filesTree.header().setStretchLastSection(True) def __resortFiles(self): """ Private method to resort the changed files tree. """ sortColumn = self.filesTree.sortColumn() self.filesTree.sortItems(1, self.filesTree.header().sortIndicatorOrder()) self.filesTree.sortItems(sortColumn, self.filesTree.header().sortIndicatorOrder()) def __generateLogItem(self, author, date, message, revision, changedPaths): """ Private method to generate a log tree entry. @param author author info (string) @param date date info (integer) @param message text of the log message (string) @param revision revision info (string or pysvn.opt_revision_kind) @param changedPaths list of pysvn dictionary like objects containing info about the changed files/directories @return reference to the generated item (QTreeWidgetItem) """ if revision == "": rev = "" self.__lastRev = 0 else: rev = revision.number self.__lastRev = revision.number if date == "": dt = "" else: dt = formatTime(date) itm = QTreeWidgetItem(self.logTree) itm.setData(0, Qt.DisplayRole, rev) itm.setData(1, Qt.DisplayRole, author) itm.setData(2, Qt.DisplayRole, dt) itm.setData(3, Qt.DisplayRole, " ".join(message.splitlines())) changes = [] for changedPath in changedPaths: if changedPath["copyfrom_path"] is None: copyPath = "" else: copyPath = changedPath["copyfrom_path"] if changedPath["copyfrom_revision"] is None: copyRev = "" else: copyRev = "{0:7d}".format( changedPath["copyfrom_revision"].number) change = { "action": changedPath["action"], "path": changedPath["path"], "copyfrom_path": copyPath, "copyfrom_revision": copyRev, } changes.append(change) itm.setData(0, self.__messageRole, message) itm.setData(0, self.__changesRole, changes) itm.setTextAlignment(0, Qt.AlignRight) itm.setTextAlignment(1, Qt.AlignLeft) itm.setTextAlignment(2, Qt.AlignLeft) itm.setTextAlignment(3, Qt.AlignLeft) itm.setTextAlignment(4, Qt.AlignLeft) return itm def __generateFileItem(self, action, path, copyFrom, copyRev): """ Private method to generate a changed files tree entry. @param action indicator for the change action ("A", "D" or "M") @param path path of the file in the repository (string) @param copyFrom path the file was copied from (None, string) @param copyRev revision the file was copied from (None, string) @return reference to the generated item (QTreeWidgetItem) """ itm = QTreeWidgetItem(self.filesTree, [self.flags[action], path, copyFrom, copyRev]) itm.setTextAlignment(3, Qt.AlignRight) return itm def __getLogEntries(self, startRev=None): """ Private method to retrieve log entries from the repository. @param startRev revision number to start from (integer, string) """ fetchLimit = 10 self._reset() QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() limit = self.limitSpinBox.value() if startRev is None: start = pysvn.Revision(pysvn.opt_revision_kind.head) else: try: start = pysvn.Revision(pysvn.opt_revision_kind.number, int(startRev)) except TypeError: start = pysvn.Revision(pysvn.opt_revision_kind.head) locker = QMutexLocker(self.vcs.vcsExecutionMutex) cwd = os.getcwd() os.chdir(self.dname) try: nextRev = 0 fetched = 0 logs = [] while fetched < limit: flimit = min(fetchLimit, limit - fetched) if fetched == 0: revstart = start else: revstart = pysvn.Revision(pysvn.opt_revision_kind.number, nextRev) allLogs = self.client.log( self.fname, revision_start=revstart, discover_changed_paths=True, limit=flimit + 1, strict_node_history=self.stopCheckBox.isChecked()) if len(allLogs) <= flimit or self._clientCancelCallback(): logs.extend(allLogs) break else: logs.extend(allLogs[:-1]) nextRev = allLogs[-1]["revision"].number fetched += fetchLimit locker.unlock() for log in logs: author = log["author"] message = log["message"] if sys.version_info[0] == 2: author = author.decode('utf-8') message = message.decode('utf-8') self.__generateLogItem(author, log["date"], message, log["revision"], log['changed_paths']) dt = dateFromTime_t(log["date"]) if not self.__maxDate.isValid() and \ not self.__minDate.isValid(): self.__maxDate = dt self.__minDate = dt else: if self.__maxDate < dt: self.__maxDate = dt if self.__minDate > dt: self.__minDate = dt if len(logs) < limit and not self.cancelled: self.nextButton.setEnabled(False) self.limitSpinBox.setEnabled(False) self.__filterLogsEnabled = False self.fromDate.setMinimumDate(self.__minDate) self.fromDate.setMaximumDate(self.__maxDate) self.fromDate.setDate(self.__minDate) self.toDate.setMinimumDate(self.__minDate) self.toDate.setMaximumDate(self.__maxDate) self.toDate.setDate(self.__maxDate) self.__filterLogsEnabled = True self.__resizeColumnsLog() self.__resortLog() self.__filterLogs() except pysvn.ClientError as e: locker.unlock() self.__showError(e.args[0]) os.chdir(cwd) self.__finish() def start(self, fn, isFile=False): """ Public slot to start the svn log command. @param fn filename to show the log for (string) @keyparam isFile flag indicating log for a file is to be shown (boolean) """ self.sbsCheckBox.setEnabled(isFile) self.sbsCheckBox.setVisible(isFile) self.__initData() self.filename = fn self.dname, self.fname = self.vcs.splitPath(fn) self.activateWindow() self.raise_() self.logTree.clear() self.__getLogEntries() self.logTree.setCurrentItem(self.logTree.topLevelItem(0)) def __finish(self): """ Private slot called when the user pressed the button. """ QApplication.restoreOverrideCursor() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self._cancel() def __diffRevisions(self, rev1, rev2, peg_rev): """ Private method to do a diff of two revisions. @param rev1 first revision number (integer) @param rev2 second revision number (integer) @param peg_rev revision number to use as a reference (integer) """ if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked(): self.vcs.svnSbsDiff(self.filename, revisions=(str(rev1), str(rev2))) else: if self.diff is None: from .SvnDiffDialog import SvnDiffDialog self.diff = SvnDiffDialog(self.vcs) self.diff.show() self.diff.raise_() QApplication.processEvents() self.diff.start(self.filename, [rev1, rev2], pegRev=peg_rev) def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.cancelled = True self.__finish() @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_logTree_currentItemChanged(self, current, previous): """ Private slot called, when the current item of the log tree changes. @param current reference to the new current item (QTreeWidgetItem) @param previous reference to the old current item (QTreeWidgetItem) """ if current is not None: self.messageEdit.setPlainText(current.data(0, self.__messageRole)) self.filesTree.clear() changes = current.data(0, self.__changesRole) if len(changes) > 0: for change in changes: self.__generateFileItem(change["action"], change["path"], change["copyfrom_path"], change["copyfrom_revision"]) self.__resizeColumnsFiles() self.__resortFiles() self.diffPreviousButton.setEnabled( current != self.logTree.topLevelItem( self.logTree.topLevelItemCount() - 1)) @pyqtSlot() def on_logTree_itemSelectionChanged(self): """ Private slot called, when the selection has changed. """ self.diffRevisionsButton.setEnabled( len(self.logTree.selectedItems()) == 2) @pyqtSlot() def on_nextButton_clicked(self): """ Private slot to handle the Next button. """ if self.__lastRev > 1: self.__getLogEntries(self.__lastRev - 1) @pyqtSlot() def on_diffPreviousButton_clicked(self): """ Private slot to handle the Diff to Previous button. """ itm = self.logTree.topLevelItem(0) if itm is None: self.diffPreviousButton.setEnabled(False) return peg_rev = int(itm.text(0)) itm = self.logTree.currentItem() if itm is None: self.diffPreviousButton.setEnabled(False) return rev2 = int(itm.text(0)) itm = self.logTree.topLevelItem( self.logTree.indexOfTopLevelItem(itm) + 1) if itm is None: self.diffPreviousButton.setEnabled(False) return rev1 = int(itm.text(0)) self.__diffRevisions(rev1, rev2, peg_rev) @pyqtSlot() def on_diffRevisionsButton_clicked(self): """ Private slot to handle the Compare Revisions button. """ items = self.logTree.selectedItems() if len(items) != 2: self.diffRevisionsButton.setEnabled(False) return rev2 = int(items[0].text(0)) rev1 = int(items[1].text(0)) itm = self.logTree.topLevelItem(0) if itm is None: self.diffPreviousButton.setEnabled(False) return peg_rev = int(itm.text(0)) self.__diffRevisions(min(rev1, rev2), max(rev1, rev2), peg_rev) def __showError(self, msg): """ Private slot to show an error message. @param msg error message to show (string) """ E5MessageBox.critical(self, self.tr("Subversion Error"), msg) @pyqtSlot(QDate) def on_fromDate_dateChanged(self, date): """ Private slot called, when the from date changes. @param date new date (QDate) """ self.__filterLogs() @pyqtSlot(QDate) def on_toDate_dateChanged(self, date): """ Private slot called, when the from date changes. @param date new date (QDate) """ self.__filterLogs() @pyqtSlot(str) def on_fieldCombo_activated(self, txt): """ Private slot called, when a new filter field is selected. @param txt text of the selected field (string) """ self.__filterLogs() @pyqtSlot(str) def on_rxEdit_textChanged(self, txt): """ Private slot called, when a filter expression is entered. @param txt filter expression (string) """ self.__filterLogs() def __filterLogs(self): """ Private method to filter the log entries. """ if self.__filterLogsEnabled: from_ = self.fromDate.date().toString("yyyy-MM-dd") to_ = self.toDate.date().addDays(1).toString("yyyy-MM-dd") txt = self.fieldCombo.currentText() if txt == self.tr("Author"): fieldIndex = 1 searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive) elif txt == self.tr("Revision"): fieldIndex = 0 txt = self.rxEdit.text() if txt.startswith("^"): searchRx = QRegExp("^\s*{0}".format(txt[1:]), Qt.CaseInsensitive) else: searchRx = QRegExp(txt, Qt.CaseInsensitive) else: fieldIndex = 3 searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive) currentItem = self.logTree.currentItem() for topIndex in range(self.logTree.topLevelItemCount()): topItem = self.logTree.topLevelItem(topIndex) if topItem.text(2) <= to_ and topItem.text(2) >= from_ and \ searchRx.indexIn(topItem.text(fieldIndex)) > -1: topItem.setHidden(False) if topItem is currentItem: self.on_logTree_currentItemChanged(topItem, None) else: topItem.setHidden(True) if topItem is currentItem: self.messageEdit.clear() self.filesTree.clear() @pyqtSlot(bool) def on_stopCheckBox_clicked(self, checked): """ Private slot called, when the stop on copy/move checkbox is clicked. @param checked flag indicating the check box state (boolean) """ self.vcs.getPlugin().setPreferences("StopLogOnCopy", int(self.stopCheckBox.isChecked())) self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True)
class HgConflictsListDialog(QWidget, Ui_HgConflictsListDialog): """ Class implementing a dialog to show a list of files which had or still have conflicts. """ StatusRole = Qt.UserRole + 1 FilenameRole = Qt.UserRole + 2 def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(HgConflictsListDialog, self).__init__(parent) self.setupUi(self) self.__position = QPoint() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.conflictsList.headerItem().setText( self.conflictsList.columnCount(), "") self.conflictsList.header().setSortIndicator(0, Qt.AscendingOrder) self.refreshButton = self.buttonBox.addButton( self.tr("&Refresh"), QDialogButtonBox.ActionRole) self.refreshButton.setToolTip( self.tr("Press to refresh the list of conflicts")) self.refreshButton.setEnabled(False) self.vcs = vcs self.project = e5App().getObject("Project") self.__hgClient = vcs.getClient() if self.__hgClient: self.process = None else: self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) def closeEvent(self, e): """ Protected slot implementing a close event handler. @param e close event (QCloseEvent) """ if self.__hgClient: if self.__hgClient.isExecuting(): self.__hgClient.cancel() else: if self.process is not None and \ self.process.state() != QProcess.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.__position = self.pos() e.accept() def show(self): """ Public slot to show the dialog. """ if not self.__position.isNull(): self.move(self.__position) super(HgConflictsListDialog, self).show() def start(self, path): """ Public slot to start the tags command. @param path name of directory to list conflicts for (string) """ self.errorGroup.hide() QApplication.processEvents() self.intercept = False dname, fname = self.vcs.splitPath(path) # find the root of the repo self.__repodir = dname while not os.path.isdir( os.path.join(self.__repodir, self.vcs.adminDir)): self.__repodir = os.path.dirname(self.__repodir) if os.path.splitdrive(self.__repodir)[1] == os.sep: return self.activateWindow() self.raise_() self.conflictsList.clear() self.__started = True self.__getEntries() def __getEntries(self): """ Private method to get the conflict entries. """ args = self.vcs.initCommand("resolve") args.append('--list') if self.__hgClient: self.inputGroup.setEnabled(False) self.inputGroup.hide() out, err = self.__hgClient.runcommand(args) if err: self.__showError(err) if out: for line in out.splitlines(): self.__processOutputLine(line) if self.__hgClient.wasCanceled(): break self.__finish() else: self.process.kill() self.process.setWorkingDirectory(self.__repodir) self.process.start('hg', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.' ).format('hg')) else: self.inputGroup.setEnabled(True) self.inputGroup.show() def __finish(self): """ Private slot called when the process finished or the user pressed the button. """ if self.process is not None and \ self.process.state() != QProcess.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) QApplication.restoreOverrideCursor() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.inputGroup.setEnabled(False) self.inputGroup.hide() self.refreshButton.setEnabled(True) self.__resizeColumns() self.__resort() self.on_conflictsList_itemSelectionChanged() @pyqtSlot(QAbstractButton) def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): if self.__hgClient: self.__hgClient.cancel() else: self.__finish() elif button == self.refreshButton: self.on_refreshButton_clicked() def __procFinished(self, exitCode, exitStatus): """ Private slot connected to the finished signal. @param exitCode exit code of the process (integer) @param exitStatus exit status of the process (QProcess.ExitStatus) """ self.__finish() def __resort(self): """ Private method to resort the tree. """ self.conflictsList.sortItems( self.conflictsList.sortColumn(), self.conflictsList.header().sortIndicatorOrder()) def __resizeColumns(self): """ Private method to resize the list columns. """ self.conflictsList.header().resizeSections( QHeaderView.ResizeToContents) self.conflictsList.header().setStretchLastSection(True) def __generateItem(self, status, name): """ Private method to generate a tag item in the tag list. @param status status of the file (string) @param name name of the file (string) """ itm = QTreeWidgetItem(self.conflictsList) if status == "U": itm.setText(0, self.tr("Unresolved")) elif status == "R": itm.setText(0, self.tr("Resolved")) else: itm.setText(0, self.tr("Unknown Status")) itm.setText(1, name) itm.setData(0, self.StatusRole, status) itm.setData(0, self.FilenameRole, self.project.getAbsolutePath(name)) def __readStdout(self): """ Private slot to handle the readyReadStdout signal. It reads the output of the process, formats it and inserts it into the contents pane. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): s = str(self.process.readLine(), self.vcs.getEncoding(), 'replace').strip() self.__processOutputLine(s) def __processOutputLine(self, line): """ Private method to process the lines of output. @param line output line to be processed (string) """ status, filename = line.strip().split(None, 1) self.__generateItem(status, filename) @pyqtSlot() def on_refreshButton_clicked(self): """ Private slot to refresh the log. """ self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.inputGroup.setEnabled(True) self.inputGroup.show() self.refreshButton.setEnabled(False) self.start(self.__repodir) def __readStderr(self): """ Private slot to handle the readyReadStderr signal. It reads the error output of the process and inserts it into the error pane. """ if self.process is not None: s = str(self.process.readAllStandardError(), self.vcs.getEncoding(), 'replace') self.__showError(s) def __showError(self, out): """ Private slot to show some error. @param out error to be shown (string) """ self.errorGroup.show() self.errors.insertPlainText(out) self.errors.ensureCursorVisible() def on_passwordCheckBox_toggled(self, isOn): """ Private slot to handle the password checkbox toggled. @param isOn flag indicating the status of the check box (boolean) """ if isOn: self.input.setEchoMode(QLineEdit.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.process.write(input) self.passwordCheckBox.setChecked(False) self.input.clear() def on_input_returnPressed(self): """ Private slot to handle the press of the return key in the input field. """ self.intercept = True self.on_sendButton_clicked() def keyPressEvent(self, evt): """ Protected slot to handle a key press event. @param evt the key press event (QKeyEvent) """ if self.intercept: self.intercept = False evt.accept() return super(HgConflictsListDialog, self).keyPressEvent(evt) @pyqtSlot(QTreeWidgetItem, int) def on_conflictsList_itemDoubleClicked(self, item, column): """ Private slot to open the double clicked entry. @param item reference to the double clicked item (QTreeWidgetItem) @param column column that was double clicked (integer) """ self.on_editButton_clicked() @pyqtSlot() def on_conflictsList_itemSelectionChanged(self): """ Private slot to handle a change of selected conflict entries. """ selectedCount = len(self.conflictsList.selectedItems()) unresolved = resolved = 0 for itm in self.conflictsList.selectedItems(): status = itm.data(0, self.StatusRole) if status == "U": unresolved += 1 elif status == "R": resolved += 1 self.resolvedButton.setEnabled(unresolved > 0) self.unresolvedButton.setEnabled(resolved > 0) self.reMergeButton.setEnabled(unresolved > 0) self.editButton.setEnabled( selectedCount == 1 and Utilities.MimeTypes.isTextFile( self.conflictsList.selectedItems()[0].data( 0, self.FilenameRole))) @pyqtSlot() def on_resolvedButton_clicked(self): """ Private slot to mark the selected entries as resolved. """ names = [ itm.data(0, self.FilenameRole) for itm in self.conflictsList.selectedItems() if itm.data(0, self.StatusRole) == "U" ] if names: self.vcs.hgResolved(names) self.on_refreshButton_clicked() @pyqtSlot() def on_unresolvedButton_clicked(self): """ Private slot to mark the selected entries as unresolved. """ names = [ itm.data(0, self.FilenameRole) for itm in self.conflictsList.selectedItems() if itm.data(0, self.StatusRole) == "R" ] if names: self.vcs.hgResolved(names, unresolve=True) self.on_refreshButton_clicked() @pyqtSlot() def on_reMergeButton_clicked(self): """ Private slot to re-merge the selected entries. """ names = [ itm.data(0, self.FilenameRole) for itm in self.conflictsList.selectedItems() if itm.data(0, self.StatusRole) == "U" ] if names: self.vcs.hgReMerge(names) @pyqtSlot() def on_editButton_clicked(self): """ Private slot to open the selected file in an editor. """ itm = self.conflictsList.selectedItems()[0] filename = itm.data(0, self.FilenameRole) if Utilities.MimeTypes.isTextFile(filename): e5App().getObject("ViewManager").getEditor(filename)
class SlideViewer(QWidget): eventSignal = pyqtSignal(PyQt5.QtCore.QEvent) def __init__(self, parent: QWidget = None, viewer_top_else_left=True): super().__init__(parent) self.init_view() self.init_labels(word_wrap=viewer_top_else_left) self.init_layout(viewer_top_else_left) def init_view(self): self.scene = MyGraphicsScene() self.view = QGraphicsView() self.view.setScene(self.scene) self.view.setTransformationAnchor(QGraphicsView.NoAnchor) self.view.viewport().installEventFilter(self) self.rubber_band = QRubberBand(QRubberBand.Rectangle, self) self.mouse_press_view = QPoint() self.view.horizontalScrollBar().sliderMoved.connect( self.on_view_changed) self.view.verticalScrollBar().sliderMoved.connect(self.on_view_changed) self.scale_initializer_deffered_function = None self.slide_view_params = None self.slide_helper = None def init_labels(self, word_wrap): # word_wrap = True self.level_downsample_label = QLabel() self.level_downsample_label.setWordWrap(word_wrap) self.level_size_label = QLabel() self.level_size_label.setWordWrap(word_wrap) self.selected_rect_label = QLabel() self.selected_rect_label.setWordWrap(word_wrap) self.mouse_pos_scene_label = QLabel() self.mouse_pos_scene_label.setWordWrap(word_wrap) self.view_rect_scene_label = QLabel() self.view_rect_scene_label.setWordWrap(word_wrap) self.labels_layout = QVBoxLayout() self.labels_layout.setAlignment(Qt.AlignTop) self.labels_layout.addWidget(self.level_downsample_label) self.labels_layout.addWidget(self.level_size_label) self.labels_layout.addWidget(self.mouse_pos_scene_label) # self.labels_layout.addWidget(self.selected_rect_label) self.labels_layout.addWidget(self.view_rect_scene_label) def init_layout(self, viewer_top_else_left=True): main_layout = QVBoxLayout( self) if viewer_top_else_left else QHBoxLayout(self) main_layout.addWidget(self.view, ) main_layout.addLayout(self.labels_layout) # main_layout.setContentsMargins(0, 0, 0, 0) self.setLayout(main_layout) """ If you want to start view frome some point at some level, specify <level> and <level_rect> params. level_rect : rect in dimensions of slide at level=level. If None - fits the whole size of slide """ def load(self, slide_view_params: SlideViewParams, preffered_rects_count=2000, zoom_step=1.15): self.zoom_step = zoom_step self.slide_view_params = slide_view_params self.slide_helper = SlideHelper(slide_view_params.slide_path) self.slide_graphics = SlideGraphicsGroup(slide_view_params, preffered_rects_count) self.scene.clear() self.scene.addItem(self.slide_graphics) if self.slide_view_params.level == -1 or self.slide_view_params.level is None: self.slide_view_params.level = self.slide_helper.get_max_level() self.slide_graphics.update_visible_level(self.slide_view_params.level) self.scene.setSceneRect( self.slide_helper.get_rect_for_level(self.slide_view_params.level)) def scale_initializer_deffered_function(): self.view.resetTransform() # print("size when loading: ", self.view.viewport().size()) if self.slide_view_params.level_rect: # self.view.fitInView(QRectF(*self.slide_view_params.level_rect), Qt.KeepAspectRatioByExpanding) self.view.fitInView(QRectF(*self.slide_view_params.level_rect), Qt.KeepAspectRatio) # print("after fit: ", self.get_current_view_scene_rect()) else: start_margins = QMarginsF(200, 200, 200, 200) start_image_rect_ = self.slide_helper.get_rect_for_level( self.slide_view_params.level) self.view.fitInView(start_image_rect_ + start_margins, Qt.KeepAspectRatio) self.scale_initializer_deffered_function = scale_initializer_deffered_function def eventFilter(self, qobj: 'QObject', event: QEvent): self.eventSignal.emit(event) event_processed = False # print("size when event: ", event, event.type(), self.view.viewport().size()) if isinstance(event, QShowEvent): """ we need it deffered because fitInView logic depends on current viewport size. Expecting at this point widget is finally resized before being shown at first """ if self.scale_initializer_deffered_function: # TODO labels start to occupy some space after view was already fitted, and labels will reduce size of viewport # self.update_labels() self.scale_initializer_deffered_function() self.on_view_changed() self.scale_initializer_deffered_function = None elif isinstance(event, QWheelEvent): event_processed = self.process_viewport_wheel_event(event) # we handle wheel event to prevent GraphicsView interpret it as scrolling elif isinstance(event, QMouseEvent): event_processed = self.process_mouse_event(event) return event_processed def process_viewport_wheel_event(self, event: QWheelEvent): # print("size when wheeling: ", self.view.viewport().size()) zoom_in = self.zoom_step zoom_out = 1 / zoom_in zoom_ = zoom_in if event.angleDelta().y() > 0 else zoom_out self.update_scale(event.pos(), zoom_) event.accept() self.on_view_changed() return True def process_mouse_event(self, event: QMouseEvent): if self.slide_helper is None: return False if event.button() == Qt.MiddleButton: if event.type() == QEvent.MouseButtonPress: self.slide_graphics.update_grid_visibility( not self.slide_graphics.slide_view_params.grid_visible) # items=self.scene.items() # QMessageBox.information(None, "Items", str(items)) return True # self.update_scale(QPoint(), 1.15) elif event.button() == Qt.LeftButton: if event.type() == QEvent.MouseButtonPress: self.mouse_press_view = QPoint(event.pos()) self.rubber_band.setGeometry( QRect(self.mouse_press_view, QSize())) self.rubber_band.show() return True elif event.type() == QEvent.MouseButtonRelease: self.rubber_band.hide() self.remember_selected_rect_params() self.slide_graphics.update_selected_rect_0_level( self.slide_view_params.selected_rect_0_level) self.update_labels() self.scene.invalidate() return True elif event.type() == QEvent.MouseMove: self.mouse_pos_scene_label.setText( "mouse_scene: " + point_to_str(self.view.mapToScene(event.pos()))) if not self.mouse_press_view.isNull(): self.rubber_band.setGeometry( QRect(self.mouse_press_view, event.pos()).normalized()) return True return False def remember_selected_rect_params(self): pos_scene = self.view.mapToScene(self.rubber_band.pos()) rect_scene = self.view.mapToScene( self.rubber_band.rect()).boundingRect() downsample = self.slide_helper.get_downsample_for_level( self.slide_view_params.level) selected_qrectf_0_level = QRectF(pos_scene * downsample, rect_scene.size() * downsample) self.slide_view_params.selected_rect_0_level = selected_qrectf_0_level.getRect( ) def update_scale(self, mouse_pos: QPoint, zoom): old_mouse_pos_scene = self.view.mapToScene(mouse_pos) old_view_scene_rect = self.view.mapToScene( self.view.viewport().rect()).boundingRect() old_level = self.get_best_level_for_scale( self.get_current_view_scale()) old_level_downsample = self.slide_helper.get_downsample_for_level( old_level) new_level = self.get_best_level_for_scale( self.get_current_view_scale() * zoom) new_level_downsample = self.slide_helper.get_downsample_for_level( new_level) level_scale_delta = 1 / (new_level_downsample / old_level_downsample) r = old_view_scene_rect.topLeft() m = old_mouse_pos_scene new_view_scene_rect_top_left = (m - (m - r) / zoom) * level_scale_delta new_view_scene_rect = QRectF( new_view_scene_rect_top_left, old_view_scene_rect.size() * level_scale_delta / zoom) new_scale = self.get_current_view_scale( ) * zoom * new_level_downsample / old_level_downsample transform = QTransform().scale(new_scale, new_scale).translate( -new_view_scene_rect.x(), -new_view_scene_rect.y()) new_rect = self.slide_helper.get_rect_for_level(new_level) self.scene.setSceneRect(new_rect) self.slide_view_params.level = new_level self.reset_view_transform() self.view.setTransform(transform, False) self.slide_graphics.update_visible_level(new_level) self.update_labels() def get_best_level_for_scale(self, scale): scene_width = self.scene.sceneRect().size().width() candidates = [0] for level in self.slide_helper.get_levels(): w, h = self.slide_helper.get_level_size(level) if scene_width * scale <= w: candidates.append(level) best_level = max(candidates) return best_level def update_labels(self): level_downsample = self.slide_helper.get_downsample_for_level( self.slide_view_params.level) level_size = self.slide_helper.get_level_size( self.slide_view_params.level) self.level_downsample_label.setText( "level, downsample: {}, {:.0f}".format( self.slide_view_params.level, level_downsample)) self.level_size_label.setText( "level_size: ({}, {})".format(*level_size)) self.view_rect_scene_label.setText( "view_scene: ({:.0f},{:.0f},{:.0f},{:.0f})".format( *self.get_current_view_scene_rect().getRect())) if self.slide_view_params.selected_rect_0_level: self.selected_rect_label.setText( "selected rect (0-level): ({:.0f},{:.0f},{:.0f},{:.0f})". format(*self.slide_view_params.selected_rect_0_level)) def on_view_changed(self): if self.scale_initializer_deffered_function is None and self.slide_view_params: self.slide_view_params.level_rect = self.get_current_view_scene_rect( ).getRect() self.update_labels() def reset_view_transform(self): self.view.resetTransform() self.view.horizontalScrollBar().setValue(0) self.view.verticalScrollBar().setValue(0) def get_current_view_scene_rect(self): return self.view.mapToScene(self.view.viewport().rect()).boundingRect() def get_current_view_scale(self): scale = self.view.transform().m11() return scale
class ImageViewer(QGraphicsView, QObject): points_selection_sgn = pyqtSignal(list) key_press_sgn = pyqtSignal(QtGui.QKeyEvent) def __init__(self, parent=None): super(ImageViewer, self).__init__(parent) self.setDragMode(QGraphicsView.ScrollHandDrag) self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setResizeAnchor(QGraphicsView.AnchorUnderMouse) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) self._scene = ImageViewerScene(self) self.setScene(self._scene) self._image = None self._image_original = None self._pixmap = None self._img_contrast = 1.0 self._img_brightness = 50.0 self._img_gamma = 1.0 self._create_grid() self._channels = [] self._current_tool = SELECTION_TOOL.POINTER self._dataset = None # create grid lines pen_color = QColor(255, 255, 255, 255) pen = QPen(pen_color) pen.setWidth(2) pen.setStyle(QtCore.Qt.DotLine) self.vline = QGraphicsLineItem() self.vline.setVisible(False) self.vline.setPen(pen) self.hline = QGraphicsLineItem() self.hline.setVisible(False) self.hline.setPen(pen) self._scene.addItem(self.vline) self._scene.addItem(self.hline) self._current_label = None # rectangle selection tool self._rectangle_tool_origin = QPoint() self._rectangle_tool_picker = QRubberBand(QRubberBand.Rectangle, self) # polygon selection tool app = QApplication.instance() color = app.palette().color(QPalette.Highlight) self._polygon_guide_line_pen = QPen(color) self._polygon_guide_line_pen.setWidth(3) self._polygon_guide_line_pen.setStyle(QtCore.Qt.DotLine) self._polygon_guide_line = QGraphicsLineItem() self._polygon_guide_line.setVisible(False) self._polygon_guide_line.setPen(self._polygon_guide_line_pen) self._scene.addItem(self._polygon_guide_line) self._current_polygon = None # circle self._current_ellipse = None # free selection tool self._current_free_path = None self._is_drawing = False self._last_point_drawn = QPoint() self._last_click_point = None self._free_Path_pen = QPen(color) self._free_Path_pen.setWidth(10) self._extreme_points = Queue(maxsize=4) @property def current_label(self): return self._current_label @current_label.setter def current_label(self, value): self._current_label = value if self._current_label: color = QColor(self._current_label.color) self._free_Path_pen.setColor(color) self._polygon_guide_line_pen.setColor(color) self._polygon_guide_line.setPen(self._polygon_guide_line_pen) @property def dataset(self): return self._dataset @dataset.setter def dataset(self, value): self._dataset = value @property def img_contrast(self): return self._img_contrast @img_contrast.setter def img_contrast(self, value): self._img_contrast = value @property def img_gamma(self): return self._img_gamma @img_gamma.setter def img_gamma(self, value): self._img_gamma = value @property def img_brightness(self): return self._img_brightness @img_brightness.setter def img_brightness(self, value): self._img_brightness = value @property def image(self): return self._image @image.setter def image(self, value): self._image = value self._image_original = value.copy() self.update_viewer() @property def pixmap(self) -> ImagePixmap: return self._pixmap @gui_exception def update_viewer(self, fit_image=True): rgb = cv2.cvtColor(self._image, cv2.COLOR_BGR2RGB) rgb = ImageUtilities.adjust_image(rgb, self._img_contrast, self._img_brightness) rgb = ImageUtilities.adjust_gamma(rgb, self._img_gamma) pil_image = Image.fromarray(rgb) qppixmap_image = pil_image.toqpixmap() x, y = -qppixmap_image.width() / 2, -qppixmap_image.height() / 2 if self._pixmap: self._pixmap.resetTransform() self._pixmap.setPixmap(qppixmap_image) self._pixmap.setOffset(x, y) else: self._pixmap = ImagePixmap() self._pixmap.setPixmap(qppixmap_image) self._pixmap.setOffset(x, y) self._scene.addItem(self._pixmap) self._pixmap.signals.hoverEnterEventSgn.connect( self.pixmap_hoverEnterEvent_slot) self._pixmap.signals.hoverLeaveEventSgn.connect( self.pixmap_hoverLeaveEvent_slot) self._pixmap.signals.hoverMoveEventSgn.connect( self.pixmap_hoverMoveEvent_slot) self._hide_guide_lines() if fit_image: self.fit_to_window() @gui_exception def reset_viewer(self): self._img_contrast = 1.0 self._img_brightness = 50.0 self._img_gamma = 1.0 self._image = self._image_original.copy() @gui_exception def equalize_histogram(self): self._image = ImageUtilities.histogram_equalization(self._image) @gui_exception def correct_lightness(self): self._image = ImageUtilities.correct_lightness(self._image) def clusterize(self, k): self._image = ImageUtilities.kmeans(self._image.copy(), k) @property def current_tool(self): return self._current_tool @current_tool.setter def current_tool(self, value): self._polygon_guide_line.hide() self._current_polygon = None self._current_free_path = None self._current_ellipse = None self._is_drawing = value == SELECTION_TOOL.FREE self._current_tool = value self.clear_extreme_points() if value == SELECTION_TOOL.POINTER: self.enable_items(True) else: self.enable_items(False) def fit_to_window(self): if not self._pixmap or not self._pixmap.pixmap(): return self.resetTransform() self.setTransform(QtGui.QTransform()) self.fitInView(self._pixmap, QtCore.Qt.KeepAspectRatio) def _create_grid(self, gridSize=15): app: QApplication = QApplication.instance() curr_theme = "dark" if app: curr_theme = app.property("theme") if curr_theme == "light": color1 = QtGui.QColor("white") color2 = QtGui.QColor(237, 237, 237) else: color1 = QtGui.QColor(20, 20, 20) color2 = QtGui.QColor(0, 0, 0) backgroundPixmap = QtGui.QPixmap(gridSize * 2, gridSize * 2) backgroundPixmap.fill(color1) painter = QtGui.QPainter(backgroundPixmap) painter.fillRect(0, 0, gridSize, gridSize, color2) painter.fillRect(gridSize, gridSize, gridSize, gridSize, color2) painter.end() self._scene.setBackgroundBrush(QtGui.QBrush(backgroundPixmap)) def wheelEvent(self, event: QWheelEvent): adj = (event.angleDelta().y() / 120) * 0.1 self.scale(1 + adj, 1 + adj) @gui_exception def keyPressEvent(self, event: QKeyEvent): if event.key() == QtCore.Qt.Key_Space: image_rect: QRectF = self._pixmap.sceneBoundingRect() if self.current_tool == SELECTION_TOOL.POLYGON and self._current_polygon: points = self._current_polygon.points self._polygon_guide_line.hide() self.setDragMode(QGraphicsView.ScrollHandDrag) if len(points) <= 2: self._current_polygon.delete_item() self.current_tool = SELECTION_TOOL.POINTER elif self.current_tool == SELECTION_TOOL.EXTREME_POINTS and \ self._extreme_points.full(): points = [] image_offset = QPointF(image_rect.width() / 2, image_rect.height() / 2) for pt in self._extreme_points.queue: pt: EditablePolygonPoint center = pt.sceneBoundingRect().center() x = math.floor(center.x() + image_offset.x()) y = math.floor(center.y() + image_offset.y()) points.append([x, y]) self.points_selection_sgn.emit(points) self.current_tool = SELECTION_TOOL.POINTER else: event.ignore() # guide lines events def _show_guide_lines(self): if self.hline and self.vline: self.hline.show() self.vline.show() def _hide_guide_lines(self): if self.hline and self.vline: self.hline.hide() self.vline.hide() def _update_guide_lines(self, x, y): bbox: QRect = self._pixmap.boundingRect() offset = QPointF(bbox.width() / 2, bbox.height() / 2) self.vline.setLine(x, -offset.y(), x, bbox.height() - offset.y()) self.vline.setZValue(1) self.hline.setLine(-offset.x(), y, bbox.width() - offset.x(), y) self.hline.setZValue(1) def pixmap_hoverMoveEvent_slot(self, evt: QGraphicsSceneHoverEvent, x, y): self._update_guide_lines(x, y) def pixmap_hoverEnterEvent_slot(self): self._show_guide_lines() def pixmap_hoverLeaveEvent_slot(self): self._hide_guide_lines() def delete_polygon_slot(self, polygon: EditablePolygon): self._current_polygon = None self.current_tool = SELECTION_TOOL.POINTER self._polygon_guide_line.hide() @gui_exception def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None: image_rect: QRectF = self._pixmap.boundingRect() mouse_pos = self.mapToScene(evt.pos()) if evt.buttons() == QtCore.Qt.LeftButton: if self.current_tool == SELECTION_TOOL.BOX: # create rectangle self.setDragMode(QGraphicsView.NoDrag) self._rectangle_tool_origin = evt.pos() geometry = QRect(self._rectangle_tool_origin, QSize()) self._rectangle_tool_picker.setGeometry(geometry) self._rectangle_tool_picker.show() elif self.current_tool == SELECTION_TOOL.POLYGON: if image_rect.contains(mouse_pos): if self._current_polygon is None: self._current_polygon = EditablePolygon() self._current_polygon.label = self._current_label self._current_polygon.tag = self._dataset self._current_polygon.signals.deleted.connect( self.delete_polygon_slot) self._scene.addItem(self._current_polygon) self._current_polygon.addPoint(mouse_pos) else: self._current_polygon.addPoint(mouse_pos) elif self.current_tool == SELECTION_TOOL.ELLIPSE: if image_rect.contains(mouse_pos): self.setDragMode(QGraphicsView.NoDrag) ellipse_rec = QtCore.QRectF(mouse_pos.x(), mouse_pos.y(), 0, 0) self._current_ellipse = EditableEllipse() self._current_ellipse.tag = self.dataset self._current_ellipse.label = self._current_label self._current_ellipse.setRect(ellipse_rec) self._scene.addItem(self._current_ellipse) elif self.current_tool == SELECTION_TOOL.FREE: # consider only the points into the image if image_rect.contains(mouse_pos): self.setDragMode(QGraphicsView.NoDrag) self._last_point_drawn = mouse_pos self._current_free_path = QGraphicsPathItem() self._current_free_path.setOpacity(0.6) self._current_free_path.setPen(self._free_Path_pen) painter = QPainterPath() painter.moveTo(self._last_point_drawn) self._current_free_path.setPath(painter) self._scene.addItem(self._current_free_path) elif self.current_tool == SELECTION_TOOL.EXTREME_POINTS: if image_rect.contains(mouse_pos): if not self._extreme_points.full(): def delete_point(idx): del self._extreme_points.queue[idx] idx = self._extreme_points.qsize() editable_pt = EditablePolygonPoint(idx) editable_pt.signals.deleted.connect(delete_point) editable_pt.setPos(mouse_pos) self._scene.addItem(editable_pt) self._extreme_points.put(editable_pt) else: self.setDragMode(QGraphicsView.ScrollHandDrag) super(ImageViewer, self).mousePressEvent(evt) @gui_exception def mouseMoveEvent(self, evt: QtGui.QMouseEvent) -> None: mouse_pos = self.mapToScene(evt.pos()) image_rect: QRectF = self._pixmap.boundingRect() if self.current_tool == SELECTION_TOOL.BOX: if not self._rectangle_tool_origin.isNull(): geometry = QRect(self._rectangle_tool_origin, evt.pos()).normalized() self._rectangle_tool_picker.setGeometry(geometry) elif self.current_tool == SELECTION_TOOL.POLYGON: if self._current_polygon and image_rect.contains(mouse_pos): if self._current_polygon.count > 0: last_point: QPointF = self._current_polygon.last_point self._polygon_guide_line.setZValue(1) self._polygon_guide_line.show() mouse_pos = self.mapToScene(evt.pos()) self._polygon_guide_line.setLine(last_point.x(), last_point.y(), mouse_pos.x(), mouse_pos.y()) else: self._polygon_guide_line.hide() elif self.current_tool == SELECTION_TOOL.ELLIPSE: if self._current_ellipse and image_rect.contains(mouse_pos): ellipse_rect = self._current_ellipse.rect() ellipse_pos = QPointF(ellipse_rect.x(), ellipse_rect.y()) distance = math.hypot(mouse_pos.x() - ellipse_pos.x(), mouse_pos.y() - ellipse_pos.y()) ellipse_rect.setWidth(distance) ellipse_rect.setHeight(distance) self._current_ellipse.setRect(ellipse_rect) elif self.current_tool == SELECTION_TOOL.FREE and evt.buttons( ) and QtCore.Qt.LeftButton: if self._current_free_path and image_rect.contains(mouse_pos): painter: QPainterPath = self._current_free_path.path() self._last_point_drawn = self.mapToScene(evt.pos()) painter.lineTo(self._last_point_drawn) self._current_free_path.setPath(painter) super(ImageViewer, self).mouseMoveEvent(evt) @gui_exception def mouseReleaseEvent(self, evt: QtGui.QMouseEvent) -> None: image_rect: QRectF = self._pixmap.boundingRect() if self.current_tool == SELECTION_TOOL.BOX: roi: QRect = self._rectangle_tool_picker.geometry() roi: QRectF = self.mapToScene(roi).boundingRect() self._rectangle_tool_picker.hide() if image_rect == roi.united(image_rect): rect = EditableBox(roi) rect.label = self.current_label rect.tag = self._dataset self._scene.addItem(rect) self.current_tool = SELECTION_TOOL.POINTER self.setDragMode(QGraphicsView.ScrollHandDrag) elif self.current_tool == SELECTION_TOOL.ELLIPSE and self._current_ellipse: roi: QRect = self._current_ellipse.boundingRect() if image_rect == roi.united(image_rect): self.current_tool = SELECTION_TOOL.POINTER self.setDragMode(QGraphicsView.ScrollHandDrag) else: self._current_ellipse.delete_item() elif self.current_tool == SELECTION_TOOL.FREE and self._current_free_path: # create polygon self._current_free_path: QGraphicsPathItem path_rect = self._current_free_path.boundingRect() if image_rect == path_rect.united(image_rect): path = self._current_free_path.path() path_polygon = EditablePolygon() path_polygon.tag = self.dataset path_polygon.label = self.current_label self._scene.addItem(path_polygon) for i in range(0, path.elementCount(), 10): x, y = path.elementAt(i).x, path.elementAt(i).y path_polygon.addPoint(QPointF(x, y)) self._scene.removeItem(self._current_free_path) self.current_tool = SELECTION_TOOL.POINTER self.setDragMode(QGraphicsView.ScrollHandDrag) super(ImageViewer, self).mouseReleaseEvent(evt) def remove_annotations(self): for item in self._scene.items(): if isinstance(item, EditableItem): item.delete_item() def remove_annotations_by_label(self, label_name): for item in self._scene.items(): if isinstance(item, EditableItem): if item.label and item.label.name == label_name: item.delete_item() def enable_items(self, value): for item in self._scene.items(): if isinstance(item, EditableItem): item.setEnabled(value) def clear_extreme_points(self): if self._extreme_points.qsize() > 0: for pt in self._extreme_points.queue: self._scene.removeItem(pt) self._extreme_points.queue.clear()
class SvnLogBrowserDialog(QWidget, Ui_SvnLogBrowserDialog): """ Class implementing a dialog to browse the log history. """ def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(SvnLogBrowserDialog, self).__init__(parent) self.setupUi(self) self.__position = QPoint() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.filesTree.headerItem().setText(self.filesTree.columnCount(), "") self.filesTree.header().setSortIndicator(0, Qt.AscendingOrder) self.vcs = vcs self.__initData() self.fromDate.setDisplayFormat("yyyy-MM-dd") self.toDate.setDisplayFormat("yyyy-MM-dd") self.__resetUI() self.__messageRole = Qt.UserRole self.__changesRole = Qt.UserRole + 1 self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) self.rx_sep1 = QRegExp('\\-+\\s*') self.rx_sep2 = QRegExp('=+\\s*') self.rx_rev1 = QRegExp( 'rev ([0-9]+): ([^|]*) \| ([^|]*) \| ([0-9]+) .*') # "rev" followed by one or more decimals followed by a colon followed # anything up to " | " (twice) followed by one or more decimals # followed by anything self.rx_rev2 = QRegExp( 'r([0-9]+) \| ([^|]*) \| ([^|]*) \| ([0-9]+) .*') # "r" followed by one or more decimals followed by " | " followed # anything up to " | " (twice) followed by one or more decimals # followed by anything self.rx_flags1 = QRegExp( r""" ([ADM])\s(.*)\s+\(\w+\s+(.*):([0-9]+)\)\s*""") # three blanks followed by A or D or M followed by path followed by # path copied from followed by copied from revision self.rx_flags2 = QRegExp(' ([ADM]) (.*)\\s*') # three blanks followed by A or D or M followed by path self.flags = { 'A': self.tr('Added'), 'D': self.tr('Deleted'), 'M': self.tr('Modified'), 'R': self.tr('Replaced'), } self.intercept = False def __initData(self): """ Private method to (re-)initialize some data. """ self.__maxDate = QDate() self.__minDate = QDate() self.__filterLogsEnabled = True self.buf = [] # buffer for stdout self.diff = None self.__started = False self.__lastRev = 0 def closeEvent(self, e): """ Protected slot implementing a close event handler. @param e close event (QCloseEvent) """ if self.process is not None and \ self.process.state() != QProcess.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.__position = self.pos() e.accept() def show(self): """ Public slot to show the dialog. """ if not self.__position.isNull(): self.move(self.__position) self.__resetUI() super(SvnLogBrowserDialog, self).show() def __resetUI(self): """ Private method to reset the user interface. """ self.fromDate.setDate(QDate.currentDate()) self.toDate.setDate(QDate.currentDate()) self.fieldCombo.setCurrentIndex(self.fieldCombo.findText( self.tr("Message"))) self.limitSpinBox.setValue(self.vcs.getPlugin().getPreferences( "LogLimit")) self.stopCheckBox.setChecked(self.vcs.getPlugin().getPreferences( "StopLogOnCopy")) self.logTree.clear() self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) def __resizeColumnsLog(self): """ Private method to resize the log tree columns. """ self.logTree.header().resizeSections(QHeaderView.ResizeToContents) self.logTree.header().setStretchLastSection(True) def __resortLog(self): """ Private method to resort the log tree. """ self.logTree.sortItems( self.logTree.sortColumn(), self.logTree.header().sortIndicatorOrder()) def __resizeColumnsFiles(self): """ Private method to resize the changed files tree columns. """ self.filesTree.header().resizeSections(QHeaderView.ResizeToContents) self.filesTree.header().setStretchLastSection(True) def __resortFiles(self): """ Private method to resort the changed files tree. """ sortColumn = self.filesTree.sortColumn() self.filesTree.sortItems( 1, self.filesTree.header().sortIndicatorOrder()) self.filesTree.sortItems( sortColumn, self.filesTree.header().sortIndicatorOrder()) def __generateLogItem(self, author, date, message, revision, changedPaths): """ Private method to generate a log tree entry. @param author author info (string) @param date date info (string) @param message text of the log message (list of strings) @param revision revision info (string) @param changedPaths list of dictionary objects containing info about the changed files/directories @return reference to the generated item (QTreeWidgetItem) """ msg = [] for line in message: msg.append(line.strip()) itm = QTreeWidgetItem(self.logTree) itm.setData(0, Qt.DisplayRole, int(revision)) itm.setData(1, Qt.DisplayRole, author) itm.setData(2, Qt.DisplayRole, date) itm.setData(3, Qt.DisplayRole, " ".join(msg)) itm.setData(0, self.__messageRole, message) itm.setData(0, self.__changesRole, changedPaths) itm.setTextAlignment(0, Qt.AlignRight) itm.setTextAlignment(1, Qt.AlignLeft) itm.setTextAlignment(2, Qt.AlignLeft) itm.setTextAlignment(3, Qt.AlignLeft) itm.setTextAlignment(4, Qt.AlignLeft) try: self.__lastRev = int(revision) except ValueError: self.__lastRev = 0 return itm def __generateFileItem(self, action, path, copyFrom, copyRev): """ Private method to generate a changed files tree entry. @param action indicator for the change action ("A", "D" or "M") @param path path of the file in the repository (string) @param copyFrom path the file was copied from (None, string) @param copyRev revision the file was copied from (None, string) @return reference to the generated item (QTreeWidgetItem) """ itm = QTreeWidgetItem(self.filesTree, [ self.flags[action], path, copyFrom, copyRev, ]) itm.setTextAlignment(3, Qt.AlignRight) return itm def __getLogEntries(self, startRev=None): """ Private method to retrieve log entries from the repository. @param startRev revision number to start from (integer, string) """ self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) QApplication.processEvents() QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() self.intercept = False self.process.kill() self.buf = [] self.cancelled = False self.errors.clear() args = [] args.append('log') self.vcs.addArguments(args, self.vcs.options['global']) self.vcs.addArguments(args, self.vcs.options['log']) args.append('--verbose') args.append('--limit') args.append('{0:d}'.format(self.limitSpinBox.value())) if startRev is not None: args.append('--revision') args.append('{0}:0'.format(startRev)) if self.stopCheckBox.isChecked(): args.append('--stop-on-copy') args.append(self.fname) self.process.setWorkingDirectory(self.dname) self.inputGroup.setEnabled(True) self.inputGroup.show() self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.' ).format('svn')) def start(self, fn, isFile=False): """ Public slot to start the svn log command. @param fn filename to show the log for (string) @keyparam isFile flag indicating log for a file is to be shown (boolean) """ self.sbsCheckBox.setEnabled(isFile) self.sbsCheckBox.setVisible(isFile) self.errorGroup.hide() QApplication.processEvents() self.__initData() self.filename = fn self.dname, self.fname = self.vcs.splitPath(fn) self.activateWindow() self.raise_() self.logTree.clear() self.__started = True self.__getLogEntries() def __procFinished(self, exitCode, exitStatus): """ Private slot connected to the finished signal. @param exitCode exit code of the process (integer) @param exitStatus exit status of the process (QProcess.ExitStatus) """ self.__processBuffer() self.__finish() def __finish(self): """ Private slot called when the process finished or the user pressed the button. """ if self.process is not None and \ self.process.state() != QProcess.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) QApplication.restoreOverrideCursor() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.inputGroup.setEnabled(False) self.inputGroup.hide() def __processBuffer(self): """ Private method to process the buffered output of the svn log command. """ noEntries = 0 log = {"message": []} changedPaths = [] for s in self.buf: if self.rx_rev1.exactMatch(s): log["revision"] = self.rx_rev.cap(1) log["author"] = self.rx_rev.cap(2) log["date"] = self.rx_rev.cap(3) # number of lines is ignored elif self.rx_rev2.exactMatch(s): log["revision"] = self.rx_rev2.cap(1) log["author"] = self.rx_rev2.cap(2) log["date"] = self.rx_rev2.cap(3) # number of lines is ignored elif self.rx_flags1.exactMatch(s): changedPaths.append({ "action": self.rx_flags1.cap(1).strip(), "path": self.rx_flags1.cap(2).strip(), "copyfrom_path": self.rx_flags1.cap(3).strip(), "copyfrom_revision": self.rx_flags1.cap(4).strip(), }) elif self.rx_flags2.exactMatch(s): changedPaths.append({ "action": self.rx_flags2.cap(1).strip(), "path": self.rx_flags2.cap(2).strip(), "copyfrom_path": "", "copyfrom_revision": "", }) elif self.rx_sep1.exactMatch(s) or self.rx_sep2.exactMatch(s): if len(log) > 1: self.__generateLogItem( log["author"], log["date"], log["message"], log["revision"], changedPaths) dt = QDate.fromString(log["date"], Qt.ISODate) if not self.__maxDate.isValid() and \ not self.__minDate.isValid(): self.__maxDate = dt self.__minDate = dt else: if self.__maxDate < dt: self.__maxDate = dt if self.__minDate > dt: self.__minDate = dt noEntries += 1 log = {"message": []} changedPaths = [] else: if s.strip().endswith(":") or not s.strip(): continue else: log["message"].append(s) self.__resizeColumnsLog() self.__resortLog() if self.__started: self.logTree.setCurrentItem(self.logTree.topLevelItem(0)) self.__started = False if noEntries < self.limitSpinBox.value() and not self.cancelled: self.nextButton.setEnabled(False) self.limitSpinBox.setEnabled(False) self.__filterLogsEnabled = False self.fromDate.setMinimumDate(self.__minDate) self.fromDate.setMaximumDate(self.__maxDate) self.fromDate.setDate(self.__minDate) self.toDate.setMinimumDate(self.__minDate) self.toDate.setMaximumDate(self.__maxDate) self.toDate.setDate(self.__maxDate) self.__filterLogsEnabled = True self.__filterLogs() def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal. It reads the output of the process and inserts it into a buffer. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): line = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') self.buf.append(line) def __readStderr(self): """ Private slot to handle the readyReadStandardError signal. It reads the error output of the process and inserts it into the error pane. """ if self.process is not None: self.errorGroup.show() s = str(self.process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(s) self.errors.ensureCursorVisible() def __diffRevisions(self, rev1, rev2): """ Private method to do a diff of two revisions. @param rev1 first revision number (integer) @param rev2 second revision number (integer) """ if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked(): self.vcs.svnSbsDiff(self.filename, revisions=(str(rev1), str(rev2))) else: if self.diff is None: from .SvnDiffDialog import SvnDiffDialog self.diff = SvnDiffDialog(self.vcs) self.diff.show() self.diff.raise_() self.diff.start(self.filename, [rev1, rev2]) def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.cancelled = True self.__finish() @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_logTree_currentItemChanged(self, current, previous): """ Private slot called, when the current item of the log tree changes. @param current reference to the new current item (QTreeWidgetItem) @param previous reference to the old current item (QTreeWidgetItem) """ if current is not None: self.messageEdit.clear() for line in current.data(0, self.__messageRole): self.messageEdit.append(line.strip()) self.filesTree.clear() changes = current.data(0, self.__changesRole) if len(changes) > 0: for change in changes: self.__generateFileItem( change["action"], change["path"], change["copyfrom_path"], change["copyfrom_revision"]) self.__resizeColumnsFiles() self.__resortFiles() self.diffPreviousButton.setEnabled( current != self.logTree.topLevelItem( self.logTree.topLevelItemCount() - 1)) @pyqtSlot() def on_logTree_itemSelectionChanged(self): """ Private slot called, when the selection has changed. """ self.diffRevisionsButton.setEnabled( len(self.logTree.selectedItems()) == 2) @pyqtSlot() def on_nextButton_clicked(self): """ Private slot to handle the Next button. """ if self.__lastRev > 1: self.__getLogEntries(self.__lastRev - 1) @pyqtSlot() def on_diffPreviousButton_clicked(self): """ Private slot to handle the Diff to Previous button. """ itm = self.logTree.currentItem() if itm is None: self.diffPreviousButton.setEnabled(False) return rev2 = int(itm.text(0)) itm = self.logTree.topLevelItem( self.logTree.indexOfTopLevelItem(itm) + 1) if itm is None: self.diffPreviousButton.setEnabled(False) return rev1 = int(itm.text(0)) self.__diffRevisions(rev1, rev2) @pyqtSlot() def on_diffRevisionsButton_clicked(self): """ Private slot to handle the Compare Revisions button. """ items = self.logTree.selectedItems() if len(items) != 2: self.diffRevisionsButton.setEnabled(False) return rev2 = int(items[0].text(0)) rev1 = int(items[1].text(0)) self.__diffRevisions(min(rev1, rev2), max(rev1, rev2)) @pyqtSlot(QDate) def on_fromDate_dateChanged(self, date): """ Private slot called, when the from date changes. @param date new date (QDate) """ self.__filterLogs() @pyqtSlot(QDate) def on_toDate_dateChanged(self, date): """ Private slot called, when the from date changes. @param date new date (QDate) """ self.__filterLogs() @pyqtSlot(str) def on_fieldCombo_activated(self, txt): """ Private slot called, when a new filter field is selected. @param txt text of the selected field (string) """ self.__filterLogs() @pyqtSlot(str) def on_rxEdit_textChanged(self, txt): """ Private slot called, when a filter expression is entered. @param txt filter expression (string) """ self.__filterLogs() def __filterLogs(self): """ Private method to filter the log entries. """ if self.__filterLogsEnabled: from_ = self.fromDate.date().toString("yyyy-MM-dd") to_ = self.toDate.date().addDays(1).toString("yyyy-MM-dd") txt = self.fieldCombo.currentText() if txt == self.tr("Author"): fieldIndex = 1 searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive) elif txt == self.tr("Revision"): fieldIndex = 0 txt = self.rxEdit.text() if txt.startswith("^"): searchRx = QRegExp( "^\s*{0}".format(txt[1:]), Qt.CaseInsensitive) else: searchRx = QRegExp(txt, Qt.CaseInsensitive) else: fieldIndex = 3 searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive) currentItem = self.logTree.currentItem() for topIndex in range(self.logTree.topLevelItemCount()): topItem = self.logTree.topLevelItem(topIndex) if topItem.text(2) <= to_ and topItem.text(2) >= from_ and \ searchRx.indexIn(topItem.text(fieldIndex)) > -1: topItem.setHidden(False) if topItem is currentItem: self.on_logTree_currentItemChanged(topItem, None) else: topItem.setHidden(True) if topItem is currentItem: self.messageEdit.clear() self.filesTree.clear() @pyqtSlot(bool) def on_stopCheckBox_clicked(self, checked): """ Private slot called, when the stop on copy/move checkbox is clicked. @param checked flag indicating the checked state (boolean) """ self.vcs.getPlugin().setPreferences("StopLogOnCopy", self.stopCheckBox.isChecked()) self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) def on_passwordCheckBox_toggled(self, isOn): """ Private slot to handle the password checkbox toggled. @param isOn flag indicating the status of the check box (boolean) """ if isOn: self.input.setEchoMode(QLineEdit.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.errorGroup.show() self.process.write(input) self.passwordCheckBox.setChecked(False) self.input.clear() def on_input_returnPressed(self): """ Private slot to handle the press of the return key in the input field. """ self.intercept = True self.on_sendButton_clicked() def keyPressEvent(self, evt): """ Protected slot to handle a key press event. @param evt the key press event (QKeyEvent) """ if self.intercept: self.intercept = False evt.accept() return super(SvnLogBrowserDialog, self).keyPressEvent(evt)
class PictureBox(QLabel): changedNumberRegions = pyqtSignal(int) def __init__(self, image, regions, parent=None): QLabel.__init__(self, parent) self.rubberBand = QRubberBand(QRubberBand.Rectangle, self) self.origin = QPoint() self.regions = None self.image = image def region_to_rect(region): y1, x1, y2, x2 = region.bbox return QRect(x1, y1, x2 - x1, y2 - y1) self.regions = list(map(region_to_rect, regions)) self._update_picture() def mousePressEvent(self, event): pal = QPalette() if event.button() == Qt.LeftButton: pal.setBrush(QPalette.Highlight, QBrush(Qt.blue)) elif event.button() == Qt.RightButton: pal.setBrush(QPalette.Highlight, QBrush(Qt.green)) self.rubberBand.setPalette(pal) self.origin = QPoint(event.pos()) self.rubberBand.setGeometry(QRect(self.origin, QSize())) self.rubberBand.show() def mouseMoveEvent(self, event): if not self.origin.isNull(): self.rubberBand.setGeometry( QRect(self.origin, event.pos()).normalized()) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: drawnRect = QRect(self.origin, event.pos()).normalized() for region in self.getIntersectedRegions(drawnRect): self.regions.remove(region) elif event.button() == Qt.RightButton: drawnRect = QRect(self.origin, event.pos()).normalized() meanArea = np.mean([r.width() * r.height() for r in self.regions]) meanHeight = np.mean([r.height() for r in self.regions]) meanWidth = np.mean([r.width() for r in self.regions]) if drawnRect.width() * drawnRect.height() * 2 < meanArea: rect = QRect(self.origin.x() - meanWidth / 2, self.origin.y() - meanHeight / 2, meanWidth, meanHeight) self.regions.append(rect) else: self.regions.append(drawnRect) self.rubberBand.hide() self._update_picture() def getIntersectedRegions(self, drawnRect): result = [] for region in self.regions: if region.intersects(drawnRect): result.append(region) return result def _update_regions(self): pixmap = self._get_pixmap() paint = QPainter(pixmap) paint.setPen(QColor("green")) for region in self.regions: paint.drawRect(region) # rect = mpatches.Rectangle((minc, minr), maxc - minc, maxr - minr, # fill=False, edgecolor='green', linewidth=2) del paint return pixmap def savePicture(self, fname): pixmap = self._update_regions() return pixmap.save(fname) def _update_picture(self): pixmap = self._update_regions() self.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.setPixmap(pixmap) self.changedNumberRegions.emit(len(self.regions)) def _get_pixmap(self): # Convert image to QImage qimg = qimage2ndarray.array2qimage(self.image, True) return QPixmap(qimg)
class HgConflictsListDialog(QWidget, Ui_HgConflictsListDialog): """ Class implementing a dialog to show a list of files which had or still have conflicts. """ StatusRole = Qt.UserRole + 1 FilenameRole = Qt.UserRole + 2 def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(HgConflictsListDialog, self).__init__(parent) self.setupUi(self) self.__position = QPoint() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.conflictsList.headerItem().setText( self.conflictsList.columnCount(), "") self.conflictsList.header().setSortIndicator(0, Qt.AscendingOrder) self.refreshButton = self.buttonBox.addButton( self.tr("&Refresh"), QDialogButtonBox.ActionRole) self.refreshButton.setToolTip( self.tr("Press to refresh the list of conflicts")) self.refreshButton.setEnabled(False) self.vcs = vcs self.project = e5App().getObject("Project") self.__hgClient = vcs.getClient() def closeEvent(self, e): """ Protected slot implementing a close event handler. @param e close event (QCloseEvent) """ if self.__hgClient.isExecuting(): self.__hgClient.cancel() self.__position = self.pos() e.accept() def show(self): """ Public slot to show the dialog. """ if not self.__position.isNull(): self.move(self.__position) super(HgConflictsListDialog, self).show() def start(self, path): """ Public slot to start the tags command. @param path name of directory to list conflicts for (string) """ self.errorGroup.hide() QApplication.processEvents() self.intercept = False dname, fname = self.vcs.splitPath(path) # find the root of the repo self.__repodir = dname while not os.path.isdir(os.path.join(self.__repodir, self.vcs.adminDir)): self.__repodir = os.path.dirname(self.__repodir) if os.path.splitdrive(self.__repodir)[1] == os.sep: return self.activateWindow() self.raise_() self.conflictsList.clear() self.__started = True self.__getEntries() def __getEntries(self): """ Private method to get the conflict entries. """ args = self.vcs.initCommand("resolve") args.append('--list') out, err = self.__hgClient.runcommand(args) if err: self.__showError(err) if out: for line in out.splitlines(): self.__processOutputLine(line) if self.__hgClient.wasCanceled(): break self.__finish() def __finish(self): """ Private slot called when the process finished or the user pressed the button. """ QApplication.restoreOverrideCursor() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.refreshButton.setEnabled(True) self.__resizeColumns() self.__resort() self.on_conflictsList_itemSelectionChanged() @pyqtSlot(QAbstractButton) def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.__hgClient.cancel() elif button == self.refreshButton: self.on_refreshButton_clicked() def __resort(self): """ Private method to resort the tree. """ self.conflictsList.sortItems( self.conflictsList.sortColumn(), self.conflictsList.header().sortIndicatorOrder()) def __resizeColumns(self): """ Private method to resize the list columns. """ self.conflictsList.header().resizeSections( QHeaderView.ResizeToContents) self.conflictsList.header().setStretchLastSection(True) def __generateItem(self, status, name): """ Private method to generate a tag item in the tag list. @param status status of the file (string) @param name name of the file (string) """ itm = QTreeWidgetItem(self.conflictsList) if status == "U": itm.setText(0, self.tr("Unresolved")) elif status == "R": itm.setText(0, self.tr("Resolved")) else: itm.setText(0, self.tr("Unknown Status")) itm.setText(1, name) itm.setData(0, self.StatusRole, status) itm.setData(0, self.FilenameRole, self.project.getAbsolutePath(name)) def __processOutputLine(self, line): """ Private method to process the lines of output. @param line output line to be processed (string) """ status, filename = line.strip().split(None, 1) self.__generateItem(status, filename) def __showError(self, out): """ Private slot to show some error. @param out error to be shown (string) """ self.errorGroup.show() self.errors.insertPlainText(out) self.errors.ensureCursorVisible() @pyqtSlot() def on_refreshButton_clicked(self): """ Private slot to refresh the log. """ self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.refreshButton.setEnabled(False) self.start(self.__repodir) @pyqtSlot(QTreeWidgetItem, int) def on_conflictsList_itemDoubleClicked(self, item, column): """ Private slot to open the double clicked entry. @param item reference to the double clicked item (QTreeWidgetItem) @param column column that was double clicked (integer) """ self.on_editButton_clicked() @pyqtSlot() def on_conflictsList_itemSelectionChanged(self): """ Private slot to handle a change of selected conflict entries. """ selectedCount = len(self.conflictsList.selectedItems()) unresolved = resolved = 0 for itm in self.conflictsList.selectedItems(): status = itm.data(0, self.StatusRole) if status == "U": unresolved += 1 elif status == "R": resolved += 1 self.resolvedButton.setEnabled(unresolved > 0) self.unresolvedButton.setEnabled(resolved > 0) self.reMergeButton.setEnabled(unresolved > 0) self.editButton.setEnabled( selectedCount == 1 and Utilities.MimeTypes.isTextFile( self.conflictsList.selectedItems()[0].data( 0, self.FilenameRole))) @pyqtSlot() def on_resolvedButton_clicked(self): """ Private slot to mark the selected entries as resolved. """ names = [ itm.data(0, self.FilenameRole) for itm in self.conflictsList.selectedItems() if itm.data(0, self.StatusRole) == "U" ] if names: self.vcs.hgResolved(names) self.on_refreshButton_clicked() @pyqtSlot() def on_unresolvedButton_clicked(self): """ Private slot to mark the selected entries as unresolved. """ names = [ itm.data(0, self.FilenameRole) for itm in self.conflictsList.selectedItems() if itm.data(0, self.StatusRole) == "R" ] if names: self.vcs.hgResolved(names, unresolve=True) self.on_refreshButton_clicked() @pyqtSlot() def on_reMergeButton_clicked(self): """ Private slot to re-merge the selected entries. """ names = [ itm.data(0, self.FilenameRole) for itm in self.conflictsList.selectedItems() if itm.data(0, self.StatusRole) == "U" ] if names: self.vcs.hgReMerge(names) @pyqtSlot() def on_editButton_clicked(self): """ Private slot to open the selected file in an editor. """ itm = self.conflictsList.selectedItems()[0] filename = itm.data(0, self.FilenameRole) if Utilities.MimeTypes.isTextFile(filename): e5App().getObject("ViewManager").getEditor(filename)
class HgConflictsListDialog(QWidget, Ui_HgConflictsListDialog): """ Class implementing a dialog to show a list of files which had or still have conflicts. """ StatusRole = Qt.UserRole + 1 FilenameRole = Qt.UserRole + 2 def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(HgConflictsListDialog, self).__init__(parent) self.setupUi(self) self.__position = QPoint() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.conflictsList.headerItem().setText( self.conflictsList.columnCount(), "") self.conflictsList.header().setSortIndicator(0, Qt.AscendingOrder) self.refreshButton = self.buttonBox.addButton( self.tr("&Refresh"), QDialogButtonBox.ActionRole) self.refreshButton.setToolTip( self.tr("Press to refresh the list of conflicts")) self.refreshButton.setEnabled(False) self.vcs = vcs self.project = e5App().getObject("Project") self.__hgClient = vcs.getClient() if self.__hgClient: self.process = None else: self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) def closeEvent(self, e): """ Protected slot implementing a close event handler. @param e close event (QCloseEvent) """ if self.__hgClient: if self.__hgClient.isExecuting(): self.__hgClient.cancel() else: if self.process is not None and \ self.process.state() != QProcess.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.__position = self.pos() e.accept() def show(self): """ Public slot to show the dialog. """ if not self.__position.isNull(): self.move(self.__position) super(HgConflictsListDialog, self).show() def start(self, path): """ Public slot to start the tags command. @param path name of directory to list conflicts for (string) """ self.errorGroup.hide() QApplication.processEvents() self.intercept = False dname, fname = self.vcs.splitPath(path) # find the root of the repo self.__repodir = dname while not os.path.isdir(os.path.join(self.__repodir, self.vcs.adminDir)): self.__repodir = os.path.dirname(self.__repodir) if os.path.splitdrive(self.__repodir)[1] == os.sep: return self.activateWindow() self.raise_() self.conflictsList.clear() self.__started = True self.__getEntries() def __getEntries(self): """ Private method to get the conflict entries. """ args = self.vcs.initCommand("resolve") args.append('--list') if self.__hgClient: self.inputGroup.setEnabled(False) self.inputGroup.hide() out, err = self.__hgClient.runcommand(args) if err: self.__showError(err) if out: for line in out.splitlines(): self.__processOutputLine(line) if self.__hgClient.wasCanceled(): break self.__finish() else: self.process.kill() self.process.setWorkingDirectory(self.__repodir) self.process.start('hg', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.').format('hg')) else: self.inputGroup.setEnabled(True) self.inputGroup.show() def __finish(self): """ Private slot called when the process finished or the user pressed the button. """ if self.process is not None and \ self.process.state() != QProcess.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) QApplication.restoreOverrideCursor() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.inputGroup.setEnabled(False) self.inputGroup.hide() self.refreshButton.setEnabled(True) self.__resizeColumns() self.__resort() self.on_conflictsList_itemSelectionChanged() @pyqtSlot(QAbstractButton) def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): if self.__hgClient: self.__hgClient.cancel() else: self.__finish() elif button == self.refreshButton: self.on_refreshButton_clicked() def __procFinished(self, exitCode, exitStatus): """ Private slot connected to the finished signal. @param exitCode exit code of the process (integer) @param exitStatus exit status of the process (QProcess.ExitStatus) """ self.__finish() def __resort(self): """ Private method to resort the tree. """ self.conflictsList.sortItems( self.conflictsList.sortColumn(), self.conflictsList.header().sortIndicatorOrder()) def __resizeColumns(self): """ Private method to resize the list columns. """ self.conflictsList.header().resizeSections( QHeaderView.ResizeToContents) self.conflictsList.header().setStretchLastSection(True) def __generateItem(self, status, name): """ Private method to generate a tag item in the tag list. @param status status of the file (string) @param name name of the file (string) """ itm = QTreeWidgetItem(self.conflictsList) if status == "U": itm.setText(0, self.tr("Unresolved")) elif status == "R": itm.setText(0, self.tr("Resolved")) else: itm.setText(0, self.tr("Unknown Status")) itm.setText(1, name) itm.setData(0, self.StatusRole, status) itm.setData(0, self.FilenameRole, self.project.getAbsolutePath(name)) def __readStdout(self): """ Private slot to handle the readyReadStdout signal. It reads the output of the process, formats it and inserts it into the contents pane. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): s = str(self.process.readLine(), self.vcs.getEncoding(), 'replace').strip() self.__processOutputLine(s) def __processOutputLine(self, line): """ Private method to process the lines of output. @param line output line to be processed (string) """ status, filename = line.strip().split(None, 1) self.__generateItem(status, filename) @pyqtSlot() def on_refreshButton_clicked(self): """ Private slot to refresh the log. """ self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.inputGroup.setEnabled(True) self.inputGroup.show() self.refreshButton.setEnabled(False) self.start(self.__repodir) def __readStderr(self): """ Private slot to handle the readyReadStderr signal. It reads the error output of the process and inserts it into the error pane. """ if self.process is not None: s = str(self.process.readAllStandardError(), self.vcs.getEncoding(), 'replace') self.__showError(s) def __showError(self, out): """ Private slot to show some error. @param out error to be shown (string) """ self.errorGroup.show() self.errors.insertPlainText(out) self.errors.ensureCursorVisible() def on_passwordCheckBox_toggled(self, isOn): """ Private slot to handle the password checkbox toggled. @param isOn flag indicating the status of the check box (boolean) """ if isOn: self.input.setEchoMode(QLineEdit.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.process.write(input) self.passwordCheckBox.setChecked(False) self.input.clear() def on_input_returnPressed(self): """ Private slot to handle the press of the return key in the input field. """ self.intercept = True self.on_sendButton_clicked() def keyPressEvent(self, evt): """ Protected slot to handle a key press event. @param evt the key press event (QKeyEvent) """ if self.intercept: self.intercept = False evt.accept() return super(HgConflictsListDialog, self).keyPressEvent(evt) @pyqtSlot(QTreeWidgetItem, int) def on_conflictsList_itemDoubleClicked(self, item, column): """ Private slot to open the double clicked entry. @param item reference to the double clicked item (QTreeWidgetItem) @param column column that was double clicked (integer) """ self.on_editButton_clicked() @pyqtSlot() def on_conflictsList_itemSelectionChanged(self): """ Private slot to handle a change of selected conflict entries. """ selectedCount = len(self.conflictsList.selectedItems()) unresolved = resolved = 0 for itm in self.conflictsList.selectedItems(): status = itm.data(0, self.StatusRole) if status == "U": unresolved += 1 elif status == "R": resolved += 1 self.resolvedButton.setEnabled(unresolved > 0) self.unresolvedButton.setEnabled(resolved > 0) self.reMergeButton.setEnabled(unresolved > 0) self.editButton.setEnabled( selectedCount == 1 and Utilities.MimeTypes.isTextFile( self.conflictsList.selectedItems()[0].data( 0, self.FilenameRole))) @pyqtSlot() def on_resolvedButton_clicked(self): """ Private slot to mark the selected entries as resolved. """ names = [ itm.data(0, self.FilenameRole) for itm in self.conflictsList.selectedItems() if itm.data(0, self.StatusRole) == "U" ] if names: self.vcs.hgResolved(names) self.on_refreshButton_clicked() @pyqtSlot() def on_unresolvedButton_clicked(self): """ Private slot to mark the selected entries as unresolved. """ names = [ itm.data(0, self.FilenameRole) for itm in self.conflictsList.selectedItems() if itm.data(0, self.StatusRole) == "R" ] if names: self.vcs.hgResolved(names, unresolve=True) self.on_refreshButton_clicked() @pyqtSlot() def on_reMergeButton_clicked(self): """ Private slot to re-merge the selected entries. """ names = [ itm.data(0, self.FilenameRole) for itm in self.conflictsList.selectedItems() if itm.data(0, self.StatusRole) == "U" ] if names: self.vcs.hgReMerge(names) @pyqtSlot() def on_editButton_clicked(self): """ Private slot to open the selected file in an editor. """ itm = self.conflictsList.selectedItems()[0] filename = itm.data(0, self.FilenameRole) if Utilities.MimeTypes.isTextFile(filename): e5App().getObject("ViewManager").getEditor(filename)
class SnapWidget(QWidget, Ui_SnapWidget): """ Class implementing the snapshot widget. """ ModeFullscreen = 0 ModeScreen = 1 ModeRectangle = 2 ModeFreehand = 3 ModeEllipse = 4 def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget (QWidget) """ super(SnapWidget, self).__init__(parent) self.setupUi(self) self.saveButton.setIcon(UI.PixmapCache.getIcon("fileSaveAs.png")) self.takeButton.setIcon(UI.PixmapCache.getIcon("cameraPhoto.png")) self.copyButton.setIcon(UI.PixmapCache.getIcon("editCopy.png")) self.copyPreviewButton.setIcon(UI.PixmapCache.getIcon("editCopy.png")) self.setWindowIcon(UI.PixmapCache.getIcon("ericSnap.png")) self.modeCombo.addItem(self.tr("Fullscreen"), SnapWidget.ModeFullscreen) self.modeCombo.addItem(self.tr("Rectangular Selection"), SnapWidget.ModeRectangle) self.modeCombo.addItem(self.tr("Ellipical Selection"), SnapWidget.ModeEllipse) self.modeCombo.addItem(self.tr("Freehand Selection"), SnapWidget.ModeFreehand) if QApplication.desktop().screenCount() > 1: self.modeCombo.addItem(self.tr("Current Screen"), SnapWidget.ModeScreen) self.__mode = int(Preferences.Prefs.settings.value("Snapshot/Mode", 0)) index = self.modeCombo.findData(self.__mode) if index == -1: index = 0 self.modeCombo.setCurrentIndex(index) self.__delay = int( Preferences.Prefs.settings.value("Snapshot/Delay", 0)) self.delaySpin.setValue(self.__delay) if PYQT_VERSION_STR >= "5.0.0": from PyQt5.QtCore import QStandardPaths picturesLocation = QStandardPaths.writableLocation( QStandardPaths.PicturesLocation) else: from PyQt5.QtGui import QDesktopServices picturesLocation = QDesktopServices.storageLocation( QDesktopServices.PicturesLocation) self.__filename = Preferences.Prefs.settings.value( "Snapshot/Filename", os.path.join(picturesLocation, self.tr("snapshot") + "1.png")) self.__grabber = None self.__snapshot = QPixmap() self.__savedPosition = QPoint() self.__modified = False self.__locale = QLocale() self.__grabberWidget = QWidget(None, Qt.X11BypassWindowManagerHint) self.__grabberWidget.move(-10000, -10000) self.__grabberWidget.installEventFilter(self) self.__initFileFilters() self.__initShortcuts() self.preview.startDrag.connect(self.__dragSnapshot) from .SnapshotTimer import SnapshotTimer self.__grabTimer = SnapshotTimer() self.__grabTimer.timeout.connect(self.__grabTimerTimeout) self.__updateTimer = QTimer() self.__updateTimer.setSingleShot(True) self.__updateTimer.timeout.connect(self.__updatePreview) self.__updateCaption() self.takeButton.setFocus() def __initFileFilters(self): """ Private method to define the supported image file filters. """ filters = { 'bmp': self.tr("Windows Bitmap File (*.bmp)"), 'gif': self.tr("Graphic Interchange Format File (*.gif)"), 'ico': self.tr("Windows Icon File (*.ico)"), 'jpg': self.tr("JPEG File (*.jpg)"), 'mng': self.tr("Multiple-Image Network Graphics File (*.mng)"), 'pbm': self.tr("Portable Bitmap File (*.pbm)"), 'pcx': self.tr("Paintbrush Bitmap File (*.pcx)"), 'pgm': self.tr("Portable Graymap File (*.pgm)"), 'png': self.tr("Portable Network Graphics File (*.png)"), 'ppm': self.tr("Portable Pixmap File (*.ppm)"), 'sgi': self.tr("Silicon Graphics Image File (*.sgi)"), 'svg': self.tr("Scalable Vector Graphics File (*.svg)"), 'tga': self.tr("Targa Graphic File (*.tga)"), 'tif': self.tr("TIFF File (*.tif)"), 'xbm': self.tr("X11 Bitmap File (*.xbm)"), 'xpm': self.tr("X11 Pixmap File (*.xpm)"), } outputFormats = [] writeFormats = QImageWriter.supportedImageFormats() for writeFormat in writeFormats: try: outputFormats.append(filters[bytes(writeFormat).decode()]) except KeyError: pass outputFormats.sort() self.__outputFilter = ';;'.join(outputFormats) self.__defaultFilter = filters['png'] def __initShortcuts(self): """ Private method to initialize the keyboard shortcuts. """ self.__quitShortcut = QShortcut( QKeySequence(QKeySequence.Quit), self, self.close) self.__copyShortcut = QShortcut( QKeySequence(QKeySequence.Copy), self, self.copyButton.animateClick) self.__quickSaveShortcut = QShortcut( QKeySequence(Qt.Key_Q), self, self.__quickSave) self.__save1Shortcut = QShortcut( QKeySequence(QKeySequence.Save), self, self.saveButton.animateClick) self.__save2Shortcut = QShortcut( QKeySequence(Qt.Key_S), self, self.saveButton.animateClick) self.__grab1Shortcut = QShortcut( QKeySequence(QKeySequence.New), self, self.takeButton.animateClick) self.__grab2Shortcut = QShortcut( QKeySequence(Qt.Key_N), self, self.takeButton.animateClick) self.__grab3Shortcut = QShortcut( QKeySequence(Qt.Key_Space), self, self.takeButton.animateClick) def __quickSave(self): """ Private slot to save the snapshot bypassing the file selection dialog. """ if not self.__snapshot.isNull(): while os.path.exists(self.__filename): self.__autoIncFilename() if self.__saveImage(self.__filename): self.__modified = False self.__autoIncFilename() self.__updateCaption() @pyqtSlot() def on_saveButton_clicked(self): """ Private slot to save the snapshot. """ if not self.__snapshot.isNull(): while os.path.exists(self.__filename): self.__autoIncFilename() fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( self, self.tr("Save Snapshot"), self.__filename, self.__outputFilter, self.__defaultFilter, E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) if not fileName: return ext = QFileInfo(fileName).suffix() if not ext: ex = selectedFilter.split("(*")[1].split(")")[0] if ex: fileName += ex if self.__saveImage(fileName): self.__modified = False self.__filename = fileName self.__autoIncFilename() self.__updateCaption() def __saveImage(self, fileName): """ Private method to save the snapshot. @param fileName name of the file to save to (string) @return flag indicating success (boolean) """ if QFileInfo(fileName).exists(): res = E5MessageBox.yesNo( self, self.tr("Save Snapshot"), self.tr("<p>The file <b>{0}</b> already exists." " Overwrite it?</p>").format(fileName), icon=E5MessageBox.Warning) if not res: return False file = QFile(fileName) if not file.open(QFile.WriteOnly): E5MessageBox.warning( self, self.tr("Save Snapshot"), self.tr("Cannot write file '{0}:\n{1}.") .format(fileName, file.errorString())) return False ok = self.__snapshot.save(file) file.close() if not ok: E5MessageBox.warning( self, self.tr("Save Snapshot"), self.tr("Cannot write file '{0}:\n{1}.") .format(fileName, file.errorString())) return ok def __autoIncFilename(self): """ Private method to auto-increment the file name. """ # Extract the file name name = os.path.basename(self.__filename) # If the name contains a number, then increment it. numSearch = QRegExp("(^|[^\\d])(\\d+)") # We want to match as far left as possible, and when the number is # at the start of the name. # Does it have a number? start = numSearch.lastIndexIn(name) if start != -1: # It has a number, increment it. start = numSearch.pos(2) # Only the second group is of interest. numAsStr = numSearch.capturedTexts()[2] number = "{0:0{width}d}".format( int(numAsStr) + 1, width=len(numAsStr)) name = name[:start] + number + name[start + len(numAsStr):] else: # no number start = name.rfind('.') if start != -1: # has a '.' somewhere, e.g. it has an extension name = name[:start] + '1' + name[start:] else: # no extension, just tack it on to the end name += '1' self.__filename = os.path.join(os.path.dirname(self.__filename), name) self.__updateCaption() @pyqtSlot() def on_takeButton_clicked(self): """ Private slot to take a snapshot. """ self.__mode = self.modeCombo.itemData(self.modeCombo.currentIndex()) self.__delay = self.delaySpin.value() self.__savedPosition = self.pos() self.hide() if self.__delay: self.__grabTimer.start(self.__delay) else: QTimer.singleShot(200, self.__startUndelayedGrab) def __grabTimerTimeout(self): """ Private slot to perform a delayed grab operation. """ if self.__mode == SnapWidget.ModeRectangle: self.__grabRectangle() elif self.__mode == SnapWidget.ModeEllipse: self.__grabEllipse() elif self.__mode == SnapWidget.ModeFreehand: self.__grabFreehand() else: self.__performGrab() def __startUndelayedGrab(self): """ Private slot to perform an undelayed grab operation. """ if self.__mode == SnapWidget.ModeRectangle: self.__grabRectangle() elif self.__mode == SnapWidget.ModeEllipse: self.__grabEllipse() elif self.__mode == SnapWidget.ModeFreehand: self.__grabFreehand() else: if Globals.isMacPlatform(): self.__performGrab() else: self.__grabberWidget.show() self.__grabberWidget.grabMouse(Qt.CrossCursor) def __grabRectangle(self): """ Private method to grab a rectangular screen region. """ from .SnapshotRegionGrabber import SnapshotRegionGrabber self.__grabber = SnapshotRegionGrabber( mode=SnapshotRegionGrabber.Rectangle) self.__grabber.grabbed.connect(self.__captured) def __grabEllipse(self): """ Private method to grab an elliptical screen region. """ from .SnapshotRegionGrabber import SnapshotRegionGrabber self.__grabber = SnapshotRegionGrabber( mode=SnapshotRegionGrabber.Ellipse) self.__grabber.grabbed.connect(self.__captured) def __grabFreehand(self): """ Private method to grab a non-rectangular screen region. """ from .SnapshotFreehandGrabber import SnapshotFreehandGrabber self.__grabber = SnapshotFreehandGrabber() self.__grabber.grabbed.connect(self.__captured) def __performGrab(self): """ Private method to perform a screen grab other than a selected region. """ self.__grabberWidget.releaseMouse() self.__grabberWidget.hide() self.__grabTimer.stop() if self.__mode == SnapWidget.ModeFullscreen: desktop = QApplication.desktop() if qVersion() >= "5.0.0": self.__snapshot = QApplication.screens()[0].grabWindow( desktop.winId(), desktop.x(), desktop.y(), desktop.width(), desktop.height()) else: self.__snapshot = QPixmap.grabWindow( desktop.winId(), desktop.x(), desktop.y(), desktop.width(), desktop.height()) elif self.__mode == SnapWidget.ModeScreen: desktop = QApplication.desktop() screenId = desktop.screenNumber(QCursor.pos()) geom = desktop.screenGeometry(screenId) x = geom.x() y = geom.y() if qVersion() >= "5.0.0": self.__snapshot = QApplication.screens()[0].grabWindow( desktop.winId(), x, y, geom.width(), geom.height()) else: self.__snapshot = QPixmap.grabWindow( desktop.winId(), x, y, geom.width(), geom.height()) else: self.__snapshot = QPixmap() self.__redisplay() self.__modified = True self.__updateCaption() def __redisplay(self): """ Private method to redisplay the window. """ self.__updatePreview() QApplication.restoreOverrideCursor() if not self.__savedPosition.isNull(): self.move(self.__savedPosition) self.show() self.raise_() self.saveButton.setEnabled(not self.__snapshot.isNull()) self.copyButton.setEnabled(not self.__snapshot.isNull()) self.copyPreviewButton.setEnabled(not self.__snapshot.isNull()) @pyqtSlot() def on_copyButton_clicked(self): """ Private slot to copy the snapshot to the clipboard. """ if not self.__snapshot.isNull(): QApplication.clipboard().setPixmap(QPixmap(self.__snapshot)) @pyqtSlot() def on_copyPreviewButton_clicked(self): """ Private slot to copy the snapshot preview to the clipboard. """ QApplication.clipboard().setPixmap(self.preview.pixmap()) def __captured(self, pixmap): """ Private slot to show a preview of the snapshot. @param pixmap pixmap of the snapshot (QPixmap) """ self.__grabber.close() self.__snapshot = QPixmap(pixmap) self.__grabber.grabbed.disconnect(self.__captured) self.__grabber = None self.__redisplay() self.__modified = True self.__updateCaption() def __updatePreview(self): """ Private slot to update the preview picture. """ self.preview.setToolTip(self.tr( "Preview of the snapshot image ({0} x {1})").format( self.__locale.toString(self.__snapshot.width()), self.__locale.toString(self.__snapshot.height())) ) self.preview.setPreview(self.__snapshot) self.preview.adjustSize() def resizeEvent(self, evt): """ Protected method handling a resizing of the window. @param evt resize event (QResizeEvent) """ self.__updateTimer.start(200) def __dragSnapshot(self): """ Private slot handling the dragging of the preview picture. """ drag = QDrag(self) mimeData = QMimeData() mimeData.setImageData(self.__snapshot) drag.setMimeData(mimeData) drag.setPixmap(self.preview.pixmap()) drag.exec_(Qt.CopyAction) def eventFilter(self, obj, evt): """ Public method to handle event for other objects. @param obj reference to the object (QObject) @param evt reference to the event (QEvent) @return flag indicating that the event should be filtered out (boolean) """ if obj == self.__grabberWidget and \ evt.type() == QEvent.MouseButtonPress: if QWidget.mouseGrabber() != self.__grabberWidget: return False if evt.button() == Qt.LeftButton: self.__performGrab() return False def closeEvent(self, evt): """ Protected method handling the close event. @param evt close event (QCloseEvent) """ if self.__modified: res = E5MessageBox.question( self, self.tr("eric6 Snapshot"), self.tr( """The application contains an unsaved snapshot."""), E5MessageBox.StandardButtons( E5MessageBox.Abort | E5MessageBox.Discard | E5MessageBox.Save)) if res == E5MessageBox.Abort: evt.ignore() return elif res == E5MessageBox.Save: self.on_saveButton_clicked() Preferences.Prefs.settings.setValue( "Snapshot/Delay", self.delaySpin.value()) Preferences.Prefs.settings.setValue( "Snapshot/Mode", self.modeCombo.itemData(self.modeCombo.currentIndex())) Preferences.Prefs.settings.setValue( "Snapshot/Filename", self.__filename) Preferences.Prefs.settings.sync() def __updateCaption(self): """ Private method to update the window caption. """ self.setWindowTitle("{0}[*] - {1}".format( os.path.basename(self.__filename), self.tr("eric6 Snapshot"))) self.setWindowModified(self.__modified) self.pathNameEdit.setText(os.path.dirname(self.__filename))
class HgShelveBrowserDialog(QWidget, Ui_HgShelveBrowserDialog): """ Class implementing Mercurial shelve browser dialog. """ NameColumn = 0 AgeColumn = 1 MessageColumn = 2 def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(HgShelveBrowserDialog, self).__init__(parent) self.setupUi(self) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.__position = QPoint() self.__fileStatisticsRole = Qt.UserRole self.__totalStatisticsRole = Qt.UserRole + 1 self.shelveList.header().setSortIndicator(0, Qt.AscendingOrder) self.refreshButton = self.buttonBox.addButton( self.tr("&Refresh"), QDialogButtonBox.ActionRole) self.refreshButton.setToolTip( self.tr("Press to refresh the list of shelves")) self.refreshButton.setEnabled(False) self.vcs = vcs self.__hgClient = vcs.getClient() self.__resetUI() if self.__hgClient: self.process = None else: self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) self.__contextMenu = QMenu() self.__unshelveAct = self.__contextMenu.addAction( self.tr("Restore selected shelve"), self.__unshelve) self.__deleteAct = self.__contextMenu.addAction( self.tr("Delete selected shelves"), self.__deleteShelves) self.__contextMenu.addAction( self.tr("Delete all shelves"), self.__cleanupShelves) def closeEvent(self, e): """ Protected slot implementing a close event handler. @param e close event (QCloseEvent) """ if self.__hgClient: if self.__hgClient.isExecuting(): self.__hgClient.cancel() else: if self.process is not None and \ self.process.state() != QProcess.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.__position = self.pos() e.accept() def show(self): """ Public slot to show the dialog. """ if not self.__position.isNull(): self.move(self.__position) self.__resetUI() super(HgShelveBrowserDialog, self).show() def __resetUI(self): """ Private method to reset the user interface. """ self.shelveList.clear() def __resizeColumnsShelves(self): """ Private method to resize the shelve list columns. """ self.shelveList.header().resizeSections(QHeaderView.ResizeToContents) self.shelveList.header().setStretchLastSection(True) def __generateShelveEntry(self, name, age, message, fileStatistics, totals): """ Private method to generate the shelve items. @param name name of the shelve (string) @param age age of the shelve (string) @param message shelve message (string) @param fileStatistics per file change statistics (tuple of four strings with file name, number of changes, number of added lines and number of deleted lines) @param totals overall statistics (tuple of three strings with number of changed files, number of added lines and number of deleted lines) """ itm = QTreeWidgetItem(self.shelveList, [name, age, message]) itm.setData(0, self.__fileStatisticsRole, fileStatistics) itm.setData(0, self.__totalStatisticsRole, totals) def __getShelveEntries(self): """ Private method to retrieve the list of shelves. """ self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) QApplication.processEvents() QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() self.buf = [] self.errors.clear() self.intercept = False args = self.vcs.initCommand("shelve") args.append("--list") args.append("--stat") if self.__hgClient: self.inputGroup.setEnabled(False) self.inputGroup.hide() out, err = self.__hgClient.runcommand(args) self.buf = out.splitlines(True) if err: self.__showError(err) self.__processBuffer() self.__finish() else: self.process.kill() self.process.setWorkingDirectory(self.repodir) self.inputGroup.setEnabled(True) self.inputGroup.show() self.process.start('hg', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.' ).format('hg')) def start(self, projectDir): """ Public slot to start the hg shelve command. @param projectDir name of the project directory (string) """ self.errorGroup.hide() QApplication.processEvents() self.__projectDir = projectDir # find the root of the repo self.repodir = self.__projectDir while not os.path.isdir(os.path.join(self.repodir, self.vcs.adminDir)): self.repodir = os.path.dirname(self.repodir) if os.path.splitdrive(self.repodir)[1] == os.sep: return self.activateWindow() self.raise_() self.shelveList.clear() self.__started = True self.__getShelveEntries() def __procFinished(self, exitCode, exitStatus): """ Private slot connected to the finished signal. @param exitCode exit code of the process (integer) @param exitStatus exit status of the process (QProcess.ExitStatus) """ self.__processBuffer() self.__finish() def __finish(self): """ Private slot called when the process finished or the user pressed the button. """ if self.process is not None and \ self.process.state() != QProcess.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) QApplication.restoreOverrideCursor() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.inputGroup.setEnabled(False) self.inputGroup.hide() self.refreshButton.setEnabled(True) def __processBuffer(self): """ Private method to process the buffered output of the hg shelve command. """ lastWasFileStats = False firstLine = True itemData = {} for line in self.buf: if firstLine: name, line = line.split("(", 1) age, message = line.split(")", 1) itemData["name"] = name.strip() itemData["age"] = age.strip() itemData["message"] = message.strip() itemData["files"] = [] firstLine = False elif '|' in line: # file stats: foo.py | 3 ++- file, changes = line.strip().split("|", 1) if changes.strip().endswith(("+", "-")): total, addDelete = changes.strip().split(None, 1) additions = str(addDelete.count("+")) deletions = str(addDelete.count("-")) else: total = changes.strip() additions = '0' deletions = '0' itemData["files"].append((file, total, additions, deletions)) lastWasFileStats = True elif lastWasFileStats: # summary line # 2 files changed, 15 insertions(+), 1 deletions(-) total, added, deleted = line.strip().split(",", 2) total = total.split()[0] added = added.split()[0] deleted = deleted.split()[0] itemData["summary"] = (total, added, deleted) self.__generateShelveEntry( itemData["name"], itemData["age"], itemData["message"], itemData["files"], itemData["summary"]) lastWasFileStats = False firstLine = True itemData = {} self.__resizeColumnsShelves() if self.__started: self.shelveList.setCurrentItem(self.shelveList.topLevelItem(0)) self.__started = False def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal. It reads the output of the process and inserts it into a buffer. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): line = str(self.process.readLine(), self.vcs.getEncoding(), 'replace') self.buf.append(line) def __readStderr(self): """ Private slot to handle the readyReadStandardError signal. It reads the error output of the process and inserts it into the error pane. """ if self.process is not None: s = str(self.process.readAllStandardError(), self.vcs.getEncoding(), 'replace') self.__showError(s) def __showError(self, out): """ Private slot to show some error. @param out error to be shown (string) """ self.errorGroup.show() self.errors.insertPlainText(out) self.errors.ensureCursorVisible() @pyqtSlot(QAbstractButton) def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.cancelled = True if self.__hgClient: self.__hgClient.cancel() else: self.__finish() elif button == self.refreshButton: self.on_refreshButton_clicked() @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_shelveList_currentItemChanged(self, current, previous): """ Private slot called, when the current item of the shelve list changes. @param current reference to the new current item (QTreeWidgetItem) @param previous reference to the old current item (QTreeWidgetItem) """ self.statisticsList.clear() if current: for dataSet in current.data(0, self.__fileStatisticsRole): QTreeWidgetItem(self.statisticsList, list(dataSet)) self.statisticsList.header().resizeSections( QHeaderView.ResizeToContents) self.statisticsList.header().setStretchLastSection(True) totals = current.data(0, self.__totalStatisticsRole) self.filesLabel.setText( self.tr("%n file(s) changed", None, int(totals[0]))) self.insertionsLabel.setText( self.tr("%n line(s) inserted", None, int(totals[1]))) self.deletionsLabel.setText( self.tr("%n line(s) deleted", None, int(totals[2]))) else: self.filesLabel.setText("") self.insertionsLabel.setText("") self.deletionsLabel.setText("") @pyqtSlot(QPoint) def on_shelveList_customContextMenuRequested(self, pos): """ Private slot to show the context menu of the shelve list. @param pos position of the mouse pointer (QPoint) """ selectedItemsCount = len(self.shelveList.selectedItems()) self.__unshelveAct.setEnabled(selectedItemsCount == 1) self.__deleteAct.setEnabled(selectedItemsCount > 0) self.__contextMenu.popup(self.mapToGlobal(pos)) @pyqtSlot() def on_refreshButton_clicked(self): """ Private slot to refresh the list of shelves. """ self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.inputGroup.setEnabled(True) self.inputGroup.show() self.refreshButton.setEnabled(False) self.start(self.__projectDir) def on_passwordCheckBox_toggled(self, isOn): """ Private slot to handle the password checkbox toggled. @param isOn flag indicating the status of the check box (boolean) """ if isOn: self.input.setEchoMode(QLineEdit.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the mercurial process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.errorGroup.show() self.process.write(input) self.passwordCheckBox.setChecked(False) self.input.clear() def on_input_returnPressed(self): """ Private slot to handle the press of the return key in the input field. """ self.intercept = True self.on_sendButton_clicked() def keyPressEvent(self, evt): """ Protected slot to handle a key press event. @param evt the key press event (QKeyEvent) """ if self.intercept: self.intercept = False evt.accept() return super(HgShelveBrowserDialog, self).keyPressEvent(evt) def __unshelve(self): """ Private slot to restore the selected shelve of changes. """ itm = self.shelveList.selectedItems()[0] if itm is not None: name = itm.text(self.NameColumn) self.vcs.getExtensionObject("shelve")\ .hgUnshelve(self.__projectDir, shelveName=name) self.on_refreshButton_clicked() def __deleteShelves(self): """ Private slot to delete the selected shelves. """ shelveNames = [] for itm in self.shelveList.selectedItems(): shelveNames.append(itm.text(self.NameColumn)) if shelveNames: self.vcs.getExtensionObject("shelve")\ .hgDeleteShelves(self.__projectDir, shelveNames=shelveNames) self.on_refreshButton_clicked() def __cleanupShelves(self): """ Private slot to delete all shelves. """ self.vcs.getExtensionObject("shelve")\ .hgCleanupShelves(self.__projectDir) self.on_refreshButton_clicked()
class MandelbrotWidget(QWidget): def __init__(self, parent=None): super(MandelbrotWidget, self).__init__(parent) self.thread = RenderThread() self.pixmap = QPixmap() self.pixmapOffset = QPoint() self.lastDragPos = QPoint() self.centerX = DefaultCenterX self.centerY = DefaultCenterY self.pixmapScale = DefaultScale self.curScale = DefaultScale self.thread.renderedImage.connect(self.updatePixmap) self.setWindowTitle("Mandelbrot") self.setCursor(Qt.CrossCursor) self.resize(550, 400) def paintEvent(self, event): painter = QPainter(self) painter.fillRect(self.rect(), Qt.black) if self.pixmap.isNull(): painter.setPen(Qt.white) painter.drawText(self.rect(), Qt.AlignCenter, "Rendering initial image, please wait...") return if self.curScale == self.pixmapScale: painter.drawPixmap(self.pixmapOffset, self.pixmap) else: scaleFactor = self.pixmapScale / self.curScale newWidth = int(self.pixmap.width() * scaleFactor) newHeight = int(self.pixmap.height() * scaleFactor) newX = self.pixmapOffset.x() + (self.pixmap.width() - newWidth) / 2 newY = self.pixmapOffset.y() + (self.pixmap.height() - newHeight) / 2 painter.save() painter.translate(newX, newY) painter.scale(scaleFactor, scaleFactor) exposed, _ = painter.matrix().inverted() exposed = exposed.mapRect(self.rect()).adjusted(-1, -1, 1, 1) painter.drawPixmap(exposed, self.pixmap, exposed) painter.restore() text = "Use mouse wheel or the '+' and '-' keys to zoom. Press and " \ "hold left mouse button to scroll." metrics = painter.fontMetrics() textWidth = metrics.width(text) painter.setPen(Qt.NoPen) painter.setBrush(QColor(0, 0, 0, 127)) painter.drawRect((self.width() - textWidth) / 2 - 5, 0, textWidth + 10, metrics.lineSpacing() + 5) painter.setPen(Qt.white) painter.drawText((self.width() - textWidth) / 2, metrics.leading() + metrics.ascent(), text) def resizeEvent(self, event): self.thread.render(self.centerX, self.centerY, self.curScale, self.size()) def keyPressEvent(self, event): if event.key() == Qt.Key_Plus: self.zoom(ZoomInFactor) elif event.key() == Qt.Key_Minus: self.zoom(ZoomOutFactor) elif event.key() == Qt.Key_Left: self.scroll(-ScrollStep, 0) elif event.key() == Qt.Key_Right: self.scroll(+ScrollStep, 0) elif event.key() == Qt.Key_Down: self.scroll(0, -ScrollStep) elif event.key() == Qt.Key_Up: self.scroll(0, +ScrollStep) else: super(MandelbrotWidget, self).keyPressEvent(event) def wheelEvent(self, event): numDegrees = event.angleDelta().y() / 8 numSteps = numDegrees / 15.0 self.zoom(pow(ZoomInFactor, numSteps)) def mousePressEvent(self, event): if event.buttons() == Qt.LeftButton: self.lastDragPos = QPoint(event.pos()) def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton: self.pixmapOffset += event.pos() - self.lastDragPos self.lastDragPos = QPoint(event.pos()) self.update() def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: self.pixmapOffset += event.pos() - self.lastDragPos self.lastDragPos = QPoint() deltaX = (self.width() - self.pixmap.width()) / 2 - self.pixmapOffset.x() deltaY = (self.height() - self.pixmap.height()) / 2 - self.pixmapOffset.y() self.scroll(deltaX, deltaY) def updatePixmap(self, image, scaleFactor): if not self.lastDragPos.isNull(): return self.pixmap = QPixmap.fromImage(image) self.pixmapOffset = QPoint() self.lastDragPosition = QPoint() self.pixmapScale = scaleFactor self.update() def zoom(self, zoomFactor): self.curScale *= zoomFactor self.update() self.thread.render(self.centerX, self.centerY, self.curScale, self.size()) def scroll(self, deltaX, deltaY): self.centerX += deltaX * self.curScale self.centerY += deltaY * self.curScale self.update() self.thread.render(self.centerX, self.centerY, self.curScale, self.size())
class BoxInterpreter(QObject): rightClickReceived = pyqtSignal(object, QPoint) # list of indexes, global window coordinate of click leftClickReceived = pyqtSignal(object, QPoint) leftClickReleased = pyqtSignal(object, object) #boxAdded= pyqtSignal(object, object) #focusObjectChages= pyqtSignal(object, QPoint) cursorPositionChanged = pyqtSignal(object) deleteSelectedItemsSignal= pyqtSignal() #send the signal that we want to delete the currently selected item acceptBoxManipulation = True def __init__(self, navigationInterpreter, positionModel, BoxContr, widget): ''' Class which interacts directly with the image scene :param navigationInterpreter: :param positionModel: :param BoxContr: :param widget: The main widget ''' QObject.__init__(self) self.baseInterpret = navigationInterpreter self._posModel = positionModel self.rubberBand = RedRubberBand(QRubberBand.Rectangle, widget) self.boxController=BoxContr self.leftClickReleased.connect(BoxContr.addNewBox) self.rightClickReceived.connect(BoxContr.onChangedPos) #self.deleteSelectedItemsSignal.connect(BoxContr.deleteSelectedItems) self.origin = QPoint() self.originpos = object() def start( self ): self.baseInterpret.start() def stop( self ): self.baseInterpret.stop() def eventFilter( self, watched, event ): pos = [int(i) for i in self._posModel.cursorPos] pos = [self._posModel.time] + pos + [self._posModel.channel] #Rectangles under the current point items=watched.scene().items(QPointF(*pos[1:3])) items=[el for el in items if isinstance(el, QGraphicsResizableRect)] #Keyboard interaction if event.type()==QEvent.KeyPress: #Switch selection if event.key()==Qt.Key_Space : #assert items[0]._hovering #items[0].setZValue(1) #for el in items: # print el.zValue() if len(items)>1: items[-1].setZValue(items[0].zValue()+1) items[0].setSelected(False) items[-1].setSelected(True) #items[0].setZero() if event.key()==Qt.Key_Control : QApplication.setOverrideCursor(Qt.OpenHandCursor) # #Delete element # if event.key()==Qt.Key_Delete: # self.deleteSelectedItemsSignal.emit() if event.type()==QEvent.KeyRelease: if event.key()==Qt.Key_Control : QApplication.restoreOverrideCursor() #Pressing mouse and menaging rubber band if event.type() == QEvent.MouseButtonPress: if event.button() == Qt.LeftButton: self.origin = QPoint(event.pos()) self.originpos = pos self.rubberBand.setGeometry(QRect(self.origin, QSize())) itemsall=watched.scene().items(QPointF(*pos[1:3])) itemsall =[el for el in itemsall if isinstance(el, ResizeHandle)] # if len(itemsall)==0: #show rubber band only if there is no rubbber band # self.rubberBand.show() modifiers=QApplication.keyboardModifiers() if modifiers != Qt.ControlModifier and modifiers != Qt.ShiftModifier and len(itemsall)==0: #show rubber band if Ctrl is not pressed self.rubberBand.show() gPos = watched.mapToGlobal( event.pos() ) self.leftClickReceived.emit( pos, gPos ) if event.button() == Qt.RightButton: gPos = watched.mapToGlobal( event.pos() ) self.rightClickReceived.emit( pos, gPos ) if event.type() == QEvent.MouseMove: self.cursorPositionChanged.emit(event.pos()) if not self.origin.isNull(): self.rubberBand.setGeometry(QRect(self.origin, event.pos()).normalized()) #Relasing the button if event.type() == QEvent.MouseButtonRelease: pos = [int(i) for i in self._posModel.cursorPos] pos = [self._posModel.time] + pos + [self._posModel.channel] if self.rubberBand.isVisible(): if event.button() == Qt.LeftButton: self.rubberBand.hide() self.leftClickReleased.emit( self.originpos,pos ) # Event is always forwarded to the navigation interpreter. return self.baseInterpret.eventFilter(watched, event)
class GitReflogBrowserDialog(QWidget, Ui_GitReflogBrowserDialog): """ Class implementing a dialog to browse the reflog history. """ CommitIdColumn = 0 SelectorColumn = 1 NameColumn = 2 OperationColumn = 3 SubjectColumn = 4 def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent reference to the parent widget (QWidget) """ super(GitReflogBrowserDialog, self).__init__(parent) self.setupUi(self) self.__position = QPoint() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.logTree.headerItem().setText(self.logTree.columnCount(), "") self.refreshButton = self.buttonBox.addButton( self.tr("&Refresh"), QDialogButtonBox.ActionRole) self.refreshButton.setToolTip( self.tr("Press to refresh the list of commits")) self.refreshButton.setEnabled(False) self.vcs = vcs self.__formatTemplate = ('format:recordstart%n' 'commit|%h%n' 'selector|%gd%n' 'name|%gn%n' 'subject|%gs%n' 'recordend%n') self.repodir = "" self.__currentCommitId = "" self.__initData() self.__resetUI() self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) def __initData(self): """ Private method to (re-)initialize some data. """ self.buf = [] # buffer for stdout self.__started = False self.__skipEntries = 0 def closeEvent(self, e): """ Protected slot implementing a close event handler. @param e close event (QCloseEvent) """ if (self.process is not None and self.process.state() != QProcess.NotRunning): self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.__position = self.pos() e.accept() def show(self): """ Public slot to show the dialog. """ if not self.__position.isNull(): self.move(self.__position) self.__resetUI() super(GitReflogBrowserDialog, self).show() def __resetUI(self): """ Private method to reset the user interface. """ self.limitSpinBox.setValue( self.vcs.getPlugin().getPreferences("LogLimit")) self.logTree.clear() def __resizeColumnsLog(self): """ Private method to resize the log tree columns. """ self.logTree.header().resizeSections(QHeaderView.ResizeToContents) self.logTree.header().setStretchLastSection(True) def __generateReflogItem(self, commitId, selector, name, subject): """ Private method to generate a reflog tree entry. @param commitId commit id info (string) @param selector selector info (string) @param name name info (string) @param subject subject of the reflog entry (string) @return reference to the generated item (QTreeWidgetItem) """ operation, subject = subject.strip().split(": ", 1) columnLabels = [ commitId, selector, name, operation, subject, ] itm = QTreeWidgetItem(self.logTree, columnLabels) return itm def __getReflogEntries(self, skip=0): """ Private method to retrieve reflog entries from the repository. @param skip number of reflog entries to skip (integer) """ self.refreshButton.setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) QApplication.processEvents() QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() self.buf = [] self.cancelled = False self.errors.clear() self.intercept = False args = self.vcs.initCommand("log") args.append("--walk-reflogs") args.append('--max-count={0}'.format(self.limitSpinBox.value())) args.append('--abbrev={0}'.format( self.vcs.getPlugin().getPreferences("CommitIdLength"))) args.append('--format={0}'.format(self.__formatTemplate)) args.append('--skip={0}'.format(skip)) self.process.kill() self.process.setWorkingDirectory(self.repodir) self.inputGroup.setEnabled(True) self.inputGroup.show() self.process.start('git', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.').format('git')) def start(self, projectdir): """ Public slot to start the git log command. @param projectdir directory name of the project (string) """ self.errorGroup.hide() QApplication.processEvents() self.__initData() # find the root of the repo self.repodir = projectdir while not os.path.isdir(os.path.join(self.repodir, self.vcs.adminDir)): self.repodir = os.path.dirname(self.repodir) if os.path.splitdrive(self.repodir)[1] == os.sep: return self.activateWindow() self.raise_() self.logTree.clear() self.__started = True self.__getReflogEntries() def __procFinished(self, exitCode, exitStatus): """ Private slot connected to the finished signal. @param exitCode exit code of the process (integer) @param exitStatus exit status of the process (QProcess.ExitStatus) """ self.__processBuffer() self.__finish() def __finish(self): """ Private slot called when the process finished or the user pressed the button. """ if (self.process is not None and self.process.state() != QProcess.NotRunning): self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) QApplication.restoreOverrideCursor() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.inputGroup.setEnabled(False) self.inputGroup.hide() self.refreshButton.setEnabled(True) def __processBuffer(self): """ Private method to process the buffered output of the git log command. """ noEntries = 0 for line in self.buf: line = line.rstrip() if line == "recordstart": logEntry = {} elif line == "recordend": if len(logEntry) > 1: self.__generateReflogItem( logEntry["commit"], logEntry["selector"], logEntry["name"], logEntry["subject"], ) noEntries += 1 else: try: key, value = line.split("|", 1) except ValueError: key = "" value = line if key in ("commit", "selector", "name", "subject"): logEntry[key] = value.strip() self.__resizeColumnsLog() if self.__started: self.logTree.setCurrentItem(self.logTree.topLevelItem(0)) self.__started = False self.__skipEntries += noEntries if noEntries < self.limitSpinBox.value() and not self.cancelled: self.nextButton.setEnabled(False) self.limitSpinBox.setEnabled(False) else: self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) # restore current item if self.__currentCommitId: items = self.logTree.findItems(self.__currentCommitId, Qt.MatchExactly, self.CommitIdColumn) if items: self.logTree.setCurrentItem(items[0]) self.__currentCommitId = "" def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal. It reads the output of the process and inserts it into a buffer. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): line = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') self.buf.append(line) def __readStderr(self): """ Private slot to handle the readyReadStandardError signal. It reads the error output of the process and inserts it into the error pane. """ if self.process is not None: s = str(self.process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.__showError(s) def __showError(self, out): """ Private slot to show some error. @param out error to be shown (string) """ self.errorGroup.show() self.errors.insertPlainText(out) self.errors.ensureCursorVisible() def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.cancelled = True self.__finish() elif button == self.refreshButton: self.on_refreshButton_clicked() @pyqtSlot() def on_refreshButton_clicked(self): """ Private slot to refresh the log. """ # save the current item's commit ID itm = self.logTree.currentItem() if itm is not None: self.__currentCommitId = itm.text(self.CommitIdColumn) else: self.__currentCommitId = "" self.start(self.repodir) def on_passwordCheckBox_toggled(self, isOn): """ Private slot to handle the password checkbox toggled. @param isOn flag indicating the status of the check box (boolean) """ if isOn: self.input.setEchoMode(QLineEdit.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the git process. """ inputTxt = self.input.text() inputTxt += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(inputTxt) self.errors.ensureCursorVisible() self.errorGroup.show() self.process.write(strToQByteArray(inputTxt)) self.passwordCheckBox.setChecked(False) self.input.clear() def on_input_returnPressed(self): """ Private slot to handle the press of the return key in the input field. """ self.intercept = True self.on_sendButton_clicked() def keyPressEvent(self, evt): """ Protected slot to handle a key press event. @param evt the key press event (QKeyEvent) """ if self.intercept: self.intercept = False evt.accept() return super(GitReflogBrowserDialog, self).keyPressEvent(evt) @pyqtSlot() def on_nextButton_clicked(self): """ Private slot to handle the Next button. """ if self.__skipEntries > 0: self.__getReflogEntries(self.__skipEntries)
class HgShelveBrowserDialog(QWidget, Ui_HgShelveBrowserDialog): """ Class implementing Mercurial shelve browser dialog. """ NameColumn = 0 AgeColumn = 1 MessageColumn = 2 def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(HgShelveBrowserDialog, self).__init__(parent) self.setupUi(self) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.__position = QPoint() self.__fileStatisticsRole = Qt.UserRole self.__totalStatisticsRole = Qt.UserRole + 1 self.shelveList.header().setSortIndicator(0, Qt.AscendingOrder) self.refreshButton = self.buttonBox.addButton( self.tr("&Refresh"), QDialogButtonBox.ActionRole) self.refreshButton.setToolTip( self.tr("Press to refresh the list of shelves")) self.refreshButton.setEnabled(False) self.vcs = vcs self.__hgClient = vcs.getClient() self.__resetUI() if self.__hgClient: self.process = None else: self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) self.__contextMenu = QMenu() self.__unshelveAct = self.__contextMenu.addAction( self.tr("Restore selected shelve"), self.__unshelve) self.__deleteAct = self.__contextMenu.addAction( self.tr("Delete selected shelves"), self.__deleteShelves) self.__contextMenu.addAction(self.tr("Delete all shelves"), self.__cleanupShelves) def closeEvent(self, e): """ Protected slot implementing a close event handler. @param e close event (QCloseEvent) """ if self.__hgClient: if self.__hgClient.isExecuting(): self.__hgClient.cancel() else: if self.process is not None and \ self.process.state() != QProcess.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.__position = self.pos() e.accept() def show(self): """ Public slot to show the dialog. """ if not self.__position.isNull(): self.move(self.__position) self.__resetUI() super(HgShelveBrowserDialog, self).show() def __resetUI(self): """ Private method to reset the user interface. """ self.shelveList.clear() def __resizeColumnsShelves(self): """ Private method to resize the shelve list columns. """ self.shelveList.header().resizeSections(QHeaderView.ResizeToContents) self.shelveList.header().setStretchLastSection(True) def __generateShelveEntry(self, name, age, message, fileStatistics, totals): """ Private method to generate the shelve items. @param name name of the shelve (string) @param age age of the shelve (string) @param message shelve message (string) @param fileStatistics per file change statistics (tuple of four strings with file name, number of changes, number of added lines and number of deleted lines) @param totals overall statistics (tuple of three strings with number of changed files, number of added lines and number of deleted lines) """ itm = QTreeWidgetItem(self.shelveList, [name, age, message]) itm.setData(0, self.__fileStatisticsRole, fileStatistics) itm.setData(0, self.__totalStatisticsRole, totals) def __getShelveEntries(self): """ Private method to retrieve the list of shelves. """ self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) QApplication.processEvents() QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() self.buf = [] self.errors.clear() self.intercept = False args = self.vcs.initCommand("shelve") args.append("--list") args.append("--stat") if self.__hgClient: self.inputGroup.setEnabled(False) self.inputGroup.hide() out, err = self.__hgClient.runcommand(args) self.buf = out.splitlines(True) if err: self.__showError(err) self.__processBuffer() self.__finish() else: self.process.kill() self.process.setWorkingDirectory(self.repodir) self.inputGroup.setEnabled(True) self.inputGroup.show() self.process.start('hg', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.').format('hg')) def start(self, projectDir): """ Public slot to start the hg shelve command. @param projectDir name of the project directory (string) """ self.errorGroup.hide() QApplication.processEvents() self.__projectDir = projectDir # find the root of the repo self.repodir = self.__projectDir while not os.path.isdir(os.path.join(self.repodir, self.vcs.adminDir)): self.repodir = os.path.dirname(self.repodir) if os.path.splitdrive(self.repodir)[1] == os.sep: return self.activateWindow() self.raise_() self.shelveList.clear() self.__started = True self.__getShelveEntries() def __procFinished(self, exitCode, exitStatus): """ Private slot connected to the finished signal. @param exitCode exit code of the process (integer) @param exitStatus exit status of the process (QProcess.ExitStatus) """ self.__processBuffer() self.__finish() def __finish(self): """ Private slot called when the process finished or the user pressed the button. """ if self.process is not None and \ self.process.state() != QProcess.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) QApplication.restoreOverrideCursor() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.inputGroup.setEnabled(False) self.inputGroup.hide() self.refreshButton.setEnabled(True) def __processBuffer(self): """ Private method to process the buffered output of the hg shelve command. """ lastWasFileStats = False firstLine = True itemData = {} for line in self.buf: if firstLine: name, line = line.split("(", 1) age, message = line.split(")", 1) itemData["name"] = name.strip() itemData["age"] = age.strip() itemData["message"] = message.strip() itemData["files"] = [] firstLine = False elif '|' in line: # file stats: foo.py | 3 ++- file, changes = line.strip().split("|", 1) if changes.strip().endswith(("+", "-")): total, addDelete = changes.strip().split(None, 1) additions = str(addDelete.count("+")) deletions = str(addDelete.count("-")) else: total = changes.strip() additions = '0' deletions = '0' itemData["files"].append((file, total, additions, deletions)) lastWasFileStats = True elif lastWasFileStats: # summary line # 2 files changed, 15 insertions(+), 1 deletions(-) total, added, deleted = line.strip().split(",", 2) total = total.split()[0] added = added.split()[0] deleted = deleted.split()[0] itemData["summary"] = (total, added, deleted) self.__generateShelveEntry(itemData["name"], itemData["age"], itemData["message"], itemData["files"], itemData["summary"]) lastWasFileStats = False firstLine = True itemData = {} self.__resizeColumnsShelves() if self.__started: self.shelveList.setCurrentItem(self.shelveList.topLevelItem(0)) self.__started = False def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal. It reads the output of the process and inserts it into a buffer. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): line = str(self.process.readLine(), self.vcs.getEncoding(), 'replace') self.buf.append(line) def __readStderr(self): """ Private slot to handle the readyReadStandardError signal. It reads the error output of the process and inserts it into the error pane. """ if self.process is not None: s = str(self.process.readAllStandardError(), self.vcs.getEncoding(), 'replace') self.__showError(s) def __showError(self, out): """ Private slot to show some error. @param out error to be shown (string) """ self.errorGroup.show() self.errors.insertPlainText(out) self.errors.ensureCursorVisible() @pyqtSlot(QAbstractButton) def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.cancelled = True if self.__hgClient: self.__hgClient.cancel() else: self.__finish() elif button == self.refreshButton: self.on_refreshButton_clicked() @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_shelveList_currentItemChanged(self, current, previous): """ Private slot called, when the current item of the shelve list changes. @param current reference to the new current item (QTreeWidgetItem) @param previous reference to the old current item (QTreeWidgetItem) """ self.statisticsList.clear() if current: for dataSet in current.data(0, self.__fileStatisticsRole): QTreeWidgetItem(self.statisticsList, list(dataSet)) self.statisticsList.header().resizeSections( QHeaderView.ResizeToContents) self.statisticsList.header().setStretchLastSection(True) totals = current.data(0, self.__totalStatisticsRole) self.filesLabel.setText( self.tr("%n file(s) changed", None, int(totals[0]))) self.insertionsLabel.setText( self.tr("%n line(s) inserted", None, int(totals[1]))) self.deletionsLabel.setText( self.tr("%n line(s) deleted", None, int(totals[2]))) else: self.filesLabel.setText("") self.insertionsLabel.setText("") self.deletionsLabel.setText("") @pyqtSlot(QPoint) def on_shelveList_customContextMenuRequested(self, pos): """ Private slot to show the context menu of the shelve list. @param pos position of the mouse pointer (QPoint) """ selectedItemsCount = len(self.shelveList.selectedItems()) self.__unshelveAct.setEnabled(selectedItemsCount == 1) self.__deleteAct.setEnabled(selectedItemsCount > 0) self.__contextMenu.popup(self.mapToGlobal(pos)) @pyqtSlot() def on_refreshButton_clicked(self): """ Private slot to refresh the list of shelves. """ self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.inputGroup.setEnabled(True) self.inputGroup.show() self.refreshButton.setEnabled(False) self.start(self.__projectDir) def on_passwordCheckBox_toggled(self, isOn): """ Private slot to handle the password checkbox toggled. @param isOn flag indicating the status of the check box (boolean) """ if isOn: self.input.setEchoMode(QLineEdit.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the mercurial process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.errorGroup.show() self.process.write(input) self.passwordCheckBox.setChecked(False) self.input.clear() def on_input_returnPressed(self): """ Private slot to handle the press of the return key in the input field. """ self.intercept = True self.on_sendButton_clicked() def keyPressEvent(self, evt): """ Protected slot to handle a key press event. @param evt the key press event (QKeyEvent) """ if self.intercept: self.intercept = False evt.accept() return super(HgShelveBrowserDialog, self).keyPressEvent(evt) def __unshelve(self): """ Private slot to restore the selected shelve of changes. """ itm = self.shelveList.selectedItems()[0] if itm is not None: name = itm.text(self.NameColumn) self.vcs.getExtensionObject("shelve")\ .hgUnshelve(self.__projectDir, shelveName=name) self.on_refreshButton_clicked() def __deleteShelves(self): """ Private slot to delete the selected shelves. """ shelveNames = [] for itm in self.shelveList.selectedItems(): shelveNames.append(itm.text(self.NameColumn)) if shelveNames: self.vcs.getExtensionObject("shelve")\ .hgDeleteShelves(self.__projectDir, shelveNames=shelveNames) self.on_refreshButton_clicked() def __cleanupShelves(self): """ Private slot to delete all shelves. """ self.vcs.getExtensionObject("shelve")\ .hgCleanupShelves(self.__projectDir) self.on_refreshButton_clicked()
class SnapWidget(QWidget, Ui_SnapWidget): """ Class implementing the snapshot widget. """ ModeFullscreen = 0 ModeScreen = 1 ModeRectangle = 2 ModeFreehand = 3 ModeEllipse = 4 def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget (QWidget) """ super(SnapWidget, self).__init__(parent) self.setupUi(self) self.saveButton.setIcon(UI.PixmapCache.getIcon("fileSaveAs.png")) self.takeButton.setIcon(UI.PixmapCache.getIcon("cameraPhoto.png")) self.copyButton.setIcon(UI.PixmapCache.getIcon("editCopy.png")) self.copyPreviewButton.setIcon(UI.PixmapCache.getIcon("editCopy.png")) self.setWindowIcon(UI.PixmapCache.getIcon("ericSnap.png")) self.modeCombo.addItem(self.tr("Fullscreen"), SnapWidget.ModeFullscreen) self.modeCombo.addItem(self.tr("Rectangular Selection"), SnapWidget.ModeRectangle) self.modeCombo.addItem(self.tr("Ellipical Selection"), SnapWidget.ModeEllipse) self.modeCombo.addItem(self.tr("Freehand Selection"), SnapWidget.ModeFreehand) if QApplication.desktop().screenCount() > 1: self.modeCombo.addItem(self.tr("Current Screen"), SnapWidget.ModeScreen) self.__mode = int(Preferences.Prefs.settings.value("Snapshot/Mode", 0)) index = self.modeCombo.findData(self.__mode) if index == -1: index = 0 self.modeCombo.setCurrentIndex(index) self.__delay = int( Preferences.Prefs.settings.value("Snapshot/Delay", 0)) self.delaySpin.setValue(self.__delay) if PYQT_VERSION_STR >= "5.0.0": from PyQt5.QtCore import QStandardPaths picturesLocation = QStandardPaths.writableLocation( QStandardPaths.PicturesLocation) else: from PyQt5.QtGui import QDesktopServices picturesLocation = QDesktopServices.storageLocation( QDesktopServices.PicturesLocation) self.__filename = Preferences.Prefs.settings.value( "Snapshot/Filename", os.path.join(picturesLocation, self.tr("snapshot") + "1.png")) self.__grabber = None self.__snapshot = QPixmap() self.__savedPosition = QPoint() self.__modified = False self.__locale = QLocale() self.__grabberWidget = QWidget(None, Qt.X11BypassWindowManagerHint) self.__grabberWidget.move(-10000, -10000) self.__grabberWidget.installEventFilter(self) self.__initFileFilters() self.__initShortcuts() self.preview.startDrag.connect(self.__dragSnapshot) from .SnapshotTimer import SnapshotTimer self.__grabTimer = SnapshotTimer() self.__grabTimer.timeout.connect(self.__grabTimerTimeout) self.__updateTimer = QTimer() self.__updateTimer.setSingleShot(True) self.__updateTimer.timeout.connect(self.__updatePreview) self.__updateCaption() self.takeButton.setFocus() def __initFileFilters(self): """ Private method to define the supported image file filters. """ filters = { 'bmp': self.tr("Windows Bitmap File (*.bmp)"), 'gif': self.tr("Graphic Interchange Format File (*.gif)"), 'ico': self.tr("Windows Icon File (*.ico)"), 'jpg': self.tr("JPEG File (*.jpg)"), 'mng': self.tr("Multiple-Image Network Graphics File (*.mng)"), 'pbm': self.tr("Portable Bitmap File (*.pbm)"), 'pcx': self.tr("Paintbrush Bitmap File (*.pcx)"), 'pgm': self.tr("Portable Graymap File (*.pgm)"), 'png': self.tr("Portable Network Graphics File (*.png)"), 'ppm': self.tr("Portable Pixmap File (*.ppm)"), 'sgi': self.tr("Silicon Graphics Image File (*.sgi)"), 'svg': self.tr("Scalable Vector Graphics File (*.svg)"), 'tga': self.tr("Targa Graphic File (*.tga)"), 'tif': self.tr("TIFF File (*.tif)"), 'xbm': self.tr("X11 Bitmap File (*.xbm)"), 'xpm': self.tr("X11 Pixmap File (*.xpm)"), } outputFormats = [] writeFormats = QImageWriter.supportedImageFormats() for writeFormat in writeFormats: try: outputFormats.append(filters[bytes(writeFormat).decode()]) except KeyError: pass outputFormats.sort() self.__outputFilter = ';;'.join(outputFormats) self.__defaultFilter = filters['png'] def __initShortcuts(self): """ Private method to initialize the keyboard shortcuts. """ self.__quitShortcut = QShortcut(QKeySequence(QKeySequence.Quit), self, self.close) self.__copyShortcut = QShortcut(QKeySequence(QKeySequence.Copy), self, self.copyButton.animateClick) self.__quickSaveShortcut = QShortcut(QKeySequence(Qt.Key_Q), self, self.__quickSave) self.__save1Shortcut = QShortcut(QKeySequence(QKeySequence.Save), self, self.saveButton.animateClick) self.__save2Shortcut = QShortcut(QKeySequence(Qt.Key_S), self, self.saveButton.animateClick) self.__grab1Shortcut = QShortcut(QKeySequence(QKeySequence.New), self, self.takeButton.animateClick) self.__grab2Shortcut = QShortcut(QKeySequence(Qt.Key_N), self, self.takeButton.animateClick) self.__grab3Shortcut = QShortcut(QKeySequence(Qt.Key_Space), self, self.takeButton.animateClick) def __quickSave(self): """ Private slot to save the snapshot bypassing the file selection dialog. """ if not self.__snapshot.isNull(): while os.path.exists(self.__filename): self.__autoIncFilename() if self.__saveImage(self.__filename): self.__modified = False self.__autoIncFilename() self.__updateCaption() @pyqtSlot() def on_saveButton_clicked(self): """ Private slot to save the snapshot. """ if not self.__snapshot.isNull(): while os.path.exists(self.__filename): self.__autoIncFilename() fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( self, self.tr("Save Snapshot"), self.__filename, self.__outputFilter, self.__defaultFilter, E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) if not fileName: return ext = QFileInfo(fileName).suffix() if not ext: ex = selectedFilter.split("(*")[1].split(")")[0] if ex: fileName += ex if self.__saveImage(fileName): self.__modified = False self.__filename = fileName self.__autoIncFilename() self.__updateCaption() def __saveImage(self, fileName): """ Private method to save the snapshot. @param fileName name of the file to save to (string) @return flag indicating success (boolean) """ if QFileInfo(fileName).exists(): res = E5MessageBox.yesNo( self, self.tr("Save Snapshot"), self.tr("<p>The file <b>{0}</b> already exists." " Overwrite it?</p>").format(fileName), icon=E5MessageBox.Warning) if not res: return False file = QFile(fileName) if not file.open(QFile.WriteOnly): E5MessageBox.warning( self, self.tr("Save Snapshot"), self.tr("Cannot write file '{0}:\n{1}.").format( fileName, file.errorString())) return False ok = self.__snapshot.save(file) file.close() if not ok: E5MessageBox.warning( self, self.tr("Save Snapshot"), self.tr("Cannot write file '{0}:\n{1}.").format( fileName, file.errorString())) return ok def __autoIncFilename(self): """ Private method to auto-increment the file name. """ # Extract the file name name = os.path.basename(self.__filename) # If the name contains a number, then increment it. numSearch = QRegExp("(^|[^\\d])(\\d+)") # We want to match as far left as possible, and when the number is # at the start of the name. # Does it have a number? start = numSearch.lastIndexIn(name) if start != -1: # It has a number, increment it. start = numSearch.pos(2) # Only the second group is of interest. numAsStr = numSearch.capturedTexts()[2] number = "{0:0{width}d}".format(int(numAsStr) + 1, width=len(numAsStr)) name = name[:start] + number + name[start + len(numAsStr):] else: # no number start = name.rfind('.') if start != -1: # has a '.' somewhere, e.g. it has an extension name = name[:start] + '1' + name[start:] else: # no extension, just tack it on to the end name += '1' self.__filename = os.path.join(os.path.dirname(self.__filename), name) self.__updateCaption() @pyqtSlot() def on_takeButton_clicked(self): """ Private slot to take a snapshot. """ self.__mode = self.modeCombo.itemData(self.modeCombo.currentIndex()) self.__delay = self.delaySpin.value() self.__savedPosition = self.pos() self.hide() if self.__delay: self.__grabTimer.start(self.__delay) else: QTimer.singleShot(200, self.__startUndelayedGrab) def __grabTimerTimeout(self): """ Private slot to perform a delayed grab operation. """ if self.__mode == SnapWidget.ModeRectangle: self.__grabRectangle() elif self.__mode == SnapWidget.ModeEllipse: self.__grabEllipse() elif self.__mode == SnapWidget.ModeFreehand: self.__grabFreehand() else: self.__performGrab() def __startUndelayedGrab(self): """ Private slot to perform an undelayed grab operation. """ if self.__mode == SnapWidget.ModeRectangle: self.__grabRectangle() elif self.__mode == SnapWidget.ModeEllipse: self.__grabEllipse() elif self.__mode == SnapWidget.ModeFreehand: self.__grabFreehand() else: if Globals.isMacPlatform(): self.__performGrab() else: self.__grabberWidget.show() self.__grabberWidget.grabMouse(Qt.CrossCursor) def __grabRectangle(self): """ Private method to grab a rectangular screen region. """ from .SnapshotRegionGrabber import SnapshotRegionGrabber self.__grabber = SnapshotRegionGrabber( mode=SnapshotRegionGrabber.Rectangle) self.__grabber.grabbed.connect(self.__captured) def __grabEllipse(self): """ Private method to grab an elliptical screen region. """ from .SnapshotRegionGrabber import SnapshotRegionGrabber self.__grabber = SnapshotRegionGrabber( mode=SnapshotRegionGrabber.Ellipse) self.__grabber.grabbed.connect(self.__captured) def __grabFreehand(self): """ Private method to grab a non-rectangular screen region. """ from .SnapshotFreehandGrabber import SnapshotFreehandGrabber self.__grabber = SnapshotFreehandGrabber() self.__grabber.grabbed.connect(self.__captured) def __performGrab(self): """ Private method to perform a screen grab other than a selected region. """ self.__grabberWidget.releaseMouse() self.__grabberWidget.hide() self.__grabTimer.stop() if self.__mode == SnapWidget.ModeFullscreen: desktop = QApplication.desktop() if qVersion() >= "5.0.0": self.__snapshot = QApplication.screens()[0].grabWindow( desktop.winId(), desktop.x(), desktop.y(), desktop.width(), desktop.height()) else: self.__snapshot = QPixmap.grabWindow(desktop.winId(), desktop.x(), desktop.y(), desktop.width(), desktop.height()) elif self.__mode == SnapWidget.ModeScreen: desktop = QApplication.desktop() screenId = desktop.screenNumber(QCursor.pos()) geom = desktop.screenGeometry(screenId) x = geom.x() y = geom.y() if qVersion() >= "5.0.0": self.__snapshot = QApplication.screens()[0].grabWindow( desktop.winId(), x, y, geom.width(), geom.height()) else: self.__snapshot = QPixmap.grabWindow(desktop.winId(), x, y, geom.width(), geom.height()) else: self.__snapshot = QPixmap() self.__redisplay() self.__modified = True self.__updateCaption() def __redisplay(self): """ Private method to redisplay the window. """ self.__updatePreview() QApplication.restoreOverrideCursor() if not self.__savedPosition.isNull(): self.move(self.__savedPosition) self.show() self.raise_() self.saveButton.setEnabled(not self.__snapshot.isNull()) self.copyButton.setEnabled(not self.__snapshot.isNull()) self.copyPreviewButton.setEnabled(not self.__snapshot.isNull()) @pyqtSlot() def on_copyButton_clicked(self): """ Private slot to copy the snapshot to the clipboard. """ if not self.__snapshot.isNull(): QApplication.clipboard().setPixmap(QPixmap(self.__snapshot)) @pyqtSlot() def on_copyPreviewButton_clicked(self): """ Private slot to copy the snapshot preview to the clipboard. """ QApplication.clipboard().setPixmap(self.preview.pixmap()) def __captured(self, pixmap): """ Private slot to show a preview of the snapshot. @param pixmap pixmap of the snapshot (QPixmap) """ self.__grabber.close() self.__snapshot = QPixmap(pixmap) self.__grabber.grabbed.disconnect(self.__captured) self.__grabber = None self.__redisplay() self.__modified = True self.__updateCaption() def __updatePreview(self): """ Private slot to update the preview picture. """ self.preview.setToolTip( self.tr("Preview of the snapshot image ({0} x {1})").format( self.__locale.toString(self.__snapshot.width()), self.__locale.toString(self.__snapshot.height()))) self.preview.setPreview(self.__snapshot) self.preview.adjustSize() def resizeEvent(self, evt): """ Protected method handling a resizing of the window. @param evt resize event (QResizeEvent) """ self.__updateTimer.start(200) def __dragSnapshot(self): """ Private slot handling the dragging of the preview picture. """ drag = QDrag(self) mimeData = QMimeData() mimeData.setImageData(self.__snapshot) drag.setMimeData(mimeData) drag.setPixmap(self.preview.pixmap()) drag.exec_(Qt.CopyAction) def eventFilter(self, obj, evt): """ Public method to handle event for other objects. @param obj reference to the object (QObject) @param evt reference to the event (QEvent) @return flag indicating that the event should be filtered out (boolean) """ if obj == self.__grabberWidget and \ evt.type() == QEvent.MouseButtonPress: if QWidget.mouseGrabber() != self.__grabberWidget: return False if evt.button() == Qt.LeftButton: self.__performGrab() return False def closeEvent(self, evt): """ Protected method handling the close event. @param evt close event (QCloseEvent) """ if self.__modified: res = E5MessageBox.question( self, self.tr("eric6 Snapshot"), self.tr("""The application contains an unsaved snapshot."""), E5MessageBox.StandardButtons(E5MessageBox.Abort | E5MessageBox.Discard | E5MessageBox.Save)) if res == E5MessageBox.Abort: evt.ignore() return elif res == E5MessageBox.Save: self.on_saveButton_clicked() Preferences.Prefs.settings.setValue("Snapshot/Delay", self.delaySpin.value()) Preferences.Prefs.settings.setValue( "Snapshot/Mode", self.modeCombo.itemData(self.modeCombo.currentIndex())) Preferences.Prefs.settings.setValue("Snapshot/Filename", self.__filename) Preferences.Prefs.settings.sync() def __updateCaption(self): """ Private method to update the window caption. """ self.setWindowTitle("{0}[*] - {1}".format( os.path.basename(self.__filename), self.tr("eric6 Snapshot"))) self.setWindowModified(self.__modified) self.pathNameEdit.setText(os.path.dirname(self.__filename))
class SvnLogBrowserDialog(QWidget, Ui_SvnLogBrowserDialog): """ Class implementing a dialog to browse the log history. """ def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(SvnLogBrowserDialog, self).__init__(parent) self.setupUi(self) self.__position = QPoint() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.filesTree.headerItem().setText(self.filesTree.columnCount(), "") self.filesTree.header().setSortIndicator(0, Qt.AscendingOrder) self.vcs = vcs self.__initData() self.fromDate.setDisplayFormat("yyyy-MM-dd") self.toDate.setDisplayFormat("yyyy-MM-dd") self.__resetUI() self.__messageRole = Qt.UserRole self.__changesRole = Qt.UserRole + 1 self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) self.rx_sep1 = QRegExp('\\-+\\s*') self.rx_sep2 = QRegExp('=+\\s*') self.rx_rev1 = QRegExp( 'rev ([0-9]+): ([^|]*) \| ([^|]*) \| ([0-9]+) .*') # "rev" followed by one or more decimals followed by a colon followed # anything up to " | " (twice) followed by one or more decimals # followed by anything self.rx_rev2 = QRegExp( 'r([0-9]+) \| ([^|]*) \| ([^|]*) \| ([0-9]+) .*') # "r" followed by one or more decimals followed by " | " followed # anything up to " | " (twice) followed by one or more decimals # followed by anything self.rx_flags1 = QRegExp( r""" ([ADM])\s(.*)\s+\(\w+\s+(.*):([0-9]+)\)\s*""") # three blanks followed by A or D or M followed by path followed by # path copied from followed by copied from revision self.rx_flags2 = QRegExp(' ([ADM]) (.*)\\s*') # three blanks followed by A or D or M followed by path self.flags = { 'A': self.tr('Added'), 'D': self.tr('Deleted'), 'M': self.tr('Modified'), 'R': self.tr('Replaced'), } def __initData(self): """ Private method to (re-)initialize some data. """ self.__maxDate = QDate() self.__minDate = QDate() self.__filterLogsEnabled = True self.buf = [] # buffer for stdout self.diff = None self.__started = False self.__lastRev = 0 def closeEvent(self, e): """ Protected slot implementing a close event handler. @param e close event (QCloseEvent) """ if self.process is not None and \ self.process.state() != QProcess.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.__position = self.pos() e.accept() def show(self): """ Public slot to show the dialog. """ if not self.__position.isNull(): self.move(self.__position) self.__resetUI() super(SvnLogBrowserDialog, self).show() def __resetUI(self): """ Private method to reset the user interface. """ self.fromDate.setDate(QDate.currentDate()) self.toDate.setDate(QDate.currentDate()) self.fieldCombo.setCurrentIndex( self.fieldCombo.findText(self.tr("Message"))) self.limitSpinBox.setValue( self.vcs.getPlugin().getPreferences("LogLimit")) self.stopCheckBox.setChecked( self.vcs.getPlugin().getPreferences("StopLogOnCopy")) self.logTree.clear() self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) def __resizeColumnsLog(self): """ Private method to resize the log tree columns. """ self.logTree.header().resizeSections(QHeaderView.ResizeToContents) self.logTree.header().setStretchLastSection(True) def __resortLog(self): """ Private method to resort the log tree. """ self.logTree.sortItems(self.logTree.sortColumn(), self.logTree.header().sortIndicatorOrder()) def __resizeColumnsFiles(self): """ Private method to resize the changed files tree columns. """ self.filesTree.header().resizeSections(QHeaderView.ResizeToContents) self.filesTree.header().setStretchLastSection(True) def __resortFiles(self): """ Private method to resort the changed files tree. """ sortColumn = self.filesTree.sortColumn() self.filesTree.sortItems(1, self.filesTree.header().sortIndicatorOrder()) self.filesTree.sortItems(sortColumn, self.filesTree.header().sortIndicatorOrder()) def __generateLogItem(self, author, date, message, revision, changedPaths): """ Private method to generate a log tree entry. @param author author info (string) @param date date info (string) @param message text of the log message (list of strings) @param revision revision info (string) @param changedPaths list of dictionary objects containing info about the changed files/directories @return reference to the generated item (QTreeWidgetItem) """ msg = [] for line in message: msg.append(line.strip()) itm = QTreeWidgetItem(self.logTree) itm.setData(0, Qt.DisplayRole, int(revision)) itm.setData(1, Qt.DisplayRole, author) itm.setData(2, Qt.DisplayRole, date) itm.setData(3, Qt.DisplayRole, " ".join(msg)) itm.setData(0, self.__messageRole, message) itm.setData(0, self.__changesRole, changedPaths) itm.setTextAlignment(0, Qt.AlignRight) itm.setTextAlignment(1, Qt.AlignLeft) itm.setTextAlignment(2, Qt.AlignLeft) itm.setTextAlignment(3, Qt.AlignLeft) itm.setTextAlignment(4, Qt.AlignLeft) try: self.__lastRev = int(revision) except ValueError: self.__lastRev = 0 return itm def __generateFileItem(self, action, path, copyFrom, copyRev): """ Private method to generate a changed files tree entry. @param action indicator for the change action ("A", "D" or "M") @param path path of the file in the repository (string) @param copyFrom path the file was copied from (None, string) @param copyRev revision the file was copied from (None, string) @return reference to the generated item (QTreeWidgetItem) """ itm = QTreeWidgetItem(self.filesTree, [ self.flags[action], path, copyFrom, copyRev, ]) itm.setTextAlignment(3, Qt.AlignRight) return itm def __getLogEntries(self, startRev=None): """ Private method to retrieve log entries from the repository. @param startRev revision number to start from (integer, string) """ self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) QApplication.processEvents() QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() self.intercept = False self.process.kill() self.buf = [] self.cancelled = False self.errors.clear() args = [] args.append('log') self.vcs.addArguments(args, self.vcs.options['global']) self.vcs.addArguments(args, self.vcs.options['log']) args.append('--verbose') args.append('--limit') args.append('{0:d}'.format(self.limitSpinBox.value())) if startRev is not None: args.append('--revision') args.append('{0}:0'.format(startRev)) if self.stopCheckBox.isChecked(): args.append('--stop-on-copy') args.append(self.fname) self.process.setWorkingDirectory(self.dname) self.inputGroup.setEnabled(True) self.inputGroup.show() self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.').format('svn')) def start(self, fn, isFile=False): """ Public slot to start the svn log command. @param fn filename to show the log for (string) @keyparam isFile flag indicating log for a file is to be shown (boolean) """ self.sbsCheckBox.setEnabled(isFile) self.sbsCheckBox.setVisible(isFile) self.errorGroup.hide() QApplication.processEvents() self.__initData() self.filename = fn self.dname, self.fname = self.vcs.splitPath(fn) self.activateWindow() self.raise_() self.logTree.clear() self.__started = True self.__getLogEntries() def __procFinished(self, exitCode, exitStatus): """ Private slot connected to the finished signal. @param exitCode exit code of the process (integer) @param exitStatus exit status of the process (QProcess.ExitStatus) """ self.__processBuffer() self.__finish() def __finish(self): """ Private slot called when the process finished or the user pressed the button. """ if self.process is not None and \ self.process.state() != QProcess.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) QApplication.restoreOverrideCursor() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.inputGroup.setEnabled(False) self.inputGroup.hide() def __processBuffer(self): """ Private method to process the buffered output of the svn log command. """ noEntries = 0 log = {"message": []} changedPaths = [] for s in self.buf: if self.rx_rev1.exactMatch(s): log["revision"] = self.rx_rev.cap(1) log["author"] = self.rx_rev.cap(2) log["date"] = self.rx_rev.cap(3) # number of lines is ignored elif self.rx_rev2.exactMatch(s): log["revision"] = self.rx_rev2.cap(1) log["author"] = self.rx_rev2.cap(2) log["date"] = self.rx_rev2.cap(3) # number of lines is ignored elif self.rx_flags1.exactMatch(s): changedPaths.append({ "action": self.rx_flags1.cap(1).strip(), "path": self.rx_flags1.cap(2).strip(), "copyfrom_path": self.rx_flags1.cap(3).strip(), "copyfrom_revision": self.rx_flags1.cap(4).strip(), }) elif self.rx_flags2.exactMatch(s): changedPaths.append({ "action": self.rx_flags2.cap(1).strip(), "path": self.rx_flags2.cap(2).strip(), "copyfrom_path": "", "copyfrom_revision": "", }) elif self.rx_sep1.exactMatch(s) or self.rx_sep2.exactMatch(s): if len(log) > 1: self.__generateLogItem(log["author"], log["date"], log["message"], log["revision"], changedPaths) dt = QDate.fromString(log["date"], Qt.ISODate) if not self.__maxDate.isValid() and \ not self.__minDate.isValid(): self.__maxDate = dt self.__minDate = dt else: if self.__maxDate < dt: self.__maxDate = dt if self.__minDate > dt: self.__minDate = dt noEntries += 1 log = {"message": []} changedPaths = [] else: if s.strip().endswith(":") or not s.strip(): continue else: log["message"].append(s) self.__resizeColumnsLog() self.__resortLog() if self.__started: self.logTree.setCurrentItem(self.logTree.topLevelItem(0)) self.__started = False if noEntries < self.limitSpinBox.value() and not self.cancelled: self.nextButton.setEnabled(False) self.limitSpinBox.setEnabled(False) self.__filterLogsEnabled = False self.fromDate.setMinimumDate(self.__minDate) self.fromDate.setMaximumDate(self.__maxDate) self.fromDate.setDate(self.__minDate) self.toDate.setMinimumDate(self.__minDate) self.toDate.setMaximumDate(self.__maxDate) self.toDate.setDate(self.__maxDate) self.__filterLogsEnabled = True self.__filterLogs() def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal. It reads the output of the process and inserts it into a buffer. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): line = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') self.buf.append(line) def __readStderr(self): """ Private slot to handle the readyReadStandardError signal. It reads the error output of the process and inserts it into the error pane. """ if self.process is not None: self.errorGroup.show() s = str(self.process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(s) self.errors.ensureCursorVisible() def __diffRevisions(self, rev1, rev2): """ Private method to do a diff of two revisions. @param rev1 first revision number (integer) @param rev2 second revision number (integer) """ if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked(): self.vcs.svnSbsDiff(self.filename, revisions=(str(rev1), str(rev2))) else: if self.diff is None: from .SvnDiffDialog import SvnDiffDialog self.diff = SvnDiffDialog(self.vcs) self.diff.show() self.diff.raise_() self.diff.start(self.filename, [rev1, rev2]) def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.cancelled = True self.__finish() @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_logTree_currentItemChanged(self, current, previous): """ Private slot called, when the current item of the log tree changes. @param current reference to the new current item (QTreeWidgetItem) @param previous reference to the old current item (QTreeWidgetItem) """ if current is not None: self.messageEdit.clear() for line in current.data(0, self.__messageRole): self.messageEdit.append(line.strip()) self.filesTree.clear() changes = current.data(0, self.__changesRole) if len(changes) > 0: for change in changes: self.__generateFileItem(change["action"], change["path"], change["copyfrom_path"], change["copyfrom_revision"]) self.__resizeColumnsFiles() self.__resortFiles() self.diffPreviousButton.setEnabled( current != self.logTree.topLevelItem( self.logTree.topLevelItemCount() - 1)) @pyqtSlot() def on_logTree_itemSelectionChanged(self): """ Private slot called, when the selection has changed. """ self.diffRevisionsButton.setEnabled( len(self.logTree.selectedItems()) == 2) @pyqtSlot() def on_nextButton_clicked(self): """ Private slot to handle the Next button. """ if self.__lastRev > 1: self.__getLogEntries(self.__lastRev - 1) @pyqtSlot() def on_diffPreviousButton_clicked(self): """ Private slot to handle the Diff to Previous button. """ itm = self.logTree.currentItem() if itm is None: self.diffPreviousButton.setEnabled(False) return rev2 = int(itm.text(0)) itm = self.logTree.topLevelItem( self.logTree.indexOfTopLevelItem(itm) + 1) if itm is None: self.diffPreviousButton.setEnabled(False) return rev1 = int(itm.text(0)) self.__diffRevisions(rev1, rev2) @pyqtSlot() def on_diffRevisionsButton_clicked(self): """ Private slot to handle the Compare Revisions button. """ items = self.logTree.selectedItems() if len(items) != 2: self.diffRevisionsButton.setEnabled(False) return rev2 = int(items[0].text(0)) rev1 = int(items[1].text(0)) self.__diffRevisions(min(rev1, rev2), max(rev1, rev2)) @pyqtSlot(QDate) def on_fromDate_dateChanged(self, date): """ Private slot called, when the from date changes. @param date new date (QDate) """ self.__filterLogs() @pyqtSlot(QDate) def on_toDate_dateChanged(self, date): """ Private slot called, when the from date changes. @param date new date (QDate) """ self.__filterLogs() @pyqtSlot(str) def on_fieldCombo_activated(self, txt): """ Private slot called, when a new filter field is selected. @param txt text of the selected field (string) """ self.__filterLogs() @pyqtSlot(str) def on_rxEdit_textChanged(self, txt): """ Private slot called, when a filter expression is entered. @param txt filter expression (string) """ self.__filterLogs() def __filterLogs(self): """ Private method to filter the log entries. """ if self.__filterLogsEnabled: from_ = self.fromDate.date().toString("yyyy-MM-dd") to_ = self.toDate.date().addDays(1).toString("yyyy-MM-dd") txt = self.fieldCombo.currentText() if txt == self.tr("Author"): fieldIndex = 1 searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive) elif txt == self.tr("Revision"): fieldIndex = 0 txt = self.rxEdit.text() if txt.startswith("^"): searchRx = QRegExp("^\s*{0}".format(txt[1:]), Qt.CaseInsensitive) else: searchRx = QRegExp(txt, Qt.CaseInsensitive) else: fieldIndex = 3 searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive) currentItem = self.logTree.currentItem() for topIndex in range(self.logTree.topLevelItemCount()): topItem = self.logTree.topLevelItem(topIndex) if topItem.text(2) <= to_ and topItem.text(2) >= from_ and \ searchRx.indexIn(topItem.text(fieldIndex)) > -1: topItem.setHidden(False) if topItem is currentItem: self.on_logTree_currentItemChanged(topItem, None) else: topItem.setHidden(True) if topItem is currentItem: self.messageEdit.clear() self.filesTree.clear() @pyqtSlot(bool) def on_stopCheckBox_clicked(self, checked): """ Private slot called, when the stop on copy/move checkbox is clicked. @param checked flag indicating the checked state (boolean) """ self.vcs.getPlugin().setPreferences("StopLogOnCopy", self.stopCheckBox.isChecked()) self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) def on_passwordCheckBox_toggled(self, isOn): """ Private slot to handle the password checkbox toggled. @param isOn flag indicating the status of the check box (boolean) """ if isOn: self.input.setEchoMode(QLineEdit.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.errorGroup.show() self.process.write(input) self.passwordCheckBox.setChecked(False) self.input.clear() def on_input_returnPressed(self): """ Private slot to handle the press of the return key in the input field. """ self.intercept = True self.on_sendButton_clicked() def keyPressEvent(self, evt): """ Protected slot to handle a key press event. @param evt the key press event (QKeyEvent) """ if self.intercept: self.intercept = False evt.accept() return super(SvnLogBrowserDialog, self).keyPressEvent(evt)
class E5PassivePopup(QFrame): """ Class implementing dialog-like popup that displays messages without interrupting the user. @signal clicked emitted to indicate a mouse button click """ Boxed = 0 Custom = 128 clicked = pyqtSignal((), (QPoint, )) def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget (QWidget) """ super(E5PassivePopup, self).__init__(None) self.__popupStyle = DEFAULT_POPUP_TYPE self.__msgView = None self.__topLayout = None self.__hideDelay = DEFAULT_POPUP_TIME self.__hideTimer = QTimer(self) self.__autoDelete = False self.__fixedPosition = QPoint() self.setWindowFlags(POPUP_FLAGS) self.setFrameStyle(QFrame.Box | QFrame.Plain) self.setLineWidth(2) self.__hideTimer.timeout.connect(self.hide) self.clicked.connect(self.hide) self.__customData = {} # dictionary to store some custom data def setView(self, child): """ Public method to set the message view. @param child reference to the widget to set as the message view (QWidget) """ self.__msgView = child self.__topLayout = QVBoxLayout(self) self.__topLayout.addWidget(self.__msgView) self.__topLayout.activate() def view(self): """ Public method to get a reference to the message view. @return reference to the message view (QWidget) """ return self.__msgView def setVisible(self, visible): """ Public method to show or hide the popup. @param visible flag indicating the visibility status (boolean) """ if not visible: super(E5PassivePopup, self).setVisible(visible) return if self.size() != self.sizeHint(): self.resize(self.sizeHint()) if self.__fixedPosition.isNull(): self.__positionSelf() else: self.move(self.__fixedPosition) super(E5PassivePopup, self).setVisible(True) delay = self.__hideDelay if delay < 0: delay = DEFAULT_POPUP_TIME if delay > 0: self.__hideTimer.start(delay) def show(self, p=None): """ Public slot to show the popup. @param p position for the popup (QPoint) """ if p is not None: self.__fixedPosition = p super(E5PassivePopup, self).show() def setTimeout(self, delay): """ Public method to set the delay for the popup is removed automatically. Setting the delay to 0 disables the timeout. If you're doing this, you may want to connect the clicked() signal to the hide() slot. Setting the delay to -1 makes it use the default value. @param delay value for the delay in milliseconds (integer) """ self.__hideDelay = delay if self.__hideTimer.isActive(): if delay: if delay == -1: delay = DEFAULT_POPUP_TIME self.__hideTimer.start(delay) else: self.__hideTimer.stop() def timeout(self): """ Public method to get the delay before the popup is removed automatically. @return the delay before the popup is removed automatically (integer) """ return self.__hideDelay def mouseReleaseEvent(self, evt): """ Protected method to handle a mouse release event. @param evt reference to the mouse event (QMouseEvent) """ self.clicked.emit() self.clicked.emit(evt.pos()) def hideEvent(self, evt): """ Protected method to handle the hide event. @param evt reference to the hide event (QHideEvent) """ self.__hideTimer.stop() def __defaultArea(self): """ Private method to determine the default rectangle to be passed to moveNear(). @return default rectangle (QRect) """ return QRect(100, 100, 200, 200) def __positionSelf(self): """ Private method to position the popup. """ self.__moveNear(self.__defaultArea()) def __moveNear(self, target): """ Private method to move the popup to be adjacent to the specified rectangle. @param target rectangle to be placed at (QRect) """ pos = self.__calculateNearbyPoint(target) self.move(pos.x(), pos.y()) def __calculateNearbyPoint(self, target): """ Private method to calculate the position to place the popup near the specified rectangle. @param target rectangle to be placed at (QRect) @return position to place the popup (QPoint) """ pos = target.topLeft() x = pos.x() y = pos.y() w = self.minimumSizeHint().width() h = self.minimumSizeHint().height() if qVersionTuple() >= (5, 10, 0): r = (QApplication.screenAt(QPoint(x + w // 2, y + h // 2)).geometry()) else: r = QApplication.desktop().screenGeometry( QPoint(x + w // 2, y + h // 2)) if x < r.center().x(): x += target.width() else: x -= w # It's apparently trying to go off screen, so display it ALL at the # bottom. if (y + h) > r.bottom(): y = r.bottom() - h if (x + w) > r.right(): x = r.right() - w if y < r.top(): y = r.top() if x < r.left(): x = r.left() return QPoint(x, y) def setCustomData(self, key, data): """ Public method to set some custom data. @param key key for the custom data @type str @param data data to be stored @type any """ self.__customData[key] = data def getCustomData(self, key): """ Public method to get some custom data. @param key key for the custom data @type str @return stored data @rtype any """ return self.__customData[key]
class QToolWindowRollupBarArea(QRollupBar): def __init__(self, manager, parent=None): super().__init__(parent) self.manager = manager self.tabDragCanStart = False self.areaDragCanStart = False self.topWidget = manager self.areaDragStart = QPoint() self.areaDraggable = True self.setRollupsClosable(True) self.rollupCloseRequested.connect(self.closeRollup) currentLayout = self.layout() if currentLayout: if self.manager.config.setdefault(QTWM_AREA_IMAGE_HANDLE, False): horizontalLayout = QHBoxLayout() horizontalLayout.setSpacing(0) horizontalLayout.addStretch() self.topWidget = QLabel(self) self.topWidget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.topWidget.setFixedHeight(16) horizontalLayout.addWidget(self.topWidget) currentLayout.insertLayout(0, horizontalLayout) corner_img = QPixmap() corner_img.load( manager.config.setdefault( QTWM_DROPTARGET_COMBINE, "./QToolWindowManager/resources/drag_handle.png")) self.topWidget.setPixmap(corner_img) else: self.topWidget = QDragger(self) self.topWidget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.topWidget.setFixedHeight(8) currentLayout.insertWidget(0, self.topWidget) self.topWidget.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.topWidget.setCursor(QtCore.Qt.OpenHandCursor) self.topWidget.installEventFilter(self) self.setRollupsReorderable(True) print("QToolWindowRollupBarArea.__init__", parent) def addToolWindow(self, toolWindow, index=-1): self.addToolWindows([toolWindow], index) def addToolWindows(self, toolWindows, index=-1): for toolWindow in toolWindows: if index < 0: self.addWidget(toolWindow) else: self.insertWidget(toolWindow, index) def removeToolWindow(self, toolWindow): self.removeWidget(toolWindow) def toolWindows(self): result = [] for i in range(0, self.count()): result.append(self.widget(i)) return result def saveState(self): result = {} result["type"] = "rollup" objectNames = [] collapsedObjects = [] for i in range(0, self.count()): name = self.widget(i).objectName() if not name: print("cannot save state of tool window without object name") else: frame = self.rollupAt(i) if frame.collapsed: collapsedObjects.append(name) objectNames.append(name) result["objectNames"] = objectNames result["collapsedObjects"] = collapsedObjects return result def restoreState(self, data, stateFormat): collapsed = data["collapsedObjects"] for objectNameValue in data["objectNames"]: objectName = objectNameValue if not objectName: continue found = False for toolWindow in self.manager.toolWindows(): if toolWindow.objectName() == objectName: self.addToolWindow(toolWindow) if objectName in collapsed: frame = self.rollupAt(self.count() - 1) frame.setCollapsed(True) found = True break if not found: print("tool window with name '%s' not found" % objectName) def adjustDragVisuals(self): if self.count() == 1 and self.manager.isFloatingWrapper( self.parentWidget()): self.rollupAt(0).getDragHandler().setHidden(True) elif self.count() >= 1: self.rollupAt(0).getDragHandler().setHidden(False) if self.count() == 1: self.topWidget.setHidden(True) else: self.topWidget.setHidden(False) if self.manager.config.setdefault( QTWM_RETITLE_WRAPPER, True) and self.manager.isFloatingWrapper( self.parentWidget()) and self.count() == 1: w = self.wrapper() if w: w.getWidget().setWindowTitle(self.widget(0).windowTitle()) else: w = self.wrapper() if w: w.getWidget().setWindowTitle(qApp.applicationName()) def getWidget(self): return self def switchAutoHide(self, newValue): return True def tabBar(self): return super().tabBar(self) def palette(self): return super().palette() def clear(self): super().clear() def rect(self): return super().rect() def size(self): return super().size() def count(self): return super().count() def widget(self, index): return super().widget(index) def deleteLater(self, index): super().deleteLater(index) def width(self): return super().width() def height(self): return super().height() def geometry(self): return super().geometry() def hide(self): super().hide() def parent(self): return super().parent() def setParent(self, parent): super().setParent(parent) def indexOf(self, w): return super().indexOf(w) def parentWidget(self): return super().parentWidget() def mapFromGlobal(self, pos): return super().mapFromGlobal(pos) def mapToGlobal(self, pos): return super().mapToGlobal(pos) def setCurrentWidget(self, w): pass def mapCombineDropAreaFromGlobal(self, pos): return self.tabBar().mapFromGlobal(pos) def combineAreaRect(self): return self.getDropTarget().rect() def combineSubWidgetRect(self, index): rollup = self.rollupAt(index) if rollup: newPos = rollup.geometry() newPos.moveTop(newPos.y() + self.getDropTarget().pos().y()) return newPos return QRect() def subWidgetAt(self, pos): return self.rollupIndexAt(pos) def areaType(self): return QTWMWrapperAreaType.watRollups def eventFilter(self, o, e): if self.isDragHandle(o) or o == self.topWidget: if e.type() == QEvent.MouseButtonPress: if o == self.topWidget: if self.areaDraggable: self.areaDragCanStart = True self.areaDragStart = e.pos() else: self.tabDragCanStart = True if self.manager.isMainWrapper(self.parentWidget()): self.areaDragCanStart = False if self.count() == 1: self.tabDragCanStart = False elif e.type() == QEvent.MouseButtonRelease: self.areaDragCanStart = False self.tabDragCanStart = False elif e.type() == QEvent.MouseMove: if self.tabDragCanStart: if self.rect().contains(self.mapFromGlobal(e.globalPos())): return super().eventFilter(o, e) if qApp.mouseButtons() != QtCore.Qt.LeftButton: return super().eventFilter(o, e) toolWindow = None for i in range(0, self.count()): if self.getDragHandleAt(i) == o: toolWindow = self.widget(i) self.tabDragCanStart = False releaseEvent = QMouseEvent(QEvent.MouseButtonRelease, e.pos(), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier) qApp.sendEvent(o, releaseEvent) self.manager.startDrag([toolWindow], self) self.releaseMouse() elif self.areaDragCanStart: if qApp.mouseButtons( ) != QtCore.Qt.LeftButton and not self.areaDragStart.isNull( ): toolWindows = [] for i in range(0, self.count()): toolWindow = self.widget(i) toolWindows.append(toolWindow) releaseEvent = QMouseEvent(QEvent.MouseButtonRelease, e.pos(), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier) qApp.sendEvent(o, releaseEvent) self.manager.startDrag(toolWindows, self) self.releaseMouse() super().eventFilter(o, e) def closeRollup(self, index): self.manager.releaseToolWindow(self.widget(index), True) def mouseReleaseEvent(self, e): if e.button() == QtCore.Qt.RightButton: if self.topWidget.rect().contains(e.pos()): swap = QAction("Swap to Tabs", self) e.accept() swap.triggered.connect(self.swapToRollup) menu = QMenu(self) menu.addAction(swap) menu.exec_( self.mapToGlobal(QPoint(e.pos().x(), e.pos().y() + 10))) super().mouseReleaseEvent(e) def swapToRollup(self): self.manager.swapAreaType(self, QTWMWrapperAreaType.watTabs) def setDraggable(self, draggable): self.areaDraggable = draggable if draggable: self.topWidget.setCursor(QtCore.Qt.OpenHandCursor) else: self.topWidget.setCursor(QtCore.Qt.ArrowCursor)
class ImageViewer(QGraphicsView, QObject): def __init__(self, parent=None): super(ImageViewer, self).__init__(parent) self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) #self.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setDragMode(QGraphicsView.ScrollHandDrag) #self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) #self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) self._scene = ImageViewerScene(self) self.setScene(self._scene) self._create_grid() self._create_grid_lines() self._pixmap = None self._selection_mode = SELECTION_MODE.NONE # polygon selection _polygon_guide_line_pen = QPen(QtGui.QColor(235, 72, 40)) _polygon_guide_line_pen.setWidth(2) _polygon_guide_line_pen.setStyle(QtCore.Qt.DotLine) self._polygon_guide_line = QGraphicsLineItem() self._polygon_guide_line.setVisible(False) self._polygon_guide_line.setPen(_polygon_guide_line_pen) self._scene.addItem(self._polygon_guide_line) self._current_polygon = None # rectangle selection self._box_origin = QPoint() self._box_picker = QRubberBand(QRubberBand.Rectangle, self) # free selection self._current_free_path = None self._is_drawing = False self._last_point_drawn = QPoint() self._current_label = None @property def current_label(self): return self._current_label @current_label.setter def current_label(self, value): self._current_label = value @property def pixmap(self) -> ImagePixmap: return self._pixmap @pixmap.setter def pixmap(self, value: QPixmap): self.selection_mode = SELECTION_MODE.NONE self.resetTransform() if self.pixmap: self._scene.removeItem(self._pixmap) self.remove_annotations() self._pixmap = ImagePixmap() self._pixmap.setPixmap(value) self._pixmap.setOffset(-value.width() / 2, -value.height() / 2) self._pixmap.setTransformationMode(QtCore.Qt.SmoothTransformation) self._pixmap.signals.hoverEnterEventSgn.connect( self.pixmap_hoverEnterEvent_slot) self._pixmap.signals.hoverLeaveEventSgn.connect( self.pixmap_hoverLeaveEvent_slot) self._pixmap.signals.hoverMoveEventSgn.connect( self.pixmap_hoverMoveEvent_slot) self._scene.addItem(self._pixmap) # rect=self._scene.addRect(QtCore.QRectF(0,0,100,100), QtGui.QPen(QtGui.QColor("red"))) # rect.setZValue(1.0) self.fit_to_window() @property def selection_mode(self): return self._selection_mode @selection_mode.setter def selection_mode(self, value): self._polygon_guide_line.hide() self._current_polygon = None self._current_free_path = None self._is_drawing = value == SELECTION_MODE.FREE if value == SELECTION_MODE.NONE: self.enable_items(True) else: self.enable_items(False) self._selection_mode = value def remove_annotations(self): for item in self._scene.items(): if isinstance(item, EditableBox): self._scene.removeItem(item) elif isinstance(item, EditablePolygon): item.delete_polygon() def remove_annotations_by_label(self, label_name): for item in self._scene.items(): if isinstance(item, EditableBox): if item.label and item.label.name == label_name: self._scene.removeItem(item) elif isinstance(item, EditablePolygon): if item.label and item.label.name == label_name: item.delete_polygon() def enable_items(self, value): for item in self._scene.items(): if isinstance(item, EditablePolygon) or isinstance( item, EditableBox): item.setEnabled(value) def _create_grid(self): gridSize = 15 backgroundPixmap = QtGui.QPixmap(gridSize * 2, gridSize * 2) #backgroundPixmap.fill(QtGui.QColor("white")) backgroundPixmap.fill(QtGui.QColor(20, 20, 20)) #backgroundPixmap.fill(QtGui.QColor("powderblue")) painter = QtGui.QPainter(backgroundPixmap) #backgroundColor=QtGui.QColor("palegoldenrod") #backgroundColor=QtGui.QColor(237,237,237) backgroundColor = QtGui.QColor(0, 0, 0) painter.fillRect(0, 0, gridSize, gridSize, backgroundColor) painter.fillRect(gridSize, gridSize, gridSize, gridSize, backgroundColor) painter.end() self._scene.setBackgroundBrush(QtGui.QBrush(backgroundPixmap)) def _create_grid_lines(self): pen_color = QColor(255, 255, 255, 255) pen = QPen(pen_color) pen.setWidth(2) pen.setStyle(QtCore.Qt.DotLine) self.vline = QGraphicsLineItem() self.vline.setVisible(False) self.vline.setPen(pen) self.hline = QGraphicsLineItem() self.hline.setVisible(False) self.hline.setPen(pen) self._scene.addItem(self.vline) self._scene.addItem(self.hline) def wheelEvent(self, event: QWheelEvent): adj = (event.angleDelta().y() / 120) * 0.1 self.scale(1 + adj, 1 + adj) def fit_to_window(self): """Fit image within view.""" if not self.pixmap or not self._pixmap.pixmap(): return #self._pixmap.setTransformationMode(QtCore.Qt.SmoothTransformation) self.fitInView(self._pixmap, QtCore.Qt.KeepAspectRatio) def show_guide_lines(self): if self.hline and self.vline: self.hline.show() self.vline.show() def hide_guide_lines(self): if self.hline and self.vline: self.hline.hide() self.vline.hide() def pixmap_hoverEnterEvent_slot(self): self.show_guide_lines() def pixmap_hoverLeaveEvent_slot(self): self.hide_guide_lines() def pixmap_hoverMoveEvent_slot(self, evt: QGraphicsSceneHoverEvent, x, y): bbox: QRect = self._pixmap.boundingRect() offset = QPointF(bbox.width() / 2, bbox.height() / 2) self.vline.setLine(x, -offset.y(), x, bbox.height() - offset.y()) self.vline.setZValue(1) self.hline.setLine(-offset.x(), y, bbox.width() - offset.x(), y) self.hline.setZValue(1) def mouseMoveEvent(self, evt: QtGui.QMouseEvent) -> None: if self.selection_mode == SELECTION_MODE.BOX: if not self._box_origin.isNull(): self._box_picker.setGeometry( QRect(self._box_origin, evt.pos()).normalized()) elif self.selection_mode == SELECTION_MODE.POLYGON: if self._current_polygon: if self._current_polygon.count > 0: last_point: QPointF = self._current_polygon.last_point self._polygon_guide_line.setZValue(1) self._polygon_guide_line.show() mouse_pos = self.mapToScene(evt.pos()) self._polygon_guide_line.setLine(last_point.x(), last_point.y(), mouse_pos.x(), mouse_pos.y()) else: self._polygon_guide_line.hide() elif self.selection_mode == SELECTION_MODE.FREE and evt.buttons( ) and QtCore.Qt.LeftButton: if self._current_free_path: painter: QPainterPath = self._current_free_path.path() self._last_point_drawn = self.mapToScene(evt.pos()) painter.lineTo(self._last_point_drawn) self._current_free_path.setPath(painter) super(ImageViewer, self).mouseMoveEvent(evt) def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None: if evt.buttons() == QtCore.Qt.LeftButton: if self.selection_mode == SELECTION_MODE.BOX: self.setDragMode(QGraphicsView.NoDrag) self._box_origin = evt.pos() self._box_picker.setGeometry(QRect(self._box_origin, QSize())) self._box_picker.show() elif self._selection_mode == SELECTION_MODE.POLYGON: pixmap_rect: QRectF = self._pixmap.boundingRect() new_point = self.mapToScene(evt.pos()) # consider only the points intothe image if pixmap_rect.contains(new_point): if self._current_polygon is None: self._current_polygon = EditablePolygon() self._current_polygon.signals.deleted.connect( self.delete_polygon_slot) self._scene.addItem(self._current_polygon) self._current_polygon.addPoint(new_point) else: self._current_polygon.addPoint(new_point) elif self._selection_mode == SELECTION_MODE.FREE: # start drawing new_point = self.mapToScene(evt.pos()) pixmap_rect: QRectF = self._pixmap.boundingRect() # consider only the points intothe image if pixmap_rect.contains(new_point): self.setDragMode(QGraphicsView.NoDrag) pen = QPen(QtGui.QColor(235, 72, 40)) pen.setWidth(10) self._last_point_drawn = new_point self._current_free_path = QGraphicsPathItem() self._current_free_path.setOpacity(0.6) self._current_free_path.setPen(pen) painter = QPainterPath() painter.moveTo(self._last_point_drawn) self._current_free_path.setPath(painter) self._scene.addItem(self._current_free_path) else: self.setDragMode(QGraphicsView.ScrollHandDrag) super(ImageViewer, self).mousePressEvent(evt) def mouseReleaseEvent(self, evt: QtGui.QMouseEvent) -> None: if evt.button() == QtCore.Qt.LeftButton: if self.selection_mode == SELECTION_MODE.BOX: roi: QRect = self._box_picker.geometry() roi: QRectF = self.mapToScene(roi).boundingRect() pixmap_rect = self._pixmap.boundingRect() self._box_picker.hide() if pixmap_rect == roi.united(pixmap_rect): rect = EditableBox(roi) rect.label = self.current_label self._scene.addItem(rect) self.selection_mode = SELECTION_MODE.NONE self.setDragMode(QGraphicsView.ScrollHandDrag) elif self.selection_mode == SELECTION_MODE.FREE and self._current_free_path: # create polygon self._current_free_path: QGraphicsPathItem path_rect = self._current_free_path.boundingRect() pixmap_rect = self._pixmap.boundingRect() if pixmap_rect == path_rect.united(pixmap_rect): path = self._current_free_path.path() path_polygon = EditablePolygon() path_polygon.label = self.current_label self._scene.addItem(path_polygon) for i in range(0, path.elementCount(), 10): x, y = path.elementAt(i).x, path.elementAt(i).y path_polygon.addPoint(QPointF(x, y)) self._scene.removeItem(self._current_free_path) self.selection_mode = SELECTION_MODE.NONE self.setDragMode(QGraphicsView.ScrollHandDrag) super(ImageViewer, self).mouseReleaseEvent(evt) def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: if self._current_polygon and event.key() == QtCore.Qt.Key_Space: points = self._current_polygon.points self._current_polygon.label = self.current_label self._current_polygon = None self.selection_mode = SELECTION_MODE.NONE self._polygon_guide_line.hide() self.setDragMode(QGraphicsView.ScrollHandDrag) super(ImageViewer, self).keyPressEvent(event) def delete_polygon_slot(self, polygon: EditablePolygon): self._current_polygon = None self.selection_mode = SELECTION_MODE.NONE self._polygon_guide_line.hide()
class Viewer(QGraphicsView): def __init__(self): QGraphicsView.__init__(self) self.setGeometry(QRect(100, 100, 800, 600)) self.setWindowTitle("PIHDL Graphics Window") # Create a QGraphicsScene which this view looks at self.scene = QGraphicsScene(self) self.scene.setSceneRect(QRectF()) self.setScene(self.scene) # Customize QGraphicsView self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setInteractive(False) self.scale(1, -1) # Flips around the Y axis # Use OpenGL http://ralsina.me/stories/BBS53.html # self.setViewport(QtOpenGL.QGLWidget()) self.rubberBand = QRubberBand(QRubberBand.Rectangle, self) self.pen = QPen(QtCore.Qt.black, 0) self.portpen = QPen(PORT_COLOR, 3) self.portpen.setCosmetic(True) # Makes constant width self.portfont = QtGui.QFont('Arial', pointSize=14) self.portfontcolor = PORT_COLOR self.subportpen = QPen(SUBPORT_COLOR, 3) self.subportpen.setCosmetic(True) # Makes constant width self.subportfont = QtGui.QFont('Arial', pointSize=14) self.subportfontcolor = SUBPORT_COLOR # Tracking ports # Various status variables self._mousePressed = None self._rb_origin = QPoint() self.zoom_factor_total = 1 # Grid variables self.gridpen = QPen(QtCore.Qt.black, 0) self.gridpen.setStyle(QtCore.Qt.DotLine) self.gridpen.setDashPattern([1, 4]) self.gridpen.setColor(QtGui.QColor(0, 0, 0, 125)) # self.gridpen = QPen(QtCore.Qt.black, 1) # self.gridpen.setCosmetic(True) # Makes constant width # self.gridlinesx = [self.scene.addLine(-10,-10,10,10, self.gridpen) for n in range(100)] # self.gridlinesy = [self.scene.addLine(-10,-10,10,10, self.gridpen) for n in range(100)] self.initialize() def itemsBoundingRect_nogrid(self): self.remove_grid() r = self.scene.itemsBoundingRect() self.create_grid() return r def add_polygons(self, polygons, color='#A8F22A', alpha=1): qcolor = QColor() qcolor.setNamedColor(color) qcolor.setAlphaF(alpha) for points in polygons: qpoly = QPolygonF([QPointF(p[0], p[1]) for p in points]) scene_poly = self.scene.addPolygon(qpoly) scene_poly.setBrush(qcolor) scene_poly.setPen(self.pen) sr = self.itemsBoundingRect_nogrid() ymax = sr.top() xmin = sr.left() width = sr.width() height = sr.height() self.scene.setSceneRect( QRectF(xmin - 2 * width, ymax - 2 * height, width * 5, height * 5)) def reset_view(self): self.fitInView(self.itemsBoundingRect_nogrid(), Qt.KeepAspectRatio) self.update_grid() def add_port(self, port, is_subport=False): if (port.width is None) or (port.width == 0): x, y = port.midpoint cs = 1 # cross size pn = QPointF(x, y + cs) ps = QPointF(x, y - cs) pe = QPointF(x + cs, y) pw = QPointF(x - cs, y) qline1 = self.scene.addLine(QLineF(pn, ps)) qline2 = self.scene.addLine(QLineF(pw, pe)) port_shapes = [qline1, qline2] else: point1, point2 = port.endpoints point1 = QPointF(point1[0], point1[1]) point2 = QPointF(point2[0], point2[1]) qline = self.scene.addLine(QLineF(point1, point2)) arrow_points = np.array([[0, 0], [10, 0], [6, 4], [6, 2], [0, 2] ]) / (40) * port.width arrow_qpoly = QPolygonF( [QPointF(p[0], p[1]) for p in arrow_points]) port_scene_poly = self.scene.addPolygon(arrow_qpoly) port_scene_poly.setRotation(port.orientation) port_scene_poly.moveBy(port.midpoint[0], port.midpoint[1]) port_shapes = [qline, port_scene_poly] qtext = self.scene.addText(str(port.name), self.portfont) port_items = port_shapes + [qtext] rad = port.orientation * np.pi / 180 x, y = port.endpoints[0] * 1 / 4 + port.endpoints[ 1] * 3 / 4 + np.array([np.cos(rad), np.sin(rad)]) * port.width / 8 # x,y = port.midpoint[0], port.midpoint[1] # x,y = x - qtext.boundingRect().width()/2, y - qtext.boundingRect().height()/2 qtext.setPos(QPointF(x, y)) qtext.setFlag(QGraphicsItem.ItemIgnoresTransformations) if not is_subport: [shape.setPen(self.portpen) for shape in port_shapes] qtext.setDefaultTextColor(self.portfontcolor) self.portitems += port_items else: [shape.setPen(self.subportpen) for shape in port_shapes] qtext.setDefaultTextColor(self.subportfontcolor) self.subportitems += port_items # self.portlabels.append(qtext) def add_aliases(self, aliases): for name, ref in aliases.items(): qtext = self.scene.addText(str(name), self.portfont) x, y = ref.center qtext.setPos(QPointF(x, y)) qtext.setFlag(QGraphicsItem.ItemIgnoresTransformations) self.aliasitems += [qtext] # x,y = port.midpoint[0], port.midpoint[1] # x,y = x - qtext.boundingRect().width()/2, y - qtext.boundingRect().height()/2 def set_port_visibility(self, visible=True): for item in self.portitems: item.setVisible(visible) self.ports_visible = visible def set_subport_visibility(self, visible=True): for item in self.subportitems: item.setVisible(visible) self.subports_visible = visible def set_alias_visibility(self, visible=True): for item in self.aliasitems: item.setVisible(visible) self.aliases_visible = visible def initialize(self): self.scene.clear() self.polygons = {} self.portitems = [] self.subportitems = [] self.aliasitems = [] self.aliases_visible = True self.ports_visible = True self.subports_visible = True self.create_grid() self.update_grid() #============================================================================== # Grid creation #============================================================================== def update_grid(self): grid_pixels = 50 grid_snaps = [1, 2, 4] # Number of pixels in the viewer view_width, view_height = self.rect().width(), self.rect().height() # Rectangle of viewport in terms of scene coordinates r = self.mapToScene(self.rect()).boundingRect() width, height = r.width(), r.height() xmin, ymin, xmax, ymax = r.x(), r.y(), r.x() + width, r.y() + height grid_size = grid_pixels * (width / view_width) exponent = np.floor(np.log10(grid_size)) digits = round(grid_size / 10**(exponent), 2) digits_snapped = min(grid_snaps, key=lambda x: abs(x - digits)) grid_size_snapped = digits_snapped * 10**(exponent) # Starting coordinates for gridlines x = round((xmin - 2 * width) / grid_size_snapped) * grid_size_snapped y = round((ymin - 2 * height) / grid_size_snapped) * grid_size_snapped # print('\n xmin = %s, xmax = %s, ymin = %s, ymax = %s' % (xmin, xmax, ymin, ymax)) # print('Starting at x = %s' % x) # print('Starting at y = %s' % y) for gl in self.gridlinesx: gl.setLine(x, -1e10, x, 1e10) x += grid_size_snapped for gl in self.gridlinesy: gl.setLine(-1e10, y, 1e10, y) y += grid_size_snapped def create_grid(self): self.gridlinesx = [ self.scene.addLine(-10, -10, 10, 10, self.gridpen) for n in range(200) ] self.gridlinesy = [ self.scene.addLine(-10, -10, 10, 10, self.gridpen) for n in range(200) ] self.update_grid() def remove_grid(self): for gl in self.gridlinesx + self.gridlinesy: self.scene.removeItem(gl) self.gridlinesx == [] self.gridlinesy == [] #============================================================================== # Mousewheel zoom, taken from http://stackoverflow.com/a/29026916 #============================================================================== def wheelEvent(self, event): # Zoom Factor zoom_percentage = 1.4 # Set Anchors self.setTransformationAnchor(QGraphicsView.NoAnchor) self.setResizeAnchor(QGraphicsView.NoAnchor) # Save the scene pos oldPos = self.mapToScene(event.pos()) # Zoom mousewheel_rotation = event.angleDelta().y( ) # Typically = 120 on most mousewheels zoom_factor = zoom_percentage**(mousewheel_rotation / 120) zoom_factor = np.clip(zoom_factor, 0.5, 2.0) # Check to make sure we're not overzoomed actual_rect = self.mapToScene(self.rect()) bbox_size = actual_rect[0] - actual_rect[2] actual_width = abs(bbox_size.x()) actual_height = abs(bbox_size.y()) max_width = abs(self.scene.sceneRect().x() * 3) max_height = abs(self.scene.sceneRect().y() * 3) min_width = 1 min_height = 1 if ((actual_width > max_width) or (actual_height > max_height)) and (zoom_factor < 1): pass elif ((actual_width < min_width) or (actual_height < min_height)) and (zoom_factor > 1): pass else: self.zoom_view(zoom_factor) # Get the new position and move scene to old position newPos = self.mapToScene(event.pos()) delta = newPos - oldPos self.translate(delta.x(), delta.y()) self.update_grid() def zoom_view(self, zoom_factor): self.scale(zoom_factor, zoom_factor) self.zoom_factor_total *= zoom_factor def mousePressEvent(self, event): #============================================================================== # Zoom to rectangle, from # https://wiki.python.org/moin/PyQt/Selecting%20a%20region%20of%20a%20widget #============================================================================== if event.button() == Qt.RightButton: self._mousePressed = Qt.RightButton self._rb_origin = QPoint(event.pos()) self.rubberBand.setGeometry(QRect(self._rb_origin, QSize())) self.rubberBand.show() #============================================================================== # Mouse panning, taken from # http://stackoverflow.com/a/15043279 #============================================================================== elif event.button() == Qt.MidButton: self._mousePressed = Qt.MidButton self._mousePressedPos = event.pos() self.setCursor(QtCore.Qt.ClosedHandCursor) self._dragPos = event.pos() def mouseMoveEvent(self, event): if not self._rb_origin.isNull( ) and self._mousePressed == Qt.RightButton: self.rubberBand.setGeometry( QRect(self._rb_origin, event.pos()).normalized()) if self._mousePressed == Qt.MidButton: newPos = event.pos() diff = newPos - self._dragPos self._dragPos = newPos self.horizontalScrollBar().setValue( self.horizontalScrollBar().value() - diff.x()) self.verticalScrollBar().setValue( self.verticalScrollBar().value() - diff.y()) # event.accept() def mouseReleaseEvent(self, event): if event.button() == Qt.RightButton: self.rubberBand.hide() rb_rect = QRect(self._rb_origin, event.pos()) rb_center = rb_rect.center() rb_size = rb_rect.size() if abs(rb_size.width()) > 3 and abs(rb_size.height()) > 3: viewport_size = self.viewport().geometry().size() zoom_factor_x = abs(viewport_size.width() / rb_size.width()) zoom_factor_y = abs(viewport_size.height() / rb_size.height()) new_center = self.mapToScene(rb_center) zoom_factor = min(zoom_factor_x, zoom_factor_y) self.zoom_view(zoom_factor) self.centerOn(new_center) self.update_grid() if event.button() == Qt.MidButton: self.setCursor(Qt.ArrowCursor) self._mousePressed = None self.update_grid() def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.reset_view() if event.key() == Qt.Key_F1: self.set_alias_visibility(not self.aliases_visible) if event.key() == Qt.Key_F2: self.set_port_visibility(not self.ports_visible) if event.key() == Qt.Key_F3: self.set_subport_visibility(not self.subports_visible)
class OCRTranslator(QDialog): def __init__(self, parent=None, f=Qt.Tool | Qt.FramelessWindowHint | Qt.X11BypassWindowManagerHint): super(OCRTranslator, self).__init__(parent, f) self.desktopGeometry = self.getDesktopGeometry() self.setGeometry(self.desktopGeometry) self.setStyleSheet("background-color: #FFF;") self.setModal(True) self.setWindowOpacity(0.4) self.setCursor(Qt.CrossCursor) self.rubberBand = Selector(QRubberBand.Rectangle, self) self.rubberBand.setWindowOpacity(0.7) self.start, self.end = QPoint(), QPoint() self.hasSelected = False self.screenshot = QPixmap() self.shotfilename = None self.info = InfoPanel(self) self.info.setFixedSize(QSize(450, 100)) self.info.setGeometry(OCRTranslator.getDesktopGeometry().width() - 460, 0, 450, 100) self.info.show() def mousePressEvent(self, ev): if ev.button() == Qt.LeftButton: self.info.close() self.setCursor(Qt.CrossCursor) self.start = QPoint(ev.pos()) self.rubberBand.setGeometry(QRect(self.start, QSize())) self.rubberBand.show() def mouseMoveEvent(self, ev): if not self.start.isNull(): self.rubberBand.setGeometry(QRect(self.start, ev.pos()).normalized()) self.hasSelected = True def mouseReleaseEvent(self, ev): if ev.button() == Qt.LeftButton and self.hasSelected: self.end = QPoint(ev.pos()) self.setCursor(Qt.ArrowCursor) self.hasSelected = False def keyPressEvent(self, ev): if ev.key() == Qt.Key_Escape: self.close() elif ev.key() in [Qt.Key_Enter, Qt.Key_Return] and not self.start.isNull() and not self.end.isNull(): self.hide() QTimer().singleShot(500, self.takeScreenshot) def takeScreenshot(self): x = self.desktopGeometry.x() + self.start.x() y = self.desktopGeometry.y() + self.start.y() w = self.end.x() - self.start.x() h = self.end.y() - self.start.y() self.screenshot = qApp.screens()[0].grabWindow(qApp.desktop().winId(), x, y, w, h) self.shotfilename = os.path.join(self.getFilePath(), "www", "temp", strftime('%Y%m%d-%H%M%S')) + ".png" self.screenshot.save(self.shotfilename, "PNG", 100) if not self.shotfilename is None and type(self.screenshot) is QPixmap: self.openTranslator() @staticmethod def getDesktopGeometry(): totalWidth = 0 maxHeight = 0 minX = 0 screens = (qApp.screens()[i] for i in range(qApp.desktop().screenCount())) rects = [screen.geometry() for screen in screens] for rect in rects: totalWidth += rect.width() if rect.x() < minX: minX = rect.x() if rect.height() > maxHeight: maxHeight = rect.height() return QRect(minX, 0, totalWidth, maxHeight) @staticmethod def getFilePath(): if getattr(sys, 'frozen', False): return sys._MEIPASS return os.path.dirname(os.path.realpath(sys.argv[0])) def getAppFrameSize(self): viewWidth = self.screenshot.width() + 85 if viewWidth < 600: viewWidth = 600 viewHeight = self.screenshot.height() + 185 if viewHeight < 550: viewHeight = 550 return QSize(viewWidth, viewHeight) def webServer(self): if getattr(sys, 'frozen', False): os.chdir(sys._MEIPASS) for r, d, f in os.walk(sys._MEIPASS): os.chmod(r, 0o755) else: os.chdir(os.path.join(QFileInfo(__file__).absolutePath(), 'www')) httpd = HTTPServer((self.address, self.port), OCRHTTPHandler) httpd.serve_forever() def openTranslator(self): self.address = "127.0.0.1" self.port = randint(2000, 5000) self.t = Thread(target=self.webServer) self.t.daemon = True self.t.start() self.view = QWebEngineView() self.view.setWindowTitle("OCR Translator") self.view.setWindowIcon(QIcon(os.path.join(self.getFilePath(), "www", "img", "app-icon.png"))) self.view.setContextMenuPolicy(Qt.NoContextMenu) qryparam = urlencode({'img': self.shotfilename.split('\\').pop().split('/').pop()}) self.url = QUrl("http://" + self.address + ":" + str(self.port) + "/index.html#?" + qryparam) self.view.load(self.url) self.view.setMinimumSize(self.getAppFrameSize()) self.view.closeEvent = self.closeEvent self.view.show() def closeEvent(self, ev): qApp.quit()
class E5PassivePopup(QFrame): """ Class implementing dialog-like popup that displays messages without interrupting the user. """ Boxed = 0 Custom = 128 clicked = pyqtSignal((), (QPoint, )) def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget (QWidget) """ super(E5PassivePopup, self).__init__(None) self.__popupStyle = DEFAULT_POPUP_TYPE self.__msgView = None self.__topLayout = None self.__hideDelay = DEFAULT_POPUP_TIME self.__hideTimer = QTimer(self) self.__autoDelete = False self.__fixedPosition = QPoint() self.setWindowFlags(POPUP_FLAGS) self.setFrameStyle(QFrame.Box | QFrame.Plain) self.setLineWidth(2) self.__hideTimer.timeout.connect(self.hide) self.clicked.connect(self.hide) def setView(self, child): """ Public method to set the message view. @param child reference to the widget to set as the message view (QWidget) """ self.__msgView = child self.__topLayout = QVBoxLayout(self) self.__topLayout.addWidget(self.__msgView) self.__topLayout.activate() def view(self): """ Public method to get a reference to the message view. @return reference to the message view (QWidget) """ return self.__msgView def setVisible(self, visible): """ Public method to show or hide the popup. @param visible flag indicating the visibility status (boolean) """ if not visible: super(E5PassivePopup, self).setVisible(visible) return if self.size() != self.sizeHint(): self.resize(self.sizeHint()) if self.__fixedPosition.isNull(): self.__positionSelf() else: self.move(self.__fixedPosition) super(E5PassivePopup, self).setVisible(True) delay = self.__hideDelay if delay < 0: delay = DEFAULT_POPUP_TIME if delay > 0: self.__hideTimer.start(delay) def show(self, p=None): """ Public slot to show the popup. @param p position for the popup (QPoint) """ if p is not None: self.__fixedPosition = p super(E5PassivePopup, self).show() def setTimeout(self, delay): """ Public method to set the delay for the popup is removed automatically. Setting the delay to 0 disables the timeout. If you're doing this, you may want to connect the clicked() signal to the hide() slot. Setting the delay to -1 makes it use the default value. @param delay value for the delay in milliseconds (integer) """ self.__hideDelay = delay if self.__hideTimer.isActive(): if delay: if delay == -1: delay = DEFAULT_POPUP_TIME self.__hideTimer.start(delay) else: self.__hideTimer.stop() def timeout(self): """ Public method to get the delay before the popup is removed automatically. @return the delay before the popup is removed automatically (integer) """ return self.__hideDelay def mouseReleaseEvent(self, evt): """ Protected method to handle a mouse release event. @param evt reference to the mouse event (QMouseEvent) """ self.clicked.emit() self.clicked.emit(evt.pos()) def hideEvent(self, evt): """ Protected method to handle the hide event. @param evt reference to the hide event (QHideEvent) """ self.__hideTimer.stop() def __defaultArea(self): """ Private method to determine the default rectangle to be passed to moveNear(). @return default rectangle (QRect) """ return QRect(100, 100, 200, 200) def __positionSelf(self): """ Private method to position the popup. """ self.__moveNear(self.__defaultArea()) def __moveNear(self, target): """ Private method to move the popup to be adjacent to the specified rectangle. @param target rectangle to be placed at (QRect) """ pos = self.__calculateNearbyPoint(target) self.move(pos.x(), pos.y()) def __calculateNearbyPoint(self, target): """ Private method to calculate the position to place the popup near the specified rectangle. @param target rectangle to be placed at (QRect) @return position to place the popup (QPoint) """ pos = target.topLeft() x = pos.x() y = pos.y() w = self.minimumSizeHint().width() h = self.minimumSizeHint().height() r = QApplication.desktop().screenGeometry( QPoint(x + w // 2, y + h // 2)) if x < r.center().x(): x += target.width() else: x -= w # It's apparently trying to go off screen, so display it ALL at the # bottom. if (y + h) > r.bottom(): y = r.bottom() - h if (x + w) > r.right(): x = r.right() - w if y < r.top(): y = r.top() if x < r.left(): x = r.left() return QPoint(x, y)
class BoxInterpreter(QObject): rightClickReceived = pyqtSignal(object, QPoint) # list of indexes, global window coordinate of click leftClickReceived = pyqtSignal(object, QPoint) leftClickReleased = pyqtSignal(QRect) # boxAdded= pyqtSignal(object, object) # focusObjectChages= pyqtSignal(object, QPoint) cursorPositionChanged = pyqtSignal(object) deleteSelectedItemsSignal = pyqtSignal() # send the signal that we want to delete the currently selected item acceptBoxManipulation = True def __init__(self, navigationInterpreter, positionModel, BoxContr, widget): """ Class which interacts directly with the image scene :param navigationInterpreter: :param positionModel: :param BoxContr: :param widget: The main widget """ QObject.__init__(self) self.baseInterpret = navigationInterpreter self._posModel = positionModel self.rubberBand = RedRubberBand(QRubberBand.Rectangle, widget) self.boxController = BoxContr self.leftClickReleased.connect(BoxContr.addNewBox) self.rightClickReceived.connect(BoxContr.onChangedPos) # self.deleteSelectedItemsSignal.connect(BoxContr.deleteSelectedItems) self.origin = QPoint() self.originpos = object() def start(self): self.baseInterpret.start() def stop(self): self.baseInterpret.stop() def eventFilter(self, watched, event): pos = [int(i) for i in self._posModel.cursorPos] pos = [self._posModel.time] + pos + [self._posModel.channel] # Rectangles under the current point items = watched.scene().items(QPointF(*pos[1:3])) items = [el for el in items if isinstance(el, QGraphicsResizableRect)] # Keyboard interaction if event.type() == QEvent.KeyPress: # Switch selection if event.key() == Qt.Key_Space: # assert items[0]._hovering # items[0].setZValue(1) # for el in items: # print el.zValue() if len(items) > 1: items[-1].setZValue(items[0].zValue() + 1) items[0].setSelected(False) items[-1].setSelected(True) # items[0].setZero() if event.key() == Qt.Key_Control: QApplication.setOverrideCursor(Qt.OpenHandCursor) # #Delete element # if event.key()==Qt.Key_Delete: # self.deleteSelectedItemsSignal.emit() if event.type() == QEvent.KeyRelease: if event.key() == Qt.Key_Control: QApplication.restoreOverrideCursor() # Pressing mouse and menaging rubber band if event.type() == QEvent.MouseButtonPress: if event.button() == Qt.LeftButton: self.origin = QPoint(event.pos()) self.originpos = pos self.rubberBand.setGeometry(QRect(self.origin, QSize())) itemsall = watched.scene().items(QPointF(*pos[1:3])) itemsall = [el for el in itemsall if isinstance(el, ResizeHandle)] # if len(itemsall)==0: #show rubber band only if there is no rubbber band # self.rubberBand.show() modifiers = QApplication.keyboardModifiers() if ( modifiers != Qt.ControlModifier and modifiers != Qt.ShiftModifier and len(itemsall) == 0 ): # show rubber band if Ctrl is not pressed self.rubberBand.show() gPos = watched.mapToGlobal(event.pos()) self.leftClickReceived.emit(pos, gPos) if event.button() == Qt.RightButton: gPos = watched.mapToGlobal(event.pos()) self.rightClickReceived.emit(pos, gPos) if event.type() == QEvent.MouseMove: self.cursorPositionChanged.emit(event.pos()) if not self.origin.isNull(): self.rubberBand.setGeometry(QRect(self.origin, event.pos()).normalized()) if ( event.type() == QEvent.MouseButtonRelease and event.button() == Qt.LeftButton and self.rubberBand.isVisible() ): self.rubberBand.hide() self.leftClickReleased.emit(roi2rect(self.originpos[1:3], self._posModel.cursorPos[:2])) # Event is always forwarded to the navigation interpreter. return self.baseInterpret.eventFilter(watched, event)
class App(QWidget): lbls = [] letters = [] pad = 0 word = "" count = 0 i = 0 def __init__(self): super().__init__() self.initUI() def initUI(self): self.setFixedSize(800, 600) self.centre() self.setWindowTitle("Hangman") self.setWindowIcon(QIcon("ya.png")) self.labels() self.buttons() self.show() self.center = QPoint() self.rad = 50 self.lines = [] def paintEvent(self, event): lin = [QLineF(400, 150, 770, 150)] QWidget.paintEvent(self, event) painter = QPainter(self) painter.setPen(QPen(Qt.darkYellow, 6)) painter.drawLines(ln for ln in lin) painter.setPen(QPen(Qt.lightGray, 6)) painter.drawLines(el for el in self.lines) if not self.center.isNull(): painter.drawEllipse(self.center, self.rad, self.rad) def labels(self): self.word = hangmanqt.getWord() length = self.word.__len__() self.pad = szh = 120 sz = 720 / length if sz > szh: sz = szh mv = (780 - (sz + 3) * length) / 2 mvy = 10 for i in range(length): lbl = QPushButton(self) lbl.setDisabled(True) #lbl.setFont(QFont("Consolas", 35)) lbl.setStyleSheet("""QPushButton{ background-color: white; color: darkgray; border: 0px outset; border-color: black; border-radius: 10px; font-size: 30pt; }""") lbl.resize(sz, szh) lbl.move(mv, mvy) mv += sz + 5 self.lbls.append(lbl) lbl.show() def buttons(self): letters = list(string.ascii_lowercase + "-") sz = 70 mvy = self.pad + 25 mv = 5 for i in range(letters.__len__()): btn = QPushButton(letters[i], self) btn.resize(sz, sz) btn.move(mv, mvy) btn.setStyleSheet("""QPushButton{ background-color: white; text-align: center; color: darkgray; border: 3px solid; border-color: lightgray; border-radius: 10px; font-size: 30px; text-align: center; } QPushButton:hover{ background-color: lightgray; }""") mv += sz + 5 if mv > 350: mv = 5 if mvy > 450: mv = sz + 10 mvy += sz + 5 btn.show() #btn.clicked.connect(self.draw) btn.clicked.connect(self.buttonClicked) def draw(self): lines = [ QLineF(600, 160, 600, 200), QLineF(600, 300, 600, 310), QLineF(600, 310, 600, 450), QLineF(600, 310, 500, 370), QLineF(600, 310, 700, 370), QLineF(600, 450, 510, 550), QLineF(600, 450, 690, 550) ] if self.i == 1: self.center = QPoint(600, 250) self.lines.append(lines[self.i]) self.i += 1 self.update() if self.i == 7: num = 0 for i in self.lbls: i.setText(self.word[num]) num += 1 buttonReply = QMessageBox.question( self, 'Game over!', "You lose...\nRight answer: {}".format(self.word.upper()), QMessageBox.Ok) if buttonReply == QMessageBox.Ok: self.close() def buttonClicked(self): sender = self.sender() sender.setStyleSheet("""QPushButton{ background-color: darksalmon; text-align: center; color: darkgray; border: 3px solid; border-color: lightgray; border-radius: 10px; font-size: 30px; padding: 10%; }""") sender.setDisabled(True) numbers = hangmanqt.play(self.word, sender.text()) if numbers.__len__() == 0: self.draw() else: for i in numbers: self.lbls[i].setText(self.word[i]) self.letters.append(i) self.letters.sort() if self.letters.__len__() == self.lbls.__len__(): buttonReply = QMessageBox.question(self, 'Game over!', "You win!", QMessageBox.Ok) if buttonReply == QMessageBox.Ok: self.close() def centre(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft())