示例#1
0
    def drawAction(self, painter, actionName, pixmap, left, top,
                   highlightedColor, drawDisclosure, borderColor):
        if (pixmap != None):
            iconRect = QRect(left, top, self.ICON_WIDTH, self.ICON_WIDTH)
            if highlightedColor is not None:
                painter.fillRect(iconRect, highlightedColor)

            # draw the icon.  Its opacity depends on mouse over.
            p = self.treeView().mapFromGlobal(QCursor.pos())
            if not iconRect.contains(p):
                painter.setOpacity(0.7)
            else:
                painter.setOpacity(1.0)
                self.lastHitAction = actionName
            self.drawPixmap(painter, pixmap, left, top)
            painter.setOpacity(1.0)

            if drawDisclosure:
                painter.drawPixmap(iconRect, self.DISCLOSURE_IMAGE)

            if borderColor:
                oldPen = painter.pen()
                painter.setPen(QPen(borderColor, 1))
                painter.drawRect(iconRect)
                painter.setPen(oldPen)
示例#2
0
 def resize_visible_rows(self):
     viewport_rect = QRect(QPoint(0, 0), self.viewport().size())
     for row in range(self.model().rowCount()):
         rect = self.visualRect(self.model().index(row, 0))
         is_visible = viewport_rect.contains(rect)
         if is_visible:
             self.resizeRowToContents(row)
示例#3
0
    def drawIcon(self, painter: QPainter, rect: QRect, icon: int) -> None:
        """  """
        if icon != 0:
            if rect.contains(self.__lastPoint):
                painter.setPen(self.__iconPressColor if self.__pressed else self.__iconHoverColor)
            else:
                painter.setPen(self.__iconNormalColor)

            painter.drawText(rect, Qt.AlignHCenter | Qt.AlignVCenter, chr(int(icon)))
示例#4
0
文件: codeeditor.py 项目: sb362/ide
	def updateLineNumberArea(self, rect: QRect, dy: int = 0):
		# todo: move this into LineNumberArea
		if dy:
			self.lineNumberArea.scroll(0, dy)
		else:
			self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height())

		if rect.contains(self.viewport().rect()):
			self.setViewportMargins(self.lineNumberArea.numberWidth(), 0, 0, 0)
示例#5
0
    def _object_in_jump_area(level: Level, level_object: LevelObject):
        for jump in level.jumps:
            screen = jump.screen_index

            if level.is_vertical:
                rect = QRect(0, SCREEN_WIDTH * screen, SCREEN_WIDTH, SCREEN_HEIGHT,)
            else:
                rect = QRect(SCREEN_WIDTH * screen, 0, SCREEN_WIDTH, GROUND,)

            if rect.contains(QPoint(*level_object.get_position())):
                return True
        else:
            return False
示例#6
0
文件: game_snap.py 项目: whs/runekit
    def _update_game_snap(self, game_pos: QRect):
        # If the top left spot of us fall into game window, then we move with the game
        tl = self.geometry().topLeft()
        if not game_pos.contains(tl):
            self.__last_game_pos = game_pos
            return

        dx = game_pos.x() - self.__last_game_pos.x()
        dy = game_pos.y() - self.__last_game_pos.y()

        pos = self.pos()
        pos.setX(pos.x() + dx)
        pos.setY(pos.y() + dy)
        self.move(pos)

        self.__last_game_pos = game_pos
示例#7
0
 def mouseReleaseEvent(self, e):
     # rubberBand selection
     if e.button() == Qt.RightButton:
         return
     self.rubberBand.hide()
     grid = self.scene().grid
     screenOrigin = e.screenPos() - e.pos().toPoint()
     rubberRect = QRect(self.origin, e.screenPos()).normalized()
     for i in range(grid.size):
         for j in range(grid.size):
             if rubberRect.contains((grid.gridNodes[i][j].pos() + screenOrigin).toPoint()):
                 grid.gridNodes[i][j].setSelected(True)
             else:
                 if type(grid.gridNodes[i][j].parentItem()) is nodeGroup:
                     grid.gridNodes[i][j].parentItem().setSelected(False)
                 grid.gridNodes[i][j].setSelected(False)
     # pick color from self.QImg
     p = e.pos().toPoint()
     c = QColor(self.QImg.pixel(p - self.offset().toPoint()))
     r, g, b, _ = c.getRgb()
     self.onMouseRelease(p, r, g, b)
示例#8
0
    def _drawWarning(self, painter, rect, item):
        warning = item.data(renderSetupRoles.NODE_WARNING)
        if warning and len(warning) > 0:
            fm = QFontMetrics(self.treeView().font())
            if item.type() == renderSetup.RENDER_OVERRIDE_TYPE:
                left = self.getTextRect(
                    rect,
                    item).right() + baseDelegate.BaseDelegate.ACTION_BORDER
            else:
                left = self.getTextRect(rect, item).left() + fm.boundingRect(
                    item.data(Qt.DisplayRole)).width(
                    ) + baseDelegate.BaseDelegate.ACTION_BORDER
            top = rect.top() + baseDelegate.BaseDelegate.ICON_TOP_OFFSET
            painter.drawPixmap(left, top,
                               baseDelegate.BaseDelegate.WARNING_IMAGE)

            iconRect = QRect(left, top,
                             baseDelegate.BaseDelegate.WARNING_ICON_WIDTH,
                             baseDelegate.BaseDelegate.WARNING_ICON_WIDTH)
            p = self.treeView().mapFromGlobal(QCursor.pos())
            if iconRect.contains(p):
                item.setToolTip(warning)
示例#9
0
def intersect_node(node: Node, pos: QPoint):
    node_rect = QRect(node.metadata['PositionX'], node.metadata['PositionY'],
                      node_width, node_header_height)
    return node_rect.contains(pos)
