Example #1
0
 def show_text(self, text, size):
     self.update()
     window_bg = QGraphicsRectItem()
     window_bg.setRect(-OFSET[0], -OFSET[1], SCREEN_WIDTH, SCREEN_HEIGHT)
     window_bg.setBrush(QBrush(Qt.black))
     self.addItem(window_bg)
     self.text = QGraphicsTextItem(text)
     self.text.setDefaultTextColor(Qt.white)
     self.text.setPos(window_bg.rect().center() - QPoint(120, size))
     self.text.setFont(QFont('Press Start', int(size)))
     self.addItem(self.text)
Example #2
0
class PickingStation(VisualizerGraphicItem):
    def __init__(self, ID=0, x=0, y=0):
        super(self.__class__, self).__init__(ID, x, y)
        self._kind_name = 'pickingStation'

        self._items = []
        self._graphics_item = QGraphicsRectItem(self)
        self._items.append(QGraphicsRectItem(self._graphics_item))
        self._items.append(QGraphicsRectItem(self._graphics_item))
        self._text = QGraphicsTextItem(self._graphics_item)

    def set_rect(self, rect):
        scale = config.get('display', 'id_font_scale')
        bold = config.get('display', 'id_font_bold')
        self._text.setFont(QFont('', rect.width() * 0.08 * scale))
        self._text.setPos(rect.x(), rect.y() + 0.6 * rect.height())
        self._text.setDefaultTextColor(
            QColor(config.get('display', 'id_font_color')))
        if self._display_mode == 0:
            if bold:
                self._text.setHtml('<b>P(' + str(self._id) + ')</b>')
            else:
                self._text.setHtml('P(' + str(self._id) + ')')
            self._graphics_item.setRect(rect.x(), rect.y(), rect.width(),
                                        rect.height())
            self._items[0].setRect(rect.x() + rect.width() / 5, rect.y(),
                                   rect.width() / 5, rect.height())
            self._items[1].setRect(rect.x() + rect.width() / 5 * 3, rect.y(),
                                   rect.width() / 5, rect.height())
        elif self._display_mode == 1:
            self._text.setPlainText('')
            self._graphics_item.setRect(rect.x(), rect.y(), rect.width(),
                                        rect.height())
            self._items[0].setRect(rect.x() + rect.width() / 5, rect.y(),
                                   rect.width() / 5, rect.height())
            self._items[1].setRect(rect.x() + rect.width() / 5 * 3, rect.y(),
                                   rect.width() / 5, rect.height())

    def determine_color(self, number, count, pattern=None):
        color = self._colors[0]
        color2 = self._colors[1]
        color.setAlpha(150)
        color2.setAlpha(150)
        brush = QBrush(color)
        brush2 = QBrush(color2)

        self._graphics_item.setBrush(brush)
        self._items[0].setBrush(brush2)
        self._items[1].setBrush(brush2)

    def get_rect(self):
        return self._graphics_item.rect()
Example #3
0
    def get_elements(self, cell, height):
        result = []
        current = []

        rectangles = self.doc.elementsByTagName('rect')

        for i in range(rectangles.size()):
            rect = rectangles.item(i).toElement()
            fill = '#aaaaaa'

            item = QGraphicsRectItem(float(rect.attribute('x')), float(rect.attribute('y')),
                                     float(rect.attribute('width')), float(rect.attribute('height')))
            item.setBrush(QBrush(QColor(fill)))
            result.append(item)

            if rect.attribute('id') == str(cell):
                side_fill = fill
                size = item.rect().getRect()

                for j in range(1, height+1):
                    x = size[0] - 12 * j
                    y = size[1] - 20 * j

                    if j == height:
                        side_fill = '#ff0000'
                        top = QGraphicsRectItem(x, y, size[2], size[3])
                        top.setBrush(QBrush(QColor(fill).darker(200)))
                        current.append(top)

                    path = QPainterPath()
                    path.moveTo(x + size[2], y + size[3])
                    path.lineTo(x + size[2] + 12, y + size[3] + 20)
                    path.lineTo(x + 12, y + size[3] + 20)
                    path.lineTo(x, y + size[3])
                    front = QGraphicsPathItem(path)
                    front.setBrush(QBrush(QColor(side_fill)))
                    current.append(front)

                    path = QPainterPath()
                    path.moveTo(x + size[2], y + size[3])
                    path.lineTo(x + size[2] + 12, y + size[3] + 20)
                    path.lineTo(x + size[2] + 12, y + 20)
                    path.lineTo(x + size[2], y)
                    right = QGraphicsPathItem(path)
                    right.setBrush(QBrush(QColor(side_fill).lighter()))
                    current.append(right)

        return result + current
Example #4
0
    def draw_board(self, size=Cons.SIZE):
        """ Draw playing board """

        self.board_size = size
        self.c_size, self.r_size = Cons.SIZE_DICT[self.board_size]
        self.digits = Cons.DIGITS[:self.board_size]
        Tile.board_size = self.board_size
        Tile.digits = self.digits

        self.content = [["0"] * self.board_size for _ in range(self.board_size)]
        self.sudoku = [["0"] * self.board_size for _ in range(self.board_size)]
        self.tiles = [[None] * self.board_size for _ in range(self.board_size)]
        self.poss_tiles = [[None] * self.board_size for _ in range(self.board_size)]

        board_w = self.board_size * (Cons.WIDTH + Cons.MARGIN) + Cons.MARGIN  # width of playing board
        board_h = self.board_size * (Cons.HEIGHT + Cons.MARGIN) + Cons.MARGIN  # height of playing board
        board_x = (Cons.WINDOW_SIZE[0] - board_w) / 2  # x-coordinate of scrabble board
        board_y = (Cons.WINDOW_SIZE[1] - board_h) / 2  # y-coordinate of scrabble board
        board = [board_x, board_y, board_w, board_h]  # board rect details

        self.cells = []
        self.background_item = QGraphicsRectItem(board[0], board[1],
                                                 board[2], board[3])
        self.background_item.persistent = False
        self.background_item.setBrush(Qt.black)

        for row in range(self.board_size):
            for col in range(self.board_size):
                left_adj = Cons.INT_CELLS if col % self.c_size else 0
                top_adj = Cons.INT_CELLS if row % self.r_size else 0
                width_adj = Cons.INT_CELLS if col % self.c_size else 0
                height_adj = Cons.INT_CELLS if row % self.r_size else 0
                square = QGraphicsRectItem(board[0] + (Cons.MARGIN + Cons.WIDTH) * col +
                                           Cons.MARGIN - left_adj, board[1] + (Cons.MARGIN + Cons.HEIGHT) * row +
                                           Cons.MARGIN - top_adj, Cons.WIDTH + width_adj, Cons.HEIGHT + height_adj,
                                           parent=self.background_item)
                self.cells.append(square.rect())
                setattr(square, 'cell', (row, col))
                colour = Cons.NORMAL_COLOUR
                square.setBrush(QColor(colour[0], colour[1], colour[2]))
                square.setToolTip("Square")
                square.persistent = False

        self.scene.addItem(self.background_item)
        self.set_digits()
Example #5
0
    def on_actItem_Rect(self):  # 添加一个矩形  _triggered
        rect = QRectF(-50, -25, 100, 50)
        item = QGraphicsRectItem(rect)  # x,y 为左上角的图元局部坐标,图元中心点为0,0
        item.rect = rect
        item.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable)
        item.brush = Qt.yellow
        item.setBrush(QBrush(item.brush))
        item.style = Qt.SolidLine
        #item.setTransform(QTransform())
        self.view.frontZ = self.view.frontZ + 1
        item.setZValue(self.view.frontZ)
        item.setPos(-50 + (QtCore.qrand() % 100), -50 + (QtCore.qrand() % 100))
        self.view.seqNum = self.view.seqNum + 1
        item.setData(self.view.ItemId, self.view.seqNum)
        item.setData(self.view.ItemDesciption, "矩形")

        self.scene.addItem(item)
        self.scene.clearSelection()
        item.setSelected(True)
Example #6
0
class MyScene(QGraphicsScene):
    def __init__(self, data, parent=None):
        QGraphicsScene.__init__(self, parent)
        self.data = data
        self.rec = QGraphicsRectItem()
        self.plot: MyItem(data) = None
        self.bounding_rect = QGraphicsRectItem()
        self.setBackgroundBrush(QColor('#14161f'))

        self.addItem(self.bounding_rect)
        self.printed = False

    def mouseMoveEvent(self, event: 'QGraphicsSceneMouseEvent'):
        print()

        print("rec rect : ", self.rec.rect())
        print("Scene rect : ", self.sceneRect())
        print("ItemBounding rect : ", self.itemsBoundingRect())
        print("transform : ",
              self.plot.transform().m11(), ", ",
              self.plot.transform().m22())
        item = self.itemAt(event.scenePos(), self.plot.transform())

        if item and isinstance(item, MyItem):
            print()
            print('collides path : ',
                  self.rec.collidesWithPath(item.mapToScene(item.path)))
            print('collides item : ', self.rec.collidesWithItem(item))

        super().mouseMoveEvent(event)

    def print_bound(self, rect):
        self.bounding_rect.setPen(QPen(Qt.green))
        self.bounding_rect.setRect(rect.x() + 5,
                                   rect.y() + 5,
                                   rect.width() - 10,
                                   rect.height() - 10)
Example #7
0
class Robot(VisualizerGraphicItem):
    def __init__(self, ID=0, x=0, y=0):
        super(self.__class__, self).__init__(ID, x, y)
        self._kind_name = 'robot'
        self._carries = None
        self._initial_carries = None
        self._tasks = []
        self._graphics_item = QGraphicsRectItem(self)
        self._text = QGraphicsTextItem(self)
        self.setZValue(1.0)

    def set_position(self, x, y):
        super(self.__class__, self).set_position(x, y)
        if self._carries is not None:
            self._carries.set_position(x, y)

    def set_starting_position(self, x, y):
        super(self.__class__, self).set_starting_position(x, y)
        if self._initial_carries is not None:
            self._initial_carries.set_starting_position(x, y)

    def set_carries(self, shelf):
        if shelf == self._carries:
            return
        old = self._carries
        self._carries = shelf
        if old != None: old.set_carried(None)
        if self._carries != None: self._carries.set_carried(self)

    def set_initial_carries(self, shelf):
        self._initial_carries = shelf

    def set_rect(self, rect):
        scale = config.get('display', 'id_font_scale')
        bold = config.get('display', 'id_font_bold')
        self._text.setFont(QFont('', rect.width() * 0.08 * scale))
        self._text.setPos(rect.x(), rect.y() + 0.2 * rect.height())
        self._text.setDefaultTextColor(
            QColor(config.get('display', 'id_font_color')))

        if self._display_mode == 0:
            if bold:
                self._text.setHtml('<b>R(' + str(self._id) + ')</b>')
            else:
                self._text.setHtml('R(' + str(self._id) + ')')
            self._graphics_item.setRect(
                rect.x() + 0.25 * rect.width(),
                rect.y() + 0.25 * rect.height(),
                rect.width() * 0.5,
                rect.height() * 0.5,
            )

        elif self._display_mode == 1:
            self._text.setPlainText('')
            self._graphics_item.setRect(rect.x() + 0.05 * rect.width(),
                                        rect.y() + 0.05 * rect.height(),
                                        rect.width() * 0.9,
                                        rect.height() * 0.9)

        if self._carries is not None:
            self._carries.set_rect(rect)

    def add_task(self, task):
        if task is None:
            return
        if task in self._tasks:
            return
        self._tasks.append(task)
        task.set_robot(self)

    def parse_init_value(self, name, value):
        result = super(self.__class__, self).parse_init_value(name, value)
        if result <= 0: return result
        if name == 'carries':
            shelf = self._model.get_item('shelf', value, True, True)
            self.set_initial_carries(shelf)
            self.set_carries(shelf)
            return 0
        return 1

    def restart(self):
        super(self.__class__, self).restart()
        self.set_carries(self._initial_carries)

    def to_init_str(self):
        s = super(self.__class__, self).to_init_str()
        if self._initial_carries is not None:
            s += ("init(object(robot," + str(self._id) + "),value(carries," +
                  str(self._initial_carries.get_id()) + ")).")
        return s

    def do_action(self, time_step):
        if time_step >= len(self._actions):
            return 0  #break, if no action is defined
        if self._actions[time_step] == None:
            return 0  #break, if no action is defined
        if self._model is None:
            return -3

        try:
            action = self._actions[time_step].arguments[0]
            value = self._actions[time_step].arguments[1]
        except:
            return -1

        if action.name == 'move':
            if len(value.arguments) != 2:
                return -1
            try:
                move_x = value.arguments[0].number
                move_y = value.arguments[1].number
                self.set_position(self._position[0] + move_x,
                                  self._position[1] + move_y)
            except:
                self.set_position(self._position[0], self._position[1])

            for task in self._tasks:
                for checkpoint in task.get_checkpoints():
                    checkpoint = checkpoint[0]
                    pos = checkpoint.get_position()
                    if pos[0] == self._position[0] and pos[
                            1] == self._position[1]:
                        task.visit_checkpoint(checkpoint)
                        break
            return 1

        elif action.name == 'pickup':
            shelf = self._model.filter_items(item_kind='shelf',
                                             position=self._position,
                                             return_first=True)[0]
            if shelf is None:
                return -2
            self.set_carries(shelf)
            return 2

        elif action.name == 'putdown':
            if self._carries == None:
                return -2
            self.set_carries(None)
            return 3

        elif action.name == 'deliver' and len(value.arguments) > 2:
            try:
                if self._carries is not None:
                    self._carries.remove_product(value.arguments[1],
                                                 value.arguments[2].number)
                order = self._model.filter_items(item_kind='order',
                                                 ID=value.arguments[0],
                                                 return_first=True)[0]
                if order is None:
                    return -2
                order.deliver(value.arguments[1], value.arguments[2].number,
                              time_step)
            except:
                return -3
            return 4

        elif action.name == 'deliver' and len(value.arguments) > 1:
            try:
                if self._carries is not None:
                    self._carries.remove_product(value.arguments[1], 0)
                order = self._model.filter_items(item_kind='order',
                                                 ID=value.arguments[0],
                                                 return_first=True)[0]
                if order is None:
                    return -2
                order.deliver(value.arguments[1], 0, time_step)
            except:
                return -3
            return 5
        return 0

    def undo_action(self, time_step):
        if time_step >= len(self._actions):
            return 0  #break, if no action is defined
        if self._actions[time_step] == None:
            return 0  #break, if no action is defined
        if self._model is None:
            return -3

        try:
            action = self._actions[time_step].arguments[0]
            value = self._actions[time_step].arguments[1]
        except:
            return -1

        if action.name == 'move':
            if len(value.arguments) != 2:
                return -1

            for task in self._tasks:
                for checkpoint in task.get_checkpoints():
                    checkpoint = checkpoint[0]
                    pos = checkpoint.get_position()
                    if pos[0] == self._position[0] and pos[
                            1] == self._position[1]:
                        task.unvisit_checkpoint(checkpoint)
                        break

            try:
                move_x = value.arguments[0].number
                move_y = value.arguments[1].number
                self.set_position(self._position[0] - move_x,
                                  self._position[1] - move_y)
            except:
                self.set_position(self._position[0], self._position[1])
            if self._carries is not None:
                self._carries.set_position(self._position[0],
                                           self._position[1])
            return 1

        elif action.name == 'putdown':
            shelf = self._model.filter_items(item_kind='shelf',
                                             position=self._position,
                                             return_first=True)[0]
            if shelf is None:
                return -2
            self.set_carries(shelf)
            return 3

        elif action.name == 'pickup':
            if self._carries == None:
                return -2
            self.set_carries(None)
            return 2

        elif action.name == 'deliver' and len(value.arguments) > 2:
            try:
                if self._carries is not None:
                    self._carries.remove_product(value.arguments[1],
                                                 -value.arguments[2].number)
                order = self._model.filter_items(item_kind='order',
                                                 ID=value.arguments[0],
                                                 return_first=True)[0]
                if order is None:
                    return -2
                order.deliver(value.arguments[1], -value.arguments[2].number,
                              time_step)
            except:
                return -3
            return 4

        elif action.name == 'deliver' and len(value.arguments) > 1:
            try:
                if self._carries is not None:
                    self._carries.remove_product(value.arguments[1], 0)
                order = self._model.filter_items(item_kind='order',
                                                 ID=value.arguments[0],
                                                 return_first=True)[0]
                if order is None:
                    return -2
                order.deliver(value.arguments[1], 0, time_step)
            except:
                return -3
            return 5
        return 0

    def determine_color(self, number, count, pattern=None):
        color = calculate_color(self._colors[0], self._colors[1],
                                (float)(number) / count)
        brush = QBrush(color)
        self._graphics_item.setBrush(brush)

    def get_rect(self):
        if self._display_mode == 0:
            rect = self._graphics_item.rect()
            width = rect.width() * 2
            height = rect.height() * 2
            rect.setLeft(rect.x() - 0.25 * width)
            rect.setTop(rect.y() - 0.25 * height)
            rect.setWidth(width)
            rect.setHeight(height)
            return rect
        elif self._display_mode == 1:
            rect = self._graphics_item.rect()
            width = rect.width() / 0.9
            height = rect.height() / 0.9
            rect.setLeft(rect.x() - 0.05 * width)
            rect.setTop(rect.y() - 0.05 * height)
            rect.setWidth(width)
            rect.setHeight(height)
            return rect

    def get_carries(self):
        return self._carries

    def get_initial_carries(self):
        return self._initial_carries

    def edit_position_to(self, x, y):
        if (x, y) == self._position:
            return
        item2 = self._model.filter_items(item_kind=self._kind_name,
                                         position=(x, y),
                                         return_first=True)[0]
        shelf = self._model.filter_items('shelf',
                                         position=self._position,
                                         return_first=True)[0]
        shelf2 = self._model.filter_items('shelf',
                                          position=(x, y),
                                          return_first=True)[0]

        if shelf2 is not None and shelf is not None:
            if shelf.get_carried() is not None or shelf2.get_carried(
            ) is not None:
                shelf.set_position(x, y)
                shelf.set_starting_position(x, y)
                shelf2.set_position(self._position[0], self._position[1])
                shelf2.set_starting_position(self._position, self._position[1])
        if item2 is not None:
            item2.set_position(self._position[0], self._position[1])
            item2.set_starting_position(self._position[0], self._position[1])
        self.set_position(x, y)
        self.set_starting_position(x, y)
