示例#1
0
    def _update_overlay_geometry(self, widget, centered, padding):
        """Reposition/resize the given overlay."""
        if not widget.isVisible():
            return

        size_hint = widget.sizeHint()
        if widget.sizePolicy().horizontalPolicy() == QSizePolicy.Expanding:
            width = self.width() - 2 * padding
            left = padding
        else:
            width = size_hint.width()
            left = (self.width() - size_hint.width()) / 2 if centered else 0

        height_padding = 20
        status_position = config.get('ui', 'status-position')
        if status_position == 'bottom':
            top = self.height() - self.status.height() - size_hint.height()
            top = qtutils.check_overflow(top, 'int', fatal=False)
            topleft = QPoint(left, max(height_padding, top))
            bottomright = QPoint(left + width, self.status.geometry().top())
        elif status_position == 'top':
            topleft = QPoint(left, self.status.geometry().bottom())
            bottom = self.status.height() + size_hint.height()
            bottom = qtutils.check_overflow(bottom, 'int', fatal=False)
            bottomright = QPoint(left + width,
                                 min(self.height() - height_padding, bottom))
        else:
            raise ValueError("Invalid position {}!".format(status_position))

        rect = QRect(topleft, bottomright)
        log.misc.debug('new geometry for {!r}: {}'.format(widget, rect))
        if rect.isValid():
            widget.setGeometry(rect)
示例#2
0
 def resize_completion(self):
     """Adjust completion according to config."""
     if not self._completion.isVisible():
         # It doesn't make sense to resize the completion as long as it's
         # not shown anyways.
         return
     # Get the configured height/percentage.
     confheight = str(config.get("completion", "height"))
     if confheight.endswith("%"):
         perc = int(confheight.rstrip("%"))
         height = self.height() * perc / 100
     else:
         height = int(confheight)
     # Shrink to content size if needed and shrinking is enabled
     if config.get("completion", "shrink"):
         contents_height = (
             self._completion.viewportSizeHint().height()
             + self._completion.horizontalScrollBar().sizeHint().height()
         )
         if contents_height <= height:
             height = contents_height
     else:
         contents_height = -1
     # hpoint now would be the bottom-left edge of the widget if it was on
     # the top of the main window.
     topleft_y = self.height() - self.status.height() - height
     topleft_y = qtutils.check_overflow(topleft_y, "int", fatal=False)
     topleft = QPoint(0, topleft_y)
     bottomright = self.status.geometry().topRight()
     rect = QRect(topleft, bottomright)
     log.misc.debug("completion rect: {}".format(rect))
     if rect.isValid():
         self._completion.setGeometry(rect)
示例#3
0
 def test_invalid_frame_geometry(self, stubs):
     """Test with an invalid frame geometry."""
     rect = QRect(0, 0, 0, 0)
     assert not rect.isValid()
     frame = stubs.FakeWebFrame(rect)
     elem = get_webelem(QRect(0, 0, 10, 10), frame)
     assert not elem._is_visible(frame)
示例#4
0
class FrameWidget(QWidget):
    # 一个全屏的透明窗口

    def __init__(self, *args, **kwargs):
        super(FrameWidget, self).__init__(*args, **kwargs)
        self.setWindowFlags(Qt.Tool | Qt.FramelessWindowHint
                            | Qt.WindowStaysOnTopHint)
        self.setAttribute(Qt.WA_TranslucentBackground, True)
        self.showFullScreen()  # 全屏
        self._rect = QRect()  # 被探测的窗口的矩形位置

    def setRect(self, x, y, w, h):
        # 更新矩形框
        self._rect.setX(x)
        self._rect.setY(y)
        self._rect.setWidth(w - x)
        self._rect.setHeight(h - y)
        self.update()

    def paintEvent(self, event):
        super(FrameWidget, self).paintEvent(event)
        if self._rect.isValid():  # 画边框
            painter = QPainter(self)
            painter.setPen(QPen(Qt.red, 4))
            painter.drawRect(self._rect)
示例#5
0
 def resize_completion(self):
     """Adjust completion according to config."""
     # Get the configured height/percentage.
     confheight = str(config.get('completion', 'height'))
     if confheight.endswith('%'):
         perc = int(confheight.rstrip('%'))
         height = self.height() * perc / 100
     else:
         height = int(confheight)
     # Shrink to content size if needed and shrinking is enabled
     if config.get('completion', 'shrink'):
         contents_height = (
             self._completion.viewportSizeHint().height() +
             self._completion.horizontalScrollBar().sizeHint().height())
         if contents_height <= height:
             height = contents_height
     else:
         contents_height = -1
     # hpoint now would be the bottom-left edge of the widget if it was on
     # the top of the main window.
     topleft_y = self.height() - self.status.height() - height
     topleft_y = qtutils.check_overflow(topleft_y, 'int', fatal=False)
     topleft = QPoint(0, topleft_y)
     bottomright = self.status.geometry().topRight()
     rect = QRect(topleft, bottomright)
     if rect.isValid():
         self._completion.setGeometry(rect)
示例#6
0
 def test_invalid_frame_geometry(self, stubs):
     """Test with an invalid frame geometry."""
     rect = QRect(0, 0, 0, 0)
     assert not rect.isValid()
     frame = stubs.FakeWebFrame(rect)
     elem = get_webelem(QRect(0, 0, 10, 10), frame)
     assert not elem.is_visible(frame)
示例#7
0
 def resize_completion(self):
     """Adjust completion according to config."""
     if not self._completion.isVisible():
         # It doesn't make sense to resize the completion as long as it's
         # not shown anyways.
         return
     # Get the configured height/percentage.
     confheight = str(config.get('completion', 'height'))
     if confheight.endswith('%'):
         perc = int(confheight.rstrip('%'))
         height = self.height() * perc / 100
     else:
         height = int(confheight)
     # Shrink to content size if needed and shrinking is enabled
     if config.get('completion', 'shrink'):
         contents_height = (
             self._completion.viewportSizeHint().height() +
             self._completion.horizontalScrollBar().sizeHint().height())
         if contents_height <= height:
             height = contents_height
     else:
         contents_height = -1
     # hpoint now would be the bottom-left edge of the widget if it was on
     # the top of the main window.
     topleft_y = self.height() - self.status.height() - height
     topleft_y = qtutils.check_overflow(topleft_y, 'int', fatal=False)
     topleft = QPoint(0, topleft_y)
     bottomright = self.status.geometry().topRight()
     rect = QRect(topleft, bottomright)
     log.misc.debug('completion rect: {}'.format(rect))
     if rect.isValid():
         self._completion.setGeometry(rect)
示例#8
0
def is_visible(elem, mainframe):
    """Check if the given element is visible in the frame.

    We need this as a standalone function (as opposed to a WebElementWrapper
    method) because we want to check this before wrapping when hinting for
    performance reasons.

    Args:
        elem: The QWebElement to check.
        mainframe: The QWebFrame in which the element should be visible.
    """
    if elem.isNull():
        raise IsNullError("Got called on a null element!")
    # CSS attributes which hide an element
    hidden_attributes = {
        'visibility': 'hidden',
        'display': 'none',
    }
    for k, v in hidden_attributes.items():
        if elem.styleProperty(k, QWebElement.ComputedStyle) == v:
            return False
    elem_geometry = elem.geometry()
    if not elem_geometry.isValid() and elem_geometry.x() == 0:
        # Most likely an invisible link
        return False
    # First check if the element is visible on screen
    elem_rect = rect_on_view(elem, elem_geometry=elem_geometry)
    mainframe_geometry = mainframe.geometry()
    if elem_rect.isValid():
        visible_on_screen = mainframe_geometry.intersects(elem_rect)
    else:
        # We got an invalid rectangle (width/height 0/0 probably), but this
        # can still be a valid link.
        visible_on_screen = mainframe_geometry.contains(
            elem_rect.topLeft())
    # Then check if it's visible in its frame if it's not in the main
    # frame.
    elem_frame = elem.webFrame()
    framegeom = QRect(elem_frame.geometry())
    if not framegeom.isValid():
        visible_in_frame = False
    elif elem_frame.parentFrame() is not None:
        framegeom.moveTo(0, 0)
        framegeom.translate(elem_frame.scrollPosition())
        if elem_geometry.isValid():
            visible_in_frame = framegeom.intersects(elem_geometry)
        else:
            # We got an invalid rectangle (width/height 0/0 probably), but
            # this can still be a valid link.
            visible_in_frame = framegeom.contains(elem_geometry.topLeft())
    else:
        visible_in_frame = visible_on_screen
    return all([visible_on_screen, visible_in_frame])
示例#9
0
def is_visible(elem, mainframe):
    """Check if the given element is visible in the frame.

    We need this as a standalone function (as opposed to a WebElementWrapper
    method) because we want to check this before wrapping when hinting for
    performance reasons.

    Args:
        elem: The QWebElement to check.
        mainframe: The QWebFrame in which the element should be visible.
    """
    if elem.isNull():
        raise IsNullError("Got called on a null element!")
    # CSS attributes which hide an element
    hidden_attributes = {
        'visibility': 'hidden',
        'display': 'none',
    }
    for k, v in hidden_attributes.items():
        if elem.styleProperty(k, QWebElement.ComputedStyle) == v:
            return False
    elem_geometry = elem.geometry()
    if not elem_geometry.isValid() and elem_geometry.x() == 0:
        # Most likely an invisible link
        return False
    # First check if the element is visible on screen
    elem_rect = rect_on_view(elem, elem_geometry=elem_geometry)
    mainframe_geometry = mainframe.geometry()
    if elem_rect.isValid():
        visible_on_screen = mainframe_geometry.intersects(elem_rect)
    else:
        # We got an invalid rectangle (width/height 0/0 probably), but this
        # can still be a valid link.
        visible_on_screen = mainframe_geometry.contains(
            elem_rect.topLeft())
    # Then check if it's visible in its frame if it's not in the main
    # frame.
    elem_frame = elem.webFrame()
    framegeom = QRect(elem_frame.geometry())
    if not framegeom.isValid():
        visible_in_frame = False
    elif elem_frame.parentFrame() is not None:
        framegeom.moveTo(0, 0)
        framegeom.translate(elem_frame.scrollPosition())
        if elem_geometry.isValid():
            visible_in_frame = framegeom.intersects(elem_geometry)
        else:
            # We got an invalid rectangle (width/height 0/0 probably), but
            # this can still be a valid link.
            visible_in_frame = framegeom.contains(elem_geometry.topLeft())
    else:
        visible_in_frame = visible_on_screen
    return all([visible_on_screen, visible_in_frame])
示例#10
0
    def _is_visible(self, mainframe: QWebFrame) -> bool:
        """Check if the given element is visible in the given frame.

        This is not public API because it can't be implemented easily here with
        QtWebEngine, and is only used via find_css(..., only_visible=True) via
        the tab API.
        """
        self._check_vanished()
        # CSS attributes which hide an element
        hidden_attributes = {
            'visibility': 'hidden',
            'display': 'none',
            'opacity': '0',
        }
        for k, v in hidden_attributes.items():
            if (self._elem.styleProperty(k, QWebElement.ComputedStyle) == v and
                    'ace_text-input' not in self.classes()):
                return False

        elem_geometry = self._elem.geometry()
        if not elem_geometry.isValid() and elem_geometry.x() == 0:
            # Most likely an invisible link
            return False
        # First check if the element is visible on screen
        elem_rect = self.rect_on_view(elem_geometry=elem_geometry)
        mainframe_geometry = mainframe.geometry()
        if elem_rect.isValid():
            visible_on_screen = mainframe_geometry.intersects(elem_rect)
        else:
            # We got an invalid rectangle (width/height 0/0 probably), but this
            # can still be a valid link.
            visible_on_screen = mainframe_geometry.contains(
                elem_rect.topLeft())
        # Then check if it's visible in its frame if it's not in the main
        # frame.
        elem_frame = self._elem.webFrame()
        framegeom = QRect(elem_frame.geometry())
        if not framegeom.isValid():
            visible_in_frame = False
        elif elem_frame.parentFrame() is not None:
            framegeom.moveTo(0, 0)
            framegeom.translate(elem_frame.scrollPosition())
            if elem_geometry.isValid():
                visible_in_frame = framegeom.intersects(elem_geometry)
            else:
                # We got an invalid rectangle (width/height 0/0 probably), but
                # this can still be a valid link.
                visible_in_frame = framegeom.contains(elem_geometry.topLeft())
        else:
            visible_in_frame = visible_on_screen
        return all([visible_on_screen, visible_in_frame])
示例#11
0
    def _is_visible(self, mainframe):
        """Check if the given element is visible in the given frame.

        This is not public API because it can't be implemented easily here with
        QtWebEngine, and is only used via find_css(..., only_visible=True) via
        the tab API.
        """
        self._check_vanished()
        # CSS attributes which hide an element
        hidden_attributes = {
            'visibility': 'hidden',
            'display': 'none',
            'opacity': '0',
        }
        for k, v in hidden_attributes.items():
            if (self._elem.styleProperty(k, QWebElement.ComputedStyle) == v and
                    'ace_text-input' not in self.classes()):
                return False

        elem_geometry = self._elem.geometry()
        if not elem_geometry.isValid() and elem_geometry.x() == 0:
            # Most likely an invisible link
            return False
        # First check if the element is visible on screen
        elem_rect = self.rect_on_view(elem_geometry=elem_geometry)
        mainframe_geometry = mainframe.geometry()
        if elem_rect.isValid():
            visible_on_screen = mainframe_geometry.intersects(elem_rect)
        else:
            # We got an invalid rectangle (width/height 0/0 probably), but this
            # can still be a valid link.
            visible_on_screen = mainframe_geometry.contains(
                elem_rect.topLeft())
        # Then check if it's visible in its frame if it's not in the main
        # frame.
        elem_frame = self._elem.webFrame()
        framegeom = QRect(elem_frame.geometry())
        if not framegeom.isValid():
            visible_in_frame = False
        elif elem_frame.parentFrame() is not None:
            framegeom.moveTo(0, 0)
            framegeom.translate(elem_frame.scrollPosition())
            if elem_geometry.isValid():
                visible_in_frame = framegeom.intersects(elem_geometry)
            else:
                # We got an invalid rectangle (width/height 0/0 probably), but
                # this can still be a valid link.
                visible_in_frame = framegeom.contains(elem_geometry.topLeft())
        else:
            visible_in_frame = visible_on_screen
        return all([visible_on_screen, visible_in_frame])
示例#12
0
 def paintSection(self, painter: QPainter, rect: QRect, logicalIndex: int):
     if rect.isValid():
         leafIndex = QModelIndex(self._pd.leafIndex(logicalIndex))
         if leafIndex.isValid():
             if self.orientation() == Qt.Horizontal:
                 self._pd.paintHorizontalSection(
                     painter, rect, logicalIndex, self,
                     self.styleOptionForCell(logicalIndex), leafIndex)
             else:
                 self._pd.paintVerticalSection(
                     painter, rect, logicalIndex, self,
                     self.styleOptionForCell(logicalIndex), leafIndex)
             return
     super().paintSection(painter, rect, logicalIndex)
示例#13
0
    def _tab_layout(self, opt):
        """Compute the text/icon rect from the opt rect.

        This is based on Qt's QCommonStylePrivate::tabLayout
        (qtbase/src/widgets/styles/qcommonstyle.cpp) as we can't use the
        private implementation.

        Args:
            opt: QStyleOptionTab

        Return:
            A Layout object with two QRects.
        """
        padding = config.cache['tabs.padding']
        indicator_padding = config.cache['tabs.indicator.padding']

        text_rect = QRect(opt.rect)
        if not text_rect.isValid():
            # This happens sometimes according to crash reports, but no idea
            # why...
            return None

        text_rect.adjust(padding.left, padding.top, -padding.right,
                         -padding.bottom)

        indicator_width = config.cache['tabs.indicator.width']
        if indicator_width == 0:
            indicator_rect = QRect()
        else:
            indicator_rect = QRect(opt.rect)
            qtutils.ensure_valid(indicator_rect)
            indicator_rect.adjust(padding.left + indicator_padding.left,
                                  padding.top + indicator_padding.top, 0,
                                  -(padding.bottom + indicator_padding.bottom))
            indicator_rect.setWidth(indicator_width)

            text_rect.adjust(
                indicator_width + indicator_padding.left +
                indicator_padding.right, 0, 0, 0)

        icon_rect = self._get_icon_rect(opt, text_rect)
        if icon_rect.isValid():
            text_rect.adjust(icon_rect.width() + TabBarStyle.ICON_PADDING, 0,
                             0, 0)

        text_rect = self._style.visualRect(opt.direction, opt.rect, text_rect)
        return Layouts(text=text_rect,
                       icon=icon_rect,
                       indicator=indicator_rect)
示例#14
0
    def _tab_layout(self, opt):
        """Compute the text/icon rect from the opt rect.

        This is based on Qt's QCommonStylePrivate::tabLayout
        (qtbase/src/widgets/styles/qcommonstyle.cpp) as we can't use the
        private implementation.

        Args:
            opt: QStyleOptionTab

        Return:
            A Layout namedtuple with two QRects.
        """
        padding = config.get('tabs', 'padding')
        indicator_padding = config.get('tabs', 'indicator-padding')

        text_rect = QRect(opt.rect)

        qtutils.ensure_valid(text_rect)
        text_rect.adjust(padding.left, padding.top, -padding.right,
                         -padding.bottom)

        indicator_width = config.get('tabs', 'indicator-width')
        if indicator_width == 0:
            indicator_rect = QRect()
        else:
            indicator_rect = QRect(opt.rect)
            qtutils.ensure_valid(indicator_rect)
            indicator_rect.adjust(padding.left + indicator_padding.left,
                                  padding.top + indicator_padding.top, 0,
                                  -(padding.bottom + indicator_padding.bottom))
            indicator_rect.setWidth(indicator_width)

            text_rect.adjust(
                indicator_width + indicator_padding.left +
                indicator_padding.right, 0, 0, 0)

        if opt.icon.isNull():
            icon_rect = QRect()
        else:
            icon_padding = self.pixelMetric(PixelMetrics.icon_padding, opt)
            icon_rect = self._get_icon_rect(opt, text_rect)
            if icon_rect.isValid():
                text_rect.adjust(icon_rect.width() + icon_padding, 0, 0, 0)

        text_rect = self._style.visualRect(opt.direction, opt.rect, text_rect)
        return Layouts(text=text_rect,
                       icon=icon_rect,
                       indicator=indicator_rect)
示例#15
0
 def reposition_keyhint(self):
     """Adjust keyhint according to config."""
     if not self._keyhint.isVisible():
         return
     # Shrink the window to the shown text and place it at the bottom left
     width = self._keyhint.width()
     height = self._keyhint.height()
     topleft_y = self.height() - self.status.height() - height
     topleft_y = qtutils.check_overflow(topleft_y, 'int', fatal=False)
     topleft = QPoint(0, topleft_y)
     bottomright = (self.status.geometry().topLeft() + QPoint(width, 0))
     rect = QRect(topleft, bottomright)
     log.misc.debug('keyhint rect: {}'.format(rect))
     if rect.isValid():
         self._keyhint.setGeometry(rect)
示例#16
0
 def reposition_keyhint(self):
     """Adjust keyhint according to config."""
     if not self._keyhint.isVisible():
         return
     # Shrink the window to the shown text and place it at the bottom left
     width = self._keyhint.width()
     height = self._keyhint.height()
     topleft_y = self.height() - self.status.height() - height
     topleft_y = qtutils.check_overflow(topleft_y, "int", fatal=False)
     topleft = QPoint(0, topleft_y)
     bottomright = self.status.geometry().topLeft() + QPoint(width, 0)
     rect = QRect(topleft, bottomright)
     log.misc.debug("keyhint rect: {}".format(rect))
     if rect.isValid():
         self._keyhint.setGeometry(rect)
