class CartPoleShape: def __init__(self): self._boundingBox = QRectF(-239 / 2, -216 / 2, 239, 216) self._cartBody = QRectF(self._boundingBox.left(), self._boundingBox.top() + 82, self._boundingBox.width(), 106) self._cartJointBody = QRectF( self._cartBody.center().x() - 53 / 2, self._boundingBox.top() + 53 / 2, 53, self._cartBody.top() - self._boundingBox.top() - 53 / 2) self._cartJointBall = QRectF( self._cartJointBody.center().x() - self._cartJointBody.width() / 2, self._cartJointBody.top() - self._cartJointBody.width() / 2, self._cartJointBody.width(), self._cartJointBody.width()) self._poleCenter = self._cartJointBall.center() self._leftGear = QRectF(self._boundingBox.left() + 43 - 26, self._boundingBox.top() + 188 - 26, 26 * 2, 26 * 2) self._leftGearJoint = QRectF(self._boundingBox.left() + 43 - 2, self._boundingBox.top() + 188 - 2, 4, 4) self._rightGear = QRectF(self._boundingBox.left() + 194 - 26, self._boundingBox.top() + 188 - 26, 26 * 2, 26 * 2) self._rightGearJoint = QRectF(self._boundingBox.left() + 194 - 2, self._boundingBox.top() + 188 - 2, 4, 4) def draw(self, qp, pen): qp.setPen(pen) qp.drawRect(self._cartBody) qp.drawLines([ QLineF(self._cartJointBody.topLeft(), self._cartJointBody.bottomLeft()), QLineF(self._cartJointBody.topRight(), self._cartJointBody.bottomRight()), QLineF(self._cartJointBody.bottomLeft(), self._cartJointBody.bottomRight()) ]) qp.drawArc(self._cartJointBall, 0, 180 * 16) qp.drawEllipse(self._leftGear) qp.drawEllipse(self._leftGearJoint) qp.drawEllipse(self._rightGear) qp.drawEllipse(self._rightGearJoint) # Computes the model matrix that moves the cart in center and has # size width def modelMatrix(self, center, width): return QTransform.fromScale(width / 239, width / 239) * QTransform.fromTranslate( -center.x(), -center.y())
def _rect(self, rect, image_index): irect = QRectF(rect) irect.translate(-self.rect().center()) irect.setTopLeft(irect.topLeft() * self.zoom[image_index]) irect.setBottomRight(irect.bottomRight() * self.zoom[image_index]) irect.translate(self.centers[image_index]) return irect
def draw_bg(self, qp, rect, text): path = QPainterPath() # add container path.addRoundedRect(rect, 4, 4) if self.isEnabled(): highlight_color, bg_color, text_color = ( self.settings['highlight'], self.settings['bg'], self.settings['text']) else: highlight_color, bg_color, text_color = ( self.settings['highlight_disabled'], self.settings['bg_disabled'], self.settings['text_disabled']) qp.setPen(QPen(highlight_color, 2)) qp.fillPath(path, bg_color) # add close button circle_size = rect.height() / 1.8 pen_size = 2 qp.setPen(QPen(text_color, pen_size, Qt.SolidLine)) rect = QRectF( rect.right() - circle_size - self.settings['padding-x'] / 2, rect.top() + (rect.height() - circle_size) / 2, circle_size, circle_size) path.addEllipse(rect) qp.drawPath(path) # draw cross inside_rect = QRectF(rect) inside_rect.adjust(pen_size, pen_size, -pen_size, -pen_size) qp.drawLine(inside_rect.topLeft(), inside_rect.bottomRight()) qp.drawLine(inside_rect.bottomLeft(), inside_rect.topRight()) self.close_rectangles[text] = rect
def _hitTest(self, rc: QRectF, mousePos: QPointF) -> Hit: maxdist = 4 if not rc.adjusted(-maxdist, -maxdist, maxdist, maxdist).contains(mousePos): return Hit.NoHit def dist(p1, p2): return (p1 - p2).manhattanLength() if dist(rc.topLeft(), mousePos) < maxdist: return Hit.TopLeft elif dist(rc.topRight(), mousePos) < maxdist: return Hit.TopRight elif dist(rc.bottomRight(), mousePos) < maxdist: return Hit.BottomRight elif dist(rc.bottomLeft(), mousePos) < maxdist: return Hit.BottomLeft elif abs(rc.left() - mousePos.x()) < maxdist: return Hit.Left elif abs(rc.right() - mousePos.x()) < maxdist: return Hit.Right elif abs(rc.top() - mousePos.y()) < maxdist: return Hit.Top elif abs(rc.bottom() - mousePos.y()) < maxdist: return Hit.Bottom elif rc.contains(mousePos): return Hit.Center else: return Hit.NoHit
def zoom(self, logAmount): scale = math.exp(logAmount) border = self.getViewportRect() mid = border.center() border.translate(-mid) border = QRectF(border.topLeft() * scale, border.bottomRight() * scale) border.translate(mid) self.moveViewRect(border.intersected(self.sceneRect()))
class MapObjectOutline(QGraphicsItem): def __init__(self, object, parent=None): super().__init__(parent) self.mObject = object self.mBoundingRect = QRectF() self.setZValue(1) # makes sure outlines are above labels def syncWithMapObject(self, renderer): pixelPos = renderer.pixelToScreenCoords_(self.mObject.position()) bounds = objectBounds(self.mObject, renderer) bounds.translate(-pixelPos) self.setPos(pixelPos + self.mObject.objectGroup().offset()) self.setRotation(self.mObject.rotation()) if (self.mBoundingRect != bounds): self.prepareGeometryChange() self.mBoundingRect = bounds def boundingRect(self): return self.mBoundingRect def paint(self, painter, arg2, arg3): horizontal = [ QLineF(self.mBoundingRect.topRight(), self.mBoundingRect.topLeft()), QLineF(self.mBoundingRect.bottomRight(), self.mBoundingRect.bottomLeft()) ] vertical = [ QLineF(self.mBoundingRect.bottomLeft(), self.mBoundingRect.topLeft()), QLineF(self.mBoundingRect.bottomRight(), self.mBoundingRect.topRight()) ] dashPen = QPen(Qt.DashLine) dashPen.setCosmetic(True) dashPen.setDashOffset(max(0.0, self.x())) painter.setPen(dashPen) painter.drawLines(horizontal) dashPen.setDashOffset(max(0.0, self.y())) painter.setPen(dashPen) painter.drawLines(vertical)
def draw(self, p: QtGui.QPainter, rect: QtCore.QRectF) -> None: p.setPen(self._pen) if self._orient_v == 'bottom': lp, rp = rect.topLeft(), rect.topRight() # p.drawLine(rect.topLeft(), rect.topRight()) elif self._orient_v == 'top': lp, rp = rect.bottomLeft(), rect.bottomRight() p.drawLine(lp.x(), lp.y(), rp.x(), rp.y())
def qgraphicsview_map_rect_from_scene(view: QGraphicsView, rect: QRectF) -> QPolygonF: """Like QGraphicsView.mapFromScene(QRectF) but returning a QPolygonF (without rounding). """ tr = view.viewportTransform() p1 = tr.map(rect.topLeft()) p2 = tr.map(rect.topRight()) p3 = tr.map(rect.bottomRight()) p4 = tr.map(rect.bottomLeft()) return QPolygonF([p1, p2, p3, p4])
def get_sides_of(rect: QRectF): """ This method returns the sides of a rect as a dictionary. Parameters ---------- rect : QRectF The rect to inspect. Returns ---------- dict A container of the sides represented as QLineF objects and their position as a key. """ return { "top": QLineF(rect.topLeft(), rect.topRight()), "right": QLineF(rect.topRight(), rect.bottomRight()), "bottom": QLineF(rect.bottomLeft(), rect.bottomRight()), "left": QLineF(rect.bottomLeft(), rect.topLeft()) }
class MapObjectOutline(QGraphicsItem): def __init__(self, object, parent = None): super().__init__(parent) self.mObject = object self.mBoundingRect = QRectF() self.setZValue(1) # makes sure outlines are above labels def syncWithMapObject(self, renderer): pixelPos = renderer.pixelToScreenCoords_(self.mObject.position()) bounds = objectBounds(self.mObject, renderer) bounds.translate(-pixelPos) self.setPos(pixelPos + self.mObject.objectGroup().offset()) self.setRotation(self.mObject.rotation()) if (self.mBoundingRect != bounds): self.prepareGeometryChange() self.mBoundingRect = bounds def boundingRect(self): return self.mBoundingRect def paint(self, painter, arg2, arg3): horizontal = [ QLineF(self.mBoundingRect.topRight(), self.mBoundingRect.topLeft()), QLineF(self.mBoundingRect.bottomRight(), self.mBoundingRect.bottomLeft())] vertical = [ QLineF(self.mBoundingRect.bottomLeft(), self.mBoundingRect.topLeft()), QLineF(self.mBoundingRect.bottomRight(), self.mBoundingRect.topRight())] dashPen = QPen(Qt.DashLine) dashPen.setCosmetic(True) dashPen.setDashOffset(max(0.0, self.x())) painter.setPen(dashPen) painter.drawLines(horizontal) dashPen.setDashOffset(max(0.0, self.y())) painter.setPen(dashPen) painter.drawLines(vertical)
def paint(self, painter, option, widget): # Main rectangle rectangle = QRectF( self._bounding_box.topLeft().x(), self._bounding_box.topLeft().y() + self._bounding_box.height() * 0.1, self._bounding_box.width() - self._bounding_box.height() * 0.1, self._bounding_box.height() - self._bounding_box.height() * 0.1) painter.drawRect(rectangle) # Top line painter.drawLine( rectangle.topLeft().x() + self._bounding_box.height() * 0.1, self._bounding_box.topLeft().y(), self._bounding_box.topRight().x(), self._bounding_box.topRight().y()) # Top left corner painter.drawLine( rectangle.topLeft().x() + self._bounding_box.height() * 0.1, self._bounding_box.topLeft().y(), self._bounding_box.topLeft().x() + 1, rectangle.topLeft().y()) # Top right corner painter.drawLine(self._bounding_box.topRight().x(), self._bounding_box.topRight().y(), rectangle.topRight().x(), rectangle.topRight().y()) # Bottom right corner painter.drawLine( rectangle.bottomRight().x() + 1, rectangle.bottomRight().y() - 1, self._bounding_box.bottomRight().x(), rectangle.bottomRight().y() - self._bounding_box.height() * 0.1) # Right line painter.drawLine( self._bounding_box.topRight().x(), self._bounding_box.topRight().y(), self._bounding_box.topRight().x(), self._bounding_box.bottomRight().y() - self._bounding_box.height() * 0.1)
class RoundRectItem(QGraphicsObject): def __init__(self, bounds, color, parent=None): super(RoundRectItem, self).__init__(parent) self.fillRect = False self.bounds = QRectF(bounds) self.pix = QPixmap() self.gradient = QLinearGradient() self.gradient.setStart(self.bounds.topLeft()) self.gradient.setFinalStop(self.bounds.bottomRight()) self.gradient.setColorAt(0, color) self.gradient.setColorAt(1, color.darker(200)) self.setCacheMode(QGraphicsItem.ItemCoordinateCache) def setFill(self, fill): self.fillRect = fill self.update() def fill(self): return self.fillRect fill = pyqtProperty(bool, fill, setFill) def paint(self, painter, option, widget): painter.setPen(Qt.NoPen) painter.setBrush(QColor(0, 0, 0, 64)) painter.drawRoundedRect(self.bounds.translated(2, 2), 25.0, 25.0) if self.fillRect: painter.setBrush(QApplication.palette().brush(QPalette.Window)) else: painter.setBrush(self.gradient) painter.setPen(QPen(Qt.black, 1)) painter.drawRoundedRect(self.bounds, 25.0, 25.0) if not self.pix.isNull(): painter.scale(1.95, 1.95) painter.drawPixmap(-self.pix.width() / 2, -self.pix.height() / 2, self.pix) def boundingRect(self): return self.bounds.adjusted(0, 0, 2, 2) def pixmap(self): return QPixmap(self.pix) def setPixmap(self, pixmap): self.pix = QPixmap(pixmap) self.update()
def paintEvent(self, event: QPaintEvent): bar = self._bar if bar.isVisible(): painter = QPainter(self) rect = bar.rect().adjusted(0, 0, 1, 0) rect = self.style().visualRect(self.layoutDirection(), self.geometry(), rect) boderRect = QRectF(rect).adjusted(0.5, 0.5, -0.5, -0.5) if not FLAT_STYLE: verticalGradient(painter, rect, rect) painter.setPen(BORDER_COLOR) painter.drawLine(boderRect.topRight(), boderRect.bottomRight())
def draw_boat(self, x, y): # width and height of the boat boat_w = Settings.WIDTH * 0.75 boat_h = Settings.HEIGHT * 0.75 # create boat boat = QRectF(x * Settings.WIDTH, y * Settings.HEIGHT, boat_w, boat_h) # set gradient gradient = QLinearGradient(boat.topLeft(), boat.bottomRight()) gradient.setColorAt(1, QColor(50, 175, 255, 150)) gradient.setColorAt(0, QColor(0, 50, 200, 100)) # add boat self.addEllipse(boat, Qt.black, gradient)
def testIntersects(self): """ """ rect = QRectF(QPointF(-50, -10), QPointF(50, 10)) # line completely outside of the rectangle self.assertFalse(PathFinding.intersects(rect, QLineF(QPointF(-100, -50), QPointF(-100, 50)))) # the line is a top side of the rectangle self.assertFalse(PathFinding.intersects(rect, QLineF(rect.topLeft(), rect.topRight()))) # the line starts at the left corner of the rectangle and is not perpendicular to any of the rectangle sides; # the line ends outside of the rectangle, not going through it self.assertFalse(PathFinding.intersects(rect, QLineF(rect.topLeft(), QPointF(-100, -100)))) # the line starts at the left corner of the rectangle and is perpendicular to the top side of the rectangle; # the line ends outside of the rectangle, not going through it self.assertFalse(PathFinding.intersects(rect, QLineF(rect.topLeft(), QPointF(rect.left(), rect.top() - 100)))) # the line is horizontal and goes straight through the center of the rectangle self.assertTrue(PathFinding.intersects(rect, QLineF(QPointF(-100, 0), QPointF(100, 0)))) # the line is vertical and goes straight through the center of the rectangle self.assertTrue(PathFinding.intersects(rect, QLineF(QPointF(0, -100), QPointF(0, 100)))) # the line is vertical and goes up from the bottom right corner of the rectangle self.assertFalse(PathFinding.intersects(rect, QLineF(rect.bottomRight(), QPointF(rect.right(), rect.top()-100)))) # the line is diagonal of the rectangle self.assertTrue(PathFinding.intersects(rect, QLineF(rect.topLeft(), rect.bottomRight())))
def getLinePoint(self, end): #rect = QRectF(self.pos(), QPoint(self.pos().x()+120, self.pos().y()+60)) rect = QRectF(self.pos(), QSizeF(120, 60)) #calculate center points left = QPointF(rect.topLeft().x(), rect.center().y()) right = QPointF(rect.bottomRight().x(), rect.center().y()) top = QPointF(rect.center().x(), rect.topLeft().y()) bottom = QPointF(rect.center().x(), rect.bottomRight().y()) if rect.topLeft().x() < end.x(): #point is not left from rect if rect.bottomRight().x() < end.x(): y1 = GeoHelper.getY(-1, rect.bottomRight().x(), rect.topLeft().y(), end.x()) if end.y() < y1: return "top", top y1 = GeoHelper.getY(1, rect.bottomRight().x(), rect.bottomRight().y(), end.x()) if end.y() < y1: return "right", right return "bottom", bottom elif rect.center().y() < end.y(): return "bottom", bottom else: return "top", top else: #point is left from rect y1 = GeoHelper.getY(1, rect.topLeft().x(), rect.topLeft().y(), end.x()) if end.y() < y1: return "top", top y1 = GeoHelper.getY(-1, rect.topLeft().x(), rect.bottomRight().y(), end.x()) if end.y() < y1: return "left", left return "bottom", bottom
class PathNucleicAcidPartItem(QAbstractPartItem): """Summary Attributes: active_virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): Description findChild (TYPE): Description grab_corner (TYPE): Description prexover_manager (TYPE): Description """ findChild = util.findChild # for debug _BOUNDING_RECT_PADDING = 20 _GC_SIZE = 10 def __init__(self, model_part_instance, viewroot, parent): """parent should always be pathrootitem Args: model_part_instance (TYPE): Description viewroot (TYPE): Description parent (TYPE): Description """ super(PathNucleicAcidPartItem, self).__init__(model_part_instance, viewroot, parent) self.setAcceptHoverEvents(True) self._getActiveTool = viewroot.manager.activeToolGetter self.active_virtual_helix_item = None m_p = self._model_part self._controller = NucleicAcidPartItemController(self, m_p) self.prexover_manager = PreXoverManager(self) self._virtual_helix_item_list = [] self._initModifierRect() self._proxy_parent = ProxyParentItem(self) self._proxy_parent.setFlag(QGraphicsItem.ItemHasNoContents) self._scale_2_model = m_p.baseWidth() / _BASE_WIDTH self._scale_2_Qt = _BASE_WIDTH / m_p.baseWidth() # self._rect = QRectF() self._vh_rect = QRectF() # self.setPen(getPenObj(styles.ORANGE_STROKE, 0)) self.setPen(getNoPen()) # self.setRect(self._rect) self.outline = outline = PathRectItem(self) outline.setFlag(QGraphicsItem.ItemStacksBehindParent) self.setZValue(styles.ZPART) self._proxy_parent.setZValue(styles.ZPART) outline.setZValue(styles.ZDESELECTOR) self.outline.setPen(getPenObj(m_p.getColor(), _DEFAULT_WIDTH)) o_rect = self._configureOutline(outline) model_color = m_p.getColor() self.resize_handle_group = ResizeHandleGroup( o_rect, _HANDLE_SIZE, model_color, True, # HandleType.LEFT | HandleType.RIGHT, self) self.model_bounds_hint = m_b_h = QGraphicsRectItem(self) m_b_h.setBrush(getBrushObj(styles.BLUE_FILL, alpha=32)) m_b_h.setPen(getNoPen()) m_b_h.hide() self.workplane = PathWorkplaneItem(m_p, self) self.hide() # show on adding first vh # end def def proxy(self): """Summary Returns: TYPE: Description """ return self._proxy_parent # end def def modelColor(self): """Summary Returns: TYPE: Description """ return self._model_part.getProperty('color') # end def def convertToModelZ(self, z): """scale Z-axis coordinate to the model Args: z (TYPE): Description """ return z * self._scale_2_model # end def def convertToQtZ(self, z): """Summary Args: z (TYPE): Description Returns: TYPE: Description """ return z * self._scale_2_Qt # end def def _initModifierRect(self): """docstring for _initModifierRect """ self._can_show_mod_rect = False self._mod_rect = m_r = QGraphicsRectItem(_DEFAULT_RECT, self) m_r.setPen(_MOD_PEN) m_r.hide() # end def def vhItemForIdNum(self, id_num): """Returns the pathview VirtualHelixItem corresponding to id_num Args: id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods. """ return self._virtual_helix_item_hash.get(id_num) ### SIGNALS ### ### SLOTS ### def partActiveVirtualHelixChangedSlot(self, part, id_num): """Summary Args: part (TYPE): Description id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods. Returns: TYPE: Description """ vhi = self._virtual_helix_item_hash.get(id_num, None) self.setActiveVirtualHelixItem(vhi) self.setPreXoverItemsVisible(vhi) # end def def partActiveBaseInfoSlot(self, part, info): """Summary Args: part (TYPE): Description info (TYPE): Description Returns: TYPE: Description """ pxi_m = self.prexover_manager pxi_m.deactivateNeighbors() if info and info is not None: id_num, is_fwd, idx, to_vh_id_num = info pxi_m.activateNeighbors(id_num, is_fwd, idx) # end def def partZDimensionsChangedSlot(self, model_part, min_id_num, max_id_num, ztf=False): """Summary Args: model_part (Part): The model part min_id_num (TYPE): Description max_id_num (TYPE): Description ztf (bool, optional): Description Returns: TYPE: Description """ if len(self._virtual_helix_item_list) > 0: vhi_hash = self._virtual_helix_item_hash vhi_max = vhi_hash[max_id_num] vhi_rect_max = vhi_max.boundingRect() self._vh_rect.setRight(vhi_rect_max.right() + vhi_max.x()) vhi_min = vhi_hash[min_id_num] vhi_h_rect = vhi_min.handle().boundingRect() self._vh_rect.setLeft( (vhi_h_rect.left() - styles.VH_XOFFSET + vhi_min.x())) if ztf: self.scene().views()[0].zoomToFit() TLx, TLy, BRx, BRy = self._getVHRectCorners() self.reconfigureRect((TLx, TLy), (BRx, BRy)) # end def def partSelectedChangedSlot(self, model_part, is_selected): """Summary Args: model_part (Part): The model part is_selected (TYPE): Description Returns: TYPE: Description """ # print("partSelectedChangedSlot", is_selected) if is_selected: self.resetPen(styles.SELECTED_COLOR, styles.SELECTED_PEN_WIDTH) self.resetBrush(styles.SELECTED_BRUSH_COLOR, styles.SELECTED_ALPHA) else: self.resetPen(self.modelColor()) self.resetBrush(styles.DEFAULT_BRUSH_COLOR, styles.DEFAULT_ALPHA) def partPropertyChangedSlot(self, model_part, property_key, new_value): """Summary Args: model_part (Part): The model part property_key (TYPE): Description new_value (TYPE): Description Returns: TYPE: Description """ if self._model_part == model_part: self._model_props[property_key] = new_value if property_key == 'color': for vhi in self._virtual_helix_item_list: vhi.handle().refreshColor() # self.workplane.outline.setPen(getPenObj(new_value, 0)) TLx, TLy, BRx, BRy = self._getVHRectCorners() self.reconfigureRect((TLx, TLy), (BRx, BRy)) elif property_key == 'is_visible': if new_value: self.show() else: self.hide() elif property_key == 'virtual_helix_order': vhi_dict = self._virtual_helix_item_hash new_list = [vhi_dict[id_num] for id_num in new_value] ztf = False self._setVirtualHelixItemList(new_list, zoom_to_fit=ztf) elif property_key == 'workplane_idxs': if hasattr(self, 'workplane'): self.workplane.setIdxs(new_idxs=new_value) # end def def partVirtualHelicesTranslatedSlot(self, sender, vh_set, left_overs, do_deselect): """Summary Args: sender (obj): Model object that emitted the signal. vh_set (TYPE): Description left_overs (TYPE): Description do_deselect (TYPE): Description Returns: TYPE: Description """ # self.prexover_manager.clearPreXoverItems() # if self.active_virtual_helix_item is not None: # self.active_virtual_helix_item.deactivate() # self.active_virtual_helix_item = None # if self.active_virtual_helix_item is not None: # self.setPreXoverItemsVisible(self.active_virtual_helix_item) pass # end def def partRemovedSlot(self, sender): """docstring for partRemovedSlot Args: sender (obj): Model object that emitted the signal. """ self.parentItem().removePartItem(self) scene = self.scene() scene.removeItem(self) self._model_part = None self._virtual_helix_item_hash = None self._virtual_helix_item_list = None self._controller.disconnectSignals() self._controller = None # self.grab_corner = None # end def def partVirtualHelixAddedSlot(self, model_part, id_num, virtual_helix, neighbors): """ When a virtual helix is added to the model, this slot handles the instantiation of a virtualhelix item. Args: model_part (Part): The model part id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods. """ # print("NucleicAcidPartItem.partVirtualHelixAddedSlot") vhi = PathVirtualHelixItem(virtual_helix, self, self._viewroot) self._virtual_helix_item_hash[id_num] = vhi vhi_list = self._virtual_helix_item_list vhi_list.append(vhi) ztf = not getBatch() self._setVirtualHelixItemList(vhi_list, zoom_to_fit=ztf) if not self.isVisible(): self.show() # end def def partVirtualHelixResizedSlot(self, sender, id_num, virtual_helix): """Notifies the virtualhelix at coord to resize. Args: sender (obj): Model object that emitted the signal. id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods. """ vhi = self._virtual_helix_item_hash[id_num] # print("resize:", id_num, virtual_helix.getSize()) vhi.resize() # end def def partVirtualHelixRemovingSlot(self, sender, id_num, virtual_helix, neighbors): """Summary Args: sender (obj): Model object that emitted the signal. id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods. Returns: TYPE: Description """ self.removeVirtualHelixItem(id_num) # end def def partVirtualHelixRemovedSlot(self, sender, id_num): """ Step 2 of removing a VHI """ ztf = not getBatch() self._setVirtualHelixItemList(self._virtual_helix_item_list, zoom_to_fit=ztf) if len(self._virtual_helix_item_list) == 0: self.hide() self.reconfigureRect((), ()) # end def def partVirtualHelixPropertyChangedSlot(self, sender, id_num, virtual_helix, keys, values): """Summary Args: sender (obj): Model object that emitted the signal. id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods. keys (TYPE): Description values (TYPE): Description Returns: TYPE: Description """ if self._model_part == sender: vh_i = self._virtual_helix_item_hash[id_num] vh_i.virtualHelixPropertyChangedSlot(keys, values) # end def def partVirtualHelicesSelectedSlot(self, sender, vh_set, is_adding): """is_adding (bool): adding (True) virtual helices to a selection or removing (False) Args: sender (obj): Model object that emitted the signal. vh_set (TYPE): Description is_adding (TYPE): Description """ vhhi_group = self._viewroot.vhiHandleSelectionGroup() vh_hash = self._virtual_helix_item_hash doc = self._viewroot.document() if is_adding: # print("got the adding slot in path") for id_num in vh_set: vhi = vh_hash[id_num] vhhi = vhi.handle() vhhi.modelSelect(doc) # end for vhhi_group.processPendingToAddList() else: # print("got the removing slot in path") for id_num in vh_set: vhi = vh_hash[id_num] vhhi = vhi.handle() vhhi.modelDeselect(doc) # end for vhhi_group.processPendingToAddList() # end def ### ACCESSORS ### def removeVirtualHelixItem(self, id_num): """Summary Args: id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods. Returns: TYPE: Description """ self.setActiveVirtualHelixItem(None) vhi = self._virtual_helix_item_hash[id_num] vhi.virtualHelixRemovedSlot() self._virtual_helix_item_list.remove(vhi) del self._virtual_helix_item_hash[id_num] # end def def window(self): """Summary Returns: TYPE: Description """ return self.parentItem().window() # end def ### PRIVATE METHODS ### def _configureOutline(self, outline): """Adjusts `outline` size with default padding. Args: outline (TYPE): Description Returns: o_rect (QRect): `outline` rect adjusted by _BOUNDING_RECT_PADDING """ _p = self._BOUNDING_RECT_PADDING o_rect = self.rect().adjusted(-_p, -_p, _p, _p) outline.setRect(o_rect) return o_rect # end def def _getVHRectCorners(self): vhTL = self._vh_rect.topLeft() vhBR = self._vh_rect.bottomRight() # vhTLx, vhTLy = vhTL.x(), vhTL.y() # vhBRx, vhBRy = vhBR.x(), vhBR.y() return vhTL.x(), vhTL.y(), vhBR.x(), vhBR.y() # end def def _setVirtualHelixItemList(self, new_list, zoom_to_fit=True): """ Give me a list of VirtualHelixItems and I'll parent them to myself if necessary, position them in a column, adopt their handles, and position them as well. Args: new_list (TYPE): Description zoom_to_fit (bool, optional): Description """ y = 0 # How far down from the top the next PH should be vhi_rect = None vhi_h_rect = None vhi_h_selection_group = self._viewroot.vhiHandleSelectionGroup() for vhi in new_list: _, _, _z = vhi.cnModel().getAxisPoint(0) _z *= self._scale_2_Qt vhi.setPos(_z, y) if vhi_rect is None: vhi_rect = vhi.boundingRect() step = vhi_rect.height() + styles.PATH_HELIX_PADDING # end if # get the VirtualHelixHandleItem vhi_h = vhi.handle() do_reselect = False if vhi_h.parentItem() == vhi_h_selection_group: do_reselect = True vhi_h.tempReparent() # so positioning works if vhi_h_rect is None: vhi_h_rect = vhi_h.boundingRect() vhi_h_x = _z - _VH_XOFFSET vhi_h_y = y + (vhi_rect.height() - vhi_h_rect.height()) / 2 vhi_h.setPos(vhi_h_x, vhi_h_y) y += step self.updateXoverItems(vhi) if do_reselect: vhi_h_selection_group.addToGroup(vhi_h) # end for # this need only adjust top and bottom edges of the bounding rectangle # self._vh_rect.setTop() self._vh_rect.setBottom(y) self._virtual_helix_item_list = new_list # now update Z dimension (X in Qt space in the Path view) part = self.part() self.partZDimensionsChangedSlot(part, *part.zBoundsIds(), ztf=zoom_to_fit) # end def def resetPen(self, color, width=0): """Summary Args: color (TYPE): Description width (int, optional): Description Returns: TYPE: Description """ pen = getPenObj(color, width) self.outline.setPen(pen) # self.setPen(pen) # end def def resetBrush(self, color, alpha): """Summary Args: color (TYPE): Description alpha (TYPE): Description Returns: TYPE: Description """ brush = getBrushObj(color, alpha=alpha) self.setBrush(brush) # end def def reconfigureRect(self, top_left, bottom_right, finish=False, padding=80): """ Updates the bounding rect to the size of the childrenBoundingRect. Refreshes the outline and grab_corner locations. Called by partZDimensionsChangedSlot and partPropertyChangedSlot. """ outline = self.outline hasTL = True if top_left else False hasBR = True if bottom_right else False if hasTL ^ hasBR: # called via resizeHandle mouseMove? ptTL = QPointF(*top_left) if top_left else outline.rect().topLeft() ptBR = QPointF(*bottom_right) if bottom_right else outline.rect( ).bottomRight() o_rect = QRectF(ptTL, ptBR) pad_xoffset = self._BOUNDING_RECT_PADDING * 2 new_size = int( (o_rect.width() - _VH_XOFFSET - pad_xoffset) / _BASE_WIDTH) substep = self._model_part.subStepSize() snap_size = new_size - new_size % substep snap_offset = -(new_size % substep) * _BASE_WIDTH self.resize_handle_group.updateText(HandleType.RIGHT, snap_size) if finish: self._model_part.setAllVirtualHelixSizes(snap_size) o_rect = o_rect.adjusted(0, 0, snap_offset, 0) # print("finish", vh_size, new_size, substep, snap_size) self.outline.setRect(o_rect) else: # 1. Temporarily remove children that shouldn't affect size outline.setParentItem(None) self.workplane.setParentItem(None) self.model_bounds_hint.setParentItem(None) self.resize_handle_group.setParentItemAll(None) self.prexover_manager.setParentItem(None) # 2. Get the tight bounding rect self.setRect(self.childrenBoundingRect()) # vh_items only # 3. Restore children like nothing happened outline.setParentItem(self) self.workplane.setParentItem(self) self.model_bounds_hint.setParentItem(self) self.resize_handle_group.setParentItemAll(self) self.prexover_manager.setParentItem(self) self._configureOutline(outline) self.resetPen(self.modelColor(), 0) # cosmetic self.resetBrush(styles.DEFAULT_BRUSH_COLOR, styles.DEFAULT_ALPHA) self.workplane.reconfigureRect((), ()) self.resize_handle_group.alignHandles(outline.rect()) return outline.rect() # end def ### PUBLIC METHODS ### def getModelMinBounds(self, handle_type=None): """Bounds in form of Qt scaled from model Absolute min should be 2*stepsize. Round up from indexOfRightmostNonemptyBase to nearest substep. Returns: Tuple (xTL, yTL, xBR, yBR) """ _p = self._BOUNDING_RECT_PADDING default_idx = self._model_part.stepSize() * 2 nonempty_idx = self._model_part.indexOfRightmostNonemptyBase() right_bound_idx = max(default_idx, nonempty_idx) substep = self._model_part.subStepSize() snap_idx = (right_bound_idx / substep) * substep xTL = 0 xBR = snap_idx * _BASE_WIDTH + _p min_rect = self.rect().adjusted(-_p, -_p, _p, _p) yTL = min_rect.top() yBR = min_rect.bottom() return xTL, yTL, xBR, yBR # end def def showModelMinBoundsHint(self, handle_type, show=True): """Shows QGraphicsRectItem reflecting current model bounds. ResizeHandleGroup should toggle this when resizing. Args: status_str (str): Description to display in status bar. """ m_b_h = self.model_bounds_hint if show: xTL, yTL, xBR, yBR = self.getModelMinBounds() m_b_h.setRect(QRectF(QPointF(xTL, yTL), QPointF(xBR, yBR))) m_b_h.show() else: m_b_h.hide() # end def def setModifyState(self, bool): """Hides the modRect when modify state disabled. Args: bool (TYPE): Description """ self._can_show_mod_rect = bool if bool is False: self._mod_rect.hide() def getOrderedVirtualHelixList(self): """Used for encoding. """ ret = [] for vhi in self._virtual_helix_item_list: ret.append(vhi.coord()) return ret # end def def reorderHelices(self, id_nums, index_delta): """ Reorder helices by moving helices _pathHelixList[first:last] by a distance delta in the list. Notify each PathHelix and PathHelixHandle of its new location. Args: first (TYPE): Description last (TYPE): Description index_delta (TYPE): Description """ vhi_list = self._virtual_helix_item_list helix_numbers = [vhi.idNum() for vhi in vhi_list] first_index = helix_numbers.index(id_nums[0]) last_index = helix_numbers.index(id_nums[-1]) + 1 for id_num in id_nums: helix_numbers.remove(id_num) if index_delta < 0: # move group earlier in the list new_index = max(0, index_delta + first_index) - len(id_nums) else: # move group later in list new_index = min(len(vhi_list), index_delta + last_index) - len(id_nums) new_list = helix_numbers[:new_index] + id_nums + helix_numbers[ new_index:] # call the method to move the items and store the list self._model_part.setImportedVHelixOrder(new_list, check_batch=False) # end def def setActiveVirtualHelixItem(self, new_active_vhi): """Summary Args: new_active_vhi (TYPE): Description Returns: TYPE: Description """ current_vhi = self.active_virtual_helix_item if new_active_vhi != current_vhi: if current_vhi is not None: current_vhi.deactivate() if new_active_vhi is not None: new_active_vhi.activate() self.active_virtual_helix_item = new_active_vhi # end def def unsetActiveVirtualHelixItem(self): if self.active_virtual_helix_item is not None: self.active_virtual_helix_item.deactivate() self.active_virtual_helix_item = None self.prexover_manager.reset() def setPreXoverItemsVisible(self, virtual_helix_item): """ self._pre_xover_items list references prexovers parented to other PathHelices such that only the activeHelix maintains the list of visible prexovers Args: virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): Description """ vhi = virtual_helix_item if vhi is None: return # print("path.setPreXoverItemsVisible", virtual_helix_item.idNum()) part = self.part() info = part.active_base_info if info and virtual_helix_item is not None: id_num, is_fwd, idx, to_vh_id_num = info per_neighbor_hits, pairs = part.potentialCrossoverMap(id_num, idx) self.prexover_manager.activateVirtualHelix(virtual_helix_item, idx, per_neighbor_hits) else: self.prexover_manager.reset() # end def def updateXoverItems(self, virtual_helix_item): """Summary Args: virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): Description Returns: TYPE: Description """ for item in virtual_helix_item.childItems(): if isinstance(item, XoverNode3): item.refreshXover() # end def def updateStatusBar(self, status_string): """Shows status_string in the MainWindow's status bar. Args: status_string (str): The text to be displayed. """ self.window().statusBar().showMessage(status_string) ### COORDINATE METHODS ### def keyPanDeltaX(self): """How far a single press of the left or right arrow key should move the scene (in scene space) """ vhs = self._virtual_helix_item_list return vhs[0].keyPanDeltaX() if vhs else 5 # end def def keyPanDeltaY(self): """How far an an arrow key should move the scene (in scene space) for a single press """ vhs = self._virtual_helix_item_list if not len(vhs) > 1: return 5 dy = vhs[0].pos().y() - vhs[1].pos().y() dummyRect = QRectF(0, 0, 1, dy) return self.mapToScene(dummyRect).boundingRect().height() # end def ### TOOL METHODS ### def mousePressEvent(self, event): """Handler for user mouse press. Args: event (:obj:`QGraphicsSceneMouseEvent`): Contains item, scene, and screen coordinates of the the event, and previous event. """ self._viewroot.clearSelectionsIfActiveTool() self.unsetActiveVirtualHelixItem() return QGraphicsItem.mousePressEvent(self, event) def hoverMoveEvent(self, event): """ Parses a mouseMoveEvent to extract strandSet and base index, forwarding them to approproate tool method as necessary. Args: event (TYPE): Description """ active_tool = self._getActiveTool() tool_method_name = active_tool.methodPrefix() + "HoverMove" if hasattr(self, tool_method_name): getattr(self, tool_method_name)(event.pos()) # end def def createToolHoverMove(self, pt): """Create the strand is possible. Args: pt (QPointF): mouse cursor location of create tool hover. """ active_tool = self._getActiveTool() if not active_tool.isFloatingXoverBegin(): temp_xover = active_tool.floatingXover() temp_xover.updateFloatingFromPartItem(self, pt)
class RoundRectItem(QGraphicsObject): """Base class for most graphic objects in our scene""" def __init__(self, bounds, color=None, parent=None): """ Args: bounds - QRectF, geometry of object color - QColor or None parent - widget to contain this graphic item or None """ super(RoundRectItem, self).__init__(parent) self._fillRect = False self._bounds = QRectF(bounds) self._pix = QPixmap() self._color = color self.setCacheMode(QGraphicsItem.ItemCoordinateCache) def setFill(self, fill: bool): """ Changes the property of how the cell is filled. Args: fill: bool """ self._fillRect = fill self.update() @property def _gradient(self): gradient = QLinearGradient() gradient.setStart( (self._bounds.topLeft() + self._bounds.topRight()) / 2) gradient.setFinalStop( (self._bounds.bottomLeft() + self._bounds.bottomRight()) / 2) gradient.setColorAt(0, self._color) gradient.setColorAt(1, self._color.darker(200)) return gradient def paint(self, painter, option, widget): """Standard Qt paint event.""" if self._color: painter.setPen(Qt.NoPen) painter.setBrush(QColor(0, 0, 0, 64)) painter.drawRoundedRect(self._bounds.translated(2, 2), 25.0, 25.0) if self._fillRect: painter.setBrush(QApplication.palette().brush(QPalette.Window)) else: painter.setBrush(self._gradient) painter.setPen(QPen(Qt.black, 1)) painter.drawRoundedRect(self._bounds, 25.0, 25.0) if not self._pix.isNull(): if self._rounded_pixmap: painter.setRenderHint(QPainter.Antialiasing, True) brush = QBrush( self._pix.scaled(self._bounds.width(), self._bounds.height())) painter.setBrush(brush) painter.drawRoundedRect(self._bounds, 25.0, 25.0) else: painter.scale(self._bounds.width() / self._pix.width(), self._bounds.height() / self._pix.height()) painter.drawPixmap(-self._pix.width() / 2, -self._pix.height() / 2, self._pix) def boundingRect(self): """returns bounding rectangle""" return self._bounds.adjusted(0, 0, 2, 2) def setPixmap(self, pixmap_path: str, rounded_pixmap=False): """ Sets new pixmap for this graphic object. Args: pixmap_path: path to image for pixmap rounded_pixmap: make the picture rounded (used, e.g., for lava in the cells) """ self._rounded_pixmap = rounded_pixmap self._pix = QPixmap(pixmap_path) self.update()
class JoyPad(QtWidgets.QWidget, IndicatorPosition): IndicatorPosition = IndicatorPosition Q_ENUM(IndicatorPosition) joy_btn_pressed = QtCore.pyqtSignal(str) joy_btn_released = QtCore.pyqtSignal(str) joy_l_pressed = QtCore.pyqtSignal(bool) joy_l_released = QtCore.pyqtSignal(bool) joy_r_pressed = QtCore.pyqtSignal(bool) joy_r_released = QtCore.pyqtSignal(bool) joy_c_pressed = QtCore.pyqtSignal(bool) joy_c_released = QtCore.pyqtSignal(bool) joy_t_pressed = QtCore.pyqtSignal(bool) joy_t_released = QtCore.pyqtSignal(bool) joy_b_pressed = QtCore.pyqtSignal(bool) joy_b_released = QtCore.pyqtSignal(bool) def __init__(self, parent=None): super(JoyPad, self).__init__(parent) self.rect1 = QRectF() self.rect2 = QRectF() self.left_image = None self.right_image = None self.top_image = None self.bottom_image = None self.center_image = None self._dummyPixmap = QtGui.QPixmap() self._font = QFont('Lato Heavy', 20) self._text_color = QColor('white') self._textL = '' self._textR = '' self._textC = '' self._textT = '' self._textB = '' self.colorState = False self._true_color = QColor('lawngreen') self._false_color = QColor('gray') self.highlight_color = self._false_color self._highlightPosition = IndicatorPosition.NONE self.highlight_left = False self.highlight_right = False self.highlight_top = False self.highlight_bottom = False self.highlight_center = False self.last_active_btn = None self.setMouseTracking(True) self.setToolTipDuration(2000) self.installEventFilter(self) self.btn_names = { 'L': 'left', 'R': 'right', 'T': 'top', 'B': 'bottom', 'C': 'center' } self.tooltips = {'L': '', 'R': '', 'T': '', 'B': '', 'C': ''} self.axis_list = ('X', 'Y', 'Z', 'A') def eventFilter(self, obj, event): if obj is self and self.isEnabled(): if event.type() == QEvent.MouseButtonPress: if event.button() == Qt.RightButton: event.ignore() else: pos = event.localPos() active_btn = self.get_active_btn(pos) self.last_active_btn = active_btn if active_btn is not None: self._pressedOutput(active_btn) elif event.type() == QEvent.MouseButtonRelease: if event.button() == Qt.RightButton: event.ignore() elif self.last_active_btn is not None: self._releasedOutput(self.last_active_btn) elif event.type() == QEvent.MouseMove: pos = event.pos() active_btn = self.get_active_btn(pos) if active_btn is not None: self.setToolTip(self.tooltips[active_btn]) return super(JoyPad, self).eventFilter(obj, event) def _pressedOutput(self, btncode): self.joy_btn_pressed.emit(btncode) self['joy_{}_pressed'.format(btncode.lower())].emit(True) def _releasedOutput(self, btncode): self.joy_btn_released.emit(btncode) self['joy_{}_released'.format(btncode.lower())].emit(False) def get_active_btn(self, pos): if self.center_path.contains(pos): return 'C' elif self.left_path.contains(pos): return 'L' elif self.right_path.contains(pos): return 'R' elif self.bottom_path.contains(pos): return 'B' elif self.top_path.contains(pos): return 'T' return None def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(painter.Antialiasing) w = min(event.rect().width(), event.rect().height()) self.rect1.setSize(QSizeF(w * 0.4, w * 0.4)) self.rect2.setSize(QSizeF(w * 0.9, w * 0.9)) self.create_paths(painter, event) self.draw_painter_paths(painter, event) self.draw_icons(painter, event) self.draw_highlight(painter, event) painter.end() def create_paths(self, qp, event): self.left_path = QPainterPath() self.right_path = QPainterPath() self.bottom_path = QPainterPath() self.top_path = QPainterPath() self.center_path = QPainterPath() center = event.rect().center() self.rect1.moveCenter(center) self.rect2.moveCenter(center) left_start = QPointF(self.rect1.topLeft()) right_start = QPointF(self.rect1.bottomRight()) bottom_start = QPointF(self.rect1.bottomLeft()) top_start = QPointF(self.rect1.topRight()) path = (self.right_path, self.top_path, self.left_path, self.bottom_path) start = (right_start, top_start, left_start, bottom_start) angle = -45 for i in range(4): path[i].moveTo(start[i]) path[i].arcTo(self.rect1, angle, 90) path[i].arcTo(self.rect2, angle + 90, -90) path[i].closeSubpath() angle += 90 cap = QRectF() cap.setSize(QSizeF(self.rect1.width() * 0.8, self.rect1.height() * 0.8)) cap.moveCenter(center) self.center_path.addEllipse(cap) def draw_painter_paths(self, qp, event): w = min(event.rect().width(), event.rect().height()) center = event.rect().center() fp = QPoint(int(center.x() - w / 4), int(center.y() - w / 4)) bg = QRadialGradient(center, w / 2, fp) bg.setColorAt(0, QColor(180, 180, 180)) bg.setColorAt(1, QColor(40, 40, 40)) qp.setBrush(QBrush(bg)) qp.setPen(QPen(QColor(Qt.black), 4)) qp.drawPath(self.left_path) qp.drawPath(self.right_path) qp.drawPath(self.top_path) qp.drawPath(self.bottom_path) qp.drawPath(self.center_path) def draw_icons(self, qp, event): rect = QRect() rect.setSize( QSize(int(self.rect1.width() * 0.4), int(self.rect1.height() * 0.4))) center = event.rect().center() qp.setPen(QPen(self._text_color, 2)) qp.setFont(self._font) # left button rect.moveCenter( QPoint(int(center.x() - self.rect2.width() / 3), center.y())) if isinstance(self.left_image, QPixmap): pix = self.left_image qp.drawPixmap(rect, pix, pix.rect()) elif isinstance(self.left_image, str): qp.drawText(rect, Qt.AlignCenter, self.left_image) # right button rect.moveCenter( QPoint(int(center.x() + self.rect2.width() / 3), center.y())) if isinstance(self.right_image, QPixmap): pix = self.right_image qp.drawPixmap(rect, pix, pix.rect()) elif isinstance(self.right_image, str): qp.drawText(rect, Qt.AlignCenter, self.right_image) # bottom button rect.moveCenter( QPoint(center.x(), int(center.y() + self.rect2.width() / 3))) if isinstance(self.bottom_image, QPixmap): pix = self.bottom_image qp.drawPixmap(rect, pix, pix.rect()) elif isinstance(self.bottom_image, str): qp.drawText(rect, Qt.AlignCenter, self.bottom_image) # top button rect.moveCenter( QPoint(center.x(), int(center.y() - self.rect2.width() / 3))) if isinstance(self.top_image, QPixmap): pix = self.top_image qp.drawPixmap(rect, pix, pix.rect()) elif isinstance(self.top_image, str): qp.drawText(rect, Qt.AlignCenter, self.top_image) # center button rect.moveCenter(QPoint(center.x(), center.y())) if isinstance(self.center_image, QPixmap): pix = self.center_image qp.drawPixmap(rect, pix, pix.rect()) elif isinstance(self.center_image, str): qp.drawText(rect, Qt.AlignCenter, self.center_image) def draw_highlight(self, qp, event): rect = QRectF() rect.setSize(self.rect1.size() * 0.9) center = event.rect().center() rect.moveCenter(center) pen_width = self.rect1.width() * 0.08 qp.setPen(QPen(self.highlight_color, pen_width, cap=Qt.FlatCap)) if self.highlight_center is True: qp.drawArc(rect, 0, 5760) else: if self.highlight_right is True: qp.drawArc(rect, -45 * 16, 90 * 16) if self.highlight_left is True: qp.drawArc(rect, 135 * 16, 90 * 16) if self.highlight_top is True: qp.drawArc(rect, 45 * 16, 90 * 16) if self.highlight_bottom is True: qp.drawArc(rect, 225 * 16, 90 * 16) def reset_highlight(self): self.highlight_left = False self.highlight_right = False self.highlight_top = False self.highlight_bottom = False self.highlight_center = False def set_highlight(self, btn, state=True): if type(btn) == int: if btn == IndicatorPosition.LEFT: btn = 'L' elif btn == IndicatorPosition.RIGHT: btn = 'R' elif btn == IndicatorPosition.CENTER: btn = 'C' elif btn == IndicatorPosition.TOP: btn = 'T' elif btn == IndicatorPosition.BOTTOM: btn = 'B' elif btn == IndicatorPosition.LEFTRIGHT: btn = 'X' elif btn == IndicatorPosition.TOPBOTTOM: btn = 'Z' elif btn == IndicatorPosition.NONE: self.reset_highlight() return else: print('undefined position:{}'.format(btn)) return if btn not in self.axis_list and btn not in self.btn_names.keys(): return if btn == 'X' or btn == 'A': self.highlight_left = state self.highlight_right = state elif btn == 'Y' or btn == 'Z': self.highlight_top = state self.highlight_bottom = state else: name = self.btn_names[btn] self['highlight_' + name] = state self.update() def set_button_icon(self, btn, path): self.set_icon(btn, 'image', path) def set_button_text(self, btn, text): self.set_icon(btn, 'text', text) def set_icon(self, btn, kind, data): if btn not in self.btn_names.keys(): return name = self.btn_names[btn] if kind == 'image': if data is None: self[name + "_image"] = None else: self[name + "_image"] = QPixmap(data) elif kind == 'text': self[name + "_image"] = data else: return self.update() def set_tooltip(self, btn, tip): if btn in self.btn_names.keys(): self.tooltips[btn] = tip def setLight(self, data): if data: self.highlight_color = self._true_color else: self.highlight_color = self._false_color self.update() def set_HighlightPosition(self, position): self._highlightPosition = position self.reset_highlight() self.set_highlight(position, True) self.update() def get_HighlightPosition(self): return self._highlightPosition def reset_HighlightPosition(self): self._highlightPosition = IndicatorPosition.NONE self.reset_highlight() self.update() highlightPosition = QtCore.pyqtProperty(IndicatorPosition, get_HighlightPosition, set_HighlightPosition, reset_HighlightPosition) @QtCore.pyqtSlot() def set_colorStateTrue(self): self.setLight(True) @QtCore.pyqtSlot() def set_colorStateFalse(self): self.setLight(False) @QtCore.pyqtSlot(bool) def set_colorState(self, state): self.colorState = bool(state) self.setLight(state) def get_colorState(self): return self.colorState def reset_colorState(self): self.colorState = False self.setLight(False) setColorState = QtCore.pyqtProperty(bool, get_colorState, set_colorState, reset_colorState) def setLeftImagePath(self, data): if data.isNull(): data = None self.set_icon('L', 'image', data) def getLeftImagePath(self): if isinstance(self.left_image, QPixmap): self.left_image else: return self._dummyPixmap def resetLeftImagePath(self): pass def setRightImagePath(self, data): if data.isNull(): data = None self.set_icon('R', 'image', data) def getRightImagePath(self): return self._dummyPixmap def resetRightImagePath(self): pass def setCenterImagePath(self, data): if data.isNull(): data = None self.set_icon('C', 'image', data) def getCenterImagePath(self): return self._dummyPixmap def resetCenterImagePath(self): pass def setTopImagePath(self, data): if data.isNull(): data = None self.set_icon('T', 'image', data) def getTopImagePath(self): return self._dummyPixmap def resetTopImagePath(self): pass def setBottomImagePath(self, data): if data.isNull(): data = None self.set_icon('B', 'image', data) def getBottomImagePath(self): return self._dummyPixmap def resetBottomImagePath(self): pass left_image_path = QtCore.pyqtProperty(QPixmap, getLeftImagePath, setLeftImagePath, resetLeftImagePath) right_image_path = QtCore.pyqtProperty(QPixmap, getRightImagePath, setRightImagePath, resetRightImagePath) center_image_path = QtCore.pyqtProperty(QPixmap, getCenterImagePath, setCenterImagePath, resetCenterImagePath) top_image_path = QtCore.pyqtProperty(QPixmap, getTopImagePath, setTopImagePath, resetTopImagePath) bottom_image_path = QtCore.pyqtProperty(QPixmap, getBottomImagePath, setBottomImagePath, resetBottomImagePath) def getFont(self): return self._font def setFont(self, value): self._font = value def resetFont(self): self._font = QFont('Lato Heavy', 20) button_font = QtCore.pyqtProperty(QFont, getFont, setFont, resetFont) def setLeftText(self, data): self._textL = data if not data.strip(): data = None self.set_icon('L', 'text', data) def getLeftText(self): return self._textL def resetLeftText(self): self._textL = '' self.set_icon('L', 'text', '') def setRightText(self, data): self._textR = data if not data.strip(): data = None self.set_icon('R', 'text', data) def getRightText(self): return self._textR def resetRightText(self): self._textR = '' self.set_icon('R', 'text', '') def setCenterText(self, data): self._textC = data if not data.strip(): data = None self.set_icon('C', 'text', data) def getCenterText(self): return self._textC def resetCenterText(self): self._textC = '' self.set_icon('C', 'text', '') def setTopText(self, data): self._textT = data if not data.strip(): data = None self.set_icon('T', 'text', data) def getTopText(self): return self._textT def resetTopText(self): self._textT = '' self.set_icon('T', 'text', '') def setBottomText(self, data): self._textB = data if not data.strip(): data = None self.set_icon('B', 'text', data) def getBottomText(self): return self._textB def resetBottomText(self): self._textB = '' self.set_icon('B', 'text', '') left_text = QtCore.pyqtProperty(str, getLeftText, setLeftText, resetLeftText) right_text = QtCore.pyqtProperty(str, getRightText, setRightText, resetRightText) center_text = QtCore.pyqtProperty(str, getCenterText, setCenterText, resetCenterText) top_text = QtCore.pyqtProperty(str, getTopText, setTopText, resetTopText) bottom_text = QtCore.pyqtProperty(str, getBottomText, setBottomText, resetBottomText) @QtCore.pyqtSlot(QColor) def set_true_color(self, color): self._true_color = color self.setLight(self.colorState) @QtCore.pyqtSlot(str) def set_true_color(self, color): self._true_color = QColor(color) self.setLight(self.colorState) def get_true_color(self): return self._true_color def reset_true_color(self): self._true_color = QColor('lawngreen') self.setLight(self.colorState) @QtCore.pyqtSlot(QColor) def set_false_color(self, color): self._false_color = color self.setLight(self.colorState) @QtCore.pyqtSlot(str) def set_false_color(self, color): self._false_color = QColor(color) self.setLight(self.colorState) def get_false_color(self): return self._false_color def reset_false_color(self): self._false_color = QColor('gray') self.setLight(self.colorState) def set_text_color(self, color): self._text_color = QColor(color) self.update() def get_text_color(self): return self._text_color def reset_text_color(self): self._text_color = QColor('white') self.update() true_color = QtCore.pyqtProperty(QColor, get_true_color, set_true_color, reset_true_color) false_color = QtCore.pyqtProperty(QColor, get_false_color, set_false_color, reset_false_color) text_color = QtCore.pyqtProperty(QColor, get_text_color, set_text_color, reset_text_color) @QtCore.pyqtSlot(str) def btn_pressed(self, btn): print("Button pressed", btn) @QtCore.pyqtSlot(str) def btn_released(self, btn): print("Button released", btn) # required code for object indexing def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): return setattr(self, item, value)
class TestPathFinding(TestCase): ################################################################################ def setUp(self): """ """ # --left --right # ' ' # I | II | III # ------+==========+------ --top # VIII | IX (in) | IV # ------+==========+------ --bottom # VII | VI | V self._offset = 10 self._rect = QRectF(QPointF(-20, -10), QPointF(20, 10)) self._pointI = QPointF(self._rect.left()-self._offset, self._rect.top()-self._offset) self._pointII = QPointF(self._rect.center().x(), self._rect.top()-self._offset) self._pointIII = QPointF(self._rect.right()+ self._offset, self._rect.top()- self._offset) self._pointIV = QPointF(self._rect.right()+self._offset, self._rect.center().y()) self._pointV = QPointF(self._rect.right()+self._offset, self._rect.bottom()+self._offset) self._pointVI = QPointF(self._rect.center().x(), self._rect.bottom()+self._offset) self._pointVII = QPointF(self._rect.left()-self._offset, self._rect.bottom()+self._offset) self._pointVIII = QPointF(self._rect.left()-self._offset, self._rect.center().y()) self._pointIX = self._rect.center() self._lineI_VII = QLineF(self._pointI, self._pointVII) self._lineI_V = QLineF(self._pointI, self._pointV) self._lineII = QLineF(self._pointII, QPointF(self._rect.center().x(), self._rect.top())) self._lineII_IV = QLineF(QPointF(self._rect.right()-self._offset, self._rect.top()-self._offset), QPointF(self._rect.right()+self._offset, self._rect.top()+self._offset)) ################################################################################ def testPointRectDist(self): """ """ lineI = PathFinding.pointRectDist(self._pointI, self._rect) self.assertEqual(lineI.p2(), self._rect.topLeft()) self.assertEqual(lineI.length(), pow(pow(self._offset, 2)+pow(self._offset, 2), 0.5)) lineII = PathFinding.pointRectDist(self._pointII, self._rect) self.assertEqual(lineII.p2(), QPointF(self._rect.center().x(), self._rect.top())) self.assertEqual(lineII.length(), self._offset) lineIII = PathFinding.pointRectDist(self._pointIII, self._rect) self.assertEqual(lineIII.p2(), self._rect.topRight()) self.assertEqual(lineIII.length(), pow(pow(self._offset, 2)+pow(self._offset, 2), 0.5)) lineIV = PathFinding.pointRectDist(self._pointIV, self._rect) self.assertEqual(lineIV.p2(), QPointF(self._rect.right(), self._rect.center().y())) self.assertEqual(lineIV.length(), self._offset) lineV = PathFinding.pointRectDist(self._pointV, self._rect) self.assertEqual(lineV.p2(), self._rect.bottomRight()) self.assertEqual(lineV.length(), pow(pow(self._offset, 2)+pow(self._offset, 2), 0.5)) lineVI = PathFinding.pointRectDist(self._pointVI, self._rect) self.assertEqual(lineVI.p2(), QPointF(self._rect.center().x(), self._rect.bottom())) self.assertEqual(lineVI.length(), self._offset) lineVII = PathFinding.pointRectDist(self._pointVII, self._rect) self.assertEqual(lineVII.p2(), self._rect.bottomLeft()) self.assertEqual(lineVII.length(), pow(pow(self._offset, 2)+pow(self._offset, 2), 0.5)) lineVIII = PathFinding.pointRectDist(self._pointVIII, self._rect) self.assertEqual(lineVIII.p2(), QPointF(self._rect.left(), self._rect.center().y())) self.assertEqual(lineVIII.length(), self._offset) lineIX = PathFinding.pointRectDist(self._pointIX, self._rect) self.assertEqual(lineIX.p2(), self._pointIX) self.assertEqual(lineIX.length(), 0) ################################################################################ def testIntersects(self): """ """ rect = QRectF(QPointF(-50, -10), QPointF(50, 10)) # line completely outside of the rectangle self.assertFalse(PathFinding.intersects(rect, QLineF(QPointF(-100, -50), QPointF(-100, 50)))) # the line is a top side of the rectangle self.assertFalse(PathFinding.intersects(rect, QLineF(rect.topLeft(), rect.topRight()))) # the line starts at the left corner of the rectangle and is not perpendicular to any of the rectangle sides; # the line ends outside of the rectangle, not going through it self.assertFalse(PathFinding.intersects(rect, QLineF(rect.topLeft(), QPointF(-100, -100)))) # the line starts at the left corner of the rectangle and is perpendicular to the top side of the rectangle; # the line ends outside of the rectangle, not going through it self.assertFalse(PathFinding.intersects(rect, QLineF(rect.topLeft(), QPointF(rect.left(), rect.top() - 100)))) # the line is horizontal and goes straight through the center of the rectangle self.assertTrue(PathFinding.intersects(rect, QLineF(QPointF(-100, 0), QPointF(100, 0)))) # the line is vertical and goes straight through the center of the rectangle self.assertTrue(PathFinding.intersects(rect, QLineF(QPointF(0, -100), QPointF(0, 100)))) # the line is vertical and goes up from the bottom right corner of the rectangle self.assertFalse(PathFinding.intersects(rect, QLineF(rect.bottomRight(), QPointF(rect.right(), rect.top()-100)))) # the line is diagonal of the rectangle self.assertTrue(PathFinding.intersects(rect, QLineF(rect.topLeft(), rect.bottomRight())))
def drawMagnifier(self): # First, calculate the magnifier position due to the mouse position watchAreaWidth = 16 watchAreaHeight = 16 watchAreaPixmap = QPixmap() cursor_pos = self.mousePoint watchArea = QRect( QPoint(cursor_pos.x() - watchAreaWidth / 2, cursor_pos.y() - watchAreaHeight / 2), QPoint(cursor_pos.x() + watchAreaWidth / 2, cursor_pos.y() + watchAreaHeight / 2)) if watchArea.left() < 0: watchArea.moveLeft(0) watchArea.moveRight(watchAreaWidth) if self.mousePoint.x() + watchAreaWidth / 2 >= self.screenPixel.width( ): watchArea.moveRight(self.screenPixel.width() - 1) watchArea.moveLeft(watchArea.right() - watchAreaWidth) if self.mousePoint.y() - watchAreaHeight / 2 < 0: watchArea.moveTop(0) watchArea.moveBottom(watchAreaHeight) if self.mousePoint.y( ) + watchAreaHeight / 2 >= self.screenPixel.height(): watchArea.moveBottom(self.screenPixel.height() - 1) watchArea.moveTop(watchArea.bottom() - watchAreaHeight) # tricks to solve the hidpi impact on QCursor.pos() watchArea.setTopLeft( QPoint(watchArea.topLeft().x() * self.scale, watchArea.topLeft().y() * self.scale)) watchArea.setBottomRight( QPoint(watchArea.bottomRight().x() * self.scale, watchArea.bottomRight().y() * self.scale)) watchAreaPixmap = self.screenPixel.copy(watchArea) # second, calculate the magnifier area magnifierAreaWidth = watchAreaWidth * 10 magnifierAreaHeight = watchAreaHeight * 10 fontAreaHeight = 40 cursorSize = 24 magnifierArea = QRectF( QPoint(QCursor.pos().x() + cursorSize, QCursor.pos().y() + cursorSize), QPoint(QCursor.pos().x() + cursorSize + magnifierAreaWidth, QCursor.pos().y() + cursorSize + magnifierAreaHeight)) if magnifierArea.right() >= self.screenPixel.width(): magnifierArea.moveLeft(QCursor.pos().x() - magnifierAreaWidth - cursorSize / 2) if magnifierArea.bottom() + fontAreaHeight >= self.screenPixel.height( ): magnifierArea.moveTop(QCursor.pos().y() - magnifierAreaHeight - cursorSize / 2 - fontAreaHeight) # third, draw the watch area to magnifier area watchAreaScaled = watchAreaPixmap.scaled( QSize(magnifierAreaWidth * self.scale, magnifierAreaHeight * self.scale)) magnifierPixmap = self.graphicsScene.addPixmap(watchAreaScaled) magnifierPixmap.setOffset(magnifierArea.topLeft()) # then draw lines and text self.graphicsScene.addRect(QRectF(magnifierArea), QPen(QColor(255, 255, 255), 2)) self.graphicsScene.addLine( QLineF(QPointF(magnifierArea.center().x(), magnifierArea.top()), QPointF(magnifierArea.center().x(), magnifierArea.bottom())), QPen(QColor(0, 255, 255), 2)) self.graphicsScene.addLine( QLineF(QPointF(magnifierArea.left(), magnifierArea.center().y()), QPointF(magnifierArea.right(), magnifierArea.center().y())), QPen(QColor(0, 255, 255), 2)) # get the rgb of mouse point pointRgb = QColor(self.screenPixel.toImage().pixel(self.mousePoint)) # draw information self.graphicsScene.addRect( QRectF( magnifierArea.bottomLeft(), magnifierArea.bottomRight() + QPoint(0, fontAreaHeight + 30)), Qt.black, QBrush(Qt.black)) rgbInfo = self.graphicsScene.addSimpleText( ' Rgb: ({0}, {1}, {2})'.format(pointRgb.red(), pointRgb.green(), pointRgb.blue())) rgbInfo.setPos(magnifierArea.bottomLeft() + QPoint(0, 5)) rgbInfo.setPen(QPen(QColor(255, 255, 255), 2)) rect = self.selectedArea.normalized() sizeInfo = self.graphicsScene.addSimpleText(' Size: {0} x {1}'.format( rect.width() * self.scale, rect.height() * self.scale)) sizeInfo.setPos(magnifierArea.bottomLeft() + QPoint(0, 15) + QPoint(0, fontAreaHeight / 2)) sizeInfo.setPen(QPen(QColor(255, 255, 255), 2))
def redraw(self): self.graphicsScene.clear() # draw screenshot self.graphicsScene.addPixmap(self.screenPixel) # prepare for drawing selected area rect = QRectF(self.selectedArea) rect = rect.normalized() topLeftPoint = rect.topLeft() topRightPoint = rect.topRight() bottomLeftPoint = rect.bottomLeft() bottomRightPoint = rect.bottomRight() topMiddlePoint = (topLeftPoint + topRightPoint) / 2 leftMiddlePoint = (topLeftPoint + bottomLeftPoint) / 2 bottomMiddlePoint = (bottomLeftPoint + bottomRightPoint) / 2 rightMiddlePoint = (topRightPoint + bottomRightPoint) / 2 # draw the picture mask mask = QColor(0, 0, 0, 155) if self.selectedArea == QRect(): self.graphicsScene.addRect(0, 0, self.screenPixel.width(), self.screenPixel.height(), QPen(Qt.NoPen), mask) else: self.graphicsScene.addRect(0, 0, self.screenPixel.width(), topRightPoint.y(), QPen(Qt.NoPen), mask) self.graphicsScene.addRect(0, topLeftPoint.y(), topLeftPoint.x(), rect.height(), QPen(Qt.NoPen), mask) self.graphicsScene.addRect( topRightPoint.x(), topRightPoint.y(), self.screenPixel.width() - topRightPoint.x(), rect.height(), QPen(Qt.NoPen), mask) self.graphicsScene.addRect( 0, bottomLeftPoint.y(), self.screenPixel.width(), self.screenPixel.height() - bottomLeftPoint.y(), QPen(Qt.NoPen), mask) # draw the toolBar if self.action != ACTION_SELECT: spacing = 5 # show the toolbar first, then move it to the correct position # because the width of it may be wrong if this is the first time it shows self.tooBar.show() dest = QPointF(rect.bottomRight() - QPointF(self.tooBar.width(), 0) - QPointF(spacing, -spacing)) if dest.x() < spacing: dest.setX(spacing) pen_set_bar_height = self.penSetBar.height( ) if self.penSetBar is not None else 0 if dest.y() + self.tooBar.height( ) + pen_set_bar_height >= self.height(): if rect.top() - self.tooBar.height( ) - pen_set_bar_height < spacing: dest.setY(rect.top() + spacing) else: dest.setY(rect.top() - self.tooBar.height() - pen_set_bar_height - spacing) self.tooBar.move(dest.toPoint()) if self.penSetBar is not None: self.penSetBar.show() self.penSetBar.move(dest.toPoint() + QPoint(0, self.tooBar.height() + spacing)) if self.action == ACTION_TEXT: self.penSetBar.showFontWidget() else: self.penSetBar.showPenWidget() else: self.tooBar.hide() if self.penSetBar is not None: self.penSetBar.hide() # draw the list for step in self.drawListResult: self.drawOneStep(step) if self.drawListProcess is not None: self.drawOneStep(self.drawListProcess) if self.action != ACTION_TEXT: self.drawListProcess = None if self.selectedArea != QRect(): self.itemsToRemove = [] # draw the selected rectangle pen = QPen(QColor(0, 255, 255), 2) self.itemsToRemove.append(self.graphicsScene.addRect(rect, pen)) # draw the drag point radius = QPoint(3, 3) brush = QBrush(QColor(0, 255, 255)) self.itemsToRemove.append( self.graphicsScene.addEllipse( QRectF(topLeftPoint - radius, topLeftPoint + radius), pen, brush)) self.itemsToRemove.append( self.graphicsScene.addEllipse( QRectF(topMiddlePoint - radius, topMiddlePoint + radius), pen, brush)) self.itemsToRemove.append( self.graphicsScene.addEllipse( QRectF(topRightPoint - radius, topRightPoint + radius), pen, brush)) self.itemsToRemove.append( self.graphicsScene.addEllipse( QRectF(leftMiddlePoint - radius, leftMiddlePoint + radius), pen, brush)) self.itemsToRemove.append( self.graphicsScene.addEllipse( QRectF(rightMiddlePoint - radius, rightMiddlePoint + radius), pen, brush)) self.itemsToRemove.append( self.graphicsScene.addEllipse( QRectF(bottomLeftPoint - radius, bottomLeftPoint + radius), pen, brush)) self.itemsToRemove.append( self.graphicsScene.addEllipse( QRectF(bottomMiddlePoint - radius, bottomMiddlePoint + radius), pen, brush)) self.itemsToRemove.append( self.graphicsScene.addEllipse( QRectF(bottomRightPoint - radius, bottomRightPoint + radius), pen, brush)) # draw the textedit if self.textPosition is not None: textSpacing = 50 position = QPoint() if self.textPosition.x() + self.textInput.width( ) >= self.screenPixel.width(): position.setX(self.textPosition.x() - self.textInput.width()) else: position.setX(self.textPosition.x()) if self.textRect is not None: if self.textPosition.y() + self.textInput.height( ) + self.textRect.height() >= self.screenPixel.height(): position.setY(self.textPosition.y() - self.textInput.height() - self.textRect.height()) else: position.setY(self.textPosition.y() + self.textRect.height()) else: if self.textPosition.y() + self.textInput.height( ) >= self.screenPixel.height(): position.setY(self.textPosition.y() - self.textInput.height()) else: position.setY(self.textPosition.y()) self.textInput.move(position) self.textInput.show() # self.textInput.getFocus() # draw the magnifier if self.action == ACTION_SELECT: self.drawMagnifier() if self.mousePressed: self.drawSizeInfo() if self.action == ACTION_MOVE_SELECTED: self.drawSizeInfo()
def drawPrimitive(self, element, opt, painter, widget): if not self.__panel_widget(widget): return QProxyStyle.drawPrimitive(self, element, opt, painter, widget) if element == QStyle.PE_PanelButtonTool: flat = True pressed = (opt.state & STATE_SUNKEN or opt.state & STATE_ON) hovered = opt.state & STATE_ENABLED and opt.state & STATE_MOUSEOVER button_color = _COLORS['ToolButtonColor'] if not flat and widget.property("gradient"): button_color = QLinearGradient(opt.rect.topRight(), opt.rect.bottomRight()) button_color.setColorAt(0, QColor("#454545")) button_color.setColorAt(1, QColor("#191919")) if pressed: button_color = _COLORS['ToolButtonSelected'] elif hovered: if not flat and widget.property("gradient"): button_color.setColorAt(0, QColor("#555")) button_color.setColorAt(1, QColor("#191919")) else: button_color = _COLORS['ToolButtonHover'] if widget.property("border_bottom"): painter.setPen(_COLORS['Border']) painter.drawLine(opt.rect.bottomLeft(), opt.rect.bottomRight()) painter.fillRect(opt.rect.adjusted(2, 2, -2, -2), button_color) # elif not opt.state & STATE_ENABLED: # color = _PALETTE['ButtonDisabled'] # painter.fillRect(opt.rect, color) # TODO: keyboard focus change state # elif element == QStyle.PE_PanelButtonCommand: # Draw a flat push button # is_down = opt.state & STATE_SUNKEN or opt.state & STATE_ON # is_enabled = opt.state & STATE_ENABLED # is_hover = is_enabled and opt.state & STATE_MOUSEOVER # FIXME: has_focus state # FIXME: from theme # color = QColor("#444a58") # if is_down: # color = color.darker(130) # elif is_hover: # color = color.lighter(110) # painter.fillRect(opt.rect, color) elif element == QStyle.PE_PanelLineEdit: painter.save() # Fill background rect = opt.rect enabled = False if opt.state & STATE_ENABLED: enabled = True if not enabled: painter.setOpacity(0.55) painter.fillRect(rect, _COLORS['LineEditBackground']) has_focus = False if opt.state & QStyle.State_HasFocus: has_focus = True if enabled and (has_focus or opt.state & STATE_MOUSEOVER): # FIXME: color from theme # hover = QColor("#6a6ea9") # if has_focus: # alpha = 200 # else: # alpha = 55 # hover.setAlpha(alpha) # painter.setPen(QPen(hover, 2, Qt.SolidLine, # Qt.SquareCap, Qt.RoundJoin)) # painter.drawRect(rect.adjusted(0, 0, 0, 0)) pass painter.restore() elif element == QStyle.PE_IndicatorToolBarSeparator: rect = opt.rect painter.setPen(_COLORS['SeparatorColor']) border_rect = QRectF(rect).adjusted(0.5, 0.5, -0.5, -0.5) if opt.state & QStyle.State_Horizontal: border_rect.setWidth(1) painter.drawLine(border_rect.topRight() + QPointF(0, 3), border_rect.bottomRight() - QPointF(0, 3)) else: border_rect.setHeight(1) painter.drawLine(border_rect.topLeft() + QPointF(3, 0), border_rect.topRight() - QPointF(3, 0)) elif element == QStyle.PE_IndicatorToolBarHandle: # FIXME: draw a fancy handler QProxyStyle.drawPrimitive(self, element, opt, painter, widget) else: QProxyStyle.drawPrimitive(self, element, opt, painter, widget)
def _hashMarkGen(path, p1, p2, p3): path.moveTo(p1) path.lineTo(p2) path.lineTo(p3) # end # create hash marks QPainterPaths only once _PP_RECT = QRectF(0, 0, styles.PATH_BASE_WIDTH, styles.PATH_BASE_WIDTH) _PATH_CENTER = QPointF(styles.PATH_BASE_WIDTH / 2,\ styles.PATH_BASE_WIDTH / 2) _PATH_U_CENTER = QPointF(styles.PATH_BASE_WIDTH / 2, 0) _PATH_D_CENTER = QPointF(styles.PATH_BASE_WIDTH / 2, styles.PATH_BASE_WIDTH) _PPATH_LU = QPainterPath() _hashMarkGen(_PPATH_LU, _PP_RECT.bottomLeft(), _PATH_D_CENTER, _PATH_CENTER) _PPATH_RU = QPainterPath() _hashMarkGen(_PPATH_RU, _PP_RECT.bottomRight(), _PATH_D_CENTER, _PATH_CENTER) _PPATH_RD = QPainterPath() _hashMarkGen(_PPATH_RD, _PP_RECT.topRight(), _PATH_U_CENTER, _PATH_CENTER) _PPATH_LD = QPainterPath() _hashMarkGen(_PPATH_LD, _PP_RECT.topLeft(), _PATH_U_CENTER, _PATH_CENTER) _SCAF_PEN = QPen(styles.PXI_SCAF_STROKE, styles.PATH_STRAND_STROKE_WIDTH) _SCAF_PEN.setCapStyle(Qt.FlatCap) # or Qt.RoundCap _SCAF_PEN.setJoinStyle(Qt.RoundJoin) _STAP_PEN = QPen(styles.PXI_STAP_STROKE, styles.PATH_STRAND_STROKE_WIDTH) _STAP_PEN.setCapStyle(Qt.FlatCap) # or Qt.RoundCap _STAP_PEN.setJoinStyle(Qt.RoundJoin) _DISAB_PEN = QPen(styles.PXI_DISAB_STROKE, styles.PATH_STRAND_STROKE_WIDTH) _DISAB_PEN.setCapStyle(Qt.FlatCap) _DISAB_PEN.setJoinStyle(Qt.RoundJoin) _DISAB_BRUSH = QBrush(styles.PXI_DISAB_STROKE) # For the helix number label
class RectZoomMoveView(QChartView): """ Filter data to be displayed in rectangular body """ rangeSig = pyqtSignal(list) def __init__(self, parent=None): super(RectZoomMoveView, self).__init__(parent) self.setChart(QChart()) self.chart().setMargins(QMargins(5, 5, 5, 5)) self.chart().setContentsMargins(-10, -10, -10, -10) self.chart().setTitle(" ") self.relationState = True # Define two rectangles for background and drawing respectively self.parentRect = QGraphicsRectItem(self.chart()) self.parentRect.setFlag(QGraphicsItem.ItemClipsChildrenToShape, True) self.parentRect.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.RangeItem = RectRangeItem(parent=self.parentRect) self.RangeItem.setZValue(998) pen = QPen(Qt.gray) pen.setWidth(1) self.parentRect.setPen(pen) self.parentRect.setZValue(997) self.scene().addItem(self.parentRect) self.scene().addItem(self.RangeItem) self.RangeItem.hide() self.m_chartRectF = QRectF() self.m_rubberBandOrigin = QPointF(0, 0) self.dataLength = 0 self.RangeItem.selectedChange.connect(self.changeFromRectItem) self.BtnsWidget = ViewButtonsWidget(self) self.BtnsWidget.refreshBtn.clicked.connect(self.updateView) self.BtnsWidget.RelationSig.connect(self.setRelationState) self.BtnsWidget.dateRangeEdit.dateRangeSig.connect(self.changDateRect) def changDateRect(self, daterange): if self.chartTypes == "Bar": v = 3 else: v = 2 l = len(self.RangeItem.rangePoints) if l > 2: try: num = self.mintimeData.date().daysTo(daterange[0]) left = self.RangeItem.rangePoints[num] num = self.mintimeData.date().daysTo(daterange[1]) right = self.RangeItem.rangePoints[num] rect = self.chart().plotArea() rect.setLeft(left) rect.setRight(right) except: rect = self.chart().plotArea() self.RangeItem.setRect(rect) self.RangeItem.updateHandlesPos() else: try: num = self.mintimeData.date().daysTo(daterange[0]) left = self.RangeItem.rangePoints[num] num = self.mintimeData.date().daysTo(daterange[0]) right = self.RangeItem.rangePoints[num] rect = self.chart().plotArea() rect.setLeft(left) rect.setRight(right) except: rect = self.chart().plotArea() self.RangeItem.setRect(rect) self.RangeItem.updateHandlesPos() def lineSpace(self, start, end, num): res = [] if self.chartTypes == "Bar": step = (end - start) / (num) for i in range(num + 2): res.append(start + i * step) else: step = (end - start) / (num - 1) for i in range(num + 1): res.append(start + i * step) return res def getRangePoints(self): count = self.zoomSeries.count() rect = self.chart().plotArea() left = rect.left() right = rect.right() if count == 0: self.RangeItem.rangePoints = [left, right] else: # Get coordinate position for each node self.RangeItem.rangePoints = self.lineSpace(left, right, count) def setRangeColor(self, color): self.RangeItem.setRangeColor(color) def setRelationState(self, state): self.relationState = state def initSeries(self, chartTypes="Bar"): self.chartTypes = chartTypes axisX = QDateTimeAxis() axisX.setFormat("yyyy-MM-dd") self.zoomSeries = QLineSeries(self.chart()) self.chart().addSeries(self.zoomSeries) self.chart().setAxisY(QValueAxis(), self.zoomSeries) self.chart().setAxisX(axisX, self.zoomSeries) self.initView() def clearAll(self): # Clear all series and axes self.chart().removeAllSeries() axess = self.chart().axes() for axes in axess: self.chart().removeAxis(axes) def setData(self, timeData, valueData, chartTypes="Bar"): axisX = QDateTimeAxis() axisX.setFormat("yyyy-MM-dd") if self.chartTypes == "Bar": # Clear all series self.clearAll() self.zoomSeries = QLineSeries(self.chart()) barSeries = QBarSeries(self.chart()) barset = QBarSet("data") barSeries.setBarWidth(0.8) barSeries.append(barset) for td, vd in zip(timeData, valueData): self.zoomSeries.append(td.toMSecsSinceEpoch(), vd) barset.append(valueData) self.zoomSeries.hide() self.chart().addSeries(self.zoomSeries) self.chart().addSeries(barSeries) self.chart().setAxisY(QValueAxis(), self.zoomSeries) axisX.setRange(min(timeData), max(timeData)) self.chart().setAxisX(axisX, self.zoomSeries) elif self.chartTypes == "Scatter": # Clear all series self.clearAll() self.zoomSeries = QLineSeries(self.chart()) scattSeries = QScatterSeries(self.chart()) scattSeries.setMarkerSize(8) for td, vd in zip(timeData, valueData): self.zoomSeries.append(td.toMSecsSinceEpoch(), vd) scattSeries.append(td.toMSecsSinceEpoch(), vd) self.zoomSeries.hide() self.chart().addSeries(self.zoomSeries) self.chart().addSeries(scattSeries) self.chart().setAxisY(QValueAxis(), self.zoomSeries) axisX.setRange(min(timeData), max(timeData)) self.chart().setAxisX(axisX, self.zoomSeries) elif self.chartTypes in ["Line", "PLine"]: self.clearAll() if self.chartTypes == "Line": self.zoomSeries = QLineSeries(self.chart()) else: self.zoomSeries = QSplineSeries(self.chart()) for td, vd in zip(timeData, valueData): self.zoomSeries.append(td.toMSecsSinceEpoch(), vd) self.chart().addSeries(self.zoomSeries) self.chart().setAxisY(QValueAxis(), self.zoomSeries) axisX.setRange(min(timeData), max(timeData)) self.chart().setAxisX(axisX, self.zoomSeries) elif self.chartTypes == "Area": self.clearAll() self.zoomSeries = QLineSeries() self.zoomSeries.setColor(QColor("#666666")) for td, vd in zip(timeData, valueData): self.zoomSeries.append(td.toMSecsSinceEpoch(), vd) areaSeries = QAreaSeries(self.zoomSeries, None) self.chart().addSeries(self.zoomSeries) self.chart().addSeries(areaSeries) self.chart().setAxisY(QValueAxis(), areaSeries) axisX.setRange(min(timeData), max(timeData)) self.chart().setAxisX(axisX, areaSeries) self.zoomSeries.hide() self.mintimeData = min(timeData) self.maxtimeData = max(timeData) self.BtnsWidget.dateRangeEdit.setDateRange([ self.mintimeData.toString("yyyy-MM-dd"), self.maxtimeData.toString("yyyy-MM-dd"), ]) self.updateView() def resetView(self): rect = self.chart().plotArea() self.parentRect.setRect(rect) topRight = self.chart().plotArea().topRight() x = int(topRight.x()) y = int(topRight.y()) self.BtnsWidget.setGeometry(QRect(x - 420, 0, 420, 23)) self.RangeItem.setRect(rect) self.RangeItem.show() self.save_current_rubber_band() self.RangeItem.updateHandlesPos() self.apply_nice_numbers() self.getRangePoints() self.sendRang() def initView(self): self.RangeItem.hide() # Hide y-axis if self.chart().axisY(): self.chart().axisY().setVisible(False) if self.chart().axisX(): self.chart().axisX().setGridLineVisible(False) self.m_chartRectF = QRectF() self.m_rubberBandOrigin = QPointF(0, 0) self.getRangePoints() def updateView(self): self.RangeItem.hide() # Hide y-axis if self.chart().axisY(): self.chart().axisY().setVisible(False) if self.chart().axisX(): self.chart().axisX().setGridLineVisible(False) self.m_chartRectF = QRectF() self.m_rubberBandOrigin = QPointF(0, 0) self.resetView() # Map points to chart def point_to_chart(self, pnt): scene_point = self.mapToScene(pnt) chart_point = self.chart().mapToValue(scene_point) return chart_point # Map chart to points def chart_to_view_point(self, char_coord): scene_point = self.chart().mapToPosition(char_coord) view_point = self.mapFromScene(scene_point) return view_point # Save positions of rectangles def save_current_rubber_band(self): rect = self.RangeItem.rect() chart_top_left = self.point_to_chart(rect.topLeft().toPoint()) self.m_chartRectF.setTopLeft(chart_top_left) chart_bottom_right = self.point_to_chart(rect.bottomRight().toPoint()) self.m_chartRectF.setBottomRight(chart_bottom_right) # Respond to change in positions of rectangles def changeFromRectItem(self, rectIndex): self.save_current_rubber_band() self.sendRang(rectIndex) def sendRang(self, rectIndex=[]): if self.RangeItem.rect() != self.parentRect.rect(): self.BtnsWidget.setPalActive() else: self.BtnsWidget.setPalDisActive() if self.chartTypes == "Bar": v = 3 else: v = 2 if rectIndex == []: maxData = QDateTime.fromMSecsSinceEpoch( self.zoomSeries.at(len(self.RangeItem.rangePoints) - v).x()) minData = QDateTime.fromMSecsSinceEpoch(self.zoomSeries.at(0).x()) else: minData = max(rectIndex[0], 0) maxData = min(rectIndex[1], len(self.RangeItem.rangePoints) - v) minData = QDateTime.fromMSecsSinceEpoch( self.zoomSeries.at(minData).x()) maxData = QDateTime.fromMSecsSinceEpoch( self.zoomSeries.at(maxData).x()) if minData > maxData: if self.RangeItem.handleSelected is None: self.resetView() else: self.BtnsWidget.dateRangeEdit.setDate([ minData.toString("yyyy-MM-dd"), maxData.toString("yyyy-MM-dd") ]) if self.relationState: self.rangeSig.emit([ minData.toString("yyyy-MM-dd HH:mm:ss"), maxData.toString("yyyy-MM-dd HH:mm:ss"), ]) # Change positions of rectangles in scaling def resizeEvent(self, event): super().resizeEvent(event) rect = self.chart().plotArea() self.parentRect.setRect(rect) self.getRangePoints() topRight = self.chart().plotArea().topRight() x = int(topRight.x()) y = int(topRight.y()) self.BtnsWidget.setGeometry(QRect(x - 420, 0, 420, 23)) if self.RangeItem.isVisible(): self.restore_rubber_band() self.save_current_rubber_band() self.RangeItem.updateHandlesPos() else: self.RangeItem.setRect(self.parentRect.rect()) self.RangeItem.show() self.RangeItem.setRect(self.parentRect.rect()) self.save_current_rubber_band() self.RangeItem.updateHandlesPos() self.apply_nice_numbers() # Restore to original positions of rectangles def restore_rubber_band(self): view_top_left = self.chart_to_view_point(self.m_chartRectF.topLeft()) view_bottom_right = self.chart_to_view_point( self.m_chartRectF.bottomRight()) self.m_rubberBandOrigin = view_top_left height = self.chart().plotArea().height() rect = QRectF() rect.setTopLeft(view_top_left) rect.setBottomRight(view_bottom_right) rect.setHeight(height) self.RangeItem.setRect(rect) # Adjust display coordinates of axes automatically def apply_nice_numbers(self): axes_list = self.chart().axes() for value_axis in axes_list: if value_axis: pass
class TextBoxItem(QGraphicsItem): max_rect = QRect(-56, -20, 112, 40) init_offset = QPointF(66, -34) dummy_contents = 'XX-ABCDE ####\n##### xxxxx\n10000 = 10000 X####' dummy_contents_compact = 'XX-ABCDE\n10000 #### ' txt_rect_2lines = QRectF() # STATIC txt_rect_3lines = QRectF() # STATIC def setRectanglesFromFont(font): TextBoxItem.txt_rect_2lines = QRectF( QFontMetrics(font).boundingRect( TextBoxItem.max_rect, Qt.AlignLeft, TextBoxItem.dummy_contents_compact)) TextBoxItem.txt_rect_3lines = QRectF( QFontMetrics(font).boundingRect(TextBoxItem.max_rect, Qt.AlignLeft, TextBoxItem.dummy_contents)) def __init__(self, parent_item): QGraphicsItem.__init__(self, parent_item) self.radar_contact = parent_item.radar_contact self.info_text = '' self.rectangle = QRectF() self.setCursor(Qt.PointingHandCursor) self.setPos(TextBoxItem.init_offset) self.mouse_hovering = False self.paint_border = True self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setAcceptHoverEvents(True) self.updateContents() def updateContents(self): self.prepareGeometryChange() expand = self.mouse_hovering or self.radar_contact is selection.acft or env.linkedStrip( self.radar_contact) != None self.paint_border = expand self.info_text = infoTextLines(self.radar_contact, not expand) self.rectangle = TextBoxItem.txt_rect_3lines if expand else TextBoxItem.txt_rect_2lines def positionQuadrant(self): return (1 if self.pos().x() > 0 else -1), (1 if self.pos().y() > 0 else -1) def calloutConnectingPoint(self): q = self.positionQuadrant() if q == (-1, -1): return self.rectangle.bottomRight() elif q == (-1, 1): return self.rectangle.topRight() elif q == (1, -1): return self.rectangle.bottomLeft() elif q == (1, 1): return self.rectangle.topLeft() def paint(self, painter, option, widget): coloured_pen = new_pen(ACFT_pen_colour(self.radar_contact)) # 1. Write info text painter.setPen(coloured_pen) painter.drawText(self.rectangle, Qt.AlignLeft | Qt.AlignVCenter, self.info_text) # 2. Draw container box? if self.paint_border: pen = coloured_pen if self.radar_contact is selection.acft else new_pen( settings.colour('radar_tag_line')) if self.radar_contact.individual_cheat: pen.setStyle(Qt.DashLine) painter.setPen(pen) painter.drawRect(self.rectangle) def boundingRect(self): return self.rectangle # EVENTS def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionChange: self.parentItem().textBoxChanged() return QGraphicsItem.itemChange(self, change, value) def hoverEnterEvent(self, event): self.mouse_hovering = True self.updateContents() self.parentItem().textBoxChanged() QGraphicsItem.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): self.mouse_hovering = False self.updateContents() self.parentItem().textBoxChanged() QGraphicsItem.hoverLeaveEvent(self, event) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: selection.selectAircraft(self.radar_contact) elif event.button() == Qt.MiddleButton: if event.modifiers() & Qt.ShiftModifier: selection.unlinkAircraft(self.radar_contact) else: selection.linkAircraft(self.radar_contact) event.accept() QGraphicsItem.mousePressEvent(self, event) def mouseDoubleClickEvent(self, event): if event.button() == Qt.LeftButton: if event.modifiers() & Qt.ShiftModifier: # reset box position self.setPos(TextBoxItem.init_offset) self.parentItem().textBoxChanged() else: strip = selection.strip if strip != None: signals.stripEditRequest.emit(strip) event.accept() else: QGraphicsItem.mouseDoubleClickEvent(self, event)
class PangoBboxGraphic(PangoGraphic): def __init__(self, parent=None): super().__init__(parent) self.setFlag(QGraphicsItem.ItemIsSelectable) self.fpath = None self.rect = QRectF() def paint(self, painter, option, widget): super().paint(painter, option, widget) w = self.dw() pen = self.pen() pen.setWidth(int(w)) painter.setPen(pen) if not self.force_opaque: painter.setOpacity(0.8) painter.drawRect(self.rect) if self.parentItem() is not None: self.paint_text_rect(painter) painter.setOpacity(1) if option.state & QStyle.State_MouseOver or self.isSelected(): painter.drawEllipse(self.rect.topLeft(), w, w) painter.drawEllipse(self.rect.topRight(), w, w) painter.drawEllipse(self.rect.bottomLeft(), w, w) painter.drawEllipse(self.rect.bottomRight(), w, w) def paint_text_rect(self, painter): p = painter.pen() font = QFont() font.setPointSizeF(self.dw()*3) painter.setFont(font) painter.setBrush(self.brush()) fm = QFontMetrics(font) w = fm.width(self.parentItem().name) h = fm.height() br = self.rect.bottomRight() text_rect = QRectF(QPointF(br.x()-w, br.y()-h), br) if text_rect.width() < self.boundingRect().width()/2 and\ text_rect.height() < self.boundingRect().height()/2: painter.drawRect(text_rect) pen = self.pen() pen.setColor(QColor("black")) painter.setPen(pen) painter.drawText(text_rect, Qt.AlignCenter, self.parentItem().name) painter.setPen(p) def boundingRect(self): w = self.dw() return self.shape().controlPointRect().adjusted(-w*2, -w*2, w*2, w*2) def shape(self): path = QPainterPath() path.addRect(self.rect) return self.shape_from_path(path, self.pen())