Example #8
0
    def refresh(self):
        if not self._mdlPlots or not self._mdlOutline or not self._mdlPersos:
            pass

        LINE_HEIGHT = 18
        SPACING = 3
        TEXT_WIDTH = self.sldTxtSize.value()
        CIRCLE_WIDTH = 10
        LEVEL_HEIGHT = 12

        s = self.scene
        s.clear()

        # Get Max Level (max depth)
        root = self._mdlOutline.rootItem
        def maxLevel(item, level=0, max=0):
            if level > max:
                max = level
            for c in item.children():
                m = maxLevel(c, level + 1)
                if m > max:
                    max = m
            return max

        MAX_LEVEL = maxLevel(root)

        # Generate left entries
        # (As of now, plot only)
        plotsID = self._mdlPlots.getPlotsByImportance()
        trackedItems = []
        fm = QFontMetrics(s.font())
        max_name = 0

        for importance in plotsID:
            for ID in importance:
                name = self._mdlPlots.getPlotNameByID(ID)
                ref = references.plotReference(ID, searchable=True)

                trackedItems.append((ID, ref, name))
                max_name = max(fm.width(name), max_name)

        ROWS_HEIGHT = len(trackedItems) * (LINE_HEIGHT + SPACING )
        TITLE_WIDTH = max_name + 2 * SPACING


        # Add Folders and Texts
        outline = OutlineRect(0, 0, 0, ROWS_HEIGHT + SPACING + MAX_LEVEL * LEVEL_HEIGHT)
        s.addItem(outline)
        outline.setPos(TITLE_WIDTH + SPACING, 0)

        refCircles = [] # a list of all references, to be added later on the lines

        # A Function to add a rect with centered elided text
        def addRectText(x, w, parent, text="", level=0, tooltip=""):
            deltaH = LEVEL_HEIGHT if level else 0
            r = OutlineRect(0, 0, w, parent.rect().height()-deltaH, parent, title=text)
            r.setPos(x, deltaH)

            txt = QGraphicsSimpleTextItem(text, r)
            f = txt.font()
            f.setPointSize(8)
            fm = QFontMetricsF(f)
            elidedText = fm.elidedText(text, Qt.ElideMiddle, w)
            txt.setFont(f)
            txt.setText(elidedText)
            txt.setPos(r.boundingRect().center() - txt.boundingRect().center())
            txt.setY(0)
            return r

        # A function to returns an item's width, by counting its children
        def itemWidth(item):
            if item.isFolder():
                r = 0
                for c in item.children():
                    r += itemWidth(c)
                return r or TEXT_WIDTH
            else:
                return TEXT_WIDTH

        def listItems(item, rect, level=0):
            delta = 0
            for child in item.children():
                w = itemWidth(child)

                if child.isFolder():
                    parent = addRectText(delta, w, rect, child.title(), level, tooltip=child.title())
                    parent.setToolTip(references.tooltip(references.textReference(child.ID())))
                    listItems(child, parent, level + 1)

                else:
                    rectChild = addRectText(delta, TEXT_WIDTH, rect, "", level, tooltip=child.title())
                    rectChild.setToolTip(references.tooltip(references.textReference(child.ID())))
                    
                    # Find tracked references in that scene (or parent folders)
                    for ID, ref, name in trackedItems:

                        result = []
                        c = child
                        while c:
                            result += references.findReferencesTo(ref, c, recursive=False)
                            c = c.parent()

                        if result:
                            ref2 = result[0]
                            
                            # Create a RefCircle with the reference
                            c = RefCircle(TEXT_WIDTH / 2, - CIRCLE_WIDTH / 2, CIRCLE_WIDTH, ID=ref2)
                            
                            # Store it, with the position of that item, to display it on the line later on
                            refCircles.append((ref, c, rect.mapToItem(outline, rectChild.pos())))

                delta += w

        listItems(root, outline)

        OUTLINE_WIDTH = itemWidth(root)

        # Add Plots
        i = 0
        itemsRect = s.addRect(0, 0, 0, 0)
        itemsRect.setPos(0, MAX_LEVEL * LEVEL_HEIGHT + SPACING)

        for ID, ref, name in trackedItems:
            color = randomColor()

            # Rect
            r = QGraphicsRectItem(0, 0, TITLE_WIDTH, LINE_HEIGHT, itemsRect)
            r.setPen(QPen(Qt.NoPen))
            r.setBrush(QBrush(color))
            r.setPos(0, i * LINE_HEIGHT + i * SPACING)
            i += 1

            # Text
            txt = QGraphicsSimpleTextItem(name, r)
            txt.setPos(r.boundingRect().center() - txt.boundingRect().center())

            # Line
            line = PlotLine(0, 0,
                            OUTLINE_WIDTH + SPACING, 0)
            line.setPos(TITLE_WIDTH, r.mapToScene(r.rect().center()).y())
            s.addItem(line)
            line.setPen(QPen(color, 5))
            line.setToolTip(self.tr("Plot: ") + name)

            # We add the circles / references to text, on the line
            for ref2, circle, pos in refCircles:
                if ref2 == ref:
                    circle.setParentItem(line)
                    circle.setPos(pos.x(), 0)

        # self.view.fitInView(0, 0, TOTAL_WIDTH, i * LINE_HEIGHT, Qt.KeepAspectRatioByExpanding) # KeepAspectRatio
        self.view.setSceneRect(0, 0, 0, 0)
Example #9
0
class RectItemInserter(ItemInserter):
    def __init__(self,
                 labeltool,
                 scene,
                 default_properties=None,
                 prefix="",
                 commit=True):
        ItemInserter.__init__(self, labeltool, scene, default_properties,
                              prefix, commit)
        self._aiming = True
        self._helpLines = None
        self._helpLinesPen = QPen(Qt.green, 2, Qt.DashLine)
        self._init_pos = None

    def mousePressEvent(self, event, image_item):
        self._aiming = False
        if self._helpLines is not None and self._helpLines.scene() is not None:
            self._scene.removeItem(self._helpLines)
            self._helpLines = None

        pos = event.scenePos()
        self._init_pos = pos
        self._item = QGraphicsRectItem(QRectF(pos.x(), pos.y(), 0, 0))
        self._item.setPen(self.pen())
        self._scene.addItem(self._item)
        event.accept()

    def mouseMoveEvent(self, event, image_item):
        if self._aiming:
            if self._helpLines is not None and self._helpLines.scene(
            ) is not None:
                self._scene.removeItem(self._helpLines)

            self._helpLines = QGraphicsItemGroup()
            group = self._helpLines

            verticalHelpLine = QGraphicsLineItem(event.scenePos().x(), 0,
                                                 event.scenePos().x(),
                                                 self._scene.height())
            horizontalHelpLine = QGraphicsLineItem(0,
                                                   event.scenePos().y(),
                                                   self._scene.width(),
                                                   event.scenePos().y())

            horizontalHelpLine.setPen(self._helpLinesPen)
            verticalHelpLine.setPen(self._helpLinesPen)

            group.addToGroup(verticalHelpLine)
            group.addToGroup(horizontalHelpLine)

            self._scene.addItem(self._helpLines)
        else:
            if self._item is not None:
                assert self._init_pos is not None
                rect = QRectF(self._init_pos, event.scenePos()).normalized()
                self._item.setRect(rect)

        event.accept()

    def mouseReleaseEvent(self, event, image_item):
        if self._item is not None:
            if self._item.rect().width() > 1 and \
               self._item.rect().height() > 1:
                rect = self._item.rect()
                self._ann.update({
                    self._prefix + 'x': rect.x(),
                    self._prefix + 'y': rect.y(),
                    self._prefix + 'width': rect.width(),
                    self._prefix + 'height': rect.height()
                })
                self._ann.update(self._default_properties)
                if self._commit:
                    image_item.addAnnotation(self._ann)
            if self._item is not None and self._item.scene() is not None:
                self._scene.removeItem(self._item)
            self.annotationFinished.emit()
            self._init_pos = None
            self._item = None

        self._aiming = True
        self._scene.views()[0].viewport().setCursor(Qt.CrossCursor)
        event.accept()

    def allowOutOfSceneEvents(self):
        return True

    def abort(self):
        if self._helpLines is not None and self._helpLines.scene() is not None:
            self._scene.removeItem(self._helpLines)
            self._helpLines = None

        if self._item is not None and self._item.scene() is not None:
            self._scene.removeItem(self._item)
            self._item = None
            self._init_pos = None
        ItemInserter.abort(self)
Example #10
0
    def refresh(self):
        if not self._mdlPlots or not self._mdlOutline or not self._mdlCharacter:
            return

        if not self.isVisible():
            return

        LINE_HEIGHT = 18
        SPACING = 3
        TEXT_WIDTH = self.sldTxtSize.value()
        CIRCLE_WIDTH = 10
        LEVEL_HEIGHT = 12

        s = self.scene
        s.clear()

        # Get Max Level (max depth)
        root = self._mdlOutline.rootItem

        def maxLevel(item, level=0, max=0):
            if level > max:
                max = level
            for c in item.children():
                m = maxLevel(c, level + 1)
                if m > max:
                    max = m
            return max

        MAX_LEVEL = maxLevel(root)

        # Get the list of tracked items (array of references)
        trackedItems = []

        if self.actPlots.isChecked():
            trackedItems += self.plotReferences()

        if self.actCharacters.isChecked():
            trackedItems += self.charactersReferences()

        ROWS_HEIGHT = len(trackedItems) * (LINE_HEIGHT + SPACING)

        fm = QFontMetrics(s.font())
        max_name = 0
        for ref in trackedItems:
            name = references.title(ref)
            max_name = max(fm.width(name), max_name)

        TITLE_WIDTH = max_name + 2 * SPACING

        # Add Folders and Texts
        outline = OutlineRect(0, 0, 0,
                              ROWS_HEIGHT + SPACING + MAX_LEVEL * LEVEL_HEIGHT)
        s.addItem(outline)
        outline.setPos(TITLE_WIDTH + SPACING, 0)

        refCircles = [
        ]  # a list of all references, to be added later on the lines

        # A Function to add a rect with centered elided text
        def addRectText(x, w, parent, text="", level=0, tooltip=""):
            deltaH = LEVEL_HEIGHT if level else 0
            r = OutlineRect(0,
                            0,
                            w,
                            parent.rect().height() - deltaH,
                            parent,
                            title=text)
            r.setPos(x, deltaH)

            txt = QGraphicsSimpleTextItem(text, r)
            f = txt.font()
            f.setPointSize(8)
            fm = QFontMetricsF(f)
            elidedText = fm.elidedText(text, Qt.ElideMiddle, w)
            txt.setFont(f)
            txt.setText(elidedText)
            txt.setPos(r.boundingRect().center() - txt.boundingRect().center())
            txt.setY(0)
            return r

        # A function to returns an item's width, by counting its children
        def itemWidth(item):
            if item.isFolder():
                r = 0
                for c in item.children():
                    r += itemWidth(c)
                return r or TEXT_WIDTH
            else:
                return TEXT_WIDTH

        def listItems(item, rect, level=0):
            delta = 0
            for child in item.children():
                w = itemWidth(child)

                if child.isFolder():
                    parent = addRectText(delta,
                                         w,
                                         rect,
                                         child.title(),
                                         level,
                                         tooltip=child.title())
                    parent.setToolTip(
                        references.tooltip(references.textReference(
                            child.ID())))
                    listItems(child, parent, level + 1)

                else:
                    rectChild = addRectText(delta,
                                            TEXT_WIDTH,
                                            rect,
                                            "",
                                            level,
                                            tooltip=child.title())
                    rectChild.setToolTip(
                        references.tooltip(references.textReference(
                            child.ID())))

                    # Find tracked references in that scene (or parent folders)
                    for ref in trackedItems:

                        result = []

                        # Tests if POV
                        scenePOV = False  # Will hold true of character is POV of the current text, not containing folder
                        if references.type(ref) == references.CharacterLetter:
                            ID = references.ID(ref)
                            c = child
                            while c:
                                if c.POV() == ID:
                                    result.append(c.ID())
                                    if c == child: scenePOV = True
                                c = c.parent()

                        # Search in notes/references
                        c = child
                        while c:
                            result += references.findReferencesTo(
                                ref, c, recursive=False)
                            c = c.parent()

                        if result:
                            ref2 = result[0]

                            # Create a RefCircle with the reference
                            c = RefCircle(TEXT_WIDTH / 2,
                                          -CIRCLE_WIDTH / 2,
                                          CIRCLE_WIDTH,
                                          ID=ref2,
                                          important=scenePOV)

                            # Store it, with the position of that item, to display it on the line later on
                            refCircles.append(
                                (ref, c,
                                 rect.mapToItem(outline, rectChild.pos())))

                delta += w

        listItems(root, outline)

        OUTLINE_WIDTH = itemWidth(root)

        # Add Tracked items
        i = 0
        itemsRect = s.addRect(0, 0, 0, 0)
        itemsRect.setPos(0, MAX_LEVEL * LEVEL_HEIGHT + SPACING)

        # Set of colors for plots (as long as they don't have their own colors)
        colors = [
            "#D97777",
            "#AE5F8C",
            "#D9A377",
            "#FFC2C2",
            "#FFDEC2",
            "#D2A0BC",
            "#7B0F0F",
            "#7B400F",
            "#620C3D",
            "#AA3939",
            "#AA6C39",
            "#882D61",
            "#4C0000",
            "#4C2200",
            "#3D0022",
        ]

        for ref in trackedItems:
            if references.type(ref) == references.CharacterLetter:
                color = self._mdlCharacter.getCharacterByID(
                    references.ID(ref)).color()
            else:
                color = QColor(colors[i % len(colors)])

            # Rect
            r = QGraphicsRectItem(0, 0, TITLE_WIDTH, LINE_HEIGHT, itemsRect)
            r.setPen(QPen(Qt.NoPen))
            r.setBrush(QBrush(color))
            r.setPos(0, i * LINE_HEIGHT + i * SPACING)
            r.setToolTip(references.tooltip(ref))
            i += 1

            # Text
            name = references.title(ref)
            txt = QGraphicsSimpleTextItem(name, r)
            txt.setPos(r.boundingRect().center() - txt.boundingRect().center())

            # Line
            line = PlotLine(0, 0, OUTLINE_WIDTH + SPACING, 0)
            line.setPos(TITLE_WIDTH, r.mapToScene(r.rect().center()).y())
            s.addItem(line)
            line.setPen(QPen(color, 5))
            line.setToolTip(references.tooltip(ref))

            # We add the circles / references to text, on the line
            for ref2, circle, pos in refCircles:
                if ref2 == ref:
                    circle.setParentItem(line)
                    circle.setPos(pos.x(), 0)

        # self.view.fitInView(0, 0, TOTAL_WIDTH, i * LINE_HEIGHT, Qt.KeepAspectRatioByExpanding) # KeepAspectRatio
        self.view.setSceneRect(0, 0, 0, 0)
Example #11
0
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
Example #12
0
class OverlayGraphics(QGraphicsView):
    def __init__(self):
        super(OverlayGraphics, self).__init__()
        self.setStyleSheet("background:transparent")  # ビューの背景透明化
        self.overlayScene = QGraphicsScene()
        self.setScene(self.overlayScene)
        self.overlayScene.setSceneRect(QRectF(self.rect()))
        self.createItem()

        self.luRect = QRect()
        self.rbRect = QRect()
        self.isSelected = False
        self.setTargetMode(False)
        self.hide()  # 初期状態は非表示

    def createItem(self):
        #  ターゲットマーカの作成
        self.target_circle = QGraphicsEllipseItem(
            QtCore.QRectF(-10, -10, 20, 20))
        self.target_circle.setBrush(QBrush(Qt.red))
        self.target_circle.setPen(QPen(Qt.black))
        self.overlayScene.addItem(self.target_circle)
        self.setTargetMode(False)

        # モーダルの作成:モーダルはターゲット位置に追従する
        self.pop_rect = QGraphicsRectItem(QtCore.QRectF(0, 0, 100, 60),
                                          self.target_circle)
        self.pop_rect.setBrush(QBrush(Qt.gray))
        self.pop_rect.setPen(QPen(Qt.gray))
        self.pop_rect.setOpacity(0.8)  # 透明度を設定

        self.operate_text = QGraphicsSimpleTextItem("", self.pop_rect)
        self.operate_text.setScale(1.7)
        self.sub_operate_text = QGraphicsSimpleTextItem("", self.pop_rect)
        self.sub_operate_text.setScale(1.7)
        self.setTargetPos(400, 180, DirectionEnum.VERTICAL.value)

    # オーバレイヤのサイズが変わると呼び出される.シーンのサイズをビューの大きさに追従(-5 はマージン)
    def resizeEvent(self, event):
        self.overlayScene.setSceneRect(
            QRectF(0, 0,
                   self.size().width() - 5,
                   self.size().height() - 5))

    # ウィンドウ左上からの位置(画面位置からウインドウ左上の位置を引く)
    def setTargetPos(self, x_pos, y_pos, direction):
        self.target_circle.setPos(QPointF(x_pos, y_pos))
        self.setPopPos(direction)

    def setPopTextPos(self, text1, text2):
        lentext1 = len(text1)
        lentext2 = len(text2)
        if lentext2 == 0:
            self.operate_text.setPos(
                (self.pop_rect.rect().size().width() / 2) -
                (lentext1 / 2 * 14), 15)
        else:
            self.operate_text.setPos(
                (self.pop_rect.rect().size().width() / 2) -
                (lentext1 / 2 * 14), 5)
            self.sub_operate_text.setPos(
                (self.pop_rect.rect().size().width() / 2) -
                (lentext2 / 2 * 14), 30)

    def setPopPos(self, direction):
        if direction == DirectionEnum.VERTICAL.value:
            # モーダルを右に表示しきれない場合に左に表示
            if self.target_circle.pos().x() > self.overlayScene.width(
            ) - self.pop_rect.rect().size().width() * 1.5:
                self.pop_rect.setPos(
                    -self.pop_rect.rect().size().width() * 1.5,
                    -self.pop_rect.rect().size().height() / 2)
            else:  # 右に表示
                self.pop_rect.setPos(self.pop_rect.rect().size().width() / 2,
                                     -self.pop_rect.rect().size().height() / 2)

        else:
            # モーダルを下に表示しきれない場合に上に表示
            if self.target_circle.pos().y() > self.overlayScene.height(
            ) - self.pop_rect.rect().size().height() * 1.5:
                self.pop_rect.setPos(
                    -self.pop_rect.rect().size().width() / 2,
                    -self.pop_rect.rect().size().height() * 1.5)
            else:  # 下に表示
                self.pop_rect.setPos(-self.pop_rect.rect().size().width() / 2,
                                     self.pop_rect.rect().size().height() / 2)

    def setTargetMode(self, active):
        self.targetMode = active
        if active:
            self.target_circle.setRect(-10, -10, 20, 20)
        else:  # ターゲットを非表示にする
            self.target_circle.setRect(0, 0, 0, 0)

    # def targetVisible(self, visible):
    #     if visible:
    #         self.target_circle.setRect(-10, -10, 20, 20)
    #     else:
    #         self.target_circle.setRect(0, 0, 0, 0)

    def feedbackShow(self, text1, text2, direction):
        if self.isSelected:
            self.operate_text.setText(text1)
            self.sub_operate_text.setText(text2)
            # ターゲットモードがアクティブでないとき,ターゲットマーカの位置は選択セルに依存
            if not self.targetMode:
                if direction == DirectionEnum.HORIZON.value:
                    x_pos = (self.luRect.left() + self.rbRect.right()) / 2 + 20
                    y_pos = self.rbRect.bottom(
                    ) - self.rbRect.height() / 2 + 20
                else:
                    x_pos = self.rbRect.right() - self.rbRect.width() / 2 + 20
                    y_pos = (self.luRect.top() + self.rbRect.bottom()) / 2 + 20

                self.setTargetPos(x_pos, y_pos, direction)
                self.setPopTextPos(text1, text2)
            self.show()