示例#17
0
    def _tab_layout(self, opt):
        """Compute the text/icon rect from the opt rect.

        This is based on Qt's QCommonStylePrivate::tabLayout
        (qtbase/src/widgets/styles/qcommonstyle.cpp) as we can't use the
        private implementation.

        Args:
            opt: QStyleOptionTab

        Return:
            A Layout namedtuple with two QRects.
        """
        padding = config.get('tabs', 'padding')
        indicator_padding = config.get('tabs', 'indicator-padding')

        text_rect = QRect(opt.rect)
        indicator_rect = QRect(opt.rect)

        qtutils.ensure_valid(text_rect)
        text_rect.adjust(padding.left, padding.top, -padding.right,
                         -padding.bottom)

        indicator_width = config.get('tabs', 'indicator-width')
        if indicator_width == 0:
            indicator_rect = 0
        else:
            qtutils.ensure_valid(indicator_rect)
            indicator_rect.adjust(padding.left + indicator_padding.left,
                                  padding.top + indicator_padding.top,
                                  0,
                                  -(padding.bottom + indicator_padding.bottom))
            indicator_rect.setWidth(indicator_width)

            text_rect.adjust(indicator_width + indicator_padding.left +
                             indicator_padding.right, 0, 0, 0)

        if opt.icon.isNull():
            icon_rect = QRect()
        else:
            icon_padding = self.pixelMetric(PixelMetrics.icon_padding, opt)
            icon_rect = self._get_icon_rect(opt, text_rect)
            if icon_rect.isValid():
                text_rect.adjust(icon_rect.width() + icon_padding, 0, 0, 0)

        text_rect = self._style.visualRect(opt.direction, opt.rect, text_rect)
        return Layouts(text=text_rect, icon=icon_rect,
                       indicator=indicator_rect)
示例#18
0
    def _tab_layout(self, opt):
        """Compute the text/icon rect from the opt rect.

        This is based on Qt's QCommonStylePrivate::tabLayout
        (qtbase/src/widgets/styles/qcommonstyle.cpp) as we can't use the
        private implementation.

        Args:
            opt: QStyleOptionTab

        Return:
            A Layout object with two QRects.
        """
        padding = config.val.tabs.padding
        indicator_padding = config.val.tabs.indicator_padding

        text_rect = QRect(opt.rect)
        if not text_rect.isValid():
            # This happens sometimes according to crash reports, but no idea
            # why...
            return None

        text_rect.adjust(padding.left, padding.top, -padding.right,
                         -padding.bottom)

        indicator_width = config.val.tabs.width.indicator
        if indicator_width == 0:
            indicator_rect = QRect()
        else:
            indicator_rect = QRect(opt.rect)
            qtutils.ensure_valid(indicator_rect)
            indicator_rect.adjust(padding.left + indicator_padding.left,
                                  padding.top + indicator_padding.top,
                                  0,
                                  -(padding.bottom + indicator_padding.bottom))
            indicator_rect.setWidth(indicator_width)

            text_rect.adjust(indicator_width + indicator_padding.left +
                             indicator_padding.right, 0, 0, 0)

        icon_rect = self._get_icon_rect(opt, text_rect)
        if icon_rect.isValid():
            icon_padding = self.pixelMetric(PixelMetrics.icon_padding, opt)
            text_rect.adjust(icon_rect.width() + icon_padding, 0, 0, 0)

        text_rect = self._style.visualRect(opt.direction, opt.rect, text_rect)
        return Layouts(text=text_rect, icon=icon_rect,
                       indicator=indicator_rect)
示例#19
0
 def is_visible(self, mainframe):
     """Check if the given element is visible in the given frame."""
     self._check_vanished()
     # CSS attributes which hide an element
     hidden_attributes = {
         'visibility': 'hidden',
         'display': 'none',
     }
     for k, v in hidden_attributes.items():
         if self._elem.styleProperty(k, QWebElement.ComputedStyle) == v:
             return False
     elem_geometry = self._elem.geometry()
     if not elem_geometry.isValid() and elem_geometry.x() == 0:
         # Most likely an invisible link
         return False
     # First check if the element is visible on screen
     elem_rect = self.rect_on_view(elem_geometry=elem_geometry)
     mainframe_geometry = mainframe.geometry()
     if elem_rect.isValid():
         visible_on_screen = mainframe_geometry.intersects(elem_rect)
     else:
         # We got an invalid rectangle (width/height 0/0 probably), but this
         # can still be a valid link.
         visible_on_screen = mainframe_geometry.contains(
             elem_rect.topLeft())
     # Then check if it's visible in its frame if it's not in the main
     # frame.
     elem_frame = self._elem.webFrame()
     framegeom = QRect(elem_frame.geometry())
     if not framegeom.isValid():
         visible_in_frame = False
     elif elem_frame.parentFrame() is not None:
         framegeom.moveTo(0, 0)
         framegeom.translate(elem_frame.scrollPosition())
         if elem_geometry.isValid():
             visible_in_frame = framegeom.intersects(elem_geometry)
         else:
             # We got an invalid rectangle (width/height 0/0 probably), but
             # this can still be a valid link.
             visible_in_frame = framegeom.contains(elem_geometry.topLeft())
     else:
         visible_in_frame = visible_on_screen
     return all([visible_on_screen, visible_in_frame])
示例#20
0
    def _is_visible(self, mainframe: QWebFrame) -> bool:
        """Check if the given element is visible in the given frame.

        This is not public API because it can't be implemented easily here with
        QtWebEngine, and is only used via find_css(..., only_visible=True) via
        the tab API.
        """
        self._check_vanished()
        if self._is_hidden_css():
            return False

        elem_geometry = self._elem.geometry()
        if not elem_geometry.isValid() and elem_geometry.x() == 0:
            # Most likely an invisible link
            return False
        # First check if the element is visible on screen
        elem_rect = self.rect_on_view(elem_geometry=elem_geometry)
        mainframe_geometry = mainframe.geometry()
        if elem_rect.isValid():
            visible_on_screen = mainframe_geometry.intersects(elem_rect)
        else:
            # We got an invalid rectangle (width/height 0/0 probably), but this
            # can still be a valid link.
            visible_on_screen = mainframe_geometry.contains(
                elem_rect.topLeft())
        # Then check if it's visible in its frame if it's not in the main
        # frame.
        elem_frame = self._elem.webFrame()
        framegeom = QRect(elem_frame.geometry())
        if not framegeom.isValid():
            visible_in_frame = False
        elif elem_frame.parentFrame() is not None:
            framegeom.moveTo(0, 0)
            framegeom.translate(elem_frame.scrollPosition())
            if elem_geometry.isValid():
                visible_in_frame = framegeom.intersects(elem_geometry)
            else:
                # We got an invalid rectangle (width/height 0/0 probably), but
                # this can still be a valid link.
                visible_in_frame = framegeom.contains(elem_geometry.topLeft())
        else:
            visible_in_frame = visible_on_screen
        return all([visible_on_screen, visible_in_frame])
示例#21
0
 def is_visible(self, mainframe):
     """Check if the given element is visible in the given frame."""
     self._check_vanished()
     # CSS attributes which hide an element
     hidden_attributes = {
         'visibility': 'hidden',
         'display': 'none',
     }
     for k, v in hidden_attributes.items():
         if self._elem.styleProperty(k, QWebElement.ComputedStyle) == v:
             return False
     elem_geometry = self._elem.geometry()
     if not elem_geometry.isValid() and elem_geometry.x() == 0:
         # Most likely an invisible link
         return False
     # First check if the element is visible on screen
     elem_rect = self.rect_on_view(elem_geometry=elem_geometry)
     mainframe_geometry = mainframe.geometry()
     if elem_rect.isValid():
         visible_on_screen = mainframe_geometry.intersects(elem_rect)
     else:
         # We got an invalid rectangle (width/height 0/0 probably), but this
         # can still be a valid link.
         visible_on_screen = mainframe_geometry.contains(
             elem_rect.topLeft())
     # Then check if it's visible in its frame if it's not in the main
     # frame.
     elem_frame = self._elem.webFrame()
     framegeom = QRect(elem_frame.geometry())
     if not framegeom.isValid():
         visible_in_frame = False
     elif elem_frame.parentFrame() is not None:
         framegeom.moveTo(0, 0)
         framegeom.translate(elem_frame.scrollPosition())
         if elem_geometry.isValid():
             visible_in_frame = framegeom.intersects(elem_geometry)
         else:
             # We got an invalid rectangle (width/height 0/0 probably), but
             # this can still be a valid link.
             visible_in_frame = framegeom.contains(elem_geometry.topLeft())
     else:
         visible_in_frame = visible_on_screen
     return all([visible_on_screen, visible_in_frame])
示例#22
0
 def __grabRect(self):
     """
     Private method to grab the selected rectangle (i.e. do the snapshot).
     """
     if self.__mode == SnapshotRegionGrabber.Ellipse:
         ell = QRegion(self.__selection, QRegion.Ellipse)
         if not ell.isEmpty():
             self.__grabbing = True
             
             xOffset = self.__pixmap.rect().x() - ell.boundingRect().x()
             yOffset = self.__pixmap.rect().y() - ell.boundingRect().y()
             translatedEll = ell.translated(xOffset, yOffset)
             
             pixmap2 = QPixmap(ell.boundingRect().size())
             pixmap2.fill(Qt.transparent)
             
             pt = QPainter()
             pt.begin(pixmap2)
             if pt.paintEngine().hasFeature(QPaintEngine.PorterDuff):
                 pt.setRenderHints(
                     QPainter.Antialiasing |
                     QPainter.HighQualityAntialiasing |
                     QPainter.SmoothPixmapTransform,
                     True)
                 pt.setBrush(Qt.black)
                 pt.setPen(QPen(QBrush(Qt.black), 0.5))
                 pt.drawEllipse(translatedEll.boundingRect())
                 pt.setCompositionMode(QPainter.CompositionMode_SourceIn)
             else:
                 pt.setClipRegion(translatedEll)
                 pt.setCompositionMode(QPainter.CompositionMode_Source)
             
             pt.drawPixmap(pixmap2.rect(), self.__pixmap,
                           ell.boundingRect())
             pt.end()
             
             self.grabbed.emit(pixmap2)
     else:
         r = QRect(self.__selection)
         if not r.isNull() and r.isValid():
             self.__grabbing = True
             self.grabbed.emit(self.__pixmap.copy(r))
    def __grabRect(self):
        """
        Private method to grab the selected rectangle (i.e. do the snapshot).
        """
        if self.__mode == SnapshotRegionGrabber.Ellipse:
            ell = QRegion(self.__selection, QRegion.Ellipse)
            if not ell.isEmpty():
                self.__grabbing = True

                xOffset = self.__pixmap.rect().x() - ell.boundingRect().x()
                yOffset = self.__pixmap.rect().y() - ell.boundingRect().y()
                translatedEll = ell.translated(xOffset, yOffset)

                pixmap2 = QPixmap(ell.boundingRect().size())
                pixmap2.fill(Qt.transparent)

                pt = QPainter()
                pt.begin(pixmap2)
                if pt.paintEngine().hasFeature(QPaintEngine.PorterDuff):
                    pt.setRenderHints(
                        QPainter.Antialiasing
                        | QPainter.HighQualityAntialiasing
                        | QPainter.SmoothPixmapTransform, True)
                    pt.setBrush(Qt.black)
                    pt.setPen(QPen(QBrush(Qt.black), 0.5))
                    pt.drawEllipse(translatedEll.boundingRect())
                    pt.setCompositionMode(QPainter.CompositionMode_SourceIn)
                else:
                    pt.setClipRegion(translatedEll)
                    pt.setCompositionMode(QPainter.CompositionMode_Source)

                pt.drawPixmap(pixmap2.rect(), self.__pixmap,
                              ell.boundingRect())
                pt.end()

                self.grabbed.emit(pixmap2)
        else:
            r = QRect(self.__selection)
            if not r.isNull() and r.isValid():
                self.__grabbing = True
                self.grabbed.emit(self.__pixmap.copy(r))
示例#24
0
 def resize_completion(self):
     """Adjust completion according to config."""
     if not self._completion.isVisible():
         # It doesn't make sense to resize the completion as long as it's
         # not shown anyways.
         return
     # Get the configured height/percentage.
     confheight = str(config.get('completion', 'height'))
     if confheight.endswith('%'):
         perc = int(confheight.rstrip('%'))
         height = self.height() * perc / 100
     else:
         height = int(confheight)
     # Shrink to content size if needed and shrinking is enabled
     if config.get('completion', 'shrink'):
         contents_height = (
             self._completion.viewportSizeHint().height() +
             self._completion.horizontalScrollBar().sizeHint().height())
         if contents_height <= height:
             height = contents_height
     else:
         contents_height = -1
     status_position = config.get('ui', 'status-position')
     if status_position == 'bottom':
         top = self.height() - self.status.height() - height
         top = qtutils.check_overflow(top, 'int', fatal=False)
         topleft = QPoint(0, top)
         bottomright = self.status.geometry().topRight()
     elif status_position == 'top':
         topleft = self.status.geometry().bottomLeft()
         bottom = self.status.height() + height
         bottom = qtutils.check_overflow(bottom, 'int', fatal=False)
         bottomright = QPoint(self.width(), bottom)
     else:
         raise ValueError("Invalid position {}!".format(status_position))
     rect = QRect(topleft, bottomright)
     log.misc.debug('completion rect: {}'.format(rect))
     if rect.isValid():
         self._completion.setGeometry(rect)
示例#25
0
 def resize_completion(self):
     """Adjust completion according to config."""
     if not self._completion.isVisible():
         # It doesn't make sense to resize the completion as long as it's
         # not shown anyways.
         return
     # Get the configured height/percentage.
     confheight = str(config.get('completion', 'height'))
     if confheight.endswith('%'):
         perc = int(confheight.rstrip('%'))
         height = self.height() * perc / 100
     else:
         height = int(confheight)
     # Shrink to content size if needed and shrinking is enabled
     if config.get('completion', 'shrink'):
         contents_height = (
             self._completion.viewportSizeHint().height() +
             self._completion.horizontalScrollBar().sizeHint().height())
         if contents_height <= height:
             height = contents_height
     else:
         contents_height = -1
     status_position = config.get('ui', 'status-position')
     if status_position == 'bottom':
         top = self.height() - self.status.height() - height
         top = qtutils.check_overflow(top, 'int', fatal=False)
         topleft = QPoint(0, top)
         bottomright = self.status.geometry().topRight()
     elif status_position == 'top':
         topleft = self.status.geometry().bottomLeft()
         bottom = self.status.height() + height
         bottom = qtutils.check_overflow(bottom, 'int', fatal=False)
         bottomright = QPoint(self.width(), bottom)
     else:
         raise ValueError("Invalid position {}!".format(status_position))
     rect = QRect(topleft, bottomright)
     log.misc.debug('completion rect: {}'.format(rect))
     if rect.isValid():
         self._completion.setGeometry(rect)
示例#26
0
def parse_rect(s: str) -> QRect:
    """Parse a rectangle string like 20x20+5+3.

    Negative offsets aren't supported, and neither is leaving off parts of the string.
    """
    match = _RECT_PATTERN.match(s)
    if not match:
        raise ValueError(f"String {s} does not match WxH+X+Y")

    w = int(match.group('w'))
    h = int(match.group('h'))
    x = int(match.group('x'))
    y = int(match.group('y'))

    try:
        rect = QRect(x, y, w, h)
    except OverflowError as e:
        raise ValueError(e)

    if not rect.isValid():
        raise ValueError("Invalid rectangle")

    return rect
示例#27
0
    def _update_overlay_geometry(self, widget=None):
        """Reposition/resize the given overlay.

        If no widget is given, reposition/resize all overlays.
        """
        if widget is None:
            for w, _signal in self._overlays:
                self._update_overlay_geometry(w)
            return

        if not widget.isVisible():
            return

        size_hint = widget.sizeHint()
        if widget.sizePolicy().horizontalPolicy() == QSizePolicy.Expanding:
            width = self.width()
        else:
            width = size_hint.width()

        status_position = config.get("ui", "status-position")
        if status_position == "bottom":
            top = self.height() - self.status.height() - size_hint.height()
            top = qtutils.check_overflow(top, "int", fatal=False)
            topleft = QPoint(0, top)
            bottomright = QPoint(width, self.status.geometry().top())
        elif status_position == "top":
            topleft = self.status.geometry().bottomLeft()
            bottom = self.status.height() + size_hint.height()
            bottom = qtutils.check_overflow(bottom, "int", fatal=False)
            bottomright = QPoint(width, bottom)
        else:
            raise ValueError("Invalid position {}!".format(status_position))

        rect = QRect(topleft, bottomright)
        log.misc.debug("new geometry for {!r}: {}".format(widget, rect))
        if rect.isValid():
            widget.setGeometry(rect)
示例#28
0
    def _update_overlay_geometry(self, widget=None):
        """Reposition/resize the given overlay.

        If no widget is given, reposition/resize all overlays.
        """
        if widget is None:
            for w, _signal in self._overlays:
                self._update_overlay_geometry(w)
            return

        if not widget.isVisible():
            return

        size_hint = widget.sizeHint()
        if widget.sizePolicy().horizontalPolicy() == QSizePolicy.Expanding:
            width = self.width()
        else:
            width = size_hint.width()

        status_position = config.get('ui', 'status-position')
        if status_position == 'bottom':
            top = self.height() - self.status.height() - size_hint.height()
            top = qtutils.check_overflow(top, 'int', fatal=False)
            topleft = QPoint(0, top)
            bottomright = QPoint(width, self.status.geometry().top())
        elif status_position == 'top':
            topleft = self.status.geometry().bottomLeft()
            bottom = self.status.height() + size_hint.height()
            bottom = qtutils.check_overflow(bottom, 'int', fatal=False)
            bottomright = QPoint(width, bottom)
        else:
            raise ValueError("Invalid position {}!".format(status_position))

        rect = QRect(topleft, bottomright)
        log.misc.debug('new geometry for {!r}: {}'.format(widget, rect))
        if rect.isValid():
            widget.setGeometry(rect)