示例#10
0
class LevelObject(ObjectLike):
    def __init__(
        self,
        data: bytearray,
        object_set: int,
        palette_group,
        pattern_table: PatternTable,
        objects_ref: List["LevelObject"],
        is_vertical: bool,
        index: int,
        size_minimal: bool = False,
    ):
        self.object_set = ObjectSet(object_set)

        self.pattern_table = pattern_table
        self.tsa_data = ROM.get_tsa_data(object_set)

        self.x_position = 0
        self.y_position = 0

        self.rendered_base_x = 0
        self.rendered_base_y = 0

        self.palette_group = palette_group

        self.index_in_level = index
        self.objects_ref = objects_ref
        self.vertical_level = is_vertical

        self.data = data

        self.selected = False

        self.size_minimal = size_minimal

        if self.size_minimal:
            self.ground_level = 0
        else:
            self.ground_level = GROUND

        self._setup()

    def _setup(self):
        data = self.data

        # where to look for the graphic data?
        self.domain = (data[0] & 0b1110_0000) >> 5

        # position relative to the start of the level (top)
        self.original_y = data[0] & 0b0001_1111
        self.y_position = self.original_y

        # position relative to the start of the level (left)
        self.original_x = data[1]
        self.x_position = self.original_x

        if self.vertical_level:
            offset = (self.x_position // SCREEN_WIDTH) * SCREEN_HEIGHT

            self.y_position += offset
            self.x_position %= SCREEN_WIDTH

        # describes what object it is
        self._obj_index = 0x00

        self.obj_index = data[2]

        object_data = self.object_set.get_definition_of(self.type)

        self.width = object_data.bmp_width
        self.height = object_data.bmp_height
        self.orientation = object_data.orientation
        self.ending = object_data.ending
        self.description = object_data.description

        self.blocks = [int(block) for block in object_data.rom_object_design]

        self.block_cache = {}

        self.is_4byte = object_data.is_4byte

        if self.is_4byte and len(self.data) == 3:
            self.data.append(0)
        elif not self.is_4byte and len(data) == 4:
            del self.data[3]

        self._length = 0
        self.secondary_length = 0

        self._calculate_lengths()

        self.rect = QRect()

        self._render()

    @property
    def obj_index(self):
        return self._obj_index

    @obj_index.setter
    def obj_index(self, value):
        self._obj_index = value

        self.is_single_block = self.obj_index <= 0x0F

        domain_offset = self.domain * 0x1F

        if self.is_single_block:
            self.type = self.obj_index + domain_offset
        else:
            self.type = (self.obj_index >> 4) + domain_offset + 16 - 1

    @property
    def length(self):
        return self._length

    @length.setter
    def length(self, value):
        if not self.is_4byte and not self.is_single_block:
            self._obj_index &= 0xF0
            self._obj_index |= value & 0x0F

        self._length = value

    def _calculate_lengths(self):
        if self.is_single_block:
            self._length = 1
        else:
            self._length = self.obj_index & 0b0000_1111

        if self.is_4byte:
            self.secondary_length = self.length
            self.length = self.data[3]

    def render(self):
        self._render()

    def _render(self):
        self.rendered_base_x = base_x = self.x_position
        self.rendered_base_y = base_y = self.y_position

        self.rendered_width = new_width = self.width
        self.rendered_height = new_height = self.height

        try:
            self.index_in_level = self.objects_ref.index(self)
        except ValueError:
            # the object has not been added yet, so stick with the one given in the constructor
            pass

        blocks_to_draw = []

        if self.orientation == TO_THE_SKY:
            base_x = self.x_position
            base_y = SKY

            for _ in range(self.y_position):
                blocks_to_draw.extend(self.blocks[0:self.width])

            blocks_to_draw.extend(self.blocks[-self.width:])

        elif self.orientation == DESERT_PIPE_BOX:
            # segments are the horizontal sections, which are 8 blocks long
            # two of those are drawn per length bit
            # rows are the 4 block high rows Mario can walk in

            is_pipe_box_type_b = self.obj_index // 0x10 == 4

            rows_per_box = self.height
            lines_per_row = 4

            segment_width = self.width
            segments = (self.length + 1) * 2

            box_height = lines_per_row * rows_per_box

            new_width = segments * segment_width
            new_height = box_height

            for row_number in range(rows_per_box):
                for line in range(lines_per_row):
                    if is_pipe_box_type_b and row_number > 0 and line == 0:
                        # in pipebox type b we do not repeat the horizontal beams
                        line += 1

                    start = line * segment_width
                    stop = start + segment_width

                    for segment_number in range(segments):
                        blocks_to_draw.extend(self.blocks[start:stop])

            if is_pipe_box_type_b:
                # draw another open row
                start = segment_width
            else:
                # draw the first row again to close the box
                start = 0

            stop = start + segment_width

            for segment_number in range(segments):
                blocks_to_draw.extend(self.blocks[start:stop])

        elif self.orientation in [
                DIAG_DOWN_LEFT, DIAG_DOWN_RIGHT, DIAG_UP_RIGHT, DIAG_WEIRD
        ]:
            if self.ending == UNIFORM:
                new_height = (self.length + 1) * self.height
                new_width = (self.length + 1) * self.width

                left = [BLANK]
                right = [BLANK]
                slopes = self.blocks

            elif self.ending == END_ON_TOP_OR_LEFT:
                new_height = (self.length + 1) * self.height
                new_width = (self.length + 1) * (self.width - 1
                                                 )  # without fill block

                if self.orientation in [DIAG_DOWN_RIGHT, DIAG_UP_RIGHT]:
                    fill_block = self.blocks[0:1]
                    slopes = self.blocks[1:]

                    left = fill_block
                    right = [BLANK]
                elif self.orientation == DIAG_DOWN_LEFT:
                    fill_block = self.blocks[-1:]
                    slopes = self.blocks[0:-1]

                    right = fill_block
                    left = [BLANK]

                else:
                    fill_block = self.blocks[0:1]
                    slopes = self.blocks[1:]

                    right = [BLANK]
                    left = fill_block

            elif self.ending == END_ON_BOTTOM_OR_RIGHT:
                new_height = (self.length + 1) * self.height
                new_width = (self.length + 1) * (self.width - 1
                                                 )  # without fill block

                fill_block = self.blocks[-1:]
                slopes = self.blocks[0:-1]

                left = [BLANK]
                right = fill_block
            else:
                # todo other two ends not used with diagonals?
                print(self.description)
                self.rendered_blocks = []
                return

            rows = []

            if self.height > self.width:
                slope_width = self.width
            else:
                slope_width = len(slopes)

            for y in range(new_height):
                amount_right = (y // self.height) * slope_width
                amount_left = new_width - slope_width - amount_right

                offset = y % self.height

                rows.append(amount_left * left +
                            slopes[offset:offset + slope_width] +
                            amount_right * right)

            if self.orientation in [DIAG_UP_RIGHT]:
                for row in rows:
                    row.reverse()

            if self.orientation in [DIAG_DOWN_RIGHT, DIAG_UP_RIGHT]:
                if not self.height > self.width:  # special case for 60 degree platform wire down right
                    rows.reverse()

            if self.orientation in [DIAG_UP_RIGHT]:
                base_y -= new_height - 1

            if self.orientation in [DIAG_DOWN_LEFT]:
                base_x -= new_width - slope_width

            for row in rows:
                blocks_to_draw.extend(row)

        elif self.orientation in [PYRAMID_TO_GROUND, PYRAMID_2]:
            # since pyramids grow horizontally in both directions when extending
            # we need to check for new ground every time it grows

            base_x += 1  # set the new base_x to the tip of the pyramid

            for y in range(base_y, self.ground_level):
                new_height = y - base_y
                new_width = 2 * new_height

                bottom_row = QRect(base_x, y, new_width, 1)

                if any([
                        bottom_row.intersects(obj.get_rect())
                        and y == obj.get_rect().top()
                        for obj in self.objects_ref[0:self.index_in_level]
                ]):
                    break

            base_x = base_x - (new_width // 2)

            blank = self.blocks[0]
            left_slope = self.blocks[1]
            left_fill = self.blocks[2]
            right_fill = self.blocks[3]
            right_slope = self.blocks[4]

            for y in range(new_height):
                blank_blocks = (new_width // 2) - (y + 1)
                middle_blocks = y  # times two

                blocks_to_draw.extend(blank_blocks * [blank])

                blocks_to_draw.append(left_slope)
                blocks_to_draw.extend(middle_blocks * [left_fill] +
                                      middle_blocks * [right_fill])
                blocks_to_draw.append(right_slope)

                blocks_to_draw.extend(blank_blocks * [blank])

        elif self.orientation == ENDING:
            page_width = 16
            page_limit = page_width - self.x_position % page_width

            new_width = page_width + page_limit + 1
            new_height = (GROUND - 1) - SKY

            for y in range(SKY, GROUND - 1):
                blocks_to_draw.append(self.blocks[0])
                blocks_to_draw.extend([self.blocks[1]] * (new_width - 1))

            # todo magic number
            # ending graphics
            rom_offset = ENDING_OBJECT_OFFSET + self.object_set.get_ending_offset(
            ) * 0x60

            rom = ROM()

            ending_graphic_height = 6
            floor_height = 1

            y_offset = GROUND - floor_height - ending_graphic_height

            for y in range(ending_graphic_height):
                for x in range(page_width):
                    block_index = rom.get_byte(rom_offset + y * page_width +
                                               x - 1)

                    block_position = (y_offset +
                                      y) * new_width + x + page_limit + 1
                    blocks_to_draw[block_position] = block_index

            # Mushroom/Fire flower/Star is categorized as an enemy

        elif self.orientation == VERTICAL:
            new_height = self.length + 1
            new_width = self.width

            if self.ending == UNIFORM:
                if self.is_4byte:
                    # there is one VERTICAL 4-byte object: Vertically oriented X-blocks
                    # the width is the primary expansion
                    new_width = (self.obj_index & 0x0F) + 1

                for _ in range(new_height):
                    for x in range(new_width):
                        for y in range(self.height):
                            blocks_to_draw.append(self.blocks[x % self.width])

            elif self.ending == END_ON_TOP_OR_LEFT:
                # in case the drawn object is smaller than its actual size
                for y in range(min(self.height, new_height)):
                    offset = y * self.width
                    blocks_to_draw.extend(self.blocks[offset:offset +
                                                      self.width])

                additional_rows = new_height - self.height

                # assume only the last row needs to repeat
                # todo true for giant blocks?
                if additional_rows > 0:
                    last_row = self.blocks[-self.width:]

                    for _ in range(additional_rows):
                        blocks_to_draw.extend(last_row)

            elif self.ending == END_ON_BOTTOM_OR_RIGHT:
                additional_rows = new_height - self.height

                # assume only the first row needs to repeat
                # todo true for giant blocks?
                if additional_rows > 0:
                    last_row = self.blocks[0:self.width]

                    for _ in range(additional_rows):
                        blocks_to_draw.extend(last_row)

                # in case the drawn object is smaller than its actual size
                for y in range(min(self.height, new_height)):
                    offset = y * self.width
                    blocks_to_draw.extend(self.blocks[offset:offset +
                                                      self.width])

            elif self.ending == TWO_ENDS:
                # object exists on ships
                top_row = self.blocks[0:self.width]
                bottom_row = self.blocks[-self.width:]

                blocks_to_draw.extend(top_row)

                additional_rows = new_height - 2

                # repeat second to last row
                if additional_rows > 0:
                    for _ in range(additional_rows):
                        blocks_to_draw.extend(
                            self.blocks[-2 * self.width:-self.width])

                if new_height > 1:
                    blocks_to_draw.extend(bottom_row)

        elif self.orientation in [HORIZONTAL, HORIZ_TO_GROUND, HORIZONTAL_2]:
            new_width = self.length + 1

            if self.orientation == HORIZ_TO_GROUND:
                # to the ground only, until it hits something
                for y in range(base_y, self.ground_level):
                    bottom_row = QRect(base_x, y, new_width, 1)

                    if any([
                            bottom_row.intersects(obj.get_rect())
                            and y == obj.get_rect().top()
                            for obj in self.objects_ref[0:self.index_in_level]
                    ]):
                        new_height = y - base_y
                        break
                else:
                    # nothing underneath this object, extend to the ground
                    new_height = self.ground_level - base_y

                if self.is_single_block:
                    new_width = self.length

            elif self.orientation == HORIZONTAL_2 and self.ending == TWO_ENDS:
                # floating platforms seem to just be one shorter for some reason
                new_width -= 1
            else:
                new_height = self.height + self.secondary_length

            if self.ending == UNIFORM and not self.is_4byte:
                for y in range(new_height):
                    offset = (y % self.height) * self.width

                    for _ in range(0, new_width):
                        blocks_to_draw.extend(self.blocks[offset:offset +
                                                          self.width])

                # in case of giant blocks
                new_width *= self.width

            elif self.ending == UNIFORM and self.is_4byte:
                # 4 byte objects
                top = self.blocks[0:1]
                bottom = self.blocks[-1:]

                new_height = self.height + self.secondary_length

                # ceilings are one shorter than normal
                if self.height > self.width:
                    new_height -= 1

                blocks_to_draw.extend(new_width * top)

                for _ in range(1, new_height):
                    blocks_to_draw.extend(new_width * bottom)

            elif self.ending == END_ON_TOP_OR_LEFT:
                for y in range(new_height):
                    offset = y * self.width

                    blocks_to_draw.append(self.blocks[offset])

                    for x in range(1, new_width):
                        blocks_to_draw.append(self.blocks[offset + 1])

            elif self.ending == END_ON_BOTTOM_OR_RIGHT:
                for y in range(new_height):
                    offset = y * self.width

                    for x in range(new_width - 1):
                        blocks_to_draw.append(self.blocks[offset])

                    blocks_to_draw.append(self.blocks[offset + self.width - 1])

            elif self.ending == TWO_ENDS:
                top_and_bottom_line = 2

                for y in range(self.height):
                    offset = y * self.width
                    left, *middle, right = self.blocks[offset:offset +
                                                       self.width]

                    blocks_to_draw.append(left)
                    blocks_to_draw.extend(middle *
                                          (new_width - top_and_bottom_line))
                    blocks_to_draw.append(right)

                if not len(blocks_to_draw) % self.height == 0:
                    print(
                        f"Blocks to draw are not divisible by height. {self}")

                new_width = int(len(blocks_to_draw) / self.height)

                top_row = blocks_to_draw[0:new_width]
                bottom_row = blocks_to_draw[-new_width:]

                middle_blocks = blocks_to_draw[new_width:-new_width]

                new_rows = new_height - top_and_bottom_line

                if new_rows >= 0:
                    blocks_to_draw = top_row + middle_blocks * new_rows + bottom_row
        else:
            if not self.orientation == SINGLE_BLOCK_OBJECT:
                print(f"Didn't render {self.description}")
                # breakpoint()

        # for not yet implemented objects and single block objects
        if blocks_to_draw:
            self.rendered_blocks = blocks_to_draw
        else:
            self.rendered_blocks = self.blocks

        self.rendered_width = new_width
        self.rendered_height = new_height
        self.rendered_base_x = base_x
        self.rendered_base_y = base_y

        if new_width and not self.rendered_height == len(
                self.rendered_blocks) / new_width:
            print(
                f"Not enough Blocks for calculated height: {self.description}. "
                f"Blocks for height: {len(self.rendered_blocks) / new_width}. Rendered height: {self.rendered_height}"
            )

            self.rendered_height = len(self.rendered_blocks) / new_width
        elif new_width == 0:
            print(
                f"Calculated Width is 0, setting to 1: {self.description}. "
                f"Blocks to draw: {len(self.rendered_blocks)}. Rendered height: {self.rendered_height}"
            )

            self.rendered_width = 1

        self.rect = QRect(self.rendered_base_x, self.rendered_base_y,
                          self.rendered_width, self.rendered_height)

    def draw(self, painter: QPainter, block_length, transparent):
        for index, block_index in enumerate(self.rendered_blocks):
            if block_index == BLANK:
                continue

            x = self.rendered_base_x + index % self.rendered_width
            y = self.rendered_base_y + index // self.rendered_width

            self._draw_block(painter, block_index, x, y, block_length,
                             transparent)

    def _draw_block(self, painter: QPainter, block_index, x, y, block_length,
                    transparent):
        if block_index not in self.block_cache:
            if block_index > 0xFF:
                rom_block_index = ROM().get_byte(
                    block_index
                )  # block_index is an offset into the graphic memory
                block = Block(rom_block_index, self.palette_group,
                              self.pattern_table, self.tsa_data)
            else:
                block = Block(block_index, self.palette_group,
                              self.pattern_table, self.tsa_data)

            self.block_cache[block_index] = block

        self.block_cache[block_index].draw(
            painter,
            x * block_length,
            y * block_length,
            block_length=block_length,
            selected=self.selected,
            transparent=transparent,
        )

    def set_position(self, x, y):
        # todo also check for the upper bounds
        x = max(0, x)
        y = max(0, y)

        x_diff = self.x_position - self.rendered_base_x
        y_diff = self.y_position - self.rendered_base_y

        self.rendered_base_x = int(x)
        self.rendered_base_y = int(y)

        self.x_position = self.rendered_base_x + x_diff
        self.y_position = self.rendered_base_y + y_diff

        self._render()

    def move_by(self, dx: int, dy: int):
        new_x = self.rendered_base_x + dx
        new_y = self.rendered_base_y + dy

        self.set_position(new_x, new_y)

    def get_position(self):
        return self.x_position, self.y_position

    def expands(self):
        expands = EXPANDS_NOT

        if self.is_single_block:
            return expands

        if self.is_4byte:
            expands |= EXPANDS_BOTH

        elif self.orientation in [HORIZONTAL, HORIZONTAL_2, HORIZ_TO_GROUND
                                  ] or self.orientation in [
                                      DIAG_DOWN_LEFT,
                                      DIAG_DOWN_RIGHT,
                                      DIAG_UP_RIGHT,
                                      DIAG_WEIRD,
                                  ]:
            expands |= EXPANDS_HORIZ

        elif self.orientation in [VERTICAL, DIAG_WEIRD]:
            expands |= EXPANDS_VERT

        return expands

    def primary_expansion(self):
        if self.orientation in [HORIZONTAL, HORIZONTAL_2, HORIZ_TO_GROUND
                                ] or self.orientation in [
                                    DIAG_DOWN_LEFT,
                                    DIAG_DOWN_RIGHT,
                                    DIAG_UP_RIGHT,
                                    DIAG_WEIRD,
                                ]:
            if self.is_4byte:
                return EXPANDS_VERT
            else:
                return EXPANDS_HORIZ
        elif self.orientation in [VERTICAL]:
            if self.is_4byte:
                return EXPANDS_HORIZ
            else:
                return EXPANDS_VERT
        else:
            return EXPANDS_BOTH

    def resize_x(self, x: int):
        if self.expands() & EXPANDS_HORIZ == 0:
            return

        if self.primary_expansion() == EXPANDS_HORIZ:
            length = x - self.x_position

            length = max(0, length)
            length = min(length, 0x0F)

            base_index = (self.obj_index // 0x10) * 0x10

            self.obj_index = base_index + length
            self.data[2] = self.obj_index
        else:
            length = x - self.x_position
            length = max(0, length)
            length = min(length, 0xFF)

            if self.is_4byte:
                self.data[3] = length
            else:
                raise ValueError("Resize impossible", self)

        self._calculate_lengths()

        self._render()

    def resize_y(self, y: int):
        if self.expands() & EXPANDS_VERT == 0:
            return

        if self.primary_expansion() == EXPANDS_VERT:
            length = y - self.y_position

            length = max(0, length)
            length = min(length, 0x0F)

            base_index = (self.obj_index // 0x10) * 0x10

            self.obj_index = base_index + length
            self.data[2] = self.obj_index
        else:
            length = y - self.y_position
            length = max(0, length)
            length = min(length, 0xFF)

            if self.is_4byte:
                self.data[3] = length
            else:
                raise ValueError("Resize impossible", self)

        self._calculate_lengths()

        self._render()

    def resize_by(self, dx: int, dy: int):
        if dx:
            self.resize_x(self.x_position + dx)

        if dy:
            self.resize_y(self.y_position + dy)

    def increment_type(self):
        self.change_type(True)

    def decrement_type(self):
        self.change_type(False)

    def change_type(self, increment: int):
        if self.obj_index < 0x10 or self.obj_index == 0x10 and not increment:
            value = 1
        else:
            self.obj_index = self.obj_index // 0x10 * 0x10
            value = 0x10

        if not increment:
            value *= -1

        new_type = self.obj_index + value

        if new_type < 0 and self.domain > 0:
            new_domain = self.domain - 1
            new_type = 0xF0
        elif new_type > 0xFF and self.domain < 7:
            new_domain = self.domain + 1
            new_type = 0x00
        else:
            new_type = min(0xFF, new_type)
            new_type = max(0, new_type)

            new_domain = self.domain

        self.data[0] &= 0b0001_1111
        self.data[0] |= new_domain << 5

        self.data[2] = new_type

        self._setup()

    def __contains__(self, item: Tuple[int, int]) -> bool:
        x, y = item

        return self.point_in(x, y)

    def point_in(self, x: int, y: int) -> bool:
        return self.rect.contains(x, y)

    def get_status_info(self) -> List[tuple]:
        return [
            ("x", self.rendered_base_x),
            ("y", self.rendered_base_y),
            ("Width", self.rendered_width),
            ("Height", self.rendered_height),
            ("Orientation", ORIENTATION_TO_STR[self.orientation]),
            ("Ending", ENDING_STR[self.ending]),
        ]

    def display_size(self, zoom_factor: int = 1):
        return QSize(self.rendered_width * Block.SIDE_LENGTH,
                     self.rendered_height * Block.SIDE_LENGTH) * zoom_factor

    def as_image(self) -> QImage:
        self.rendered_base_x = 0
        self.rendered_base_y = 0

        image = QImage(
            QSize(self.rendered_width * Block.SIDE_LENGTH,
                  self.rendered_height * Block.SIDE_LENGTH),
            QImage.Format_RGB888,
        )

        bg_color = bg_color_for_object_set(self.object_set.number, 0)

        image.fill(bg_color)

        painter = QPainter(image)

        self.draw(painter, Block.SIDE_LENGTH, True)

        return image

    def to_bytes(self) -> bytearray:
        data = bytearray()

        if self.vertical_level:
            # todo from vertical to non-vertical is bugged, because it
            # seems like you can't convert the coordinates 1:1
            # there seems to be ambiguity

            offset = self.y_position // SCREEN_HEIGHT

            x_position = self.x_position + offset * SCREEN_WIDTH
            y_position = self.y_position % SCREEN_HEIGHT
        else:
            x_position = self.x_position
            y_position = self.y_position

        if self.orientation in [PYRAMID_TO_GROUND, PYRAMID_2]:
            x_position = self.rendered_base_x - 1 + self.rendered_width // 2

        data.append((self.domain << 5) | y_position)
        data.append(x_position)

        if not self.is_4byte and not self.is_single_block:
            third_byte = (self.obj_index & 0xF0) + self.length
        else:
            third_byte = self.obj_index

        data.append(third_byte)

        if self.is_4byte:
            data.append(self.length)

        return data

    def __repr__(self) -> str:
        return f"LevelObject {self.description} at {self.x_position}, {self.y_position}"

    def __eq__(self, other):
        if not isinstance(other, LevelObject):
            return False
        else:
            return self.to_bytes() == other.to_bytes()

    def __lt__(self, other):
        return self.index_in_level < other.index_in_level
示例#11
0
class PaletteWidget(QtWidgets.QWidget):

    # Color changed signal
    changed = QtCore.Signal()

    @property
    def color(self):
        return QtGui.QColor.fromHsvF(self._hue, self._saturation, self._value)

    @color.setter
    def color(self, value):
        # If this is an integer, assume is RGBA
        if not isinstance(value, QtGui.QColor):
            r = (value & 0xff000000) >> 24
            g = (value & 0xff0000) >> 16
            b = (value & 0xff00) >> 8
            value = QtGui.QColor.fromRgb(r, g, b)
        self._set_color(value)
        self.RGBvalue.setText(value.name())

    def __init__(self, parent=None, RGBvalue=None):
        super(PaletteWidget, self).__init__(parent)
        self._hue = 1.0
        self._saturation = 1.0
        self._value = 1.0
        self._hue_width = 24
        self._gap = 8
        self._color = QtGui.QColor.fromHslF(self._hue, 1.0, 1.0)
        self._calculate_bounds()
        self._draw_palette()
        self.RGBvalue = RGBvalue

    # Calculate the sizes of various bits of our UI
    def _calculate_bounds(self):
        width = self.width()
        height = self.height()
        # Hue palette
        self._hue_rect = QRect(width - self._hue_width, 0, self._hue_width, height)
        # Shades palette
        self._shades_rect = QRect(0, 0, width - (self._hue_width + self._gap), height)

    # Render our palette to an image
    def _draw_palette(self):

        # Create an image with a white background
        self._image = QtGui.QImage(QtCore.QSize(self.width(), self.height()), QtGui.QImage.Format.Format_RGB32)
        self._image.fill(QtGui.QColor.fromRgb(0xff, 0xff, 0xff))

        # Draw on our image with no pen
        qp = QtGui.QPainter()
        qp.begin(self._image)
        qp.setPen(QtCore.Qt.NoPen)

        # Render hues
        rect = self._hue_rect
        for x in xrange(rect.x(), rect.x() + rect.width()):
            for y in xrange(rect.y(), rect.y() + rect.height(), 8):
                h = float(y) / rect.height()
                s = 1.0
                v = 1.0
                c = QtGui.QColor.fromHsvF(h, s, v)
                qp.setBrush(c)
                qp.drawRect(x, y, 8, 8)

        # Render hue selection marker
        qp.setBrush(QtGui.QColor.fromRgb(0xff, 0xff, 0xff))
        qp.drawRect(rect.x(), self._hue * rect.height(), rect.width(), 2)

        # Render shades
        rect = self._shades_rect
        width = float(rect.width())
        steps = int(round(width / 8.0))
        step_size = width / steps
        x = rect.x()
        while x < rect.width() + rect.x():
            w = int(round(step_size))
            for y in xrange(rect.y(), rect.y() + rect.height(), 8):
                h = self._hue
                s = 1 - float(y) / rect.height()
                v = float(x) / rect.width()
                c = QtGui.QColor.fromHsvF(h, s, v)
                qp.setBrush(c)
                qp.drawRect(x, y, w, 8)
            x += w
            width -= w
            steps -= 1
            if steps > 0:
                step_size = width / steps

        # Render color selection marker
        qp.setBrush(QtGui.QColor.fromRgb(0xff, 0xff, 0xff))
        qp.drawRect(rect.x(), (1 - self._saturation) * rect.height(), rect.width(), 1)
        qp.drawRect(self._value * rect.width(), rect.y(), 1, rect.height())

        qp.end()

    def paintEvent(self, event):
        # Render our palette image to the screen
        qp = QtGui.QPainter()
        qp.begin(self)
        qp.drawImage(QPoint(0, 0), self._image)
        qp.end()

    def mousePressEvent(self, event):
        mouse = QPoint(event.pos())
        if event.buttons() & QtCore.Qt.LeftButton:
            # Click on hues?
            if self._hue_rect.contains(mouse.x(), mouse.y()):
                y = mouse.y()
                c = QtGui.QColor.fromHsvF(float(y) / self.height(), self._saturation, self._value)
                self.color = c
            # Click on colors?
            elif self._shades_rect.contains(mouse.x(), mouse.y()):
                # calculate saturation and value
                x = mouse.x()
                y = mouse.y()
                c = QtGui.QColor.fromHsvF(self._hue, 1 - float(y) / self._shades_rect.height(),
                                          float(x) / self._shades_rect.width())
                self.color = c

    def mouseMoveEvent(self, event):
        if event.buttons() & QtCore.Qt.LeftButton:
            self.mousePressEvent(event)

    def resizeEvent(self, event):
        self._calculate_bounds()
        self._draw_palette()

    # Set the current color
    def _set_color(self, c):
        h, s, v, _ = c.getHsvF()
        self._hue = h
        self._saturation = s
        self._value = v
        self._draw_palette()
        self.repaint()
        self.changed.emit()
示例#12
0
class QMaxRollout(QtW.QWidget):
    collapsed = Signal()
    expanded = Signal()
    toggled = Signal(bool)

    def __init__(self, *args):
        super(QMaxRollout, self).__init__(*args)
        self._expanded = True
        self._delayed = False, False

        self.setSizePolicy(
            QtW.QSizePolicy(QtW.QSizePolicy.MinimumExpanding,
                            QtW.QSizePolicy.Minimum))

        self._full_title = str()
        self._title = self._full_title

        self._header_rect = QRect()
        self._title_bound_rect = QRect()
        self._base_margin = QMargins(9, 9, 9, 9)
        self._icon_width = 24

        self._storage = QtW.QWidget()
        self._storage.setVisible(False)

        self._system_font = QtG.QFont()

    # noinspection PyPep8Naming
    def setExpanded(self, state):
        self._expand_collapse(state)

    # noinspection PyPep8Naming
    def setExpandedDelay(self, state):
        self._delayed = True, state

    def _expand_collapse(self, state):
        self._expanded = state

        children = self.children()

        for child in children:
            if hasattr(child, "setVisible"):
                child.setVisible(state)
            else:
                layout_collapse_and_restore(child, state)

        if state:
            self.expanded.emit()
        else:
            self.collapsed.emit()

        self.update()

    # noinspection PyPep8Naming
    def isExpanded(self):
        return self._expanded

    def title(self):
        return self._full_title

    # noinspection PyPep8Naming
    def setTitle(self, title):
        self._full_title = title

    def toggle(self):
        self._expand_collapse(not self._expanded)
        self.toggled.emit(self._expanded)

    # ---------------------------------------------------
    #                    Events
    # ---------------------------------------------------

    def mouseReleaseEvent(self, e):
        point = (e.localPos()).toPoint()
        if self._header_rect.contains(point):
            self.toggle()
            e.accept()
        else:
            e.ignore()

    def paintEvent(self, e):
        # If delayed expand/collapse is activated, it won't process until the next paint event
        if self._delayed[0]:
            self._expand_collapse(self._delayed[1])
            self._delayed = False, False

        # Object Repaint
        qp = QtG.QPainter()
        qp.begin(self)
        self._system_font = qp.font()
        self._recalculate_header()
        super(QMaxRollout, self).paintEvent(e)
        self._draw_widget(qp)
        qp.end()
        e.accept()

    # ---------------------------------------------------
    #               Paint Event Helpers
    # ---------------------------------------------------

    def _recalculate_header(self):
        fnt_title = bold_font(self._system_font)
        font_metric = QtG.QFontMetricsF(fnt_title)

        if self._full_title == str():
            prop = self.property('title')
            if prop is not None:
                self._title_bound_rect = font_metric.boundingRect(prop)
                self._full_title = prop
            else:
                self._title_bound_rect = font_metric.boundingRect("UNTITLED")
        else:
            self._title_bound_rect = font_metric.boundingRect(self._full_title)

        title_space = self.width() - (self._icon_width +
                                      self._base_margin.right() +
                                      self._base_margin.left())

        # If the title is too big to fit in the box
        if self._title_bound_rect.width() > title_space:
            self._title = chop_title(self._full_title, title_space,
                                     self._title_bound_rect.width())
            self._title_bound_rect = font_metric.boundingRect(self._title)
        else:
            self._title = self._full_title

        self._header_rect = QRect(
            self._base_margin.left(), self._base_margin.top(),
            self.width() -
            (self._base_margin.left() - self._base_margin.right()),
            self._title_bound_rect.height() + self._base_margin.top())

    def _draw_widget(self, qp):
        size = self.size()
        w = size.width()
        h = size.height()

        mrgn = self._base_margin

        br_bkg = QtG.QBrush(QtG.QColor(80, 80, 80))
        # pal = self.palette()
        # br_bkg = pal.alternateBase()
        pn_bkg = QtG.QPen(QtG.QColor(62, 62, 62), 2)
        br_arr = QtG.QBrush(QtG.QColor(185, 185, 185))
        pn_arr = QtG.QPen(QtG.QColor(62, 62, 62), 0)
        # For debugging
        # pn_test = QtG.QPen(Qt.red, 1)
        pn_txt = QtG.QPen(QtG.QColor(255, 255, 255))

        rnd_x, rnd_y = calculate_round_corners(w, h, 11)
        rct_bkg = QRect(0, 0, w, h)

        qp.setPen(pn_bkg)
        qp.setBrush(br_bkg)

        qp.setRenderHint(QtG.QPainter.Antialiasing)

        qp.drawRoundRect(rct_bkg, rnd_x, rnd_y, Qt.RelativeSize)

        tx_title = self._title

        qp.setFont(bold_font(self._system_font))

        rct_icon = QRect(mrgn.left(), mrgn.top(), self._icon_width,
                         self._title_bound_rect.height())

        rct_title = QRect(rct_icon.right(), mrgn.top(),
                          self._title_bound_rect.width(),
                          self._title_bound_rect.height())

        pgn_arrow = center_poly_in_rect(max_arrow(not self._expanded),
                                        rct_icon)

        qp.setPen(pn_arr)
        qp.setBrush(br_arr)

        qp.drawPolygon(pgn_arrow)

        qp.setPen(pn_txt)

        qp.drawText(rct_title, tx_title)

        open_margins = QMargins(
            mrgn.left(),
            self._title_bound_rect.height() + (mrgn.top() * 2), mrgn.right(),
            mrgn.bottom())
        closed_margins = QMargins(mrgn.left(),
                                  self._title_bound_rect.height() + mrgn.top(),
                                  mrgn.right(), mrgn.bottom())
        mrgn = open_margins if self._expanded else closed_margins
        self.setContentsMargins(mrgn)

        # For debugging
        # qp.setPen(pn_test)
        qp.setBrush(Qt.NoBrush)

        qp.setFont(self._system_font)
示例#13
0
class EnemyObject(ObjectLike):
    def __init__(self, data, png_data, palette_group):
        super(EnemyObject, self).__init__()

        self.is_4byte = False
        self.is_single_block = True
        self.length = 0

        self.obj_index = data[0]
        self.x_position = data[1]
        self.y_position = data[2]

        self.domain = 0

        self.pattern_table = PatternTable(0x4C)
        self.palette_group = palette_group

        self.object_set = ObjectSet(ENEMY_OBJECT_SET)

        self.bg_color = NESPalette[palette_group[0][0]]

        self.png_data = png_data

        self.rect = QRect()

        self.selected = False

        self._setup()

    def _setup(self):
        obj_def = self.object_set.get_definition_of(self.obj_index)

        self.description = obj_def.description

        self.width = obj_def.bmp_width
        self.height = obj_def.bmp_height

        self.rect = QRect(self.x_position, self.y_position, self.width,
                          self.height)

        self._render(obj_def)

    def _render(self, obj_def):
        self.blocks = []

        block_ids = obj_def.object_design

        for block_id in block_ids:
            x = (block_id % 64) * Block.WIDTH
            y = (block_id // 64) * Block.WIDTH

            self.blocks.append(
                self.png_data.copy(QRect(x, y, Block.WIDTH, Block.HEIGHT)))

    def render(self):
        # nothing to re-render since enemies are just copied over
        pass

    def draw(self, painter: QPainter, block_length, _):
        for i, image in enumerate(self.blocks):
            x = self.x_position + (i % self.width)
            y = self.y_position + (i // self.width)

            x_offset = int(enemy_handle_x[self.obj_index])
            y_offset = int(enemy_handle_y[self.obj_index])

            x += x_offset
            y += y_offset

            block = image.copy()

            mask = block.createMaskFromColor(
                QColor(*MASK_COLOR).rgb(), Qt.MaskOutColor)
            block.setAlphaChannel(mask)

            # todo better effect
            if self.selected:
                apply_selection_overlay(block, mask)

            if block_length != Block.SIDE_LENGTH:
                block = block.scaled(block_length, block_length)

            painter.drawImage(x * block_length, y * block_length, block)

    def get_status_info(self):
        return [("Name", self.description), ("X", self.x_position),
                ("Y", self.y_position)]

    def __contains__(self, item):
        x, y = item

        return self.point_in(x, y)

    def point_in(self, x, y):
        return self.rect.contains(x, y)

    def set_position(self, x, y):
        # todo also check for the upper bounds
        x = max(0, x)
        y = max(0, y)

        self.x_position = x
        self.y_position = y

        self.rect = QRect(self.x_position, self.y_position, self.width,
                          self.height)

    def move_by(self, dx, dy):
        new_x = self.x_position + dx
        new_y = self.y_position + dy

        self.set_position(new_x, new_y)

    def get_position(self):
        return self.x_position, self.y_position

    def resize_by(self, dx, dy):
        pass

    @property
    def type(self):
        return self.obj_index

    def change_type(self, new_type):
        self.obj_index = new_type

        self._setup()

    def increment_type(self):
        self.obj_index = min(0xFF, self.obj_index + 1)

        self._setup()

    def decrement_type(self):
        self.obj_index = max(0, self.obj_index - 1)

        self._setup()

    def to_bytes(self):
        return bytearray([self.obj_index, self.x_position, self.y_position])

    def as_image(self) -> QImage:
        image = QImage(
            QSize(self.width * Block.SIDE_LENGTH,
                  self.height * Block.SIDE_LENGTH),
            QImage.Format_RGBA8888,
        )

        image.fill(QColor(0, 0, 0, 0))

        painter = QPainter(image)

        self.draw(painter, Block.SIDE_LENGTH, True)

        return image

    def __repr__(self):
        return f"EnemyObject {self.description} at {self.x_position}, {self.y_position}"
示例#14
0
class MapObject(ObjectLike):
    def __init__(self, block, x, y):
        self.x_position = x
        self.y_position = y

        self.block = block

        self.rect = QRect(self.x_position, self.y_position, 1, 1)

        if self.block.index in map_object_names:
            self.name = map_object_names[self.block.index]
        else:
            self.name = str(hex(self.block.index))

        self.selected = False

    def set_position(self, x, y):
        x = int(x)
        y = int(y)

        self.rect = QRect(x, y, 1, 1)

        self.x_position = x
        self.y_position = y

    def get_position(self):
        return self.x_position, self.y_position

    def render(self):
        pass

    def draw(self, dc, block_length, _=None):
        self.block.draw(
            dc,
            self.x_position * block_length,
            self.y_position * block_length,
            block_length=block_length,
            selected=self.selected,
            transparent=False,
        )

    def get_status_info(self):
        return ("x", self.x_position), ("y", self.y_position), ("Block Type",
                                                                self.name)

    def to_bytes(self):
        return self.block.index

    def move_by(self, dx, dy):
        self.set_position(self.x_position + dx, self.y_position + dy)

    def resize_to(self, x, y):
        return

    def resize_by(self, dx, dy):
        return

    def point_in(self, x, y):
        return self.rect.contains(x, y)

    def change_type(self, new_type):
        pass

    def get_rect(self):
        return self.rect

    def __contains__(self, point):
        pass
示例#15
0
class SchematicEditor(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.StrongFocus)

        self.elements = list()
        self.wires = list()

        self.guidepoints = list()
        self.guidelines = list()
        self._ghost_wire = None
        self.wiring_mode = False
        self.closest_point = None

        self._wire_start = None

        self.select_rect = None

        self.selected_elements = list()
        self.moved = False
        self.grabbed_element = None
        self.grab_offset = None

    def _draw_wire(self, painter, line, ghost):
        p = QPen(Qt.black, 8, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap)
        path = QPainterPath(line.p1())
        path.lineTo(line.p2())
        stroker = QPainterPathStroker(p)
        stroke = stroker.createStroke(path)

        fill_color = QColor(255, 255, 255)
        outline_color = QColor(0, 0, 0)

        if ghost:
            fill_color.setAlphaF(0.5)
            outline_color.setAlphaF(0.5)

        painter.setPen(QPen(outline_color, 2))
        painter.fillPath(stroke, fill_color)
        painter.drawPath(stroke)

    def _draw_wires(self, painter):
        path = QPainterPath()

        for wire in self.wires:
            path.moveTo(wire.p1())
            path.lineTo(wire.p2())

        p = QPen(Qt.black, 8, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap)

        stroker = QPainterPathStroker(p)
        stroke = stroker.createStroke(path).simplified()

        fill_color = QColor(255, 255, 255)
        outline_color = QColor(0, 0, 0)

        painter.setPen(QPen(outline_color, 2))
        painter.fillPath(stroke, fill_color)
        painter.drawPath(stroke)

    def _draw_pin(self, painter, point):
        fill_color = QColor(255, 255, 255)
        outline_color = QColor(0, 0, 0)
        painter.setBrush(fill_color)
        painter.setPen(QPen(outline_color, 2))
        painter.drawEllipse(point.x() - 4, point.y() - 4, 8, 8)

    def paintEvent(self, *args):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.HighQualityAntialiasing)

        r = self.rect()
        painter.fillRect(r, Qt.white)

        for element in self.elements:
            painter.translate(element.bounding_box.topLeft())
            element.paint(painter)
            painter.translate(-element.bounding_box.topLeft())

        self._draw_wires(painter)

        for element in self.elements:
            for pin in element.pins():
                p = pin.position + element.bounding_box.topLeft()
                self._draw_pin(painter, p)

        painter.setPen(QPen(Qt.red, 1, Qt.DashLine))
        painter.setBrush(Qt.transparent)
        for element in self.selected_elements:
            bb = element.bounding_box
            bb = bb.marginsAdded(QMargins(2, 2, 1, 1))
            painter.drawRect(bb)

        if self.select_rect is not None:
            painter.setBrush(QColor(0, 0, 255, 64))
            painter.setPen(QColor(0, 0, 255, 128))
            painter.drawRect(self.select_rect)

        if self.wiring_mode:
            painter.setPen(QPen(Qt.red, 1, Qt.PenStyle.DotLine))
            for line in self.guidelines:
                painter.drawLine(line)

            if self._ghost_wire:
                self._draw_wire(painter, self._ghost_wire, True)

            if self.closest_point is not None:
                p = self.closest_point
                painter.drawEllipse(p.x() - 4, p.y() - 4, 8, 8)

    def _pick(self, p):
        for element in self.elements:
            if element.bounding_box.contains(p):
                return element
        return None

    def _closest_guideline_point(self, point):
        currd = None
        closest = None
        is_junction = False
        for element in self.elements:
            for pin in element.pins():
                p = pin.position + element.bounding_box.topLeft()
                d = QVector2D(p - point).lengthSquared()
                if (currd is None or d < currd) and d < 2500:
                    currd = d
                    closest = p
                    is_junction = True
        for wire in self.wires:
            for p in (wire.p1(), wire.p2()):
                d = QVector2D(p - point).lengthSquared()
                if (currd is None or d < currd) and d < 2500:
                    currd = d
                    closest = p
                    is_junction = True
        for line in self.guidelines:
            p = _closest_point(line, point)
            d = QVector2D(p - point).lengthSquared()
            if not _is_point_on_line(line, p):
                continue
            if self._wire_start is not None:
                delta = p - self._wire_start
                if delta.x() != 0 and delta.y() != 0:
                    continue
            if (currd is None or
                ((not is_junction and d < currd) or
                 (is_junction and abs(d - currd) > 100))) and d < 2500:
                currd = d
                closest = p
        return closest

    def _closest_assist_point(self, point):
        gp = self._closest_guideline_point(point)
        return gp

    def mousePressEvent(self, e):
        if self.wiring_mode:
            pass
        else:
            self.grabbed_element = self._pick(e.pos())
            if self.grabbed_element is not None:
                self.grab_offset = self.grabbed_element.bounding_box.topLeft(
                ) - e.pos()
            else:
                self.select_rect = QRect(e.pos(), QSize(0, 0))

    def mouseMoveEvent(self, e):
        if self.wiring_mode:
            self.closest_point = self._closest_assist_point(e.pos())
            if self._wire_start is not None and self.closest_point is not None:
                self._ghost_wire = QLine(self._wire_start, self.closest_point)
            else:
                self._ghost_wire = None
            self.update()
        else:
            if self.grabbed_element is not None:
                self.grabbed_element.bounding_box.moveTopLeft(e.pos() +
                                                              self.grab_offset)
                self.moved = True
                self.update()
            elif self.select_rect is not None:
                self.select_rect.setBottomRight(e.pos())
                if self.select_rect.size() != QSize(0, 0):
                    self.selected_elements = list()
                    for element in self.elements:
                        if self.select_rect.contains(element.bounding_box):
                            self.selected_elements.append(element)
                self.update()

    def mouseReleaseEvent(self, e):
        if self.wiring_mode:
            if e.button() == Qt.RightButton:
                self._wire_start = None
                self.update()
            elif self.closest_point is not None:
                if self._wire_start is None:
                    self._wire_start = self.closest_point
                elif self.closest_point != self._wire_start:
                    wire_end = self.closest_point
                    self.wires.append(QLine(self._wire_start, wire_end))
                    self._wire_start = None
                    self._build_guidelines()
                    self.update()
        else:
            moved = self.moved

            if self.grabbed_element is not None:
                self.grabbed_element = None
                self.moved = False

            if not moved:
                self.selected_elements = list()
                if self.select_rect is not None and self.select_rect.size(
                ) != QSize(0, 0):
                    for element in self.elements:
                        if self.select_rect.contains(element.bounding_box):
                            self.selected_elements.append(element)
                else:
                    for element in self.elements:
                        bb = element.bounding_box
                        if bb.contains(e.pos()):
                            self.selected_elements.append(element)
                            break
                self.select_rect = None
                self.update()

    def _build_guidelines(self):
        self.guidelines = list()
        for element in self.elements:
            for pin in element.pins():
                p = pin.position + element.bounding_box.topLeft()
                if pin.direction.y() == 0:
                    if pin.direction.x() > 0:
                        self.guidelines.append(
                            QLine(p.x(), p.y(),
                                  self.rect().width(), p.y()))
                    else:
                        self.guidelines.append(QLine(p.x(), p.y(), 0, p.y()))
                    self.guidelines.append(
                        QLine(p.x(), 0, p.x(),
                              self.rect().height()))
                else:
                    if pin.direction.y() > 0:
                        self.guidelines.append(
                            QLine(p.x(), p.y(), p.x(),
                                  self.rect().height()))
                    else:
                        self.guidelines.append(QLine(p.x(), p.y(), p.x(), 0))
                    self.guidelines.append(
                        QLine(0, p.y(),
                              self.rect().width(), p.y()))
        for wire in self.wires:
            for p in (wire.p1(), wire.p2()):
                self.guidelines.append(
                    QLine(0, p.y(),
                          self.rect().width(), p.y()))
                self.guidelines.append(
                    QLine(p.x(), 0, p.x(),
                          self.rect().height()))

    def resizeEvent(self, e):
        super().resizeEvent(e)
        self._build_guidelines()
        self.update()

    def _leave_wiring_mode(self):
        self.wiring_mode = False

    def _enter_wiring_mode(self):
        self.wiring_mode = True
        self._ghost_wire = None
        self.closest_point = None
        self.selected_elements = list()
        self._build_guidelines()

    def keyReleaseEvent(self, e):
        if e.key() == Qt.Key_W:
            if not self.wiring_mode:
                self._enter_wiring_mode()
                self.wiring_mode = True
            else:
                self._leave_wiring_mode()
                self.wiring_mode = False
            self.update()
        elif e.key() == Qt.Key_Escape:
            if self.wiring_mode:
                self._leave_wiring_mode()
                self.wiring_mode = False
                self.update()
示例#16
0
class Screenshot(QGraphicsView):
    """ Main Class """

    screen_shot_grabed = Signal(QImage)
    screen_shot_pos_grabed = Signal(QRect)
    widget_closed = Signal()

    def __init__(self, flags=constant.DEFAULT, parent=None):
        """
        flags: binary flags. see the flags in the constant.py
        """
        super().__init__(parent)

        # Init
        self.penColorNow = QColor(PENCOLOR)
        self.penSizeNow = PENSIZE
        self.fontNow = QFont('Sans')
        self.clipboard = QApplication.clipboard()

        self.drawListResult = [
        ]  # draw list that sure to be drew, [action, coord]
        self.drawListProcess = None  # the process to the result
        self.selected_area = QRect(
        )  # a QRect instance which stands for the selected area
        self.selectedAreaRaw = QRect()
        self.mousePosition = MousePosition.OUTSIDE_AREA  # mouse position
        self.screenPixel = None
        self.textRect = None

        self.mousePressed = False
        self.action = ACTION_SELECT
        self.mousePoint = self.cursor().pos()

        self.startX, self.startY = 0, 0  # the point where you start
        self.endX, self.endY = 0, 0  # the point where you end
        self.pointPath = QPainterPath(
        )  # the point mouse passes, used by draw free line
        self.items_to_remove = [
        ]  # the items that should not draw on screenshot picture
        self.textPosition = None

        # result
        self.target_img = None
        self.target_img_pos = None

        # Init window
        self.getscreenshot()
        self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)

        self.setMouseTracking(True)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setContentsMargins(0, 0, 0, 0)
        self.setStyleSheet("QGraphicsView { border-style: none; }")

        self.tooBar = MyToolBar(flags, self)
        self.tooBar.trigger.connect(self.changeAction)

        self.penSetBar = None
        if flags & constant.RECT or flags & constant.ELLIPSE or flags & constant.LINE or flags & constant.FREEPEN \
                or flags & constant.ARROW or flags & constant.TEXT:
            self.penSetBar = PenSetWidget(self)
            self.penSetBar.penSizeTrigger.connect(self.changePenSize)
            self.penSetBar.penColorTrigger.connect(self.changePenColor)
            self.penSetBar.fontChangeTrigger.connect(self.changeFont)

        self.textInput = TextInput(self)
        self.textInput.inputChanged.connect(self.textChange)
        self.textInput.cancelPressed.connect(self.cancelInput)
        self.textInput.okPressed.connect(self.okInput)

        self.graphics_scene = QGraphicsScene(0, 0, self.screenPixel.width(),
                                             self.screenPixel.height())

        self.show()
        self.setScene(self.graphics_scene)
        self.windowHandle().setScreen(QGuiApplication.screenAt(QCursor.pos()))
        self.scale = self.get_scale()
        # self.setFixedSize(self.screenPixel.width(), self.screenPixel.height())
        self.setGeometry(QGuiApplication.screenAt(QCursor.pos()).geometry())
        self.showFullScreen()
        self.redraw()

        QShortcut(QKeySequence('ctrl+s'),
                  self).activated.connect(self.saveScreenshot)
        QShortcut(QKeySequence('esc'), self).activated.connect(self.close)

    @staticmethod
    def take_screenshot(flags):
        loop = QEventLoop()
        screen_shot = Screenshot(flags)
        screen_shot.show()
        screen_shot.widget_closed.connect(loop.quit)

        loop.exec_()
        img = screen_shot.target_img
        return img

    @staticmethod
    def take_screenshot_pos(flags):
        loop = QEventLoop()
        screen_shot = Screenshot(flags)
        screen_shot.show()
        screen_shot.widget_closed.connect(loop.quit)

        loop.exec_()
        pos = screen_shot.target_img_pos
        return pos

    def getscreenshot(self):
        screen = QGuiApplication.screenAt(QCursor.pos())
        self.screenPixel = screen.grabWindow(0)

    def mousePressEvent(self, event):
        """
        :type event: QMouseEvent
        :param event:
        :return:
        """
        if event.button() != Qt.LeftButton:
            return

        if self.action is None:
            self.action = ACTION_SELECT

        self.startX, self.startY = event.x(), event.y()

        if self.action == ACTION_SELECT:
            if self.mousePosition == MousePosition.OUTSIDE_AREA:
                self.mousePressed = True
                self.selected_area = QRect()
                self.selected_area.setTopLeft(QPoint(event.x(), event.y()))
                self.selected_area.setBottomRight(QPoint(event.x(), event.y()))
                self.redraw()
            elif self.mousePosition == MousePosition.INSIDE_AREA:
                self.mousePressed = True
            else:
                pass
        elif self.action == ACTION_MOVE_SELECTED:
            if self.mousePosition == MousePosition.OUTSIDE_AREA:
                self.action = ACTION_SELECT
                self.selected_area = QRect()
                self.selected_area.setTopLeft(QPoint(event.x(), event.y()))
                self.selected_area.setBottomRight(QPoint(event.x(), event.y()))
                self.redraw()
            self.mousePressed = True
        elif self.action in DRAW_ACTION:
            self.mousePressed = True
            if self.action == ACTION_FREEPEN:
                self.pointPath = QPainterPath()
                self.pointPath.moveTo(QPoint(event.x(), event.y()))
            elif self.action == ACTION_TEXT:
                if self.textPosition is None:
                    self.textPosition = QPoint(event.x(), event.y())
                    self.textRect = None
                    self.redraw()

    def mouseMoveEvent(self, event: QMouseEvent):
        """
        :type event: QMouseEvent
        :param event:
        :return:
        """
        self.mousePoint = QPoint(event.globalPos().x(), event.globalPos().y())

        if self.action is None:
            self.action = ACTION_SELECT

        if not self.mousePressed:
            point = QPoint(event.x(), event.y())
            self.detect_mouse_position(point)
            self.setCursorStyle()
            self.redraw()
        else:
            self.endX, self.endY = event.x(), event.y()

            # if self.mousePosition != OUTSIDE_AREA:
            #    self.action = ACTION_MOVE_SELECTED

            if self.action == ACTION_SELECT:
                self.selected_area.setBottomRight(QPoint(event.x(), event.y()))
                self.redraw()
            elif self.action == ACTION_MOVE_SELECTED:
                self.selected_area = QRect(self.selectedAreaRaw)

                if self.mousePosition == MousePosition.INSIDE_AREA:
                    move_to_x = event.x(
                    ) - self.startX + self.selected_area.left()
                    move_to_y = event.y(
                    ) - self.startY + self.selected_area.top()
                    if 0 <= move_to_x <= self.screenPixel.width(
                    ) - 1 - self.selected_area.width():
                        self.selected_area.moveLeft(move_to_x)
                    if 0 <= move_to_y <= self.screenPixel.height(
                    ) - 1 - self.selected_area.height():
                        self.selected_area.moveTop(move_to_y)
                    self.selected_area = self.selected_area.normalized()
                    self.selectedAreaRaw = QRect(self.selected_area)
                    self.startX, self.startY = event.x(), event.y()
                    self.redraw()
                elif self.mousePosition == MousePosition.ON_THE_LEFT_SIDE:
                    move_to_x = event.x(
                    ) - self.startX + self.selected_area.left()
                    if move_to_x <= self.selected_area.right():
                        self.selected_area.setLeft(move_to_x)
                        self.selected_area = self.selected_area.normalized()
                        self.redraw()
                elif self.mousePosition == MousePosition.ON_THE_RIGHT_SIDE:
                    move_to_x = event.x(
                    ) - self.startX + self.selected_area.right()
                    self.selected_area.setRight(move_to_x)
                    self.selected_area = self.selected_area.normalized()
                    self.redraw()
                elif self.mousePosition == MousePosition.ON_THE_UP_SIDE:
                    move_to_y = event.y(
                    ) - self.startY + self.selected_area.top()
                    self.selected_area.setTop(move_to_y)
                    self.selected_area = self.selected_area.normalized()
                    self.redraw()
                elif self.mousePosition == MousePosition.ON_THE_DOWN_SIDE:
                    move_to_y = event.y(
                    ) - self.startY + self.selected_area.bottom()
                    self.selected_area.setBottom(move_to_y)
                    self.selected_area = self.selected_area.normalized()
                    self.redraw()
                elif self.mousePosition == MousePosition.ON_THE_TOP_LEFT_CORNER:
                    move_to_x = event.x(
                    ) - self.startX + self.selected_area.left()
                    move_to_y = event.y(
                    ) - self.startY + self.selected_area.top()
                    self.selected_area.setTopLeft(QPoint(move_to_x, move_to_y))
                    self.selected_area = self.selected_area.normalized()
                    self.redraw()
                elif self.mousePosition == MousePosition.ON_THE_BOTTOM_RIGHT_CORNER:
                    move_to_x = event.x(
                    ) - self.startX + self.selected_area.right()
                    move_to_y = event.y(
                    ) - self.startY + self.selected_area.bottom()
                    self.selected_area.setBottomRight(
                        QPoint(move_to_x, move_to_y))
                    self.selected_area = self.selected_area.normalized()
                    self.redraw()
                elif self.mousePosition == MousePosition.ON_THE_TOP_RIGHT_CORNER:
                    move_to_x = event.x(
                    ) - self.startX + self.selected_area.right()
                    move_to_y = event.y(
                    ) - self.startY + self.selected_area.top()
                    self.selected_area.setTopRight(QPoint(
                        move_to_x, move_to_y))
                    self.selected_area = self.selected_area.normalized()
                    self.redraw()
                elif self.mousePosition == MousePosition.ON_THE_BOTTOM_LEFT_CORNER:
                    move_to_x = event.x(
                    ) - self.startX + self.selected_area.left()
                    move_to_y = event.y(
                    ) - self.startY + self.selected_area.bottom()
                    self.selected_area.setBottomLeft(
                        QPoint(move_to_x, move_to_y))
                    self.redraw()
                else:
                    pass
            elif self.action == ACTION_RECT:
                self.drawRect(self.startX, self.startY, event.x(), event.y(),
                              False)
                self.redraw()
                pass
            elif self.action == ACTION_ELLIPSE:
                self.drawEllipse(self.startX, self.startY, event.x(),
                                 event.y(), False)
                self.redraw()
            elif self.action == ACTION_ARROW:
                self.drawArrow(self.startX, self.startY, event.x(), event.y(),
                               False)
                self.redraw()
            elif self.action == ACTION_LINE:
                self.drawLine(self.startX, self.startY, event.x(), event.y(),
                              False)
                self.redraw()
            elif self.action == ACTION_FREEPEN:
                y1, y2 = event.x(), event.y()
                rect = self.selected_area.normalized()
                if y1 <= rect.left():
                    y1 = rect.left()
                elif y1 >= rect.right():
                    y1 = rect.right()

                if y2 <= rect.top():
                    y2 = rect.top()
                elif y2 >= rect.bottom():
                    y2 = rect.bottom()

                self.pointPath.lineTo(y1, y2)
                self.drawFreeLine(self.pointPath, False)
                self.redraw()

    def mouseReleaseEvent(self, event):
        """
        :type event: QMouseEvent
        :param event:
        :return:
        """
        if event.button() != Qt.LeftButton:
            return

        if self.mousePressed:
            self.mousePressed = False
            self.endX, self.endY = event.x(), event.y()

            if self.action == ACTION_SELECT:
                self.selected_area.setBottomRight(QPoint(event.x(), event.y()))
                self.selectedAreaRaw = QRect(self.selected_area)
                self.action = ACTION_MOVE_SELECTED
                self.redraw()
            elif self.action == ACTION_MOVE_SELECTED:
                self.selectedAreaRaw = QRect(self.selected_area)
                self.redraw()
                # self.action = None
            elif self.action == ACTION_RECT:
                self.drawRect(self.startX, self.startY, event.x(), event.y(),
                              True)
                self.redraw()
            elif self.action == ACTION_ELLIPSE:
                self.drawEllipse(self.startX, self.startY, event.x(),
                                 event.y(), True)
                self.redraw()
            elif self.action == ACTION_ARROW:
                self.drawArrow(self.startX, self.startY, event.x(), event.y(),
                               True)
                self.redraw()
            elif self.action == ACTION_LINE:
                self.drawLine(self.startX, self.startY, event.x(), event.y(),
                              True)
                self.redraw()
            elif self.action == ACTION_FREEPEN:
                self.drawFreeLine(self.pointPath, True)
                self.redraw()

    def detect_mouse_position(self, point):
        """
        :type point: QPoint
        :param point: the mouse position you want to check
        :return:
        """
        if self.selected_area == QRect():
            self.mousePosition = MousePosition.OUTSIDE_AREA
            return

        if self.selected_area.left() - ERRORRANGE <= point.x(
        ) <= self.selected_area.left() and (
                self.selected_area.top() - ERRORRANGE <= point.y() <=
                self.selected_area.top()):
            self.mousePosition = MousePosition.ON_THE_TOP_LEFT_CORNER
        elif self.selected_area.right() <= point.x(
        ) <= self.selected_area.right() + ERRORRANGE and (
                self.selected_area.top() - ERRORRANGE <= point.y() <=
                self.selected_area.top()):
            self.mousePosition = MousePosition.ON_THE_TOP_RIGHT_CORNER
        elif self.selected_area.left() - ERRORRANGE <= point.x(
        ) <= self.selected_area.left() and (
                self.selected_area.bottom() <= point.y() <=
                self.selected_area.bottom() + ERRORRANGE):
            self.mousePosition = MousePosition.ON_THE_BOTTOM_LEFT_CORNER
        elif self.selected_area.right() <= point.x(
        ) <= self.selected_area.right() + ERRORRANGE and (
                self.selected_area.bottom() <= point.y() <=
                self.selected_area.bottom() + ERRORRANGE):
            self.mousePosition = MousePosition.ON_THE_BOTTOM_RIGHT_CORNER
        elif -ERRORRANGE <= point.x() - self.selected_area.left() <= 0 and (
                self.selected_area.topLeft().y() < point.y() <
                self.selected_area.bottomLeft().y()):
            self.mousePosition = MousePosition.ON_THE_LEFT_SIDE
        elif 0 <= point.x() - self.selected_area.right() <= ERRORRANGE and (
                self.selected_area.topRight().y() < point.y() <
                self.selected_area.bottomRight().y()):
            self.mousePosition = MousePosition.ON_THE_RIGHT_SIDE
        elif -ERRORRANGE <= point.y() - self.selected_area.top() <= 0 and (
                self.selected_area.topLeft().x() < point.x() <
                self.selected_area.topRight().x()):
            self.mousePosition = MousePosition.ON_THE_UP_SIDE
        elif 0 <= point.y() - self.selected_area.bottom() <= ERRORRANGE and (
                self.selected_area.bottomLeft().x() < point.x() <
                self.selected_area.bottomRight().x()):
            self.mousePosition = MousePosition.ON_THE_DOWN_SIDE
        elif not self.selected_area.contains(point):
            self.mousePosition = MousePosition.OUTSIDE_AREA
        else:
            self.mousePosition = MousePosition.INSIDE_AREA

    def setCursorStyle(self):
        if self.action in DRAW_ACTION:
            self.setCursor(Qt.CrossCursor)
            return

        if self.mousePosition == MousePosition.ON_THE_LEFT_SIDE or \
                self.mousePosition == MousePosition.ON_THE_RIGHT_SIDE:

            self.setCursor(Qt.SizeHorCursor)
        elif self.mousePosition == MousePosition.ON_THE_UP_SIDE or \
                self.mousePosition == MousePosition.ON_THE_DOWN_SIDE:

            self.setCursor(Qt.SizeVerCursor)
        elif self.mousePosition == MousePosition.ON_THE_TOP_LEFT_CORNER or \
                self.mousePosition == MousePosition.ON_THE_BOTTOM_RIGHT_CORNER:

            self.setCursor(Qt.SizeFDiagCursor)
        elif self.mousePosition == MousePosition.ON_THE_TOP_RIGHT_CORNER or \
                self.mousePosition == MousePosition.ON_THE_BOTTOM_LEFT_CORNER:

            self.setCursor(Qt.SizeBDiagCursor)
        elif self.mousePosition == MousePosition.OUTSIDE_AREA:
            self.setCursor(Qt.ArrowCursor)
        elif self.mousePosition == MousePosition.INSIDE_AREA:
            self.setCursor(Qt.OpenHandCursor)
        else:
            self.setCursor(Qt.ArrowCursor)
            pass

    def drawMagnifier(self):
        # First, calculate the magnifier position due to the mouse position
        watch_area_width = 16
        watch_area_height = 16

        cursor_pos = self.mousePoint

        watch_area = QRect(
            QPoint(cursor_pos.x() - watch_area_width / 2,
                   cursor_pos.y() - watch_area_height / 2),
            QPoint(cursor_pos.x() + watch_area_width / 2,
                   cursor_pos.y() + watch_area_height / 2))
        if watch_area.left() < 0:
            watch_area.moveLeft(0)
            watch_area.moveRight(watch_area_width)
        if self.mousePoint.x(
        ) + watch_area_width / 2 >= self.screenPixel.width():
            watch_area.moveRight(self.screenPixel.width() - 1)
            watch_area.moveLeft(watch_area.right() - watch_area_width)
        if self.mousePoint.y() - watch_area_height / 2 < 0:
            watch_area.moveTop(0)
            watch_area.moveBottom(watch_area_height)
        if self.mousePoint.y(
        ) + watch_area_height / 2 >= self.screenPixel.height():
            watch_area.moveBottom(self.screenPixel.height() - 1)
            watch_area.moveTop(watch_area.bottom() - watch_area_height)

        # tricks to solve the hidpi impact on QCursor.pos()
        watch_area.setTopLeft(
            QPoint(watch_area.topLeft().x() * self.scale,
                   watch_area.topLeft().y() * self.scale))
        watch_area.setBottomRight(
            QPoint(watch_area.bottomRight().x() * self.scale,
                   watch_area.bottomRight().y() * self.scale))
        watch_area_pixmap = self.screenPixel.copy(watch_area)

        # second, calculate the magnifier area
        magnifier_area_width = watch_area_width * 10
        magnifier_area_height = watch_area_height * 10
        font_area_height = 40

        cursor_size = 24
        magnifier_area = QRectF(
            QPoint(QCursor.pos().x() + cursor_size,
                   QCursor.pos().y() + cursor_size),
            QPoint(QCursor.pos().x() + cursor_size + magnifier_area_width,
                   QCursor.pos().y() + cursor_size + magnifier_area_height))
        if magnifier_area.right() >= self.screenPixel.width():
            magnifier_area.moveLeft(QCursor.pos().x() - magnifier_area_width -
                                    cursor_size / 2)
        if magnifier_area.bottom(
        ) + font_area_height >= self.screenPixel.height():
            magnifier_area.moveTop(QCursor.pos().y() - magnifier_area_height -
                                   cursor_size / 2 - font_area_height)

        # third, draw the watch area to magnifier area
        watch_area_scaled = watch_area_pixmap.scaled(
            QSize(magnifier_area_width * self.scale,
                  magnifier_area_height * self.scale))
        magnifier_pixmap = self.graphics_scene.addPixmap(watch_area_scaled)
        magnifier_pixmap.setOffset(magnifier_area.topLeft())

        # then draw lines and text
        self.graphics_scene.addRect(QRectF(magnifier_area),
                                    QPen(QColor(255, 255, 255), 2))
        self.graphics_scene.addLine(
            QLineF(
                QPointF(magnifier_area.center().x(), magnifier_area.top()),
                QPointF(magnifier_area.center().x(), magnifier_area.bottom())),
            QPen(QColor(0, 255, 255), 2))
        self.graphics_scene.addLine(
            QLineF(
                QPointF(magnifier_area.left(),
                        magnifier_area.center().y()),
                QPointF(magnifier_area.right(),
                        magnifier_area.center().y())),
            QPen(QColor(0, 255, 255), 2))

        # get the rgb of mouse point
        point_rgb = QColor(self.screenPixel.toImage().pixel(self.mousePoint))

        # draw information
        self.graphics_scene.addRect(
            QRectF(
                magnifier_area.bottomLeft(),
                magnifier_area.bottomRight() +
                QPoint(0, font_area_height + 30)), QPen(Qt.black),
            QBrush(Qt.black))
        rgb_info = self.graphics_scene.addSimpleText(
            ' Rgb: ({0}, {1}, {2})'.format(point_rgb.red(), point_rgb.green(),
                                           point_rgb.blue()))
        rgb_info.setPos(magnifier_area.bottomLeft() + QPoint(0, 5))
        rgb_info.setPen(QPen(QColor(255, 255, 255), 2))

        rect = self.selected_area.normalized()
        size_info = self.graphics_scene.addSimpleText(
            ' Size: {0} x {1}'.format(rect.width() * self.scale,
                                      rect.height() * self.scale))
        size_info.setPos(magnifier_area.bottomLeft() + QPoint(0, 15) +
                         QPoint(0, font_area_height / 2))
        size_info.setPen(QPen(QColor(255, 255, 255), 2))

    def get_scale(self):
        return self.devicePixelRatio()

    def saveScreenshot(self,
                       clipboard=False,
                       fileName='screenshot.png',
                       picType='png'):
        fullWindow = QRect(0, 0, self.width() - 1, self.height() - 1)
        selected = QRect(self.selected_area)
        if selected.left() < 0:
            selected.setLeft(0)
        if selected.right() >= self.width():
            selected.setRight(self.width() - 1)
        if selected.top() < 0:
            selected.setTop(0)
        if selected.bottom() >= self.height():
            selected.setBottom(self.height() - 1)

        source = (fullWindow & selected)
        source.setTopLeft(
            QPoint(source.topLeft().x() * self.scale,
                   source.topLeft().y() * self.scale))
        source.setBottomRight(
            QPoint(source.bottomRight().x() * self.scale,
                   source.bottomRight().y() * self.scale))
        image = self.screenPixel.copy(source)

        if clipboard:
            QGuiApplication.clipboard().setImage(image.toImage(),
                                                 QClipboard.Clipboard)
        else:
            image.save(fileName, picType, 10)
        self.target_img = image
        self.target_img_pos = source
        self.screen_shot_grabed.emit(image.toImage())
        self.screen_shot_pos_grabed.emit(source)

    def redraw(self):
        self.graphics_scene.clear()

        # draw screenshot
        self.graphics_scene.addPixmap(self.screenPixel)

        # prepare for drawing selected area
        rect = QRectF(self.selected_area)
        rect = rect.normalized()

        top_left_point = rect.topLeft()
        top_right_point = rect.topRight()
        bottom_left_point = rect.bottomLeft()
        bottom_right_point = rect.bottomRight()
        top_middle_point = (top_left_point + top_right_point) / 2
        left_middle_point = (top_left_point + bottom_left_point) / 2
        bottom_middle_point = (bottom_left_point + bottom_right_point) / 2
        right_middle_point = (top_right_point + bottom_right_point) / 2

        # draw the picture mask
        mask = QColor(0, 0, 0, 155)

        if self.selected_area == QRect():
            self.graphics_scene.addRect(0, 0, self.screenPixel.width(),
                                        self.screenPixel.height(),
                                        QPen(Qt.NoPen), mask)
        else:
            self.graphics_scene.addRect(0, 0, self.screenPixel.width(),
                                        top_right_point.y(), QPen(Qt.NoPen),
                                        mask)
            self.graphics_scene.addRect(0, top_left_point.y(),
                                        top_left_point.x(), rect.height(),
                                        QPen(Qt.NoPen), mask)
            self.graphics_scene.addRect(
                top_right_point.x(), top_right_point.y(),
                self.screenPixel.width() - top_right_point.x(), rect.height(),
                QPen(Qt.NoPen), mask)
            self.graphics_scene.addRect(
                0, bottom_left_point.y(), self.screenPixel.width(),
                self.screenPixel.height() - bottom_left_point.y(),
                QPen(Qt.NoPen), mask)

        # draw the toolBar
        if self.action != ACTION_SELECT:
            spacing = 5
            # show the toolbar first, then move it to the correct position
            # because the width of it may be wrong if this is the first time it shows
            self.tooBar.show()

            dest = QPointF(rect.bottomRight() -
                           QPointF(self.tooBar.width(), 0) -
                           QPointF(spacing, -spacing))
            if dest.x() < spacing:
                dest.setX(spacing)
            pen_set_bar_height = self.penSetBar.height(
            ) if self.penSetBar is not None else 0
            if dest.y() + self.tooBar.height(
            ) + pen_set_bar_height >= self.height():
                if rect.top() - self.tooBar.height(
                ) - pen_set_bar_height < spacing:
                    dest.setY(rect.top() + spacing)
                else:
                    dest.setY(rect.top() - self.tooBar.height() -
                              pen_set_bar_height - spacing)

            self.tooBar.move(dest.toPoint())

            if self.penSetBar is not None:
                self.penSetBar.show()
                self.penSetBar.move(dest.toPoint() +
                                    QPoint(0,
                                           self.tooBar.height() + spacing))

                if self.action == ACTION_TEXT:
                    self.penSetBar.showFontWidget()
                else:
                    self.penSetBar.showPenWidget()
        else:
            self.tooBar.hide()

            if self.penSetBar is not None:
                self.penSetBar.hide()

        # draw the list
        for step in self.drawListResult:
            self.drawOneStep(step)

        if self.drawListProcess is not None:
            self.drawOneStep(self.drawListProcess)
            if self.action != ACTION_TEXT:
                self.drawListProcess = None

        if self.selected_area != QRect():
            self.items_to_remove = []

            # draw the selected rectangle
            pen = QPen(QColor(0, 255, 255), 2)
            self.items_to_remove.append(self.graphics_scene.addRect(rect, pen))

            # draw the drag point
            radius = QPoint(3, 3)
            brush = QBrush(QColor(0, 255, 255))
            self.items_to_remove.append(
                self.graphics_scene.addEllipse(
                    QRectF(top_left_point - radius, top_left_point + radius),
                    pen, brush))
            self.items_to_remove.append(
                self.graphics_scene.addEllipse(
                    QRectF(top_middle_point - radius,
                           top_middle_point + radius), pen, brush))
            self.items_to_remove.append(
                self.graphics_scene.addEllipse(
                    QRectF(top_right_point - radius, top_right_point + radius),
                    pen, brush))
            self.items_to_remove.append(
                self.graphics_scene.addEllipse(
                    QRectF(left_middle_point - radius,
                           left_middle_point + radius), pen, brush))
            self.items_to_remove.append(
                self.graphics_scene.addEllipse(
                    QRectF(right_middle_point - radius,
                           right_middle_point + radius), pen, brush))
            self.items_to_remove.append(
                self.graphics_scene.addEllipse(
                    QRectF(bottom_left_point - radius,
                           bottom_left_point + radius), pen, brush))
            self.items_to_remove.append(
                self.graphics_scene.addEllipse(
                    QRectF(bottom_middle_point - radius,
                           bottom_middle_point + radius), pen, brush))
            self.items_to_remove.append(
                self.graphics_scene.addEllipse(
                    QRectF(bottom_right_point - radius,
                           bottom_right_point + radius), pen, brush))

        # draw the textedit
        if self.textPosition is not None:
            textSpacing = 50
            position = QPoint()
            if self.textPosition.x() + self.textInput.width(
            ) >= self.screenPixel.width():
                position.setX(self.textPosition.x() - self.textInput.width())
            else:
                position.setX(self.textPosition.x())

            if self.textRect is not None:
                if self.textPosition.y() + self.textInput.height(
                ) + self.textRect.height() >= self.screenPixel.height():
                    position.setY(self.textPosition.y() -
                                  self.textInput.height() -
                                  self.textRect.height())
                else:
                    position.setY(self.textPosition.y() +
                                  self.textRect.height())
            else:
                if self.textPosition.y() + self.textInput.height(
                ) >= self.screenPixel.height():
                    position.setY(self.textPosition.y() -
                                  self.textInput.height())
                else:
                    position.setY(self.textPosition.y())

            self.textInput.move(position)
            self.textInput.show()
            # self.textInput.getFocus()

        # draw the magnifier
        if self.action == ACTION_SELECT:
            self.drawMagnifier()
            if self.mousePressed:
                self.drawSizeInfo()

        if self.action == ACTION_MOVE_SELECTED:
            self.drawSizeInfo()

    # deal with every step in drawList
    def drawOneStep(self, step):
        """
        :type step: tuple
        """
        if step[0] == ACTION_RECT:
            self.graphics_scene.addRect(
                QRectF(QPointF(step[1], step[2]), QPointF(step[3], step[4])),
                step[5])
        elif step[0] == ACTION_ELLIPSE:
            self.graphics_scene.addEllipse(
                QRectF(QPointF(step[1], step[2]), QPointF(step[3], step[4])),
                step[5])
        elif step[0] == ACTION_ARROW:
            arrow = QPolygonF()

            linex = float(step[1] - step[3])
            liney = float(step[2] - step[4])
            line = sqrt(pow(linex, 2) + pow(liney, 2))

            # in case to divided by 0
            if line == 0:
                return

            sinAngel = liney / line
            cosAngel = linex / line

            # sideLength is the length of bottom side of the body of an arrow
            # arrowSize is the size of the head of an arrow, left and right
            # sides' size is arrowSize, and the bottom side's size is arrowSize / 2
            sideLength = step[5].width()
            arrowSize = 8
            bottomSize = arrowSize / 2

            tmpPoint = QPointF(step[3] + arrowSize * sideLength * cosAngel,
                               step[4] + arrowSize * sideLength * sinAngel)

            point1 = QPointF(step[1] + sideLength * sinAngel,
                             step[2] - sideLength * cosAngel)
            point2 = QPointF(step[1] - sideLength * sinAngel,
                             step[2] + sideLength * cosAngel)
            point3 = QPointF(tmpPoint.x() - sideLength * sinAngel,
                             tmpPoint.y() + sideLength * cosAngel)
            point4 = QPointF(tmpPoint.x() - bottomSize * sideLength * sinAngel,
                             tmpPoint.y() + bottomSize * sideLength * cosAngel)
            point5 = QPointF(step[3], step[4])
            point6 = QPointF(tmpPoint.x() + bottomSize * sideLength * sinAngel,
                             tmpPoint.y() - bottomSize * sideLength * cosAngel)
            point7 = QPointF(tmpPoint.x() + sideLength * sinAngel,
                             tmpPoint.y() - sideLength * cosAngel)

            arrow.append(point1)
            arrow.append(point2)
            arrow.append(point3)
            arrow.append(point4)
            arrow.append(point5)
            arrow.append(point6)
            arrow.append(point7)
            arrow.append(point1)

            self.graphics_scene.addPolygon(arrow, step[5], step[6])
        elif step[0] == ACTION_LINE:
            self.graphics_scene.addLine(
                QLineF(QPointF(step[1], step[2]), QPointF(step[3], step[4])),
                step[5])
        elif step[0] == ACTION_FREEPEN:
            self.graphics_scene.addPath(step[1], step[2])
        elif step[0] == ACTION_TEXT:
            textAdd = self.graphics_scene.addSimpleText(step[1], step[2])
            textAdd.setPos(step[3])
            textAdd.setBrush(QBrush(step[4]))
            self.textRect = textAdd.boundingRect()

    # draw the size information on the top left corner
    def drawSizeInfo(self):
        sizeInfoAreaWidth = 200
        sizeInfoAreaHeight = 30
        spacing = 5
        rect = self.selected_area.normalized()
        sizeInfoArea = QRect(rect.left(),
                             rect.top() - spacing - sizeInfoAreaHeight,
                             sizeInfoAreaWidth, sizeInfoAreaHeight)

        if sizeInfoArea.top() < 0:
            sizeInfoArea.moveTopLeft(rect.topLeft() + QPoint(spacing, spacing))
        if sizeInfoArea.right() >= self.screenPixel.width():
            sizeInfoArea.moveTopLeft(rect.topLeft() -
                                     QPoint(spacing, spacing) -
                                     QPoint(sizeInfoAreaWidth, 0))
        if sizeInfoArea.left() < spacing:
            sizeInfoArea.moveLeft(spacing)
        if sizeInfoArea.top() < spacing:
            sizeInfoArea.moveTop(spacing)

        self.items_to_remove.append(
            self.graphics_scene.addRect(QRectF(sizeInfoArea), QPen(Qt.white),
                                        QBrush(Qt.black)))

        sizeInfo = self.graphics_scene.addSimpleText('  {0} x {1}'.format(
            rect.width() * self.scale,
            rect.height() * self.scale))
        sizeInfo.setPos(sizeInfoArea.topLeft() + QPoint(0, 2))
        sizeInfo.setPen(QPen(QColor(255, 255, 255), 2))
        self.items_to_remove.append(sizeInfo)

    def drawRect(self, x1, x2, y1, y2, result):
        rect = self.selected_area.normalized()
        tmpRect = QRect(QPoint(x1, x2), QPoint(y1, y2)).normalized()
        resultRect = rect & tmpRect
        tmp = [
            ACTION_RECT,
            resultRect.topLeft().x(),
            resultRect.topLeft().y(),
            resultRect.bottomRight().x(),
            resultRect.bottomRight().y(),
            QPen(QColor(self.penColorNow), int(self.penSizeNow))
        ]
        if result:
            self.drawListResult.append(tmp)
        else:
            self.drawListProcess = tmp

    def drawEllipse(self, x1, x2, y1, y2, result):
        rect = self.selected_area.normalized()
        tmpRect = QRect(QPoint(x1, x2), QPoint(y1, y2)).normalized()
        resultRect = rect & tmpRect
        tmp = [
            ACTION_ELLIPSE,
            resultRect.topLeft().x(),
            resultRect.topLeft().y(),
            resultRect.bottomRight().x(),
            resultRect.bottomRight().y(),
            QPen(QColor(self.penColorNow), int(self.penSizeNow))
        ]
        if result:
            self.drawListResult.append(tmp)
        else:
            self.drawListProcess = tmp

    def drawArrow(self, x1, x2, y1, y2, result):
        rect = self.selected_area.normalized()
        if y1 <= rect.left():
            y1 = rect.left()
        elif y1 >= rect.right():
            y1 = rect.right()

        if y2 <= rect.top():
            y2 = rect.top()
        elif y2 >= rect.bottom():
            y2 = rect.bottom()

        tmp = [
            ACTION_ARROW, x1, x2, y1, y2,
            QPen(QColor(self.penColorNow), int(self.penSizeNow)),
            QBrush(QColor(self.penColorNow))
        ]
        if result:
            self.drawListResult.append(tmp)
        else:
            self.drawListProcess = tmp

    def drawLine(self, x1, x2, y1, y2, result):
        rect = self.selected_area.normalized()
        if y1 <= rect.left():
            y1 = rect.left()
        elif y1 >= rect.right():
            y1 = rect.right()

        if y2 <= rect.top():
            y2 = rect.top()
        elif y2 >= rect.bottom():
            y2 = rect.bottom()

        tmp = [
            ACTION_LINE, x1, x2, y1, y2,
            QPen(QColor(self.penColorNow), int(self.penSizeNow))
        ]
        if result:
            self.drawListResult.append(tmp)
        else:
            self.drawListProcess = tmp

    def drawFreeLine(self, pointPath, result):
        tmp = [
            ACTION_FREEPEN,
            QPainterPath(pointPath),
            QPen(QColor(self.penColorNow), int(self.penSizeNow))
        ]
        if result:
            self.drawListResult.append(tmp)
        else:
            self.drawListProcess = tmp

    def textChange(self):
        if self.textPosition is None:
            return
        self.text = self.textInput.getText()
        self.drawListProcess = [
            ACTION_TEXT,
            str(self.text),
            QFont(self.fontNow),
            QPoint(self.textPosition),
            QColor(self.penColorNow)
        ]
        self.redraw()

    def undoOperation(self):
        if len(self.drawListResult) == 0:
            self.action = ACTION_SELECT
            self.selected_area = QRect()
            self.selectedAreaRaw = QRect()
            self.tooBar.hide()
            if self.penSetBar is not None:
                self.penSetBar.hide()
        else:
            self.drawListResult.pop()
        self.redraw()

    def saveOperation(self):
        filename = QFileDialog.getSaveFileName(self, 'Save file',
                                               './screenshot.png',
                                               '*.png;;*.jpg')
        if len(filename[0]) == 0:
            return
        else:
            self.saveScreenshot(False, filename[0], filename[1][2:])
            self.close()

    def close(self):
        self.widget_closed.emit()
        super().close()
        self.tooBar.close()
        if self.penSetBar is not None:
            self.penSetBar.close()

    def saveToClipboard(self):
        QApplication.clipboard().setText('Test in save function')

        self.saveScreenshot(True)
        self.close()

    # slots
    def changeAction(self, nextAction):
        QApplication.clipboard().setText('Test in changeAction function')

        if nextAction == ACTION_UNDO:
            self.undoOperation()
        elif nextAction == ACTION_SAVE:
            self.saveOperation()
        elif nextAction == ACTION_CANCEL:
            self.close()
        elif nextAction == ACTION_SURE:
            self.saveToClipboard()

        else:
            self.action = nextAction

        self.setFocus()

    def changePenSize(self, nextPenSize):
        self.penSizeNow = nextPenSize

    def changePenColor(self, nextPenColor):
        self.penColorNow = nextPenColor

    def cancelInput(self):
        self.drawListProcess = None
        self.textPosition = None
        self.textRect = None
        self.textInput.hide()
        self.textInput.clearText()
        self.redraw()

    def okInput(self):
        self.text = self.textInput.getText()
        self.drawListResult.append([
            ACTION_TEXT,
            str(self.text),
            QFont(self.fontNow),
            QPoint(self.textPosition),
            QColor(self.penColorNow)
        ])
        self.textPosition = None
        self.textRect = None
        self.textInput.hide()
        self.textInput.clearText()
        self.redraw()

    def changeFont(self, font):
        self.fontNow = font