class VideoScene(QGraphicsScene):
    regionSelected = Signal(QRect)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.videoItem = QGraphicsVideoItem()
        self.videoItem.setPos(0, 0)
        self.videoItem.nativeSizeChanged.connect(self.videoSizeChanged)
        self.addItem(self.videoItem)

        self.rectangle = QGraphicsRectItem()
        self.rectangle.setPen(QColor(0, 122, 217))
        self.addItem(self.rectangle)

    def videoSizeChanged(self, size: QSizeF):
        logging.debug("%s", size)
        if not size.isEmpty():
            self.videoItem.setSize(size)
            self.setSceneRect(self.videoItem.boundingRect())
            self.clearSelection()

    def clearSelection(self) -> None:
        self.rectangle.setRect(QRectF())
        self.regionSelected.emit(self.selection)

    @property
    def selection(self) -> QRect:
        return self.rectangle.rect().toRect()

    @staticmethod
    def coerceInside(point: QPointF, rect: QRectF):
        return QPointF(
            min(max(point.x(), rect.left()), rect.right()),
            min(max(point.y(), rect.top()), rect.bottom()),
        )

    def coerceInsideVideo(self, point: QPointF):
        return self.coerceInside(point, self.videoItem.boundingRect())

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        self.startPos = self.coerceInsideVideo(event.scenePos())
        return super().mousePressEvent(event)

    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        endPos = self.coerceInsideVideo(event.scenePos())
        self.rectangle.setRect(QRectF(self.startPos, endPos).normalized())
        return super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        logging.info("Selection: %s", self.selection)
        super().mouseReleaseEvent(event)
        self.regionSelected.emit(self.selection)

    def paintGrid(self, painter: QPainter, rect: QRectF, gridSize: int):
        left, top, right, bottom = rect.getCoords()
        x_min = (int(left - 1) // gridSize + 1) * gridSize
        y_min = (int(top - 1) // gridSize + 1) * gridSize
        x_max = (int(right) // gridSize) * gridSize
        y_max = (int(bottom) // gridSize) * gridSize
        # logging.debug(
        #     "x=(%s<=%s..%s<=%s) y=(%s<=%s..%s<=%s)",
        #     *(left, x_min, x_max, right),
        #     *(top, y_min, y_max, bottom)
        # )
        painter.setPen(QPen(Qt.gray, 2))
        painter.drawLines(QLineF(0, top, 0, bottom), QLineF(left, 0, right, 0))
        painter.setPen(QPen(Qt.gray, 1))
        painter.drawLines(
            QLineF(x, top, x, bottom)
            for x in range(x_min, x_max + 1, gridSize) if x)
        painter.drawLines(
            QLineF(left, y, right, y)
            for y in range(y_min, y_max + 1, gridSize) if y)

    def drawBackground(self, painter: QPainter, rect: QRectF) -> None:
        super().drawBackground(painter, rect)
        self.paintGrid(painter, rect, 20)
Example #14
0
 def create_info_display(self, x, y, attributes):
     """
     Creates view elements for the info display
     :param x: x position of the node
     :param y: y position of the node
     :param attributes: attributes that will be displayed in the view
     :return:
     """
     start_height = y + (self.NODE_HEIGHT / 2)
     # unfold dictionary values at the bottom of the list
     sorted_attributes = []
     for k, v in sorted(attributes.items(), key=lambda tup: isinstance(tup[1], dict)):
         if isinstance(v, dict):
             sorted_attributes.append((k, v))
             sorted_attributes.extend(v.items())
         else:
             sorted_attributes.append((k, v))
     # create property rows
     for i, (k, v) in enumerate(sorted_attributes):
         value_text = None
         value_height = 0
         if isinstance(v, dict):
             # display dictionary key as title
             text = "{}".format(k)
             if len(text) > 20:
                 text = text[:20] + "..."
             key_text = QGraphicsSimpleTextItem(text)
             f = key_text.font()
             f.setBold(True)
             key_text.setFont(f)
             text_width = key_text.boundingRect().width()
         else:
             key_text = QGraphicsSimpleTextItem("{}:".format(k) if k else " ")
             text = str(v)
             if len(text) > 20:
                 text = text[:20] + "..."
             value_text = QGraphicsSimpleTextItem(text)
             value_height = value_text.boundingRect().height()
             text_width = key_text.boundingRect().width() + value_text.boundingRect().width()
         # create box around property
         attribute_container = QGraphicsRectItem(x, start_height, text_width + 10,
                                                 max(key_text.boundingRect().height(),
                                                     value_height) + 10)
         attribute_container.setBrush(QBrush(Qt.white))
         self.total_height += attribute_container.rect().height()
         key_text.setParentItem(attribute_container)
         if value_text:
             value_text.setParentItem(attribute_container)
         self.max_width = max(self.max_width, attribute_container.rect().width())
         attribute_container.setParentItem(self)
         self.info_display.append(attribute_container)
         start_height += max(key_text.boundingRect().height(), value_height) + 10
     # calculate correct coordinates for positioning of the attribute boxes
     if self.max_width > self.NODE_MIN_WIDTH - 10:
         x -= (self.max_width + 10) / 2
         y -= self.total_height / 2
         self.max_width += 10
     else:
         x -= self.NODE_MIN_WIDTH / 2
         y -= self.total_height / 2
         self.max_width = self.NODE_MIN_WIDTH
     h = 0
     # position all the elements previously created
     for attribute_container in self.info_display:
         rect: QRectF = attribute_container.rect()
         rect.setX(x)
         rect_height = rect.height()
         rect.setY(y + self.NODE_HEIGHT + h)
         rect.setHeight(rect_height)
         key_child = attribute_container.childItems()[0]
         if len(attribute_container.childItems()) == 2:
             key_child.setX(x + 5)
             value_child = attribute_container.childItems()[1]
             value_child.setX(x + self.max_width - value_child.boundingRect().width() - 5)
             value_child.setY(y + self.NODE_HEIGHT + h + 5)
         else:
             key_child.setX(x - key_child.boundingRect().width() / 2 + self.max_width / 2)
         key_child.setY(y + self.NODE_HEIGHT + h + 5)
         h += rect.height()
         rect.setWidth(self.max_width)
         attribute_container.setRect(rect)
Example #15
0
class NodeTemplateItem():
    ''' 
    This represents one node template on the diagram.  A node template can be on many diagrams
    This class creates the rectangle graphics item and the text graphics item and adds them to the scene.
    '''
    def __init__(self, scene, x, y, nodeTemplateDict=None, NZID=None):
        self.scene = scene
        self.logMsg = None
        self.x = x
        self.y = y
        self.nodeTemplateDict = nodeTemplateDict
        #        self.name = self.nodeTemplateDict.get("name", "")   THIS HAS BEEN REPLACED BY THE name FUNCTION - SEE BELOW
        self.diagramType = "Node Template"
        self.displayText = None
        self.model = self.scene.parent.model
        self.gap = 100
        self.relList = []
        # assign a unique key if it doesn't already have one
        if NZID == None:
            self.NZID = str(uuid.uuid4())
        else:
            self.NZID = NZID

        # init graphics objects to none
        self.TNode = None
        self.TNtext = None

        # draw the node template on the diagram
        self.drawIt()

    def name(self, ):
        return self.nodeTemplateDict.get("name", "")

    def getX(self, ):
        return self.TNode.boundingRect().x()

    def getY(self, ):
        return self.TNode.boundingRect().y()

    def getHeight(self, ):
        return self.TNode.boundingRect().height()

    def getWidth(self, ):
        return self.TNode.boundingRect().width()

    def getRelList(self, ):
        '''return a list of all relationitems that are inbound or outbound from this node template.
          do not include self referencing relationships
        '''
        return [
            diagramItem
            for key, diagramItem in self.scene.parent.itemDict.items()
            if diagramItem.diagramType == "Relationship Template" and (
                diagramItem.startNZID == self.NZID
                or diagramItem.endNZID == self.NZID)
        ]

    def getPoint(self, offset=None):
        '''
        This function is used by the template diagram to calculate the location to drop a node template on the diagram
        '''
        if offset is None:
            return QPointF(self.x, self.y)
        else:
            return QPointF(self.x + offset, self.y + offset)

    def getFormat(self, ):
        '''
        determine if the Node Template  has a template format or should use the project default format
        '''
        # get the node Template custom format
        customFormat = self.nodeTemplateDict.get("TNformat", None)

        if not customFormat is None:
            # get the template custom format
            self.nodeFormat = TNodeFormat(formatDict=customFormat)
        else:
            # get the project default format
            self.nodeFormat = TNodeFormat(
                formatDict=self.model.modelData["TNformat"])

    def clearItem(self, ):

        if (not self.TNode is None and not self.TNode.scene() is None):
            self.TNode.scene().removeItem(self.TNode)
        if (not self.TNtext is None and not self.TNtext.scene() is None):
            self.TNtext.scene().removeItem(self.TNtext)

    def drawIt(self, ):

        # get current format as it may have changed
        self.getFormat()

        # create the qgraphicsItems if they don't exist
        if self.TNode is None:
            # create the rectangle
            self.TNode = QGraphicsRectItem(QRectF(
                self.x, self.y, self.nodeFormat.formatDict["nodeWidth"],
                self.nodeFormat.formatDict["nodeHeight"]),
                                           parent=None)
            self.TNode.setZValue(NODELAYER)
            self.TNode.setFlag(QGraphicsItem.ItemIsMovable, True)
            self.TNode.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
            self.TNode.setFlag(QGraphicsItem.ItemIsSelectable, True)
            self.TNode.setSelected(True)
            self.TNode.setData(1, self.NZID)  # get with self.INode.data(1)
            self.TNode.setData(ITEMTYPE, NODETEMPLATE)
            # create the text box
            self.TNtext = QGraphicsTextItem("", parent=None)
            self.TNtext.setPos(self.x, self.y)
            self.TNtext.setFlag(QGraphicsItem.ItemIsMovable, True)
            self.TNtext.setFlag(QGraphicsItem.ItemIsSelectable, False)
            self.TNtext.setData(NODEID, self.NZID)
            self.TNtext.setData(ITEMTYPE, NODETEMPLATETEXT)
            self.TNtext.setZValue(NODELAYER)
            # save the location
            self.x = self.TNode.sceneBoundingRect().x()
            self.y = self.TNode.sceneBoundingRect().y()
            # generate the html and resize the rectangle
            self.formatItem()
            # add the graphics items to the scene
            self.scene.addItem(self.TNode)
            self.scene.addItem(self.TNtext)
        else:
            # generate the html and resize the rectangle
            self.formatItem()

    def formatItem(self, ):

        # configure the formatting aspects of the qgraphics item
        pen = self.nodeFormat.pen()
        brush = self.nodeFormat.brush()
        self.TNode.setBrush(brush)
        self.TNode.setPen(pen)

        # generate the HTML
        genHTML = self.generateHTML()
        self.TNtext.prepareGeometryChange()
        #        print("before html bounding rectangle width:{}".format(self.TNtext.boundingRect().width()))
        #        print("before html text width:{}".format(self.TNtext.textWidth()))
        self.TNtext.setTextWidth(
            -1
        )  # reset the width to unkonwn so it will calculate a new width based on the new html
        self.TNtext.setHtml(genHTML)
        #        print("after html bounding rectangle width:{}".format(self.TNtext.boundingRect().width()))
        #        print("after html text width:{}".format(self.TNtext.textWidth()))

        # make sure minimum width of 120
        if self.TNtext.boundingRect().width() < 120:
            self.TNtext.setTextWidth(120)
        else:
            self.TNtext.setTextWidth(
                self.TNtext.boundingRect().width()
            )  # you have to do a setTextWidth to get the html to render correctly.

        # set the rectangle item to the same size as the formatted html
        self.TNode.prepareGeometryChange()
        currentRect = self.TNode.rect()
        # insure minimum height of 120
        if self.TNtext.boundingRect().height() < 120:
            currentRect.setHeight(120)
        else:
            currentRect.setHeight(self.TNtext.boundingRect().height())
        currentRect.setWidth(self.TNtext.boundingRect().width())
        self.TNode.setRect(currentRect)

    def generateHTML(self, ):
        '''
        Generate the HTML that formats the node template data inside the rectangle
        '''
        # generate the html
        prefix = "<!DOCTYPE html><html><body>"
        #        head = "<head><style>table, th, td {border: 1px solid black; border-collapse: collapse;}</style></head>"
        suffix = "</body></html>"
        #        blankRow = "<tr><td><left>{}</left></td><td><left>{}</left></td><td><left>{}</left></td><td><left>{}</left></td></tr>".format("", "", "", "")

        name = "<center><b>{}</b></center>".format(
            self.nodeTemplateDict.get("name", ""))
        lbls = self.genLblHTML()
        props = self.genPropHTML()
        genHTML = "{}{}<hr>{}<br><hr>{}{}".format(prefix, name, lbls, props,
                                                  suffix)
        #        print("{} html: {}".format(self.name(), genHTML))

        return genHTML

    def genLblHTML(self):
        #        html = '<table width="90%">'
        html = '<table style="width:90%;border:1px solid black;">'
        if len(self.nodeTemplateDict.get("labels", [])) > 0:
            for lbl in self.nodeTemplateDict.get("labels", []):
                if lbl[NODEKEY] == Qt.Checked:
                    nk = "NK"
                else:
                    nk = "&nbsp;&nbsp;"
                if lbl[REQUIRED] == Qt.Checked:
                    rq = "R"
                else:
                    rq = ""

                html = html + '<tr align="left"><td width="15%"><left>{}</left></td><td width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format(
                    nk, lbl[LABEL], "", rq)
            html = html + "</table>"
        else:
            html = '<tr align="left"><td width="15%"><left>{}</left></td><td  width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format(
                "  ", "NO{}LABELS".format("&nbsp;"), "", "")
            html = html + "</table>"

        return html

    def genPropHTML(self):
        #        PROPERTY, DATATYPE, PROPREQ, DEFAULT, EXISTS, UNIQUE, PROPNODEKEY
        html = '<table style="width:90%;border:1px solid black;">'
        if len(self.nodeTemplateDict.get("properties", [])) > 0:
            for prop in self.nodeTemplateDict.get("properties", []):
                if prop[PROPNODEKEY] == Qt.Checked:
                    nk = "NK"
                else:
                    nk = "&nbsp;&nbsp;"
                if prop[PROPREQ] == Qt.Checked:
                    rq = "R"
                else:
                    rq = ""
                if prop[EXISTS] == Qt.Checked:
                    ex = "E"
                else:
                    ex = ""
                if prop[UNIQUE] == Qt.Checked:
                    uq = "U"
                else:
                    uq = ""
                html = html + '<tr align="left"><td width="15%"><left>{}</left></td><td width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format(
                    nk, prop[PROPERTY], rq, ex, uq)
            html = html + "</table>"
        else:
            html = html + '<tr align="left"><td width="15%"><left>{}</left></td><td width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format(
                "&nbsp;&nbsp;", "NO{}PROPERTIES".format("&nbsp;"), "", "", "")
            html = html + "</table>"
        return html

    def moveIt(self, dx, dy):
        '''
        Move the node rectangle and the node textbox to the delta x,y coordinate.
        '''
        #        print("before moveIt: sceneboundingrect {} ".format( self.INode.sceneBoundingRect()))

        self.TNode.moveBy(dx, dy)
        self.x = self.TNode.sceneBoundingRect().x()
        self.y = self.TNode.sceneBoundingRect().y()
        self.TNtext.moveBy(dx, dy)
        #        print("after moveIt: sceneboundingrect {} ".format( self.INode.sceneBoundingRect()))
        # now redraw all the relationships
        self.drawRels()

    def drawRels(self, ):
        '''Redraw all the relationship lines connected to the Node Template Rectangle'''
        # get a list of the relationship items connected to this node template
        self.relList = self.getRelList()

        # assign the correct inbound/outbound side for the rel
        for rel in self.relList:
            if rel.endNodeItem.NZID != rel.startNodeItem.NZID:  # ignore bunny ears
                rel.assignSide()
        # get a set of all the nodes and sides involved
        nodeSet = set()
        for rel in self.relList:
            if rel.endNodeItem.NZID != rel.startNodeItem.NZID:  # ignore bunny ears
                nodeSet.add((rel.endNodeItem, rel.inboundSide))
                nodeSet.add((rel.startNodeItem, rel.outboundSide))

        # tell each node side to assign rel locations
        for nodeSide in nodeSet:
            nodeSide[0].assignPoint(nodeSide[1])

        ############################################

        # now tell them all to redraw
        for rel in self.relList:
            rel.drawIt2()

    def calcOffset(self, index, totRels):
        offset = [-60, -40, -20, 0, 20, 40, 60]
        offsetStart = [3, 2, 2, 1, 1, 0, 0]
        if totRels > 7:
            totRels = 7
        return offset[offsetStart[totRels - 1] + index]

    def assignPoint(self, side):
        # go through all the rels on a side and assign their x,y coord for that side
        self.relList = self.getRelList()
        sideList = [
            rel for rel in self.relList
            if ((rel.startNZID == self.NZID and rel.outboundSide == side) or (
                rel.endNZID == self.NZID and rel.inboundSide == side))
        ]
        totRels = len(sideList)
        if totRels > 0:
            if side == R:
                # calc center of the side
                x = self.x + self.getWidth()
                y = self.y + self.getHeight() / 2
                # sort the rels connected to this side by the y value
                sideList.sort(key=self.getSortY)
                # assign each of them a position on the side starting in the center and working out in both directions
                for index, rel in enumerate(sideList):
                    if rel.startNZID == self.NZID:
                        rel.outboundPoint = QPointF(
                            x, y + (self.calcOffset(index, totRels)))
                    if rel.endNZID == self.NZID:
                        rel.inboundPoint = QPointF(
                            x, y + (self.calcOffset(index, totRels)))
            elif side == L:
                x = self.x
                y = self.y + self.getHeight() / 2
                sideList.sort(key=self.getSortY)
                for index, rel in enumerate(sideList):
                    if rel.startNZID == self.NZID:
                        rel.outboundPoint = QPointF(
                            x, y + (self.calcOffset(index, totRels)))
                    if rel.endNZID == self.NZID:
                        rel.inboundPoint = QPointF(
                            x, y + (self.calcOffset(index, totRels)))
            elif side == TOP:
                x = self.x + self.getWidth() / 2
                y = self.y
                sideList.sort(key=self.getSortX)
                for index, rel in enumerate(sideList):
                    if rel.startNZID == self.NZID:
                        rel.outboundPoint = QPointF(
                            x + (self.calcOffset(index, totRels)), y)
                    if rel.endNZID == self.NZID:
                        rel.inboundPoint = QPointF(
                            x + (self.calcOffset(index, totRels)), y)
            elif side == BOTTOM:
                x = self.x + self.getWidth() / 2
                y = self.y + self.getHeight()
                sideList.sort(key=self.getSortX)
                for index, rel in enumerate(sideList):
                    if rel.startNZID == self.NZID:
                        rel.outboundPoint = QPointF(
                            x + (self.calcOffset(index, totRels)), y)
                    if rel.endNZID == self.NZID:
                        rel.inboundPoint = QPointF(
                            x + (self.calcOffset(index, totRels)), y)
            else:
                print("error, no side")

    def getSortY(self, rel):
        # if this node is the start node then return the end node's Y
        if rel.startNZID == self.NZID:
            return rel.endNodeItem.TNode.sceneBoundingRect().center().y()
        # if this node is the end node then return the start node's Y
        if rel.endNZID == self.NZID:
            return rel.startNodeItem.TNode.sceneBoundingRect().center().y()
        # this should never happen
        return 0

    def getSortX(self, rel):
        # if this node is the start node then return the end node's X
        if rel.startNZID == self.NZID:
            return rel.endNodeItem.TNode.sceneBoundingRect().center().x()
        # if this node is the end node then return the start node's X
        if rel.endNZID == self.NZID:
            return rel.startNodeItem.TNode.sceneBoundingRect().center().x()
        # this should never happen
        return 0

    def getObjectDict(self, ):
        '''
        This function returns a dictionary with all the data that represents this node template item.  
        The dictionary is added to the Instance Diagram dictionary.'''
        objectDict = {}
        objectDict["NZID"] = self.NZID
        objectDict["name"] = self.nodeTemplateDict.get("name", "")
        objectDict["displayText"] = self.displayText
        objectDict["x"] = self.TNode.sceneBoundingRect().x()
        objectDict["y"] = self.TNode.sceneBoundingRect().y()
        objectDict["diagramType"] = self.diagramType
        objectDict["labels"] = self.nodeTemplateDict.get("labels", [])
        objectDict["properties"] = self.nodeTemplateDict.get("properties", [])

        return objectDict

    def setLogMethod(self, logMethod=None):
        if logMethod is None:
            if self.logMsg is None:
                self.logMsg = self.noLog
        else:
            self.logMsg = logMethod

    def noLog(self, msg):
        return