示例#29
0
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.calc = CalcCore()
        self.setWindowTitle('rpCalc')
        modPath = os.path.abspath(sys.path[0])
        if modPath.endswith('.zip') or modPath.endswith('.exe'):
            modPath = os.path.dirname(modPath)  # for py2exe/cx_freeze
        iconPathList = [
            iconPath,
            os.path.join(modPath, 'icons/'),
            os.path.join(modPath, '../icons')
        ]
        self.icons = icondict.IconDict()
        self.icons.addIconPath(filter(None, iconPathList))
        self.icons.addIconPath([path for path in iconPathList if path])
        try:
            QApplication.setWindowIcon(self.icons['calc_lg'])
        except KeyError:
            pass
        if self.calc.option.boolData('KeepOnTop'):
            self.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint)
        else:
            self.setWindowFlags(Qt.Window)
        self.setFocusPolicy(Qt.StrongFocus)
        self.helpView = None
        self.extraView = None
        self.altBaseView = None
        self.optDlg = None
        self.popupMenu = QMenu(self)
        self.popupMenu.addAction('Registers on &LCD', self.toggleReg)
        self.popupMenu.addSeparator()
        self.popupMenu.addAction('Show &Register List', self.viewReg)
        self.popupMenu.addAction('Show &History List', self.viewHist)
        self.popupMenu.addAction('Show &Memory List', self.viewMem)
        self.popupMenu.addSeparator()
        self.popupMenu.addAction('Show Other &Bases', self.viewAltBases)
        self.popupMenu.addSeparator()
        self.popupMenu.addAction('Show Help &File', self.help)
        self.popupMenu.addAction('&About rpCalc', self.about)
        self.popupMenu.addSeparator()
        self.popupMenu.addAction('&Quit', self.close)
        topLay = QVBoxLayout(self)
        self.setLayout(topLay)
        topLay.setSpacing(4)
        topLay.setContentsMargins(6, 6, 6, 6)
        lcdBox = LcdBox()
        topLay.addWidget(lcdBox)
        lcdLay = QGridLayout(lcdBox)
        lcdLay.setColumnStretch(1, 1)
        lcdLay.setRowStretch(3, 1)
        self.extraLabels = [QLabel(' T:', ), QLabel(' Z:', ), QLabel(' Y:', )]
        for i in range(3):
            lcdLay.addWidget(self.extraLabels[i], i, 0, Qt.AlignLeft)
        self.extraLcds = [Lcd(1.5, 13), Lcd(1.5, 13), Lcd(1.5, 13)]
        lcdLay.addWidget(self.extraLcds[2], 0, 1, Qt.AlignRight)
        lcdLay.addWidget(self.extraLcds[1], 1, 1, Qt.AlignRight)
        lcdLay.addWidget(self.extraLcds[0], 2, 1, Qt.AlignRight)
        if not self.calc.option.boolData('ViewRegisters'):
            for w in self.extraLabels + self.extraLcds:
                w.hide()
        self.lcd = Lcd(2.0, 13)
        lcdLay.addWidget(self.lcd, 3, 0, 1, 2, Qt.AlignRight)
        self.setLcdHighlight()
        self.updateLcd()
        self.updateColors()

        self.cmdLay = QGridLayout()
        topLay.addLayout(self.cmdLay)
        self.cmdDict = {}
        self.addCmdButton('x^2', 0, 0)
        self.addCmdButton('sqRT', 0, 1)
        self.addCmdButton('y^X', 0, 2)
        self.addCmdButton('xRT', 0, 3)
        self.addCmdButton('RCIP', 0, 4)
        self.addCmdButton('SIN', 1, 0)
        self.addCmdButton('COS', 1, 1)
        self.addCmdButton('TAN', 1, 2)
        self.addCmdButton('LN', 1, 3)
        self.addCmdButton('e^X', 1, 4)
        self.addCmdButton('ASIN', 2, 0)
        self.addCmdButton('ACOS', 2, 1)
        self.addCmdButton('ATAN', 2, 2)
        self.addCmdButton('LOG', 2, 3)
        self.addCmdButton('tn^X', 2, 4)
        self.addCmdButton('STO', 3, 0)
        self.addCmdButton('RCL', 3, 1)
        self.addCmdButton('R<', 3, 2)
        self.addCmdButton('R>', 3, 3)
        self.addCmdButton('x<>y', 3, 4)
        self.addCmdButton('SHOW', 4, 0)
        self.addCmdButton('CLR', 4, 1)
        self.addCmdButton('PLCS', 4, 2)
        self.addCmdButton('SCI', 4, 3)
        self.addCmdButton('DEG', 4, 4)
        self.addCmdButton('EXIT', 5, 0)
        self.addCmdButton('Pi', 5, 1)
        self.addCmdButton('EXP', 5, 2)
        self.addCmdButton('CHS', 5, 3)
        self.addCmdButton('<-', 5, 4)

        self.mainLay = QGridLayout()
        topLay.addLayout(self.mainLay)
        self.mainDict = {}
        self.addMainButton(0, 'OPT', 0, 0)
        self.addMainButton(Qt.Key_Slash, '/', 0, 1)
        self.addMainButton(Qt.Key_Asterisk, '*', 0, 2)
        self.addMainButton(Qt.Key_Minus, '-', 0, 3)
        self.addMainButton(Qt.Key_7, '7', 1, 0)
        self.addMainButton(Qt.Key_8, '8', 1, 1)
        self.addMainButton(Qt.Key_9, '9', 1, 2)
        self.addMainButton(Qt.Key_Plus, '+', 1, 3, 1, 0)
        self.addMainButton(Qt.Key_4, '4', 2, 0)
        self.addMainButton(Qt.Key_5, '5', 2, 1)
        self.addMainButton(Qt.Key_6, '6', 2, 2)
        self.addMainButton(Qt.Key_1, '1', 3, 0)
        self.addMainButton(Qt.Key_2, '2', 3, 1)
        self.addMainButton(Qt.Key_3, '3', 3, 2)
        self.addMainButton(Qt.Key_Enter, 'ENT', 3, 3, 1, 0)
        self.addMainButton(Qt.Key_0, '0', 4, 0, 0, 1)
        self.addMainButton(Qt.Key_Period, '.', 4, 2)

        self.mainDict[Qt.Key_Return] = \
                     self.mainDict[Qt.Key_Enter]
        # added for european keyboards:
        self.mainDict[Qt.Key_Comma] = \
                     self.mainDict[Qt.Key_Period]
        self.cmdDict['ENT'] = self.mainDict[Qt.Key_Enter]
        self.cmdDict['OPT'] = self.mainDict[0]

        self.entryStr = ''
        self.showMode = False

        statusBox = QFrame()
        statusBox.setFrameStyle(QFrame.Panel | QFrame.Sunken)
        statusBox.setSizePolicy(
            QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred))
        topLay.addWidget(statusBox)
        statusLay = QHBoxLayout(statusBox)
        self.entryLabel = QLabel(statusBox)
        statusLay.addWidget(self.entryLabel)
        statusLay.setContentsMargins(1, 1, 1, 1)
        self.statusLabel = QLabel(statusBox)
        self.statusLabel.setAlignment(Qt.AlignRight)
        statusLay.addWidget(self.statusLabel)

        if self.calc.option.boolData('ExtraViewStartup'):
            self.viewReg()
        if self.calc.option.boolData('AltBaseStartup'):
            self.viewAltBases()

        rect = QRect(self.calc.option.intData('MainDlgXPos', 0, 10000),
                     self.calc.option.intData('MainDlgYPos', 0, 10000),
                     self.calc.option.intData('MainDlgXSize', 0, 10000),
                     self.calc.option.intData('MainDlgYSize', 0, 10000))
        if rect.isValid():
            availRect = (
                QApplication.primaryScreen().availableVirtualGeometry())
            topMargin = self.calc.option.intData('MainDlgTopMargin', 0, 1000)
            otherMargin = self.calc.option.intData('MainDlgOtherMargin', 0,
                                                   1000)
            # remove frame space from available rect
            availRect.adjust(otherMargin, topMargin, -otherMargin,
                             -otherMargin)
            finalRect = rect.intersected(availRect)
            if finalRect.isEmpty():
                rect.moveTo(0, 0)
                finalRect = rect.intersected(availRect)
            if finalRect.isValid():
                self.setGeometry(finalRect)

        self.updateEntryLabel('rpCalc Version {0}'.format(__version__))
        QTimer.singleShot(5000, self.updateEntryLabel)
