def __init__(self, parent=None): QGraphicsView.__init__(self, parent) self.setFixedWidth(56) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # self.setFrameStyle(QFrame.NoFrame) # self.setStyleSheet(""" # QGraphicsView { border: 1px solid black } # """) scene = QGraphicsScene() scene.addSimpleText('nice!!') self.text = scene.addText('hello') scene.addText('world') self.setScene(scene)
class Overview(QGraphicsView): """Show an overview of data, such as hypnogram and data in memory. Attributes ---------- parent : instance of QMainWindow the main window. config : ConfigChannels preferences for this widget minimum : int or float start time of the recording, from the absolute time of start_time in s maximum : int or float length of the recordings in s start_time : datetime absolute start time of the recording scene : instance of QGraphicsScene to keep track of the objects idx_current : QGraphicsRectItem instance of the current time window idx_markers : list of QGraphicsRectItem list of markers in the dataset idx_annot : list of QGraphicsRectItem list of user-made annotations """ def __init__(self, parent): super().__init__() self.parent = parent self.config = ConfigOverview(self.update_settings) self.minimum = None self.maximum = None self.start_time = None # datetime, absolute start time self.scene = None self.idx_current = None self.idx_markers = [] self.idx_annot = [] self.setMinimumHeight(TOTAL_HEIGHT + 30) def update(self): """Read full duration and update maximum.""" if self.parent.info.dataset is not None: # read from the dataset, if available header = self.parent.info.dataset.header maximum = header['n_samples'] / header['s_freq'] # in s self.minimum = 0 self.maximum = maximum self.start_time = self.parent.info.dataset.header['start_time'] elif self.parent.notes.annot is not None: # read from annotations annot = self.parent.notes.annot self.minimum = annot.first_second self.maximum = annot.last_second self.start_time = annot.start_time # make it time-zone unaware self.start_time = self.start_time.replace(tzinfo=None) self.parent.value('window_start', 0) # the only value that is reset self.display() def display(self): """Updates the widgets, especially based on length of recordings.""" lg.debug('GraphicsScene is between {}s and {}s'.format(self.minimum, self.maximum)) x_scale = 1 / self.parent.value('overview_scale') lg.debug('Set scene x-scaling to {}'.format(x_scale)) self.scale(1 / self.transform().m11(), 1) # reset to 1 self.scale(x_scale, 1) self.scene = QGraphicsScene(self.minimum, 0, self.maximum, TOTAL_HEIGHT) self.setScene(self.scene) # reset annotations self.idx_markers = [] self.idx_annot = [] self.display_current() for name, pos in BARS.items(): item = QGraphicsRectItem(self.minimum, pos['pos0'], self.maximum, pos['pos1']) item.setToolTip(pos['tip']) self.scene.addItem(item) self.add_timestamps() def add_timestamps(self): """Add timestamps at the bottom of the overview.""" transform, _ = self.transform().inverted() stamps = _make_timestamps(self.start_time, self.minimum, self.maximum, self.parent.value('timestamp_steps')) for stamp, xpos in zip(*stamps): text = self.scene.addSimpleText(stamp) text.setFlag(QGraphicsItem.ItemIgnoresTransformations) # set xpos and adjust for text width text_width = text.boundingRect().width() * transform.m11() text.setPos(xpos - text_width / 2, TIME_HEIGHT) def update_settings(self): """After changing the settings, we need to recreate the whole image.""" self.display() self.display_markers() if self.parent.notes.annot is not None: self.parent.notes.display_notes() def update_position(self, new_position=None): """Update the cursor position and much more. Parameters ---------- new_position : int or float new position in s, for plotting etc. Notes ----- This is a central function. It updates the cursor, then updates the traces, the scores, and the power spectrum. In other words, this function is responsible for keep track of the changes every time the start time of the window changes. """ if new_position is not None: lg.debug('Updating position to {}'.format(new_position)) self.parent.value('window_start', new_position) self.idx_current.setPos(new_position, 0) current_time = (self.start_time + timedelta(seconds=new_position)) msg = 'Current time: ' + current_time.strftime('%H:%M:%S') self.parent.statusBar().showMessage(msg) else: lg.debug('Updating position at {}' ''.format(self.parent.value('window_start'))) if self.parent.info.dataset is not None: self.parent.traces.read_data() if self.parent.traces.data is not None: self.parent.traces.display() self.parent.spectrum.display_window() if self.parent.notes.annot is not None: self.parent.notes.set_stage_index() self.display_current() def display_current(self): """Create a rectangle showing the current window.""" if self.idx_current in self.scene.items(): self.scene.removeItem(self.idx_current) item = QGraphicsRectItem(0, CURR['pos0'], self.parent.value('window_length'), CURR['pos1']) # it's necessary to create rect first, and then move it item.setPos(self.parent.value('window_start'), 0) item.setPen(QPen(Qt.lightGray)) item.setBrush(QBrush(Qt.lightGray)) item.setZValue(-10) self.scene.addItem(item) self.idx_current = item def display_markers(self): """Mark all the markers, from the dataset. This function should be called only when we load the dataset or when we change the settings. """ for rect in self.idx_markers: self.scene.removeItem(rect) self.idx_markers = [] markers = [] if self.parent.info.markers is not None: if self.parent.value('marker_show'): markers = self.parent.info.markers for mrk in markers: rect = QGraphicsRectItem(mrk['start'], BARS['markers']['pos0'], mrk['end'] - mrk['start'], BARS['markers']['pos1']) self.scene.addItem(rect) color = self.parent.value('marker_color') rect.setPen(QPen(QColor(color))) rect.setBrush(QBrush(QColor(color))) rect.setZValue(-5) self.idx_markers.append(rect) def display_annotations(self): """Mark all the bookmarks/events, from annotations. This function is similar to display_markers, but they are called at different stages (f.e. when loading annotations file), so we keep them separate """ for rect in self.idx_annot: self.scene.removeItem(rect) self.idx_annot = [] if self.parent.notes.annot is None: return bookmarks = [] events = [] if self.parent.value('annot_show'): bookmarks = self.parent.notes.annot.get_bookmarks() events = self.parent.notes.get_selected_events() annotations = bookmarks + events for annot in annotations: rect = QGraphicsRectItem(annot['start'], BARS['annot']['pos0'], annot['end'] - annot['start'], BARS['annot']['pos1']) self.scene.addItem(rect) if annot in bookmarks: color = self.parent.value('annot_bookmark_color') if annot in events: color = convert_name_to_color(annot['name']) rect.setPen(QPen(QColor(color), LINE_WIDTH)) rect.setBrush(QBrush(QColor(color))) rect.setZValue(-5) self.idx_annot.append(rect) for epoch in self.parent.notes.annot.epochs: self.mark_stages(epoch['start'], epoch['end'] - epoch['start'], epoch['stage']) def mark_stages(self, start_time, length, stage_name): """Mark stages, only add the new ones. Parameters ---------- start_time : int start time in s of the epoch being scored. length : int duration in s of the epoch being scored. stage_name : str one of the stages defined in global stages. """ y_pos = BARS['stage']['pos0'] # the -1 is really important, otherwise we stay on the edge of the rect old_score = self.scene.itemAt(start_time + length / 2, y_pos + STAGES[stage_name]['pos0'] + STAGES[stage_name]['pos1'] - 1, self.transform()) # check we are not removing the black border if old_score is not None and old_score.pen() == NoPen: lg.debug('Removing old score at {}'.format(start_time)) self.scene.removeItem(old_score) self.idx_annot.remove(old_score) rect = QGraphicsRectItem(start_time, y_pos + STAGES[stage_name]['pos0'], length, STAGES[stage_name]['pos1']) rect.setPen(NoPen) rect.setBrush(STAGES[stage_name]['color']) self.scene.addItem(rect) self.idx_annot.append(rect) def mousePressEvent(self, event): """Jump to window when user clicks on overview. Parameters ---------- event : instance of QtCore.QEvent it contains the position that was clicked. """ if self.scene is not None: x_in_scene = self.mapToScene(event.pos()).x() window_length = self.parent.value('window_length') window_start = int(floor(x_in_scene / window_length) * window_length) self.update_position(window_start) def reset(self): """Reset the widget, and clear the scene.""" self.minimum = None self.maximum = None self.start_time = None # datetime, absolute start time self.idx_current = None self.idx_markers = [] self.idx_annot = [] if self.scene is not None: self.scene.clear() self.scene = None
class QMyGraphicsview(QGraphicsView): sigMouseMovePoint = pyqtSignal(QPoint) sigNetDeviceItemPress = pyqtSignal(list) paths = [[[0], [1, 4, 3, 2], [1, 4, 3], [1, 4], [0], [1, 6]], [[2, 1], [0], [2, 4, 3], [2, 4], [0], [2, 6]], [], [], [], []] devices = [] line_items = [] def __init__(self, parent=None): super(QMyGraphicsview, self).__init__(parent) self.rect = QRectF(-200, -200, 400, 400) self.myScene = QGraphicsScene(self.rect) self.setScene(self.myScene) self.initPoints() self.initGraphicSystem() def initPoints(self): # 准备6个节点圆形 for i in range(1, 7): item = NetDeviceItem(self, NetNodeInfo(i, "green")) x = 150 * math.sin(math.radians(60 * i)) y = 150 * math.cos(math.radians(60 * i)) item.setPos(x, y) item.setBrush(Qt.green) item.setRect(-15, -15, 30, 30) item.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable) self.devices.append(item) def initGraphicSystem(self): # 清除所有item self.myScene.clear() # 显示scene边框 item1 = WholeDeviceItem(self.rect, self, ["whole", "white"]) item1.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable) self.myScene.addItem(item1) # 显示节点 for item in self.devices: self.myScene.addItem(item) text_item = self.myScene.addSimpleText(str(item.infos.id)) text_item.setPos(item.pos().x() + 20, item.pos().y()) self.myScene.clearSelection() def mouseMoveEvent(self, evt): self.sigMouseMovePoint.emit(evt.pos()) def emitItemPressEvent(self, infos): self.sigNetDeviceItemPress.emit(infos) def removeAllLines(self): for item in self.line_items: self.myScene.removeItem(item) self.line_items.clear() def drawAllLines(self): self.removeAllLines() for path in self.paths: if len(path) == 6: for line in path: if len(line) > 1: for i in range(len(line) - 1): p1 = self.devices[line[i] - 1] p2 = self.devices[line[i + 1] - 1] line_item = self.myScene.addLine( p1.pos().x(), p1.pos().y(), p2.pos().x(), p2.pos().y()) self.line_items.append(line_item) def drawLines(self, id): self.removeAllLines() """画出id节点的连接线""" for line in self.paths[id - 1]: if len(line) > 1: for i in range(len(line) - 1): p1 = self.devices[line[i] - 1] p2 = self.devices[line[i + 1] - 1] line_item = self.myScene.addLine(p1.pos().x(), p1.pos().y(), p2.pos().x(), p2.pos().y()) self.line_items.append(line_item)
class Screenshot(QGraphicsView): """ Main Class """ screen_shot_grabed = pyqtSignal(QImage) widget_closed = pyqtSignal() def __init__(self, flags=constant.DEFAULT, parent=None): """ flags: binary flags. see the flags in the constant.py """ super().__init__(parent) # Init self.penColorNow = QColor(PENCOLOR) self.penSizeNow = PENSIZE self.fontNow = QFont('Sans') self.clipboard = QApplication.clipboard() self.drawListResult = [ ] # draw list that sure to be drew, [action, coord] self.drawListProcess = None # the process to the result self.selectedArea = QRect( ) # a QRect instance which stands for the selected area self.selectedAreaRaw = QRect() self.mousePosition = MousePosition.OUTSIDE_AREA # mouse position self.screenPixel = None self.textRect = None self.mousePressed = False self.action = ACTION_SELECT self.mousePoint = self.cursor().pos() self.startX, self.startY = 0, 0 # the point where you start self.endX, self.endY = 0, 0 # the point where you end self.pointPath = QPainterPath( ) # the point mouse passes, used by draw free line self.itemsToRemove = [ ] # the items that should not draw on screenshot picture self.textPosition = None # result self.target_img = None # Init window self.getscreenshot() self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint) self.setMouseTracking(True) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setContentsMargins(0, 0, 0, 0) self.setStyleSheet("QGraphicsView { border-style: none; }") self.tooBar = MyToolBar(flags, self) self.tooBar.trigger.connect(self.changeAction) self.penSetBar = None if flags & constant.RECT or flags & constant.ELLIPSE or flags & constant.LINE or flags & constant.FREEPEN \ or flags & constant.ARROW or flags & constant.TEXT: self.penSetBar = PenSetWidget(self) self.penSetBar.penSizeTrigger.connect(self.changePenSize) self.penSetBar.penColorTrigger.connect(self.changePenColor) self.penSetBar.fontChangeTrigger.connect(self.changeFont) self.textInput = TextInput(self) self.textInput.inputChanged.connect(self.textChange) self.textInput.cancelPressed.connect(self.cancelInput) self.textInput.okPressed.connect(self.okInput) self.graphicsScene = QGraphicsScene(0, 0, self.screenPixel.width(), self.screenPixel.height()) self.show() self.setScene(self.graphicsScene) self.windowHandle().setScreen(QGuiApplication.screenAt(QCursor.pos())) self.scale = self.get_scale() # self.setFixedSize(self.screenPixel.width(), self.screenPixel.height()) self.setGeometry(QGuiApplication.screenAt(QCursor.pos()).geometry()) self.showFullScreen() self.redraw() QShortcut(QKeySequence('ctrl+s'), self).activated.connect(self.saveScreenshot) QShortcut(QKeySequence('esc'), self).activated.connect(self.close) @staticmethod def take_screenshot(flags): loop = QEventLoop() screen_shot = Screenshot(flags) screen_shot.show() screen_shot.widget_closed.connect(loop.quit) loop.exec() img = screen_shot.target_img return img def getscreenshot(self): screen = QGuiApplication.screenAt(QCursor.pos()) self.screenPixel = screen.grabWindow(0) def mousePressEvent(self, event): """ :type event: QMouseEvent :param event: :return: """ if event.button() != Qt.LeftButton: return if self.action is None: self.action = ACTION_SELECT self.startX, self.startY = event.x(), event.y() if self.action == ACTION_SELECT: if self.mousePosition == MousePosition.OUTSIDE_AREA: self.mousePressed = True self.selectedArea = QRect() self.selectedArea.setTopLeft(QPoint(event.x(), event.y())) self.selectedArea.setBottomRight(QPoint(event.x(), event.y())) self.redraw() elif self.mousePosition == MousePosition.INSIDE_AREA: self.mousePressed = True else: pass elif self.action == ACTION_MOVE_SELECTED: if self.mousePosition == MousePosition.OUTSIDE_AREA: self.action = ACTION_SELECT self.selectedArea = QRect() self.selectedArea.setTopLeft(QPoint(event.x(), event.y())) self.selectedArea.setBottomRight(QPoint(event.x(), event.y())) self.redraw() self.mousePressed = True elif self.action in DRAW_ACTION: self.mousePressed = True if self.action == ACTION_FREEPEN: self.pointPath = QPainterPath() self.pointPath.moveTo(QPoint(event.x(), event.y())) elif self.action == ACTION_TEXT: if self.textPosition is None: self.textPosition = QPoint(event.x(), event.y()) self.textRect = None self.redraw() def mouseMoveEvent(self, event: QMouseEvent): """ :type event: QMouseEvent :param event: :return: """ self.mousePoint = QPoint(event.globalPos().x(), event.globalPos().y()) if self.action is None: self.action = ACTION_SELECT if not self.mousePressed: point = QPoint(event.x(), event.y()) self.detectMousePosition(point) self.setCursorStyle() self.redraw() else: self.endX, self.endY = event.x(), event.y() # if self.mousePosition != OUTSIDE_AREA: # self.action = ACTION_MOVE_SELECTED if self.action == ACTION_SELECT: self.selectedArea.setBottomRight(QPoint(event.x(), event.y())) self.redraw() elif self.action == ACTION_MOVE_SELECTED: self.selectedArea = QRect(self.selectedAreaRaw) if self.mousePosition == MousePosition.INSIDE_AREA: moveToX = event.x() - self.startX + self.selectedArea.left( ) moveToY = event.y() - self.startY + self.selectedArea.top() if 0 <= moveToX <= self.screenPixel.width( ) - 1 - self.selectedArea.width(): self.selectedArea.moveLeft(moveToX) if 0 <= moveToY <= self.screenPixel.height( ) - 1 - self.selectedArea.height(): self.selectedArea.moveTop(moveToY) self.selectedArea = self.selectedArea.normalized() self.selectedAreaRaw = QRect(self.selectedArea) self.startX, self.startY = event.x(), event.y() self.redraw() elif self.mousePosition == MousePosition.ON_THE_LEFT_SIDE: moveToX = event.x() - self.startX + self.selectedArea.left( ) if moveToX <= self.selectedArea.right(): self.selectedArea.setLeft(moveToX) self.selectedArea = self.selectedArea.normalized() self.redraw() elif self.mousePosition == MousePosition.ON_THE_RIGHT_SIDE: moveToX = event.x( ) - self.startX + self.selectedArea.right() self.selectedArea.setRight(moveToX) self.selectedArea = self.selectedArea.normalized() self.redraw() elif self.mousePosition == MousePosition.ON_THE_UP_SIDE: moveToY = event.y() - self.startY + self.selectedArea.top() self.selectedArea.setTop(moveToY) self.selectedArea = self.selectedArea.normalized() self.redraw() elif self.mousePosition == MousePosition.ON_THE_DOWN_SIDE: moveToY = event.y( ) - self.startY + self.selectedArea.bottom() self.selectedArea.setBottom(moveToY) self.selectedArea = self.selectedArea.normalized() self.redraw() elif self.mousePosition == MousePosition.ON_THE_TOP_LEFT_CORNER: moveToX = event.x() - self.startX + self.selectedArea.left( ) moveToY = event.y() - self.startY + self.selectedArea.top() self.selectedArea.setTopLeft(QPoint(moveToX, moveToY)) self.selectedArea = self.selectedArea.normalized() self.redraw() elif self.mousePosition == MousePosition.ON_THE_BOTTOM_RIGHT_CORNER: moveToX = event.x( ) - self.startX + self.selectedArea.right() moveToY = event.y( ) - self.startY + self.selectedArea.bottom() self.selectedArea.setBottomRight(QPoint(moveToX, moveToY)) self.selectedArea = self.selectedArea.normalized() self.redraw() elif self.mousePosition == MousePosition.ON_THE_TOP_RIGHT_CORNER: moveToX = event.x( ) - self.startX + self.selectedArea.right() moveToY = event.y() - self.startY + self.selectedArea.top() self.selectedArea.setTopRight(QPoint(moveToX, moveToY)) self.selectedArea = self.selectedArea.normalized() self.redraw() elif self.mousePosition == MousePosition.ON_THE_BOTTOM_LEFT_CORNER: moveToX = event.x() - self.startX + self.selectedArea.left( ) moveToY = event.y( ) - self.startY + self.selectedArea.bottom() self.selectedArea.setBottomLeft(QPoint(moveToX, moveToY)) self.redraw() else: pass elif self.action == ACTION_RECT: self.drawRect(self.startX, self.startY, event.x(), event.y(), False) self.redraw() pass elif self.action == ACTION_ELLIPSE: self.drawEllipse(self.startX, self.startY, event.x(), event.y(), False) self.redraw() elif self.action == ACTION_ARROW: self.drawArrow(self.startX, self.startY, event.x(), event.y(), False) self.redraw() elif self.action == ACTION_LINE: self.drawLine(self.startX, self.startY, event.x(), event.y(), False) self.redraw() elif self.action == ACTION_FREEPEN: y1, y2 = event.x(), event.y() rect = self.selectedArea.normalized() if y1 <= rect.left(): y1 = rect.left() elif y1 >= rect.right(): y1 = rect.right() if y2 <= rect.top(): y2 = rect.top() elif y2 >= rect.bottom(): y2 = rect.bottom() self.pointPath.lineTo(y1, y2) self.drawFreeLine(self.pointPath, False) self.redraw() def mouseReleaseEvent(self, event): """ :type event: QMouseEvent :param event: :return: """ if event.button() != Qt.LeftButton: return if self.mousePressed: self.mousePressed = False self.endX, self.endY = event.x(), event.y() if self.action == ACTION_SELECT: self.selectedArea.setBottomRight(QPoint(event.x(), event.y())) self.selectedAreaRaw = QRect(self.selectedArea) self.action = ACTION_MOVE_SELECTED self.redraw() elif self.action == ACTION_MOVE_SELECTED: self.selectedAreaRaw = QRect(self.selectedArea) self.redraw() # self.action = None elif self.action == ACTION_RECT: self.drawRect(self.startX, self.startY, event.x(), event.y(), True) self.redraw() elif self.action == ACTION_ELLIPSE: self.drawEllipse(self.startX, self.startY, event.x(), event.y(), True) self.redraw() elif self.action == ACTION_ARROW: self.drawArrow(self.startX, self.startY, event.x(), event.y(), True) self.redraw() elif self.action == ACTION_LINE: self.drawLine(self.startX, self.startY, event.x(), event.y(), True) self.redraw() elif self.action == ACTION_FREEPEN: self.drawFreeLine(self.pointPath, True) self.redraw() def detectMousePosition(self, point): """ :type point: QPoint :param point: the mouse position you want to check :return: """ if self.selectedArea == QRect(): self.mousePosition = MousePosition.OUTSIDE_AREA return if self.selectedArea.left() - ERRORRANGE <= point.x( ) <= self.selectedArea.left() and (self.selectedArea.top() - ERRORRANGE <= point.y() <= self.selectedArea.top()): self.mousePosition = MousePosition.ON_THE_TOP_LEFT_CORNER elif self.selectedArea.right() <= point.x() <= self.selectedArea.right( ) + ERRORRANGE and (self.selectedArea.top() - ERRORRANGE <= point.y() <= self.selectedArea.top()): self.mousePosition = MousePosition.ON_THE_TOP_RIGHT_CORNER elif self.selectedArea.left() - ERRORRANGE <= point.x( ) <= self.selectedArea.left() and ( self.selectedArea.bottom() <= point.y() <= self.selectedArea.bottom() + ERRORRANGE): self.mousePosition = MousePosition.ON_THE_BOTTOM_LEFT_CORNER elif self.selectedArea.right() <= point.x() <= self.selectedArea.right( ) + ERRORRANGE and (self.selectedArea.bottom() <= point.y() <= self.selectedArea.bottom() + ERRORRANGE): self.mousePosition = MousePosition.ON_THE_BOTTOM_RIGHT_CORNER elif -ERRORRANGE <= point.x() - self.selectedArea.left() <= 0 and ( self.selectedArea.topLeft().y() < point.y() < self.selectedArea.bottomLeft().y()): self.mousePosition = MousePosition.ON_THE_LEFT_SIDE elif 0 <= point.x() - self.selectedArea.right() <= ERRORRANGE and ( self.selectedArea.topRight().y() < point.y() < self.selectedArea.bottomRight().y()): self.mousePosition = MousePosition.ON_THE_RIGHT_SIDE elif -ERRORRANGE <= point.y() - self.selectedArea.top() <= 0 and ( self.selectedArea.topLeft().x() < point.x() < self.selectedArea.topRight().x()): self.mousePosition = MousePosition.ON_THE_UP_SIDE elif 0 <= point.y() - self.selectedArea.bottom() <= ERRORRANGE and ( self.selectedArea.bottomLeft().x() < point.x() < self.selectedArea.bottomRight().x()): self.mousePosition = MousePosition.ON_THE_DOWN_SIDE elif not self.selectedArea.contains(point): self.mousePosition = MousePosition.OUTSIDE_AREA else: self.mousePosition = MousePosition.INSIDE_AREA def setCursorStyle(self): if self.action in DRAW_ACTION: self.setCursor(Qt.CrossCursor) return if self.mousePosition == MousePosition.ON_THE_LEFT_SIDE or \ self.mousePosition == MousePosition.ON_THE_RIGHT_SIDE: self.setCursor(Qt.SizeHorCursor) elif self.mousePosition == MousePosition.ON_THE_UP_SIDE or \ self.mousePosition == MousePosition.ON_THE_DOWN_SIDE: self.setCursor(Qt.SizeVerCursor) elif self.mousePosition == MousePosition.ON_THE_TOP_LEFT_CORNER or \ self.mousePosition == MousePosition.ON_THE_BOTTOM_RIGHT_CORNER: self.setCursor(Qt.SizeFDiagCursor) elif self.mousePosition == MousePosition.ON_THE_TOP_RIGHT_CORNER or \ self.mousePosition == MousePosition.ON_THE_BOTTOM_LEFT_CORNER: self.setCursor(Qt.SizeBDiagCursor) elif self.mousePosition == MousePosition.OUTSIDE_AREA: self.setCursor(Qt.ArrowCursor) elif self.mousePosition == MousePosition.INSIDE_AREA: self.setCursor(Qt.OpenHandCursor) else: self.setCursor(Qt.ArrowCursor) pass 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 get_scale(self): return self.devicePixelRatio() def saveScreenshot(self, clipboard=False, fileName='screenshot.png', picType='png'): fullWindow = QRect(0, 0, self.width() - 1, self.height() - 1) selected = QRect(self.selectedArea) if selected.left() < 0: selected.setLeft(0) if selected.right() >= self.width(): selected.setRight(self.width() - 1) if selected.top() < 0: selected.setTop(0) if selected.bottom() >= self.height(): selected.setBottom(self.height() - 1) source = (fullWindow & selected) source.setTopLeft( QPoint(source.topLeft().x() * self.scale, source.topLeft().y() * self.scale)) source.setBottomRight( QPoint(source.bottomRight().x() * self.scale, source.bottomRight().y() * self.scale)) image = self.screenPixel.copy(source) image.setDevicePixelRatio(1) if clipboard: QGuiApplication.clipboard().setImage(QImage(image), QClipboard.Clipboard) else: image.save(fileName, picType, 10) self.target_img = image self.screen_shot_grabed.emit(QImage(image)) 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() # deal with every step in drawList def drawOneStep(self, step): """ :type step: tuple """ if step[0] == ACTION_RECT: self.graphicsScene.addRect( QRectF(QPointF(step[1], step[2]), QPointF(step[3], step[4])), step[5]) elif step[0] == ACTION_ELLIPSE: self.graphicsScene.addEllipse( QRectF(QPointF(step[1], step[2]), QPointF(step[3], step[4])), step[5]) elif step[0] == ACTION_ARROW: arrow = QPolygonF() linex = float(step[1] - step[3]) liney = float(step[2] - step[4]) line = sqrt(pow(linex, 2) + pow(liney, 2)) # in case to divided by 0 if line == 0: return sinAngel = liney / line cosAngel = linex / line # sideLength is the length of bottom side of the body of an arrow # arrowSize is the size of the head of an arrow, left and right # sides' size is arrowSize, and the bottom side's size is arrowSize / 2 sideLength = step[5].width() arrowSize = 8 bottomSize = arrowSize / 2 tmpPoint = QPointF(step[3] + arrowSize * sideLength * cosAngel, step[4] + arrowSize * sideLength * sinAngel) point1 = QPointF(step[1] + sideLength * sinAngel, step[2] - sideLength * cosAngel) point2 = QPointF(step[1] - sideLength * sinAngel, step[2] + sideLength * cosAngel) point3 = QPointF(tmpPoint.x() - sideLength * sinAngel, tmpPoint.y() + sideLength * cosAngel) point4 = QPointF(tmpPoint.x() - bottomSize * sideLength * sinAngel, tmpPoint.y() + bottomSize * sideLength * cosAngel) point5 = QPointF(step[3], step[4]) point6 = QPointF(tmpPoint.x() + bottomSize * sideLength * sinAngel, tmpPoint.y() - bottomSize * sideLength * cosAngel) point7 = QPointF(tmpPoint.x() + sideLength * sinAngel, tmpPoint.y() - sideLength * cosAngel) arrow.append(point1) arrow.append(point2) arrow.append(point3) arrow.append(point4) arrow.append(point5) arrow.append(point6) arrow.append(point7) arrow.append(point1) self.graphicsScene.addPolygon(arrow, step[5], step[6]) elif step[0] == ACTION_LINE: self.graphicsScene.addLine( QLineF(QPointF(step[1], step[2]), QPointF(step[3], step[4])), step[5]) elif step[0] == ACTION_FREEPEN: self.graphicsScene.addPath(step[1], step[2]) elif step[0] == ACTION_TEXT: textAdd = self.graphicsScene.addSimpleText(step[1], step[2]) textAdd.setPos(step[3]) textAdd.setBrush(QBrush(step[4])) self.textRect = textAdd.boundingRect() # draw the size information on the top left corner def drawSizeInfo(self): sizeInfoAreaWidth = 200 sizeInfoAreaHeight = 30 spacing = 5 rect = self.selectedArea.normalized() sizeInfoArea = QRect(rect.left(), rect.top() - spacing - sizeInfoAreaHeight, sizeInfoAreaWidth, sizeInfoAreaHeight) if sizeInfoArea.top() < 0: sizeInfoArea.moveTopLeft(rect.topLeft() + QPoint(spacing, spacing)) if sizeInfoArea.right() >= self.screenPixel.width(): sizeInfoArea.moveTopLeft(rect.topLeft() - QPoint(spacing, spacing) - QPoint(sizeInfoAreaWidth, 0)) if sizeInfoArea.left() < spacing: sizeInfoArea.moveLeft(spacing) if sizeInfoArea.top() < spacing: sizeInfoArea.moveTop(spacing) self.itemsToRemove.append( self.graphicsScene.addRect(QRectF(sizeInfoArea), Qt.white, QBrush(Qt.black))) sizeInfo = self.graphicsScene.addSimpleText(' {0} x {1}'.format( rect.width() * self.scale, rect.height() * self.scale)) sizeInfo.setPos(sizeInfoArea.topLeft() + QPoint(0, 2)) sizeInfo.setPen(QPen(QColor(255, 255, 255), 2)) self.itemsToRemove.append(sizeInfo) def drawRect(self, x1, x2, y1, y2, result): rect = self.selectedArea.normalized() tmpRect = QRect(QPoint(x1, x2), QPoint(y1, y2)).normalized() resultRect = rect & tmpRect tmp = [ ACTION_RECT, resultRect.topLeft().x(), resultRect.topLeft().y(), resultRect.bottomRight().x(), resultRect.bottomRight().y(), QPen(QColor(self.penColorNow), int(self.penSizeNow)) ] if result: self.drawListResult.append(tmp) else: self.drawListProcess = tmp def drawEllipse(self, x1, x2, y1, y2, result): rect = self.selectedArea.normalized() tmpRect = QRect(QPoint(x1, x2), QPoint(y1, y2)).normalized() resultRect = rect & tmpRect tmp = [ ACTION_ELLIPSE, resultRect.topLeft().x(), resultRect.topLeft().y(), resultRect.bottomRight().x(), resultRect.bottomRight().y(), QPen(QColor(self.penColorNow), int(self.penSizeNow)) ] if result: self.drawListResult.append(tmp) else: self.drawListProcess = tmp def drawArrow(self, x1, x2, y1, y2, result): rect = self.selectedArea.normalized() if y1 <= rect.left(): y1 = rect.left() elif y1 >= rect.right(): y1 = rect.right() if y2 <= rect.top(): y2 = rect.top() elif y2 >= rect.bottom(): y2 = rect.bottom() tmp = [ ACTION_ARROW, x1, x2, y1, y2, QPen(QColor(self.penColorNow), int(self.penSizeNow)), QBrush(QColor(self.penColorNow)) ] if result: self.drawListResult.append(tmp) else: self.drawListProcess = tmp def drawLine(self, x1, x2, y1, y2, result): rect = self.selectedArea.normalized() if y1 <= rect.left(): y1 = rect.left() elif y1 >= rect.right(): y1 = rect.right() if y2 <= rect.top(): y2 = rect.top() elif y2 >= rect.bottom(): y2 = rect.bottom() tmp = [ ACTION_LINE, x1, x2, y1, y2, QPen(QColor(self.penColorNow), int(self.penSizeNow)) ] if result: self.drawListResult.append(tmp) else: self.drawListProcess = tmp def drawFreeLine(self, pointPath, result): tmp = [ ACTION_FREEPEN, QPainterPath(pointPath), QPen(QColor(self.penColorNow), int(self.penSizeNow)) ] if result: self.drawListResult.append(tmp) else: self.drawListProcess = tmp def textChange(self): if self.textPosition is None: return self.text = self.textInput.getText() self.drawListProcess = [ ACTION_TEXT, str(self.text), QFont(self.fontNow), QPoint(self.textPosition), QColor(self.penColorNow) ] self.redraw() def undoOperation(self): if len(self.drawListResult) == 0: self.action = ACTION_SELECT self.selectedArea = QRect() self.selectedAreaRaw = QRect() self.tooBar.hide() if self.penSetBar is not None: self.penSetBar.hide() else: self.drawListResult.pop() self.redraw() def saveOperation(self): filename = QFileDialog.getSaveFileName(self, 'Save file', './screenshot.png', '*.png;;*.jpg') if len(filename[0]) == 0: return else: self.saveScreenshot(False, filename[0], filename[1][2:]) self.close() def close(self): self.widget_closed.emit() super().close() self.tooBar.close() if self.penSetBar is not None: self.penSetBar.close() def saveToClipboard(self): QApplication.clipboard().setText('Test in save function') self.saveScreenshot(True) self.close() # slots def changeAction(self, nextAction): QApplication.clipboard().setText('Test in changeAction function') if nextAction == ACTION_UNDO: self.undoOperation() elif nextAction == ACTION_SAVE: self.saveOperation() elif nextAction == ACTION_CANCEL: self.close() elif nextAction == ACTION_SURE: self.saveToClipboard() else: self.action = nextAction self.setFocus() def changePenSize(self, nextPenSize): self.penSizeNow = nextPenSize def changePenColor(self, nextPenColor): self.penColorNow = nextPenColor def cancelInput(self): self.drawListProcess = None self.textPosition = None self.textRect = None self.textInput.hide() self.textInput.clearText() self.redraw() def okInput(self): self.text = self.textInput.getText() self.drawListResult.append([ ACTION_TEXT, str(self.text), QFont(self.fontNow), QPoint(self.textPosition), QColor(self.penColorNow) ]) self.textPosition = None self.textRect = None self.textInput.hide() self.textInput.clearText() self.redraw() def changeFont(self, font): self.fontNow = font
class Overview(QGraphicsView): """Show an overview of data, such as hypnogram and data in memory. Attributes ---------- parent : instance of QMainWindow the main window. config : ConfigChannels preferences for this widget minimum : int or float start time of the recording, from the absolute time of start_time in s maximum : int or float length of the recordings in s start_time : datetime absolute start time of the recording scene : instance of QGraphicsScene to keep track of the objects idx_current : QGraphicsRectItem instance of the current time window idx_markers : list of QGraphicsRectItem list of markers in the dataset idx_annot : list of QGraphicsRectItem list of user-made annotations """ def __init__(self, parent): super().__init__() self.parent = parent self.config = ConfigOverview(self.update_settings) self.minimum = None self.maximum = None self.start_time = None # datetime, absolute start time self.scene = None self.idx_current = None self.idx_markers = [] self.idx_annot = [] self.setMinimumHeight(TOTAL_HEIGHT + 30) def update(self, reset=True): """Read full duration and update maximum. Parameters ---------- reset: bool If True, current window start time is reset to 0. """ if self.parent.info.dataset is not None: # read from the dataset, if available header = self.parent.info.dataset.header maximum = header['n_samples'] / header['s_freq'] # in s self.minimum = 0 self.maximum = maximum self.start_time = self.parent.info.dataset.header['start_time'] elif self.parent.notes.annot is not None: # read from annotations annot = self.parent.notes.annot self.minimum = annot.first_second self.maximum = annot.last_second self.start_time = annot.start_time # make it time-zone unaware self.start_time = self.start_time.replace(tzinfo=None) if reset: self.parent.value('window_start', 0) # the only value that is reset self.display() def display(self): """Updates the widgets, especially based on length of recordings.""" lg.debug('GraphicsScene is between {}s and {}s'.format( self.minimum, self.maximum)) x_scale = 1 / self.parent.value('overview_scale') lg.debug('Set scene x-scaling to {}'.format(x_scale)) self.scale(1 / self.transform().m11(), 1) # reset to 1 self.scale(x_scale, 1) self.scene = QGraphicsScene(self.minimum, 0, self.maximum, TOTAL_HEIGHT) self.setScene(self.scene) # reset annotations self.idx_markers = [] self.idx_annot = [] self.display_current() for name, pos in BARS.items(): item = QGraphicsRectItem(self.minimum, pos['pos0'], self.maximum, pos['pos1']) item.setToolTip(pos['tip']) self.scene.addItem(item) self.add_timestamps() def add_timestamps(self): """Add timestamps at the bottom of the overview.""" transform, _ = self.transform().inverted() stamps = _make_timestamps(self.start_time, self.minimum, self.maximum, self.parent.value('timestamp_steps')) for stamp, xpos in zip(*stamps): text = self.scene.addSimpleText(stamp) text.setFlag(QGraphicsItem.ItemIgnoresTransformations) # set xpos and adjust for text width text_width = text.boundingRect().width() * transform.m11() text.setPos(xpos - text_width / 2, TIME_HEIGHT) def update_settings(self): """After changing the settings, we need to recreate the whole image.""" self.display() self.display_markers() if self.parent.notes.annot is not None: self.parent.notes.display_notes() def update_position(self, new_position=None): """Update the cursor position and much more. Parameters ---------- new_position : int or float new position in s, for plotting etc. Notes ----- This is a central function. It updates the cursor, then updates the traces, the scores, and the power spectrum. In other words, this function is responsible for keep track of the changes every time the start time of the window changes. """ if new_position is not None: lg.debug('Updating position to {}'.format(new_position)) self.parent.value('window_start', new_position) self.idx_current.setPos(new_position, 0) current_time = (self.start_time + timedelta(seconds=new_position)) msg = 'Current time: ' + current_time.strftime('%H:%M:%S') self.parent.statusBar().showMessage(msg) lg.debug(msg) else: lg.debug('Updating position at {}' ''.format(self.parent.value('window_start'))) if self.parent.info.dataset is not None: self.parent.traces.read_data() if self.parent.traces.data is not None: self.parent.traces.display() self.parent.spectrum.display_window() if self.parent.notes.annot is not None: self.parent.notes.set_stage_index() self.parent.notes.set_quality_index() self.display_current() def display_current(self): """Create a rectangle showing the current window.""" if self.idx_current in self.scene.items(): self.scene.removeItem(self.idx_current) item = QGraphicsRectItem(0, CURR['pos0'], self.parent.value('window_length'), CURR['pos1']) # it's necessary to create rect first, and then move it item.setPos(self.parent.value('window_start'), 0) item.setPen(QPen(Qt.lightGray)) item.setBrush(QBrush(Qt.lightGray)) item.setZValue(-10) self.scene.addItem(item) self.idx_current = item def display_markers(self): """Mark all the markers, from the dataset. This function should be called only when we load the dataset or when we change the settings. """ for rect in self.idx_markers: self.scene.removeItem(rect) self.idx_markers = [] markers = [] if self.parent.info.markers is not None: if self.parent.value('marker_show'): markers = self.parent.info.markers for mrk in markers: rect = QGraphicsRectItem(mrk['start'], BARS['markers']['pos0'], mrk['end'] - mrk['start'], BARS['markers']['pos1']) self.scene.addItem(rect) color = self.parent.value('marker_color') rect.setPen(QPen(QColor(color))) rect.setBrush(QBrush(QColor(color))) rect.setZValue(-5) self.idx_markers.append(rect) def display_annotations(self): """Mark all the bookmarks/events, from annotations. This function is similar to display_markers, but they are called at different stages (f.e. when loading annotations file), so we keep them separate """ for rect in self.idx_annot: self.scene.removeItem(rect) self.idx_annot = [] if self.parent.notes.annot is None: return bookmarks = [] events = [] if self.parent.value('annot_show'): bookmarks = self.parent.notes.annot.get_bookmarks() events = self.parent.notes.get_selected_events() annotations = bookmarks + events for annot in annotations: rect = QGraphicsRectItem(annot['start'], BARS['annot']['pos0'], annot['end'] - annot['start'], BARS['annot']['pos1']) self.scene.addItem(rect) if annot in bookmarks: color = self.parent.value('annot_bookmark_color') if annot in events: color = convert_name_to_color(annot['name']) rect.setPen(QPen(QColor(color), LINE_WIDTH)) rect.setBrush(QBrush(QColor(color))) rect.setZValue(-5) self.idx_annot.append(rect) for epoch in self.parent.notes.annot.epochs: self.mark_stages(epoch['start'], epoch['end'] - epoch['start'], epoch['stage']) self.mark_quality(epoch['start'], epoch['end'] - epoch['start'], epoch['quality']) cycles = self.parent.notes.annot.rater.find('cycles') cyc_starts = [float(mrkr.text) for mrkr in cycles.findall('cyc_start')] cyc_ends = [float(mrkr.text) for mrkr in cycles.findall('cyc_end')] for mrkr in cyc_starts: self.mark_cycles(mrkr, 30) # TODO: better width solution for mrkr in cyc_ends: self.mark_cycles(mrkr, 30, end=True) def mark_stages(self, start_time, length, stage_name): """Mark stages, only add the new ones. Parameters ---------- start_time : int start time in s of the epoch being scored. length : int duration in s of the epoch being scored. stage_name : str one of the stages defined in global stages. """ y_pos = BARS['stage']['pos0'] # the -1 is really important, otherwise we stay on the edge of the rect old_score = self.scene.itemAt( start_time + length / 2, y_pos + STAGES[stage_name]['pos0'] + STAGES[stage_name]['pos1'] - 1, self.transform()) # check we are not removing the black border if old_score is not None and old_score.pen() == NoPen: lg.debug('Removing old score at {}'.format(start_time)) self.scene.removeItem(old_score) self.idx_annot.remove(old_score) rect = QGraphicsRectItem(start_time, y_pos + STAGES[stage_name]['pos0'], length, STAGES[stage_name]['pos1']) rect.setPen(NoPen) rect.setBrush(STAGES[stage_name]['color']) self.scene.addItem(rect) self.idx_annot.append(rect) def mark_quality(self, start_time, length, qual_name): """Mark signal quality, only add the new ones. Parameters ---------- start_time : int start time in s of the epoch being scored. length : int duration in s of the epoch being scored. qual_name : str one of the stages defined in global stages. """ y_pos = BARS['quality']['pos0'] height = 10 # the -1 is really important, otherwise we stay on the edge of the rect old_score = self.scene.itemAt(start_time + length / 2, y_pos + height - 1, self.transform()) # check we are not removing the black border if old_score is not None and old_score.pen() == NoPen: lg.debug('Removing old score at {}'.format(start_time)) self.scene.removeItem(old_score) self.idx_annot.remove(old_score) if qual_name == 'Poor': rect = QGraphicsRectItem(start_time, y_pos, length, height) rect.setPen(NoPen) rect.setBrush(Qt.black) self.scene.addItem(rect) self.idx_annot.append(rect) def mark_cycles(self, start_time, length, end=False): """Mark cycle bound, only add the new one. Parameters ---------- start_time: int start time in s of the bounding epoch length : int duration in s of the epoch being scored. end: bool If True, marker will be a cycle end marker; otherwise, it's start. """ y_pos = STAGES['cycle']['pos0'] height = STAGES['cycle']['pos1'] color = STAGES['cycle']['color'] # the -1 is really important, otherwise we stay on the edge of the rect old_rect = self.scene.itemAt(start_time + length / 2, y_pos + height - 1, self.transform()) # check we are not removing the black border if old_rect is not None and old_rect.pen() == NoPen: lg.debug('Removing old score at {}'.format(start_time)) self.scene.removeItem(old_rect) self.idx_annot.remove(old_rect) rect = QGraphicsRectItem(start_time, y_pos, length, height) rect.setPen(NoPen) rect.setBrush(color) self.scene.addItem(rect) self.idx_annot.append(rect) if end: start_time += length length = -length kink_hi = QGraphicsRectItem(start_time, y_pos, length * 5, 1) kink_hi.setPen(NoPen) kink_hi.setBrush(color) self.scene.addItem(kink_hi) self.idx_annot.append(kink_hi) kink_lo = QGraphicsRectItem(start_time, y_pos + height, length * 5, 1) kink_lo.setPen(NoPen) kink_lo.setBrush(color) self.scene.addItem(kink_lo) self.idx_annot.append(kink_lo) def mousePressEvent(self, event): """Jump to window when user clicks on overview. Parameters ---------- event : instance of QtCore.QEvent it contains the position that was clicked. """ if self.scene is not None: x_in_scene = self.mapToScene(event.pos()).x() window_length = self.parent.value('window_length') window_start = int( floor(x_in_scene / window_length) * window_length) if self.parent.notes.annot is not None: window_start = self.parent.notes.annot.get_epoch_start( window_start) self.update_position(window_start) def reset(self): """Reset the widget, and clear the scene.""" self.minimum = None self.maximum = None self.start_time = None # datetime, absolute start time self.idx_current = None self.idx_markers = [] self.idx_annot = [] if self.scene is not None: self.scene.clear() self.scene = None
class PhotoViewer(QGraphicsView): leftMouseButtonPressed = pyqtSignal(float, float) leftMouseButtonReleased = pyqtSignal(float, float) def __init__(self): QGraphicsView.__init__(self) self.screen = QDesktopWidget().screenGeometry() self.scene = QGraphicsScene() self.setScene(self.scene) self.photo_base_dir = '' self.pixmap_photo = QPixmap() self.current_photo = None self.zoom = BASE_ZOOM self.max_zoom = MAX_PHOTO_ZOOM self.min_zoom = MIN_ZOOM self.zoom_ratio = ZOOM_RATIO self.photo_rotate = 0 self.ratio_mode = Qt.KeepAspectRatio self.setMouseTracking(True) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setFixedSize(self.get_sizes()) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setCursor(QCursor(Qt.OpenHandCursor)) def get_sizes(self): """ Returns max photo viewer size for typing module window :return: QSize """ width = int(self.screen.width() * 0.5) height = int(self.screen.height() * 0.875) return QSize(width, height) def open_photo(self, filename): self.current_photo = filename path = os.path.join(self.photo_base_dir, filename) project_path = os.path.join(PROJECT_PATH, filename) self.scene.clear() if os.path.isfile(path): self._prepare_photo(path) elif os.path.isfile(project_path): self._prepare_photo(project_path) else: self.scene.addSimpleText( "Отсутствует фотография по указаному пути:\n\n{path}".format( path=path)) def _prepare_photo(self, path): self.pixmap_photo.load(path) self.scene.addItem(QGraphicsPixmapItem(self.pixmap_photo)) self.auto_rotate_photo() self.setTransform(QTransform().rotate(self.photo_rotate)) self.scene.update() self.fitInView(self.scene.sceneRect(), mode=self.ratio_mode) def auto_rotate_photo(self): width = self.pixmap_photo.width() height = self.pixmap_photo.height() if width > height: self.photo_rotate = 90 else: self.photo_rotate = 0 def zoom_in(self): self.zoom *= self.zoom_ratio if self.zoom > self.max_zoom: self.zoom = self.max_zoom self.setTransform(QTransform().scale(self.zoom, self.zoom).rotate( self.photo_rotate)) def zoom_out(self): self.zoom /= self.zoom_ratio self.scene.setMinimumRenderSize(1) if self.zoom < self.min_zoom: self.zoom = self.min_zoom self.setTransform(QTransform().scale(self.zoom, self.zoom).rotate( (self.photo_rotate))) def wheelEvent(self, wheel_event): moose = wheel_event.angleDelta().y() / 15 / 8 if moose > 0: self.zoom_in() elif moose < 0: self.zoom_out() # TODO: исправить зум def mousePressEvent(self, event): pos = self.mapToScene(event.pos()) if event.button() == Qt.LeftButton: self.setDragMode(QGraphicsView.ScrollHandDrag) self.leftMouseButtonPressed.emit(pos.x(), pos.y()) QGraphicsView.mousePressEvent(self, event) def mouseReleaseEvent(self, event): QGraphicsView.mouseReleaseEvent(self, event) pos = self.mapToScene(event.pos()) if event.button() == Qt.LeftButton: self.setDragMode(QGraphicsView.NoDrag) self.leftMouseButtonPressed.emit(pos.x(), pos.y())
class Board(QGraphicsView): """ Class that creates the game UI and manages player actions """ # Default variables BOARD_HEIGHT = 621 BOARD_WIDTH = 1000 """ Initialize the GameBoard class """ def __init__(self): super(Board, self).__init__() print("Initiated board") self.setHorizontalScrollBarPolicy(1) # Qt::ScrollBarAlwaysOff = 1 self.setVerticalScrollBarPolicy(1) self.scene = QGraphicsScene(self) self.scene.setSceneRect(0,0, self.BOARD_WIDTH, self.BOARD_HEIGHT) bgPixMap = QPixmap('C:\\Users\\michaelh\\Desktop\\CSC Project\\Server\\src\\resources\\grey_tiles.png') self.scene.setBackgroundBrush(QBrush(bgPixMap)) # Create the walls on the map wallAngPixMap = QPixmap('C:\\Users\\michaelh\\Desktop\\CSC Project\\Server\\src\\resources\\angular_wall.png') self.angWall = Wall(wallAngPixMap, 100, 100) self.scene.addItem(self.angWall) wallCirPixMap = QPixmap('C:\\Users\\michaelh\\Desktop\\CSC Project\\Server\\src\\resources\\circular_wall.png') self.cirWall = Wall(wallCirPixMap, 595, 125) self.scene.addItem(self.cirWall) self.setScene(self.scene) self.show() def start(self, adrs): self.setupUI(adrs) """ Create initial state for the initial board UI """ def setupUI(self, adrs): self.resize(self.BOARD_WIDTH, self.BOARD_HEIGHT) self.setWindowTitle('Blobbageddon!') # sort clients into correct teams for each blob, client 1 = driver, client 2 = gunner clients = [] self.listBlobs = [] pIndices = [] i = 0 # for all sockets add their key (player index) to a single list of keys and sort for s in adrs.keys(): pIndices.append(list(adrs[s].keys())[0]) pIndices = sorted(pIndices) # sort the keys by numerical value (0,1,2,3) print("sorted KEYS = ", pIndices) # go through each index in order and then add the address of the socket that has that player index for index in pIndices: for s in adrs.keys(): if index in list(adrs[s].keys()): print("KEY: ", index) print(adrs[s][index]) clients.append(adrs[s][index]) # since keys are sorted add in order to ads list i += 1 j = 0 while j < len(clients): print(clients[j]) self.listBlobs.append((Blob(self.scene, clients[j], clients[j+1], self.BOARD_WIDTH, self.BOARD_HEIGHT))) j += 2 for blob in self.listBlobs: self.scene.addItem(blob) self.timeTxt = self.scene.addSimpleText("Time: ") self.timeTxt.setPos(self.BOARD_WIDTH/2-100, 50) self.timeTxt.setScale(2) # brush = QBrush() # brush.setColor(QColor(200, 0, 0)) # self.timeTxt.setBrush(brush) self.setScene(self.scene) self.show() def getXY(self, strPos): """ Gets x and y from the movement string passed from clients :param strPos: Movement string received from clients - e.g. '5.432, -145.623532;' :return: """ # regex to distinguish if c is a correct character pattern = "-|\d|\." x = '' y = '' isXFinished = False # Receive the position from the client and change to x and y for c in strPos: if c == ';': break else: match = re.match(pattern, c) if not isXFinished: if c == ',': isXFinished = True elif match: x += c elif match: y += c return (x, y) def updatePos(self, address, strPos): """ If address is a driver for a blob then that blob's position will be recalculated using the string passed in :param address: The client that has sent the string, must appear in a blob's driver to change pos :param strPos: The movement string passed by the client :return: True if driver, meaning address is not a gunner, allows updateGun call to be skipped """ for blob in self.listBlobs: if blob.driver == address: x, y = self.getXY(strPos) # Divide x and y by 100 and remove decimal points to get speed factor x = Decimal(x) y = Decimal(y) x = int(x/100) y = int(y/100) # Speed becomes equal to x * defSpeed, y * defSpeed blob.xSpeed = blob.defSpeed * x blob.ySpeed = blob.defSpeed * y return True # return true, know not to carry out gunner else: return False # return fals, so carry out gunner def getFireTally(self, strGun): """ Get the amount of fires sent by a client, and add to the tally of fires to be made by that gunner :param strGun: :return: the amount of "fires" to add to the Gun's fire tally, followed by the remainder of strGun """ # regex to distinguish if c is a correct character pattern = "\d+\," search = re.findall(pattern, strGun) tally = 0 if not search: return -1 # return -1, no valid angle so keep orientation the same else: for e in search: tally += int(e[:len(e)-1]) return tally def getAngle(self, strGun): """ Takes a string and retries the angle passed in :param strGun: eg. ".532523523;3.151515125l;4.323123;" :return: the angle passed in eg. 3 """ # regex to distinguish if c is a correct character pattern = "-?\d+(\.\d+)\;" search = re.search(pattern, strGun) if not search: print("NOT VALID") return -1 # return -1, no valid angle so keep orientation the same else: angle = search.group(0) return math.trunc(float(angle[:len(angle)-1])) def updateGun(self, address, strGun): """ Take inputs from user and if the fire button has been pressed fire a projectile :param address: The client that sent the update gun request :param strGun: Contains information about what direction the gun is facing and if fire button has been pressed :return: True if address was a gunner """ for blob in self.listBlobs: if blob.gunner == address: # if address passed in is a gunner carry out gunner stuff blob.gun.fireTally = self.getFireTally(strGun) blob.gun.desiredAngle = self.getAngle(strGun) """ Update game graphics with respect to the current game state """ def updateGame(self, addressDict, input, currTime): try: print(currTime) diff = 120 - currTime print("diff: ", diff) timeString = "Time: {}".format(diff) print(timeString) self.timeTxt.setText(timeString) for key in addressDict.keys(): address = addressDict[key] isDriver = self.updatePos(address, input) if not isDriver: isGunner = self.updateGun(address, input) for blob in self.listBlobs: self.manageProjectiles(blob) self.drawGame() except ValueError: print("address was not assigned a value before being used") def manageBlob(self, blob): """ Check if a blob has collided with an item, if a blob has collided with an item carry out the correct response. # Wall - Blob is not allowed to pass into a wall :param blob: :return: """ isCollision = False collidedItems = blob.collidingItems() for item in collidedItems: if item is self.angWall or item is self.cirWall: isCollision = True print(isCollision) return isCollision def manageProjectiles(self, blob): """ Manage projectiles - send the call to update position on screen, remove from active projectiles if necessary, check for collisions with other Blobs :return: """ # add particles to seen if they are not in the scene already for p in blob.gun.projectiles: if not p in self.scene.items(): self.scene.addItem(p) # check for collision with other blobs # for b in self.listBlobs: # if b != blob: collidedItems = [] for p in blob.gun.projectiles: # for each projectile check if p collides with blob b collidedItems = p.collidingItems() for item in collidedItems: if item is self.angWall or item is self.cirWall: # item is a wall remove particle blob.gun.pToRemove.add(p) elif item in self.listBlobs and item != blob: # item is a blob and is not the current blob the particles belong to blob.hits += 1 blob.gun.pToRemove.add(p) tempList = [] """ Put items that are still in the group and are marked to be removed into a temporary list to allow for their removal without causing an error within the for loop """ for item in self.scene.items(): if item in blob.gun.pToRemove: tempList.append(item) # items in tempList are Projectiles that are marked to be removed for p in tempList: blob.gun.projectiles.remove(p) # remove from active projectile set self.scene.removeItem(p) # Remove the item from the group blob.gun.pToRemove.remove(p) # Remove the projectile from the set signifying it should be removed print("SCORE: ", blob.hits) """ Draw all graphics in the game """ def drawGame(self): for blob in self.listBlobs: blob.onDraw([self.width(), self.height()], self.angWall, self.cirWall) # self.manageProjectiles(blob) def gameLoop(self): """
class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.ui = ui_main_window.Ui_MainWindow() self.ui.setupUi(self) self.cities = cities self.autoplay = False self._zoom = 1 self.genetic = None self.paths = [] self.last = None self.scene = QGraphicsScene() self.ui.graphicsView.setScene(self.scene) self.ui.popSize.setValue(20) self.ui.algorithm.addItem('ant', AntColony) self.ui.algorithm.addItem('genetic', Genetic) self.ui.algorithm.addItem('solver', Test) self.ui.playBtn.clicked.connect(self.on_play_click) self.ui.nextBtn.clicked.connect(self.next) self.ui.restartBtn.clicked.connect(self.restart) self.ui.showCur.stateChanged.connect(self.redraw) self.timer = QTimer() self.timer.timeout.connect(self.gen_next) self.timer.start(1) for city in self.cities: self.scene.addEllipse(city.x, city.y, 5, 5, QPen(), QBrush(QColor(255, 0, 0))) self.scene.addSimpleText(city.name).setPos(city.x + 5, city.y + 5) self.start() def restart(self): self.start() self.autoplay = True def start(self): try: seed = int(self.ui.seed.text()) except ValueError as e: seed = -1 if seed <= 0: seed = random.randint(0, 1000) #seed = 100 print(seed) random.seed(seed) np.random.seed(seed) self.clear_paths() self.ui.distance.setText("") clazz = self.ui.algorithm.currentData() self.genetic = clazz(self.cities, int(self.ui.popSize.text())).run({}) def on_play_click(self, play): self.autoplay = play def gen_next(self): if self.autoplay: self.next() @QtCore.pyqtSlot() def next(self): try: trajectories = next(self.genetic) except StopIteration: self.ui.playBtn.setChecked(False) self.autoplay = False return self.ui.distance.setText(f"{trajectories[0].distance:.2f} units") self.last = trajectories self.draw_trajectories(trajectories) def redraw(self): self.draw_trajectories(self.last) def draw_trajectories(self, trajectories): self.clear_paths() pens = [QPen(QColor(255, 0, 0)), QPen(QColor(0, 0, 0))] for i, trajectory in enumerate(trajectories): if i != 0 and not self.ui.showCur.isChecked(): break path = QPainterPath() path.moveTo(self.cities[trajectory.path[0]].x, self.cities[trajectory.path[0]].y) for hop in trajectory.path: city = self.cities[hop] path.lineTo(city.x, city.y) path.lineTo(self.cities[trajectory.path[0]].x, self.cities[trajectory.path[0]].y) self.paths.append(self.scene.addPath(path, pen=pens[i])) def clear_paths(self): for path in self.paths: self.scene.removeItem(path) self.paths = []