Example #16
0
    def refresh(self):
        if not self._mdlPlots or not self._mdlOutline or not self._mdlCharacter:
            return

        if not self.isVisible():
            return

        LINE_HEIGHT = 18
        SPACING = 3
        TEXT_WIDTH = self.sldTxtSize.value()
        CIRCLE_WIDTH = 10
        LEVEL_HEIGHT = 12

        s = self.scene
        s.clear()

        # Get Max Level (max depth)
        root = self._mdlOutline.rootItem
        def maxLevel(item, level=0, max=0):
            if level > max:
                max = level
            for c in item.children():
                m = maxLevel(c, level + 1)
                if m > max:
                    max = m
            return max

        MAX_LEVEL = maxLevel(root)

        # Get the list of tracked items (array of references)
        trackedItems = []

        if self.actPlots.isChecked():
            trackedItems += self.plotReferences()

        if self.actCharacters.isChecked():
            trackedItems += self.charactersReferences()

        ROWS_HEIGHT = len(trackedItems) * (LINE_HEIGHT + SPACING )

        fm = QFontMetrics(s.font())
        max_name = 0
        for ref in trackedItems:
            name = references.title(ref)
            max_name = max(fm.width(name), max_name)

        TITLE_WIDTH = max_name + 2 * SPACING

        # Add Folders and Texts
        outline = OutlineRect(0, 0, 0, ROWS_HEIGHT + SPACING + MAX_LEVEL * LEVEL_HEIGHT)
        s.addItem(outline)
        outline.setPos(TITLE_WIDTH + SPACING, 0)

        refCircles = [] # a list of all references, to be added later on the lines

        # A Function to add a rect with centered elided text
        def addRectText(x, w, parent, text="", level=0, tooltip=""):
            deltaH = LEVEL_HEIGHT if level else 0
            r = OutlineRect(0, 0, w, parent.rect().height()-deltaH, parent, title=text)
            r.setPos(x, deltaH)

            txt = QGraphicsSimpleTextItem(text, r)
            f = txt.font()
            f.setPointSize(8)
            fm = QFontMetricsF(f)
            elidedText = fm.elidedText(text, Qt.ElideMiddle, w)
            txt.setFont(f)
            txt.setText(elidedText)
            txt.setPos(r.boundingRect().center() - txt.boundingRect().center())
            txt.setY(0)
            return r

        # A function to returns an item's width, by counting its children
        def itemWidth(item):
            if item.isFolder():
                r = 0
                for c in item.children():
                    r += itemWidth(c)
                return r or TEXT_WIDTH
            else:
                return TEXT_WIDTH

        def listItems(item, rect, level=0):
            delta = 0
            for child in item.children():
                w = itemWidth(child)

                if child.isFolder():
                    parent = addRectText(delta, w, rect, child.title(), level, tooltip=child.title())
                    parent.setToolTip(references.tooltip(references.textReference(child.ID())))
                    listItems(child, parent, level + 1)

                else:
                    rectChild = addRectText(delta, TEXT_WIDTH, rect, "", level, tooltip=child.title())
                    rectChild.setToolTip(references.tooltip(references.textReference(child.ID())))
                    
                    # Find tracked references in that scene (or parent folders)
                    for ref in trackedItems:

                        result = []

                        # Tests if POV
                        scenePOV = False  # Will hold true of character is POV of the current text, not containing folder
                        if references.type(ref) == references.CharacterLetter:
                            ID = references.ID(ref)
                            c = child
                            while c:
                                if c.POV() == ID:
                                    result.append(c.ID())
                                    if c == child: scenePOV = True
                                c = c.parent()

                        # Search in notes/references
                        c = child
                        while c:
                            result += references.findReferencesTo(ref, c, recursive=False)
                            c = c.parent()

                        if result:
                            ref2 = result[0]
                            
                            # Create a RefCircle with the reference
                            c = RefCircle(TEXT_WIDTH / 2, - CIRCLE_WIDTH / 2, CIRCLE_WIDTH, ID=ref2, important=scenePOV)
                            
                            # Store it, with the position of that item, to display it on the line later on
                            refCircles.append((ref, c, rect.mapToItem(outline, rectChild.pos())))

                delta += w

        listItems(root, outline)

        OUTLINE_WIDTH = itemWidth(root)

        # Add Tracked items
        i = 0
        itemsRect = s.addRect(0, 0, 0, 0)
        itemsRect.setPos(0, MAX_LEVEL * LEVEL_HEIGHT + SPACING)

        # Set of colors for plots (as long as they don't have their own colors)
        colors = [
            "#D97777", "#AE5F8C", "#D9A377", "#FFC2C2", "#FFDEC2", "#D2A0BC",
            "#7B0F0F", "#7B400F", "#620C3D", "#AA3939", "#AA6C39", "#882D61",
            "#4C0000", "#4C2200", "#3D0022",
        ]

        for ref in trackedItems:
            if references.type(ref) == references.CharacterLetter:
                color = self._mdlCharacter.getCharacterByID(references.ID(ref)).color()
            else:
                color = QColor(colors[i % len(colors)])

            # Rect
            r = QGraphicsRectItem(0, 0, TITLE_WIDTH, LINE_HEIGHT, itemsRect)
            r.setPen(QPen(Qt.NoPen))
            r.setBrush(QBrush(color))
            r.setPos(0, i * LINE_HEIGHT + i * SPACING)
            r.setToolTip(references.tooltip(ref))
            i += 1

            # Text
            name = references.title(ref)
            txt = QGraphicsSimpleTextItem(name, r)
            txt.setPos(r.boundingRect().center() - txt.boundingRect().center())

            # Line
            line = PlotLine(0, 0,
                            OUTLINE_WIDTH + SPACING, 0)
            line.setPos(TITLE_WIDTH, r.mapToScene(r.rect().center()).y())
            s.addItem(line)
            line.setPen(QPen(color, 5))
            line.setToolTip(references.tooltip(ref))

            # We add the circles / references to text, on the line
            for ref2, circle, pos in refCircles:
                if ref2 == ref:
                    circle.setParentItem(line)
                    circle.setPos(pos.x(), 0)

        # self.view.fitInView(0, 0, TOTAL_WIDTH, i * LINE_HEIGHT, Qt.KeepAspectRatioByExpanding) # KeepAspectRatio
        self.view.setSceneRect(0, 0, 0, 0)
Example #17
0
     QGraphicsItem, QGraphicsRectItem, QGraphicsTextItem, QGraphicsEllipseItem
import sys
from random import random

app = QApplication(sys.argv)

# definition de la scène
scene = QGraphicsScene()
rectGris = QGraphicsRectItem(0., 0., 200., 150.)
rectGris.setBrush(QBrush(Qt.lightGray))
scene.addItem(rectGris)
# ou (equivalent):
# rectGris = scene.addRect(0.,0.,200.,150.,brush=QBrush(Qt.lightGray))

texte = QGraphicsTextItem("Tous en scène !")
dy = rectGris.rect().height() - texte.sceneBoundingRect().height()
texte.setPos(rectGris.x(), rectGris.y() + dy)
texte.setDefaultTextColor(Qt.blue)
scene.addItem(texte)

d = 48.  # diametre smiley
ox = 4.  # largeur oeil
oy = 6.  # hauteur oeil
smiley = QGraphicsEllipseItem(-d / 2, -d / 2, d, d)
smiley.setBrush(QBrush(Qt.yellow))
yeux = [QGraphicsEllipseItem(-ox/2.,-oy/2.,ox,oy,parent=smiley) \
        for _ in range(2)]