示例#30
0
class BrushingModel(QObject):
    brushSizeChanged = pyqtSignal(int)
    brushColorChanged = pyqtSignal(QColor)
    brushStrokeAvailable = pyqtSignal(QPointF, object)
    drawnNumberChanged = pyqtSignal(int)

    minBrushSize = 1
    maxBrushSize = 61
    defaultBrushSize = 3
    defaultDrawnNumber = 1
    defaultColor = Qt.white
    erasingColor = Qt.black
    erasingNumber = 100

    def __init__(self, parent=None):
        QObject.__init__(self, parent=parent)
        self.sliceRect = None
        self.bb = QRect()  # bounding box enclosing the drawing
        self.brushSize = self.defaultBrushSize
        self.drawColor = self.defaultColor
        self._temp_color = None
        self._temp_number = None
        self.drawnNumber = self.defaultDrawnNumber

        self.pos = None
        self.erasing = False
        self._hasMoved = False

        self.drawOnto = None

        # an empty scene, where we add all drawn line segments
        # a QGraphicsLineItem, and which we can use to then
        # render to an image
        self.scene = QGraphicsScene()

    def toggleErase(self):
        self.erasing = not (self.erasing)
        if self.erasing:
            self.setErasing()
        else:
            self.disableErasing()

    def setErasing(self):
        self.erasing = True
        self._temp_color = self.drawColor
        self._temp_number = self.drawnNumber
        self.setBrushColor(self.erasingColor)
        self.brushColorChanged.emit(self.erasingColor)
        self.setDrawnNumber(self.erasingNumber)

    def disableErasing(self):
        self.erasing = False
        self.setBrushColor(self._temp_color)
        self.brushColorChanged.emit(self.drawColor)
        self.setDrawnNumber(self._temp_number)

    def setBrushSize(self, size):
        self.brushSize = size
        self.brushSizeChanged.emit(self.brushSize)

    def setDrawnNumber(self, num):
        self.drawnNumber = num
        self.drawnNumberChanged.emit(num)

    def getBrushSize(self):
        return self.brushSize

    def brushSmaller(self):
        b = self.brushSize
        if b > self.minBrushSize:
            self.setBrushSize(b - 1)

    def brushBigger(self):
        b = self.brushSize
        if self.brushSize < self.maxBrushSize:
            self.setBrushSize(b + 1)

    def setBrushColor(self, color):
        self.drawColor = QColor(color)
        self.brushColorChanged.emit(self.drawColor)

    def beginDrawing(self, pos, sliceRect):
        """

        pos -- QPointF-like
        """
        self.sliceRect = sliceRect
        self.scene.clear()
        self.bb = QRect()
        self.pos = QPointF(pos.x(), pos.y())
        self._hasMoved = False

    def endDrawing(self, pos):
        has_moved = self._hasMoved  # _hasMoved will change after calling moveTo
        if has_moved:
            self.moveTo(pos)
        else:
            assert self.pos == pos
            self.moveTo(QPointF(pos.x() + 0.0001, pos.y() + 0.0001))  # move a little

        # Qt seems to use strange rules for determining which pixels to set when rendering a brush stroke to a QImage.
        # We seem to get better results if we do the following:
        # 1) Slightly offset the source window because apparently there is a small shift in the data
        # 2) Render the scene to an image that is MUCH larger than the scene resolution (4x by 4x)
        # 3) Downsample each 4x4 patch from the large image back to a single pixel in the final image,
        #     applying some threshold to determine if the final pixel is on or off.

        tempi = QImage(
            QSize(4 * self.bb.width(), 4 * self.bb.height()), QImage.Format_ARGB32_Premultiplied
        )  # TODO: format
        tempi.fill(0)
        painter = QPainter(tempi)
        # Offset the source window.  At first I thought the right offset was 0.5, because
        #  that would seem to make sure points are rounded to pixel CENTERS, but
        #  experimentation indicates that 0.25 is slightly better for some reason...
        source_rect = QRectF(QPointF(self.bb.x() + 0.25, self.bb.y() + 0.25), QSizeF(self.bb.width(), self.bb.height()))
        target_rect = QRectF(QPointF(0, 0), QSizeF(4 * self.bb.width(), 4 * self.bb.height()))
        self.scene.render(painter, target=target_rect, source=source_rect)
        painter.end()

        # Now downsample: convert each 4x4 patch into a single pixel by summing and dividing
        ndarr = qimage2ndarray.rgb_view(tempi)[:, :, 0].astype(int)
        ndarr = ndarr.reshape((ndarr.shape[0],) + (ndarr.shape[1] // 4,) + (4,))
        ndarr = ndarr.sum(axis=-1)
        ndarr = ndarr.transpose()
        ndarr = ndarr.reshape((ndarr.shape[0],) + (ndarr.shape[1] // 4,) + (4,))
        ndarr = ndarr.sum(axis=-1)
        ndarr = ndarr.transpose()
        ndarr //= 4 * 4

        downsample_threshold = (7.0 / 16) * 255
        labels = numpy.where(ndarr >= downsample_threshold, numpy.uint8(self.drawnNumber), numpy.uint8(0))
        labels = labels.swapaxes(0, 1)
        assert labels.shape[0] == self.bb.width()
        assert labels.shape[1] == self.bb.height()

        ##
        ## ensure that at least one pixel is label when the brush size is 1
        ##
        ## this happens when the user just clicked without moving
        ## in that case the lineitem will be so tiny, that it won't be rendered
        ## into a single pixel by the code above
        if not has_moved and self.brushSize <= 1 and numpy.count_nonzero(labels) == 0:
            labels[labels.shape[0] // 2, labels.shape[1] // 2] = self.drawnNumber

        self.brushStrokeAvailable.emit(QPointF(self.bb.x(), self.bb.y()), labels)

    def dumpDraw(self, pos):
        res = self.endDrawing(pos)
        self.beginDrawing(pos, self.sliceRect)
        return res

    def moveTo(self, pos):
        # data coordinates
        oldX, oldY = self.pos.x(), self.pos.y()
        x, y = pos.x(), pos.y()

        line = QGraphicsLineItem(oldX, oldY, x, y)
        line.setPen(QPen(QBrush(Qt.white), self.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
        self.scene.addItem(line)
        self._hasMoved = True

        # update bounding Box
        if not self.bb.isValid():
            self.bb = QRect(QPoint(oldX, oldY), QSize(1, 1))
        # grow bounding box
        self.bb.setLeft(min(self.bb.left(), max(0, x - self.brushSize // 2 - 1)))
        self.bb.setRight(max(self.bb.right(), min(self.sliceRect[0] - 1, x + self.brushSize // 2 + 1)))
        self.bb.setTop(min(self.bb.top(), max(0, y - self.brushSize // 2 - 1)))
        self.bb.setBottom(max(self.bb.bottom(), min(self.sliceRect[1] - 1, y + self.brushSize // 2 + 1)))

        # update/move position
        self.pos = pos
class BrushingModel(QObject):
    brushSizeChanged = pyqtSignal(int)
    brushColorChanged = pyqtSignal(QColor)
    brushStrokeAvailable = pyqtSignal(QPointF, object)
    drawnNumberChanged = pyqtSignal(int)

    minBrushSize = 1
    maxBrushSize = 61
    defaultBrushSize = 3
    defaultDrawnNumber = 1
    defaultColor = Qt.white
    erasingColor = Qt.black
    erasingNumber = 100

    def __init__(self, parent=None):
        QObject.__init__(self, parent=parent)
        self.sliceRect = None
        self.bb = QRect()  #bounding box enclosing the drawing
        self.brushSize = self.defaultBrushSize
        self.drawColor = self.defaultColor
        self._temp_color = None
        self._temp_number = None
        self.drawnNumber = self.defaultDrawnNumber

        self.pos = None
        self.erasing = False
        self._hasMoved = False

        self.drawOnto = None

        #an empty scene, where we add all drawn line segments
        #a QGraphicsLineItem, and which we can use to then
        #render to an image
        self.scene = QGraphicsScene()

    def toggleErase(self):
        self.erasing = not (self.erasing)
        if self.erasing:
            self.setErasing()
        else:
            self.disableErasing()

    def setErasing(self):
        self.erasing = True
        self._temp_color = self.drawColor
        self._temp_number = self.drawnNumber
        self.setBrushColor(self.erasingColor)
        self.brushColorChanged.emit(self.erasingColor)
        self.setDrawnNumber(self.erasingNumber)

    def disableErasing(self):
        self.erasing = False
        self.setBrushColor(self._temp_color)
        self.brushColorChanged.emit(self.drawColor)
        self.setDrawnNumber(self._temp_number)

    def setBrushSize(self, size):
        self.brushSize = size
        self.brushSizeChanged.emit(self.brushSize)

    def setDrawnNumber(self, num):
        self.drawnNumber = num
        self.drawnNumberChanged.emit(num)

    def getBrushSize(self):
        return self.brushSize

    def brushSmaller(self):
        b = self.brushSize
        if b > self.minBrushSize:
            self.setBrushSize(b - 1)

    def brushBigger(self):
        b = self.brushSize
        if self.brushSize < self.maxBrushSize:
            self.setBrushSize(b + 1)

    def setBrushColor(self, color):
        self.drawColor = QColor(color)
        self.brushColorChanged.emit(self.drawColor)

    def beginDrawing(self, pos, sliceRect):
        '''

        pos -- QPointF-like
        '''
        self.sliceRect = sliceRect
        self.scene.clear()
        self.bb = QRect()
        self.pos = QPointF(pos.x(), pos.y())
        self._hasMoved = False

    def endDrawing(self, pos):
        has_moved = self._hasMoved  # _hasMoved will change after calling moveTo
        if has_moved:
            self.moveTo(pos)
        else:
            assert (self.pos == pos)
            self.moveTo(QPointF(pos.x() + 0.0001,
                                pos.y() + 0.0001))  # move a little

        # Qt seems to use strange rules for determining which pixels to set when rendering a brush stroke to a QImage.
        # We seem to get better results if we do the following:
        # 1) Slightly offset the source window because apparently there is a small shift in the data
        # 2) Render the scene to an image that is MUCH larger than the scene resolution (4x by 4x)
        # 3) Downsample each 4x4 patch from the large image back to a single pixel in the final image,
        #     applying some threshold to determine if the final pixel is on or off.

        tempi = QImage(QSize(4 * self.bb.width(), 4 * self.bb.height()),
                       QImage.Format_ARGB32_Premultiplied)  #TODO: format
        tempi.fill(0)
        painter = QPainter(tempi)
        # Offset the source window.  At first I thought the right offset was 0.5, because
        #  that would seem to make sure points are rounded to pixel CENTERS, but
        #  experimentation indicates that 0.25 is slightly better for some reason...
        source_rect = QRectF(QPointF(self.bb.x() + 0.25,
                                     self.bb.y() + 0.25),
                             QSizeF(self.bb.width(), self.bb.height()))
        target_rect = QRectF(QPointF(0, 0),
                             QSizeF(4 * self.bb.width(), 4 * self.bb.height()))
        self.scene.render(painter, target=target_rect, source=source_rect)
        painter.end()

        # Now downsample: convert each 4x4 patch into a single pixel by summing and dividing
        ndarr = qimage2ndarray.rgb_view(tempi)[:, :, 0].astype(int)
        ndarr = ndarr.reshape((ndarr.shape[0], ) + (ndarr.shape[1] // 4, ) +
                              (4, ))
        ndarr = ndarr.sum(axis=-1)
        ndarr = ndarr.transpose()
        ndarr = ndarr.reshape((ndarr.shape[0], ) + (ndarr.shape[1] // 4, ) +
                              (4, ))
        ndarr = ndarr.sum(axis=-1)
        ndarr = ndarr.transpose()
        ndarr //= 4 * 4

        downsample_threshold = (7. / 16) * 255
        labels = numpy.where(ndarr >= downsample_threshold,
                             numpy.uint8(self.drawnNumber), numpy.uint8(0))
        labels = labels.swapaxes(0, 1)
        assert labels.shape[0] == self.bb.width()
        assert labels.shape[1] == self.bb.height()

        ##
        ## ensure that at least one pixel is label when the brush size is 1
        ##
        ## this happens when the user just clicked without moving
        ## in that case the lineitem will be so tiny, that it won't be rendered
        ## into a single pixel by the code above
        if not has_moved and self.brushSize <= 1 and numpy.count_nonzero(
                labels) == 0:
            labels[labels.shape[0] // 2,
                   labels.shape[1] // 2] = self.drawnNumber

        self.brushStrokeAvailable.emit(QPointF(self.bb.x(), self.bb.y()),
                                       labels)

    def dumpDraw(self, pos):
        res = self.endDrawing(pos)
        self.beginDrawing(pos, self.sliceRect)
        return res

    def moveTo(self, pos):
        #data coordinates
        oldX, oldY = self.pos.x(), self.pos.y()
        x, y = pos.x(), pos.y()

        line = QGraphicsLineItem(oldX, oldY, x, y)
        line.setPen(
            QPen(QBrush(Qt.white), self.brushSize, Qt.SolidLine, Qt.RoundCap,
                 Qt.RoundJoin))
        self.scene.addItem(line)
        self._hasMoved = True

        #update bounding Box
        if not self.bb.isValid():
            self.bb = QRect(QPoint(oldX, oldY), QSize(1, 1))
        #grow bounding box
        self.bb.setLeft(
            min(self.bb.left(), max(0, x - self.brushSize // 2 - 1)))
        self.bb.setRight(
            max(self.bb.right(),
                min(self.sliceRect[0] - 1, x + self.brushSize // 2 + 1)))
        self.bb.setTop(min(self.bb.top(), max(0, y - self.brushSize // 2 - 1)))
        self.bb.setBottom(
            max(self.bb.bottom(),
                min(self.sliceRect[1] - 1, y + self.brushSize // 2 + 1)))

        #update/move position
        self.pos = pos
示例#32
0
class PuzzleWidget(QWidget):

    puzzleCompleted = pyqtSignal()

    def __init__(self, parent=None):
        super(PuzzleWidget, self).__init__(parent)

        self.piecePixmaps = []
        self.pieceRects = []
        self.pieceLocations = []
        self.highlightedRect = QRect()
        self.inPlace = 0

        self.setAcceptDrops(True)
        self.setMinimumSize(400, 400)
        self.setMaximumSize(400, 400)

    def clear(self):
        self.pieceLocations = []
        self.piecePixmaps = []
        self.pieceRects = []
        self.highlightedRect = QRect()
        self.inPlace = 0
        self.update()

    def dragEnterEvent(self, event):
        if event.mimeData().hasFormat('image/x-puzzle-piece'):
            event.accept()
        else:
            event.ignore()

    def dragLeaveEvent(self, event):
        updateRect = self.highlightedRect
        self.highlightedRect = QRect()
        self.update(updateRect)
        event.accept()

    def dragMoveEvent(self, event):
        updateRect = self.highlightedRect.united(self.targetSquare(event.pos()))

        if event.mimeData().hasFormat('image/x-puzzle-piece') and self.findPiece(self.targetSquare(event.pos())) == -1:
            self.highlightedRect = self.targetSquare(event.pos())
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            self.highlightedRect = QRect()
            event.ignore()

        self.update(updateRect)

    def dropEvent(self, event):
        if event.mimeData().hasFormat('image/x-puzzle-piece') and self.findPiece(self.targetSquare(event.pos())) == -1:
            pieceData = event.mimeData().data('image/x-puzzle-piece')
            dataStream = QDataStream(pieceData, QIODevice.ReadOnly)
            square = self.targetSquare(event.pos())
            pixmap = QPixmap()
            location = QPoint()
            dataStream >> pixmap >> location

            self.pieceLocations.append(location)
            self.piecePixmaps.append(pixmap)
            self.pieceRects.append(square)

            self.hightlightedRect = QRect()
            self.update(square)

            event.setDropAction(Qt.MoveAction)
            event.accept()

            if location == QPoint(square.x() / 80, square.y() / 80):
                self.inPlace += 1
                if self.inPlace == 25:
                    self.puzzleCompleted.emit()
        else:
            self.highlightedRect = QRect()
            event.ignore()

    def findPiece(self, pieceRect):
        try:
            return self.pieceRects.index(pieceRect)
        except ValueError:
            return -1

    def mousePressEvent(self, event):
        square = self.targetSquare(event.pos())
        found = self.findPiece(square)

        if found == -1:
            return

        location = self.pieceLocations[found]
        pixmap = self.piecePixmaps[found]
        del self.pieceLocations[found]
        del self.piecePixmaps[found]
        del self.pieceRects[found]

        if location == QPoint(square.x() / 80, square.y() / 80):
            self.inPlace -= 1

        self.update(square)

        itemData = QByteArray()
        dataStream = QDataStream(itemData, QIODevice.WriteOnly)

        dataStream << pixmap << location

        mimeData = QMimeData()
        mimeData.setData('image/x-puzzle-piece', itemData)

        drag = QDrag(self)
        drag.setMimeData(mimeData)
        drag.setHotSpot(event.pos() - square.topLeft())
        drag.setPixmap(pixmap)

        if drag.exec_(Qt.MoveAction) != Qt.MoveAction:
            self.pieceLocations.insert(found, location)
            self.piecePixmaps.insert(found, pixmap)
            self.pieceRects.insert(found, square)
            self.update(self.targetSquare(event.pos()))

            if location == QPoint(square.x() / 80, square.y() / 80):
                self.inPlace += 1

    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        painter.fillRect(event.rect(), Qt.white)

        if self.highlightedRect.isValid():
            painter.setBrush(QColor("#ffcccc"))
            painter.setPen(Qt.NoPen)
            painter.drawRect(self.highlightedRect.adjusted(0, 0, -1, -1))

        for rect, pixmap in zip(self.pieceRects, self.piecePixmaps):
            painter.drawPixmap(rect, pixmap)

        painter.end()

    def targetSquare(self, position):
        return QRect(position.x() // 80 * 80, position.y() // 80 * 80, 80, 80)
示例#33
0
class IconEditorGrid(QWidget):
    """
    Class implementing the icon editor grid.
    
    @signal canRedoChanged(bool) emitted after the redo status has changed
    @signal canUndoChanged(bool) emitted after the undo status has changed
    @signal clipboardImageAvailable(bool) emitted to signal the availability
        of an image to be pasted
    @signal colorChanged(QColor) emitted after the drawing color was changed
    @signal imageChanged(bool) emitted after the image was modified
    @signal positionChanged(int, int) emitted after the cursor poition was
        changed
    @signal previewChanged(QPixmap) emitted to signal a new preview pixmap
    @signal selectionAvailable(bool) emitted to signal a change of the
        selection
    @signal sizeChanged(int, int) emitted after the size has been changed
    @signal zoomChanged(int) emitted to signal a change of the zoom value
    """
    canRedoChanged = pyqtSignal(bool)
    canUndoChanged = pyqtSignal(bool)
    clipboardImageAvailable = pyqtSignal(bool)
    colorChanged = pyqtSignal(QColor)
    imageChanged = pyqtSignal(bool)
    positionChanged = pyqtSignal(int, int)
    previewChanged = pyqtSignal(QPixmap)
    selectionAvailable = pyqtSignal(bool)
    sizeChanged = pyqtSignal(int, int)
    zoomChanged = pyqtSignal(int)

    Pencil = 1
    Rubber = 2
    Line = 3
    Rectangle = 4
    FilledRectangle = 5
    Circle = 6
    FilledCircle = 7
    Ellipse = 8
    FilledEllipse = 9
    Fill = 10
    ColorPicker = 11

    RectangleSelection = 20
    CircleSelection = 21

    MarkColor = QColor(255, 255, 255, 255)
    NoMarkColor = QColor(0, 0, 0, 0)

    ZoomMinimum = 100
    ZoomMaximum = 10000
    ZoomStep = 100
    ZoomDefault = 1200
    ZoomPercent = True

    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(IconEditorGrid, self).__init__(parent)

        self.setAttribute(Qt.WA_StaticContents)
        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        self.__curColor = Qt.black
        self.__zoom = 12
        self.__curTool = self.Pencil
        self.__startPos = QPoint()
        self.__endPos = QPoint()
        self.__dirty = False
        self.__selecting = False
        self.__selRect = QRect()
        self.__isPasting = False
        self.__clipboardSize = QSize()
        self.__pasteRect = QRect()

        self.__undoStack = QUndoStack(self)
        self.__currentUndoCmd = None

        self.__image = QImage(32, 32, QImage.Format_ARGB32)
        self.__image.fill(qRgba(0, 0, 0, 0))
        self.__markImage = QImage(self.__image)
        self.__markImage.fill(self.NoMarkColor.rgba())

        self.__compositingMode = QPainter.CompositionMode_SourceOver
        self.__lastPos = (-1, -1)

        self.__gridEnabled = True
        self.__selectionAvailable = False

        self.__initCursors()
        self.__initUndoTexts()

        self.setMouseTracking(True)

        self.__undoStack.canRedoChanged.connect(self.canRedoChanged)
        self.__undoStack.canUndoChanged.connect(self.canUndoChanged)
        self.__undoStack.cleanChanged.connect(self.__cleanChanged)

        self.imageChanged.connect(self.__updatePreviewPixmap)
        QApplication.clipboard().dataChanged.connect(self.__checkClipboard)

        self.__checkClipboard()

    def __initCursors(self):
        """
        Private method to initialize the various cursors.
        """
        self.__normalCursor = QCursor(Qt.ArrowCursor)

        pix = QPixmap(":colorpicker-cursor.xpm")
        mask = pix.createHeuristicMask()
        pix.setMask(mask)
        self.__colorPickerCursor = QCursor(pix, 1, 21)

        pix = QPixmap(":paintbrush-cursor.xpm")
        mask = pix.createHeuristicMask()
        pix.setMask(mask)
        self.__paintCursor = QCursor(pix, 0, 19)

        pix = QPixmap(":fill-cursor.xpm")
        mask = pix.createHeuristicMask()
        pix.setMask(mask)
        self.__fillCursor = QCursor(pix, 3, 20)

        pix = QPixmap(":aim-cursor.xpm")
        mask = pix.createHeuristicMask()
        pix.setMask(mask)
        self.__aimCursor = QCursor(pix, 10, 10)

        pix = QPixmap(":eraser-cursor.xpm")
        mask = pix.createHeuristicMask()
        pix.setMask(mask)
        self.__rubberCursor = QCursor(pix, 1, 16)

    def __initUndoTexts(self):
        """
        Private method to initialize texts to be associated with undo commands
        for the various drawing tools.
        """
        self.__undoTexts = {
            self.Pencil: self.tr("Set Pixel"),
            self.Rubber: self.tr("Erase Pixel"),
            self.Line: self.tr("Draw Line"),
            self.Rectangle: self.tr("Draw Rectangle"),
            self.FilledRectangle: self.tr("Draw Filled Rectangle"),
            self.Circle: self.tr("Draw Circle"),
            self.FilledCircle: self.tr("Draw Filled Circle"),
            self.Ellipse: self.tr("Draw Ellipse"),
            self.FilledEllipse: self.tr("Draw Filled Ellipse"),
            self.Fill: self.tr("Fill Region"),
        }

    def isDirty(self):
        """
        Public method to check the dirty status.
        
        @return flag indicating a modified status (boolean)
        """
        return self.__dirty

    def setDirty(self, dirty, setCleanState=False):
        """
        Public slot to set the dirty flag.
        
        @param dirty flag indicating the new modification status (boolean)
        @param setCleanState flag indicating to set the undo stack to clean
            (boolean)
        """
        self.__dirty = dirty
        self.imageChanged.emit(dirty)

        if not dirty and setCleanState:
            self.__undoStack.setClean()

    def sizeHint(self):
        """
        Public method to report the size hint.
        
        @return size hint (QSize)
        """
        size = self.__zoom * self.__image.size()
        if self.__zoom >= 3 and self.__gridEnabled:
            size += QSize(1, 1)
        return size

    def setPenColor(self, newColor):
        """
        Public method to set the drawing color.
        
        @param newColor reference to the new color (QColor)
        """
        self.__curColor = QColor(newColor)
        self.colorChanged.emit(QColor(newColor))

    def penColor(self):
        """
        Public method to get the current drawing color.
        
        @return current drawing color (QColor)
        """
        return QColor(self.__curColor)

    def setCompositingMode(self, mode):
        """
        Public method to set the compositing mode.
        
        @param mode compositing mode to set (QPainter.CompositionMode)
        """
        self.__compositingMode = mode

    def compositingMode(self):
        """
        Public method to get the compositing mode.
        
        @return compositing mode (QPainter.CompositionMode)
        """
        return self.__compositingMode

    def setTool(self, tool):
        """
        Public method to set the current drawing tool.
        
        @param tool drawing tool to be used
            (IconEditorGrid.Pencil ... IconEditorGrid.CircleSelection)
        """
        self.__curTool = tool
        self.__lastPos = (-1, -1)

        if self.__curTool in [self.RectangleSelection, self.CircleSelection]:
            self.__selecting = True
        else:
            self.__selecting = False

        if self.__curTool in [
                self.RectangleSelection, self.CircleSelection, self.Line,
                self.Rectangle, self.FilledRectangle, self.Circle,
                self.FilledCircle, self.Ellipse, self.FilledEllipse
        ]:
            self.setCursor(self.__aimCursor)
        elif self.__curTool == self.Fill:
            self.setCursor(self.__fillCursor)
        elif self.__curTool == self.ColorPicker:
            self.setCursor(self.__colorPickerCursor)
        elif self.__curTool == self.Pencil:
            self.setCursor(self.__paintCursor)
        elif self.__curTool == self.Rubber:
            self.setCursor(self.__rubberCursor)
        else:
            self.setCursor(self.__normalCursor)

    def tool(self):
        """
        Public method to get the current drawing tool.
        
        @return current drawing tool
            (IconEditorGrid.Pencil ... IconEditorGrid.CircleSelection)
        """
        return self.__curTool

    def setIconImage(self, newImage, undoRedo=False, clearUndo=False):
        """
        Public method to set a new icon image.
        
        @param newImage reference to the new image (QImage)
        @keyparam undoRedo flag indicating an undo or redo operation (boolean)
        @keyparam clearUndo flag indicating to clear the undo stack (boolean)
        """
        if newImage != self.__image:
            self.__image = newImage.convertToFormat(QImage.Format_ARGB32)
            self.update()
            self.updateGeometry()
            self.resize(self.sizeHint())

            self.__markImage = QImage(self.__image)
            self.__markImage.fill(self.NoMarkColor.rgba())

            if undoRedo:
                self.setDirty(not self.__undoStack.isClean())
            else:
                self.setDirty(False)

            if clearUndo:
                self.__undoStack.clear()

            self.sizeChanged.emit(*self.iconSize())

    def iconImage(self):
        """
        Public method to get a copy of the icon image.
        
        @return copy of the icon image (QImage)
        """
        return QImage(self.__image)

    def iconSize(self):
        """
        Public method to get the size of the icon.
        
        @return width and height of the image as a tuple (integer, integer)
        """
        return self.__image.width(), self.__image.height()

    def setZoomFactor(self, newZoom):
        """
        Public method to set the zoom factor in percent.
        
        @param newZoom zoom factor (integer >= 100)
        """
        newZoom = max(100, newZoom)  # must not be less than 100
        if newZoom != self.__zoom:
            self.__zoom = newZoom // 100
            self.update()
            self.updateGeometry()
            self.resize(self.sizeHint())
            self.zoomChanged.emit(int(self.__zoom * 100))

    def zoomFactor(self):
        """
        Public method to get the current zoom factor in percent.
        
        @return zoom factor (integer)
        """
        return self.__zoom * 100

    def setGridEnabled(self, enable):
        """
        Public method to enable the display of grid lines.
        
        @param enable enabled status of the grid lines (boolean)
        """
        if enable != self.__gridEnabled:
            self.__gridEnabled = enable
            self.update()

    def isGridEnabled(self):
        """
        Public method to get the grid lines status.
        
        @return enabled status of the grid lines (boolean)
        """
        return self.__gridEnabled

    def paintEvent(self, evt):
        """
        Protected method called to repaint some of the widget.
        
        @param evt reference to the paint event object (QPaintEvent)
        """
        painter = QPainter(self)

        if self.__zoom >= 3 and self.__gridEnabled:
            painter.setPen(self.palette().windowText().color())
            i = 0
            while i <= self.__image.width():
                painter.drawLine(self.__zoom * i, 0, self.__zoom * i,
                                 self.__zoom * self.__image.height())
                i += 1
            j = 0
            while j <= self.__image.height():
                painter.drawLine(0, self.__zoom * j,
                                 self.__zoom * self.__image.width(),
                                 self.__zoom * j)
                j += 1

        col = QColor("#aaa")
        painter.setPen(Qt.DashLine)
        for i in range(0, self.__image.width()):
            for j in range(0, self.__image.height()):
                rect = self.__pixelRect(i, j)
                if evt.region().intersects(rect):
                    color = QColor.fromRgba(self.__image.pixel(i, j))
                    painter.fillRect(rect, QBrush(Qt.white))
                    painter.fillRect(QRect(rect.topLeft(), rect.center()), col)
                    painter.fillRect(QRect(rect.center(), rect.bottomRight()),
                                     col)
                    painter.fillRect(rect, QBrush(color))

                    if self.__isMarked(i, j):
                        painter.drawRect(rect.adjusted(0, 0, -1, -1))

        painter.end()

    def __pixelRect(self, i, j):
        """
        Private method to determine the rectangle for a given pixel coordinate.
        
        @param i x-coordinate of the pixel in the image (integer)
        @param j y-coordinate of the pixel in the image (integer)
        @return rectangle for the given pixel coordinates (QRect)
        """
        if self.__zoom >= 3 and self.__gridEnabled:
            return QRect(self.__zoom * i + 1, self.__zoom * j + 1,
                         self.__zoom - 1, self.__zoom - 1)
        else:
            return QRect(self.__zoom * i, self.__zoom * j, self.__zoom,
                         self.__zoom)

    def mousePressEvent(self, evt):
        """
        Protected method to handle mouse button press events.
        
        @param evt reference to the mouse event object (QMouseEvent)
        """
        if evt.button() == Qt.LeftButton:
            if self.__isPasting:
                self.__isPasting = False
                self.editPaste(True)
                self.__markImage.fill(self.NoMarkColor.rgba())
                self.update(self.__pasteRect)
                self.__pasteRect = QRect()
                return

            if self.__curTool == self.Pencil:
                cmd = IconEditCommand(self, self.__undoTexts[self.__curTool],
                                      self.__image)
                self.__setImagePixel(evt.pos(), True)
                self.setDirty(True)
                self.__undoStack.push(cmd)
                self.__currentUndoCmd = cmd
            elif self.__curTool == self.Rubber:
                cmd = IconEditCommand(self, self.__undoTexts[self.__curTool],
                                      self.__image)
                self.__setImagePixel(evt.pos(), False)
                self.setDirty(True)
                self.__undoStack.push(cmd)
                self.__currentUndoCmd = cmd
            elif self.__curTool == self.Fill:
                i, j = self.__imageCoordinates(evt.pos())
                col = QColor()
                col.setRgba(self.__image.pixel(i, j))
                cmd = IconEditCommand(self, self.__undoTexts[self.__curTool],
                                      self.__image)
                self.__drawFlood(i, j, col)
                self.setDirty(True)
                self.__undoStack.push(cmd)
                cmd.setAfterImage(self.__image)
            elif self.__curTool == self.ColorPicker:
                i, j = self.__imageCoordinates(evt.pos())
                col = QColor()
                col.setRgba(self.__image.pixel(i, j))
                self.setPenColor(col)
            else:
                self.__unMark()
                self.__startPos = evt.pos()
                self.__endPos = evt.pos()

    def mouseMoveEvent(self, evt):
        """
        Protected method to handle mouse move events.
        
        @param evt reference to the mouse event object (QMouseEvent)
        """
        self.positionChanged.emit(*self.__imageCoordinates(evt.pos()))

        if self.__isPasting and not (evt.buttons() & Qt.LeftButton):
            self.__drawPasteRect(evt.pos())
            return

        if evt.buttons() & Qt.LeftButton:
            if self.__curTool == self.Pencil:
                self.__setImagePixel(evt.pos(), True)
                self.setDirty(True)
            elif self.__curTool == self.Rubber:
                self.__setImagePixel(evt.pos(), False)
                self.setDirty(True)
            elif self.__curTool in [self.Fill, self.ColorPicker]:
                pass  # do nothing
            else:
                self.__drawTool(evt.pos(), True)

    def mouseReleaseEvent(self, evt):
        """
        Protected method to handle mouse button release events.
        
        @param evt reference to the mouse event object (QMouseEvent)
        """
        if evt.button() == Qt.LeftButton:
            if self.__curTool in [self.Pencil, self.Rubber]:
                if self.__currentUndoCmd:
                    self.__currentUndoCmd.setAfterImage(self.__image)
                    self.__currentUndoCmd = None

            if self.__curTool not in [
                    self.Pencil, self.Rubber, self.Fill, self.ColorPicker,
                    self.RectangleSelection, self.CircleSelection
            ]:
                cmd = IconEditCommand(self, self.__undoTexts[self.__curTool],
                                      self.__image)
                if self.__drawTool(evt.pos(), False):
                    self.__undoStack.push(cmd)
                    cmd.setAfterImage(self.__image)
                    self.setDirty(True)

    def __setImagePixel(self, pos, opaque):
        """
        Private slot to set or erase a pixel.
        
        @param pos position of the pixel in the widget (QPoint)
        @param opaque flag indicating a set operation (boolean)
        """
        i, j = self.__imageCoordinates(pos)

        if self.__image.rect().contains(i, j) and (i, j) != self.__lastPos:
            if opaque:
                painter = QPainter(self.__image)
                painter.setPen(self.penColor())
                painter.setCompositionMode(self.__compositingMode)
                painter.drawPoint(i, j)
            else:
                self.__image.setPixel(i, j, qRgba(0, 0, 0, 0))
            self.__lastPos = (i, j)

            self.update(self.__pixelRect(i, j))

    def __imageCoordinates(self, pos):
        """
        Private method to convert from widget to image coordinates.
        
        @param pos widget coordinate (QPoint)
        @return tuple with the image coordinates (tuple of two integers)
        """
        i = pos.x() // self.__zoom
        j = pos.y() // self.__zoom
        return i, j

    def __drawPasteRect(self, pos):
        """
        Private slot to draw a rectangle for signaling a paste operation.
        
        @param pos widget position of the paste rectangle (QPoint)
        """
        self.__markImage.fill(self.NoMarkColor.rgba())
        if self.__pasteRect.isValid():
            self.__updateImageRect(
                self.__pasteRect.topLeft(),
                self.__pasteRect.bottomRight() + QPoint(1, 1))

        x, y = self.__imageCoordinates(pos)
        isize = self.__image.size()
        if x + self.__clipboardSize.width() <= isize.width():
            sx = self.__clipboardSize.width()
        else:
            sx = isize.width() - x
        if y + self.__clipboardSize.height() <= isize.height():
            sy = self.__clipboardSize.height()
        else:
            sy = isize.height() - y

        self.__pasteRect = QRect(QPoint(x, y), QSize(sx - 1, sy - 1))

        painter = QPainter(self.__markImage)
        painter.setPen(self.MarkColor)
        painter.drawRect(self.__pasteRect)
        painter.end()

        self.__updateImageRect(self.__pasteRect.topLeft(),
                               self.__pasteRect.bottomRight() + QPoint(1, 1))

    def __drawTool(self, pos, mark):
        """
        Private method to perform a draw operation depending of the current
        tool.
        
        @param pos widget coordinate to perform the draw operation at (QPoint)
        @param mark flag indicating a mark operation (boolean)
        @return flag indicating a successful draw (boolean)
        """
        self.__unMark()

        if mark:
            self.__endPos = QPoint(pos)
            drawColor = self.MarkColor
            img = self.__markImage
        else:
            drawColor = self.penColor()
            img = self.__image

        start = QPoint(*self.__imageCoordinates(self.__startPos))
        end = QPoint(*self.__imageCoordinates(pos))

        painter = QPainter(img)
        painter.setPen(drawColor)
        painter.setCompositionMode(self.__compositingMode)

        if self.__curTool == self.Line:
            painter.drawLine(start, end)

        elif self.__curTool in [
                self.Rectangle, self.FilledRectangle, self.RectangleSelection
        ]:
            left = min(start.x(), end.x())
            top = min(start.y(), end.y())
            right = max(start.x(), end.x())
            bottom = max(start.y(), end.y())
            if self.__curTool == self.RectangleSelection:
                painter.setBrush(QBrush(drawColor))
            if self.__curTool == self.FilledRectangle:
                for y in range(top, bottom + 1):
                    painter.drawLine(left, y, right, y)
            else:
                painter.drawRect(left, top, right - left, bottom - top)
            if self.__selecting:
                self.__selRect = QRect(left, top, right - left + 1,
                                       bottom - top + 1)
                self.__selectionAvailable = True
                self.selectionAvailable.emit(True)

        elif self.__curTool in [
                self.Circle, self.FilledCircle, self.CircleSelection
        ]:
            r = max(abs(start.x() - end.x()), abs(start.y() - end.y()))
            if self.__curTool in [self.FilledCircle, self.CircleSelection]:
                painter.setBrush(QBrush(drawColor))
            painter.drawEllipse(start, r, r)
            if self.__selecting:
                self.__selRect = QRect(start.x() - r,
                                       start.y() - r, 2 * r + 1, 2 * r + 1)
                self.__selectionAvailable = True
                self.selectionAvailable.emit(True)

        elif self.__curTool in [self.Ellipse, self.FilledEllipse]:
            r1 = abs(start.x() - end.x())
            r2 = abs(start.y() - end.y())
            if r1 == 0 or r2 == 0:
                return False
            if self.__curTool == self.FilledEllipse:
                painter.setBrush(QBrush(drawColor))
            painter.drawEllipse(start, r1, r2)

        painter.end()

        if self.__curTool in [
                self.Circle, self.FilledCircle, self.Ellipse,
                self.FilledEllipse
        ]:
            self.update()
        else:
            self.__updateRect(self.__startPos, pos)

        return True

    def __drawFlood(self, i, j, oldColor, doUpdate=True):
        """
        Private method to perform a flood fill operation.
        
        @param i x-value in image coordinates (integer)
        @param j y-value in image coordinates (integer)
        @param oldColor reference to the color at position i, j (QColor)
        @param doUpdate flag indicating an update is requested (boolean)
            (used for speed optimizations)
        """
        if not self.__image.rect().contains(i, j) or \
           self.__image.pixel(i, j) != oldColor.rgba() or \
           self.__image.pixel(i, j) == self.penColor().rgba():
            return

        self.__image.setPixel(i, j, self.penColor().rgba())

        self.__drawFlood(i, j - 1, oldColor, False)
        self.__drawFlood(i, j + 1, oldColor, False)
        self.__drawFlood(i - 1, j, oldColor, False)
        self.__drawFlood(i + 1, j, oldColor, False)

        if doUpdate:
            self.update()

    def __updateRect(self, pos1, pos2):
        """
        Private slot to update parts of the widget.
        
        @param pos1 top, left position for the update in widget coordinates
            (QPoint)
        @param pos2 bottom, right position for the update in widget
            coordinates (QPoint)
        """
        self.__updateImageRect(QPoint(*self.__imageCoordinates(pos1)),
                               QPoint(*self.__imageCoordinates(pos2)))

    def __updateImageRect(self, ipos1, ipos2):
        """
        Private slot to update parts of the widget.
        
        @param ipos1 top, left position for the update in image coordinates
            (QPoint)
        @param ipos2 bottom, right position for the update in image
            coordinates (QPoint)
        """
        r1 = self.__pixelRect(ipos1.x(), ipos1.y())
        r2 = self.__pixelRect(ipos2.x(), ipos2.y())

        left = min(r1.x(), r2.x())
        top = min(r1.y(), r2.y())
        right = max(r1.x() + r1.width(), r2.x() + r2.width())
        bottom = max(r1.y() + r1.height(), r2.y() + r2.height())
        self.update(left, top, right - left + 1, bottom - top + 1)

    def __unMark(self):
        """
        Private slot to remove the mark indicator.
        """
        self.__markImage.fill(self.NoMarkColor.rgba())
        if self.__curTool in [
                self.Circle, self.FilledCircle, self.Ellipse,
                self.FilledEllipse, self.CircleSelection
        ]:
            self.update()
        else:
            self.__updateRect(self.__startPos, self.__endPos)

        if self.__selecting:
            self.__selRect = QRect()
            self.__selectionAvailable = False
            self.selectionAvailable.emit(False)

    def __isMarked(self, i, j):
        """
        Private method to check, if a pixel is marked.
        
        @param i x-value in image coordinates (integer)
        @param j y-value in image coordinates (integer)
        @return flag indicating a marked pixel (boolean)
        """
        return self.__markImage.pixel(i, j) == self.MarkColor.rgba()

    def __updatePreviewPixmap(self):
        """
        Private slot to generate and signal an updated preview pixmap.
        """
        p = QPixmap.fromImage(self.__image)
        self.previewChanged.emit(p)

    def previewPixmap(self):
        """
        Public method to generate a preview pixmap.
        
        @return preview pixmap (QPixmap)
        """
        p = QPixmap.fromImage(self.__image)
        return p

    def __checkClipboard(self):
        """
        Private slot to check, if the clipboard contains a valid image, and
        signal the result.
        """
        ok = self.__clipboardImage()[1]
        self.__clipboardImageAvailable = ok
        self.clipboardImageAvailable.emit(ok)

    def canPaste(self):
        """
        Public slot to check the availability of the paste operation.
        
        @return flag indicating availability of paste (boolean)
        """
        return self.__clipboardImageAvailable

    def __clipboardImage(self):
        """
        Private method to get an image from the clipboard.
        
        @return tuple with the image (QImage) and a flag indicating a
            valid image (boolean)
        """
        img = QApplication.clipboard().image()
        ok = not img.isNull()
        if ok:
            img = img.convertToFormat(QImage.Format_ARGB32)

        return img, ok

    def __getSelectionImage(self, cut):
        """
        Private method to get an image from the selection.
        
        @param cut flag indicating to cut the selection (boolean)
        @return image of the selection (QImage)
        """
        if cut:
            cmd = IconEditCommand(self, self.tr("Cut Selection"), self.__image)

        img = QImage(self.__selRect.size(), QImage.Format_ARGB32)
        img.fill(qRgba(0, 0, 0, 0))
        for i in range(0, self.__selRect.width()):
            for j in range(0, self.__selRect.height()):
                if self.__image.rect().contains(self.__selRect.x() + i,
                                                self.__selRect.y() + j):
                    if self.__isMarked(self.__selRect.x() + i,
                                       self.__selRect.y() + j):
                        img.setPixel(
                            i, j,
                            self.__image.pixel(self.__selRect.x() + i,
                                               self.__selRect.y() + j))
                        if cut:
                            self.__image.setPixel(self.__selRect.x() + i,
                                                  self.__selRect.y() + j,
                                                  qRgba(0, 0, 0, 0))

        if cut:
            self.__undoStack.push(cmd)
            cmd.setAfterImage(self.__image)

        self.__unMark()

        if cut:
            self.update(self.__selRect)

        return img

    def editCopy(self):
        """
        Public slot to copy the selection.
        """
        if self.__selRect.isValid():
            img = self.__getSelectionImage(False)
            QApplication.clipboard().setImage(img)

    def editCut(self):
        """
        Public slot to cut the selection.
        """
        if self.__selRect.isValid():
            img = self.__getSelectionImage(True)
            QApplication.clipboard().setImage(img)

    @pyqtSlot()
    def editPaste(self, pasting=False):
        """
        Public slot to paste an image from the clipboard.
        
        @param pasting flag indicating part two of the paste operation
            (boolean)
        """
        img, ok = self.__clipboardImage()
        if ok:
            if img.width() > self.__image.width() or \
                    img.height() > self.__image.height():
                res = E5MessageBox.yesNo(
                    self, self.tr("Paste"),
                    self.tr("""<p>The clipboard image is larger than the"""
                            """ current image.<br/>Paste as new image?</p>"""))
                if res:
                    self.editPasteAsNew()
                return
            elif not pasting:
                self.__isPasting = True
                self.__clipboardSize = img.size()
            else:
                cmd = IconEditCommand(self, self.tr("Paste Clipboard"),
                                      self.__image)
                self.__markImage.fill(self.NoMarkColor.rgba())
                painter = QPainter(self.__image)
                painter.setPen(self.penColor())
                painter.setCompositionMode(self.__compositingMode)
                painter.drawImage(self.__pasteRect.x(), self.__pasteRect.y(),
                                  img, 0, 0,
                                  self.__pasteRect.width() + 1,
                                  self.__pasteRect.height() + 1)

                self.__undoStack.push(cmd)
                cmd.setAfterImage(self.__image)

                self.__updateImageRect(
                    self.__pasteRect.topLeft(),
                    self.__pasteRect.bottomRight() + QPoint(1, 1))
        else:
            E5MessageBox.warning(
                self, self.tr("Pasting Image"),
                self.tr("""Invalid image data in clipboard."""))

    def editPasteAsNew(self):
        """
        Public slot to paste the clipboard as a new image.
        """
        img, ok = self.__clipboardImage()
        if ok:
            cmd = IconEditCommand(self,
                                  self.tr("Paste Clipboard as New Image"),
                                  self.__image)
            self.setIconImage(img)
            self.setDirty(True)
            self.__undoStack.push(cmd)
            cmd.setAfterImage(self.__image)

    def editSelectAll(self):
        """
        Public slot to select the complete image.
        """
        self.__unMark()

        self.__startPos = QPoint(0, 0)
        self.__endPos = QPoint(self.rect().bottomRight())
        self.__markImage.fill(self.MarkColor.rgba())
        self.__selRect = self.__image.rect()
        self.__selectionAvailable = True
        self.selectionAvailable.emit(True)

        self.update()

    def editClear(self):
        """
        Public slot to clear the image.
        """
        self.__unMark()

        cmd = IconEditCommand(self, self.tr("Clear Image"), self.__image)
        self.__image.fill(qRgba(0, 0, 0, 0))
        self.update()
        self.setDirty(True)
        self.__undoStack.push(cmd)
        cmd.setAfterImage(self.__image)

    def editResize(self):
        """
        Public slot to resize the image.
        """
        from .IconSizeDialog import IconSizeDialog
        dlg = IconSizeDialog(self.__image.width(), self.__image.height())
        res = dlg.exec_()
        if res == QDialog.Accepted:
            newWidth, newHeight = dlg.getData()
            if newWidth != self.__image.width() or \
                    newHeight != self.__image.height():
                cmd = IconEditCommand(self, self.tr("Resize Image"),
                                      self.__image)
                img = self.__image.scaled(newWidth, newHeight,
                                          Qt.IgnoreAspectRatio,
                                          Qt.SmoothTransformation)
                self.setIconImage(img)
                self.setDirty(True)
                self.__undoStack.push(cmd)
                cmd.setAfterImage(self.__image)

    def editNew(self):
        """
        Public slot to generate a new, empty image.
        """
        from .IconSizeDialog import IconSizeDialog
        dlg = IconSizeDialog(self.__image.width(), self.__image.height())
        res = dlg.exec_()
        if res == QDialog.Accepted:
            width, height = dlg.getData()
            img = QImage(width, height, QImage.Format_ARGB32)
            img.fill(qRgba(0, 0, 0, 0))
            self.setIconImage(img)

    def grayScale(self):
        """
        Public slot to convert the image to gray preserving transparency.
        """
        cmd = IconEditCommand(self, self.tr("Convert to Grayscale"),
                              self.__image)
        for x in range(self.__image.width()):
            for y in range(self.__image.height()):
                col = self.__image.pixel(x, y)
                if col != qRgba(0, 0, 0, 0):
                    gray = qGray(col)
                    self.__image.setPixel(x, y,
                                          qRgba(gray, gray, gray, qAlpha(col)))
        self.update()
        self.setDirty(True)
        self.__undoStack.push(cmd)
        cmd.setAfterImage(self.__image)

    def editUndo(self):
        """
        Public slot to perform an undo operation.
        """
        if self.__undoStack.canUndo():
            self.__undoStack.undo()

    def editRedo(self):
        """
        Public slot to perform a redo operation.
        """
        if self.__undoStack.canRedo():
            self.__undoStack.redo()

    def canUndo(self):
        """
        Public method to return the undo status.
        
        @return flag indicating the availability of undo (boolean)
        """
        return self.__undoStack.canUndo()

    def canRedo(self):
        """
        Public method to return the redo status.
        
        @return flag indicating the availability of redo (boolean)
        """
        return self.__undoStack.canRedo()

    def __cleanChanged(self, clean):
        """
        Private slot to handle the undo stack clean state change.
        
        @param clean flag indicating the clean state (boolean)
        """
        self.setDirty(not clean)

    def shutdown(self):
        """
        Public slot to perform some shutdown actions.
        """
        self.__undoStack.canRedoChanged.disconnect(self.canRedoChanged)
        self.__undoStack.canUndoChanged.disconnect(self.canUndoChanged)
        self.__undoStack.cleanChanged.disconnect(self.__cleanChanged)

    def isSelectionAvailable(self):
        """
        Public method to check the availability of a selection.
        
        @return flag indicating the availability of a selection (boolean)
        """
        return self.__selectionAvailable
示例#34
0
class PuzzleShow(QWidget):

    '''
    拼图的合成区
    '''

    puzzleCompleted = pyqtSignal()
    dropCompleted = pyqtSignal()

    def __init__(self, imageSize):
        super().__init__()
        self.imageSize = imageSize
        # 载入图片的大小

        self.inPlace = 0
        # 位置摆放正确的拼图数量

        self.piecePixmaps = []
        # 存储从拼图展示区拖到拼图合成区的拼图

        self.pieceRects = []
        # 存储拼图放到拼图合成区的目标矩形

        self.pieceLocations = []
        # 存储从拼图展示区拖到拼图合成区的拼图的位置

        self.highlightedRect = QRect()
        # 拼图放到拼图合成区的目标矩形,以高亮形式出现

        self.initUi()

    def initUi(self):
        self.setAcceptDrops(True)
        self.setMaximumSize(self.imageSize, self.imageSize)
        self.setMinimumSize(self.imageSize, self.imageSize)

    def dragEnterEvent(self, event):
        """
        鼠标移入准备拖动
        """
        if event.mimeData().hasFormat("image/x-puzzle-xdbcb8"):
            event.accept()
        else:
            event.ignore()
        # 鼠标移入准备拖动,因为你放置的拼图不一定合适,需要再次调整放置

    def dragLeaveEvent(self, event):
        """
        鼠标移开小窗口
        """
        updateRect = self.highlightedRect
        self.highlightedRect = QRect()
        self.update(updateRect)
        event.accept()
        # 避免多了一个高亮的矩形出来

    def dragMoveEvent(self, event):
        """
        拖动
        """
        updateRect = self.highlightedRect.united(self.targetSquare(event.pos()))
        # 两个矩形的拼接

        if event.mimeData().hasFormat("image/x-puzzle-xdbcb8") and self.findPiece(self.targetSquare(event.pos())) == -1:
            self.highlightedRect = self.targetSquare(event.pos())
            # 若我们拖动时符合要求的,我们将取得一个矩形,这个矩形就是我们可以放置在拼图合成区的目标矩形,并将其赋值给self.highlightedRect
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            self.highlightedRect = QRect()
            event.ignore()

        self.update(updateRect)

    def dropEvent(self, event):
        """
        拖动结束,图片落下
        """
        if event.mimeData().hasFormat("image/x-puzzle-xdbcb8") and self.findPiece(self.targetSquare(event.pos())) == -1:
            # 要是我们拖动的符合要求以及没有在存储目标矩形的列表中找到的话
            pieceData = event.mimeData().data("image/x-puzzle-xdbcb8")
            dataStream = QDataStream(pieceData, QIODevice.ReadOnly)
            square = self.targetSquare(event.pos())
            pixmap = QPixmap()
            location = QPoint()
            dataStream >> pixmap >> location

            self.pieceLocations.append(location)
            self.piecePixmaps.append(pixmap)
            self.pieceRects.append(square)
            # 把拖动拼图的位置、图片、以及目标矩形分别存入self.pieceLocations、self.piecePixmaps、self.pieceRects

            self.highlightedRect = QRect()
            self.update(square)
            # 必须要有这句话,否则,哼哼!
    
            event.setDropAction(Qt.MoveAction)
            event.accept()

            if location == QPoint(square.x()/self.pieceSize(), square.y()/self.pieceSize()):
                self.inPlace += 1
                # 要是我们放置的拼图位置和正确的拼图位置是一样的,self.inPlace += 1。
                if self.inPlace == 25:
                    self.puzzleCompleted.emit()
                    # 要是正确位置的拼图达到25个,发射puzzleCompleted信号。

        else:
            self.highlightedRect = QRect()
            event.ignore()

    def mousePressEvent(self, event):
        """
        鼠标按下事件(QWidget中是没有self.setDragEnabled(True)这个属性的。故我们重写了mousePressEvent方法)
        """
        square = self.targetSquare(event.pos())
        found = self.findPiece(square)
        if found == -1:
            return
        # 先看看鼠标点击的地方有没有我们的目标矩形,没有的话直接return,有的话返回相关的索引
        location = self.pieceLocations[found]
        pixmap = self.piecePixmaps[found]
        del self.pieceLocations[found]
        del self.piecePixmaps[found]
        del self.pieceRects[found]
    
        if location == QPoint(square.x()/self.pieceSize(), square.y()/self.pieceSize()):
            self.inPlace -= 1
        # 得到位置信息、拼图信息,并把它们从相关的列表中删除,当然拼图正确的总数也要减1
        self.update(square)
    
        itemData = QByteArray()
        dataStream = QDataStream(itemData, QIODevice.WriteOnly)
    
        dataStream << pixmap << location
        # 相关的变量放入数据流中。
    
        mimeData = QMimeData()
        mimeData.setData("image/x-puzzle-xdbcb8", itemData)
    
        drag = QDrag(self)
        drag.setMimeData(mimeData)
        drag.setHotSpot(event.pos() - square.topLeft())
        drag.setPixmap(pixmap)
        # 新建一个QDrag对象,该对象中设置mimeData,HotSpot和图片信息
    
        if drag.exec(Qt.MoveAction) != Qt.MoveAction:
            self.pieceLocations.insert(found, location)
            self.piecePixmaps.insert(found, pixmap)
            self.pieceRects.insert(found, square)
            self.update(self.targetSquare(event.pos()))
    
            if location == QPoint(square.x()/self.pieceSize(), square.y()/self.pieceSize()):
                self.inPlace += 1
                # 正确的拼图数也要加上1

        # 要是drag不是拖动,那么我们给self.pieceLocations、self.piecePixmaps、self.pieceRects
        # 按照之前找到的索引增加位置、图片和目标矩形信息,相当于把之前删除掉的又加上

    def paintEvent(self, event):
        """
        绘画事件
        """
        painter = QPainter()
        painter.begin(self)
        painter.fillRect(event.rect(), Qt.white)
        if self.highlightedRect.isValid():
            painter.setBrush(QColor("#E6E6FA"))
            painter.setPen(Qt.NoPen)
            painter.drawRect(self.highlightedRect.adjusted(0, 0, -1, -1))
            # 首先绘制目标矩形
    
        for i in range(len(self.pieceRects)):
            painter.drawPixmap(self.pieceRects[i], self.piecePixmaps[i])
        painter.end()
        # 到目前矩形后,要画出来,所以就重写绘图事件

    def targetSquare(self, position):
        """
        拼图放下的矩形位置
        """
        x = position.x()//self.pieceSize() * self.pieceSize()
        y = position.y()//self.pieceSize() * self.pieceSize()
        return QRect(x, y, self.pieceSize(), self.pieceSize())
        # x、y表示的是拼图放下具体矩形位置的x、y坐标,矩形的大小是拼图的大小

    def findPiece(self, piece_Rect):
        """
        找到拼图
        """
        try:
            return self.pieceRects.index(piece_Rect)
        except ValueError:
            return -1
        # 找一找拼图合成区中的单个拼图的矩形是否已经在pieceRects列表中,有的话返回索引,没有的话返回-1。
    
    def pieceSize(self):
        """
        单个拼图的大小
        """
        return int(self.imageSize // 5)

    def clear(self):
        """
        相关数据清空。这个我们在游戏初始化的时候会用到
        """
        self.piecePixmaps.clear()
        self.pieceRects = []
        self.pieceLocations = []
        self.highlightedRect = QRect()
        self.inPlace = 0
        self.update()
示例#35
0
class IconEditorGrid(QWidget):
    """
    Class implementing the icon editor grid.
    
    @signal canRedoChanged(bool) emitted after the redo status has changed
    @signal canUndoChanged(bool) emitted after the undo status has changed
    @signal clipboardImageAvailable(bool) emitted to signal the availability
        of an image to be pasted
    @signal colorChanged(QColor) emitted after the drawing color was changed
    @signal imageChanged(bool) emitted after the image was modified
    @signal positionChanged(int, int) emitted after the cursor poition was
        changed
    @signal previewChanged(QPixmap) emitted to signal a new preview pixmap
    @signal selectionAvailable(bool) emitted to signal a change of the
        selection
    @signal sizeChanged(int, int) emitted after the size has been changed
    @signal zoomChanged(int) emitted to signal a change of the zoom value
    """
    canRedoChanged = pyqtSignal(bool)
    canUndoChanged = pyqtSignal(bool)
    clipboardImageAvailable = pyqtSignal(bool)
    colorChanged = pyqtSignal(QColor)
    imageChanged = pyqtSignal(bool)
    positionChanged = pyqtSignal(int, int)
    previewChanged = pyqtSignal(QPixmap)
    selectionAvailable = pyqtSignal(bool)
    sizeChanged = pyqtSignal(int, int)
    zoomChanged = pyqtSignal(int)
    
    Pencil = 1
    Rubber = 2
    Line = 3
    Rectangle = 4
    FilledRectangle = 5
    Circle = 6
    FilledCircle = 7
    Ellipse = 8
    FilledEllipse = 9
    Fill = 10
    ColorPicker = 11
    
    RectangleSelection = 20
    CircleSelection = 21
    
    MarkColor = QColor(255, 255, 255, 255)
    NoMarkColor = QColor(0, 0, 0, 0)
    
    ZoomMinimum = 100
    ZoomMaximum = 10000
    ZoomStep = 100
    ZoomDefault = 1200
    ZoomPercent = True
    
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(IconEditorGrid, self).__init__(parent)
        
        self.setAttribute(Qt.WA_StaticContents)
        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        
        self.__curColor = Qt.black
        self.__zoom = 12
        self.__curTool = self.Pencil
        self.__startPos = QPoint()
        self.__endPos = QPoint()
        self.__dirty = False
        self.__selecting = False
        self.__selRect = QRect()
        self.__isPasting = False
        self.__clipboardSize = QSize()
        self.__pasteRect = QRect()
        
        self.__undoStack = QUndoStack(self)
        self.__currentUndoCmd = None
        
        self.__image = QImage(32, 32, QImage.Format_ARGB32)
        self.__image.fill(qRgba(0, 0, 0, 0))
        self.__markImage = QImage(self.__image)
        self.__markImage.fill(self.NoMarkColor.rgba())
        
        self.__compositingMode = QPainter.CompositionMode_SourceOver
        self.__lastPos = (-1, -1)
        
        self.__gridEnabled = True
        self.__selectionAvailable = False
        
        self.__initCursors()
        self.__initUndoTexts()
        
        self.setMouseTracking(True)
        
        self.__undoStack.canRedoChanged.connect(self.canRedoChanged)
        self.__undoStack.canUndoChanged.connect(self.canUndoChanged)
        self.__undoStack.cleanChanged.connect(self.__cleanChanged)
        
        self.imageChanged.connect(self.__updatePreviewPixmap)
        QApplication.clipboard().dataChanged.connect(self.__checkClipboard)
        
        self.__checkClipboard()
    
    def __initCursors(self):
        """
        Private method to initialize the various cursors.
        """
        self.__normalCursor = QCursor(Qt.ArrowCursor)
        
        pix = QPixmap(":colorpicker-cursor.xpm")
        mask = pix.createHeuristicMask()
        pix.setMask(mask)
        self.__colorPickerCursor = QCursor(pix, 1, 21)
        
        pix = QPixmap(":paintbrush-cursor.xpm")
        mask = pix.createHeuristicMask()
        pix.setMask(mask)
        self.__paintCursor = QCursor(pix, 0, 19)
        
        pix = QPixmap(":fill-cursor.xpm")
        mask = pix.createHeuristicMask()
        pix.setMask(mask)
        self.__fillCursor = QCursor(pix, 3, 20)
        
        pix = QPixmap(":aim-cursor.xpm")
        mask = pix.createHeuristicMask()
        pix.setMask(mask)
        self.__aimCursor = QCursor(pix, 10, 10)
        
        pix = QPixmap(":eraser-cursor.xpm")
        mask = pix.createHeuristicMask()
        pix.setMask(mask)
        self.__rubberCursor = QCursor(pix, 1, 16)
    
    def __initUndoTexts(self):
        """
        Private method to initialize texts to be associated with undo commands
        for the various drawing tools.
        """
        self.__undoTexts = {
            self.Pencil: self.tr("Set Pixel"),
            self.Rubber: self.tr("Erase Pixel"),
            self.Line: self.tr("Draw Line"),
            self.Rectangle: self.tr("Draw Rectangle"),
            self.FilledRectangle: self.tr("Draw Filled Rectangle"),
            self.Circle: self.tr("Draw Circle"),
            self.FilledCircle: self.tr("Draw Filled Circle"),
            self.Ellipse: self.tr("Draw Ellipse"),
            self.FilledEllipse: self.tr("Draw Filled Ellipse"),
            self.Fill: self.tr("Fill Region"),
        }
    
    def isDirty(self):
        """
        Public method to check the dirty status.
        
        @return flag indicating a modified status (boolean)
        """
        return self.__dirty
    
    def setDirty(self, dirty, setCleanState=False):
        """
        Public slot to set the dirty flag.
        
        @param dirty flag indicating the new modification status (boolean)
        @param setCleanState flag indicating to set the undo stack to clean
            (boolean)
        """
        self.__dirty = dirty
        self.imageChanged.emit(dirty)
        
        if not dirty and setCleanState:
            self.__undoStack.setClean()
    
    def sizeHint(self):
        """
        Public method to report the size hint.
        
        @return size hint (QSize)
        """
        size = self.__zoom * self.__image.size()
        if self.__zoom >= 3 and self.__gridEnabled:
            size += QSize(1, 1)
        return size
    
    def setPenColor(self, newColor):
        """
        Public method to set the drawing color.
        
        @param newColor reference to the new color (QColor)
        """
        self.__curColor = QColor(newColor)
        self.colorChanged.emit(QColor(newColor))
    
    def penColor(self):
        """
        Public method to get the current drawing color.
        
        @return current drawing color (QColor)
        """
        return QColor(self.__curColor)
    
    def setCompositingMode(self, mode):
        """
        Public method to set the compositing mode.
        
        @param mode compositing mode to set (QPainter.CompositionMode)
        """
        self.__compositingMode = mode
    
    def compositingMode(self):
        """
        Public method to get the compositing mode.
        
        @return compositing mode (QPainter.CompositionMode)
        """
        return self.__compositingMode
    
    def setTool(self, tool):
        """
        Public method to set the current drawing tool.
        
        @param tool drawing tool to be used
            (IconEditorGrid.Pencil ... IconEditorGrid.CircleSelection)
        """
        self.__curTool = tool
        self.__lastPos = (-1, -1)
        
        if self.__curTool in [self.RectangleSelection, self.CircleSelection]:
            self.__selecting = True
        else:
            self.__selecting = False
        
        if self.__curTool in [self.RectangleSelection, self.CircleSelection,
                              self.Line, self.Rectangle, self.FilledRectangle,
                              self.Circle, self.FilledCircle,
                              self.Ellipse, self.FilledEllipse]:
            self.setCursor(self.__aimCursor)
        elif self.__curTool == self.Fill:
            self.setCursor(self.__fillCursor)
        elif self.__curTool == self.ColorPicker:
            self.setCursor(self.__colorPickerCursor)
        elif self.__curTool == self.Pencil:
            self.setCursor(self.__paintCursor)
        elif self.__curTool == self.Rubber:
            self.setCursor(self.__rubberCursor)
        else:
            self.setCursor(self.__normalCursor)
    
    def tool(self):
        """
        Public method to get the current drawing tool.
        
        @return current drawing tool
            (IconEditorGrid.Pencil ... IconEditorGrid.CircleSelection)
        """
        return self.__curTool
    
    def setIconImage(self, newImage, undoRedo=False, clearUndo=False):
        """
        Public method to set a new icon image.
        
        @param newImage reference to the new image (QImage)
        @keyparam undoRedo flag indicating an undo or redo operation (boolean)
        @keyparam clearUndo flag indicating to clear the undo stack (boolean)
        """
        if newImage != self.__image:
            self.__image = newImage.convertToFormat(QImage.Format_ARGB32)
            self.update()
            self.updateGeometry()
            self.resize(self.sizeHint())
            
            self.__markImage = QImage(self.__image)
            self.__markImage.fill(self.NoMarkColor.rgba())
            
            if undoRedo:
                self.setDirty(not self.__undoStack.isClean())
            else:
                self.setDirty(False)
            
            if clearUndo:
                self.__undoStack.clear()
            
            self.sizeChanged.emit(*self.iconSize())
    
    def iconImage(self):
        """
        Public method to get a copy of the icon image.
        
        @return copy of the icon image (QImage)
        """
        return QImage(self.__image)
    
    def iconSize(self):
        """
        Public method to get the size of the icon.
        
        @return width and height of the image as a tuple (integer, integer)
        """
        return self.__image.width(), self.__image.height()
    
    def setZoomFactor(self, newZoom):
        """
        Public method to set the zoom factor in percent.
        
        @param newZoom zoom factor (integer >= 100)
        """
        newZoom = max(100, newZoom)   # must not be less than 100
        if newZoom != self.__zoom:
            self.__zoom = newZoom // 100
            self.update()
            self.updateGeometry()
            self.resize(self.sizeHint())
            self.zoomChanged.emit(int(self.__zoom * 100))
    
    def zoomFactor(self):
        """
        Public method to get the current zoom factor in percent.
        
        @return zoom factor (integer)
        """
        return self.__zoom * 100
    
    def setGridEnabled(self, enable):
        """
        Public method to enable the display of grid lines.
        
        @param enable enabled status of the grid lines (boolean)
        """
        if enable != self.__gridEnabled:
            self.__gridEnabled = enable
            self.update()
    
    def isGridEnabled(self):
        """
        Public method to get the grid lines status.
        
        @return enabled status of the grid lines (boolean)
        """
        return self.__gridEnabled
    
    def paintEvent(self, evt):
        """
        Protected method called to repaint some of the widget.
        
        @param evt reference to the paint event object (QPaintEvent)
        """
        painter = QPainter(self)
        
        if self.__zoom >= 3 and self.__gridEnabled:
            painter.setPen(self.palette().windowText().color())
            i = 0
            while i <= self.__image.width():
                painter.drawLine(
                    self.__zoom * i, 0,
                    self.__zoom * i, self.__zoom * self.__image.height())
                i += 1
            j = 0
            while j <= self.__image.height():
                painter.drawLine(
                    0, self.__zoom * j,
                    self.__zoom * self.__image.width(), self.__zoom * j)
                j += 1
        
        col = QColor("#aaa")
        painter.setPen(Qt.DashLine)
        for i in range(0, self.__image.width()):
            for j in range(0, self.__image.height()):
                rect = self.__pixelRect(i, j)
                if evt.region().intersects(rect):
                    color = QColor.fromRgba(self.__image.pixel(i, j))
                    painter.fillRect(rect, QBrush(Qt.white))
                    painter.fillRect(QRect(rect.topLeft(), rect.center()), col)
                    painter.fillRect(QRect(rect.center(), rect.bottomRight()),
                                     col)
                    painter.fillRect(rect, QBrush(color))
                
                    if self.__isMarked(i, j):
                        painter.drawRect(rect.adjusted(0, 0, -1, -1))
        
        painter.end()
    
    def __pixelRect(self, i, j):
        """
        Private method to determine the rectangle for a given pixel coordinate.
        
        @param i x-coordinate of the pixel in the image (integer)
        @param j y-coordinate of the pixel in the image (integer)
        @return rectangle for the given pixel coordinates (QRect)
        """
        if self.__zoom >= 3 and self.__gridEnabled:
            return QRect(self.__zoom * i + 1, self.__zoom * j + 1,
                         self.__zoom - 1, self.__zoom - 1)
        else:
            return QRect(self.__zoom * i, self.__zoom * j,
                         self.__zoom, self.__zoom)
    
    def mousePressEvent(self, evt):
        """
        Protected method to handle mouse button press events.
        
        @param evt reference to the mouse event object (QMouseEvent)
        """
        if evt.button() == Qt.LeftButton:
            if self.__isPasting:
                self.__isPasting = False
                self.editPaste(True)
                self.__markImage.fill(self.NoMarkColor.rgba())
                self.update(self.__pasteRect)
                self.__pasteRect = QRect()
                return
            
            if self.__curTool == self.Pencil:
                cmd = IconEditCommand(self, self.__undoTexts[self.__curTool],
                                      self.__image)
                self.__setImagePixel(evt.pos(), True)
                self.setDirty(True)
                self.__undoStack.push(cmd)
                self.__currentUndoCmd = cmd
            elif self.__curTool == self.Rubber:
                cmd = IconEditCommand(self, self.__undoTexts[self.__curTool],
                                      self.__image)
                self.__setImagePixel(evt.pos(), False)
                self.setDirty(True)
                self.__undoStack.push(cmd)
                self.__currentUndoCmd = cmd
            elif self.__curTool == self.Fill:
                i, j = self.__imageCoordinates(evt.pos())
                col = QColor()
                col.setRgba(self.__image.pixel(i, j))
                cmd = IconEditCommand(self, self.__undoTexts[self.__curTool],
                                      self.__image)
                self.__drawFlood(i, j, col)
                self.setDirty(True)
                self.__undoStack.push(cmd)
                cmd.setAfterImage(self.__image)
            elif self.__curTool == self.ColorPicker:
                i, j = self.__imageCoordinates(evt.pos())
                col = QColor()
                col.setRgba(self.__image.pixel(i, j))
                self.setPenColor(col)
            else:
                self.__unMark()
                self.__startPos = evt.pos()
                self.__endPos = evt.pos()
    
    def mouseMoveEvent(self, evt):
        """
        Protected method to handle mouse move events.
        
        @param evt reference to the mouse event object (QMouseEvent)
        """
        self.positionChanged.emit(*self.__imageCoordinates(evt.pos()))
        
        if self.__isPasting and not (evt.buttons() & Qt.LeftButton):
            self.__drawPasteRect(evt.pos())
            return
        
        if evt.buttons() & Qt.LeftButton:
            if self.__curTool == self.Pencil:
                self.__setImagePixel(evt.pos(), True)
                self.setDirty(True)
            elif self.__curTool == self.Rubber:
                self.__setImagePixel(evt.pos(), False)
                self.setDirty(True)
            elif self.__curTool in [self.Fill, self.ColorPicker]:
                pass    # do nothing
            else:
                self.__drawTool(evt.pos(), True)
    
    def mouseReleaseEvent(self, evt):
        """
        Protected method to handle mouse button release events.
        
        @param evt reference to the mouse event object (QMouseEvent)
        """
        if evt.button() == Qt.LeftButton:
            if self.__curTool in [self.Pencil, self.Rubber]:
                if self.__currentUndoCmd:
                    self.__currentUndoCmd.setAfterImage(self.__image)
                    self.__currentUndoCmd = None
            
            if self.__curTool not in [self.Pencil, self.Rubber,
                                      self.Fill, self.ColorPicker,
                                      self.RectangleSelection,
                                      self.CircleSelection]:
                cmd = IconEditCommand(self, self.__undoTexts[self.__curTool],
                                      self.__image)
                if self.__drawTool(evt.pos(), False):
                    self.__undoStack.push(cmd)
                    cmd.setAfterImage(self.__image)
                    self.setDirty(True)
    
    def __setImagePixel(self, pos, opaque):
        """
        Private slot to set or erase a pixel.
        
        @param pos position of the pixel in the widget (QPoint)
        @param opaque flag indicating a set operation (boolean)
        """
        i, j = self.__imageCoordinates(pos)
        
        if self.__image.rect().contains(i, j) and (i, j) != self.__lastPos:
            if opaque:
                painter = QPainter(self.__image)
                painter.setPen(self.penColor())
                painter.setCompositionMode(self.__compositingMode)
                painter.drawPoint(i, j)
            else:
                self.__image.setPixel(i, j, qRgba(0, 0, 0, 0))
            self.__lastPos = (i, j)
        
            self.update(self.__pixelRect(i, j))
    
    def __imageCoordinates(self, pos):
        """
        Private method to convert from widget to image coordinates.
        
        @param pos widget coordinate (QPoint)
        @return tuple with the image coordinates (tuple of two integers)
        """
        i = pos.x() // self.__zoom
        j = pos.y() // self.__zoom
        return i, j
    
    def __drawPasteRect(self, pos):
        """
        Private slot to draw a rectangle for signaling a paste operation.
        
        @param pos widget position of the paste rectangle (QPoint)
        """
        self.__markImage.fill(self.NoMarkColor.rgba())
        if self.__pasteRect.isValid():
            self.__updateImageRect(
                self.__pasteRect.topLeft(),
                self.__pasteRect.bottomRight() + QPoint(1, 1))
        
        x, y = self.__imageCoordinates(pos)
        isize = self.__image.size()
        if x + self.__clipboardSize.width() <= isize.width():
            sx = self.__clipboardSize.width()
        else:
            sx = isize.width() - x
        if y + self.__clipboardSize.height() <= isize.height():
            sy = self.__clipboardSize.height()
        else:
            sy = isize.height() - y
        
        self.__pasteRect = QRect(QPoint(x, y), QSize(sx - 1, sy - 1))
        
        painter = QPainter(self.__markImage)
        painter.setPen(self.MarkColor)
        painter.drawRect(self.__pasteRect)
        painter.end()
        
        self.__updateImageRect(self.__pasteRect.topLeft(),
                               self.__pasteRect.bottomRight() + QPoint(1, 1))
    
    def __drawTool(self, pos, mark):
        """
        Private method to perform a draw operation depending of the current
        tool.
        
        @param pos widget coordinate to perform the draw operation at (QPoint)
        @param mark flag indicating a mark operation (boolean)
        @return flag indicating a successful draw (boolean)
        """
        self.__unMark()
        
        if mark:
            self.__endPos = QPoint(pos)
            drawColor = self.MarkColor
            img = self.__markImage
        else:
            drawColor = self.penColor()
            img = self.__image
        
        start = QPoint(*self.__imageCoordinates(self.__startPos))
        end = QPoint(*self.__imageCoordinates(pos))
        
        painter = QPainter(img)
        painter.setPen(drawColor)
        painter.setCompositionMode(self.__compositingMode)
        
        if self.__curTool == self.Line:
            painter.drawLine(start, end)
        
        elif self.__curTool in [self.Rectangle, self.FilledRectangle,
                                self.RectangleSelection]:
            left = min(start.x(), end.x())
            top = min(start.y(), end.y())
            right = max(start.x(), end.x())
            bottom = max(start.y(), end.y())
            if self.__curTool == self.RectangleSelection:
                painter.setBrush(QBrush(drawColor))
            if self.__curTool == self.FilledRectangle:
                for y in range(top, bottom + 1):
                    painter.drawLine(left, y, right, y)
            else:
                painter.drawRect(left, top, right - left, bottom - top)
            if self.__selecting:
                self.__selRect = QRect(
                    left, top, right - left + 1, bottom - top + 1)
                self.__selectionAvailable = True
                self.selectionAvailable.emit(True)
        
        elif self.__curTool in [self.Circle, self.FilledCircle,
                                self.CircleSelection]:
            r = max(abs(start.x() - end.x()), abs(start.y() - end.y()))
            if self.__curTool in [self.FilledCircle, self.CircleSelection]:
                painter.setBrush(QBrush(drawColor))
            painter.drawEllipse(start, r, r)
            if self.__selecting:
                self.__selRect = QRect(start.x() - r, start.y() - r,
                                       2 * r + 1, 2 * r + 1)
                self.__selectionAvailable = True
                self.selectionAvailable.emit(True)
        
        elif self.__curTool in [self.Ellipse, self.FilledEllipse]:
            r1 = abs(start.x() - end.x())
            r2 = abs(start.y() - end.y())
            if r1 == 0 or r2 == 0:
                return False
            if self.__curTool == self.FilledEllipse:
                painter.setBrush(QBrush(drawColor))
            painter.drawEllipse(start, r1, r2)
        
        painter.end()
        
        if self.__curTool in [self.Circle, self.FilledCircle,
                              self.Ellipse, self.FilledEllipse]:
            self.update()
        else:
            self.__updateRect(self.__startPos, pos)
        
        return True
    
    def __drawFlood(self, i, j, oldColor, doUpdate=True):
        """
        Private method to perform a flood fill operation.
        
        @param i x-value in image coordinates (integer)
        @param j y-value in image coordinates (integer)
        @param oldColor reference to the color at position i, j (QColor)
        @param doUpdate flag indicating an update is requested (boolean)
            (used for speed optimizations)
        """
        if not self.__image.rect().contains(i, j) or \
           self.__image.pixel(i, j) != oldColor.rgba() or \
           self.__image.pixel(i, j) == self.penColor().rgba():
            return
        
        self.__image.setPixel(i, j, self.penColor().rgba())
        
        self.__drawFlood(i, j - 1, oldColor, False)
        self.__drawFlood(i, j + 1, oldColor, False)
        self.__drawFlood(i - 1, j, oldColor, False)
        self.__drawFlood(i + 1, j, oldColor, False)
        
        if doUpdate:
            self.update()
    
    def __updateRect(self, pos1, pos2):
        """
        Private slot to update parts of the widget.
        
        @param pos1 top, left position for the update in widget coordinates
            (QPoint)
        @param pos2 bottom, right position for the update in widget
            coordinates (QPoint)
        """
        self.__updateImageRect(QPoint(*self.__imageCoordinates(pos1)),
                               QPoint(*self.__imageCoordinates(pos2)))
    
    def __updateImageRect(self, ipos1, ipos2):
        """
        Private slot to update parts of the widget.
        
        @param ipos1 top, left position for the update in image coordinates
            (QPoint)
        @param ipos2 bottom, right position for the update in image
            coordinates (QPoint)
        """
        r1 = self.__pixelRect(ipos1.x(), ipos1.y())
        r2 = self.__pixelRect(ipos2.x(), ipos2.y())
        
        left = min(r1.x(), r2.x())
        top = min(r1.y(), r2.y())
        right = max(r1.x() + r1.width(), r2.x() + r2.width())
        bottom = max(r1.y() + r1.height(), r2.y() + r2.height())
        self.update(left, top, right - left + 1, bottom - top + 1)
    
    def __unMark(self):
        """
        Private slot to remove the mark indicator.
        """
        self.__markImage.fill(self.NoMarkColor.rgba())
        if self.__curTool in [self.Circle, self.FilledCircle,
                              self.Ellipse, self.FilledEllipse,
                              self.CircleSelection]:
            self.update()
        else:
            self.__updateRect(self.__startPos, self.__endPos)
        
        if self.__selecting:
            self.__selRect = QRect()
            self.__selectionAvailable = False
            self.selectionAvailable.emit(False)
    
    def __isMarked(self, i, j):
        """
        Private method to check, if a pixel is marked.
        
        @param i x-value in image coordinates (integer)
        @param j y-value in image coordinates (integer)
        @return flag indicating a marked pixel (boolean)
        """
        return self.__markImage.pixel(i, j) == self.MarkColor.rgba()
    
    def __updatePreviewPixmap(self):
        """
        Private slot to generate and signal an updated preview pixmap.
        """
        p = QPixmap.fromImage(self.__image)
        self.previewChanged.emit(p)
    
    def previewPixmap(self):
        """
        Public method to generate a preview pixmap.
        
        @return preview pixmap (QPixmap)
        """
        p = QPixmap.fromImage(self.__image)
        return p
    
    def __checkClipboard(self):
        """
        Private slot to check, if the clipboard contains a valid image, and
        signal the result.
        """
        ok = self.__clipboardImage()[1]
        self.__clipboardImageAvailable = ok
        self.clipboardImageAvailable.emit(ok)
    
    def canPaste(self):
        """
        Public slot to check the availability of the paste operation.
        
        @return flag indicating availability of paste (boolean)
        """
        return self.__clipboardImageAvailable
    
    def __clipboardImage(self):
        """
        Private method to get an image from the clipboard.
        
        @return tuple with the image (QImage) and a flag indicating a
            valid image (boolean)
        """
        img = QApplication.clipboard().image()
        ok = not img.isNull()
        if ok:
            img = img.convertToFormat(QImage.Format_ARGB32)
        
        return img, ok
    
    def __getSelectionImage(self, cut):
        """
        Private method to get an image from the selection.
        
        @param cut flag indicating to cut the selection (boolean)
        @return image of the selection (QImage)
        """
        if cut:
            cmd = IconEditCommand(self, self.tr("Cut Selection"),
                                  self.__image)
        
        img = QImage(self.__selRect.size(), QImage.Format_ARGB32)
        img.fill(qRgba(0, 0, 0, 0))
        for i in range(0, self.__selRect.width()):
            for j in range(0, self.__selRect.height()):
                if self.__image.rect().contains(self.__selRect.x() + i,
                                                self.__selRect.y() + j):
                    if self.__isMarked(
                            self.__selRect.x() + i, self.__selRect.y() + j):
                        img.setPixel(i, j, self.__image.pixel(
                            self.__selRect.x() + i, self.__selRect.y() + j))
                        if cut:
                            self.__image.setPixel(self.__selRect.x() + i,
                                                  self.__selRect.y() + j,
                                                  qRgba(0, 0, 0, 0))
        
        if cut:
            self.__undoStack.push(cmd)
            cmd.setAfterImage(self.__image)
        
        self.__unMark()
        
        if cut:
            self.update(self.__selRect)
        
        return img
    
    def editCopy(self):
        """
        Public slot to copy the selection.
        """
        if self.__selRect.isValid():
            img = self.__getSelectionImage(False)
            QApplication.clipboard().setImage(img)
    
    def editCut(self):
        """
        Public slot to cut the selection.
        """
        if self.__selRect.isValid():
            img = self.__getSelectionImage(True)
            QApplication.clipboard().setImage(img)
    
    @pyqtSlot()
    def editPaste(self, pasting=False):
        """
        Public slot to paste an image from the clipboard.
        
        @param pasting flag indicating part two of the paste operation
            (boolean)
        """
        img, ok = self.__clipboardImage()
        if ok:
            if img.width() > self.__image.width() or \
                    img.height() > self.__image.height():
                res = E5MessageBox.yesNo(
                    self,
                    self.tr("Paste"),
                    self.tr(
                        """<p>The clipboard image is larger than the"""
                        """ current image.<br/>Paste as new image?</p>"""))
                if res:
                    self.editPasteAsNew()
                return
            elif not pasting:
                self.__isPasting = True
                self.__clipboardSize = img.size()
            else:
                cmd = IconEditCommand(self, self.tr("Paste Clipboard"),
                                      self.__image)
                self.__markImage.fill(self.NoMarkColor.rgba())
                painter = QPainter(self.__image)
                painter.setPen(self.penColor())
                painter.setCompositionMode(self.__compositingMode)
                painter.drawImage(
                    self.__pasteRect.x(), self.__pasteRect.y(), img, 0, 0,
                    self.__pasteRect.width() + 1,
                    self.__pasteRect.height() + 1)
                
                self.__undoStack.push(cmd)
                cmd.setAfterImage(self.__image)
                
                self.__updateImageRect(
                    self.__pasteRect.topLeft(),
                    self.__pasteRect.bottomRight() + QPoint(1, 1))
        else:
            E5MessageBox.warning(
                self,
                self.tr("Pasting Image"),
                self.tr("""Invalid image data in clipboard."""))
    
    def editPasteAsNew(self):
        """
        Public slot to paste the clipboard as a new image.
        """
        img, ok = self.__clipboardImage()
        if ok:
            cmd = IconEditCommand(
                self, self.tr("Paste Clipboard as New Image"),
                self.__image)
            self.setIconImage(img)
            self.setDirty(True)
            self.__undoStack.push(cmd)
            cmd.setAfterImage(self.__image)
    
    def editSelectAll(self):
        """
        Public slot to select the complete image.
        """
        self.__unMark()
        
        self.__startPos = QPoint(0, 0)
        self.__endPos = QPoint(self.rect().bottomRight())
        self.__markImage.fill(self.MarkColor.rgba())
        self.__selRect = self.__image.rect()
        self.__selectionAvailable = True
        self.selectionAvailable.emit(True)
        
        self.update()
    
    def editClear(self):
        """
        Public slot to clear the image.
        """
        self.__unMark()
        
        cmd = IconEditCommand(self, self.tr("Clear Image"), self.__image)
        self.__image.fill(qRgba(0, 0, 0, 0))
        self.update()
        self.setDirty(True)
        self.__undoStack.push(cmd)
        cmd.setAfterImage(self.__image)
    
    def editResize(self):
        """
        Public slot to resize the image.
        """
        from .IconSizeDialog import IconSizeDialog
        dlg = IconSizeDialog(self.__image.width(), self.__image.height())
        res = dlg.exec_()
        if res == QDialog.Accepted:
            newWidth, newHeight = dlg.getData()
            if newWidth != self.__image.width() or \
                    newHeight != self.__image.height():
                cmd = IconEditCommand(self, self.tr("Resize Image"),
                                      self.__image)
                img = self.__image.scaled(
                    newWidth, newHeight, Qt.IgnoreAspectRatio,
                    Qt.SmoothTransformation)
                self.setIconImage(img)
                self.setDirty(True)
                self.__undoStack.push(cmd)
                cmd.setAfterImage(self.__image)
    
    def editNew(self):
        """
        Public slot to generate a new, empty image.
        """
        from .IconSizeDialog import IconSizeDialog
        dlg = IconSizeDialog(self.__image.width(), self.__image.height())
        res = dlg.exec_()
        if res == QDialog.Accepted:
            width, height = dlg.getData()
            img = QImage(width, height, QImage.Format_ARGB32)
            img.fill(qRgba(0, 0, 0, 0))
            self.setIconImage(img)
    
    def grayScale(self):
        """
        Public slot to convert the image to gray preserving transparency.
        """
        cmd = IconEditCommand(self, self.tr("Convert to Grayscale"),
                              self.__image)
        for x in range(self.__image.width()):
            for y in range(self.__image.height()):
                col = self.__image.pixel(x, y)
                if col != qRgba(0, 0, 0, 0):
                    gray = qGray(col)
                    self.__image.setPixel(
                        x, y, qRgba(gray, gray, gray, qAlpha(col)))
        self.update()
        self.setDirty(True)
        self.__undoStack.push(cmd)
        cmd.setAfterImage(self.__image)

    def editUndo(self):
        """
        Public slot to perform an undo operation.
        """
        if self.__undoStack.canUndo():
            self.__undoStack.undo()
    
    def editRedo(self):
        """
        Public slot to perform a redo operation.
        """
        if self.__undoStack.canRedo():
            self.__undoStack.redo()
    
    def canUndo(self):
        """
        Public method to return the undo status.
        
        @return flag indicating the availability of undo (boolean)
        """
        return self.__undoStack.canUndo()
    
    def canRedo(self):
        """
        Public method to return the redo status.
        
        @return flag indicating the availability of redo (boolean)
        """
        return self.__undoStack.canRedo()
    
    def __cleanChanged(self, clean):
        """
        Private slot to handle the undo stack clean state change.
        
        @param clean flag indicating the clean state (boolean)
        """
        self.setDirty(not clean)
    
    def shutdown(self):
        """
        Public slot to perform some shutdown actions.
        """
        self.__undoStack.canRedoChanged.disconnect(self.canRedoChanged)
        self.__undoStack.canUndoChanged.disconnect(self.canUndoChanged)
        self.__undoStack.cleanChanged.disconnect(self.__cleanChanged)
    
    def isSelectionAvailable(self):
        """
        Public method to check the availability of a selection.
        
        @return flag indicating the availability of a selection (boolean)
        """
        return self.__selectionAvailable
示例#36
0
class PhotoViewer(QtWidgets.QGraphicsView):
    photoClicked = QtCore.pyqtSignal(QtCore.QPoint)
    photoRightClicked = QtCore.pyqtSignal(QtCore.QPoint, QtCore.QPoint)
    showThreadImageUpdate = QtCore.pyqtSignal(QPixmap)
    MOUSEWIDTH = 100

    def __init__(self, parent):
        super(PhotoViewer, self).__init__(parent)
        self.logger = logging.getLogger('PSILOG')

        self._zoom = 0
        self._empty = True
        self._scene = QtWidgets.QGraphicsScene(self)
        self._photo = QtWidgets.QGraphicsPixmapItem()
        self._scene.addItem(self._photo)
        self.setScene(self._scene)
        self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(11, 152, 60)))
        self.setFrameShape(QtWidgets.QFrame.NoFrame)

        self.photoRightClicked.connect(self.MouseRightClick)
        self.showThreadImageUpdate.connect(self.UpdateImage)

        self.CURSOR_NEW = QCursor(
            QPixmap(':/icons/cursor.png').scaled(PhotoViewer.MOUSEWIDTH,
                                                 PhotoViewer.MOUSEWIDTH,
                                                 Qt.KeepAspectRatio,
                                                 Qt.SmoothTransformation))
        self.CUR_CUESOR = self.cursor()
        self._imagepixmap = None
        #self._imagepixmapback = None
        self.w = 0
        self.h = 0
        self.imagedresult = 0
        self.isPreviewMode = True
        self.profilerootpath = os.path.join(
            os.path.dirname(os.path.realpath(__file__)), "profiles")
        self._camerapoisition = CAMERA.TOP
        self.profile = profiledata.profile("", "")
        self.profilepoints = []
        self.pixRect = QRect()
        self.rightClickPoint = (QPoint(), QPoint())
        self.curfactor = 0.0
        self.screwDraw = 0
        self.screwReal = 0
        self._client = None

    def hasPhoto(self):
        return not self._empty

    def fitInSameImage(self):
        rect = QtCore.QRectF(self._photo.pixmap().rect())
        if not rect.isNull():
            self.setSceneRect(rect)
            if self.hasPhoto():
                unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
                self.scale(1 / unity.width(), 1 / unity.height())
                viewrect = self.viewport().rect()
                scenerect = self.transform().mapRect(rect)
                self.scale(1, 1)
            self._zoom = 10

    def fitInView(self, scale=True):
        rect = QtCore.QRectF(self._photo.pixmap().rect())
        if not rect.isNull():
            self.setSceneRect(rect)
            if self.hasPhoto():
                unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
                #print(str(unity.width())+"======"+str(unity.height()))
                self.scale(1 / unity.width(), 1 / unity.height())
                viewrect = self.viewport().rect()
                #print(viewrect)
                scenerect = self.transform().mapRect(rect)
                #print(scenerect)
                factor = min(viewrect.width() / scenerect.width(),
                             viewrect.height() / scenerect.height())
                #print(str(factor)+"======")
                self.scale(factor, factor)
                #scenerect = self.transform().mapRect(rect)
                #print(scenerect)
                self.curfactor = factor

            self._zoom = 0

    def checkPoint(self, pt):
        if self.pixRect != None and self.pixRect.isValid():
            return self.pixRect.contains(pt)
        return False

    def setPhoto(self, pixmap=None):
        self._zoom = 0
        if pixmap and not pixmap.isNull():
            self._empty = False
            if not self.isPreviewMode:
                self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
            self._photo.setPixmap(pixmap)
            self._imagepixmap = pixmap
            self.pixRect = self._photo.pixmap().rect()
        else:
            self._empty = True
            self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
            self._photo.setPixmap(QtGui.QPixmap())
            self._imagepixmap = QtGui.QPixmap()
            self.pixRect = QRect()
        self.fitInView()

    def wheelEvent(self, event):
        if self.isPreviewMode:
            return
        if self.hasPhoto():
            if event.angleDelta().y() > 0:
                factor = 1.1
                self._zoom += 1
            else:
                factor = 0.9
                self._zoom -= 1
            #print(str(factor)+"======"+str(self._zoom))
            if self._zoom > 0:
                if self._zoom == 10:
                    self.fitInSameImage()
                else:
                    self.scale(factor, factor)
            elif self._zoom == 0:
                self.fitInView()
            else:
                self._zoom = 0
            rect = QtCore.QRectF(self._photo.pixmap().rect())
            scenerect = self.transform().mapRect(rect)
            self.curfactor = scenerect.width() / rect.width()

    def toggleReviewMode(self, yes):
        if yes:
            self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
            self.isPreviewMode = True
        elif not self._photo.pixmap().isNull():
            self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
            self.isPreviewMode = False

    def toggleDragMode(self):
        if self.dragMode() == QtWidgets.QGraphicsView.ScrollHandDrag:
            self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
        elif not self._photo.pixmap().isNull():
            self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)

    def mousePressEvent(self, event):
        #print("mousepress:"+str(event.pos().x())+"=>"+str(event.pos().y()))
        if self._photo.isUnderMouse():
            self.photoClicked.emit(self.mapToScene(event.pos()).toPoint())
        if event.button() == Qt.RightButton:
            self.photoRightClicked.emit(
                self.mapToScene(event.pos()).toPoint(), event.pos())
        super(PhotoViewer, self).mousePressEvent(event)

    def MouseRightClick(self, pt, pos):
        if self.checkPoint(pt):
            print("rightclick:" + str(pt.x()) + "=>" + str(pt.y()))
            self.rightClickPoint = (pt, pos)
            sc = self.rightClickPoint[1] + QPoint(
                int(PhotoViewer.MOUSEWIDTH / 2), int(
                    PhotoViewer.MOUSEWIDTH / 2))
            abc = self.mapToScene(sc).toPoint()
            abc = abc - self.rightClickPoint[0]
            self.screwReal = abc.x() * 2
            self.screwDraw = int(PhotoViewer.MOUSEWIDTH / 2 / self.curfactor)
            print("rightinfoclick:" + str(self.screwReal) + "=>" +
                  str(self.screwDraw) + '==>' + str(self.curfactor))
            self.scene().update()
        else:
            print("right no click:" + str(pt.x()) + "=>" + str(pt.y()))
            self.rightClickPoint = (QPoint(), QPoint())

    def UpdateImage(self, bmp):
        self.ShowPreImage(bmp)

    def enterEvent(self, event):
        if not self.isPreviewMode:
            QtWidgets.QApplication.setOverrideCursor(self.CURSOR_NEW)

    def leaveEvent(self, event):
        QtWidgets.QApplication.setOverrideCursor(self.CUR_CUESOR)

    def clear(self):
        self.setPhoto(QPixmap())

    def SetCamera(self, which):
        self._camerapoisition = which
        self.logger = logging.getLogger('PSILOG')

    def SetProfile(self, profilename, filename):
        self.profile.profilename = profilename
        self.profile.filename = filename

    # get profile directory sub directory
    def DirSub(self, argument):
        switcher = {
            CAMERA.LEFT: "left",
            CAMERA.TOP: "top",
            CAMERA.RIGHT: "right",
        }
        return switcher.get(argument, "Invalid")

    def setServerProxy(self, value):
        self._client = value

    def ShowPreImage(self, image):
        self._imagepixmap = image
        self.setPhoto(self._imagepixmap)

    def setImageScale(self):
        if self.w == 0 or self.h == 0:
            self.w = self.width()
            self.h = self.height()
            self.logger.info("uigview:" + str(self.w) + "X" + str(self.h))

    def drawForeground(self, painter, rect):
        if self._empty or self.rightClickPoint[0].isNull():
            return
        pen = QPen(QColor(21, 51, 255))
        pen.setWidth(3)
        painter.setPen(pen)

        for x in self.profilepoints:
            if x.screwDraw > 0:
                painter.drawEllipse(x.centrpoint, x.screwDraw, x.screwDraw)

        painter.drawEllipse(self.rightClickPoint[0], self.screwDraw,
                            self.screwDraw)

    def InitProfile(self):
        self.profilepoints = []
        self.rightClickPoint = (QPoint(), QPoint())
        self.screwDraw = 0
        self.screwReal = 0

    def DrawProfile(self, profilename):
        self.logger.info("DrawImageProfile ++ " + str(self._camerapoisition))
        ret = 0
        imagename = os.path.join(self.profilerootpath, profilename,
                                 self.DirSub(self._camerapoisition),
                                 profilename + ".jpg")
        txtname = os.path.join(self.profilerootpath, profilename,
                               self.DirSub(self._camerapoisition),
                               profilename + ".txt")
        if not os.path.exists(imagename):
            self.logger.info(imagename)
            return 1
        self.LoadProfilePoints(txtname)
        _imagepixmap = QPixmap(imagename)
        painterInstance = QPainter(_imagepixmap)
        penRectangle = QPen(Qt.red)
        penRectangle.setWidth(3)
        painterInstance.setPen(penRectangle)

        for pt in self.profilepoints:
            # draw rectangle on painter
            painterInstance.drawEllipse(pt.centrpoint,
                                        pt.rect.width() / 2,
                                        pt.rect.height() / 2)

        painterInstance.end()
        self.ShowPreImage(_imagepixmap)
        self.toggleReviewMode(False)
        self.logger.info("DrawImageProfile -- ")
        return ret

    def DrawImageResults(self, data, imagepic):
        self.logger.info("DrawImageResults ++ " + str(self._camerapoisition))
        ret = 0
        self.imagedresult = ret
        if imagepic is not None:
            self._imagepixmap = imagepic
        if self._imagepixmap == None or len(data) == 0:
            self.imagedresult = ret
            return ret
        self.logger.info(data)
        painterInstance = QPainter(self._imagepixmap)
        penRectangle = QPen(Qt.red)
        penRectangle.setWidth(3)

        for itemscrew in data:
            location = itemscrew[1]
            if itemscrew[0] < 0.35:
                ret = 2
                penRectangle.setColor(Qt.red)
            elif itemscrew[0] >= 0.45:
                penRectangle.setColor(Qt.green)
            else:
                if ret != 2:
                    ret = 1
                penRectangle.setColor(Qt.yellow)

            painterInstance.setPen(penRectangle)
            painterInstance.drawRect(
                QRect(QPoint(int(location[0] / 4), int(location[2] / 4)),
                      QPoint(int(location[1] / 4), int(location[3] / 4))))

        #self.setPhoto(self._imagepixmap)
        self.showThreadImageUpdate.emit(self._imagepixmap)
        painterInstance.end()
        self.imagedresult = ret
        self.logger.info("DrawImageResults -- ")
        return ret

    def LoadProfilePoints(self, fname):
        if fname == None or fname == '':
            fname = os.path.join(self.profilerootpath, self.DirSub(index),
                                 self.profile.profilename + ".txt")

        if os.path.exists(fname):
            self.profilepoints = []
            with open(fname) as f:
                for line in f:
                    words = line.split()
                    roi_0 = int(words[1][:-1])
                    roi_1 = int(words[2][:-1])
                    roi_2 = int(words[3][:-1])
                    roi_3 = int(words[4])
                    self.profilepoints.append(
                        ProfilePoint(roi_0, roi_2, roi_1, roi_3))
                    #x = roi_0 + int((roi_1 - roi_0 + 1)/2)
                    #y = roi_2 + int((roi_3 - roi_2 + 1)/2)
                    #centerpoint.append((x,y))

            return True

        return False

    def AddProfilePoint(self):
        if self.screwReal == 0 or self.screwDraw == 0:
            return
        pt = ProfilePoint(0, 0, 0, 0)
        pt.setCentrSize(self.rightClickPoint[0], self.screwReal,
                        self.screwReal)
        pt.screwDraw = self.screwDraw
        self.profilepoints.append(pt)
        self.screwReal = 0
        self.screwDraw = 0

    def fileprechar(self, argument):
        switcher = {
            CAMERA.LEFT: "L",
            CAMERA.TOP: "T",
            CAMERA.RIGHT: "R",
        }
        return switcher.get(argument, "Invalid")

    def _savescrew(self, ptt, _indexscrew):
        if self._imagepixmap is None or self._imagepixmap.isNull():
            return

        x = ptt.lefttop.x()
        y = ptt.lefttop.y()
        x1 = ptt.rightbottom.x()
        y1 = ptt.rightbottom.y()
        currentQRect = QRect(QPoint(x, y), QPoint(x1 - 1, y1 - 1))
        cropQPixmap = self._imagepixmap.copy(currentQRect)
        profilepath = os.path.join(self.profilerootpath,
                                   self.profile.profilename)
        filename = self.fileprechar(
            self._camerapoisition) + str(_indexscrew) + ".png"
        profilepath = os.path.join(profilepath,
                                   self.DirSub(self._camerapoisition),
                                   filename)
        cropQPixmap.save(profilepath)
        #screwpoint = profiledata.screw(self.profile.profilename, filename, ptt.centrpoint, QPoint(x,y), QPoint(x1,y1))
        #self.ProfilePoint.append(screwpoint)
        sinfo = profilepath + ", " + str(x) + ", " + str(x1) + ", " + str(
            y) + ", " + str(y1)
        profiletxt = os.path.join(self.profilerootpath,
                                  self.profile.profilename,
                                  self.DirSub(self._camerapoisition),
                                  self.profile.profilename + ".txt")
        self.append_new_line(profiletxt, sinfo)

    def append_new_line(self, file_name, text_to_append):
        """Append given text as a new line at the end of file"""
        # Open the file in append & read mode ('a+')
        with open(file_name, "a+") as file_object:
            # Move read cursor to the start of file.
            file_object.seek(0)
            # If file is not empty then append '\n'
            data = file_object.read(100)
            if len(data) > 0:
                file_object.write("\n")
            # Append text at the end of file
            file_object.write(text_to_append)

    def needSaveProfile(self):
        return len(self.profilepoints) > 0

    def SaveProfile(self):
        self.logger.info("SaveProfile ++ ")
        if len(self.profilepoints) == 0:
            return
        index = 0
        profiletxt = os.path.join(self.profilerootpath,
                                  self.profile.profilename,
                                  self.DirSub(self._camerapoisition),
                                  self.profile.profilename + ".txt")
        if os.path.exists(profiletxt):
            os.remove(profiletxt)

        rects = []
        if self._client:
            for x in self.profilepoints:
                rects.append([
                    x.lefttop.x(),
                    x.lefttop.y(),
                    x.rightbottom.x(),
                    x.rightbottom.y()
                ])

            self.logger.info(rects)
            self._client.SaveProfile(self._camerapoisition.value, rects)
            for i in range(0, 2):
                while True:
                    try:
                        self._client.SaveProfile(self._camerapoisition.value,
                                                 rects)
                    except Exception as e:
                        self.logger.exception(str(e))
                        time.sleep(0.1)
                        continue
                    break
        else:
            for x in self.profilepoints:
                self._savescrew(x, index)
                index += 1

        self.profilepoints = []
        self.logger.info("SaveProfile -- ")
示例#37
0
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle('ConvertAll')
        modPath = os.path.abspath(sys.path[0])
        if modPath.endswith('.zip'):  # for py2exe
            modPath = os.path.dirname(modPath)
        iconPathList = [iconPath, os.path.join(modPath, 'icons/'),
                         os.path.join(modPath, '../icons')]
        self.icons = icondict.IconDict()
        self.icons.addIconPath([path for path in iconPathList if path])
        try:
            QApplication.setWindowIcon(self.icons['convertall_med'])
        except KeyError:
            pass
        self.helpView = None
        self.basesDialog = None
        self.fractionDialog = None
        self.option = Option('convertall', 25)
        self.option.loadAll(optiondefaults.defaultList)
        self.colorSet = colorset.ColorSet(self.option)
        if self.option.strData('ColorTheme') != 'system':
            self.colorSet.setAppColors()
        self.sysFont = self.font()
        self.guiFont = None
        fontStr = self.option.strData('GuiFont', True)
        if fontStr:
            guiFont = self.font()
            if guiFont.fromString(fontStr):
                QApplication.setFont(guiFont)
                self.guiFont = guiFont
        self.recentUnits = recentunits.RecentUnits(self.option)
        try:
            num = ConvertDlg.unitData.readData()
        except unitdata.UnitDataError as text:
            QMessageBox.warning(self, 'ConvertAll',
                                _('Error in unit data - {0}').  format(text))
            sys.exit(1)
        try:
            print(_('{0} units loaded').format(num))
        except UnicodeError:
            print('{0} units loaded'.format(num))
        self.fromGroup = UnitGroup(ConvertDlg.unitData, self.option)
        self.toGroup = UnitGroup(ConvertDlg.unitData, self.option)
        self.unitButtons = []
        self.textButtons = []

        topLayout = QHBoxLayout(self)    # divide main, buttons
        mainLayout = QVBoxLayout()
        mainLayout.setSpacing(8)
        topLayout.addLayout(mainLayout)
        unitLayout = QGridLayout()       # unit selection
        unitLayout.setVerticalSpacing(3)
        unitLayout.setHorizontalSpacing(20)
        mainLayout.addLayout(unitLayout)

        fromLabel = QLabel(_('From Unit'))
        unitLayout.addWidget(fromLabel, 0, 0)
        self.fromUnitEdit = unitedit.UnitEdit(self.fromGroup)
        unitLayout.addWidget(self.fromUnitEdit, 1, 0)
        self.fromUnitEdit.setFocus()

        toLabel = QLabel(_('To Unit'))
        unitLayout.addWidget(toLabel, 0, 1)
        self.toUnitEdit = unitedit.UnitEdit(self.toGroup)
        unitLayout.addWidget(self.toUnitEdit, 1, 1)
        self.fromUnitEdit.gotFocus.connect(self.toUnitEdit.setInactive)
        self.toUnitEdit.gotFocus.connect(self.fromUnitEdit.setInactive)

        vertButtonLayout = QVBoxLayout()
        vertButtonLayout.setSpacing(2)
        mainLayout.addLayout(vertButtonLayout)

        self.unitListView = unitlistview.UnitListView(ConvertDlg.unitData)
        mainLayout.addWidget(self.unitListView)
        self.fromUnitEdit.currentChanged.connect(self.unitListView.
                                                 updateFiltering)
        self.toUnitEdit.currentChanged.connect(self.unitListView.
                                               updateFiltering)
        self.fromUnitEdit.keyPressed.connect(self.unitListView.handleKeyPress)
        self.toUnitEdit.keyPressed.connect(self.unitListView.handleKeyPress)
        self.unitListView.unitChanged.connect(self.fromUnitEdit.unitUpdate)
        self.unitListView.unitChanged.connect(self.toUnitEdit.unitUpdate)
        self.unitListView.haveCurrentUnit.connect(self.enableButtons)
        self.unitListView.setFocusPolicy(Qt.NoFocus)

        textButtonLayout = QHBoxLayout()
        textButtonLayout.setSpacing(6)
        vertButtonLayout.addLayout(textButtonLayout)
        textButtonLayout.addStretch(1)
        self.textButtons.append(QPushButton('{0} (^2)'.format(_('Square'))))
        self.textButtons.append(QPushButton('{0} (^3)'.format(_('Cube'))))
        self.textButtons.append(QPushButton('{0} (*)'.format(_('Multiply'))))
        self.textButtons.append(QPushButton('{0} (/)'.format(_('Divide'))))
        for button in self.textButtons:
            button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
            button.setFocusPolicy(Qt.NoFocus)
            textButtonLayout.addWidget(button)
            button.clicked.connect(self.unitListView.addUnitText)
        textButtonLayout.addStretch(1)

        unitButtonLayout = QHBoxLayout()
        unitButtonLayout.setSpacing(6)
        vertButtonLayout.addLayout(unitButtonLayout)
        unitButtonLayout.addStretch(1)
        self.clearButton = QPushButton(_('Clear Unit'))
        self.clearButton.clicked.connect(self.unitListView.clearUnitText)
        self.recentButton = QPushButton(_('Recent Unit'))
        self.recentButton.clicked.connect(self.recentMenu)
        self.filterButton = QPushButton(_('Filter List'))
        self.filterButton.clicked.connect(self.filterMenu)
        self.unitButtons = [self.clearButton, self.recentButton,
                            self.filterButton]
        for button in self.unitButtons:
            button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
            button.setFocusPolicy(Qt.NoFocus)
            unitButtonLayout.addWidget(button)
        unitButtonLayout.addStretch(1)
        self.showHideButtons()

        numberLayout = QGridLayout()
        numberLayout.setVerticalSpacing(3)
        mainLayout.addLayout(numberLayout)
        statusLabel = QLabel(_('Set units'))
        statusLabel.setFrameStyle(QFrame.Panel | QFrame.Sunken)
        mainLayout.addWidget(statusLabel)

        fromNumLabel = QLabel(_('No Unit Set'))
        numberLayout.addWidget(fromNumLabel, 0, 0)
        self.fromNumEdit = numedit.NumEdit(self.fromGroup, self.toGroup,
                                           fromNumLabel, statusLabel,
                                           self.recentUnits, True)
        numberLayout.addWidget(self.fromNumEdit, 1, 0)
        self.fromUnitEdit.unitChanged.connect(self.fromNumEdit.unitUpdate)
        self.fromNumEdit.gotFocus.connect(self.fromUnitEdit.setInactive)
        self.fromNumEdit.gotFocus.connect(self.toUnitEdit.setInactive)
        self.fromNumEdit.gotFocus.connect(self.unitListView.resetFiltering)
        self.fromNumEdit.setEnabled(False)
        equalsLabel = QLabel(' = ')
        equalsLabel.setFont(QFont(self.font().family(), 20))
        numberLayout.addWidget(equalsLabel, 0, 1, 2, 1)

        toNumLabel = QLabel(_('No Unit Set'))
        numberLayout.addWidget(toNumLabel, 0, 3)
        self.toNumEdit = numedit.NumEdit(self.toGroup, self.fromGroup,
                                         toNumLabel, statusLabel,
                                         self.recentUnits, False)
        numberLayout.addWidget(self.toNumEdit, 1, 3)
        self.toUnitEdit.unitChanged.connect(self.toNumEdit.unitUpdate)
        self.toNumEdit.gotFocus.connect(self.fromUnitEdit.setInactive)
        self.toNumEdit.gotFocus.connect(self.toUnitEdit.setInactive)
        self.toNumEdit.gotFocus.connect(self.unitListView.resetFiltering)
        self.toNumEdit.setEnabled(False)
        self.fromNumEdit.convertNum.connect(self.toNumEdit.setNum)
        self.toNumEdit.convertNum.connect(self.fromNumEdit.setNum)
        self.fromNumEdit.convertRqd.connect(self.toNumEdit.convert)
        self.toNumEdit.convertRqd.connect(self.fromNumEdit.convert)

        buttonLayout = QVBoxLayout()     # major buttons
        topLayout.addLayout(buttonLayout)
        closeButton = QPushButton(_('&Close'))
        buttonLayout.addWidget(closeButton)
        closeButton.setFocusPolicy(Qt.NoFocus)
        closeButton.clicked.connect(self.close)
        optionsButton = QPushButton(_('&Options...'))
        buttonLayout.addWidget(optionsButton)
        optionsButton.setFocusPolicy(Qt.NoFocus)
        optionsButton.clicked.connect(self.changeOptions)
        basesButton = QPushButton(_('&Bases...'))
        buttonLayout.addWidget(basesButton)
        basesButton.setFocusPolicy(Qt.NoFocus)
        basesButton.clicked.connect(self.showBases)
        fractionsButton = QPushButton(_('&Fractions...'))
        buttonLayout.addWidget(fractionsButton)
        fractionsButton.setFocusPolicy(Qt.NoFocus)
        fractionsButton.clicked.connect(self.showFractions)
        helpButton = QPushButton(_('&Help...'))
        buttonLayout.addWidget(helpButton)
        helpButton.setFocusPolicy(Qt.NoFocus)
        helpButton.clicked.connect(self.help)
        aboutButton = QPushButton(_('&About...'))
        buttonLayout.addWidget(aboutButton)
        aboutButton.setFocusPolicy(Qt.NoFocus)
        aboutButton.clicked.connect(self.about)
        buttonLayout.addStretch()

        if self.option.boolData('RemenberDlgPos'):
            rect = QRect(self.option.intData('MainDlgXPos', 0, 10000),
                         self.option.intData('MainDlgYPos', 0, 10000),
                         self.option.intData('MainDlgXSize', 0, 10000),
                         self.option.intData('MainDlgYSize', 0, 10000))
            if rect.isValid():
                availRect = (QApplication.primaryScreen().
                             availableVirtualGeometry())
                topMargin = self.option.intData('MainDlgTopMargin', 0, 1000)
                otherMargin = self.option.intData('MainDlgOtherMargin', 0,
                                                  1000)
                # remove frame space from available rect
                availRect.adjust(otherMargin, topMargin,
                                 -otherMargin, -otherMargin)
                finalRect = rect.intersected(availRect)
                if finalRect.isEmpty():
                    rect.moveTo(0, 0)
                    finalRect = rect.intersected(availRect)
                if finalRect.isValid():
                    self.setGeometry(finalRect)
        if self.option.boolData('LoadLastUnit') and len(self.recentUnits) > 1:
            self.fromGroup.update(self.recentUnits[0])
            self.fromUnitEdit.unitUpdate()
            self.toGroup.update(self.recentUnits[1])
            self.toUnitEdit.unitUpdate()
            self.unitListView.updateFiltering()
            self.fromNumEdit.setFocus()
            self.fromNumEdit.selectAll()
        if self.option.boolData('ShowStartupTip'):
            self.show()
            tipDialog = TipDialog(self.option, self)
            tipDialog.exec_()
示例#38
0
class PuzzleWidget(QWidget):

    puzzleCompleted = pyqtSignal()

    def __init__(self, parent=None):
        super(PuzzleWidget, self).__init__(parent)

        self.piecePixmaps = []
        self.pieceRects = []
        self.pieceLocations = []
        self.highlightedRect = QRect()
        self.inPlace = 0

        self.setAcceptDrops(True)
        self.setMinimumSize(400, 400)
        self.setMaximumSize(400, 400)

    def clear(self):
        self.pieceLocations = []
        self.piecePixmaps = []
        self.pieceRects = []
        self.highlightedRect = QRect()
        self.inPlace = 0
        self.update()

    def dragEnterEvent(self, event):
        if event.mimeData().hasFormat('image/x-puzzle-piece'):
            event.accept()
        else:
            event.ignore()

    def dragLeaveEvent(self, event):
        updateRect = self.highlightedRect
        self.highlightedRect = QRect()
        self.update(updateRect)
        event.accept()

    def dragMoveEvent(self, event):
        updateRect = self.highlightedRect.united(self.targetSquare(
            event.pos()))

        if event.mimeData().hasFormat(
                'image/x-puzzle-piece') and self.findPiece(
                    self.targetSquare(event.pos())) == -1:
            self.highlightedRect = self.targetSquare(event.pos())
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            self.highlightedRect = QRect()
            event.ignore()

        self.update(updateRect)

    def dropEvent(self, event):
        if event.mimeData().hasFormat(
                'image/x-puzzle-piece') and self.findPiece(
                    self.targetSquare(event.pos())) == -1:
            pieceData = event.mimeData().data('image/x-puzzle-piece')
            dataStream = QDataStream(pieceData, QIODevice.ReadOnly)
            square = self.targetSquare(event.pos())
            pixmap = QPixmap()
            location = QPoint()
            dataStream >> pixmap >> location

            self.pieceLocations.append(location)
            self.piecePixmaps.append(pixmap)
            self.pieceRects.append(square)

            self.hightlightedRect = QRect()
            self.update(square)

            event.setDropAction(Qt.MoveAction)
            event.accept()

            if location == QPoint(square.x() / 80, square.y() / 80):
                self.inPlace += 1
                if self.inPlace == 25:
                    self.puzzleCompleted.emit()
        else:
            self.highlightedRect = QRect()
            event.ignore()

    def findPiece(self, pieceRect):
        try:
            return self.pieceRects.index(pieceRect)
        except ValueError:
            return -1

    def mousePressEvent(self, event):
        square = self.targetSquare(event.pos())
        found = self.findPiece(square)

        if found == -1:
            return

        location = self.pieceLocations[found]
        pixmap = self.piecePixmaps[found]
        del self.pieceLocations[found]
        del self.piecePixmaps[found]
        del self.pieceRects[found]

        if location == QPoint(square.x() / 80, square.y() / 80):
            self.inPlace -= 1

        self.update(square)

        itemData = QByteArray()
        dataStream = QDataStream(itemData, QIODevice.WriteOnly)

        dataStream << pixmap << location

        mimeData = QMimeData()
        mimeData.setData('image/x-puzzle-piece', itemData)

        drag = QDrag(self)
        drag.setMimeData(mimeData)
        drag.setHotSpot(event.pos() - square.topLeft())
        drag.setPixmap(pixmap)

        if drag.exec_(Qt.MoveAction) != Qt.MoveAction:
            self.pieceLocations.insert(found, location)
            self.piecePixmaps.insert(found, pixmap)
            self.pieceRects.insert(found, square)
            self.update(self.targetSquare(event.pos()))

            if location == QPoint(square.x() / 80, square.y() / 80):
                self.inPlace += 1

    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        painter.fillRect(event.rect(), Qt.white)

        if self.highlightedRect.isValid():
            painter.setBrush(QColor("#ffcccc"))
            painter.setPen(Qt.NoPen)
            painter.drawRect(self.highlightedRect.adjusted(0, 0, -1, -1))

        for rect, pixmap in zip(self.pieceRects, self.piecePixmaps):
            painter.drawPixmap(rect, pixmap)

        painter.end()

    def targetSquare(self, position):
        return QRect(position.x() // 80 * 80, position.y() // 80 * 80, 80, 80)