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)
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)
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)
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)
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)
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)
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)
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])
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])
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])
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)
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)
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)
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)
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)
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)
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)
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])
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])
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 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)
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
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)
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)
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)
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
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)
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
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()
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
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 -- ")
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_()
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)