yeux[0].setPos(-d / 6, -d / 8)
yeux[1].setPos(+d / 6, -d / 8)
brush = QBrush(Qt.black)
for oeil in yeux:
Example #18
0
class BankNodeItem (NodeItem):
    maincolor = FlPalette.bank
    altcolor = FlPalette.bankvar
    label = "%s Bank"
    
    def __init__ (self, nodeobj, parent=None, view=None, state=1):
        super().__init__(nodeobj, parent, view, state)
        self.rect = QRectF()
        self.setZValue(-1)
        self.updatecomment()
        self.updatebanktype()
        self.updatebankmode()
    
    def nudgechildren(self):
        super().nudgechildren()
        for sub in self.sublist():
            sub.setrank(self)
    
    def sublist (self):
        ID = self.nodeobj.ID
        itemtable = self.view.itemtable
        if self.state == 1 and ID in itemtable and not self.iscollapsed():
            children = []
            for child in self.nodeobj.subnodes:
                if child in itemtable[ID]:
                    item = itemtable[ID][child]
                else:
                    continue
                if item.state > -1:
                    children.append(item)
            return children
        else:
            return []
    
    def treeposition (self, ranks=None):
        self.updatelayout(external=True)
        return super().treeposition(ranks)
    
    def graphicsetup (self):
        super().graphicsetup()
        darkbrush = QBrush(FlPalette.bg)
        nopen = QPen(0)
        viewport = self.view.viewport()
        
        self.btypeicon = QGraphicsPixmapItemCond(self.icon, self, viewport)
        self.btypeicon.setPos(self.iconx-self.style.itemmargin-self.iwidth, self.style.itemmargin)
        self.iconx = self.btypeicon.x()
        self.fggroup.addToGroup(self.btypeicon)
        
        self.centerbox = QGraphicsRectItem(self)
        self.centerbox.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        self.centerbox.setRect(QRectF())
        self.centerbox.setBrush(darkbrush)
        self.centerbox.setPen(nopen)
        self.centerbox.setPos(0, self.nodelabel.y()+self.nodelabel.boundingRect().height()+self.style.itemmargin*2)
        self.fggroup.addToGroup(self.centerbox)
    
    def updatebanktype (self):
        types = {"talk": "(T)", "response": "(R)", "": ""}
        self.nodelabel.setText("%s Bank %s" % (self.realid(), types[self.nodeobj.banktype]))
    
    def updatebankmode (self):
        icons = {"First": "bank-first", "All": "bank-all", "Append": "bank-append", "": "blank"}
        pixmap = self.pixmap("images/%s.png" % icons[self.nodeobj.bankmode])
        self.btypeicon.setPixmap(pixmap)
        if self.nodeobj.bankmode:
            self.btypeicon.setToolTip("Bank mode: %s" % self.nodeobj.bankmode)
        else:
            self.btypeicon.setToolTip("")
    
    def updatecenterbox (self):
        verticalpos = self.centerbox.y()
        maxwidth = self.style.nodetextwidth
        subnodes = self.sublist()
        for subnode in subnodes:
            if subnode.nodeobj.typename == "bank":
                subnode.updatelayout(external=True)
            noderect = subnode.boundingRect()
            nodeheight = noderect.height()
            nodewidth = noderect.width()
            subnode.show()
            subnode.yoffset = self.mapToScene(0,verticalpos + nodeheight/2+self.style.activemargin).y()-self.y_bottom()
            verticalpos += nodeheight+self.style.activemargin*2
            maxwidth = max(maxwidth, nodewidth)
        centerrect = self.centerbox.rect()
        centerrect.setWidth(maxwidth+self.style.selectmargin*2)
        centerrect.setHeight(verticalpos-self.centerbox.y())
        self.centerbox.setRect(centerrect)
        centerrect = self.centerbox.mapRectToParent(centerrect)
        
        self.comment.setY(centerrect.bottom()+self.style.itemmargin)
    
    def updatelayout (self, external=False):
        subnodes = self.sublist()
        if self.iscollapsed():
            rect = self.nodelabel.mapRectToParent(self.nodelabel.boundingRect())
        else:
            self.updatecenterbox()
            rect = self.fggroup.childrenBoundingRect()
        mainrect = rect.marginsAdded(self.style.banknodemargins)
        self.mainbox.setRect(mainrect)
        self.shadowbox.setRect(mainrect)
        self.selectbox.setRect(mainrect.marginsAdded(self.style.selectmargins))
        activerect = mainrect.marginsAdded(self.style.activemargins)
        self.activebox.setRect(activerect)
        oldypos = self.centerbox.mapToScene(self.centerbox.pos()).y()
        self.graphgroup.setPos(-activerect.width()//2-activerect.x(), -activerect.height()//2-activerect.y())
        newypos = self.centerbox.mapToScene(self.centerbox.pos()).y()
        for subnode in subnodes:
            subnode.yoffset += newypos - oldypos
            subnode.setY(self.y())
        self.prepareGeometryChange()
        self.rect = self.graphgroup.mapRectToParent(mainrect)
        if not external:
            self.view.updatelayout()
    
    def setY (self, y):
        super().setY(y)
        for subnode in self.sublist():
            subnode.setY(y)
    
    def contextMenuEvent (self, event):
        menu = QMenu()
        if self.isselected():
            window = FlGlob.mainwindow
            menu.addAction(window.actions["collapse"])
            if self.isghost():
                menu.addAction(window.actions["selectreal"])
            menu.addAction(window.actions["copynode"])
            menu.addMenu(window.subnodemenu)
            menu.addMenu(window.addmenu)
            menu.addAction(window.actions["moveup"])
            menu.addAction(window.actions["movedown"])
            menu.addAction(window.actions["parentswap"])
            menu.addAction(window.actions["unlinknode"])
            menu.addAction(window.actions["unlinkstree"])
            menu.addAction(window.actions["settemplate"])
            menu.addMenu(window.transformmenu)
        if not menu.isEmpty():
            menu.exec_(event.screenPos())
Example #19
0
class Checkpoint(VisualizerGraphicItem):
    def __init__(self, ID=0, x=0, y=0):
        super(self.__class__, self).__init__(ID, x, y)
        self._kind_name = 'checkpoint'

        self._ids = {}

        self._graphics_item = QGraphicsRectItem(self)
        self._text = QGraphicsTextItem(self._graphics_item)
        self._shine = False

    def set_rect(self, rect):
        scale = config.get('display', 'id_font_scale')
        bold = config.get('display', 'id_font_bold')
        font = QFont('', rect.width() * 0.08 * scale)
        self._text.setFont(font)
        self._text.setPos(rect.x(), rect.y() + 0.6 * rect.height())
        self._text.setDefaultTextColor(
            QColor(config.get('display', 'id_font_color')))

        if self._display_mode == 0:
            ss = ''
            if bold:
                ss = '<b>'
            for key in self._ids:
                count = 0
                for ii in self._ids[key]:
                    if count == 0:
                        ss = ss + '(' + key + ': ' + ii[0]
                    else:
                        ss = ss + ', ' + ii[0]
                    count += 1
                ss = ss + ')\n'

            if bold:
                ss += '</b>'

            self._text.setHtml(ss)
            self._graphics_item.setRect(rect.x(), rect.y(), rect.width(),
                                        rect.height())

        elif self._display_mode == 1:
            self._text.setPlainText('')
            self._graphics_item.setRect(rect.x(), rect.y(), rect.width(),
                                        rect.height())

    def do_action(self, time_step):
        self._shine = False
        return

    def undo_action(self, time_step):
        self._shine = False
        return

    def visit(self):
        self._shine = True

    def determine_color(self, number, count, pattern=None):
        color = None
        if len(self._ids) == 1:
            for key in self._ids:
                color_id = self._ids[key][0][1] + 1
                if color_id < len(self._colors):
                    color = self._colors[color_id]

        if color is None:
            color = self._colors[0]

        if self._shine:
            color = calculate_color(color, QColor(255, 255, 255), 0.4)
        self._graphics_item.setBrush(QBrush(color))

    def parse_init_value(self, name, value):
        result = super(self.__class__, self).parse_init_value(name, value)
        if result != 1:
            return result

        if name == 'checkpoint' and len(value.arguments) == 3:
            if str(value.arguments[0]) not in self._ids:
                self._ids[str(value.arguments[0])] = []
            self._ids[str(value.arguments[0])].append(
                (str(value.arguments[1]), value.arguments[2].number))
            return 0
        return 1

    def get_rect(self):
        return self._graphics_item.rect()
Example #20
0
class PaintArea(QGraphicsView):

    def __init__(self, width=10, parent=None):
        QGraphicsView.__init__(self, parent)
        self._frame = None
        self._instructions = None
        self.setScene(QGraphicsScene(self))
        self._items = self.scene().createItemGroup([])
        self.setMouseTracking(True)
        self.pen = QPen(Qt.black, width, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
        self.painting = False
        self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
        self.viewport().setCursor(self.getCursor())
        self.updateScene()

    def updateScene(self):
        if self._frame:
            self.scene().setBackgroundBrush(Qt.gray)
        oldCanvas = self.canvas()
        self.setSceneRect(QRectF(self.contentsRect()))
        self.centerScene()
        self.scaleItems(oldCanvas, self.canvas())

    def centerScene(self):
        self.centerFrame()
        self.centerInstructions()

    def scaleItems(self, oldCanvas, newCanvas):
        pass

    def canvas(self):
        if self._frame:
            return self._frame.rect()
        return QRectF(self.contentsRect())

    def fitInstructions(self):
        textSize = self._instructions.document().size()
        factor = min(self.canvas().size().width() / textSize.width(),
                     self.canvas().size().height() / textSize.height())
        f = self._instructions.font()
        f.setPointSizeF(f.pointSizeF() * factor)
        self._instructions.setFont(f)

    def centerInstructions(self):
        if self._instructions:
            self.fitInstructions()
            size = self.size()
            textSize = self._instructions.document().size()
            self._instructions.setPos((size.width() - textSize.width()) / 2.0,
                                      (size.height() - textSize.height()) / 2.0)

    def setInstructions(self, text):
        if self._instructions:
            self._instructions.setPlainText(text)
        else:
            self._instructions = self.scene().addText(text, QFont('Arial', 10, QFont.Bold))
            self._instructions.setZValue(-1)
            self._instructions.setDefaultTextColor(QColor(220, 220, 220))
        self._text = text
        self.centerInstructions()

    def setFrame(self, width, height):
        if self._frame:
            self._frame.setRect(0, 0, width, height)
        else:
            self.addFrame(QRectF(0, 0, width, height))
        self.centerScene()

    def addFrame(self, rect):
        self._frame = QGraphicsRectItem(rect)
        self._frame.setPen(QPen(Qt.NoPen))
        self._frame.setBrush(Qt.white)
        self._frame.setZValue(-2)
        self.scene().addItem(self._frame)

    def centerFrame(self):
        if self._frame:
            rect = self._frame.rect()
            size = self.contentsRect()
            factor = min((size.width() + 1) / rect.width(),
                         (size.height() + 1) / rect.height())
            w, h = rect.width() * factor, rect.height() * factor
            self._frame.setRect(size.x() + (size.width() - w) / 2.0,
                                size.y() + (size.height() - h) / 2.0,
                                w, h)

    def resizeEvent(self, event):
        self.updateScene()

    def setBrushSize(self, size):
        self.pen.setWidth(size)
        self.viewport().setCursor(self.getCursor())

    def render(self, painter):
        if self._instructions:
            self.scene().removeItem(self._instructions)
        self.scene().render(painter,
                            source=self.scene().itemsBoundingRect())
        if self._instructions:
            self.scene().addItem(self._instructions)

    def getLines(self):
        items = [item for item in self.scene().items()
                 if item.group() == self._items]
        return self.canvas(), items

    def clear(self):
        for item in self.scene().items():
            if item.group() == self._items:
                self._items.removeFromGroup(item)

    def getCursor(self):
        antialiasing_margin = 1
        size = self.pen.width()
        pixmap = QPixmap(size + antialiasing_margin * 2,
                         size + antialiasing_margin * 2)
        pixmap.fill(Qt.transparent)
        painter = QPainter(pixmap)
        painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
        painter.drawEllipse(QRectF(QPointF(antialiasing_margin, antialiasing_margin),
                                   QSizeF(size, size)))
        painter.end()
        return QCursor(pixmap)

    def addLine(self, start, end):
        if start == end:
            delta = QPointF(.0001, 0)
            end = start - delta
        line = self.scene().addLine(QLineF(start, end), self.pen)
        self._items.addToGroup(line)

    def drawPoint(self, pos):
        delta = QPointF(.0001, 0)
        line = self.scene().addLine(QLineF(pos, pos - delta), self.pen)
        self._items.addToGroup(line)

    def mousePressEvent(self, event):
        self.start = QPointF(self.mapToScene(event.pos()))
        self.painting = True
        self.addLine(self.start, self.start)

    def mouseReleaseEvent(self, event):
        self.painting = False

    def mouseMoveEvent(self, event):
        pos = QPointF(self.mapToScene(event.pos()))
        if self.painting:
            self.addLine(self.start, pos)
            self.start = pos
Example #21
0
class RecordsetWindow(QWidget):

    dataDisplayRequest = pyqtSignal(str, int)
    dataUpdateRequest = pyqtSignal(str, Base)

    def __init__(self, manager, recordset: list, parent=None):
        super().__init__(parent=parent)
        self.UI = Ui_frmRecordsets()
        self.UI.setupUi(self)

        # Internal lists
        self.sensors = {}           # List of sensors objects
        self.sensors_items = {}     # List of QAction corresponding to each sensors
        self.sensors_graphs = {}    # List of graph corresponding to each sensor graph
        self.sensors_location = []  # List of sensors location in this recordset
        self.sensors_blocks = {}    # Sensor blocks - each block of data for each sensor

        # Variables
        self.time_pixmap = False    # Flag used to check if we need to repaint the timeline or not
        self.zoom_level = 1         # Current timeview zoom level

        # Manually created UI objects
        self.time_bar = QGraphicsLineItem()         # Currently selected timestamp bar
        self.selection_rec = QGraphicsRectItem()    # Section rectangle
        self.sensors_menu = QMenu(self.UI.btnNewGraph)
        self.UI.btnNewGraph.setMenu(self.sensors_menu)

        # Data access informations
        self.dbMan = manager
        self.recordsets = recordset

        # Init temporal browser
        self.timeScene = QGraphicsScene()
        self.UI.graphTimeline.setScene(self.timeScene)
        self.UI.graphTimeline.fitInView(self.timeScene.sceneRect())
        self.UI.graphTimeline.time_clicked.connect(self.timeview_clicked)
        self.UI.graphTimeline.time_selected.connect(self.timeview_selected)
        self.UI.scrollTimeline.valueChanged.connect(self.timeview_scroll)

        # Init temporal sensor list
        self.timeSensorsScene = QGraphicsScene()
        self.UI.graphSensorsTimeline.setScene(self.timeSensorsScene)
        self.UI.graphSensorsTimeline.fitInView(self.timeSensorsScene.sceneRect(), Qt.KeepAspectRatio)

        # Update general informations about recordsets
        self.update_recordset_infos()

        # Load sensors for that recordset
        self.load_sensors()

        # Connect signals to slots
        self.UI.btnClearSelection.clicked.connect(self.on_timeview_clear_selection_requested)
        self.UI.btnTimeZoomSelection.clicked.connect(self.on_timeview_zoom_selection_requested)
        self.UI.btnZoomReset.clicked.connect(self.on_timeview_zoom_reset_requested)
        self.UI.btnDisplayTimeline.clicked.connect(self.on_timeview_show_hide_requested)
        self.UI.btnTileHorizontal.clicked.connect(self.tile_graphs_horizontally)
        self.UI.btnTileVertical.clicked.connect(self.tile_graphs_vertically)
        self.UI.btnTileAuto.clicked.connect(self.tile_graphs_auto)
        self.sensors_menu.triggered.connect(self.sensor_graph_selected)

        # Initial UI state
        self.UI.btnZoomReset.setEnabled(False)
        self.UI.btnTimeZoomSelection.setEnabled(False)
        self.UI.btnClearSelection.setEnabled(False)
        self.update_tile_buttons_state()

    def paintEvent(self, paint_event):
        if not self.time_pixmap:
            self.refresh_timeview()
            self.time_pixmap = True

    def resizeEvent(self, resize_event):
        if self.time_pixmap:
            self.refresh_timeview()

    def refresh_timeview(self):
        QGuiApplication.setOverrideCursor(Qt.BusyCursor)

        # Computes required timescene size
        min_width = self.UI.graphTimeline.width() - 5
        if len(self.recordsets) > 0:
            num_days = (self.get_recordset_end_day_date() - self.get_recordset_start_day_date()).days

            # Minimum size for days
            if num_days * 75 > min_width:
                min_width = num_days * 75 - 5

        # Resize timeScene correctly
        self.timeScene.clear()
        self.timeScene.setSceneRect(self.timeScene.itemsBoundingRect())
        self.timeScene.addLine(0, 80, min_width, 80, QPen(Qt.transparent))

        # Set background color
        back_brush = QBrush(Qt.lightGray)
        self.timeScene.setBackgroundBrush(back_brush)
        self.timeSensorsScene.setBackgroundBrush(back_brush)

        # Update display
        self.draw_dates()
        self.draw_sensors_names()
        self.draw_recordsets()
        self.draw_sensors()
        self.draw_grid()
        self.draw_timebar()

        # Adjust splitter sizes
        self.adjust_timeview_size()
        # self.UI.frmSensors.hide()

        QGuiApplication.restoreOverrideCursor()

    def adjust_timeview_size(self):
        self.UI.frameScrollSpacer.setFixedWidth(self.UI.graphTimeline.pos().x())
        if self.timeScene.itemsBoundingRect().width() * self.zoom_level > self.UI.graphTimeline.width():
            self.UI.scrollTimeline.setVisible(True)
            # self.UI.scrollTimeline.setMinimum(self.UI.graphTimeline.width()/2)
            self.UI.scrollTimeline.setMinimum(0)
            self.UI.scrollTimeline.setMaximum(self.timeScene.itemsBoundingRect().width() * self.zoom_level)
            self.UI.scrollTimeline.setPageStep(self.UI.graphTimeline.width()/2)
            self.UI.scrollTimeline.setSingleStep(self.UI.graphTimeline.width()/5)
        else:
            self.UI.scrollTimeline.setVisible(False)

    def load_sensors(self):

        # self.UI.lstSensors.clear()
        self.sensors = {}
        self.sensors_items = {}
        self.sensors_location = []
        self.sensors_menu.clear()

        if len(self.recordsets) > 0:
            for recordset in self.recordsets:
                for sensor in self.dbMan.get_sensors(recordset):
                    if sensor.location not in self.sensors_location:
                        self.sensors_location.append(sensor.location)
                        self.sensors_menu.addSection(sensor.location)
                    if sensor.id_sensor not in self.sensors:
                        self.sensors[sensor.id_sensor] = sensor
                        sensor_item = QAction(sensor.name)
                        sensor_item.setCheckable(True)
                        sensor_item.setProperty("sensor_id", sensor.id_sensor)
                        self.sensors_items[sensor.id_sensor] = sensor_item
                        self.sensors_menu.addAction(sensor_item)

            # Create sensors blocks for display
            self.load_sensors_blocks()
        else:
            self.UI.btnNewGraph.setEnabled(False)

    def update_recordset_infos(self):
        if len(self.recordsets) == 0:
            self.UI.lblTotalValue.setText("Aucune donnée.")
            self.UI.lblDurationValue.setText("Aucune donnée.")
            return

        start_time = self.recordsets[0].start_timestamp
        end_time = self.recordsets[len(self.recordsets) - 1].end_timestamp

        # Coverage
        self.UI.lblTotalValue.setText(start_time.strftime('%d-%m-%Y %H:%M:%S') + " @ " + end_time.strftime(
                                                                                                '%d-%m-%Y %H:%M:%S'))

        # Duration
        # TODO: format better
        self.UI.lblDurationValue.setText(str(end_time - start_time))

        self.UI.lblCursorTime.setText(start_time.strftime('%d-%m-%Y %H:%M:%S'))

    def get_recordset_start_day_date(self):
        if len(self.recordsets) == 0:
            return None
        start_time = self.recordsets[0].start_timestamp
        start_time = (datetime(start_time.year, start_time.month, start_time.day, 0, 0, 0))
        return start_time

    def get_recordset_end_day_date(self):
        if len(self.recordsets) == 0:
            return None
        end_time = self.recordsets[len(self.recordsets) - 1].end_timestamp
        end_time = (datetime(end_time.year, end_time.month, end_time.day, 0, 0, 0) + timedelta(days=1))
        return end_time

    def get_relative_timeview_pos(self, current_time):
        # start_time = self.recordsets[0].start_timestamp.timestamp()
        start_time = self.get_recordset_start_day_date().timestamp()
        # end_time = self.recordsets[len(self.recordsets) - 1].end_timestamp.timestamp()
        end_time = self.get_recordset_end_day_date().timestamp()
        time_span = (end_time - start_time)  # Total number of seconds in recordsets
        # if type(current_time) is datetime:
        if isinstance(current_time, datetime):
            current_time = current_time.timestamp()

        if time_span > 0:
            # return ((current_time - start_time) / time_span) * self.UI.graphTimeline.width()
            return ((current_time - start_time) / time_span) * self.timeScene.width()
        else:
            return 0

    def get_time_from_timeview_pos(self, pos):
        if len(self.recordsets) == 0:
            return None;

        start_time = self.get_recordset_start_day_date().timestamp()
        end_time = self.get_recordset_end_day_date().timestamp()

        current_time = (pos / self.timeScene.width()) * (end_time - start_time) + start_time
        current_time = datetime.fromtimestamp(current_time)

        return current_time

    def draw_dates(self):
        if len(self.recordsets) == 0:
            return

        # Computations
        start_time = self.recordsets[0].start_timestamp
        end_time = self.recordsets[len(self.recordsets) - 1].end_timestamp
        # time_span = (end_time - start_time).total_seconds()  # Total number of seconds in recordsets
        current_time = (datetime(start_time.year, start_time.month, start_time.day, 0, 0, 0) + timedelta(days=1))

        # Drawing tools
        black_pen = QPen(Qt.black)
        blue_brush = QBrush(Qt.darkBlue)

        # Date background
        self.timeScene.addRect(0, 0, self.timeScene.width(), 20, black_pen, blue_brush)
        self.timeSensorsScene.addRect(0, 0, self.timeSensorsScene.width(), 20, black_pen, blue_brush)

        # First date
        date_text = self.timeScene.addText(start_time.strftime("%d-%m-%Y"))
        date_text.setPos(0, 0)  # -5
        date_text.setDefaultTextColor(Qt.white)
        date_text.setFlag(QGraphicsItem.ItemIgnoresTransformations, True)

        # Date separators
        while current_time <= end_time:
            pos = self.get_relative_timeview_pos(current_time)
            date_text = self.timeScene.addText(current_time.strftime("%d-%m-%Y"))
            date_text.setPos(pos, 0)  # -5
            date_text.setDefaultTextColor(Qt.white)
            date_text.setFlag(QGraphicsItem.ItemIgnoresTransformations, True)
            current_time += timedelta(days=1)

        # self.UI.graphTimeline.fitInView(self.timeScene.sceneRect(), Qt.KeepAspectRatio)

    def draw_grid(self):
        if len(self.recordsets) == 0:
            return

        # Computations
        start_time = self.recordsets[0].start_timestamp
        end_time = self.recordsets[len(self.recordsets) - 1].end_timestamp
        # time_span = (end_time - start_time).total_seconds()  # Total number of seconds in recordsets
        current_time = (datetime(start_time.year, start_time.month, start_time.day, 0, 0, 0) + timedelta(days=1))

        vgrid_pen = QPen(Qt.gray)
        hgrid_pen = QPen(Qt.black)
        vgrid_pen.setCosmetic(True)

        # Horizontal lines
        pos = 20
        # last_location = ""
        sensor_location_brush = QBrush(Qt.black)
        sensor_location_pen = QPen(Qt.transparent)
        for location in self.sensors_location:
            # Must create a new location line
            self.timeScene.addRect(0, pos, self.timeScene.width() - 1, 15, sensor_location_pen, sensor_location_brush)
            pos += 15
            sensors = self.get_sensors_for_location(location)
            for _ in sensors:
                self.timeScene.addLine(0, pos, self.timeScene.width() - 1, pos, hgrid_pen)
                self.timeSensorsScene.addLine(0, pos, self.timeSensorsScene.width(), pos, hgrid_pen)
                pos += 20

        # Final line
        self.timeScene.addLine(0, pos, self.timeScene.width() - 1, pos, hgrid_pen)
        self.timeSensorsScene.addLine(0, pos, self.timeSensorsScene.width() - 1, pos, hgrid_pen)

        # Date separators
        self.timeScene.addLine(0, 0, 0, self.timeScene.height(), vgrid_pen)

        # Other dates
        while current_time <= end_time:
            pos = self.get_relative_timeview_pos(current_time)
            self.timeScene.addLine(pos, 0, pos, self.timeScene.height(), vgrid_pen)
            current_time += timedelta(days=1)

    def draw_recordsets(self):
        recordset_brush = QBrush(QColor(212, 247, 192))  # Green
        recordset_pen = QPen(Qt.transparent)

        # Recording length
        for record in self.recordsets:
            start_pos = self.get_relative_timeview_pos(record.start_timestamp)
            end_pos = self.get_relative_timeview_pos(record.end_timestamp)
            span = end_pos - start_pos
            # print (str(span))
            self.timeScene.addRect(start_pos, 21, span, self.timeSensorsScene.height()-21, recordset_pen,
                                   recordset_brush)

        # self.UI.graphTimeline.update()
        return

    def draw_sensors_names(self):
        if len(self.sensors) == 0:
            return

        sensor_location_brush = QBrush(Qt.black)
        sensor_location_pen = QPen(Qt.transparent)

        # Sensor names
        pos = 20

        for location in self.sensors_location:
            # Must create a new location space for later
            pos += 15
            sensors = self.get_sensors_for_location(location)
            for sensor_id in sensors:
                sensor = self.sensors[sensor_id]
                # Sensor names
                label = self.timeSensorsScene.addText(sensor.name)
                label.setPos(0, pos)
                label.setDefaultTextColor(Qt.black)
                # label.setFont(QFont("Times", 10, QFont.Bold))
                pos += 20

        # Adjust size appropriately
        self.timeSensorsScene.setSceneRect(self.timeSensorsScene.itemsBoundingRect())
        self.UI.graphSensorsTimeline.setMaximumWidth(self.timeSensorsScene.itemsBoundingRect().width())

        # Sensor location background
        pos = 20
        for location in self.sensors_location:
            # Must create a new location line
            self.timeSensorsScene.addRect(0, pos, self.timeSensorsScene.width(), 15, sensor_location_pen,
                                          sensor_location_brush)
            label = self.timeSensorsScene.addText(location)
            label.setPos(0, pos)
            label.setDefaultTextColor(Qt.white)
            label.setFont(QFont("Times", 7))
            pos += 15
            sensors = self.get_sensors_for_location(location)
            for sensor_id in sensors:
                pos += 20

    def draw_sensors(self):
        if len(self.sensors) == 0:
            return

        sensor_brush = QBrush(Qt.darkGreen)
        sensor_pen = QPen(Qt.transparent)

        sensors_rects = self.create_sensors_rects()
        for _, rect in enumerate(sensors_rects):
            self.timeScene.addRect(rect, sensor_pen, sensor_brush)

        # Adjust size appropriately
        self.timeSensorsScene.setSceneRect(self.timeSensorsScene.itemsBoundingRect())
        self.UI.graphSensorsTimeline.setMaximumWidth(self.timeSensorsScene.itemsBoundingRect().width())
        # self.UI.graphSensorsTimeline.setMaximumHeight(self.timeSensorsScene.itemsBoundingRect().height())

    def load_sensors_blocks(self):
        # Create request tasks
        tasks = []
        for location in self.sensors_location:
            sensors = self.get_sensors_for_location(location)
            for sensor_id in sensors:
                for record in self.recordsets:
                    tasks.append(DBSensorTimesTask(title="Chargement des données temporelles", db_manager=self.dbMan,
                                                   sensor_id=sensor_id, recordset=record))

        QGuiApplication.setOverrideCursor(Qt.BusyCursor)
        process = BackgroundProcess(tasks)
        dialog = ProgressDialog(process, "Chargement")

        process.start()
        dialog.exec()
        QGuiApplication.restoreOverrideCursor()

        # Combine tasks results
        self.sensors_blocks = {}
        for task in tasks:
                for result in task.results:
                    if result['sensor_id'] not in self.sensors_blocks:
                        self.sensors_blocks[result['sensor_id']] = []
                    start_time = result['start_time']
                    end_time = result['end_time']
                    data = {"start_time": start_time, "end_time": end_time}
                    self.sensors_blocks[result['sensor_id']].append(data)

    def create_sensors_rects(self):
        rects = []
        pos = 20
        for location in self.sensors_location:
            # Must create a new location space for later
            pos += 15
            sensors = self.get_sensors_for_location(location)
            for sensor_id in sensors:
                # sensor = self.sensors[sensor_id]
                # for record in self.recordsets:
                #    datas = self.dbMan.get_all_sensor_data(sensor=sensor, recordset=record, channel=sensor.channels[0])
                #     for data in datas:
                #         start_pos = self.get_relative_timeview_pos(data.timestamps.start_timestamp)
                #         end_pos = self.get_relative_timeview_pos(data.timestamps.end_timestamp)
                #         span = max(end_pos - start_pos, 1)
                #         rects.append(QRectF(start_pos, pos + 3, span, 14))
                if sensor_id in self.sensors_blocks:
                    for block in self.sensors_blocks[sensor_id]:
                        start_pos = self.get_relative_timeview_pos(block['start_time'])
                        end_pos = self.get_relative_timeview_pos(block['end_time'])
                        span = max(end_pos - start_pos, 1)
                        rects.append(QRectF(start_pos, pos + 3, span, 14))
                pos += 20

        return rects

    def draw_timebar(self):
        line_pen = QPen(Qt.cyan)
        line_pen.setWidth(2)
        self.time_bar = self.timeScene.addLine(1, 21, 1, self.timeScene.height()-1, line_pen)
        # self.time_bar = self.timeScene.addLine(0, 1, 0, self.timeScene.height() - 1, line_pen)
        self.time_bar.setFlag(QGraphicsItem.ItemIgnoresTransformations, True)

    def get_sensors_for_location(self, location):
        sensors_id = []
        for sensor in self.sensors.values():
            if sensor.location == location:
                sensors_id.append(sensor.id_sensor)

        return sensors_id

    @staticmethod
    def get_sensor_graph_type(sensor):
        if sensor.id_sensor_type == SensorType.ACCELEROMETER \
                or sensor.id_sensor_type == SensorType.GYROMETER \
                or sensor.id_sensor_type == SensorType.BATTERY \
                or sensor.id_sensor_type == SensorType.LUX \
                or sensor.id_sensor_type == SensorType.CURRENT \
                or sensor.id_sensor_type == SensorType.BAROMETER \
                or sensor.id_sensor_type == SensorType.MAGNETOMETER \
                or sensor.id_sensor_type == SensorType.TEMPERATURE \
                or sensor.id_sensor_type == SensorType.HEARTRATE \
                or sensor.id_sensor_type == SensorType.ORIENTATION \
                or sensor.id_sensor_type == SensorType.FSR:
            return GraphType.LINECHART

        if sensor.id_sensor_type == SensorType.GPS:
            return GraphType.MAP

        return GraphType.UNKNOWN

    @pyqtSlot(Sensor, datetime, datetime)
    def query_sensor_data(self, sensor: Sensor, start_time: datetime, end_time: datetime):
        timeseries = self.get_sensor_data(sensor, start_time, end_time)[0]

        if self.sensors_graphs[sensor.id_sensor]:
            graph_type = self.get_sensor_graph_type(sensor)
            graph_window = self.sensors_graphs[sensor.id_sensor]
            if graph_type == GraphType.LINECHART:
                series_id = 0
                for series in timeseries:
                    # Filter times that don't fit in the range
                    y_range = series['y']
                    x_range = series['x']
                    y_range = y_range[x_range >= start_time.timestamp()]
                    x_range = x_range[x_range >= start_time.timestamp()]
                    y_range = y_range[x_range <= end_time.timestamp()]
                    x_range = x_range[x_range <= end_time.timestamp()]
                    if len(x_range)>0 and len(y_range)>0:
                        graph_window.graph.update_data(x_range, y_range, series_id)
                    series_id += 1
        return

    @pyqtSlot(QAction)
    # @timing
    def sensor_graph_selected(self, sensor_item):
        sensor_id = sensor_item.property("sensor_id")
        sensor = self.sensors[sensor_id]
        sensor_label = sensor.name + " (" + sensor.location + ")"

        if sensor_item.isChecked():
            # Choose the correct display for each sensor
            graph_window = None
            timeseries, channel_data = self.get_sensor_data(sensor)  # Fetch all sensor data

            # Color map for curves
            colors = [Qt.blue, Qt.green, Qt.yellow, Qt.red]

            graph_type = self.get_sensor_graph_type(sensor)
            graph_window = GraphWindow(graph_type, sensor, self.UI.mdiArea)
            graph_window.setStyleSheet(self.styleSheet() + graph_window.styleSheet())

            if graph_type == GraphType.LINECHART:
                # Add series
                for series in timeseries:
                    graph_window.graph.add_data(series['x'], series['y'], color=colors.pop(),
                                                legend_text=series['label'])

                graph_window.graph.set_title(sensor_label)

            if graph_type == GraphType.MAP:
                for data in channel_data:
                    gps = GPSGeodetic()
                    gps.from_bytes(data.data)
                    if gps.latitude != 0 and gps.longitude != 0:
                        graph_window.graph.addPosition(data.timestamps.start_timestamp, gps.latitude / 1e7,
                                                       gps.longitude / 1e7)
                        graph_window.setCursorPositionFromTime(data.timestamps.start_timestamp)

            if graph_window is not None:
                self.UI.mdiArea.addSubWindow(graph_window).setWindowTitle(sensor_label)
                self.sensors_graphs[sensor.id_sensor] = graph_window
                # self.UI.displayContents.layout().insertWidget(0,graph)

                graph_window.show()
                QApplication.instance().processEvents()

                graph_window.aboutToClose.connect(self.graph_was_closed)
                graph_window.requestData.connect(self.query_sensor_data)
                graph_window.graph.cursorMoved.connect(self.graph_cursor_changed)
                graph_window.graph.selectedAreaChanged.connect(self.graph_selected_area_changed)
                graph_window.graph.clearedSelectionArea.connect(self.on_timeview_clear_selection_requested)
                graph_window.zoomAreaRequested.connect(self.graph_zoom_area)
                graph_window.zoomResetRequested.connect(self.graph_zoom_reset)

                self.UI.mdiArea.tileSubWindows()
        else:
            # Remove from display
            try:
                if self.sensors_graphs[sensor.id_sensor] is not None:
                    self.UI.mdiArea.removeSubWindow(self.sensors_graphs[sensor.id_sensor].parent())
                    self.sensors_graphs[sensor.id_sensor].hide()
                    del self.sensors_graphs[sensor.id_sensor]
                    self.UI.mdiArea.tileSubWindows()
            except KeyError:
                pass
        self.update_tile_buttons_state()

    # @timing
    def get_sensor_data(self, sensor, start_time=None, end_time=None):
        QGuiApplication.setOverrideCursor(Qt.BusyCursor)
        task = DBSensorAllDataTask("Chargement des données...", self.dbMan, sensor, start_time, end_time,
                                   self.recordsets)

        process = BackgroundProcess([task])
        dialog = ProgressDialog(process, "Traitement")

        process.start()
        dialog.exec()
        QGuiApplication.restoreOverrideCursor()

        return task.results['timeseries'], task.results['channel_data']

    def update_tile_buttons_state(self):
        if self.sensors_graphs.keys().__len__() > 1:
            self.UI.btnTileAuto.setEnabled(True)
            self.UI.btnTileHorizontal.setEnabled(True)
            self.UI.btnTileVertical.setEnabled(True)
        else:
            self.UI.btnTileAuto.setEnabled(False)
            self.UI.btnTileHorizontal.setEnabled(False)
            self.UI.btnTileVertical.setEnabled(False)

    @pyqtSlot(QObject)
    def graph_was_closed(self, graph):
        for sensor_id, sensor_graph in self.sensors_graphs.items():
            if sensor_graph == graph:
                # self.sensors_graphs[sensor_id] = None
                del self.sensors_graphs[sensor_id]
                self.sensors_items[sensor_id].setChecked(False)
                break

        self.UI.mdiArea.tileSubWindows()
        self.update_tile_buttons_state()

    @pyqtSlot(float)
    def graph_cursor_changed(self, timestamp):
        current_time = timestamp / 1000
        for graph in self.sensors_graphs.values():
            if graph is not None:
                graph.setCursorPositionFromTime(current_time, False)

        pos = self.get_relative_timeview_pos(current_time)
        self.time_bar.setPos(pos,0)

        # Ensure time bar is visible if scrollable
        self.ensure_time_bar_visible(pos)

        self.UI.lblCursorTime.setText(datetime.fromtimestamp(current_time).strftime('%d-%m-%Y %H:%M:%S'))

    def ensure_time_bar_visible(self, pos):
        if self.UI.scrollTimeline.isVisible():
            max_visible_x = self.UI.graphTimeline.mapToScene(self.UI.graphTimeline.rect()).boundingRect().x() \
                            + self.UI.graphTimeline.mapToScene(self.UI.graphTimeline.rect()).boundingRect().width()
            min_visible_x = self.UI.graphTimeline.mapToScene(self.UI.graphTimeline.rect()).boundingRect().x()
            if pos < min_visible_x or pos > max_visible_x:
                self.UI.scrollTimeline.setValue(pos)

    @pyqtSlot(datetime, datetime)
    def graph_zoom_area(self, start_time, end_time):
        for graph in self.sensors_graphs.values():
            if graph is not None:
                graph.zoomAreaRequestTime(start_time, end_time)

    @pyqtSlot()
    def graph_zoom_reset(self):
        for graph in self.sensors_graphs.values():
            if graph is not None:
                graph.zoomResetRequest(False)

    @pyqtSlot(float, float)
    def graph_selected_area_changed(self, start_timestamp, end_timestamp):
        # Update timeview selection area
        start_pos = self.get_relative_timeview_pos(start_timestamp / 1000)
        end_pos = self.get_relative_timeview_pos(end_timestamp / 1000)
        self.timeview_selected(start_pos, end_pos)

        # Ensure time bar is visible if scrollable
        self.ensure_time_bar_visible(start_pos)

        # Update selection for each graph
        for graph in self.sensors_graphs.values():
            if graph is not None:
                graph.setSelectionAreaFromTime(start_timestamp, end_timestamp)

    @pyqtSlot(int)
    def timeview_scroll(self, pos):
        self.UI.graphTimeline.centerOn(pos / self.zoom_level, 0)

    @pyqtSlot(float)
    def timeview_clicked(self, x):
        self.time_bar.setPos(x, 0)
        if len(self.recordsets)==0:
            return

        # Find time corresponding to that position
        timestamp = self.get_time_from_timeview_pos(x)
        self.UI.lblCursorTime.setText(timestamp.strftime('%d-%m-%Y %H:%M:%S'))

        for graph in self.sensors_graphs.values():
            if graph is not None:
                # try:
                graph.setCursorPositionFromTime(timestamp, False)
            # except AttributeError:
            #    continue

    @pyqtSlot(float, float)
    def timeview_selected(self, start_x, end_x):
        selection_brush = QBrush(QColor(153, 204, 255, 128))
        selection_pen = QPen(Qt.transparent)
        self.timeScene.removeItem(self.selection_rec)
        self.selection_rec = self.timeScene.addRect(start_x, 20, end_x-start_x, self.timeScene.height()-20,
                                                    selection_pen, selection_brush)

        self.UI.btnClearSelection.setEnabled(True)
        self.UI.btnTimeZoomSelection.setEnabled(True)

        # Update selection for each graph
        for graph in self.sensors_graphs.values():
            if graph is not None:
                graph.setSelectionAreaFromTime(self.get_time_from_timeview_pos(start_x),
                                               self.get_time_from_timeview_pos(end_x))

    @pyqtSlot()
    def on_timeview_clear_selection_requested(self):
        self.timeScene.removeItem(self.selection_rec)
        self.UI.btnClearSelection.setEnabled(False)
        self.UI.btnTimeZoomSelection.setEnabled(False)

        # Clear all selected areas in graphs
        for graph in self.sensors_graphs.values():
            graph.clearSelectionArea()

    @pyqtSlot()
    def on_timeview_zoom_selection_requested(self):
        self.UI.graphTimeline.scale(1 / self.zoom_level, 1)
        # zoom_value = (self.timeScene.width() / (self.selection_rec.rect().width()))
        zoom_value = (self.UI.graphTimeline.width() / (self.selection_rec.rect().width()))
        self.zoom_level = zoom_value
        self.UI.graphTimeline.scale(zoom_value, 1)
        self.UI.btnZoomReset.setEnabled(True)
        self.adjust_timeview_size()
        # self.UI.graphTimeline.ensureVisible(self.selection_rec.rect(), 0, 0)
        # self.UI.graphTimeline.centerOn(self.selection_rec.rect().x() + self.selection_rec.rect().width() / 2, 0)
        self.UI.scrollTimeline.setValue((self.selection_rec.rect().x() + self.selection_rec.rect().width() / 2)
                                        * self.zoom_level)
        self.on_timeview_clear_selection_requested()

    @pyqtSlot()
    def on_timeview_zoom_reset_requested(self):
        self.UI.graphTimeline.scale(1 / self.zoom_level, 1)
        self.zoom_level = 1
        self.UI.btnZoomReset.setEnabled(False)
        self.adjust_timeview_size()
        self.UI.scrollTimeline.setValue(0)

    @pyqtSlot()
    def on_timeview_show_hide_requested(self):
        visible = not self.UI.frameTimeline.isVisible()
        self.UI.frameTimeline.setVisible(visible)
        self.UI.frameTimelineControls.setVisible(visible)
        # self.UI.lblCursorTime.setVisible(visible)

    @pyqtSlot()
    def on_process_recordset(self):
        # Display Process Window
        window = ProcessSelectWindow(self.dbMan, self.recordsets)
        window.setStyleSheet(self.styleSheet())

        if window.exec() == QDialog.Accepted:
            self.dataUpdateRequest.emit("result", window.processed_data)
            self.dataDisplayRequest.emit("result", window.processed_data.id_processed_data)

    @pyqtSlot()
    def tile_graphs_horizontally(self):
        self.tile_graphs(True)

    @pyqtSlot()
    def tile_graphs_vertically(self):
        self.tile_graphs(False)

    def tile_graphs(self, horizontal: bool):
        if self.UI.mdiArea.subWindowList() is None:
            return

        position = QPoint(0, 0)

        for window in self.UI.mdiArea.subWindowList():
            if horizontal:
                rect = QRect(0, 0, self.UI.mdiArea.width() / len(self.UI.mdiArea.subWindowList()),
                             self.UI.mdiArea.height())
            else:
                rect = QRect(0, 0, self.UI.mdiArea.width(),
                             self.UI.mdiArea.height() / len(self.UI.mdiArea.subWindowList()))
            window.setGeometry(rect)
            window.move(position)
            if horizontal:
                position.setX(position.x() + window.width())
            else:
                position.setY(position.y() + window.height())

    @pyqtSlot()
    def tile_graphs_auto(self):
        self.UI.mdiArea.tileSubWindows()
class SelectionScene(QGraphicsScene):

    selection_drawing_finished = pyqtSignal(QRectF)

    def __init__(self, scene_rect: QRectF, parent: QObject = None):
        super(SelectionScene, self).__init__(scene_rect, parent)
        rectangle_color = QColor(Qt.red)
        self.default_border_pen = QPen(Qt.red, 3, Qt.SolidLine, Qt.SquareCap,
                                       Qt.MiterJoin)
        self.default_fill_brush = QBrush(rectangle_color, Qt.BDiagPattern)
        self.new_selection_view: QGraphicsRectItem = None
        self.new_selection_origin: QPointF = None
        self.mode = EditorMode.DRAW_MODE

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
        if self.mode == EditorMode.DRAW_MODE:
            self._handle_mouse_press_draw_mode(event)
        elif self.mode == EditorMode.MOVE_MODE:
            raise NotImplementedError("Move mode is not implemented!")

    def _handle_mouse_press_draw_mode(self, event: QGraphicsSceneMouseEvent):
        if event.button() == Qt.LeftButton and self.new_selection_view is None:
            self.new_selection_origin = event.scenePos()
            self._restrict_to_scene_space(self.new_selection_origin)
            scene_logger.info(
                f"Beginning to draw a new selection: "
                f"X={self.new_selection_origin.x()}, Y={self.new_selection_origin.y()}"
            )
            self.new_selection_view = QGraphicsRectItem(
                self.new_selection_origin.x(), self.new_selection_origin.y(),
                0, 0)
            self.new_selection_view.setPen(self.default_border_pen)
            self.new_selection_view.setBrush(self.default_fill_brush)
            self.addItem(self.new_selection_view)
            event.accept()

    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
        if self.mode == EditorMode.DRAW_MODE:
            self._handle_mouse_move_draw_mode(event)
        elif self.mode == EditorMode.MOVE_MODE:
            raise NotImplementedError("Move mode is not implemented!")

    def _handle_mouse_move_draw_mode(self, event: QGraphicsSceneMouseEvent):
        point2 = event.scenePos()
        self._restrict_to_scene_space(point2)
        if self.new_selection_origin is None:
            scene_logger.warning("Move event: Selection origin is None!")
            event.accept()
            return

        rectangle = QRectF(
            QPointF(min(self.new_selection_origin.x(), point2.x()),
                    min(self.new_selection_origin.y(), point2.y())),
            QPointF(max(self.new_selection_origin.x(), point2.x()),
                    max(self.new_selection_origin.y(), point2.y())))
        self.new_selection_view.setRect(rectangle)
        event.accept()

    def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent):
        if self.mode == EditorMode.DRAW_MODE:
            self._handle_mouse_release_draw_mode(event)
        elif self.mode == EditorMode.MOVE_MODE:
            raise NotImplementedError("Move mode is not implemented!")

    def _handle_mouse_release_draw_mode(self, event: QGraphicsSceneMouseEvent):
        self.new_selection_view: QGraphicsRectItem
        if event.button(
        ) == Qt.LeftButton and self.new_selection_view is not None:
            absolute_rectangle = self.new_selection_view.mapRectFromScene(
                self.new_selection_view.rect())
            if self.is_rectangle_valid_selection(absolute_rectangle):
                self.selection_drawing_finished.emit(absolute_rectangle)
            else:
                scene_logger.info(
                    f"Discarding invalid selection: "
                    f"x={absolute_rectangle.x()}, y={absolute_rectangle.y()}, "
                    f"width={absolute_rectangle.width()}, height={absolute_rectangle.height()}"
                )
                self.removeItem(self.new_selection_view)
            self.new_selection_origin = None
            self.new_selection_view = None
            event.accept()

    def load_selections(self, current: QModelIndex):
        selection_count: int = current.model().rowCount(
            current)  # The number of child nodes, which are selections
        current_first_column = current.sibling(
            current.row(), 0)  # Selections are below the first column
        selections: typing.List[Selection] = [
            current_first_column.child(index, 0).data(Qt.UserRole)
            for index in range(selection_count)
        ]
        editor_logger.debug(f"Loading selection list: {selections}")
        for selection in selections:
            self._draw_rectangle(current, selection)

    def _restrict_to_scene_space(self, point: QPointF):
        """Restrict rectangle drawing to the screen space. This prevents drawing out of the source image bounds."""
        point.setX(min(max(point.x(), 0), self.sceneRect().width()))
        point.setY(min(max(point.y(), 0), self.sceneRect().height()))

    def _draw_rectangle(self, current: QModelIndex, rectangle: Selection):
        self.addRect(self._to_local_coordinates(current, rectangle),
                     self.default_border_pen, self.default_fill_brush)

    def _to_local_coordinates(self, current: QModelIndex,
                              rectangle: Selection) -> QRectF:
        """
        Scales a model Selection to local coordinates. Large images are scaled down, so the rectangles need to be
        scaled, too. This function performs the scaling and conversion to floating point based rectangles, as expected
        by QGraphicsView.
        """
        scaling_factor: float = self.width() / current.sibling(
            current.row(), 0).data(Qt.UserRole).width

        if scaling_factor >= 1:
            result = rectangle.as_qrectf
        else:
            result = QRectF(rectangle.top_left.x * scaling_factor,
                            rectangle.top_left.y * scaling_factor,
                            rectangle.width * scaling_factor,
                            rectangle.height * scaling_factor)
            scene_logger.debug(
                f"Scaled {rectangle} to {result.topLeft(), result.bottomRight()}"
            )
        return result

    def is_rectangle_valid_selection(self, selection: QRectF) -> bool:
        """
        Returns True, if the given rectangle is a valid selection.
        A selection is determined to be valid if its width and height is at least 0.5% of the source image or 20 pixels
        large, whichever comes first
        """
        return (selection.width() > self.sceneRect().width() * 0.01 or selection.width() > 20) \
            and (selection.height() > self.sceneRect().height() * 0.01 or selection.height() > 20)
Example #23
0
class IDView(QGraphicsView):
    """Simple extension of QGraphicsView
    - containing an image and click-to-zoom/unzoom
    """
    def __init__(self, parent, fnames):
        QGraphicsView.__init__(self)
        self.parent = parent
        self.initUI(fnames)

    def initUI(self, fnames):
        # Make QGraphicsScene
        self.scene = QGraphicsScene()
        # TODO = handle different image sizes.
        self.images = {}
        self.imageGItem = QGraphicsItemGroup()
        self.scene.addItem(self.imageGItem)
        self.updateImage(fnames)
        self.setBackgroundBrush(QBrush(Qt.darkCyan))
        self.parent.tool = "zoom"

        self.boxFlag = False
        self.originPos = QPointF(0, 0)
        self.currentPos = self.originPos
        self.boxItem = QGraphicsRectItem()
        self.boxItem.setPen(QPen(Qt.darkCyan, 1))
        self.boxItem.setBrush(QBrush(QColor(0, 255, 0, 64)))

    def updateImage(self, fnames):
        """Update the image with that from filename"""
        for n in self.images:
            self.imageGItem.removeFromGroup(self.images[n])
            self.images[n].setVisible(False)
        if fnames is not None:
            x = 0
            n = 0
            for fn in fnames:
                self.images[n] = QGraphicsPixmapItem(QPixmap(fn))
                self.images[n].setTransformationMode(Qt.SmoothTransformation)
                self.images[n].setPos(x, 0)
                self.images[n].setVisible(True)
                self.scene.addItem(self.images[n])
                x += self.images[n].boundingRect().width() + 10
                self.imageGItem.addToGroup(self.images[n])
                n += 1

        # Set sensible sizes and put into the view, and fit view to the image.
        br = self.imageGItem.boundingRect()
        self.scene.setSceneRect(
            0,
            0,
            max(1000, br.width()),
            max(1000, br.height()),
        )
        self.setScene(self.scene)
        self.fitInView(self.imageGItem, Qt.KeepAspectRatio)

    def deleteRect(self):
        if self.boxItem.scene() is None:
            return
        self.scene.removeItem(self.boxItem)
        self.parent.rectangle = None

    def mousePressEvent(self, event):
        if self.parent.tool == "rect":
            self.originPos = self.mapToScene(event.pos())
            self.currentPos = self.originPos
            self.boxItem.setRect(QRectF(self.originPos, self.currentPos))
            if self.boxItem.scene() is None:
                self.scene.addItem(self.boxItem)
            self.boxFlag = True
        else:
            super(IDView, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if self.parent.tool == "rect" and self.boxFlag:
            self.currentPos = self.mapToScene(event.pos())
            if self.boxItem is None:
                return
            else:
                self.boxItem.setRect(QRectF(self.originPos, self.currentPos))
        else:
            super(IDView, self).mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        if self.boxFlag:
            self.boxFlag = False
            self.parent.rectangle = self.boxItem.rect()
            self.parent.whichFile = self.parent.vTW.currentIndex()
            return
        """Left/right click to zoom in and out"""
        if (event.button() == Qt.RightButton) or (
                QGuiApplication.queryKeyboardModifiers() == Qt.ShiftModifier):
            self.scale(0.8, 0.8)
        else:
            self.scale(1.25, 1.25)
        self.centerOn(event.pos())

    def resetView(self):
        """Reset the view to its reasonable initial state."""
        self.fitInView(self.imageGItem, Qt.KeepAspectRatio)
Example #24
0
class GameArea(QWidget):
    #constants of widget
    RATIO_WITH_SHIPLIST = 11 / 14
    RATIO_WITHOUT_SHIPLIST = 1
    EPS = 0.3

    #signals
    shipPlaced = pyqtSignal(Ship)

    def __init__(self, parent=None):
        super(GameArea, self).__init__(parent)
        self.__ui = Ui_GameArea()
        self.__ui.setupUi(self)

        self.__ratio = self.RATIO_WITH_SHIPLIST
        self.__scaleFactor = 1

        self.controller = Controller()
        self.controller._accept = self.__accept
        self.controller._decline = self.__decline
        self.__gameModel = None

        self.__shipList = {
            "boat": ShipListItem(length=1, name="boat", count=4),
            "destroyer": ShipListItem(length=2, name="destroyer", count=3),
            "cruiser": ShipListItem(length=3, name="cruiser", count=2),
            "battleship": ShipListItem(length=4, name="battleship", count=1),
        }

        # resources
        self.__cellImages = {"intact": None, "hit": None, "miss": None}

        self.__sprites = {"explosion": None, "splash": None}

        self.__originalTileSize = 0
        self.tileSize = 0

        self.__shipListImage = QImage()
        self.__counterImage = QImage()

        # drawing items
        self.__placedShips = []
        self.__field = [QGraphicsPixmapItem() for _ in range(100)]
        self.__letters = [QGraphicsTextItem() for _ in range(10)]
        self.__numbers = [QGraphicsTextItem() for _ in range(10)]
        for i in range(10):
            self.__letters[i].setDefaultTextColor(QColor.fromRgb(0, 0, 0))
            self.__numbers[i].setDefaultTextColor(QColor.fromRgb(0, 0, 0))

        self.__spriteAnimations = []

        self.__shipListItem = QGraphicsPixmapItem()
        self.__ghostShip = QGraphicsPixmapItem(
        )  # data: 0 - rotation; 1 - ShipListItem
        self.__placer = QGraphicsRectItem()
        self.__dragShip = False

        self.__targetPixmap = None
        self.__targets = []
        self.__scanEffect = None

        # prepare Qt objects
        self.__scene = QGraphicsScene()
        self.__loadResources()
        self.__initGraphicsView()
        self.__adjustedToSize = 0

    def serviceModel(self, game_model):
        self.removeModel()
        self.__gameModel = game_model
        self.__gameModel.shipKilled.connect(self.__shootAround)

    def removeModel(self):
        if self.__gameModel:
            self.shipKilled.disconnect()
            self.__gameModel = None

    def __loadResources(self):
        if DEBUG_RESOURCE:
            resourcesPath = os.path.join(os.path.dirname(__file__),
                                         DEBUG_RESOURCE)
        else:
            resourcesPath = Environment.Resources.path()

        first = True
        for imageName in self.__cellImages:
            image = QImage(
                os.path.join(resourcesPath, "img", "cells",
                             f"{imageName}.png"))
            if first:
                first = False
                self.__originalTileSize = min(image.width(), image.height())
            else:
                self.__originalTileSize = min(self.__originalTileSize,
                                              image.width(), image.height())
            self.__cellImages[imageName] = image

        for spriteName in self.__sprites:
            image = QImage(
                os.path.join(resourcesPath, "img", "cells",
                             f"{spriteName}.png"))
            self.__sprites[spriteName] = image

        for shipName, ship in self.__shipList.items():
            ship.image = QImage(
                os.path.join(resourcesPath, "img", "ships", f"{shipName}.png"))

        self.__shipListImage = QImage(
            os.path.join(resourcesPath, "img", "backgrounds", "shiplist.png"))
        self.__counterImage = QImage(
            os.path.join(resourcesPath, "img", "miscellaneous",
                         "shipcounter.png"))

        self.__targetPixmap = QPixmap(
            os.path.join(resourcesPath, "img", "cells", "target.png"))

    def __initGraphicsView(self):
        self.__ui.graphicsView.setScene(self.__scene)
        self.__ui.graphicsView.viewport().installEventFilter(self)
        self.__ui.graphicsView.setRenderHints(
            QPainter.RenderHint.HighQualityAntialiasing
            | QPainter.RenderHint.TextAntialiasing
            | QPainter.RenderHint.SmoothPixmapTransform)
        self.__ui.graphicsView.horizontalScrollBar().blockSignals(True)
        self.__ui.graphicsView.verticalScrollBar().blockSignals(True)

        for cell in self.__field:
            cell.setData(0, "intact")
            pixmap = QPixmap.fromImage(self.__cellImages["intact"])
            cell.setPixmap(pixmap)
            cell.setTransformationMode(
                Qt.TransformationMode.SmoothTransformation)
            self.__scene.addItem(cell)

        ca_letters = ["А", "Б", "В", "Г", "Д", "Е", "Ж", "З", "И", "К"]
        # ca_letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]
        ca_numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]

        font = QFont("Roboto")
        font.setPixelSize(int(self.__originalTileSize * 0.8))
        for i in range(10):
            self.__letters[i].setPlainText(ca_letters[i])
            self.__letters[i].setFont(font)
            self.__numbers[i].setPlainText(ca_numbers[i])
            self.__numbers[i].setFont(font)
            self.__scene.addItem(self.__letters[i])
            self.__scene.addItem(self.__numbers[i])

        self.__shipListItem.setPixmap(QPixmap.fromImage(self.__shipListImage))
        self.__shipListItem.setTransformationMode(
            Qt.TransformationMode.SmoothTransformation)
        self.__scene.addItem(self.__shipListItem)

        font.setPixelSize(int(self.__originalTileSize * 0.3))
        for _, ship in self.__shipList.items():
            t = QTransform().rotate(90)
            ship.shipItem.setPixmap(
                QPixmap.fromImage(ship.image).transformed(t))
            ship.shipItem.setTransformationMode(
                Qt.TransformationMode.SmoothTransformation)
            ship.counterItem.setPixmap(QPixmap.fromImage(self.__counterImage))
            ship.counterItem.setTransformationMode(
                Qt.TransformationMode.SmoothTransformation)
            ship.counterText.setPlainText(str(ship.count))
            ship.counterText.setFont(font)
            self.__scene.addItem(ship.shipItem)
            self.__scene.addItem(ship.counterItem)
            self.__scene.addItem(ship.counterText)

        self.__ghostShip.setTransformationMode(
            Qt.TransformationMode.SmoothTransformation)
        self.__ghostShip.setOpacity(0.7)

        pen = QPen()
        pen.setWidth(2)
        pen.setStyle(Qt.PenStyle.DashLine)
        pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
        self.__placer.setPen(pen)

    def __setCell(self, x, y, cell_type):
        if cell_type not in self.__cellImages.keys():
            raise ValueError(
                f"Type is {cell_type}. Allowed \"intact\", \"miss\", \"hit\"")

        cellItem = self.__field[y * 10 + x]
        cellItem.setData(0, cell_type)
        pixmap = QPixmap.fromImage(self.__cellImages[cell_type])
        cellItem.setPixmap(pixmap)

    def __runAnimation(self, x, y, animation, looped=False):
        sprite = SpriteItem()
        sprite.setData(0, QPoint(x, y))
        spritePixmap = QPixmap.fromImage(self.__sprites[animation])

        sprite.setSpriteMap(spritePixmap, 60, 60, 5)
        sprite.setScale(self.__scaleFactor)
        sprite.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize)
        sprite.setZValue(10)

        self.__spriteAnimations.append(sprite)
        self.__scene.addItem(sprite)

        def removeAnimation():
            self.__scene.removeItem(sprite)
            self.__spriteAnimations.remove(sprite)
            sprite.stopAnimation()

        sprite.startAnimation(100, looped, removeAnimation)

    def __shootAround(self, ship: Ship):
        shipCells = []
        for i in range(ship.length):
            shipCells.append(ship.pos +
                             (QPoint(0, i) if ship.vertical else QPoint(i, 0)))
        cells = []

        for cell in shipCells:
            arounds = [
                cell + QPoint(i, j) for i in range(-1, 2)
                for j in range(-1, 2)
            ]
            for candidate in arounds:
                if 0 <= candidate.x() < 10 and 0 <= candidate.y() < 10:
                    if candidate not in cells:
                        cells.append(candidate)

        for cell in cells:
            if cell not in shipCells:
                self.__setCell(cell.x(), cell.y(), "miss")
                self.__runAnimation(cell.x(), cell.y(), "splash")
        log.debug(
            f"name: {ship.name} | length: {ship.length} | pos: {ship.pos} | vertical: {ship.vertical}"
        )

    def hasHeightForWidth(self):
        return True

    def heightForWidth(self, width):
        return width / self.__ratio

    def hideShipList(self):
        self.__ratio = self.RATIO_WITHOUT_SHIPLIST
        self.__scene.removeItem(self.__shipListItem)
        for _, ship in self.__shipList.items():
            self.__scene.removeItem(ship.shipItem)

        self.__adjustedToSize = None
        resize = QResizeEvent(self.size(), self.size())
        QApplication.postEvent(self, resize)

    def getPlacedShips(self):
        return self.__placedShips

    def placedShipsCount(self):
        return len(self.__placedShips)

    def hideShips(self):
        for ship in self.__placedShips:
            self.__scene.removeItem(ship)

    def removePlacedShips(self):
        if self.__shipListItem.scene() is None:
            return

        for ship in self.__placedShips:
            shipListItem = ship.data(1)
            shipListItem.count += 1
            shipListItem.counterText.setPlainText(str(shipListItem.count))
            self.__scene.removeItem(ship)

        self.__placedShips.clear()

    def shuffleShips(self):
        if self.__shipListItem.scene() is None:
            return

        self.removePlacedShips()

        def findPossibleCells(length, rotation):
            vertical = rotation.isVertical()
            if vertical == rotation.isHorizontal():
                raise Exception(
                    "Unknown state! Rotation is not horizontal and not vertical."
                )  # wtf

            width = 1 if vertical else length
            height = length if vertical else 1

            cells = []

            for x in range(10):
                for y in range(10):
                    if QRect(0, 0, 10, 10) != QRect(0, 0, 10, 10).united(
                            QRect(x, y, width, height)):
                        break
                    if self.__validatePosition(x, y, width, height):
                        cells.append(QPoint(x, y))

            return cells

        shipList = list(self.__shipList.values())
        shipList.sort(key=lambda ship: ship.length, reverse=True)
        for shipItem in shipList:
            for i in range(shipItem.count):
                rot = random.choice(list(Rotation))
                cells = findPossibleCells(shipItem.length, rot)

                if not cells:
                    rot = rot.next()
                    cells = findPossibleCells(shipItem.length, rot)

                if not cells:
                    return

                cell = random.choice(cells)
                self.__placeShip(shipItem, cell.x(), cell.y(), rot)

    def resizeEvent(self, event):
        size = event.size()
        if size == self.__adjustedToSize:
            return

        self.__adjustedToSize = size

        nowWidth = size.width()
        nowHeight = size.height()

        width = min(nowWidth, nowHeight * self.__ratio)
        height = min(nowHeight, nowWidth / self.__ratio)

        h_margin = round((nowWidth - width) / 2) - 2
        v_margin = round((nowHeight - height) / 2) - 2

        self.setContentsMargins(
            QMargins(h_margin, v_margin, h_margin, v_margin))
        self.__resizeScene()

    def scanEffect(self, x, y):
        """
        :return: True on success, False otherwise
        """
        if self.__scanEffect:
            return False

        def scanFinisedCallback():
            self.__scene.removeItem(self.__scanEffect)
            del self.__scanEffect
            self.__scanEffect = None

            target = FadingPixmapItem(self.__targetPixmap)
            target.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize)
            target.setScale(self.__scaleFactor)
            target.setData(0, (x, y))
            self.__targets.append(target)
            self.__scene.addItem(target)

        self.__scanEffect = FloatingGradientItem(
            QRectF(self.tileSize, self.tileSize, self.__originalTileSize * 10,
                   self.__originalTileSize * 10), scanFinisedCallback)

        self.__scanEffect.setScale(self.__scaleFactor)

        self.__scanEffect.setBackwards(True)
        self.__scene.addItem(self.__scanEffect)
        return True

    def __resizeScene(self):
        width = self.__ui.graphicsView.width()
        height = self.__ui.graphicsView.height()
        self.__scene.setSceneRect(0, 0, width, height)

        self.tileSize = min(width / 11, height / (11 / self.__ratio))
        self.__scaleFactor = self.tileSize / self.__originalTileSize

        for i, cell in enumerate(self.__field):
            x = i % 10
            y = i // 10
            cell.setScale(self.__scaleFactor)
            cell.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize)

        for sprite in self.__spriteAnimations:
            pos = sprite.data(0)
            sprite.setPos((pos.x() + 1) * self.tileSize,
                          (pos.y() + 1) * self.tileSize)
            sprite.setScale(self.__scaleFactor)

        for i in range(10):
            letter = self.__letters[i]
            letter.setScale(self.__scaleFactor)
            offsetX = (self.tileSize -
                       letter.boundingRect().width() * self.__scaleFactor) / 2
            offsetY = (self.tileSize -
                       letter.boundingRect().height() * self.__scaleFactor) / 2
            letter.setPos((i + 1) * self.tileSize + offsetX, offsetY)

            number = self.__numbers[i]
            number.setScale(self.__scaleFactor)
            offsetX = (self.tileSize -
                       number.boundingRect().width() * self.__scaleFactor) / 2
            offsetY = (self.tileSize -
                       number.boundingRect().height() * self.__scaleFactor) / 2
            number.setPos(offsetX, (i + 1) * self.tileSize + offsetY)

        xPos = 0
        for _, ship in self.__shipList.items():
            xPos += (ship.length - 1)
            xOffset = xPos * self.tileSize

            ship.shipItem.setScale(self.__scaleFactor)
            ship.shipItem.setPos(self.tileSize + xOffset,
                                 self.tileSize * (12 + self.EPS))

            ship.counterItem.setScale(self.__scaleFactor)
            ship.counterItem.setPos(self.tileSize + xOffset,
                                    self.tileSize * (12.65 + self.EPS))

            counterSize = ship.counterItem.boundingRect()
            textSize = ship.counterText.boundingRect()
            textXOffset = (counterSize.width() -
                           textSize.width()) * self.__scaleFactor / 2
            textYOffset = (counterSize.height() -
                           textSize.height()) * self.__scaleFactor / 2
            textX = ship.counterItem.pos().x() + textXOffset
            textY = ship.counterItem.pos().y() + textYOffset
            ship.counterText.setScale(self.__scaleFactor)
            ship.counterText.setPos(textX, textY)

        for ship in self.__placedShips:
            mapPos = ship.data(2)
            ship.setPos((mapPos.x() + 1) * self.tileSize,
                        (mapPos.y() + 1) * self.tileSize)
            ship.setScale(self.__scaleFactor)

        for target in self.__targets:
            x, y = target.data(0)
            target.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize)
            target.setScale(self.__scaleFactor)

        if self.__scanEffect:
            self.__scanEffect.setPos(self.tileSize, self.tileSize)
            self.__scanEffect.setScale(self.__scaleFactor)

        shipListX = self.tileSize
        shipListY = self.tileSize * (11 + self.EPS)
        self.__shipListItem.setScale(self.__scaleFactor)
        self.__shipListItem.setPos(shipListX, shipListY)
        self.__ghostShip.setScale(self.__scaleFactor)

    def eventFilter(self, obj, event):
        if obj is self.__ui.graphicsView.viewport():
            if event.type() == QEvent.MouseButtonPress:
                self.__viewportMousePressEvent(event)
            elif event.type() == QEvent.MouseButtonRelease:
                self.__viewportMouseReleaseEvent(event)
            elif event.type() == QEvent.MouseMove:
                self.__viewportMouseMoveEvent(event)

        return super().eventFilter(obj, event)

    def sceneToMap(self, x, y):
        x -= self.tileSize
        y -= self.tileSize
        x //= self.tileSize
        y //= self.tileSize
        return int(x), int(y)

    def __initGhostShip(self, ship, pos):
        self.__ghostShip.setPixmap(QPixmap.fromImage(ship.image))
        self.__ghostShip.setPos(pos)
        self.__ghostShip.setRotation(0)

        width = self.__ghostShip.boundingRect().width()
        height = self.__ghostShip.boundingRect().height()
        self.__ghostShip.setOffset(-width / 2, -height / 2)
        self.__ghostShip.setData(0, Rotation.UP)
        self.__ghostShip.setData(1, ship)

        self.__placer.setRect(0, 0, self.tileSize, self.tileSize * ship.length)
        self.__placer.setZValue(50)

        self.__scene.addItem(self.__ghostShip)
        self.__ghostShip.setZValue(100)

    def __rotateGhostShip(self, rotation=None):
        rotation = rotation if rotation else self.__ghostShip.data(0).next()
        self.__ghostShip.setData(0, rotation)
        self.__ghostShip.setRotation(rotation.value)

        placerRect = self.__placer.rect()
        maxSide = max(placerRect.width(), placerRect.height())
        minSide = min(placerRect.width(), placerRect.height())

        if rotation.isHorizontal():
            self.__placer.setRect(0, 0, maxSide, minSide)
        elif rotation.isVertical():
            self.__placer.setRect(0, 0, minSide, maxSide)
        else:
            raise Exception(
                "Unknown state! Rotation is not horizontal and not vertical."
            )  # wtf
        self.__validatePlacer()

    def __ghostShipLongSurface(self):
        pos = self.__ghostShip.pos()
        x = pos.x()
        y = pos.y()
        rot: Rotation = self.__ghostShip.data(0)
        length = self.__ghostShip.data(1).length
        if rot.isHorizontal():
            x -= self.tileSize * length / 2
            return x, y

        if rot.isVertical():
            y -= self.tileSize * length / 2
            return x, y

    def __validatePosition(self, x, y, width=1, height=1):
        positionRect = QRectF((x + 1) * self.tileSize, (y + 1) * self.tileSize,
                              self.tileSize * width, self.tileSize * height)

        isPlacerValid = True
        for ship in self.__placedShips:
            shipRect = ship.mapRectToScene(ship.boundingRect()).adjusted(
                -self.tileSize / 2, -self.tileSize / 2, self.tileSize / 2,
                self.tileSize / 2)
            isPlacerValid = not positionRect.intersects(shipRect)
            if not isPlacerValid:
                break
        return isPlacerValid

    def __validatePlacer(self):
        sceneX, sceneY = self.__ghostShipLongSurface()
        x, y = self.sceneToMap(sceneX, sceneY)
        self.__placer.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize)

        permittedArea = QRectF(self.__ui.graphicsView.viewport().geometry())
        permittedArea.setTopLeft(QPointF(self.tileSize, self.tileSize))
        permittedArea.setBottomRight(
            QPointF(self.tileSize * 12, self.tileSize * 12))
        placerSize = self.__placer.boundingRect()
        placerRect = QRectF(sceneX, sceneY, placerSize.width(),
                            placerSize.height())

        isPlacerValid = False
        # first validation - ship can be placed inside game field
        if permittedArea.contains(self.__ghostShip.pos(
        )) and permittedArea == permittedArea.united(placerRect):
            if self.__placer.scene() == None:
                self.__scene.addItem(self.__placer)
            x, y = self.sceneToMap(sceneX, sceneY)
            self.__placer.setPos((x + 1) * self.tileSize,
                                 (y + 1) * self.tileSize)
            isPlacerValid = True

        else:
            if self.__placer.scene() == self.__scene:
                self.__scene.removeItem(self.__placer)

        # second validation - placer does not intersect with other ships
        if isPlacerValid:
            isPlacerValid = self.__validatePosition(
                x, y,
                placerSize.width() / self.tileSize,
                placerSize.height() / self.tileSize)

        # set color of placer
        pen = self.__placer.pen()
        if isPlacerValid:
            pen.setColor(Qt.GlobalColor.darkGreen)
        else:
            pen.setColor(Qt.GlobalColor.red)
        self.__placer.setPen(pen)
        self.__placer.setData(0, isPlacerValid)

    def __placeShip(self, shipListItem, x, y, rotation):
        sceneX, sceneY = (x + 1) * self.tileSize, (y + 1) * self.tileSize

        shipListItem.count -= 1
        shipListItem.counterText.setPlainText(str(shipListItem.count))

        pixmap = QPixmap(shipListItem.image).transformed(QTransform().rotate(
            (rotation.value)))
        placedShip = QGraphicsPixmapItem(pixmap)
        placedShip.setData(0, rotation)
        placedShip.setData(1, shipListItem)
        placedShip.setData(2, QPoint(x, y))  # position in map coordinates

        placedShip.setPos(sceneX, sceneY)
        placedShip.setTransformationMode(
            Qt.TransformationMode.SmoothTransformation)
        placedShip.setScale(self.__scaleFactor)

        self.__placedShips.append(placedShip)
        self.__scene.addItem(placedShip)

    def __placeGhostShip(self):
        isPlacingPermitted = self.__placer.data(0)
        if isPlacingPermitted:
            sceneX = self.__placer.pos().x() + self.tileSize / 2
            sceneY = self.__placer.pos().y() + self.tileSize / 2
            mapX, mapY = self.sceneToMap(sceneX, sceneY)
            rotation = self.__ghostShip.data(0)
            if rotation.isVertical():
                vertical = True
            elif rotation.isHorizontal():
                vertical = False
            else:
                raise Exception(
                    "Unknown state! Rotation is not horizontal and not vertical."
                )  # wtf

            shipListItem = self.__ghostShip.data(1)
            self.__placeShip(shipListItem, mapX, mapY, rotation)

            log.debug(
                f"ship \"{shipListItem.name}\"; "
                f"position ({mapX}, {mapY}); "
                f"oriented {'vertically' if vertical else 'horizontally'}")

            self.shipPlaced.emit(
                Ship(name=shipListItem.name,
                     length=shipListItem.length,
                     pos=QPoint(mapX, mapY),
                     vertical=vertical))

    def __viewportMousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            if self.__shipListItem.scene():
                # check press on shiplist
                shipUnderMouse = None
                for _, ship in self.__shipList.items():
                    if ship.shipItem.isUnderMouse():
                        shipUnderMouse = ship
                        break

                # check press on field
                rotation = Rotation.RIGHT
                if shipUnderMouse == None:
                    for ship in self.__placedShips:
                        if ship.isUnderMouse():
                            rotation = ship.data(0)
                            shipListItem = ship.data(1)

                            if shipListItem.count == 0:
                                shipListItem.shipItem.setGraphicsEffect(None)

                            shipListItem.count += 1
                            shipListItem.counterText.setPlainText(
                                str(shipListItem.count))
                            shipUnderMouse = shipListItem
                            self.__placedShips.remove(ship)
                            self.__scene.removeItem(ship)
                            break

                # if ship grabbed
                if shipUnderMouse and shipUnderMouse.count > 0:
                    self.__initGhostShip(shipUnderMouse, event.pos())
                    self.__rotateGhostShip(rotation)
                    self.__dragShip = True

            x, y = self.sceneToMap(event.pos().x(), event.pos().y())
            if x >= 0 and x < 10 and y >= 0 and y < 10:
                if self.controller.isBot:
                    return
                self.controller.emitHit(x, y)

        if event.button() == Qt.MouseButton.RightButton:
            if self.__dragShip:
                self.__rotateGhostShip()

    def __viewportMouseReleaseEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            if self.__dragShip:
                self.__scene.removeItem(self.__ghostShip)
                self.__dragShip = False
            if self.__placer.scene() != None:
                self.__scene.removeItem(self.__placer)

            # self.__placeShip()
            self.__placeGhostShip()
            self.__placer.setData(0, False)

    def __viewportMouseMoveEvent(self, event):
        if self.__dragShip:
            self.__ghostShip.setPos(event.pos())

            permittedArea = QRectF(
                self.__ui.graphicsView.viewport().geometry())
            permittedArea.setTopLeft(QPointF(self.tileSize, self.tileSize))

            # stop dragging ship if mouse out of field
            # if not permittedArea.contains(event.pos()):
            #     self.__scene.removeItem(self.__ghostShip)
            #     self.__dragShip = False

            self.__validatePlacer()

    def __accept(self, x, y, hit_type: CellState):
        log.debug(
            f" -- ACCEPTED -- hit on point ({x}, {y}) hit type: {hit_type}")
        cell = self.__field[y * 10 + x]

        if cell.data(0) != "intact":
            return
        if hit_type in [CellState.HIT, CellState.KILLED]:
            self.__setCell(x, y, 'hit')
            self.__runAnimation(x, y, "explosion", looped=True)
            for target in self.__targets:
                if (x, y) == target.data(0):
                    self.__scene.removeItem(target)
                    self.__targets.remove(target)
                    break
        elif hit_type in [CellState.MISS]:
            self.__setCell(x, y, 'miss')
            self.__runAnimation(x, y, "splash", looped=False)

        if hit_type == CellState.KILLED:
            for ship in self.__placedShips:
                rotation = ship.data(0)
                length = ship.data(1).length
                position = ship.data(2)
                width = 1 if rotation.isVertical() else length
                height = length if rotation.isVertical() else 1
                rect = QRect(position.x(), position.y(), width, height)
                if rect.contains(x, y):
                    self.__scene.addItem(ship)

    def __decline(self, x, y):
        log.debug(f"declined hit on point ({x}, {y})")