def _get_icon_rect(self, opt, text_rect): """Get a QRect for the icon to draw. Args: opt: QStyleOptionTab text_rect: The QRect for the text. Return: A QRect. """ icon_size = opt.iconSize if not icon_size.isValid(): icon_extent = self.pixelMetric(QStyle.PM_SmallIconSize) icon_size = QSize(icon_extent, icon_extent) icon_mode = (QIcon.Normal if opt.state & QStyle.State_Enabled else QIcon.Disabled) icon_state = (QIcon.On if opt.state & QStyle.State_Selected else QIcon.Off) tab_icon_size = opt.icon.actualSize(icon_size, icon_mode, icon_state) tab_icon_size = QSize(min(tab_icon_size.width(), icon_size.width()), min(tab_icon_size.height(), icon_size.height())) icon_rect = QRect(text_rect.left(), text_rect.center().y() - tab_icon_size.height() / 2, tab_icon_size.width(), tab_icon_size.height()) icon_rect = self._style.visualRect(opt.direction, opt.rect, icon_rect) qtutils.ensure_valid(icon_rect) return icon_rect
def _get_icon_rect(self, opt, text_rect): """Get a QRect for the icon to draw. Args: opt: QStyleOptionTab text_rect: The QRect for the text. Return: A QRect. """ icon_size = opt.iconSize if not icon_size.isValid(): icon_extent = self.pixelMetric(QStyle.PM_SmallIconSize) icon_size = QSize(icon_extent, icon_extent) icon_mode = (QIcon.Normal if opt.state & QStyle.State_Enabled else QIcon.Disabled) icon_state = (QIcon.On if opt.state & QStyle.State_Selected else QIcon.Off) # reserve space for favicon when tab bar is vertical (issue #1968) position = config.val.tabs.position if (position in [QTabWidget.East, QTabWidget.West] and config.val.tabs.favicons.show): tab_icon_size = icon_size else: actual_size = opt.icon.actualSize(icon_size, icon_mode, icon_state) tab_icon_size = QSize( min(actual_size.width(), icon_size.width()), min(actual_size.height(), icon_size.height())) icon_top = text_rect.center().y() + 1 - tab_icon_size.height() / 2 icon_rect = QRect(QPoint(text_rect.left(), icon_top), tab_icon_size) icon_rect = self._style.visualRect(opt.direction, opt.rect, icon_rect) return icon_rect
def maxSize(self): size = QSize() for variation in self.d.variations: size.setWidth(max(size.width(), variation.map.width())) size.setHeight(max(size.height(), variation.map.height())) return size
def get_favicon(self): u""" Get favicon for the site. This is called when the site_url can’t be loaded or when that page doesn’t contain a link tag with rel set to icon (the new way of doing site icons.) """ if self.site_icon: return if not with_pyqt: self.site_icon = None return ico_url = urllib.parse.urljoin(self.icon_url, "/favicon.ico") ico_request = urllib.request.Request(ico_url) if self.user_agent: ico_request.add_header('User-agent', self.user_agent) ico_response = urllib.request.urlopen(ico_request) if 200 != ico_response.code: self.site_icon = None return self.site_icon = QImage.fromData(ico_response.read()) max_size = QSize(self.max_icon_size, self.max_icon_size) ico_size = self.site_icon.size() if ico_size.width() > max_size.width() \ or ico_size.height() > max_size.height(): self.site_icon = self.site_icon.scaled( max_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
def calculate_relative_position(parent_rect: QtCore.QRect, own_size: QtCore.QSize, constraint: RelativeLayoutConstraint): """ Calculates the position of the element, given its size, the position and size of the parent and a relative layout constraint. The position is the position of the parent plus the weighted size of the parent, the weighted size of the element and an offset. The weights and the offset are given by the constraint for each direction. :param parent_rect: parent coordinates and size as rectangle :param own_size: size of the element (width and height) :param constraint: relative layout constraint to apply :return: tuple of recommended x and y positions of the element """ """ Returns the left, upper corner of an object if the parent rectangle (QRect) is given and our own size (QSize) and a relative layout constraint (see RelativeLayoutConstraint). """ x = (parent_rect.x() + constraint.x[0] * parent_rect.width() + constraint.x[1] * own_size.width() + constraint.x[2]) y = (parent_rect.y() + constraint.y[0] * parent_rect.height() + constraint.y[1] * own_size.height() + constraint.y[2]) return x, y
def renderToTexture(self, levelOfDetail = 1.0): # Determine the fbo size we will need. size = (self.sceneRect().size() * levelOfDetail).toSize() fboSize = nextPowerOfTwo(size) if fboSize.isEmpty(): fboSize = QSize(16, 16) # Create or re-create the fbo. if self.fbo is None or self.fbo.size() != fboSize: #del self.fbo self.fbo = QGLFramebufferObject(fboSize, self.format) if not self.fbo.isValid(): #del self.fbo self.fbo = None return 0 self.dirty = True # Return the previous texture contents if the scene hasn't changed. if self.fbo is not None and not self.dirty: return self.fbo.texture() # Render the scene into the fbo, scaling the QPainter's view # transform up to the power-of-two fbo size. painter = QPainter(self.fbo) painter.setWindow(0, 0, size.width(), size.height()) painter.setViewport(0, 0, fboSize.width(), fboSize.height()) self.render(painter) painter.end() self.dirty = False return self.fbo.texture()
def set_viewport(self, size, raise_if_empty=False): """ Set viewport size. If size is "full" viewport size is detected automatically. If can also be "<width>x<height>". .. note:: This will update all JS geometry variables, but window resize event is delivered asynchronously and so ``window.resize`` will not be invoked until control is yielded to the event loop. """ if size == 'full': size = self.web_page.mainFrame().contentsSize() self.logger.log("Contents size: %s" % size, min_level=2) if size.isEmpty(): if raise_if_empty: raise RuntimeError("Cannot detect viewport size") else: size = defaults.VIEWPORT_SIZE self.logger.log("Viewport is empty, falling back to: %s" % size) if not isinstance(size, QSize): validate_size_str(size) w, h = map(int, size.split('x')) size = QSize(w, h) self.web_page.setViewportSize(size) self._force_relayout() w, h = int(size.width()), int(size.height()) self.logger.log("viewport size is set to %sx%s" % (w, h), min_level=2) return w, h
def sizeHint(self): hint = QLabel.sizeHint(self) if self.maximum_size_hint != None: hint = QSize(max(hint.width(), self.maximum_size_hint.width()), max(hint.height(), self.maximum_size_hint.height())) self.maximum_size_hint = hint return hint
def calculateSize(self, sizeType): totalSize = QSize() for wrapper in self.list: position = wrapper.position itemSize = QSize() if sizeType == self.MinimumSize: itemSize = wrapper.item.minimumSize() else: # sizeType == self.SizeHint itemSize = wrapper.item.sizeHint() if position in (self.North, self.South, self.Center): totalSize.setHeight(totalSize.height() + itemSize.height()) if position in (self.West, self.East, self.Center): totalSize.setWidth(totalSize.width() + itemSize.width()) return totalSize
class FeatureTableWidgetHHeader(QTableWidgetItem): sub_trans = str.maketrans('0123456789', '₀₁₂₃₄₅₆₇₈₉') def __init__(self, column, sigma=None, window_size=3.5): QTableWidgetItem.__init__(self) # init # ------------------------------------------------ self.column = column self.sigma = sigma self.window_size = window_size self.pixmapSize = QSize(61, 61) self.setNameAndBrush(self.sigma) @property def brushSize(self): if self.sigma is None: return 0 else: return int(3.0 * self.sigma + 0.5) + 1 def setNameAndBrush(self, sigma, color=Qt.black): self.sigma = sigma self.setText(f'σ{self.column}'.translate(self.sub_trans)) if self.sigma is not None: total_window = (1 + 2 * int(self.sigma * self.window_size + 0.5)) self.setToolTip(f'sigma = {sigma:.1f} pixels, window diameter = {total_window:.1f}') font = QFont() font.setPointSize(10) # font.setBold(True) self.setFont(font) self.setForeground(color) pixmap = QPixmap(self.pixmapSize) pixmap.fill(Qt.transparent) painter = QPainter() painter.begin(pixmap) painter.setRenderHint(QPainter.Antialiasing, True) painter.setPen(color) brush = QBrush(color) painter.setBrush(brush) painter.drawEllipse(QRect(old_div(self.pixmapSize.width(), 2) - old_div(self.brushSize, 2), old_div(self.pixmapSize.height(), 2) - old_div(self.brushSize, 2), self.brushSize, self.brushSize)) painter.end() self.setIcon(QIcon(pixmap)) self.setTextAlignment(Qt.AlignVCenter) def setIconAndTextColor(self, color): self.setNameAndBrush(self.sigma, color)
def mapSize(self): p = RenderParams(self.map()) # The map size is the same regardless of which indexes are shifted. if (p.staggerX): size = QSize(self.map().width() * p.columnWidth + p.sideOffsetX, self.map().height() * (p.tileHeight + p.sideLengthY)) if (self.map().width() > 1): size.setHeight(size.height() + p.rowHeight) return size else: size = QSize(self.map().width() * (p.tileWidth + p.sideLengthX), self.map().height() * p.rowHeight + p.sideOffsetY) if (self.map().height() > 1): size.setWidth(size.width() + p.columnWidth) return size
def calculate_size(self): """Determine size by calculating the space of the visible items""" visible_items = min(self.model().rowCount(), self.MAX_VISIBLE_ITEMS) first_visible_row = self.verticalScrollBar().value() option = self.viewOptions() size_hint = QSize() for index in range(visible_items): tmp_size = self.itemDelegate().sizeHint( option, self.model().index(index + first_visible_row, 0)) if size_hint.width() < tmp_size.width(): size_hint = tmp_size height = size_hint.height() height *= visible_items size_hint.setHeight(height) return size_hint
def maybe_get_icon(self): u""" Get icon for the site as a QImage if we haven’t already. Get the site icon, either the 'rel="icon"' or the favicon, for the web page at url or passed in as page_html and store it as a QImage. This function can be called repeatedly and loads the icon only once. """ if self.site_icon: return if not with_pyqt: self.site_icon = None return page_request = urllib.request.Request(self.icon_url) if self.user_agent: page_request.add_header('User-agent', self.user_agent) page_response = urllib.request.urlopen(page_request) if 200 != page_response.code: self.get_favicon() return page_soup = soup(page_response, 'html.parser') try: icon_url = page_soup.find( name='link', attrs={'rel': 'icon'})['href'] except (TypeError, KeyError): self.get_favicon() return # The url may be absolute or relative. if not urllib.parse.urlsplit(icon_url).netloc: icon_url = urllib.parse.urljoin( self.url, urllib.parse.quote(icon_url.encode('utf-8'))) icon_request = urllib.request.Request(icon_url) if self.user_agent: icon_request.add_header('User-agent', self.user_agent) icon_response = urllib.request.urlopen(icon_request) if 200 != icon_response.code: self.site_icon = None return self.site_icon = QImage.fromData(icon_response.read()) max_size = QSize(self.max_icon_size, self.max_icon_size) icon_size = self.site_icon.size() if icon_size.width() > max_size.width() \ or icon_size.height() > max_size.height(): self.site_icon = self.site_icon.scaled( max_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
def __init__(self, parent): super().__init__(parent) self.setFocusPolicy(Qt.NoFocus) self.setBackgroundRole(QPalette.Window) self.setFrameShape(QFrame.NoFrame) self.setBackgroundBrush(QBrush(Qt.NoBrush)) self._scene = QGraphicsScene(self) self.setScene(self._scene) self._keys = {} self._midi_to_key = {} for octave in range(2, 7): for idx, note in enumerate(['C', 'D', 'E', 'F', 'G', 'A', 'B']): pitch_name = '%s%d' % (note, octave) key = PianoKey( parent, 140 * octave + 20 * idx, pitch_name, PianoKey.WHITE) self._keys[pitch_name] = key self._midi_to_key[music.NOTE_TO_MIDI[pitch_name]] = key self._scene.addItem(key) for idx, note in enumerate(['C#', 'D#', '', 'F#', 'G#', 'A#', '']): if not note: continue pitch_name = '%s%d' % (note, octave) key = PianoKey( parent, 140 * octave + 20 * idx + 10, pitch_name, PianoKey.BLACK) self._keys[pitch_name] = key self._midi_to_key[music.NOTE_TO_MIDI[pitch_name]] = key self._scene.addItem(key) size = self._scene.sceneRect().size() size = QSize(int(math.ceil(size.width())) + 1, int(math.ceil(size.height())) + 10) self.setMinimumSize(size) self.setMaximumSize(size)
def maybe_get_icon(self): if self.site_icon: return if not with_pyqt: self.site_icon = None return try: icon_data = self.get_data_from_url(self.full_icon_url) except: AudioDownloader.maybe_get_icon(self) else: self.site_icon = QImage.fromData(icon_data) max_size = QSize(self.max_icon_size, self.max_icon_size) ico_size = self.site_icon.size() if ico_size.width() > max_size.width() \ or ico_size.height() > max_size.height(): self.site_icon = self.site_icon.scaled( max_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
def setColor(self, color): if type(color)!=QColor: color = QColor(color) if (self.mColor == color or not color.isValid()): return self.mColor = color size = QSize(self.iconSize()) size.setWidth(size.width()-2) size.setHeight(size.height()-2) pixmap = QPixmap(size) pixmap.fill(self.mColor) painter = QPainter(pixmap) border = QColor(Qt.black) border.setAlpha(128) painter.setPen(border) painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1) painter.end() self.setIcon(QIcon(pixmap)) self.colorChanged.emit(color)
class ResizeMap(QUndoCommand): def __init__(self, mapDocument, size): super().__init__(QCoreApplication.translate("Undo Commands", "Resize Map")) self.mMapDocument = mapDocument self.mSize = QSize(size) def undo(self): self.swapSize() def redo(self): self.swapSize() def swapSize(self): map = self.mMapDocument.map() oldSize = QSize(map.width(), map.height()) map.setWidth(self.mSize.width()) map.setHeight(self.mSize.height()) self.mSize = oldSize self.mMapDocument.emitMapChanged()
def paintEvent(self, event, *args): """ Custom paint event """ self.mutex.lock() # Paint custom frame image on QWidget painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.TextAntialiasing, True) # Fill background black painter.fillRect(event.rect(), self.palette().window()) if self.current_image: # DRAW FRAME # Calculate new frame image size, maintaining aspect ratio pixSize = self.current_image.size() pixSize.scale(event.rect().size(), Qt.KeepAspectRatio) # Scale image scaledPix = self.current_image.scaled(pixSize, Qt.KeepAspectRatio, Qt.SmoothTransformation) # Calculate center of QWidget and Draw image center = self.centeredViewport(self.width(), self.height()) painter.drawImage(center, scaledPix) if self.transforming_clip: # Draw transform handles on top of video preview # Get framerate fps = get_app().project.get(["fps"]) fps_float = float(fps["num"]) / float(fps["den"]) # Determine frame # of clip start_of_clip = round(float(self.transforming_clip.data["start"]) * fps_float) position_of_clip = (float(self.transforming_clip.data["position"]) * fps_float) + 1 playhead_position = float(get_app().window.preview_thread.current_frame) clip_frame_number = round(playhead_position - position_of_clip) + start_of_clip + 1 # Get properties of clip at current frame raw_properties = json.loads(self.transforming_clip_object.PropertiesJSON(clip_frame_number)) # Get size of current video player player_width = self.rect().width() player_height = self.rect().height() # Determine original size of clip's reader source_width = self.transforming_clip.data['reader']['width'] source_height = self.transforming_clip.data['reader']['height'] source_size = QSize(source_width, source_height) # Determine scale of clip scale = self.transforming_clip.data['scale'] if scale == openshot.SCALE_FIT: source_size.scale(player_width, player_height, Qt.KeepAspectRatio) elif scale == openshot.SCALE_STRETCH: source_size.scale(player_width, player_height, Qt.IgnoreAspectRatio) elif scale == openshot.SCALE_CROP: width_size = QSize(player_width, round(player_width / (float(source_width) / float(source_height)))) height_size = QSize(round(player_height / (float(source_height) / float(source_width))), player_height) if width_size.width() >= player_width and width_size.height() >= player_height: source_size.scale(width_size.width(), width_size.height(), Qt.KeepAspectRatio) else: source_size.scale(height_size.width(), height_size.height(), Qt.KeepAspectRatio) # Get new source width / height (after scaling mode applied) source_width = source_size.width() source_height = source_size.height() # Init X/Y x = 0.0 y = 0.0 # Get scaled source image size (scale_x, scale_y) sx = max(float(raw_properties.get('scale_x').get('value')), 0.001) sy = max(float(raw_properties.get('scale_y').get('value')), 0.001) scaled_source_width = source_width * sx scaled_source_height = source_height * sy # Determine gravity of clip gravity = self.transforming_clip.data['gravity'] if gravity == openshot.GRAVITY_TOP_LEFT: x += self.centeredViewport(self.width(), self.height()).x() # nudge right y += self.centeredViewport(self.width(), self.height()).y() # nudge down elif gravity == openshot.GRAVITY_TOP: x = (player_width - scaled_source_width) / 2.0 # center y += self.centeredViewport(self.width(), self.height()).y() # nudge down elif gravity == openshot.GRAVITY_TOP_RIGHT: x = player_width - scaled_source_width # right x -= self.centeredViewport(self.width(), self.height()).x() # nudge left y += self.centeredViewport(self.width(), self.height()).y() # nudge down elif gravity == openshot.GRAVITY_LEFT: y = (player_height - scaled_source_height) / 2.0 # center x += self.centeredViewport(self.width(), self.height()).x() # nudge right elif gravity == openshot.GRAVITY_CENTER: x = (player_width - scaled_source_width) / 2.0 # center y = (player_height - scaled_source_height) / 2.0 # center elif gravity == openshot.GRAVITY_RIGHT: x = player_width - scaled_source_width # right y = (player_height - scaled_source_height) / 2.0 # center x -= self.centeredViewport(self.width(), self.height()).x() # nudge left elif gravity == openshot.GRAVITY_BOTTOM_LEFT: y = (player_height - scaled_source_height) # bottom x += self.centeredViewport(self.width(), self.height()).x() # nudge right y -= self.centeredViewport(self.width(), self.height()).y() # nudge up elif gravity == openshot.GRAVITY_BOTTOM: x = (player_width - scaled_source_width) / 2.0 # center y = (player_height - scaled_source_height) # bottom y -= self.centeredViewport(self.width(), self.height()).y() # nudge up elif gravity == openshot.GRAVITY_BOTTOM_RIGHT: x = player_width - scaled_source_width # right y = (player_height - scaled_source_height) # bottom x -= self.centeredViewport(self.width(), self.height()).x() # nudge left y -= self.centeredViewport(self.width(), self.height()).y() # nudge up # Track gravity starting coordinate self.gravity_point = QPointF(x, y) # Scale to fit in widget final_size = QSize(source_width, source_height) # Adjust x,y for location x_offset = raw_properties.get('location_x').get('value') y_offset = raw_properties.get('location_y').get('value') x += (scaledPix.width() * x_offset) y += (scaledPix.height() * y_offset) self.transform = QTransform() # Apply translate/move if x or y: self.transform.translate(x, y) # Apply scale if sx or sy: self.transform.scale(sx, sy) # Apply shear shear_x = raw_properties.get('shear_x').get('value') shear_y = raw_properties.get('shear_y').get('value') if shear_x or shear_y: self.transform.shear(shear_x, shear_y) # Apply rotation rotation = raw_properties.get('rotation').get('value') if rotation: origin_x = x - self.centeredViewport(self.width(), self.height()).x() + (scaled_source_width / 2.0) origin_y = y - self.centeredViewport(self.width(), self.height()).y() + (scaled_source_height / 2.0) self.transform.translate(origin_x, origin_y) self.transform.rotate(rotation) self.transform.translate(-origin_x, -origin_y) # Apply transform painter.setTransform(self.transform) # Draw transform corners and center origin circle # Corner size cs = 6.0 os = 12.0 # Calculate 4 corners coordinates self.topLeftHandle = QRectF(0.0, 0.0, cs/sx, cs/sy) self.topRightHandle = QRectF(source_width - (cs/sx), 0, cs/sx, cs/sy) self.bottomLeftHandle = QRectF(0.0, source_height - (cs/sy), cs/sx, cs/sy) self.bottomRightHandle = QRectF(source_width - (cs/sx), source_height - (cs/sy), cs/sx, cs/sy) # Draw 4 corners painter.fillRect(self.topLeftHandle, QBrush(QColor("#53a0ed"))) painter.fillRect(self.topRightHandle, QBrush(QColor("#53a0ed"))) painter.fillRect(self.bottomLeftHandle, QBrush(QColor("#53a0ed"))) painter.fillRect(self.bottomRightHandle, QBrush(QColor("#53a0ed"))) # Calculate 4 side coordinates self.topHandle = QRectF(0.0 + (source_width / 2.0) - (cs/sx/2.0), 0, cs/sx, cs/sy) self.bottomHandle = QRectF(0.0 + (source_width / 2.0) - (cs/sx/2.0), source_height - (cs/sy), cs/sx, cs/sy) self.leftHandle = QRectF(0.0, (source_height / 2.0) - (cs/sy/2.0), cs/sx, cs/sy) self.rightHandle = QRectF(source_width - (cs/sx), (source_height / 2.0) - (cs/sy/2.0), cs/sx, cs/sy) # Draw 4 sides (centered) painter.fillRect(self.topHandle, QBrush(QColor("#53a0ed"))) painter.fillRect(self.bottomHandle, QBrush(QColor("#53a0ed"))) painter.fillRect(self.leftHandle, QBrush(QColor("#53a0ed"))) painter.fillRect(self.rightHandle, QBrush(QColor("#53a0ed"))) # Calculate center coordinate self.centerHandle = QRectF((source_width / 2.0) - (os/sx), (source_height / 2.0) - (os/sy), os/sx*2.0, os/sy*2.0) # Draw origin painter.setBrush(QColor(83, 160, 237, 122)) painter.setPen(Qt.NoPen) painter.drawEllipse(self.centerHandle) # Draw translucent rectangle self.clipRect = QRectF(0, 0, final_size.width(), final_size.height()) # Remove transform painter.resetTransform() # End painter painter.end() self.mutex.unlock()
class Settings(object): l = logging.getLogger('Settings') def __init__(self, fileName = 'notepad.ini'): self.fileName = fileName self.notepads = [] self.mainWindowSize = QSize(1200, 700) self.mainWindowPos = QPoint(240, 200) self.browserPath = [] def load(self): self.l.debug('Loading local settings ...') config = configparser.ConfigParser() config.read(self.fileName) # load dropbox configuration self.dropboxToken = '' if config.has_section('dropbox'): self.dropboxToken = config['dropbox'].get('dropboxToken') # load browser state if config.has_section('browser'): browserSettings = config['browser'] path = browserSettings.get('path') self.browserPath = ast.literal_eval(path) # load main window position and size if config.has_section('mainWindow'): windowSettings = config['mainWindow'] # read position pos = windowSettings.get('pos', '{},{}'.format(self.mainWindowPos.x(), self.mainWindowPos.y())).split(',') self.mainWindowPos = QPoint(int(pos[0]), int(pos[1])) # read size size = windowSettings.get('size', '{},{}'.format(self.mainWindowSize.width(), self.mainWindowSize.height())).split(',') self.mainWindowSize = QSize(int(size[0]), int(size[1])) # load notepads self.notepads = [] for s in config.sections(): if s.startswith('notepad_'): npDef = None npType = config.get(s, 'type') if npType == 'local': npDef = {'name' : config.get(s, 'name'), 'type' : npType, 'path' : config.get(s, 'path') } elif npType == 'dropbox': npDef = {'name' : config.get(s, 'name'), 'type' : npType } if npDef is not None: self.notepads.append(npDef) def dump(self, channel): self.l.debug('Saving local settings ...') config = configparser.ConfigParser() config.add_section('dropbox') config.set('dropbox', 'dropboxToken', self.dropboxToken) config.add_section('browser') config.set('browser', 'path', str(self.browserPath)) config.add_section('mainWindow') config.set('mainWindow', 'pos', '{},{}'.format(self.mainWindowPos.x(), self.mainWindowPos.y())) config.set('mainWindow', 'size', '{},{}'.format(self.mainWindowSize.width(), self.mainWindowSize.height())) idx = 0 for s in self.notepads: sectName = "notepad_{}".format(idx) idx += 1 config.add_section(sectName) config.set(sectName, 'name', s['name']) config.set(sectName, 'type', s['type']) if s['type'] == 'local': config.set(sectName, 'path', s['path']) config.write(channel) def save(self): with open(self.fileName, 'w') as configfile: self.dump(configfile) def getNotepads(self): return self.notepads def addNotepad(self, npDef): self.notepads.append(npDef) def setMainWindowPos(self, pos): self.mainWindowPos = pos def setBrowserPath(self, path): self.browserPath = path def setMainWindowSize(self, size): self.mainWindowSize = size def getMainWindowPos(self): return self.mainWindowPos def getMainWindowSize(self): return self.mainWindowSize def getBrowserPath(self): return self.browserPath def getDropboxToken(self): return self.dropboxToken def setDropboxToken(self, token): self.dropboxToken = token
class ResizeHelper(QWidget): offsetChanged = pyqtSignal(QPoint) offsetXChanged = pyqtSignal(int) offsetYChanged = pyqtSignal(int) offsetBoundsChanged = pyqtSignal(QRect) def __init__(self, parent = None): super().__init__(parent) self.mMouseAnchorPoint = QPoint() self.mOffset = QPoint() self.mOldSize = QSize() self.mDragging = False self.mOffsetBounds = QRect() self.mScale = 0.0 self.mNewSize = QSize() self.mOrigOffset = QPoint() self.setMinimumSize(20, 20) self.setOldSize(QSize(1, 1)) def oldSize(self): return self.mOldSize def newSize(self): return self.mNewSize def offset(self): return self.mOffset def offsetBounds(self): return self.mOffsetBounds def setOldSize(self, size): self.mOldSize = size self.recalculateMinMaxOffset() self.recalculateScale() def setNewSize(self, size): self.mNewSize = size self.recalculateMinMaxOffset() self.recalculateScale() def setOffset(self, offset): # Clamp the offset within the offset bounds newOffset = QPoint(min(self.mOffsetBounds.right(), max(self.mOffsetBounds.left(), offset.x())), min(self.mOffsetBounds.bottom(), max(self.mOffsetBounds.top(), offset.y()))) if (self.mOffset != newOffset): xChanged = self.mOffset.x() != newOffset.x() yChanged = self.mOffset.y() != newOffset.y() self.mOffset = newOffset if (xChanged): self.offsetXChanged.emit(self.mOffset.x()) if (yChanged): self.offsetYChanged.emit(self.mOffset.y()) self.offsetChanged.emit(self.mOffset) self.update() ## Method to set only the X offset, provided for convenience. */ def setOffsetX(self, x): self.setOffset(QPoint(x, self.mOffset.y())) ## Method to set only the Y offset, provided for convenience. */ def setOffsetY(self, y): self.setOffset(QPoint(self.mOffset.x(), y)) ## Method to set only new width, provided for convenience. */ def setNewWidth(self, width): self.mNewSize.setWidth(width) self.recalculateMinMaxOffset() self.recalculateScale() ## Method to set only new height, provided for convenience. */ def setNewHeight(self, height): self.mNewSize.setHeight(height) self.recalculateMinMaxOffset() self.recalculateScale() def paintEvent(self, event): _size = self.size() - QSize(2, 2) if (_size.isEmpty()): return origX = (_size.width() - self.mNewSize.width() * self.mScale) / 2 + 0.5 origY = (_size.height() - self.mNewSize.height() * self.mScale) / 2 + 0.5 oldRect = QRect(self.mOffset, self.mOldSize) painter = QPainter(self) painter.translate(origX, origY) painter.scale(self.mScale, self.mScale) pen = QPen(Qt.black) pen.setCosmetic(True) painter.setPen(pen) painter.drawRect(QRect(QPoint(0, 0), self.mNewSize)) pen.setColor(Qt.white) painter.setPen(pen) painter.setBrush(Qt.white) painter.setOpacity(0.5) painter.drawRect(oldRect) pen.setColor(Qt.black) pen.setStyle(Qt.DashLine) painter.setOpacity(1.0) painter.setBrush(Qt.NoBrush) painter.setPen(pen) painter.drawRect(oldRect) painter.end() def mousePressEvent(self, event): self.mMouseAnchorPoint = event.pos() self.mOrigOffset = self.mOffset self.mDragging = event.button() == Qt.LeftButton def mouseMoveEvent(self, event): if (not self.mDragging): return pos = event.pos() if (pos != self.mMouseAnchorPoint): self.setOffset(self.mOrigOffset + (pos - self.mMouseAnchorPoint) / self.mScale) self.offsetChanged.emit(self.mOffset) def resizeEvent(self, event): self.recalculateScale() def recalculateScale(self): _size = self.size() - QSize(2, 2) if (_size.isEmpty()): return if self.mOldSize.width() < self.mNewSize.width(): width = self.mNewSize.width() else: width = 2 * self.mOldSize.width() - self.mNewSize.width() if self.mOldSize.height() < self.mNewSize.height(): height = self.mNewSize.height() else: height = 2 * self.mOldSize.height() - self.mNewSize.height() # Pick the smallest scale scaleW = _size.width() / width scaleH = _size.height() / height if scaleW < scaleH: self.mScale = scaleW else: self.mScale = scaleH self.update() def recalculateMinMaxOffset(self): offsetBounds = self.mOffsetBounds if (self.mOldSize.width() <= self.mNewSize.width()): offsetBounds.setLeft(0) offsetBounds.setRight(self.mNewSize.width() - self.mOldSize.width()) else: offsetBounds.setLeft(self.mNewSize.width() - self.mOldSize.width()) offsetBounds.setRight(0) if (self.mOldSize.height() <= self.mNewSize.height()): offsetBounds.setTop(0) offsetBounds.setBottom(self.mNewSize.height() - self.mOldSize.height()) else: offsetBounds.setTop(self.mNewSize.height() - self.mOldSize.height()) offsetBounds.setBottom(0) if (self.mOffsetBounds != offsetBounds): self.mOffsetBounds = offsetBounds self.offsetBoundsChanged.emit(self.mOffsetBounds)
oppsite = { Qt.Key_Left: Qt.Key_Right, Qt.Key_Right: Qt.Key_Left, Qt.Key_Up: Qt.Key_Down, Qt.Key_Down: Qt.Key_Up } # Return current time in seconds as a int number. def now(): return int(round(time.time()*1000)) # Return a tuple of block size def bsize() return block.width()-1, block.height()-1 # return the scale of the window def scale_sz(sz) return sz.width() * block.width(), sz.height()* block.height() # return the size of snake size def scaled_pt(p): return p.x() * block.width(), p.y() * block.height() # Snake controller Game class Game(ojbect): def __init__(self): super(Snake, self).__init__()
class AbstractLayout(QObject): """Manages page.Page instances with a list-like api. You can iterate over the layout itself, which yields all Page instances. You can also iterate over pages(), which only yields the Page instances that are visible(). """ redraw = pyqtSignal(QRect) changed = pyqtSignal() scaleChanged = pyqtSignal(float) def __init__(self): super(AbstractLayout, self).__init__() self._pages = [] self._size = QSize() self._margin = 4 self._spacing = 8 self._scale = 1.0 self._scaleChanged = False self._dpi = (72, 72) def own(self, page): """(Internal) Makes the page have ourselves as layout.""" if page.layout(): page.layout().remove(page) page._layout = weakref.ref(self) page.computeSize() def disown(self, page): """(Internal) Removes ourselves as owner of the page.""" page._layout = lambda: None def append(self, page): self.own(page) self._pages.append(page) def insert(self, position, page): self.own(page) self._pages.insert(position, page) def extend(self, pages): for page in pages: self.append(page) def remove(self, page): self._pages.remove(page) self.disown(page) def pop(self, index=None): page = self._pages.pop(index) self.disown(page) return page def clear(self): del self[:] def count(self): return len(self._pages) def __len__(self): return len(self._pages) def __bool__(self): return True def __contains__(self, page): return page in self._pages def __getitem__(self, item): return self._pages[item] def __delitem__(self, item): if isinstance(item, slice): for page in self._pages[item]: self.disown(page) else: self.disown(self._pages[item]) del self._pages[item] def __setitem__(self, item, new): if isinstance(item, slice): old = self._pages[item] self._pages[item] = new for page in self._pages[item]: self.own(page) for page in old: self.disown(page) else: self.disown(self._pages[item]) self._pages[item] = new self.own(new) def index(self, page): """Returns the index at which the given Page can be found in our Layout.""" return self._pages.index(page) def setSize(self, size): """Sets our size. Mainly done after layout.""" self._size = size def size(self): """Returns our size as QSize().""" return self._size def width(self): """Returns our width.""" return self._size.width() def height(self): """Returns our height.""" return self._size.height() def setDPI(self, xdpi, ydpi=None): """Sets our DPI in X and Y direction. If Y isn't given, uses the X value.""" self._dpi = xdpi, ydpi or xdpi for page in self: page.computeSize() def dpi(self): """Returns our DPI as a tuple(XDPI, YDPI).""" return self._dpi def scale(self): """Returns the scale (1.0 == 100%).""" return self._scale def setScale(self, scale): """Sets the scale (1.0 == 100%) of all our Pages.""" if scale != self._scale: self._scale = scale for page in self: page.setScale(scale) self._scaleChanged = True def setPageWidth(self, width, sameScale=True): """Sets the width of all pages. If sameScale is True (default), the largest page will be scaled to the given width (minus margin). All pages will then be scaled to that scale. If sameScale is False all pages will be scaled individually to the same width. """ if sameScale and any(self.pages()): self.setScale(self.widest().scaleForWidth(width)) else: for page in self: page.setWidth(width) def setPageHeight(self, height, sameScale=True): """Sets the height of all pages. If sameScale is True (default), the largest page will be scaled to the given height (minus margin). All pages will then be scaled to that scale. If sameScale is False all pages will be scaled individually to the same height. """ if sameScale and any(self.pages()): self.setScale(self.highest().scaleForWidth(height)) else: for page in self: page.setHeight(height) def setMargin(self, margin): """Sets the margin around the pages in pixels.""" self._margin = margin def margin(self): """Returns the margin around the pages in pixels.""" return self._margin def setSpacing(self, spacing): """Sets the space between the pages in pixels.""" self._spacing = spacing def spacing(self): """Returns the space between the pages in pixels.""" return self._spacing def fit(self, size, mode): """Fits the layout in the given ViewMode.""" if mode and any(self.pages()): scales = [] if mode & FitWidth: scales.append(self.scaleFitWidth(size.width())) if mode & FitHeight: scales.append(self.scaleFitHeight(size.height())) self.setScale(min(scales)) def scaleFitHeight(self, height): """Return the scale this layout would need to fit in the height. This method is called by fit(). The default implementation returns a suitable scale for the highest Page. """ return self.highest().scaleForHeight(height - self.margin() * 2) def scaleFitWidth(self, width): """Return the scale this layout would need to fit in the width. This method is called by fit(). The default implementation returns a suitable scale for the widest Page. """ return self.widest().scaleForWidth(width - self.margin() * 2) def update(self): """Performs the layout (positions the Pages and adjusts our size).""" self.reLayout() if self._scaleChanged: self.scaleChanged.emit(self._scale) self._scaleChanged = False self.changed.emit() def reLayout(self): """This is called by update(). You must implement this method to position the Pages and adjust our size. See Layout for a possible implementation. """ pass def updatePage(self, page): """Called by the Page when an image has been generated.""" self.redraw.emit(page.rect()) def page(self, document, pageNumber): """Returns the page (visible or not) from a Poppler.Document with page number. Returns None if that page is not available. """ # Specific layouts may use faster algorithms to find the page. try: page = self[pageNumber] except IndexError: pass else: if page.document() == document: return page for page in self: if page.document() == document and page.pageNumber() == pageNumber: return page def pages(self): """Yields our pages that are visible().""" for page in self: if page.visible(): yield page def pageAt(self, point): """Returns the page that contains the given QPoint.""" # Specific layouts may use faster algorithms to find the page. for page in self.pages(): if page.rect().contains(point): return page def pagesAt(self, rect): """Yields the pages touched by the given QRect.""" # Specific layouts may use faster algorithms to find the pages. for page in self.pages(): if page.rect().intersects(rect): yield page def linkAt(self, point): """Returns (page, link) if pos points to a Poppler.Link in a Page, else (None, None).""" page = self.pageAt(point) if page: links = page.linksAt(point) if links: return page, links[0] return None, None def widest(self): """Returns the widest visible page (in its natural page size).""" pages = list(self.pages()) if pages: return max(pages, key = lambda p: p.pageSize().width()) def highest(self): """Returns the highest visible page (in its natural page size).""" pages = list(self.pages()) if pages: return max(pages, key = lambda p: p.pageSize().height()) def maxWidth(self): """Returns the width of the widest visible page.""" page = self.widest() return page.width() if page else 0 def maxHeight(self): """Returns the height of the highest visible page.""" page = self.highest() return page.height() if page else 0 def load(self, document): """Convenience method to load all the pages of the given Poppler.Document using page.Page().""" self.clear() for num in range(document.numPages()): p = page.Page(document, num) p.setScale(self._scale) self.append(p)
class PreviewWindow(QFrame): minsize = (32, 32) maxsize = None last_save_dir = "" raise_window = False key_signal = pyqtSignal(int, int, int) move_signal = pyqtSignal() def __init__(self, manager, name, image=None, message=None, position=None, size=None, high_quality=False): super(PreviewWindow, self).__init__() self.setObjectName("Preview window {}".format(name)) self.setWindowTitle(name) self.manager = manager desktop = QApplication.instance().desktop() if self.maxsize: self.maxsize = QSize(*self.maxsize) else: self.maxsize = desktop.screenGeometry( desktop.screenNumber(self)).size() * 0.95 self.setMinimumSize(*self.minsize) self.setMaximumSize(self.maxsize) self.image = None self.original = None self.message = message self.scale = 1. self.rotation = 0 self.quality = Qt.SmoothTransformation if high_quality else Qt.FastTransformation self.fixed_size = size self.scrollarea = PreviewScrollArea() self.scrollarea.setFrameStyle(0) self.scrollarea.setFocusPolicy(Qt.NoFocus) layout = QGridLayout() layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) layout.addWidget(self.scrollarea, 0, 0) self.preview = QLabel() self.preview.setMouseTracking(False) self.preview.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.scrollarea.setWidget(self.preview) self.message_label = QLabel(" ") self.layout().addWidget(self.message_label, 0, 0, Qt.AlignTop) self.message_label.setStyleSheet( "QLabel {color:black;background:rgba(255,255,255,32)}") self.message_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.message_label.setText("") shadow = QGraphicsDropShadowEffect() shadow.setBlurRadius(4) shadow.setColor(Qt.white) shadow.setOffset(0, 0) self.message_label.setGraphicsEffect(shadow) self.blink_widget = QWidget() self.blink_widget.hide() self.blink_widget.setStyleSheet("border:3px solid red") self.blink_timer = QTimer() self.blink_timer.setInterval(1000) self.blink_timer.timeout.connect(self.blink_) layout.addWidget(self.blink_widget, 0, 0) self.setImage(image, show=False) if image is not None and not size: size = self.autoSize() if size: self.resize(*size) if position == 'cursor': position = (QCursor.pos().x() - self.size().width() // 2, QCursor.pos().y() - self.size().height() // 2) if position: self.move(*position) self.showNormal() def setImage(self, image, show=True, scale=None, blink=False): if image is None: return self.original = image if isinstance(image, QImage): image = QPixmap.fromImage(image) elif isinstance(image, QPixmap): pass else: image = array_to_pixmap(image) self.image = image if not scale: scale = self.scale if image.width() * scale > self.maxsize.width(): scale = self.maxsize.width() / image.width() if image.height() * scale > self.maxsize.height(): scale = self.maxsize.height() / image.height() self.setZoom(scale) if self.message is not None: self.message_label.setText(self.message) if blink: self.blink(True) if show: self.show() if self.raise_window: self.raise_() def closeEvent(self, event): self.scale = 1.0 self.fixed_size = None def setImageAndParams(self, image, show=True, scale=None, position=None, size=None, hq=None, message=None, blink=False): if size: self.fixed_size = size if position: self.move(*position) if hq is not None: if hq: self.quality = Qt.SmoothTransformation else: self.quality = Qt.FastTransformation if message is not None: self.message = message self.setImage(image, show=show, scale=scale, blink=blink) def setZoom(self, scale): self.setParams(scale=scale) def setRotation(self, rotation): self.setParams(rotation=rotation) def setParams(self, scale=None, rotation=None): assert isinstance(self.image, QPixmap) if scale is None: scale = self.scale if rotation is None: rotation = self.rotation if scale != 1.0 or rotation: transform = QTransform().rotate(rotation).scale(scale, scale) pixmap = self.image.transformed(transform, self.quality) else: pixmap = self.image w = pixmap.width() h = pixmap.height() self.scale = scale self.rotation = rotation self.preview.setPixmap(pixmap) self.preview.setFixedSize(pixmap.size()) if not self.fixed_size: self.resize(w, h) def autoSize(self): size = self.image.size() w = size.width() h = size.height() return w, h def wheelEvent(self, event): assert isinstance(event, QWheelEvent) event.accept() if event.angleDelta().y() > 0: s = 1.1 else: s = 1 / 1.1 self.setZoom(s * self.scale) scrollX = self.scrollarea.horizontalScrollBar().value() posX = event.x() newX = s * (scrollX + posX) - posX self.scrollarea.horizontalScrollBar().setValue(int(newX)) scrollY = self.scrollarea.verticalScrollBar().value() posY = event.y() newY = s * (scrollY + posY) - posY self.scrollarea.verticalScrollBar().setValue(int(newY)) self.blink(False) def mousePressEvent(self, event): assert isinstance(event, QMouseEvent) self.key_signal.emit(KEY_MOUSE, event.x(), event.y()) self.blink(False) event.accept() def keyPressEvent(self, event): assert isinstance(event, QKeyEvent) self.key_signal.emit(int(event.key()), 0, 0) self.blink(False) if event.key() == Qt.Key_Escape: self.close() event.accept() def moveEvent(self, event): self.move_signal.emit() def contextMenuEvent(self, event): assert isinstance(event, QContextMenuEvent) self.blink(False) menu = QMenu() copy = menu.addAction("Copy to clipboard") reset = menu.addAction("Reset view") hq = menu.addAction("High quality") hq.setCheckable(True) if self.quality == Qt.SmoothTransformation: hq.setChecked(True) fixed = menu.addAction("Fixed size") fixed.setCheckable(True) if self.fixed_size: fixed.setChecked(True) rotate_right = menu.addAction("Rotate +") rotate_left = menu.addAction("Rotate -") save = menu.addAction("Save...") quit = menu.addAction("Close") action = menu.exec_(self.mapToGlobal(event.pos())) if action == quit: self.close() elif action == reset: self.setParams(1, 0) elif action == hq: if self.quality == Qt.SmoothTransformation: self.quality = Qt.FastTransformation else: self.quality = Qt.SmoothTransformation self.setZoom(self.scale) elif action == rotate_right: rotation = (self.rotation + 90) % 360 self.setRotation(rotation) elif action == rotate_left: rotation = (self.rotation + 270) % 360 self.setRotation(rotation) elif action == save: filename, filter = QFileDialog.getSaveFileNameAndFilter( self, "Save image...", filter="*.png;;*.jpg;;*.bmp;;*.tiff;;*.gif", directory=self.last_save_dir) if filename: try: if not str(filename).endswith(filter[1:]): filename = filename + filter[1:] PreviewWindow.last_save_dir = path.dirname(str(filename)) success = self.image.save(filename, quality=100) if not success: raise Exception("unknown error") except Exception as e: QMessageBox.critical( self, "Saving error", "Cannot save.\nError: {}".format(e.message)) print("Saving error:", e) elif action == fixed: if self.fixed_size: self.fixed_size = None else: self.fixed_size = self.size() elif action == copy: print("copy") clipboard = QApplication.instance().clipboard() clipboard.setPixmap(self.image) def blink(self, enable): if enable: self.blink_timer.start() else: self.blink_timer.stop() self.blink_widget.hide() @pyqtSlot() def blink_(self): if self.blink_widget.isHidden(): self.blink_widget.show() else: self.blink_widget.hide()
class SquircleRenderer(QObject): #QOpenGLFunctions """docstring for SquircleRenderer""" def __init__(self, parent=None): super(SquircleRenderer, self).__init__(parent) self.m_t = 0.0 self.m_program = None self.m_viewportSize = QSize() def setT(self, t): self.m_t = t def setViewportSize(self, size): self.m_viewportSize = size def setWin(self, win): self.win = win ver = QOpenGLVersionProfile() ver.setVersion(2, 1) self.m_context = self.win.openglContext() self.gl = self.m_context.versionFunctions(ver) @pyqtSlot() def paint(self): if not self.m_program: self.gl.initializeOpenGLFunctions() self.m_program = QOpenGLShaderProgram(self) self.m_program.addShaderFromSourceCode( QOpenGLShader.Vertex, "attribute highp vec4 vertices;" "varying highp vec2 coords;" "void main() {" " gl_Position = vertices;" " coords = vertices.xy;" "}") self.m_program.addShaderFromSourceCode( QOpenGLShader.Fragment, "uniform lowp float t;" "varying highp vec2 coords;" "void main() {" " lowp float i = 1. - (pow(abs(coords.x), 4.) + pow(abs(coords.y), 4.));" " i = smoothstep(t - 0.8, t + 0.8, i);" " i = floor(i * 20.) / 20.;" " gl_FragColor = vec4(coords * .5 + .5, i, i);" "}") self.m_program.bindAttributeLocation("vertices", 0) self.m_program.link() self.m_program.bind() self.m_program.enableAttributeArray(0) values = [(-1, -1), (1, -1), (-1, 1), (1, 1)] self.m_program.setAttributeArray(0, values) self.m_program.setUniformValue("t", self.m_t) #print("DATA:",self.m_viewportSize.width(), self.m_viewportSize.height(), self.m_t)#, self.gl.glViewport) self.gl.glViewport(0, 0, self.m_viewportSize.width(), self.m_viewportSize.height()) self.gl.glDisable(self.gl.GL_DEPTH_TEST) self.gl.glClearColor(0, 0, 0, 1) self.gl.glClear(self.gl.GL_COLOR_BUFFER_BIT) self.gl.glEnable(self.gl.GL_BLEND) self.gl.glBlendFunc(self.gl.GL_SRC_ALPHA, self.gl.GL_ONE) self.gl.glDrawArrays(self.gl.GL_TRIANGLE_STRIP, 0, 4) self.m_program.disableAttributeArray(0) self.m_program.release()
def __init__(self, pages: AllPages, windowSize: QSize): super().__init__(pages, windowSize) mainLayout = QVBoxLayout() mainLayout.setContentsMargins(0, 0, 0, 0) self.setLayout(mainLayout) #Titel mainLayout.addWidget( self.getTitleAsQLabel(TextKey.PAGE_SYSTEMPICTUREMANAGER_TITLE)) scroll_area_content_widget = QWidget() self.scroll_area = QScrollArea(self) self.scroll_area.setGeometry(0, 0, windowSize.width(), windowSize.height()) self.scroll_area.setWidgetResizable(True) self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scroll_area.setWidget(scroll_area_content_widget) mainContentLabel = QVBoxLayout() scroll_area_content_widget.setLayout(mainContentLabel) mainLayout.addWidget(self.scroll_area) #Funny pictures funnyTitle = QLabel( textValue[TextKey.PAGE_SYSTEMPICTUREMANAGER_FUNNY_TITEL]) funnyTitleFont = QFont() funnyTitleFont.setUnderline(True) funnyTitle.setFont(funnyTitleFont) mainContentLabel.addWidget(funnyTitle) mainContentLabel.addWidget( QLabel(textValue[TextKey.PAGE_SYSTEMPICTUREMANAGER_SOURCELABEL])) funnySource = QLineEdit() funnySource.setEnabled(False) funnySource.setText( CfgService.get( CfgKey.PAGE_SYSTEMPICTUREMANAGER_FUNNY_PICTURE_SOURCE)) mainContentLabel.addWidget(funnySource) mainContentLabel.addWidget( QLabel(textValue[TextKey.PAGE_SYSTEMPICTUREMANAGER_TARGETLABEL])) funnyTarget = QLineEdit() funnyTarget.setEnabled(False) funnyTarget.setText( CfgService.get(CfgKey.PAGE_CAPTUREPHOTO_LAST_IMAGE_FOLDER)) mainContentLabel.addWidget(funnyTarget) funnyPictureNavigation = QHBoxLayout() mainContentLabel.addLayout(funnyPictureNavigation) self.funnyDeleteButton = QPushButton( textValue[TextKey.PAGE_SYSTEMPICTUREMANAGER_DELETEBUTTON]) self.funnyDeleteButton.clicked.connect(self.deleteFunnyPictureFolder) funnyPictureNavigation.addWidget(self.funnyDeleteButton) self.funnyUpdateButton = QPushButton( textValue[TextKey.PAGE_SYSTEMPICTUREMANAGER_UPDATEBUTTON]) self.funnyUpdateButton.clicked.connect(self.updateFunnyPictures) funnyPictureNavigation.addWidget(self.funnyUpdateButton) #Loading Gifs loadingGifTitle = QLabel( textValue[TextKey.PAGE_SYSTEMPICTUREMANAGER_LOADINGGIFS_TITEL]) loadingGifFont = QFont() loadingGifFont.setUnderline(True) loadingGifTitle.setFont(funnyTitleFont) mainContentLabel.addWidget(loadingGifTitle) mainContentLabel.addWidget( QLabel(textValue[TextKey.PAGE_SYSTEMPICTUREMANAGER_SOURCELABEL])) loadingGifSource = QLineEdit() loadingGifSource.setEnabled(False) loadingGifSource.setText( CfgService.get( CfgKey.PAGE_SYSTEMPICTUREMANAGER_LOADINGGIFS_PICTURE_SOURCE)) mainContentLabel.addWidget(loadingGifSource) mainContentLabel.addWidget( QLabel(textValue[TextKey.PAGE_SYSTEMPICTUREMANAGER_TARGETLABEL])) loadingGifTarget = QLineEdit() loadingGifTarget.setEnabled(False) loadingGifTarget.setText( CfgService.get(CfgKey.PAGE_CAPTUREPHOTO_LOADING_GIF_FOLDER)) mainContentLabel.addWidget(loadingGifTarget) loadingGifNavigation = QHBoxLayout() mainContentLabel.addLayout(loadingGifNavigation) self.loadingGifDeleteButton = QPushButton( textValue[TextKey.PAGE_SYSTEMPICTUREMANAGER_DELETEBUTTON]) self.loadingGifDeleteButton.clicked.connect( self.deleteLoadingGifFolder) loadingGifNavigation.addWidget(self.loadingGifDeleteButton) self.loadingGifUpdateButton = QPushButton( textValue[TextKey.PAGE_SYSTEMPICTUREMANAGER_UPDATEBUTTON]) self.loadingGifUpdateButton.clicked.connect(self.updateLoadingGifs) loadingGifNavigation.addWidget(self.loadingGifUpdateButton) mainContentLabel.addStretch() #progressbar self.progressbar = QProgressBar() self.progressbar.setValue(0) mainLayout.addWidget(self.progressbar) #Navigationbuttons navigationBox = QHBoxLayout() mainLayout.addLayout(navigationBox) self.pictureManagerButton = QPushButton( textValue[TextKey.PAGE_SYSTEMPICTUREMANAGER_NEXTBUTTON]) self.pictureManagerButton.clicked.connect(self.nextPageEvent) self.setNavigationbuttonStyle(self.pictureManagerButton) navigationBox.addWidget(self.pictureManagerButton) #timer self.timer = QTimer() self.timer.timeout.connect(self.timerUpdate)
import sys from PyQt5.QtWidgets import QApplication from PyQt5.QtGui import QScreen from PyQt5.QtCore import QSize from mainwindow import MainWindow windowSize = QSize(1000, 800) if __name__ == "__main__": qapp = QApplication.instance() if not qapp: qapp = QApplication(sys.argv) app = MainWindow() screen: QScreen = app.screen() size: QSize = screen.size() app.setGeometry( size.width()//2 - windowSize.width()//2, size.height()//2 - windowSize.height()//2, windowSize.width(), windowSize.height(), ) app.show() app.activateWindow() app.raise_() qapp.exec_()
def pixmapForLegendNode(legend_node): # handles only symbol nodes if not isinstance(legend_node, QgsSymbolLegendNode): return # If size is default, use default implementation size = iface.layerTreeView().iconSize() if size.width() in (-1, 16): size = QSize(18, 18) symbol = legend_node.symbol() if not symbol: return # Compute minimum width model = iface.layerTreeView().layerTreeModel() if not legend_node.layerNode(): return text = legend_node.textOnSymbolLabel() minimum_width = max( max( l_node.minimumIconSize().width() + (8 if text else 0) for l_node in model.layerLegendNodes(legend_node.layerNode()) if isinstance(l_node, QgsSymbolLegendNode) ), size.width(), ) symbol_size = QSize(minimum_width, size.height()) context = QgsRenderContext.fromMapSettings(iface.mapCanvas().mapSettings()) pixmap = QgsSymbolLayerUtils.symbolPreviewPixmap(symbol, symbol_size, 0, context) if text: painter = QPainter(pixmap) text_format = legend_node.textOnSymbolTextFormat() try: text_context = createTemporaryRenderContext() if text_context: painter.setRenderHint(QPainter.Antialiasing) text_context.setPainter(painter) font_metrics = QFontMetricsF(text_format.scaledFont(context)) y_baseline_v_center = ( symbol_size.height() + font_metrics.ascent() - font_metrics.descent() ) / 2 QgsTextRenderer.drawText( QPointF(symbol_size.width() / 2, y_baseline_v_center), 0, QgsTextRenderer.AlignCenter, [text], text_context, text_format, ) text_context.setPainter(None) except Exception as e: QgsMessageLog.logMessage(str(e)) return pixmap
def check_res(res: QSize): return res.width() >= min_size and res.height() >= min_size
def sizeHint(self) -> QSize: size = QSize(100,100) for item in self._items: size.expandedTo(item.minimumSize()) return QSize(size.width() * 3, size.height() * 2)
def getBalloonImage(self, size: QSize, flip=False, soul_id=-1): if self._balloon is None: logging.warning("getBalloonImage: balloon is None") return kikka.helper.getDefaultImage() drect = [] # calculate destination rect if len(self._balloon.clipW) == 3: dw = [ self._balloon.clipW[0], size.width() - self._balloon.clipW[0] - self._balloon.clipW[2], self._balloon.clipW[2] ] elif len(self._balloon.clipW) == 5: sw = size.width() - self._balloon.clipW[0] - self._balloon.clipW[ 2] - self._balloon.clipW[4] dw = [ self._balloon.clipW[0], sw // 2, self._balloon.clipW[2], sw - sw // 2, self._balloon.clipW[4] ] else: sw = size.width() // 3 dw = [sw, size.width() - sw * 2, sw] if len(self._balloon.clipH) == 3: dh = [ self._balloon.clipH[0], size.height() - self._balloon.clipH[0] - self._balloon.clipH[2], self._balloon.clipH[2] ] elif len(self._balloon.clipH) == 5: sh = size.height() - self._balloon.clipH[0] - self._balloon.clipH[ 2] - self._balloon.clipH[4] dh = [ self._balloon.clipH[0], sh // 2, self._balloon.clipH[2], sh - sh // 2, self._balloon.clipH[4] ] else: sh = size.height() // 3 dh = [sh, size.height() - sh * 2, sh] for y in range(len(self._balloon.clipH)): dr = [] for x in range(len(self._balloon.clipW)): pt = QPoint(0, 0) if x > 0: pt.setX(dr[x - 1].x() + dw[x - 1]) if y > 0: pt.setY(drect[y - 1][0].y() + dh[y - 1]) sz = QSize(dw[x], dh[y]) dr.append(QRect(pt, sz)) drect.append(dr) pass # exit for # paint balloon image img = QImage(size, QImage.Format_ARGB32) pixmap = QPixmap().fromImage(self._balloon_image_cache, Qt.AutoColor) painter = QPainter(img) painter.setCompositionMode(QPainter.CompositionMode_Source) for y in range(len(self._balloon.clipH)): for x in range(len(self._balloon.clipW)): painter.drawPixmap(drect[y][x], pixmap, self._balloon.bgRect[y][x]) painter.end() # flip or not if self._balloon.flipBackground is True and flip is True: img = img.mirrored(True, False) if self._balloon.noFlipCenter is True and len( self._balloon.clipW) == 5 and len( self._balloon.clipH) == 5: painter = QPainter(img) painter.setCompositionMode(QPainter.CompositionMode_Source) painter.drawPixmap(drect[2][2], pixmap, self._balloon.bgRect[2][2]) painter.end() # debug draw if kikka.shell.isDebug is True: painter = QPainter(img) painter.fillRect(QRect(0, 0, 200, 64), QColor(0, 0, 0, 64)) painter.setPen(Qt.red) for y in range(len(self._balloon.clipH)): for x in range(len(self._balloon.clipW)): if x in (0, 2, 4) and y in (0, 2, 4): continue rectf = QRect(drect[y][x]) text = "(%d, %d)\n%d x %d" % ( rectf.x(), rectf.y(), rectf.width(), rectf.height()) painter.drawText(rectf, Qt.AlignCenter, text) if y > 0: painter.drawLine(drect[y][0].x(), drect[y][0].y(), drect[y][0].x() + img.width(), drect[y][0].y()) for x in range(1, len(self._balloon.clipW)): painter.drawLine(drect[0][x].x(), drect[0][x].y(), drect[0][x].x(), drect[0][x].y() + img.height()) painter.setPen(Qt.green) painter.drawRect(QRect(0, 0, img.width() - 1, img.height() - 1)) painter.drawText(3, 12, "DialogWindow") painter.drawText(3, 24, "Ghost: %d" % self.ID) painter.drawText(3, 36, "Name: %s" % self.name) painter.drawText(3, 48, "soul_id: %d" % soul_id) return img
class MainApp(QWidget): STRANGER_DANGER = 350 IMAGE_SIZE = (100, 100) stranger_color = (179, 20, 20) recognized_color = (59, 235, 62) def __init__(self, fps=30, parent=None): # type: (int, Optional[QWidget]) -> None super().__init__(parent=parent) self.pkg_path = path.dirname(path.dirname(path.abspath(__file__))) self.training_data_dir = path.join(self.pkg_path, 'train') self.models_dir = path.join(self.pkg_path, 'models') self.model_fname = 'fisherfaces.p' try: self.model = data_provider.load_model( path.join(self.models_dir, self.model_fname)) except AssertionError: self.model = None self.existing_labels = QStringListModel(self.get_existing_labels()) self.fps = fps self.video_size = QSize(640, 480) self.gray_image = None self.detected_faces = [] # Setup the UI self.main_layout = QHBoxLayout() self.setLayout(self.main_layout) self.control_layout = QVBoxLayout() self.control_layout.setSpacing(8) self.main_layout.addItem(self.control_layout) # Setup the existing label view self.labels_view = QListView(parent=self) self.labels_view.setModel(self.existing_labels) self.labels_view.setSelectionMode(QListView.SingleSelection) self.labels_view.setItemDelegate(CapitalizeDelegate(self)) self.control_layout.addWidget(self.labels_view) self.new_label_txt = QLineEdit(self) self.new_label_txt.returnPressed.connect(self.add_new_label) self.new_label_txt.returnPressed.connect(self.new_label_txt.clear) self.control_layout.addWidget(self.new_label_txt) self.add_button = QPushButton('Add Label', self) self.add_button.clicked.connect(self.add_new_label) self.control_layout.addWidget(self.add_button) # Setup the training area train_box = QGroupBox('Train', self) train_box_layout = QVBoxLayout() train_box.setLayout(train_box_layout) self.control_layout.addWidget(train_box) self.train_btn = QPushButton('Train', self) self.train_btn.clicked.connect(self.train) train_box_layout.addWidget(self.train_btn) self.control_layout.addStretch(0) # Add take picture shortcut self.take_picture_btn = QPushButton('Take picture', self) self.take_picture_btn.clicked.connect(self.take_picture) self.control_layout.addWidget(self.take_picture_btn) shortcut = QShortcut(QKeySequence('Space'), self, self.take_picture) shortcut.setWhatsThis('Take picture and add to training data.') # Add quit shortcut shortcut = QShortcut(QKeySequence('Esc'), self, self.close) shortcut.setWhatsThis('Quit') # Setup the main camera area self.image_label = QLabel(self) self.image_label.setFixedSize(self.video_size) self.main_layout.addWidget(self.image_label) # Setup the camera self.capture = cv2.VideoCapture(0) self.capture.set(cv2.CAP_PROP_FRAME_WIDTH, self.video_size.width()) self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, self.video_size.height()) self.timer = QTimer() self.timer.timeout.connect(self.display_video_stream) self.timer.start(int(1000 / self.fps)) def classify_face(self, image): if self.model is None: return label_idx, distances = self.model.predict(image.ravel(), True) label_idx, distance = label_idx[0], distances[0][label_idx] labels = self.existing_labels.stringList() return labels[label_idx], distance def get_training_data(self): """Read the images from disk into an n*(w*h) matrix.""" return data_provider.get_image_data_from_directory( self.training_data_dir) def train(self): X, y, mapping = self.get_training_data() # Inspect scree plot to determine appropriate number of PCA components classifier = PCALDAClassifier( n_components=2, pca_components=200, metric='euclidean', ).fit(X, y) # Replace the existing running model self.model = classifier # Save the classifier to file data_provider.save_model( classifier, path.join(self.models_dir, self.model_fname)) def add_new_label(self): new_label = self.new_label_txt.text() new_label = new_label.lower() # Prevent empty entries if len(new_label) < 3: return string_list = self.existing_labels.stringList() if new_label not in string_list: string_list.append(new_label) string_list.sort() self.existing_labels.setStringList(string_list) # Automatically select the added label selection_model = self.labels_view.selectionModel() index = self.existing_labels.index(string_list.index(new_label)) selection_model.setCurrentIndex(index, QItemSelectionModel.Select) def display_video_stream(self): """Read frame from camera and repaint QLabel widget.""" _, frame = self.capture.read() frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) frame = cv2.flip(frame, 1) # Use the Viola-Jones face detector to detect faces to classify face_cascade = cv2.CascadeClassifier(path.join( self.pkg_path, 'resources', 'haarcascade_frontalface_default.xml')) self.gray_image = gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) self.detected_faces = face_cascade.detectMultiScale(gray, 1.3, 5) for x, y, w, h in self.detected_faces: # Label the detected face as per the model face = gray[y:y + h, x:x + w] face = cv2.resize(face, self.IMAGE_SIZE) result = self.classify_face(face) # If a model is loaded, we can predict if result: predicted, distance = self.classify_face(face) if distance > self.STRANGER_DANGER: predicted = 'Stranger danger!' color = self.stranger_color else: predicted = predicted.capitalize() color = self.recognized_color # Draw a rectangle around the detected face cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2) text = '%s (%.1f)' % (predicted, distance) cv2.putText(frame, text, (x, y + h + 15), cv2.FONT_HERSHEY_TRIPLEX, 0.5, color) else: # Draw a rectangle around the detected face cv2.rectangle(frame, (x, y), (x + w, y + h), self.stranger_color, 2) cv2.putText(frame, 'Stranger danger!', (x, y + h + 15), cv2.FONT_HERSHEY_TRIPLEX, 0.5, self.stranger_color) # Display the image in the image area image = QImage(frame, frame.shape[1], frame.shape[0], frame.strides[0], QImage.Format_RGB888) self.image_label.setPixmap(QPixmap.fromImage(image)) @contextmanager def stop_camera_feed(self): """Temporarly stop the feed and face detection.""" try: self.timer.stop() yield finally: self.timer.start(int(1000 / self.fps)) def take_picture(self): # Notify the user there were no faces detected if self.detected_faces is None or len(self.detected_faces) < 1: return raise NoFacesError() if len(self.detected_faces) > 1: return raise MultipleFacesError() with self.stop_camera_feed(): x, y, w, h = self.detected_faces[0] face = self.gray_image[y:y + h, x:x + w] face = cv2.resize(face, self.IMAGE_SIZE) denoised_image = cv2.fastNlMeansDenoising(face) if not self.selected_label: return self.save_image(denoised_image, self.selected_label) @property def selected_label(self): index = self.labels_view.selectedIndexes() if len(index) < 1: return None label = self.existing_labels.data(index[0], Qt.DisplayRole) return label def get_existing_labels(self): """Get a list of the currently existing labels""" return data_provider.get_folder_names(self.training_data_dir) def save_image(self, image: np.ndarray, label: str) -> None: """Save an image to disk in the appropriate directory.""" if not path.exists(self.training_data_dir): mkdir(self.training_data_dir) label_path = path.join(self.training_data_dir, label) if not path.exists(label_path): mkdir(label_path) existing_files = listdir(label_path) existing_files = map(lambda p: path.splitext(p)[0], existing_files) existing_files = list(map(int, existing_files)) last_fname = sorted(existing_files)[-1] if len(existing_files) else 0 fname = path.join(label_path, '%03d.png' % (last_fname + 1)) cv2.imwrite(fname, image)
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 ]: deltaX = abs(start.x() - end.x()) deltaY = abs(start.y() - end.y()) r = max(deltaX, deltaY) 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
def __init__(self, pages: AllPages, windowsize: QSize, globalPagesVariable: GlobalPagesVariableService): super().__init__(pages, windowsize) self.globalPagesVariable = globalPagesVariable self.greenscreenColorRangeService = GreenscreenColorRangeService() self.camera = CameraService.initGreenscreenCalibrationCam( QSize(windowsize.width() / 2, windowsize.height() / 2)) mainLayout = QVBoxLayout() mainLayout.setContentsMargins(0, 0, 0, 0) self.setLayout(mainLayout) #Titel mainLayout.addWidget( self.getTitleAsQLabel(TextKey.PAGE_GREENSCREEN_COLOR_PICKER_TITLE)) #Monitoring: colorLayout = QHBoxLayout() mainLayout.addLayout(colorLayout) self.averageColorLabel = QLineEdit() self.averageColorLabel.setReadOnly(True) colorLayout.addWidget(self.averageColorLabel) self.minColorLabel = QLineEdit() self.minColorLabel.setReadOnly(True) colorLayout.addWidget(self.minColorLabel) self.maxColorLabel = QLineEdit() self.maxColorLabel.setReadOnly(True) colorLayout.addWidget(self.maxColorLabel) #Hinweis: self.hintLabel = QLabel() mainLayout.addWidget(self.hintLabel) #Picture mainLayout.addStretch() self.picture = QLabel() self.picture.setAlignment(Qt.AlignCenter) mainLayout.addWidget(self.picture) #Buttons ################################################################################################## mainLayout.addStretch() navigationTopLayout = QHBoxLayout() mainLayout.addLayout(navigationTopLayout) toleranceButton = QPushButton( textValue[TextKey.PAGE_GREENSCREEN_COLOR_PICKER_TOLERANCE_BUTTON]) toleranceButton.clicked.connect(self._toleranceButtonEvent) self.setNavigationbuttonStyle(toleranceButton) navigationTopLayout.addWidget(toleranceButton) self.capturePhotoButton = QPushButton(textValue[ TextKey.PAGE_GREENSCREEN_COLOR_PICKER_CAPTURE_PHOTO_BUTTON]) self.capturePhotoButton.clicked.connect(self._capturePhotoEvent) self.setNavigationbuttonStyle(self.capturePhotoButton) navigationTopLayout.addWidget(self.capturePhotoButton) #Bottom ------------------------------------------------ navigationBottomLayout = QHBoxLayout() mainLayout.addLayout(navigationBottomLayout) backButton = QPushButton(textValue[TextKey.PAGE_CONFIG_BACKBUTTON]) backButton.clicked.connect(self._backPageSelectEvent) self.setNavigationbuttonStyle(backButton) navigationBottomLayout.addWidget(backButton) self.saveButton = QPushButton( textValue[TextKey.PAGE_GREENSCREEN_COLOR_PICKER_SAVE_BUTTON]) self.saveButton.clicked.connect(self._saveEvent) self.setNavigationbuttonStyle(self.saveButton) navigationBottomLayout.addWidget(self.saveButton)
class MainWindow(QMainWindow, Ui_MainWindow): resized = QtCore.pyqtSignal() def __init__(self): super().__init__() self.setupUi(self) self.pushButton_bubble.clicked.connect(self.on_click) self.pushButton_dbsetting.clicked.connect(self.on_click) self.pushButton_shortcuts.clicked.connect(self.on_click) self.pushButton_calibrate.clicked.connect(self.on_click) self.pushButton_start.clicked.connect(self.on_click) self.pushButton_ok.clicked.connect(self.on_click) self.pushButton_cancel.clicked.connect(self.on_click) self.pushButton_apply.clicked.connect(self.on_click) self.edit_ratioValue.returnPressed.connect(self.on_enter) self.edit_id.returnPressed.connect(self.on_enter) self.check_id.stateChanged.connect(self.on_check) self.frame_board.installEventFilter(self) self.image = QImage() self.image_size = QSize() self.stack.setCurrentWidget(self.page_main) self.url = "" self.customConnected = False def on_calibration_click(self, name): if eq(name, "close"): self.calibration_window.destroy() if eq(name, "cancel"): self.calibration_window.destroy() if eq(name, "calibrate"): if eq(self.calibration_window.url, ""): return self.url = self.calibration_window.url self.setImageSize(self.calibration_window.image_size) self.calibration_window.destroy() self.setBoardBackground() def on_check(self): if self.check_id.isChecked(): if self.isExistingID(): self.warning("Duplicated ID!") self.check_id.setChecked(False) def on_enter(self): sending_edit = self.sender() if eq(sending_edit.objectName(), "edit_ratioValue"): if self.isFloat(self.edit_ratioValue.displayText()) is not True: return if eq(self.url, "") is True: self.edit_ratioValue.setText("") return width = (float(self.edit_ratioValue.displayText()) * self.image.size().width()) / 100 if width <= 0: self.setImageSize(self.image_size) return self.image_size.setWidth(width) self.image_size.setHeight(self.getScaledHeight(width)) self.setImageSize(self.image_size) if eq(sending_edit.objectName(), "edit_id"): if self.isExistingID(): self.warning("Duplicated ID!") elif self.isValidID() is not True: self.edit_id.setText("") else: self.check_id.setChecked(True) def warning(self, warning): msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText(warning) msg.setWindowTitle("Error") msg.show() msg.exec_() def isValidID(self): if eq(self.edit_id.displayText(), "") is True: return False if len(self.edit_id.displayText()) > 255: return False return True def isExistingID(self): if self.customConnected is True: dbconn = MYSQL(dbhost=self.table.item(0, 0).text(), dbuser=self.table.item(1, 0).text(), dbpwd=self.table.item(2, 0).text(), dbname=self.table.item(3, 0).text(), dbcharset=self.table.item(4, 0).text()) else: dbconn = MYSQL(dbhost=dbconstant.HOST, dbuser=dbconstant.USER, dbpwd=dbconstant.PASSWORD, dbname=dbconstant.DB_NAME, dbcharset=dbconstant.CHARSET) condition = {'id': self.edit_id.displayText()} count = dbconn.count(table=dbconstant.TABLE, condition=condition) dbconn.close() return True if count > 0 else False def isFloat(self, str): try: float(str) return True except: return False @pyqtSlot() def on_click(self): sending_button = self.sender() if eq(sending_button.objectName(), "pushButton_bubble"): self.toggleName(sending_button) if eq(sending_button.objectName(), "pushButton_dbsetting"): self.stack.setCurrentWidget(self.page_database) if eq(sending_button.objectName(), "pushButton_shortcuts"): self.stack.setCurrentWidget(self.page_shortcuts) if eq(sending_button.objectName(), "pushButton_calibrate"): if self.image_size.width() > pyautogui.size( ).width or self.image_size.height() > pyautogui.size().height: self.warning("Image size is too big") return self.calibration_window = Calibration(self, self.url, self.image_size) self.calibration_window.show() if eq(sending_button.objectName(), "pushButton_start"): isPlotting = True if eq(self.pushButton_calibrate.text(), "ok") else False # 2. db 체크 if self.check_id.isChecked(): if eq(self.edit_id.text(), ""): self.warning("there is no id") return if self.isExistingID(): self.warning("Database ID Duplicated!") return id = self.edit_id.text() else: id = "" # 3. 이미지 체크 if eq(self.url, ""): self.warning("There is no image!") return if self.image_size.width() > pyautogui.size( ).width or self.image_size.height() > pyautogui.size().height: self.warning("Image size is too big") return self.tracking_window = Tracker(self.url, self.image_size, isPlotting, id, self.table, self.customConnected) self.tracking_window.showFullScreen() self.tracking_window.setFixedSize(self.tracking_window.size()) if eq(sending_button.objectName(), "pushButton_ok"): self.stack.setCurrentWidget(self.page_main) if eq(sending_button.objectName(), "pushButton_cancel"): filled = self.checkFilled() if filled is False: self.table.clearContents() self.stack.setCurrentWidget(self.page_main) return if self.checkConnect() is False: self.table.clearContents() self.stack.setCurrentWidget(self.page_main) if eq(sending_button.objectName(), "pushButton_apply"): if self.checkFilled() is not True: self.warning("Not Filled!") return if self.checkConnect() is True: self.stack.setCurrentWidget(self.page_main) else: self.warning("Not Valid Information!") def checkFilled(self): for row in range(5): if self.table.item(row, 0) is None: self.customConnected = False return False return True def checkConnect(self): dbconn = MYSQL(dbhost=self.table.item(0, 0).text(), dbuser=self.table.item(1, 0).text(), dbpwd=self.table.item(2, 0).text(), dbname=self.table.item(3, 0).text(), dbcharset=self.table.item(4, 0).text()) if dbconn.session() is None: dbconn.close() self.customConnected = False return False else: dbconn.close() self.customConnected = True return True def eventFilter(self, object, event): if object is self.frame_board: if event.type() == QtCore.QEvent.Drop: if event.mimeData().hasUrls(): event.accept() self.url = event.mimeData().urls()[0].toLocalFile() self.setBoardBackground() self.setImageSize(self.image.size()) else: event.ignore() return False def setBoardBackground(self): self.image = QImage(self.url) pixmap = QPixmap(self.url) pixmap = pixmap.scaled(self.frame_board.width(), self.getScaledHeight(self.frame_board.width())) self.frame_board.setPixmap(pixmap) def getScaledHeight(self, width): height = math.floor( (width * self.image.size().height()) / self.image.size().width()) return height def setImageSize(self, size): self.image_size = size self.ratio = (100 * self.image_size.width()) / self.image.size().width() self.label_widthValue.setText("%d" % self.image_size.width()) self.label_heightValue.setText("%d" % self.image_size.height()) self.edit_ratioValue.setText("%0.2f" % self.ratio)
class ThumbnailExtractor(LoadBalancerWorker): # Exif rotation constants rotate_0 = "1" rotate_90 = "6" rotate_180 = "3" rotate_270 = "8" maxStandardSize = QSize( max(ThumbnailSize.width, ThumbnailSize.height), max(ThumbnailSize.width, ThumbnailSize.height), ) def __init__(self) -> None: self.thumbnailSizeNeeded = QSize(ThumbnailSize.width, ThumbnailSize.height) self.thumbnail_cache = ThumbnailCacheSql(create_table_if_not_exists=False) self.fdo_cache_large = FdoCacheLarge() self.fdo_cache_normal = FdoCacheNormal() super().__init__("Thumbnail Extractor") def rotate_thumb(self, thumbnail: QImage, orientation: str) -> QImage: """ If required return a rotated copy the thumbnail :param thumbnail: thumbnail to rotate :param orientation: EXIF orientation tag :return: possibly rotated thumbnail """ if orientation == self.rotate_90: thumbnail = thumbnail.transformed(QTransform().rotate(90)) elif orientation == self.rotate_270: thumbnail = thumbnail.transformed(QTransform().rotate(270)) elif orientation == self.rotate_180: thumbnail = thumbnail.transformed(QTransform().rotate(180)) return thumbnail def image_large_enough(self, size: QSize) -> bool: """Check if image is equal or bigger than thumbnail size.""" return ( size.width() >= self.thumbnailSizeNeeded.width() or size.height() >= self.thumbnailSizeNeeded.height() ) def _extract_256_thumb( self, rpd_file: RPDFile, processing: Set[ExtractionProcessing], orientation: Optional[str], ) -> PhotoDetails: thumbnail = None data = rpd_file.metadata.get_preview_256() if isinstance(data, bytes): thumbnail = QImage.fromData(data) if thumbnail.isNull(): thumbnail = None else: if thumbnail.width() > 160 or thumbnail.height() > 120: processing.add(ExtractionProcessing.resize) return PhotoDetails(thumbnail, orientation) def _extract_metadata( self, rpd_file: RPDFile, processing: Set[ExtractionProcessing] ) -> PhotoDetails: thumbnail = orientation = None try: orientation = rpd_file.metadata.orientation() except Exception: pass rpd_file.mdatatime = rpd_file.metadata.timestamp(missing=0.0) # Not all files have an exif preview, but some do # (typically CR2, ARW, PEF, RW2). # If they exist, they are (almost!) always 160x120 # TODO how about thumbnail_cache_status? if self.write_fdo_thumbnail and rpd_file.fdo_thumbnail_256 is None: photo_details = self._extract_256_thumb( rpd_file=rpd_file, processing=processing, orientation=orientation ) if photo_details.thumbnail is not None: return photo_details # if no valid preview found, fall back to the code below and make do with # the best we can get preview = rpd_file.metadata.get_small_thumbnail_or_first_indexed_preview() if preview: thumbnail = QImage.fromData(preview) if thumbnail.isNull(): thumbnail = None else: if thumbnail.width() < thumbnail.height() and orientation in ( self.rotate_270, self.rotate_90, ): # The orientation has already been applied to the thumbnail logging.debug( "Already rotated: %s", rpd_file.get_current_full_file_name() ) orientation = self.rotate_0 if max(thumbnail.width(), thumbnail.height()) > 160: logging.debug("Resizing: %s", rpd_file.get_current_full_file_name()) processing.add(ExtractionProcessing.resize) elif not rpd_file.is_jpeg(): processing.add(ExtractionProcessing.strip_bars_photo) return PhotoDetails(thumbnail, orientation) def get_disk_photo_thumb( self, rpd_file: Photo, full_file_name: str, processing: Set[ExtractionProcessing], force_exiftool: bool, ) -> PhotoDetails: """ Get the photo's thumbnail from a file that is on disk. Sets rpd_file's mdatatime. :param rpd_file: file details :param full_file_name: full name of the file from which to get the metadata :param processing: processing extraction tasks to complete, :param force_exiftool: whether to force the use of ExifTool to load the metadata :return: thumbnail and its orientation """ orientation = None thumbnail = None photo_details = PhotoDetails(thumbnail, orientation) if rpd_file.load_metadata( full_file_name=full_file_name, et_process=self.exiftool_process, force_exiftool=force_exiftool, ): photo_details = self._extract_metadata(rpd_file, processing) thumbnail = photo_details.thumbnail if thumbnail is not None: return photo_details if rpd_file.is_loadable(): thumbnail = QImage(full_file_name) processing.add(ExtractionProcessing.resize) if not rpd_file.from_camera: processing.remove(ExtractionProcessing.orient) if thumbnail.isNull(): thumbnail = None logging.warning( "Unable to create a thumbnail out of the file: {}".format( full_file_name ) ) return PhotoDetails(thumbnail, orientation) def get_from_buffer( self, rpd_file: Photo, raw_bytes: bytearray, processing: Set[ExtractionProcessing], ) -> PhotoDetails: if not rpd_file.load_metadata( raw_bytes=raw_bytes, et_process=self.exiftool_process ): return PhotoDetails(None, None) else: return self._extract_metadata(rpd_file, processing) def get_photo_orientation( self, rpd_file: Photo, force_exiftool: bool, full_file_name: Optional[str] = None, raw_bytes: Optional[bytearray] = None, ) -> Optional[str]: if rpd_file.metadata is None: self.load_photo_metadata( rpd_file=rpd_file, full_file_name=full_file_name, raw_bytes=raw_bytes, force_exiftool=force_exiftool, ) if rpd_file.metadata is not None: try: return rpd_file.metadata.orientation() except Exception: pass return None def assign_mdatatime( self, rpd_file: Union[Photo, Video], force_exiftool: bool, full_file_name: Optional[str] = None, raw_bytes: Optional[bytearray] = None, ) -> None: """ Load the file's metadata and assign the metadata time to the rpd file """ if rpd_file.file_type == FileType.photo: self.assign_photo_mdatatime( rpd_file=rpd_file, full_file_name=full_file_name, raw_bytes=raw_bytes, force_exiftool=force_exiftool, ) else: self.assign_video_mdatatime( rpd_file=rpd_file, full_file_name=full_file_name ) def assign_photo_mdatatime( self, rpd_file: Photo, force_exiftool: bool, full_file_name: Optional[str] = None, raw_bytes: Optional[bytearray] = None, ) -> None: """ Load the photo's metadata and assign the metadata time to the rpd file """ self.load_photo_metadata( rpd_file=rpd_file, full_file_name=full_file_name, raw_bytes=raw_bytes, force_exiftool=force_exiftool, ) if rpd_file.metadata is not None and rpd_file.date_time() is None: rpd_file.mdatatime = 0.0 def load_photo_metadata( self, rpd_file: Photo, force_exiftool: bool, full_file_name: Optional[str] = None, raw_bytes: Optional[bytearray] = None, ) -> None: """ Load the photo's metadata into the rpd file """ if raw_bytes is not None: if rpd_file.is_jpeg_type(): rpd_file.load_metadata( app1_segment=raw_bytes, et_process=self.exiftool_process ) else: rpd_file.load_metadata( raw_bytes=raw_bytes, et_process=self.exiftool_process ) else: rpd_file.load_metadata( full_file_name=full_file_name, et_process=self.exiftool_process, force_exiftool=force_exiftool, ) def assign_video_mdatatime(self, rpd_file: Video, full_file_name: str) -> None: """ Load the video's metadata and assign the metadata time to the rpd file """ if rpd_file.metadata is None: rpd_file.load_metadata( full_file_name=full_file_name, et_process=self.exiftool_process ) if rpd_file.date_time() is None: rpd_file.mdatatime = 0.0 def get_video_rotation(self, rpd_file: Video, full_file_name: str) -> Optional[str]: """ Some videos have a rotation tag. If this video does, return it. """ if rpd_file.metadata is None: rpd_file.load_metadata( full_file_name=full_file_name, et_process=self.exiftool_process ) orientation = rpd_file.metadata.rotation(missing=None) if orientation == 180: return self.rotate_180 elif orientation == 90: return self.rotate_90 elif orientation == 270: return self.rotate_270 return None def check_for_stop(self, directive: bytes, content: bytes): if directive == b"cmd": assert content == b"STOP" return True return False def extract_thumbnail( self, task: ExtractionTask, rpd_file: Union[Photo, Video], processing: Set[ExtractionProcessing], data: ThumbnailExtractorArgument, ) -> Tuple[Optional[QImage], Optional[str]]: """ Extract the thumbnail using one of a variety of methods, depending on the file :param task: extraction task to perform :param rpd_file: rpd_file to work on :param processing: processing tasks :param data: some other processing arguments passed to this process :return: thumbnail and its orientation, if found """ orientation = None if task == ExtractionTask.load_from_exif: thumbnail_details = self.get_disk_photo_thumb( rpd_file, data.full_file_name_to_work_on, processing, data.force_exiftool, ) thumbnail = thumbnail_details.thumbnail if thumbnail is not None: orientation = thumbnail_details.orientation elif task in ( ExtractionTask.load_file_directly, ExtractionTask.load_file_and_exif_directly, ExtractionTask.load_file_directly_metadata_from_secondary, ): thumbnail = QImage(data.full_file_name_to_work_on) if task == ExtractionTask.load_file_and_exif_directly: self.assign_photo_mdatatime( rpd_file=rpd_file, full_file_name=data.full_file_name_to_work_on, force_exiftool=data.force_exiftool, ) elif task == ExtractionTask.load_file_directly_metadata_from_secondary: self.assign_mdatatime( rpd_file=rpd_file, full_file_name=data.secondary_full_file_name, force_exiftool=data.force_exiftool, ) if ExtractionProcessing.orient in processing: orientation = self.get_photo_orientation( rpd_file=rpd_file, full_file_name=data.full_file_name_to_work_on, force_exiftool=data.force_exiftool, ) elif task in ( ExtractionTask.load_from_bytes, ExtractionTask.load_from_bytes_metadata_from_temp_extract, ): try: assert data.thumbnail_bytes is not None except AssertionError: logging.error( "Thumbnail bytes not extracted for %s (value is None)", rpd_file.get_current_full_file_name(), ) thumbnail = QImage.fromData(data.thumbnail_bytes) if ( thumbnail.width() > self.thumbnailSizeNeeded.width() or thumbnail.height() > self.thumbnailSizeNeeded.height() ): processing.add(ExtractionProcessing.resize) processing.remove(ExtractionProcessing.strip_bars_photo) if data.exif_buffer and ExtractionProcessing.orient in processing: orientation = self.get_photo_orientation( rpd_file=rpd_file, raw_bytes=data.exif_buffer, force_exiftool=data.force_exiftool, ) if task == ExtractionTask.load_from_bytes_metadata_from_temp_extract: self.assign_mdatatime( rpd_file=rpd_file, full_file_name=data.secondary_full_file_name, force_exiftool=data.force_exiftool, ) orientation = rpd_file.metadata.orientation() os.remove(data.secondary_full_file_name) rpd_file.temp_cache_full_file_chunk = "" elif task == ExtractionTask.load_from_exif_buffer: thumbnail_details = self.get_from_buffer( rpd_file, data.exif_buffer, processing ) thumbnail = thumbnail_details.thumbnail if thumbnail is not None: orientation = thumbnail_details.orientation elif task in ( ExtractionTask.load_heif_directly, ExtractionTask.load_heif_and_exif_directly, ): assert have_heif_module thumbnail = load_heif( data.full_file_name_to_work_on, process_name=self.identity.decode() ) if task == ExtractionTask.load_heif_and_exif_directly: self.assign_photo_mdatatime( rpd_file=rpd_file, full_file_name=data.full_file_name_to_work_on, force_exiftool=data.force_exiftool, ) if ExtractionProcessing.orient in processing: orientation = self.get_photo_orientation( rpd_file=rpd_file, full_file_name=data.full_file_name_to_work_on, force_exiftool=data.force_exiftool, ) else: assert task in ( ExtractionTask.extract_from_file, ExtractionTask.extract_from_file_and_load_metadata, ) if rpd_file.file_type == FileType.photo: self.assign_photo_mdatatime( rpd_file=rpd_file, full_file_name=data.full_file_name_to_work_on, force_exiftool=data.force_exiftool, ) thumbnail_bytes = ( rpd_file.metadata.get_small_thumbnail_or_first_indexed_preview() ) if thumbnail_bytes: thumbnail = QImage.fromData(thumbnail_bytes) orientation = rpd_file.metadata.orientation() else: assert rpd_file.file_type == FileType.video if ExtractionTask.extract_from_file_and_load_metadata: self.assign_video_mdatatime( rpd_file=rpd_file, full_file_name=data.full_file_name_to_work_on ) if not have_gst: thumbnail = None else: png = get_video_frame(data.full_file_name_to_work_on, 1.0) if not png: thumbnail = None logging.warning( "Could not extract video thumbnail from %s", data.rpd_file.get_display_full_name(), ) else: thumbnail = QImage.fromData(png) if thumbnail.isNull(): thumbnail = None else: processing.add(ExtractionProcessing.add_film_strip) orientation = self.get_video_rotation( rpd_file, data.full_file_name_to_work_on ) if orientation is not None: processing.add(ExtractionProcessing.orient) processing.add(ExtractionProcessing.resize) return thumbnail, orientation def process_files(self): """ Loop continuously processing photo and video thumbnails """ logging.debug("{} worker started".format(self.requester.identity.decode())) while True: directive, content = self.requester.recv_multipart() if self.check_for_stop(directive, content): break data = pickle.loads(content) # type: ThumbnailExtractorArgument thumbnail_256 = png_data = None task = data.task processing = data.processing rpd_file = data.rpd_file logging.debug( "Working on task %s for %s", task.name, rpd_file.download_name or rpd_file.name, ) self.write_fdo_thumbnail = data.write_fdo_thumbnail try: if rpd_file.fdo_thumbnail_256 is not None and data.write_fdo_thumbnail: if rpd_file.thumbnail_status != ThumbnailCacheStatus.fdo_256_ready: logging.error( "Unexpected thumbnail cache status for %s: %s", rpd_file.full_file_name, rpd_file.thumbnail_status.name, ) thumbnail = thumbnail_256 = QImage.fromData( rpd_file.fdo_thumbnail_256 ) orientation_unknown = False else: thumbnail, orientation = self.extract_thumbnail( task, rpd_file, processing, data ) if data.file_to_work_on_is_temporary: os.remove(data.full_file_name_to_work_on) rpd_file.temp_cache_full_file_chunk = "" if thumbnail is not None: if ExtractionProcessing.strip_bars_photo in processing: thumbnail = crop_160x120_thumbnail(thumbnail) elif ExtractionProcessing.strip_bars_video in processing: thumbnail = crop_160x120_thumbnail(thumbnail, 15) if ExtractionProcessing.resize in processing: # Resize the thumbnail before rotating if ( orientation == "1" or orientation is None ) and thumbnail.height() > thumbnail.width(): # Special case: pictures from some cellphones have already # been rotated thumbnail = thumbnail.scaled( self.maxStandardSize, Qt.KeepAspectRatio, Qt.SmoothTransformation, ) else: if ( rpd_file.should_write_fdo() and image_large_enough_fdo(thumbnail.size()) and max(thumbnail.height(), thumbnail.width()) > 256 ): thumbnail_256 = thumbnail.scaled( QSize(256, 256), Qt.KeepAspectRatio, Qt.SmoothTransformation, ) thumbnail = thumbnail_256 if data.send_thumb_to_main: thumbnail = thumbnail.scaled( self.thumbnailSizeNeeded, Qt.KeepAspectRatio, Qt.SmoothTransformation, ) else: thumbnail = None if not thumbnail is None and thumbnail.isNull(): thumbnail = None if orientation is not None: if thumbnail is not None: thumbnail = self.rotate_thumb(thumbnail, orientation) if thumbnail_256 is not None: thumbnail_256 = self.rotate_thumb( thumbnail_256, orientation ) if ExtractionProcessing.add_film_strip in processing: if thumbnail is not None: thumbnail = add_filmstrip(thumbnail) if thumbnail_256 is not None: thumbnail = add_filmstrip(thumbnail_256) if thumbnail is not None: buffer = qimage_to_png_buffer(thumbnail) png_data = buffer.data() orientation_unknown = ( ExtractionProcessing.orient in processing and orientation is None ) if ( data.send_thumb_to_main and data.use_thumbnail_cache and rpd_file.thumbnail_cache_status == ThumbnailCacheDiskStatus.not_found ): self.thumbnail_cache.save_thumbnail( full_file_name=rpd_file.full_file_name, size=rpd_file.size, mtime=rpd_file.modification_time, mdatatime=rpd_file.mdatatime, generation_failed=thumbnail is None, orientation_unknown=orientation_unknown, thumbnail=thumbnail, camera_model=rpd_file.camera_model, ) if ( thumbnail is not None or thumbnail_256 is not None ) and rpd_file.should_write_fdo(): if self.write_fdo_thumbnail: # The modification time of the file may have changed when the # file was saved Ideally it shouldn't, but it does sometimes, # e.g. on NTFS! So need to get the modification time from the # saved file. mtime = os.path.getmtime(rpd_file.download_full_file_name) if thumbnail_256 is not None: rpd_file.fdo_thumbnail_256_name = ( self.fdo_cache_large.save_thumbnail( full_file_name=rpd_file.download_full_file_name, size=rpd_file.size, modification_time=mtime, generation_failed=False, thumbnail=thumbnail_256, free_desktop_org=False, ) ) thumbnail_128 = thumbnail_256.scaled( QSize(128, 128), Qt.KeepAspectRatio, Qt.SmoothTransformation, ) else: thumbnail_128 = thumbnail.scaled( QSize(128, 128), Qt.KeepAspectRatio, Qt.SmoothTransformation, ) rpd_file.fdo_thumbnail_128_name = ( self.fdo_cache_normal.save_thumbnail( full_file_name=rpd_file.download_full_file_name, size=rpd_file.size, modification_time=mtime, generation_failed=False, thumbnail=thumbnail_128, free_desktop_org=False, ) ) elif ( thumbnail_256 is not None and rpd_file.fdo_thumbnail_256 is None ): rpd_file.fdo_thumbnail_256 = qimage_to_png_buffer( thumbnail ).data() if thumbnail is not None: if orientation_unknown: rpd_file.thumbnail_status = ( ThumbnailCacheStatus.orientation_unknown ) elif rpd_file.fdo_thumbnail_256 is not None: rpd_file.thumbnail_status = ThumbnailCacheStatus.fdo_256_ready else: rpd_file.thumbnail_status = ThumbnailCacheStatus.ready except SystemExit as e: self.exiftool_process.terminate() sys.exit(e) except: logging.error("Exception working on file %s", rpd_file.full_file_name) logging.error("Task: %s", task) logging.error("Processing tasks: %s", processing) logging.exception("Traceback:") # Purge metadata, as it cannot be pickled if not data.send_thumb_to_main: png_data = None rpd_file.metadata = None self.sender.send_multipart( [ b"0", b"data", pickle.dumps( GenerateThumbnailsResults( rpd_file=rpd_file, thumbnail_bytes=png_data ), pickle.HIGHEST_PROTOCOL, ), ] ) self.requester.send_multipart([b"", b"", b"OK"]) def do_work(self): if False: # exiv2 pumps out a LOT to stderr - use cautiously! context = show_errors() self.error_stream = sys.stderr else: # Redirect stderr, hiding error output from exiv2 context = stdchannel_redirected(sys.stderr, os.devnull) self.error_stream = sys.stdout with context: # In some situations, using a context manager for exiftool can # result in exiftool processes not being terminated. So let's # handle starting and terminating it manually. self.exiftool_process = exiftool.ExifTool() self.exiftool_process.start() self.process_files() self.exit() def cleanup_pre_stop(self) -> None: logging.debug( "Terminating thumbnail extractor ExifTool process for %s", self.identity.decode(), ) self.exiftool_process.terminate()
def __init__(self, app: dict, icon_cache: MemoryCache, i18n: I18n, screen_size: QSize): super(InfoDialog, self).__init__(flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint) self.setWindowTitle(str(app['__app__'])) self.screen_size = screen_size self.i18n = i18n layout = QVBoxLayout() self.setLayout(layout) scroll = QScrollArea(self) scroll.setFrameShape(QFrame.NoFrame) scroll.setWidgetResizable(True) comps_container = QWidget() comps_container.setLayout(QVBoxLayout()) scroll.setWidget(comps_container) # shows complete field string self.text_field = QPlainTextEdit() self.text_field.setReadOnly(True) comps_container.layout().addWidget(self.text_field) self.text_field.hide() self.gbox_info = QGroupBox() self.gbox_info.setLayout(QGridLayout()) comps_container.layout().addWidget(self.gbox_info) # THERE ARE CRASHES WITH SOME RARE ICONS ( like insomnia ). IT CAN BE A QT BUG. IN THE MEANTIME, ONLY THE TYPE ICON WILL BE RENDERED # # icon_data = icon_cache.get(app['__app__'].model.icon_url) # # if icon_data and icon_data.get('icon'): # self.setWindowIcon(icon_data.get('icon')) self.setWindowIcon(QIcon(app['__app__'].model.get_type_icon_path())) for idx, attr in enumerate(sorted(app.keys())): if attr not in IGNORED_ATTRS and app[attr]: i18n_key = app[ '__app__'].model.gem_name + '.info.' + attr.lower() if isinstance(app[attr], list): val = ' '.join([str(e).strip() for e in app[attr] if e]) show_val = '\n'.join( ['* ' + str(e).strip() for e in app[attr] if e]) else: val = str(app[attr]).strip() show_val = val i18n_val = i18n.get('{}.{}'.format(i18n_key, val.lower())) if i18n_val: val = i18n_val show_val = val text = QLineEdit() text.setToolTip(show_val) text.setText(val) text.setCursorPosition(0) text.setStyleSheet("width: 400px") text.setReadOnly(True) label = QLabel( i18n.get(i18n_key, i18n.get(attr.lower(), attr)).capitalize()) label.setStyleSheet("font-weight: bold") self.gbox_info.layout().addWidget(label, idx, 0) self.gbox_info.layout().addWidget(text, idx, 1) self._gen_show_button(idx, show_val) layout.addWidget(scroll) lower_bar = QToolBar() bt_back = QPushButton(self.i18n['back'].capitalize()) bt_back.setVisible(False) bt_back.setCursor(QCursor(Qt.PointingHandCursor)) bt_back.clicked.connect(self.back_to_info) self.ref_bt_back = lower_bar.addWidget(bt_back) lower_bar.addWidget(new_spacer()) bt_close = QPushButton(self.i18n['close'].capitalize()) bt_close.setCursor(QCursor(Qt.PointingHandCursor)) bt_close.clicked.connect(lambda: self.close()) lower_bar.addWidget(bt_close) layout.addWidget(lower_bar) self.setMinimumWidth(self.gbox_info.sizeHint().width() * 1.2) self.setMaximumHeight(screen_size.height() * 0.8) self.adjustSize()
class TileLayer(Layer): ## # Constructor. ## def __init__(self, name, x, y, width, height): super().__init__(Layer.TileLayerType, name, x, y, width, height) self.mMaxTileSize = QSize(0, 0) self.mGrid = QVector() for i in range(width * height): self.mGrid.append(Cell()) self.mOffsetMargins = QMargins() def __iter__(self): return self.mGrid.__iter__() ## # Returns the maximum tile size of this layer. ## def maxTileSize(self): return self.mMaxTileSize ## # Returns the margins that have to be taken into account while drawing # this tile layer. The margins depend on the maximum tile size and the # offset applied to the tiles. ## def drawMargins(self): return QMargins(self.mOffsetMargins.left(), self.mOffsetMargins.top() + self.mMaxTileSize.height(), self.mOffsetMargins.right() + self.mMaxTileSize.width(), self.mOffsetMargins.bottom()) ## # Recomputes the draw margins. Needed after the tile offset of a tileset # has changed for example. # # Generally you want to call Map.recomputeDrawMargins instead. ## def recomputeDrawMargins(self): maxTileSize = QSize(0, 0) offsetMargins = QMargins() i = 0 while(i<self.mGrid.size()): cell = self.mGrid.at(i) tile = cell.tile if tile: size = tile.size() if (cell.flippedAntiDiagonally): size.transpose() offset = tile.offset() maxTileSize = maxSize(size, maxTileSize) offsetMargins = maxMargins(QMargins(-offset.x(), -offset.y(), offset.x(), offset.y()), offsetMargins) i += 1 self.mMaxTileSize = maxTileSize self.mOffsetMargins = offsetMargins if (self.mMap): self.mMap.adjustDrawMargins(self.drawMargins()) ## # Returns whether (x, y) is inside this map layer. ## def contains(self, *args): l = len(args) if l==2: x, y = args return x >= 0 and y >= 0 and x < self.mWidth and y < self.mHeight elif l==1: point = args[0] return self.contains(point.x(), point.y()) ## # Calculates the region of cells in this tile layer for which the given # \a condition returns True. ## def region(self, *args): l = len(args) if l==1: condition = args[0] region = QRegion() for y in range(self.mHeight): for x in range(self.mWidth): if (condition(self.cellAt(x, y))): rangeStart = x x += 1 while(x<=self.mWidth): if (x == self.mWidth or not condition(self.cellAt(x, y))): rangeEnd = x region += QRect(rangeStart + self.mX, y + self.mY, rangeEnd - rangeStart, 1) break x += 1 return region elif l==0: ## # Calculates the region occupied by the tiles of this layer. Similar to # Layer.bounds(), but leaves out the regions without tiles. ## return self.region(lambda cell:not cell.isEmpty()) ## # Returns a read-only reference to the cell at the given coordinates. The # coordinates have to be within this layer. ## def cellAt(self, *args): l = len(args) if l==2: x, y = args return self.mGrid.at(x + y * self.mWidth) elif l==1: point = args[0] return self.cellAt(point.x(), point.y()) ## # Sets the cell at the given coordinates. ## def setCell(self, x, y, cell): if (cell.tile): size = cell.tile.size() if (cell.flippedAntiDiagonally): size.transpose() offset = cell.tile.offset() self.mMaxTileSize = maxSize(size, self.mMaxTileSize) self.mOffsetMargins = maxMargins(QMargins(-offset.x(), -offset.y(), offset.x(), offset.y()), self.mOffsetMargins) if (self.mMap): self.mMap.adjustDrawMargins(self.drawMargins()) self.mGrid[x + y * self.mWidth] = cell ## # Returns a copy of the area specified by the given \a region. The # caller is responsible for the returned tile layer. ## def copy(self, *args): l = len(args) if l==1: region = args[0] if type(region) != QRegion: region = QRegion(region) area = region.intersected(QRect(0, 0, self.width(), self.height())) bounds = region.boundingRect() areaBounds = area.boundingRect() offsetX = max(0, areaBounds.x() - bounds.x()) offsetY = max(0, areaBounds.y() - bounds.y()) copied = TileLayer(QString(), 0, 0, bounds.width(), bounds.height()) for rect in area.rects(): for x in range(rect.left(), rect.right()+1): for y in range(rect.top(), rect.bottom()+1): copied.setCell(x - areaBounds.x() + offsetX, y - areaBounds.y() + offsetY, self.cellAt(x, y)) return copied elif l==4: x, y, width, height = args return self.copy(QRegion(x, y, width, height)) ## # Merges the given \a layer onto this layer at position \a pos. Parts that # fall outside of this layer will be lost and empty tiles in the given # layer will have no effect. ## def merge(self, pos, layer): # Determine the overlapping area area = QRect(pos, QSize(layer.width(), layer.height())) area &= QRect(0, 0, self.width(), self.height()) for y in range(area.top(), area.bottom()+1): for x in range(area.left(), area.right()+1): cell = layer.cellAt(x - pos.x(), y - pos.y()) if (not cell.isEmpty()): self.setCell(x, y, cell) ## # Removes all cells in the specified region. ## def erase(self, area): emptyCell = Cell() for rect in area.rects(): for x in range(rect.left(), rect.right()+1): for y in range(rect.top(), rect.bottom()+1): self.setCell(x, y, emptyCell) ## # Sets the cells starting at the given position to the cells in the given # \a tileLayer. Parts that fall outside of this layer will be ignored. # # When a \a mask is given, only cells that fall within this mask are set. # The mask is applied in local coordinates. ## def setCells(self, x, y, layer, mask = QRegion()): # Determine the overlapping area area = QRegion(QRect(x, y, layer.width(), layer.height())) area &= QRect(0, 0, self.width(), self.height()) if (not mask.isEmpty()): area &= mask for rect in area.rects(): for _x in range(rect.left(), rect.right()+1): for _y in range(rect.top(), rect.bottom()+1): self.setCell(_x, _y, layer.cellAt(_x - x, _y - y)) ## # Flip this tile layer in the given \a direction. Direction must be # horizontal or vertical. This doesn't change the dimensions of the # tile layer. ## def flip(self, direction): newGrid = QVector() for i in range(self.mWidth * self.mHeight): newGrid.append(Cell()) for y in range(self.mHeight): for x in range(self.mWidth): dest = newGrid[x + y * self.mWidth] if (direction == FlipDirection.FlipHorizontally): source = self.cellAt(self.mWidth - x - 1, y) dest = source dest.flippedHorizontally = not source.flippedHorizontally elif (direction == FlipDirection.FlipVertically): source = self.cellAt(x, self.mHeight - y - 1) dest = source dest.flippedVertically = not source.flippedVertically self.mGrid = newGrid ## # Rotate this tile layer by 90 degrees left or right. The tile positions # are rotated within the layer, and the tiles themselves are rotated. The # dimensions of the tile layer are swapped. ## def rotate(self, direction): rotateRightMask = [5, 4, 1, 0, 7, 6, 3, 2] rotateLeftMask = [3, 2, 7, 6, 1, 0, 5, 4] if direction == RotateDirection.RotateRight: rotateMask = rotateRightMask else: rotateMask = rotateLeftMask newWidth = self.mHeight newHeight = self.mWidth newGrid = QVector(newWidth * newHeight) for y in range(self.mHeight): for x in range(self.mWidth): source = self.cellAt(x, y) dest = source mask = (dest.flippedHorizontally << 2) | (dest.flippedVertically << 1) | (dest.flippedAntiDiagonally << 0) mask = rotateMask[mask] dest.flippedHorizontally = (mask & 4) != 0 dest.flippedVertically = (mask & 2) != 0 dest.flippedAntiDiagonally = (mask & 1) != 0 if (direction == RotateDirection.RotateRight): newGrid[x * newWidth + (self.mHeight - y - 1)] = dest else: newGrid[(self.mWidth - x - 1) * newWidth + y] = dest t = self.mMaxTileSize.width() self.mMaxTileSize.setWidth(self.mMaxTileSize.height()) self.mMaxTileSize.setHeight(t) self.mWidth = newWidth self.mHeight = newHeight self.mGrid = newGrid ## # Computes and returns the set of tilesets used by this tile layer. ## def usedTilesets(self): tilesets = QSet() i = 0 while(i<self.mGrid.size()): tile = self.mGrid.at(i).tile if tile: tilesets.insert(tile.tileset()) i += 1 return tilesets ## # Returns whether this tile layer has any cell for which the given # \a condition returns True. ## def hasCell(self, condition): i = 0 for cell in self.mGrid: if (condition(cell)): return True i += 1 return False ## # Returns whether this tile layer is referencing the given tileset. ## def referencesTileset(self, tileset): i = 0 while(i<self.mGrid.size()): tile = self.mGrid.at(i).tile if (tile and tile.tileset() == tileset): return True i += 1 return False ## # Removes all references to the given tileset. This sets all tiles on this # layer that are from the given tileset to null. ## def removeReferencesToTileset(self, tileset): i = 0 while(i<self.mGrid.size()): tile = self.mGrid.at(i).tile if (tile and tile.tileset() == tileset): self.mGrid.replace(i, Cell()) i += 1 ## # Replaces all tiles from \a oldTileset with tiles from \a newTileset. ## def replaceReferencesToTileset(self, oldTileset, newTileset): i = 0 while(i<self.mGrid.size()): tile = self.mGrid.at(i).tile if (tile and tile.tileset() == oldTileset): self.mGrid[i].tile = newTileset.tileAt(tile.id()) i += 1 ## # Resizes this tile layer to \a size, while shifting all tiles by # \a offset. ## def resize(self, size, offset): if (self.size() == size and offset.isNull()): return newGrid = QVector() for i in range(size.width() * size.height()): newGrid.append(Cell()) # Copy over the preserved part startX = max(0, -offset.x()) startY = max(0, -offset.y()) endX = min(self.mWidth, size.width() - offset.x()) endY = min(self.mHeight, size.height() - offset.y()) for y in range(startY, endY): for x in range(startX, endX): index = x + offset.x() + (y + offset.y()) * size.width() newGrid[index] = self.cellAt(x, y) self.mGrid = newGrid self.setSize(size) ## # Offsets the tiles in this layer within \a bounds by \a offset, # and optionally wraps them. # # \sa ObjectGroup.offset() ## def offsetTiles(self, offset, bounds, wrapX, wrapY): newGrid = QVector() for i in range(self.mWidth * self.mHeight): newGrid.append(Cell()) for y in range(self.mHeight): for x in range(self.mWidth): # Skip out of bounds tiles if (not bounds.contains(x, y)): newGrid[x + y * self.mWidth] = self.cellAt(x, y) continue # Get position to pull tile value from oldX = x - offset.x() oldY = y - offset.y() # Wrap x value that will be pulled from if (wrapX and bounds.width() > 0): while oldX < bounds.left(): oldX += bounds.width() while oldX > bounds.right(): oldX -= bounds.width() # Wrap y value that will be pulled from if (wrapY and bounds.height() > 0): while oldY < bounds.top(): oldY += bounds.height() while oldY > bounds.bottom(): oldY -= bounds.height() # Set the new tile if (self.contains(oldX, oldY) and bounds.contains(oldX, oldY)): newGrid[x + y * self.mWidth] = self.cellAt(oldX, oldY) else: newGrid[x + y * self.mWidth] = Cell() self.mGrid = newGrid def canMergeWith(self, other): return other.isTileLayer() def mergedWith(self, other): o = other unitedBounds = self.bounds().united(o.bounds()) offset = self.position() - unitedBounds.topLeft() merged = self.clone() merged.resize(unitedBounds.size(), offset) merged.merge(o.position() - unitedBounds.topLeft(), o) return merged ## # Returns the region where this tile layer and the given tile layer # are different. The relative positions of the layers are taken into # account. The returned region is relative to this tile layer. ## def computeDiffRegion(self, other): ret = QRegion() dx = other.x() - self.mX dy = other.y() - self.mY r = QRect(0, 0, self.width(), self.height()) r &= QRect(dx, dy, other.width(), other.height()) for y in range(r.top(), r.bottom()+1): for x in range(r.left(), r.right()+1): if (self.cellAt(x, y) != other.cellAt(x - dx, y - dy)): rangeStart = x while (x <= r.right() and self.cellAt(x, y) != other.cellAt(x - dx, y - dy)): x += 1 rangeEnd = x ret += QRect(rangeStart, y, rangeEnd - rangeStart, 1) return ret ## # Returns True if all tiles in the layer are empty. ## def isEmpty(self): i = 0 while(i<self.mGrid.size()): if (not self.mGrid.at(i).isEmpty()): return False i += 1 return True ## # Returns a duplicate of this TileLayer. # # \sa Layer.clone() ## def clone(self): return self.initializeClone(TileLayer(self.mName, self.mX, self.mY, self.mWidth, self.mHeight)) def begin(self): return self.mGrid.begin() def end(self): return self.mGrid.end() def initializeClone(self, clone): super().initializeClone(clone) clone.mGrid = self.mGrid clone.mMaxTileSize = self.mMaxTileSize clone.mOffsetMargins = self.mOffsetMargins return clone
class PDFWidget(QLabel): ''' A widget showing one page of a PDF. If you want to show multiple pages of the same PDF, make sure you share the document (let the first PDFWidget create the document, then pass thatPDFwidget.document to any subsequent widgets you create) or use a ScrolledPDFWidget. Will try to resize to a reasonable size to fit inside the geometry passed in (typically the screen size, a PyQt5.QtCore.QRect); or specify dpi explicitly. ''' def __init__(self, url, document=None, pageno=1, dpi=None, geometry=None, parent=None, load_cb=None): ''' load_cb: will be called when the document is loaded. ''' super(PDFWidget, self).__init__(parent) self.geometry = geometry # Guess at initial size: will be overridden later. if geometry: self.winwidth = geometry.height() * .75 self.winheight = geometry.height() else: self.geometry = PyQt5.QtCore.QSize(600, 800) self.winwidth = 600 self.winheight = 800 self.filename = url self.load_cb = load_cb self.network_manager = None self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) if not document: self.document = None if url: self.start_load(url) else: self.document = document self.page = None self.pagesize = QSize(self.winwidth, self.winheight) self.dpi = dpi # Poppler page numbering starts from 0 but that's not what # most PDF users will expect, so subtract: if pageno > 0: pageno -= 1 self.pageno = pageno self.render() def sizeHint(self): if not self.page: if not self.document: return QSize(self.winwidth, self.winheight) self.page = self.document.page(self.pageno) if not self.pagesize: self.pagesize = self.page.pageSize() return self.pagesize def render(self): '''Render to a pixmap at the current DPI setting. ''' if not self.document: return if not self.page: self.page = self.document.page(self.pageno) self.pagesize = self.page.pageSize() self.document.setRenderHint(Poppler.Document.TextAntialiasing) # self.document.setRenderHint(Poppler.Document.TextHinting) if not self.dpi: # Probably first time here. # self.pagesize is sized in pixels assuming POINTS_PER_INCH; # adjust that so the page barely fits on in self.geometry. # First assume that it's portrait aspect ratio and that # vertical size will be the limiting factor. self.dpi = POINTS_PER_INCH * \ self.geometry.height() / self.pagesize.height() # Was that too much: will it overflow in width? if self.pagesize.width() * self.dpi / POINTS_PER_INCH \ > self.geometry.width(): self.dpi = POINTS_PER_INCH * \ self.geometry.width() / self.pagesize.width() self.winwidth = self.pagesize.width() * self.dpi / POINTS_PER_INCH self.winheight = self.pagesize.height( ) * self.dpi / POINTS_PER_INCH # Most Qt5 programs seem to use setGeometry(x, y, w, h) # to set initial window size. resize() is the only method I've # found that doesn't force initial position as well as size. self.resize(self.winwidth, self.winheight) self.setWindowTitle('PDF Viewer') img = self.page.renderToImage(self.dpi, self.dpi) self.pixmap = QPixmap.fromImage(img) self.setPixmap(self.pixmap) def start_load(self, url): '''Create a Poppler.Document from the given URL, QUrl or filename. Return, then asynchronously call self.load_cb. ''' # If it's not a local file, we'll need to load it. # http://doc.qt.io/qt-5/qnetworkaccessmanager.html qurl = QUrl(url) if not qurl.scheme(): qurl = QUrl.fromLocalFile(url) if not self.network_manager: self.network_manager = QNetworkAccessManager() self.network_manager.finished.connect(self.download_finished) self.network_manager.get(QNetworkRequest(qurl)) def download_finished(self, network_reply): qbytes = network_reply.readAll() self.document = Poppler.Document.loadFromData(qbytes) self.render() if self.load_cb: self.load_cb()
class Plugin(RevealerPlugin): MAX_PLAINTEXT_LEN = 189 # chars def __init__(self, parent, config, name): RevealerPlugin.__init__(self, parent, config, name) self.base_dir = os.path.join(config.electrum_path(), 'revealer') if self.config.get('calibration_h') is None: self.config.set_key('calibration_h', 0) if self.config.get('calibration_v') is None: self.config.set_key('calibration_v', 0) self.calibration_h = self.config.get('calibration_h') self.calibration_v = self.config.get('calibration_v') self.f_size = QSize(1014*2, 642*2) self.abstand_h = 21 self.abstand_v = 34 self.calibration_noise = int('10' * 128) self.rawnoise = False make_dir(self.base_dir) self.extension = False @hook def create_status_bar(self, parent): b = StatusBarButton(read_QIcon('revealer.png'), "Revealer "+_("secret backup utility"), partial(self.setup_dialog, parent)) parent.addPermanentWidget(b) def requires_settings(self): return True def settings_widget(self, window): return EnterButton(_('Printer Calibration'), partial(self.calibration_dialog, window)) def password_dialog(self, msg=None, parent=None): from vialectrum.gui.qt.password_dialog import PasswordDialog parent = parent or self d = PasswordDialog(parent, msg) return d.run() def get_seed(self): password = None if self.wallet.has_keystore_encryption(): password = self.password_dialog(parent=self.d.parent()) if not password: raise UserCancelled() keystore = self.wallet.get_keystore() if not keystore or not keystore.has_seed(): return self.extension = bool(keystore.get_passphrase(password)) return keystore.get_seed(password) def setup_dialog(self, window): self.wallet = window.parent().wallet self.update_wallet_name(self.wallet) self.user_input = False self.d = WindowModalDialog(window, "Setup Dialog") self.d.setMinimumWidth(500) self.d.setMinimumHeight(210) self.d.setMaximumHeight(320) self.d.setContentsMargins(11,11,1,1) self.hbox = QHBoxLayout(self.d) vbox = QVBoxLayout() logo = QLabel() self.hbox.addWidget(logo) logo.setPixmap(QPixmap(icon_path('revealer.png'))) logo.setAlignment(Qt.AlignLeft) self.hbox.addSpacing(16) vbox.addWidget(WWLabel("<b>"+_("Revealer Secret Backup Plugin")+"</b><br>" +_("To encrypt your backup, first we need to load some noise.")+"<br/>")) vbox.addSpacing(7) bcreate = QPushButton(_("Create a new Revealer")) bcreate.setMaximumWidth(181) bcreate.setDefault(True) vbox.addWidget(bcreate, Qt.AlignCenter) self.load_noise = ScanQRTextEdit() self.load_noise.setTabChangesFocus(True) self.load_noise.textChanged.connect(self.on_edit) self.load_noise.setMaximumHeight(33) self.hbox.addLayout(vbox) vbox.addWidget(WWLabel(_("or type an existing revealer code below and click 'next':"))) vbox.addWidget(self.load_noise) vbox.addSpacing(3) self.next_button = QPushButton(_("Next"), self.d) self.next_button.setEnabled(False) vbox.addLayout(Buttons(self.next_button)) self.next_button.clicked.connect(self.d.close) self.next_button.clicked.connect(partial(self.cypherseed_dialog, window)) vbox.addWidget( QLabel("<b>" + _("Warning") + "</b>: " + _("Each revealer should be used only once.") +"<br>"+_("more information at <a href=\"https://revealer.cc/faq\">https://revealer.cc/faq</a>"))) def mk_digital(): try: self.make_digital(self.d) except Exception: self.logger.exception('') else: self.cypherseed_dialog(window) bcreate.clicked.connect(mk_digital) return bool(self.d.exec_()) def get_noise(self): text = self.load_noise.text() return ''.join(text.split()).lower() def on_edit(self): txt = self.get_noise() versioned_seed = self.get_versioned_seed_from_user_input(txt) if versioned_seed: self.versioned_seed = versioned_seed self.user_input = bool(versioned_seed) self.next_button.setEnabled(bool(versioned_seed)) def make_digital(self, dialog): self.make_rawnoise(True) self.bdone(dialog) self.d.close() def get_path_to_revealer_file(self, ext: str= '') -> str: version = self.versioned_seed.version code_id = self.versioned_seed.checksum filename = self.filename_prefix + version + "_" + code_id + ext path = os.path.join(self.base_dir, filename) return os.path.normcase(os.path.abspath(path)) def get_path_to_calibration_file(self): path = os.path.join(self.base_dir, 'calibration.pdf') return os.path.normcase(os.path.abspath(path)) def bcrypt(self, dialog): self.rawnoise = False version = self.versioned_seed.version code_id = self.versioned_seed.checksum dialog.show_message(''.join([_("{} encrypted for Revealer {}_{} saved as PNG and PDF at: ").format(self.was, version, code_id), "<b>", self.get_path_to_revealer_file(), "</b>", "<br/>", "<br/>", "<b>", _("Always check your backups.")]), rich_text=True) dialog.close() def ext_warning(self, dialog): dialog.show_message(''.join(["<b>",_("Warning"), ": </b>", _("your seed extension will <b>not</b> be included in the encrypted backup.")]), rich_text=True) dialog.close() def bdone(self, dialog): version = self.versioned_seed.version code_id = self.versioned_seed.checksum dialog.show_message(''.join([_("Digital Revealer ({}_{}) saved as PNG and PDF at:").format(version, code_id), "<br/>","<b>", self.get_path_to_revealer_file(), '</b>']), rich_text=True) def customtxt_limits(self): txt = self.text.text() self.max_chars.setVisible(False) self.char_count.setText(f"({len(txt)}/{self.MAX_PLAINTEXT_LEN})") if len(txt)>0: self.ctext.setEnabled(True) if len(txt) > self.MAX_PLAINTEXT_LEN: self.text.setPlainText(txt[:self.MAX_PLAINTEXT_LEN]) self.max_chars.setVisible(True) def t(self): self.txt = self.text.text() self.seed_img(is_seed=False) def warn_old_revealer(self): if self.versioned_seed.version == '0': link = "https://revealer.cc/revealer-warning-and-upgrade/" self.d.show_warning(("<b>{warning}: </b>{ver0}<br>" "{url}<br>" "{risk}") .format(warning=_("Warning"), ver0=_("Revealers starting with 0 are not secure due to a vulnerability."), url=_("More info at: {}").format(f'<a href="{link}">{link}</a>'), risk=_("Proceed at your own risk.")), rich_text=True) def cypherseed_dialog(self, window): self.warn_old_revealer() d = WindowModalDialog(window, "Encryption Dialog") d.setMinimumWidth(500) d.setMinimumHeight(210) d.setMaximumHeight(450) d.setContentsMargins(11, 11, 1, 1) self.c_dialog = d hbox = QHBoxLayout(d) self.vbox = QVBoxLayout() logo = QLabel() hbox.addWidget(logo) logo.setPixmap(QPixmap(icon_path('revealer.png'))) logo.setAlignment(Qt.AlignLeft) hbox.addSpacing(16) self.vbox.addWidget(WWLabel("<b>" + _("Revealer Secret Backup Plugin") + "</b><br>" + _("Ready to encrypt for revealer {}") .format(self.versioned_seed.version+'_'+self.versioned_seed.checksum))) self.vbox.addSpacing(11) hbox.addLayout(self.vbox) grid = QGridLayout() self.vbox.addLayout(grid) cprint = QPushButton(_("Encrypt {}'s seed").format(self.wallet_name)) cprint.setMaximumWidth(250) cprint.clicked.connect(partial(self.seed_img, True)) self.vbox.addWidget(cprint) self.vbox.addSpacing(1) self.vbox.addWidget(WWLabel("<b>"+_("OR")+"</b> "+_("type a custom alphanumerical secret below:"))) self.text = ScanQRTextEdit() self.text.setTabChangesFocus(True) self.text.setMaximumHeight(70) self.text.textChanged.connect(self.customtxt_limits) self.vbox.addWidget(self.text) self.char_count = WWLabel("") self.char_count.setAlignment(Qt.AlignRight) self.vbox.addWidget(self.char_count) self.max_chars = WWLabel("<font color='red'>" + _("This version supports a maximum of {} characters.").format(self.MAX_PLAINTEXT_LEN) +"</font>") self.vbox.addWidget(self.max_chars) self.max_chars.setVisible(False) self.ctext = QPushButton(_("Encrypt custom secret")) self.ctext.clicked.connect(self.t) self.vbox.addWidget(self.ctext) self.ctext.setEnabled(False) self.vbox.addSpacing(11) self.vbox.addLayout(Buttons(CloseButton(d))) return bool(d.exec_()) def update_wallet_name(self, name): self.wallet_name = str(name) def seed_img(self, is_seed = True): if is_seed: try: cseed = self.get_seed() except UserCancelled: return except InvalidPassword as e: self.d.show_error(str(e)) return if not cseed: self.d.show_message(_("This wallet has no seed")) return txt = cseed.upper() else: txt = self.txt.upper() img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono) bitmap = QBitmap.fromImage(img, Qt.MonoOnly) bitmap.fill(Qt.white) painter = QPainter() painter.begin(bitmap) QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(__file__), 'SourceSansPro-Bold.otf') ) if len(txt) < 102 : fontsize = 15 linespace = 15 max_letters = 17 max_lines = 6 max_words = 3 else: fontsize = 12 linespace = 10 max_letters = 21 max_lines = 9 max_words = int(max_letters/4) font = QFont('Source Sans Pro', fontsize, QFont.Bold) font.setLetterSpacing(QFont.PercentageSpacing, 100) font.setPixelSize(fontsize) painter.setFont(font) seed_array = txt.split(' ') for n in range(max_lines): nwords = max_words temp_seed = seed_array[:nwords] while len(' '.join(map(str, temp_seed))) > max_letters: nwords = nwords - 1 temp_seed = seed_array[:nwords] painter.drawText(QRect(0, linespace*n , self.SIZE[0], self.SIZE[1]), Qt.AlignHCenter, ' '.join(map(str, temp_seed))) del seed_array[:nwords] painter.end() img = bitmap.toImage() if (self.rawnoise == False): self.make_rawnoise() self.make_cypherseed(img, self.rawnoise, False, is_seed) return img def make_rawnoise(self, create_revealer=False): if not self.user_input: self.versioned_seed = self.gen_random_versioned_seed() assert self.versioned_seed w, h = self.SIZE rawnoise = QImage(w, h, QImage.Format_Mono) noise_map = self.get_noise_map(self.versioned_seed) for (x,y), pixel in noise_map.items(): rawnoise.setPixel(x, y, pixel) self.rawnoise = rawnoise if create_revealer: self.make_revealer() def make_calnoise(self): random.seed(self.calibration_noise) w, h = self.SIZE rawnoise = QImage(w, h, QImage.Format_Mono) for x in range(w): for y in range(h): rawnoise.setPixel(x,y,random.randint(0, 1)) self.calnoise = self.pixelcode_2x2(rawnoise) def make_revealer(self): revealer = self.pixelcode_2x2(self.rawnoise) revealer.invertPixels() revealer = QBitmap.fromImage(revealer) revealer = revealer.scaled(self.f_size, Qt.KeepAspectRatio) revealer = self.overlay_marks(revealer) self.filename_prefix = 'revealer_' revealer.save(self.get_path_to_revealer_file('.png')) self.toPdf(QImage(revealer)) QDesktopServices.openUrl(QUrl.fromLocalFile(self.get_path_to_revealer_file('.pdf'))) def make_cypherseed(self, img, rawnoise, calibration=False, is_seed = True): img = img.convertToFormat(QImage.Format_Mono) p = QPainter() p.begin(img) p.setCompositionMode(26) #xor p.drawImage(0, 0, rawnoise) p.end() cypherseed = self.pixelcode_2x2(img) cypherseed = QBitmap.fromImage(cypherseed) cypherseed = cypherseed.scaled(self.f_size, Qt.KeepAspectRatio) cypherseed = self.overlay_marks(cypherseed, True, calibration) if not is_seed: self.filename_prefix = 'custom_secret_' self.was = _('Custom secret') else: self.filename_prefix = self.wallet_name + '_seed_' self.was = self.wallet_name + ' ' + _('seed') if self.extension: self.ext_warning(self.c_dialog) if not calibration: self.toPdf(QImage(cypherseed)) QDesktopServices.openUrl(QUrl.fromLocalFile(self.get_path_to_revealer_file('.pdf'))) cypherseed.save(self.get_path_to_revealer_file('.png')) self.bcrypt(self.c_dialog) return cypherseed def calibration(self): img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono) bitmap = QBitmap.fromImage(img, Qt.MonoOnly) bitmap.fill(Qt.black) self.make_calnoise() img = self.overlay_marks(self.calnoise.scaledToHeight(self.f_size.height()), False, True) self.calibration_pdf(img) QDesktopServices.openUrl(QUrl.fromLocalFile(self.get_path_to_calibration_file())) return img def toPdf(self, image): printer = QPrinter() printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter) printer.setResolution(600) printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(self.get_path_to_revealer_file('.pdf')) printer.setPageMargins(0,0,0,0,6) painter = QPainter() painter.begin(printer) delta_h = round(image.width()/self.abstand_v) delta_v = round(image.height()/self.abstand_h) size_h = 2028+((int(self.calibration_h)*2028/(2028-(delta_h*2)+int(self.calibration_h)))/2) size_v = 1284+((int(self.calibration_v)*1284/(1284-(delta_v*2)+int(self.calibration_v)))/2) image = image.scaled(size_h, size_v) painter.drawImage(553,533, image) wpath = QPainterPath() wpath.addRoundedRect(QRectF(553,533, size_h, size_v), 19, 19) painter.setPen(QPen(Qt.black, 1)) painter.drawPath(wpath) painter.end() def calibration_pdf(self, image): printer = QPrinter() printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter) printer.setResolution(600) printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(self.get_path_to_calibration_file()) printer.setPageMargins(0,0,0,0,6) painter = QPainter() painter.begin(printer) painter.drawImage(553,533, image) font = QFont('Source Sans Pro', 10, QFont.Bold) painter.setFont(font) painter.drawText(254,277, _("Calibration sheet")) font = QFont('Source Sans Pro', 7, QFont.Bold) painter.setFont(font) painter.drawText(600,2077, _("Instructions:")) font = QFont('Source Sans Pro', 7, QFont.Normal) painter.setFont(font) painter.drawText(700, 2177, _("1. Place this paper on a flat and well iluminated surface.")) painter.drawText(700, 2277, _("2. Align your Revealer borderlines to the dashed lines on the top and left.")) painter.drawText(700, 2377, _("3. Press slightly the Revealer against the paper and read the numbers that best " "match on the opposite sides. ")) painter.drawText(700, 2477, _("4. Type the numbers in the software")) painter.end() def pixelcode_2x2(self, img): result = QImage(img.width()*2, img.height()*2, QImage.Format_ARGB32 ) white = qRgba(255,255,255,0) black = qRgba(0,0,0,255) for x in range(img.width()): for y in range(img.height()): c = img.pixel(QPoint(x,y)) colors = QColor(c).getRgbF() if colors[0]: result.setPixel(x*2+1,y*2+1, black) result.setPixel(x*2,y*2+1, white) result.setPixel(x*2+1,y*2, white) result.setPixel(x*2, y*2, black) else: result.setPixel(x*2+1,y*2+1, white) result.setPixel(x*2,y*2+1, black) result.setPixel(x*2+1,y*2, black) result.setPixel(x*2, y*2, white) return result def overlay_marks(self, img, is_cseed=False, calibration_sheet=False): border_color = Qt.white base_img = QImage(self.f_size.width(),self.f_size.height(), QImage.Format_ARGB32) base_img.fill(border_color) img = QImage(img) painter = QPainter() painter.begin(base_img) total_distance_h = round(base_img.width() / self.abstand_v) dist_v = round(total_distance_h) / 2 dist_h = round(total_distance_h) / 2 img = img.scaledToWidth(base_img.width() - (2 * (total_distance_h))) painter.drawImage(total_distance_h, total_distance_h, img) #frame around image pen = QPen(Qt.black, 2) painter.setPen(pen) #horz painter.drawLine(0, total_distance_h, base_img.width(), total_distance_h) painter.drawLine(0, base_img.height()-(total_distance_h), base_img.width(), base_img.height()-(total_distance_h)) #vert painter.drawLine(total_distance_h, 0, total_distance_h, base_img.height()) painter.drawLine(base_img.width()-(total_distance_h), 0, base_img.width()-(total_distance_h), base_img.height()) #border around img border_thick = 6 Rpath = QPainterPath() Rpath.addRect(QRectF((total_distance_h)+(border_thick/2), (total_distance_h)+(border_thick/2), base_img.width()-((total_distance_h)*2)-((border_thick)-1), (base_img.height()-((total_distance_h))*2)-((border_thick)-1))) pen = QPen(Qt.black, border_thick) pen.setJoinStyle (Qt.MiterJoin) painter.setPen(pen) painter.drawPath(Rpath) Bpath = QPainterPath() Bpath.addRect(QRectF((total_distance_h), (total_distance_h), base_img.width()-((total_distance_h)*2), (base_img.height()-((total_distance_h))*2))) pen = QPen(Qt.black, 1) painter.setPen(pen) painter.drawPath(Bpath) pen = QPen(Qt.black, 1) painter.setPen(pen) painter.drawLine(0, base_img.height()/2, total_distance_h, base_img.height()/2) painter.drawLine(base_img.width()/2, 0, base_img.width()/2, total_distance_h) painter.drawLine(base_img.width()-total_distance_h, base_img.height()/2, base_img.width(), base_img.height()/2) painter.drawLine(base_img.width()/2, base_img.height(), base_img.width()/2, base_img.height() - total_distance_h) #print code f_size = 37 QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(__file__), 'DejaVuSansMono-Bold.ttf')) font = QFont("DejaVu Sans Mono", f_size-11, QFont.Bold) font.setPixelSize(35) painter.setFont(font) if not calibration_sheet: if is_cseed: #its a secret painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine)) painter.drawLine(0, dist_v, base_img.width(), dist_v) painter.drawLine(dist_h, 0, dist_h, base_img.height()) painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v)) painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) painter.drawImage(((total_distance_h))+11, ((total_distance_h))+11, QImage(icon_path('electrumb.png')).scaledToWidth(2.1*(total_distance_h), Qt.SmoothTransformation)) painter.setPen(QPen(Qt.white, border_thick*8)) painter.drawLine(base_img.width()-((total_distance_h))-(border_thick*8)/2-(border_thick/2)-2, (base_img.height()-((total_distance_h)))-((border_thick*8)/2)-(border_thick/2)-2, base_img.width()-((total_distance_h))-(border_thick*8)/2-(border_thick/2)-2 - 77, (base_img.height()-((total_distance_h)))-((border_thick*8)/2)-(border_thick/2)-2) painter.setPen(QColor(0,0,0,255)) painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick - 11, base_img.height()-total_distance_h - border_thick), Qt.AlignRight, self.versioned_seed.version + '_'+self.versioned_seed.checksum) painter.end() else: # revealer painter.setPen(QPen(border_color, 17)) painter.drawLine(0, dist_v, base_img.width(), dist_v) painter.drawLine(dist_h, 0, dist_h, base_img.height()) painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v)) painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) painter.setPen(QPen(Qt.black, 2)) painter.drawLine(0, dist_v, base_img.width(), dist_v) painter.drawLine(dist_h, 0, dist_h, base_img.height()) painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v)) painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) logo = QImage(icon_path('revealer_c.png')).scaledToWidth(1.3*(total_distance_h)) painter.drawImage((total_distance_h)+ (border_thick), ((total_distance_h))+ (border_thick), logo, Qt.SmoothTransformation) #frame around logo painter.setPen(QPen(Qt.black, border_thick)) painter.drawLine(total_distance_h+border_thick, total_distance_h+logo.height()+3*(border_thick/2), total_distance_h+logo.width()+border_thick, total_distance_h+logo.height()+3*(border_thick/2)) painter.drawLine(logo.width()+total_distance_h+3*(border_thick/2), total_distance_h+(border_thick), total_distance_h+logo.width()+3*(border_thick/2), total_distance_h+logo.height()+(border_thick)) #frame around code/qr qr_size = 179 painter.drawLine((base_img.width()-((total_distance_h))-(border_thick/2)-2)-qr_size, (base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2, (base_img.width()/2+(total_distance_h/2)-border_thick-(border_thick*8)/2)-qr_size, (base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2) painter.drawLine((base_img.width()/2+(total_distance_h/2)-border_thick-(border_thick*8)/2)-qr_size, (base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2, base_img.width()/2 + (total_distance_h/2)-border_thick-(border_thick*8)/2-qr_size, ((base_img.height()-((total_distance_h)))-(border_thick/2)-2)) painter.setPen(QPen(Qt.white, border_thick * 8)) painter.drawLine( base_img.width() - ((total_distance_h)) - (border_thick * 8) / 2 - (border_thick / 2) - 2, (base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2, base_img.width() / 2 + (total_distance_h / 2) - border_thick - qr_size, (base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2) painter.setPen(QColor(0,0,0,255)) painter.drawText(QRect(((base_img.width()/2) +21)-qr_size, base_img.height()-107, base_img.width()-total_distance_h - border_thick -93, base_img.height()-total_distance_h - border_thick), Qt.AlignLeft, self.versioned_seed.get_ui_string_version_plus_seed()) painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick -3 -qr_size, base_img.height()-total_distance_h - border_thick), Qt.AlignRight, self.versioned_seed.checksum) # draw qr code qr_qt = self.paintQR(self.versioned_seed.get_ui_string_version_plus_seed() + self.versioned_seed.checksum) target = QRectF(base_img.width()-65-qr_size, base_img.height()-65-qr_size, qr_size, qr_size ) painter.drawImage(target, qr_qt) painter.setPen(QPen(Qt.black, 4)) painter.drawLine(base_img.width()-65-qr_size, base_img.height()-65-qr_size, base_img.width() - 65 - qr_size, (base_img.height() - ((total_distance_h))) - ((border_thick * 8)) - (border_thick / 2) - 4 ) painter.drawLine(base_img.width()-65-qr_size, base_img.height()-65-qr_size, base_img.width() - 65, base_img.height()-65-qr_size ) painter.end() else: # calibration only painter.end() cal_img = QImage(self.f_size.width() + 100, self.f_size.height() + 100, QImage.Format_ARGB32) cal_img.fill(Qt.white) cal_painter = QPainter() cal_painter.begin(cal_img) cal_painter.drawImage(0,0, base_img) #black lines in the middle of border top left only cal_painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine)) cal_painter.drawLine(0, dist_v, base_img.width(), dist_v) cal_painter.drawLine(dist_h, 0, dist_h, base_img.height()) pen = QPen(Qt.black, 2, Qt.DashDotDotLine) cal_painter.setPen(pen) n=15 cal_painter.setFont(QFont("DejaVu Sans Mono", 21, QFont.Bold)) for x in range(-n,n): #lines on bottom (vertical calibration) cal_painter.drawLine((((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)-13, x+2+base_img.height()-(dist_v), (((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)+13, x+2+base_img.height()-(dist_v)) num_pos = 9 if x > 9 : num_pos = 17 if x < 0 : num_pos = 20 if x < -9: num_pos = 27 cal_painter.drawText((((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)-num_pos, 50+base_img.height()-(dist_v), str(x)) #lines on the right (horizontal calibrations) cal_painter.drawLine(x+2+(base_img.width()-(dist_h)), ((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()/2)-13, x+2+(base_img.width()-(dist_h)), ((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()/2)+13) cal_painter.drawText(30+(base_img.width()-(dist_h)), ((base_img.height()/(2*n)) *(x))+ (base_img.height()/2)+13, str(x)) cal_painter.end() base_img = cal_img return base_img def paintQR(self, data): if not data: return qr = qrcode.QRCode() qr.add_data(data) matrix = qr.get_matrix() k = len(matrix) border_color = Qt.white base_img = QImage(k * 5, k * 5, QImage.Format_ARGB32) base_img.fill(border_color) qrpainter = QPainter() qrpainter.begin(base_img) boxsize = 5 size = k * boxsize left = (base_img.width() - size)/2 top = (base_img.height() - size)/2 qrpainter.setBrush(Qt.black) qrpainter.setPen(Qt.black) for r in range(k): for c in range(k): if matrix[r][c]: qrpainter.drawRect(left+c*boxsize, top+r*boxsize, boxsize - 1, boxsize - 1) qrpainter.end() return base_img def calibration_dialog(self, window): d = WindowModalDialog(window, _("Revealer - Printer calibration settings")) d.setMinimumSize(100, 200) vbox = QVBoxLayout(d) vbox.addWidget(QLabel(''.join(["<br/>", _("If you have an old printer, or want optimal precision"),"<br/>", _("print the calibration pdf and follow the instructions "), "<br/>","<br/>", ]))) self.calibration_h = self.config.get('calibration_h') self.calibration_v = self.config.get('calibration_v') cprint = QPushButton(_("Open calibration pdf")) cprint.clicked.connect(self.calibration) vbox.addWidget(cprint) vbox.addWidget(QLabel(_('Calibration values:'))) grid = QGridLayout() vbox.addLayout(grid) grid.addWidget(QLabel(_('Right side')), 0, 0) horizontal = QLineEdit() horizontal.setText(str(self.calibration_h)) grid.addWidget(horizontal, 0, 1) grid.addWidget(QLabel(_('Bottom')), 1, 0) vertical = QLineEdit() vertical.setText(str(self.calibration_v)) grid.addWidget(vertical, 1, 1) vbox.addStretch() vbox.addSpacing(13) vbox.addLayout(Buttons(CloseButton(d), OkButton(d))) if not d.exec_(): return self.calibration_h = int(Decimal(horizontal.text())) self.config.set_key('calibration_h', self.calibration_h) self.calibration_v = int(Decimal(vertical.text())) self.config.set_key('calibration_v', self.calibration_v)
class SensorWorker(QThread): def __init__(self, Parent=None): QThread.__init__(self, parent) self.exiting = False self.size = QSize(0, 0) self.path = QPainterPath() angle = 2 * math.pi / 5 self.outerRadius = 20 self.innerRadius = 8 self.path.moveTo(self.outerRadius, 0) for step in range(1, 6): self.path.lineTo(self.innerRadius * math.cos((step - 0.5) * angle), self.innerRadius * math.sin((step - 0.5) * angle)) self.path.lineTo(self.outerRadius * math.cos(step * angle), self.outerRadius * math.sin(step * angle)) self.path.closeSubpath() def __del__(self): self.exiting = True self.wait() def render(self, size, stars): self.size = size self.stars = stars self.start() def run(self): random.seed() n = self.stars width = self.size.width() height = self.size.height() while not self.exiting and n > 0: image = QImage(self.outerRadius * 2, self.outerRadius * 2, QImage.Format_ARGB32) image.fill(qRgba(0, 0, 0, 0)) x = random.randrange(0, width) y = random.randrange(0, height) angle = random.randrange(0, 360) red = random.randrange(0, 256) green = random.randrange(0, 256) blue = random.randrange(0, 256) alpha = random.randrange(0, 256) painter = QPainter() painter.begin(image) painter.setRenderHint(QPainter.Antialiasing) painter.setPen(Qt.NoPen) painter.setBrush(QColor(red, green, blue, alpha)) painter.translate(self.outerRadius, self.outerRadius) painter.rotate(angle) painter.drawPath(self.path) painter.end() self.emit( SIGNAL("output(QRect, QImage)"), QRect(x - self.outerRadius, y - self.outerRadius, self.outerRadius * 2, self.outerRadius * 2), image) n -= 1
class Settings(object): l = logging.getLogger('Settings') def __init__(self, fileName='shadow.ini'): self.fileName = fileName self.tzIdx = 0 self.mainWindowSize = QSize(1200, 700) self.mainWindowPos = QPoint(240, 200) def load(self): self.l.debug('Loading local settings from {}'.format(self.fileName)) config = configparser.ConfigParser() config.read(self.fileName) if config.has_section('common'): commonSettings = config['common'] self.tzIdx = int(commonSettings.get('tz', 0)) # load main window position and size if config.has_section('mainWindow'): windowSettings = config['mainWindow'] # read position pos = windowSettings.get( 'pos', '{},{}'.format(self.mainWindowPos.x(), self.mainWindowPos.y())).split(',') self.mainWindowPos = QPoint(int(pos[0]), int(pos[1])) # read size size = windowSettings.get( 'size', '{},{}'.format(self.mainWindowSize.width(), self.mainWindowSize.height())).split(',') self.mainWindowSize = QSize(int(size[0]), int(size[1])) def dump(self, channel): self.l.debug('Saving local settings ...') config = configparser.ConfigParser() config.add_section('common') config.set('common', 'tz', str(self.tzIdx)) config.add_section('mainWindow') config.set( 'mainWindow', 'pos', '{},{}'.format(self.mainWindowPos.x(), self.mainWindowPos.y())) config.set( 'mainWindow', 'size', '{},{}'.format(self.mainWindowSize.width(), self.mainWindowSize.height())) config.write(channel) def save(self): with open(self.fileName, 'w') as configfile: self.dump(configfile) def setMainWindowPos(self, pos): self.mainWindowPos = pos def setMainWindowSize(self, size): self.mainWindowSize = size def getMainWindowPos(self): return self.mainWindowPos def getMainWindowSize(self): return self.mainWindowSize def getTzIndex(self): return self.tzIdx def setTzIdx(self, idx): self.tzIdx = idx
def sizeHint(self, default_size: QSize) -> QSize: if not self.features: return default_size width = len(self.features) * (self.ICON_SIZE.width() + 1) return QSize(width, default_size.height())
class Window(QWidget): def __init__(self, parent=None): super(QWidget, self).__init__(parent) self.m_iconSize = QSize(64, 64) self.m_scene = QGraphicsScene() self.m_ui = Ui_Form() self.m_ui.setupUi(self) self.m_ui.easingCurvePicker.setIconSize(self.m_iconSize) self.m_ui.easingCurvePicker.setMinimumHeight(self.m_iconSize.height() + 50) self.m_ui.buttonGroup.setId(self.m_ui.lineRadio, 0) self.m_ui.buttonGroup.setId(self.m_ui.circleRadio, 1) dummy = QEasingCurve() self.m_ui.periodSpinBox.setValue(dummy.period()) self.m_ui.amplitudeSpinBox.setValue(dummy.amplitude()) self.m_ui.overshootSpinBox.setValue(dummy.overshoot()) self.m_ui.easingCurvePicker.currentRowChanged.connect( self.curveChanged) self.m_ui.buttonGroup.buttonClicked[int].connect(self.pathChanged) self.m_ui.periodSpinBox.valueChanged.connect(self.periodChanged) self.m_ui.amplitudeSpinBox.valueChanged.connect(self.amplitudeChanged) self.m_ui.overshootSpinBox.valueChanged.connect(self.overshootChanged) self.createCurveIcons() pix = QPixmap(':/images/qt-logo.png') self.m_item = PixmapItem(pix) self.m_scene.addItem(self.m_item.pixmap_item) self.m_ui.graphicsView.setScene(self.m_scene) self.m_anim = Animation(self.m_item, b'pos') self.m_anim.setEasingCurve(QEasingCurve.OutBounce) self.m_ui.easingCurvePicker.setCurrentRow(int(QEasingCurve.OutBounce)) self.startAnimation() def createCurveIcons(self): pix = QPixmap(self.m_iconSize) painter = QPainter() gradient = QLinearGradient(0, 0, 0, self.m_iconSize.height()) gradient.setColorAt(0.0, QColor(240, 240, 240)) gradient.setColorAt(1.0, QColor(224, 224, 224)) brush = QBrush(gradient) # The original C++ code uses undocumented calls to get the names of the # different curve types. We do the Python equivalant (but without # cheating). curve_types = [ (n, c) for n, c in QEasingCurve.__dict__.items() if isinstance(c, QEasingCurve.Type) and c != QEasingCurve.Custom ] curve_types.sort(key=lambda ct: ct[1]) painter.begin(pix) for curve_name, curve_type in curve_types: painter.fillRect(QRect(QPoint(0, 0), self.m_iconSize), brush) curve = QEasingCurve(curve_type) if curve_type == QEasingCurve.BezierSpline: curve.addCubicBezierSegment(QPointF(0.4, 0.1), QPointF(0.6, 0.9), QPointF(1.0, 1.0)) elif curve_type == QEasingCurve.TCBSpline: curve.addTCBSegment(QPointF(0.0, 0.0), 0, 0, 0) curve.addTCBSegment(QPointF(0.3, 0.4), 0.2, 1, -0.2) curve.addTCBSegment(QPointF(0.7, 0.6), -0.2, 1, 0.2) curve.addTCBSegment(QPointF(1.0, 1.0), 0, 0, 0) painter.setPen(QColor(0, 0, 255, 64)) xAxis = self.m_iconSize.height() / 1.5 yAxis = self.m_iconSize.width() / 3.0 painter.drawLine(0, xAxis, self.m_iconSize.width(), xAxis) painter.drawLine(yAxis, 0, yAxis, self.m_iconSize.height()) curveScale = self.m_iconSize.height() / 2.0 painter.setPen(Qt.NoPen) # Start point. painter.setBrush(Qt.red) start = QPoint(yAxis, xAxis - curveScale * curve.valueForProgress(0)) painter.draw_rect(start.x() - 1, start.y() - 1, 3, 3) # End point. painter.setBrush(Qt.blue) end = QPoint(yAxis + curveScale, xAxis - curveScale * curve.valueForProgress(1)) painter.draw_rect(end.x() - 1, end.y() - 1, 3, 3) curvePath = QPainterPath() curvePath.moveTo(QPointF(start)) t = 0.0 while t <= 1.0: to = QPointF(yAxis + curveScale * t, xAxis - curveScale * curve.valueForProgress(t)) curvePath.lineTo(to) t += 1.0 / curveScale painter.setRenderHint(QPainter.Antialiasing, True) painter.strokePath(curvePath, QColor(32, 32, 32)) painter.setRenderHint(QPainter.Antialiasing, False) item = QListWidgetItem() item.setIcon(QIcon(pix)) item.setText(curve_name) self.m_ui.easingCurvePicker.addItem(item) painter.end() def startAnimation(self): self.m_anim.setStartValue(QPointF(0, 0)) self.m_anim.setEndValue(QPointF(100, 100)) self.m_anim.setDuration(2000) self.m_anim.setLoopCount(-1) self.m_anim.start() def curveChanged(self, row): curveType = QEasingCurve.Type(row) self.m_anim.setEasingCurve(curveType) self.m_anim.setCurrentTime(0) isElastic = (curveType >= QEasingCurve.InElastic and curveType <= QEasingCurve.OutInElastic) isBounce = (curveType >= QEasingCurve.InBounce and curveType <= QEasingCurve.OutInBounce) self.m_ui.periodSpinBox.setEnabled(isElastic) self.m_ui.amplitudeSpinBox.setEnabled(isElastic or isBounce) self.m_ui.overshootSpinBox.setEnabled( curveType >= QEasingCurve.InBack and curveType <= QEasingCurve.OutInBack) def pathChanged(self, index): self.m_anim.setPathType(index) def periodChanged(self, value): curve = self.m_anim.easingCurve() curve.setPeriod(value) self.m_anim.setEasingCurve(curve) def amplitudeChanged(self, value): curve = self.m_anim.easingCurve() curve.setAmplitude(value) self.m_anim.setEasingCurve(curve) def overshootChanged(self, value): curve = self.m_anim.easingCurve() curve.setOvershoot(value) self.m_anim.setEasingCurve(curve)
class TileLayer(Layer): ## # Constructor. ## def __init__(self, name, x, y, width, height): super().__init__(Layer.TileLayerType, name, x, y, width, height) self.mMaxTileSize = QSize(0, 0) self.mGrid = QVector() for i in range(width * height): self.mGrid.append(Cell()) self.mOffsetMargins = QMargins() def __iter__(self): return self.mGrid.__iter__() ## # Returns the maximum tile size of this layer. ## def maxTileSize(self): return self.mMaxTileSize ## # Returns the margins that have to be taken into account while drawing # this tile layer. The margins depend on the maximum tile size and the # offset applied to the tiles. ## def drawMargins(self): return QMargins( self.mOffsetMargins.left(), self.mOffsetMargins.top() + self.mMaxTileSize.height(), self.mOffsetMargins.right() + self.mMaxTileSize.width(), self.mOffsetMargins.bottom()) ## # Recomputes the draw margins. Needed after the tile offset of a tileset # has changed for example. # # Generally you want to call Map.recomputeDrawMargins instead. ## def recomputeDrawMargins(self): maxTileSize = QSize(0, 0) offsetMargins = QMargins() i = 0 while (i < self.mGrid.size()): cell = self.mGrid.at(i) tile = cell.tile if tile: size = tile.size() if (cell.flippedAntiDiagonally): size.transpose() offset = tile.offset() maxTileSize = maxSize(size, maxTileSize) offsetMargins = maxMargins( QMargins(-offset.x(), -offset.y(), offset.x(), offset.y()), offsetMargins) i += 1 self.mMaxTileSize = maxTileSize self.mOffsetMargins = offsetMargins if (self.mMap): self.mMap.adjustDrawMargins(self.drawMargins()) ## # Returns whether (x, y) is inside this map layer. ## def contains(self, *args): l = len(args) if l == 2: x, y = args return x >= 0 and y >= 0 and x < self.mWidth and y < self.mHeight elif l == 1: point = args[0] return self.contains(point.x(), point.y()) ## # Calculates the region of cells in this tile layer for which the given # \a condition returns True. ## def region(self, *args): l = len(args) if l == 1: condition = args[0] region = QRegion() for y in range(self.mHeight): for x in range(self.mWidth): if (condition(self.cellAt(x, y))): rangeStart = x x += 1 while (x <= self.mWidth): if (x == self.mWidth or not condition(self.cellAt(x, y))): rangeEnd = x region += QRect(rangeStart + self.mX, y + self.mY, rangeEnd - rangeStart, 1) break x += 1 return region elif l == 0: ## # Calculates the region occupied by the tiles of this layer. Similar to # Layer.bounds(), but leaves out the regions without tiles. ## return self.region(lambda cell: not cell.isEmpty()) ## # Returns a read-only reference to the cell at the given coordinates. The # coordinates have to be within this layer. ## def cellAt(self, *args): l = len(args) if l == 2: x, y = args return self.mGrid.at(x + y * self.mWidth) elif l == 1: point = args[0] return self.cellAt(point.x(), point.y()) ## # Sets the cell at the given coordinates. ## def setCell(self, x, y, cell): if (cell.tile): size = cell.tile.size() if (cell.flippedAntiDiagonally): size.transpose() offset = cell.tile.offset() self.mMaxTileSize = maxSize(size, self.mMaxTileSize) self.mOffsetMargins = maxMargins( QMargins(-offset.x(), -offset.y(), offset.x(), offset.y()), self.mOffsetMargins) if (self.mMap): self.mMap.adjustDrawMargins(self.drawMargins()) self.mGrid[x + y * self.mWidth] = cell ## # Returns a copy of the area specified by the given \a region. The # caller is responsible for the returned tile layer. ## def copy(self, *args): l = len(args) if l == 1: region = args[0] if type(region) != QRegion: region = QRegion(region) area = region.intersected(QRect(0, 0, self.width(), self.height())) bounds = region.boundingRect() areaBounds = area.boundingRect() offsetX = max(0, areaBounds.x() - bounds.x()) offsetY = max(0, areaBounds.y() - bounds.y()) copied = TileLayer(QString(), 0, 0, bounds.width(), bounds.height()) for rect in area.rects(): for x in range(rect.left(), rect.right() + 1): for y in range(rect.top(), rect.bottom() + 1): copied.setCell(x - areaBounds.x() + offsetX, y - areaBounds.y() + offsetY, self.cellAt(x, y)) return copied elif l == 4: x, y, width, height = args return self.copy(QRegion(x, y, width, height)) ## # Merges the given \a layer onto this layer at position \a pos. Parts that # fall outside of this layer will be lost and empty tiles in the given # layer will have no effect. ## def merge(self, pos, layer): # Determine the overlapping area area = QRect(pos, QSize(layer.width(), layer.height())) area &= QRect(0, 0, self.width(), self.height()) for y in range(area.top(), area.bottom() + 1): for x in range(area.left(), area.right() + 1): cell = layer.cellAt(x - pos.x(), y - pos.y()) if (not cell.isEmpty()): self.setCell(x, y, cell) ## # Removes all cells in the specified region. ## def erase(self, area): emptyCell = Cell() for rect in area.rects(): for x in range(rect.left(), rect.right() + 1): for y in range(rect.top(), rect.bottom() + 1): self.setCell(x, y, emptyCell) ## # Sets the cells starting at the given position to the cells in the given # \a tileLayer. Parts that fall outside of this layer will be ignored. # # When a \a mask is given, only cells that fall within this mask are set. # The mask is applied in local coordinates. ## def setCells(self, x, y, layer, mask=QRegion()): # Determine the overlapping area area = QRegion(QRect(x, y, layer.width(), layer.height())) area &= QRect(0, 0, self.width(), self.height()) if (not mask.isEmpty()): area &= mask for rect in area.rects(): for _x in range(rect.left(), rect.right() + 1): for _y in range(rect.top(), rect.bottom() + 1): self.setCell(_x, _y, layer.cellAt(_x - x, _y - y)) ## # Flip this tile layer in the given \a direction. Direction must be # horizontal or vertical. This doesn't change the dimensions of the # tile layer. ## def flip(self, direction): newGrid = QVector() for i in range(self.mWidth * self.mHeight): newGrid.append(Cell()) for y in range(self.mHeight): for x in range(self.mWidth): dest = newGrid[x + y * self.mWidth] if (direction == FlipDirection.FlipHorizontally): source = self.cellAt(self.mWidth - x - 1, y) dest = source dest.flippedHorizontally = not source.flippedHorizontally elif (direction == FlipDirection.FlipVertically): source = self.cellAt(x, self.mHeight - y - 1) dest = source dest.flippedVertically = not source.flippedVertically self.mGrid = newGrid ## # Rotate this tile layer by 90 degrees left or right. The tile positions # are rotated within the layer, and the tiles themselves are rotated. The # dimensions of the tile layer are swapped. ## def rotate(self, direction): rotateRightMask = [5, 4, 1, 0, 7, 6, 3, 2] rotateLeftMask = [3, 2, 7, 6, 1, 0, 5, 4] if direction == RotateDirection.RotateRight: rotateMask = rotateRightMask else: rotateMask = rotateLeftMask newWidth = self.mHeight newHeight = self.mWidth newGrid = QVector(newWidth * newHeight) for y in range(self.mHeight): for x in range(self.mWidth): source = self.cellAt(x, y) dest = source mask = (dest.flippedHorizontally << 2) | ( dest.flippedVertically << 1) | ( dest.flippedAntiDiagonally << 0) mask = rotateMask[mask] dest.flippedHorizontally = (mask & 4) != 0 dest.flippedVertically = (mask & 2) != 0 dest.flippedAntiDiagonally = (mask & 1) != 0 if (direction == RotateDirection.RotateRight): newGrid[x * newWidth + (self.mHeight - y - 1)] = dest else: newGrid[(self.mWidth - x - 1) * newWidth + y] = dest t = self.mMaxTileSize.width() self.mMaxTileSize.setWidth(self.mMaxTileSize.height()) self.mMaxTileSize.setHeight(t) self.mWidth = newWidth self.mHeight = newHeight self.mGrid = newGrid ## # Computes and returns the set of tilesets used by this tile layer. ## def usedTilesets(self): tilesets = QSet() i = 0 while (i < self.mGrid.size()): tile = self.mGrid.at(i).tile if tile: tilesets.insert(tile.tileset()) i += 1 return tilesets ## # Returns whether this tile layer has any cell for which the given # \a condition returns True. ## def hasCell(self, condition): i = 0 for cell in self.mGrid: if (condition(cell)): return True i += 1 return False ## # Returns whether this tile layer is referencing the given tileset. ## def referencesTileset(self, tileset): i = 0 while (i < self.mGrid.size()): tile = self.mGrid.at(i).tile if (tile and tile.tileset() == tileset): return True i += 1 return False ## # Removes all references to the given tileset. This sets all tiles on this # layer that are from the given tileset to null. ## def removeReferencesToTileset(self, tileset): i = 0 while (i < self.mGrid.size()): tile = self.mGrid.at(i).tile if (tile and tile.tileset() == tileset): self.mGrid.replace(i, Cell()) i += 1 ## # Replaces all tiles from \a oldTileset with tiles from \a newTileset. ## def replaceReferencesToTileset(self, oldTileset, newTileset): i = 0 while (i < self.mGrid.size()): tile = self.mGrid.at(i).tile if (tile and tile.tileset() == oldTileset): self.mGrid[i].tile = newTileset.tileAt(tile.id()) i += 1 ## # Resizes this tile layer to \a size, while shifting all tiles by # \a offset. ## def resize(self, size, offset): if (self.size() == size and offset.isNull()): return newGrid = QVector() for i in range(size.width() * size.height()): newGrid.append(Cell()) # Copy over the preserved part startX = max(0, -offset.x()) startY = max(0, -offset.y()) endX = min(self.mWidth, size.width() - offset.x()) endY = min(self.mHeight, size.height() - offset.y()) for y in range(startY, endY): for x in range(startX, endX): index = x + offset.x() + (y + offset.y()) * size.width() newGrid[index] = self.cellAt(x, y) self.mGrid = newGrid self.setSize(size) ## # Offsets the tiles in this layer within \a bounds by \a offset, # and optionally wraps them. # # \sa ObjectGroup.offset() ## def offsetTiles(self, offset, bounds, wrapX, wrapY): newGrid = QVector() for i in range(self.mWidth * self.mHeight): newGrid.append(Cell()) for y in range(self.mHeight): for x in range(self.mWidth): # Skip out of bounds tiles if (not bounds.contains(x, y)): newGrid[x + y * self.mWidth] = self.cellAt(x, y) continue # Get position to pull tile value from oldX = x - offset.x() oldY = y - offset.y() # Wrap x value that will be pulled from if (wrapX and bounds.width() > 0): while oldX < bounds.left(): oldX += bounds.width() while oldX > bounds.right(): oldX -= bounds.width() # Wrap y value that will be pulled from if (wrapY and bounds.height() > 0): while oldY < bounds.top(): oldY += bounds.height() while oldY > bounds.bottom(): oldY -= bounds.height() # Set the new tile if (self.contains(oldX, oldY) and bounds.contains(oldX, oldY)): newGrid[x + y * self.mWidth] = self.cellAt(oldX, oldY) else: newGrid[x + y * self.mWidth] = Cell() self.mGrid = newGrid def canMergeWith(self, other): return other.isTileLayer() def mergedWith(self, other): o = other unitedBounds = self.bounds().united(o.bounds()) offset = self.position() - unitedBounds.topLeft() merged = self.clone() merged.resize(unitedBounds.size(), offset) merged.merge(o.position() - unitedBounds.topLeft(), o) return merged ## # Returns the region where this tile layer and the given tile layer # are different. The relative positions of the layers are taken into # account. The returned region is relative to this tile layer. ## def computeDiffRegion(self, other): ret = QRegion() dx = other.x() - self.mX dy = other.y() - self.mY r = QRect(0, 0, self.width(), self.height()) r &= QRect(dx, dy, other.width(), other.height()) for y in range(r.top(), r.bottom() + 1): for x in range(r.left(), r.right() + 1): if (self.cellAt(x, y) != other.cellAt(x - dx, y - dy)): rangeStart = x while (x <= r.right() and self.cellAt(x, y) != other.cellAt(x - dx, y - dy)): x += 1 rangeEnd = x ret += QRect(rangeStart, y, rangeEnd - rangeStart, 1) return ret ## # Returns True if all tiles in the layer are empty. ## def isEmpty(self): i = 0 while (i < self.mGrid.size()): if (not self.mGrid.at(i).isEmpty()): return False i += 1 return True ## # Returns a duplicate of this TileLayer. # # \sa Layer.clone() ## def clone(self): return self.initializeClone( TileLayer(self.mName, self.mX, self.mY, self.mWidth, self.mHeight)) def begin(self): return self.mGrid.begin() def end(self): return self.mGrid.end() def initializeClone(self, clone): super().initializeClone(clone) clone.mGrid = self.mGrid clone.mMaxTileSize = self.mMaxTileSize clone.mOffsetMargins = self.mOffsetMargins return clone
def setMaximumSize(self, size: QSize, **kwargs): super().setMaximumSize(size) self._base_width = size.width() self._base_height = size.height()
def paintEvent(self, _event: QPaintEvent): if not self.crop or not self.resolution: return painter = QPainter(self) # Keep a backup of the transform and create a new one transform = painter.worldTransform() # Set scaling transform transform = transform.scale(self.width() / self.resolution.width(), self.height() / self.resolution.height()) # Compute the transform to flip the coordinate system on the x axis transform_flip = QTransform() if self.flip_x: transform_flip = transform_flip.translate(self.resolution.width(), 0.0) transform_flip = transform_flip.scale(-1.0, 1.0) # Small helper for tuple to QPoint def toqp(point): return QPoint(point[0], point[1]) # Starting from here we care about AA painter.setRenderHint(QPainter.Antialiasing) # Draw all the QR code results for res in self.results: painter.setWorldTransform(transform_flip * transform, False) # Draw lines between all of the QR code points pen = QPen(self.qr_outline_pen) if res in self.validator_results.result_colors: pen.setColor(self.validator_results.result_colors[res]) painter.setPen(pen) num_points = len(res.points) for i in range(0, num_points): i_n = i + 1 line_from = toqp(res.points[i]) line_from += self.crop.topLeft() line_to = toqp(res.points[i_n] if i_n < num_points else res.points[0]) line_to += self.crop.topLeft() painter.drawLine(line_from, line_to) # Draw the QR code data # Note that we reset the world transform to only the scaled transform # because otherwise the text could be flipped. We only use transform_flip # to map the center point of the result. painter.setWorldTransform(transform, False) font_metrics = painter.fontMetrics() data_metrics = QSize(font_metrics.horizontalAdvance(res.data), font_metrics.capHeight()) center_pos = toqp(res.center) center_pos += self.crop.topLeft() center_pos = transform_flip.map(center_pos) text_offset = QPoint(data_metrics.width(), data_metrics.height()) text_offset = text_offset / 2 text_offset.setX(-text_offset.x()) center_pos += text_offset padding = self.BG_RECT_PADDING bg_rect_pos = center_pos - QPoint(padding, data_metrics.height() + padding) bg_rect_size = data_metrics + (QSize(padding, padding) * 2) bg_rect = QRect(bg_rect_pos, bg_rect_size) bg_rect_path = QPainterPath() radius = self.BG_RECT_CORNER_RADIUS bg_rect_path.addRoundedRect(QRectF(bg_rect), radius, radius, Qt.AbsoluteSize) painter.setPen(self.bg_rect_pen) painter.fillPath(bg_rect_path, self.bg_rect_fill) painter.drawPath(bg_rect_path) painter.setPen(self.text_pen) painter.drawText(center_pos, res.data)
class Window(QWidget): def __init__(self, parent=None): super(QWidget, self).__init__(parent) self.m_iconSize = QSize(64, 64) self.m_scene = QGraphicsScene() self.m_ui = Ui_Form() self.m_ui.setupUi(self) self.m_ui.easingCurvePicker.setIconSize(self.m_iconSize) self.m_ui.easingCurvePicker.setMinimumHeight(self.m_iconSize.height() + 50) self.m_ui.buttonGroup.setId(self.m_ui.lineRadio, 0) self.m_ui.buttonGroup.setId(self.m_ui.circleRadio, 1) dummy = QEasingCurve() self.m_ui.periodSpinBox.setValue(dummy.period()) self.m_ui.amplitudeSpinBox.setValue(dummy.amplitude()) self.m_ui.overshootSpinBox.setValue(dummy.overshoot()) self.m_ui.easingCurvePicker.currentRowChanged.connect(self.curveChanged) self.m_ui.buttonGroup.buttonClicked[int].connect(self.pathChanged) self.m_ui.periodSpinBox.valueChanged.connect(self.periodChanged) self.m_ui.amplitudeSpinBox.valueChanged.connect(self.amplitudeChanged) self.m_ui.overshootSpinBox.valueChanged.connect(self.overshootChanged) self.createCurveIcons() pix = QPixmap(':/images/qt-logo.png') self.m_item = PixmapItem(pix) self.m_scene.addItem(self.m_item.pixmap_item) self.m_ui.graphicsView.setScene(self.m_scene) self.m_anim = Animation(self.m_item, b'pos') self.m_anim.setEasingCurve(QEasingCurve.OutBounce) self.m_ui.easingCurvePicker.setCurrentRow(int(QEasingCurve.OutBounce)) self.startAnimation() def createCurveIcons(self): pix = QPixmap(self.m_iconSize) painter = QPainter() gradient = QLinearGradient(0, 0, 0, self.m_iconSize.height()) gradient.setColorAt(0.0, QColor(240, 240, 240)) gradient.setColorAt(1.0, QColor(224, 224, 224)) brush = QBrush(gradient) # The original C++ code uses undocumented calls to get the names of the # different curve types. We do the Python equivalant (but without # cheating). curve_types = [(n, c) for n, c in QEasingCurve.__dict__.items() if isinstance(c, QEasingCurve.Type) and c != QEasingCurve.Custom] curve_types.sort(key=lambda ct: ct[1]) painter.begin(pix) for curve_name, curve_type in curve_types: painter.fillRect(QRect(QPoint(0, 0), self.m_iconSize), brush) curve = QEasingCurve(curve_type) if curve_type == QEasingCurve.BezierSpline: curve.addCubicBezierSegment(QPointF(0.4, 0.1), QPointF(0.6, 0.9), QPointF(1.0, 1.0)) elif curve_type == QEasingCurve.TCBSpline: curve.addTCBSegment(QPointF(0.0, 0.0), 0, 0, 0) curve.addTCBSegment(QPointF(0.3, 0.4), 0.2, 1, -0.2) curve.addTCBSegment(QPointF(0.7, 0.6), -0.2, 1, 0.2) curve.addTCBSegment(QPointF(1.0, 1.0), 0, 0, 0) painter.setPen(QColor(0, 0, 255, 64)) xAxis = self.m_iconSize.height() / 1.5 yAxis = self.m_iconSize.width() / 3.0 painter.drawLine(0, xAxis, self.m_iconSize.width(), xAxis) painter.drawLine(yAxis, 0, yAxis, self.m_iconSize.height()) curveScale = self.m_iconSize.height() / 2.0; painter.setPen(Qt.NoPen) # Start point. painter.setBrush(Qt.red) start = QPoint(yAxis, xAxis - curveScale * curve.valueForProgress(0)) painter.drawRect(start.x() - 1, start.y() - 1, 3, 3) # End point. painter.setBrush(Qt.blue) end = QPoint(yAxis + curveScale, xAxis - curveScale * curve.valueForProgress(1)) painter.drawRect(end.x() - 1, end.y() - 1, 3, 3) curvePath = QPainterPath() curvePath.moveTo(QPointF(start)) t = 0.0 while t <= 1.0: to = QPointF(yAxis + curveScale * t, xAxis - curveScale * curve.valueForProgress(t)) curvePath.lineTo(to) t += 1.0 / curveScale painter.setRenderHint(QPainter.Antialiasing, True) painter.strokePath(curvePath, QColor(32, 32, 32)) painter.setRenderHint(QPainter.Antialiasing, False) item = QListWidgetItem() item.setIcon(QIcon(pix)) item.setText(curve_name) self.m_ui.easingCurvePicker.addItem(item) painter.end() def startAnimation(self): self.m_anim.setStartValue(QPointF(0, 0)) self.m_anim.setEndValue(QPointF(100, 100)) self.m_anim.setDuration(2000) self.m_anim.setLoopCount(-1) self.m_anim.start() def curveChanged(self, row): curveType = QEasingCurve.Type(row) self.m_anim.setEasingCurve(curveType) self.m_anim.setCurrentTime(0) isElastic = (curveType >= QEasingCurve.InElastic and curveType <= QEasingCurve.OutInElastic) isBounce = (curveType >= QEasingCurve.InBounce and curveType <= QEasingCurve.OutInBounce) self.m_ui.periodSpinBox.setEnabled(isElastic) self.m_ui.amplitudeSpinBox.setEnabled(isElastic or isBounce) self.m_ui.overshootSpinBox.setEnabled(curveType >= QEasingCurve.InBack and curveType <= QEasingCurve.OutInBack) def pathChanged(self, index): self.m_anim.setPathType(index) def periodChanged(self, value): curve = self.m_anim.easingCurve() curve.setPeriod(value) self.m_anim.setEasingCurve(curve) def amplitudeChanged(self, value): curve = self.m_anim.easingCurve() curve.setAmplitude(value) self.m_anim.setEasingCurve(curve) def overshootChanged(self, value): curve = self.m_anim.easingCurve() curve.setOvershoot(value) self.m_anim.setEasingCurve(curve)
def setCanvas(self, size: QSize, color: QColor = QColor(255, 255, 255, 0)): self.setGeometry(0, 0, size.width(), size.height()) self.setPixmap(QPixmap(size.width(), size.height())) self.pixmap().fill(color)
def wheelEvent(self, a0: QtGui.QWheelEvent): if self.ctrlPressed: global IMAGE_SIZE deltaY = a0.angleDelta().y()/12 IMAGE_SIZE = QSize(IMAGE_SIZE.width()+deltaY, IMAGE_SIZE.height()+deltaY) self.images_listWidget.setIconSize(IMAGE_SIZE)
class Plugin(RevealerPlugin): MAX_PLAINTEXT_LEN = 189 # chars def __init__(self, parent, config, name): RevealerPlugin.__init__(self, parent, config, name) self.base_dir = os.path.join(config.actilectrum_path(), 'revealer') if self.config.get('calibration_h') is None: self.config.set_key('calibration_h', 0) if self.config.get('calibration_v') is None: self.config.set_key('calibration_v', 0) self.calibration_h = self.config.get('calibration_h') self.calibration_v = self.config.get('calibration_v') self.f_size = QSize(1014 * 2, 642 * 2) self.abstand_h = 21 self.abstand_v = 34 self.calibration_noise = int('10' * 128) self.rawnoise = False make_dir(self.base_dir) self.extension = False @hook def create_status_bar(self, parent): b = StatusBarButton(read_QIcon('revealer.png'), "Revealer " + _("secret backup utility"), partial(self.setup_dialog, parent)) parent.addPermanentWidget(b) def requires_settings(self): return True def settings_widget(self, window): return EnterButton(_('Printer Calibration'), partial(self.calibration_dialog, window)) def password_dialog(self, msg=None, parent=None): from actilectrum.gui.qt.password_dialog import PasswordDialog parent = parent or self d = PasswordDialog(parent, msg) return d.run() def get_seed(self): password = None if self.wallet.has_keystore_encryption(): password = self.password_dialog(parent=self.d.parent()) if not password: raise UserCancelled() keystore = self.wallet.get_keystore() if not keystore or not keystore.has_seed(): return self.extension = bool(keystore.get_passphrase(password)) return keystore.get_seed(password) def setup_dialog(self, window): self.wallet = window.parent().wallet self.update_wallet_name(self.wallet) self.user_input = False self.d = WindowModalDialog(window, "Setup Dialog") self.d.setMinimumWidth(500) self.d.setMinimumHeight(210) self.d.setMaximumHeight(320) self.d.setContentsMargins(11, 11, 1, 1) self.hbox = QHBoxLayout(self.d) vbox = QVBoxLayout() logo = QLabel() self.hbox.addWidget(logo) logo.setPixmap(QPixmap(icon_path('revealer.png'))) logo.setAlignment(Qt.AlignLeft) self.hbox.addSpacing(16) vbox.addWidget( WWLabel( "<b>" + _("Revealer Secret Backup Plugin") + "</b><br>" + _("To encrypt your backup, first we need to load some noise.") + "<br/>")) vbox.addSpacing(7) bcreate = QPushButton(_("Create a new Revealer")) bcreate.setMaximumWidth(181) bcreate.setDefault(True) vbox.addWidget(bcreate, Qt.AlignCenter) self.load_noise = ScanQRTextEdit() self.load_noise.setTabChangesFocus(True) self.load_noise.textChanged.connect(self.on_edit) self.load_noise.setMaximumHeight(33) self.hbox.addLayout(vbox) vbox.addWidget( WWLabel( _("or type an existing revealer code below and click 'next':")) ) vbox.addWidget(self.load_noise) vbox.addSpacing(3) self.next_button = QPushButton(_("Next"), self.d) self.next_button.setEnabled(False) vbox.addLayout(Buttons(self.next_button)) self.next_button.clicked.connect(self.d.close) self.next_button.clicked.connect( partial(self.cypherseed_dialog, window)) vbox.addWidget( QLabel("<b>" + _("Warning") + "</b>: " + _( "Each revealer should be used only once." ) + "<br>" + _( "more information at <a href=\"https://revealer.cc/faq\">https://revealer.cc/faq</a>" ))) def mk_digital(): try: self.make_digital(self.d) except Exception: self.logger.exception('') else: self.cypherseed_dialog(window) bcreate.clicked.connect(mk_digital) return bool(self.d.exec_()) def get_noise(self): text = self.load_noise.text() return ''.join(text.split()).lower() def on_edit(self): txt = self.get_noise() versioned_seed = self.get_versioned_seed_from_user_input(txt) if versioned_seed: self.versioned_seed = versioned_seed self.user_input = bool(versioned_seed) self.next_button.setEnabled(bool(versioned_seed)) def make_digital(self, dialog): self.make_rawnoise(True) self.bdone(dialog) self.d.close() def get_path_to_revealer_file(self, ext: str = '') -> str: version = self.versioned_seed.version code_id = self.versioned_seed.checksum filename = self.filename_prefix + version + "_" + code_id + ext path = os.path.join(self.base_dir, filename) return os.path.normcase(os.path.abspath(path)) def get_path_to_calibration_file(self): path = os.path.join(self.base_dir, 'calibration.pdf') return os.path.normcase(os.path.abspath(path)) def bcrypt(self, dialog): self.rawnoise = False version = self.versioned_seed.version code_id = self.versioned_seed.checksum dialog.show_message(''.join([ _("{} encrypted for Revealer {}_{} saved as PNG and PDF at: "). format(self.was, version, code_id), "<b>", self.get_path_to_revealer_file(), "</b>", "<br/>", "<br/>", "<b>", _("Always check your backups.") ]), rich_text=True) dialog.close() def ext_warning(self, dialog): dialog.show_message(''.join([ "<b>", _("Warning"), ": </b>", _("your seed extension will <b>not</b> be included in the encrypted backup." ) ]), rich_text=True) dialog.close() def bdone(self, dialog): version = self.versioned_seed.version code_id = self.versioned_seed.checksum dialog.show_message(''.join([ _("Digital Revealer ({}_{}) saved as PNG and PDF at:").format( version, code_id), "<br/>", "<b>", self.get_path_to_revealer_file(), '</b>' ]), rich_text=True) def customtxt_limits(self): txt = self.text.text() self.max_chars.setVisible(False) self.char_count.setText(f"({len(txt)}/{self.MAX_PLAINTEXT_LEN})") if len(txt) > 0: self.ctext.setEnabled(True) if len(txt) > self.MAX_PLAINTEXT_LEN: self.text.setPlainText(txt[:self.MAX_PLAINTEXT_LEN]) self.max_chars.setVisible(True) def t(self): self.txt = self.text.text() self.seed_img(is_seed=False) def warn_old_revealer(self): if self.versioned_seed.version == '0': link = "https://revealer.cc/revealer-warning-and-upgrade/" self.d.show_warning(( "<b>{warning}: </b>{ver0}<br>" "{url}<br>" "{risk}" ).format( warning=_("Warning"), ver0= _("Revealers starting with 0 are not secure due to a vulnerability." ), url=_("More info at: {}").format( f'<a href="{link}">{link}</a>'), risk=_("Proceed at your own risk.")), rich_text=True) def cypherseed_dialog(self, window): self.warn_old_revealer() d = WindowModalDialog(window, "Encryption Dialog") d.setMinimumWidth(500) d.setMinimumHeight(210) d.setMaximumHeight(450) d.setContentsMargins(11, 11, 1, 1) self.c_dialog = d hbox = QHBoxLayout(d) self.vbox = QVBoxLayout() logo = QLabel() hbox.addWidget(logo) logo.setPixmap(QPixmap(icon_path('revealer.png'))) logo.setAlignment(Qt.AlignLeft) hbox.addSpacing(16) self.vbox.addWidget( WWLabel("<b>" + _("Revealer Secret Backup Plugin") + "</b><br>" + _("Ready to encrypt for revealer {}").format( self.versioned_seed.version + '_' + self.versioned_seed.checksum))) self.vbox.addSpacing(11) hbox.addLayout(self.vbox) grid = QGridLayout() self.vbox.addLayout(grid) cprint = QPushButton(_("Encrypt {}'s seed").format(self.wallet_name)) cprint.setMaximumWidth(250) cprint.clicked.connect(partial(self.seed_img, True)) self.vbox.addWidget(cprint) self.vbox.addSpacing(1) self.vbox.addWidget( WWLabel("<b>" + _("OR") + "</b> " + _("type a custom alphanumerical secret below:"))) self.text = ScanQRTextEdit() self.text.setTabChangesFocus(True) self.text.setMaximumHeight(70) self.text.textChanged.connect(self.customtxt_limits) self.vbox.addWidget(self.text) self.char_count = WWLabel("") self.char_count.setAlignment(Qt.AlignRight) self.vbox.addWidget(self.char_count) self.max_chars = WWLabel( "<font color='red'>" + _("This version supports a maximum of {} characters.").format( self.MAX_PLAINTEXT_LEN) + "</font>") self.vbox.addWidget(self.max_chars) self.max_chars.setVisible(False) self.ctext = QPushButton(_("Encrypt custom secret")) self.ctext.clicked.connect(self.t) self.vbox.addWidget(self.ctext) self.ctext.setEnabled(False) self.vbox.addSpacing(11) self.vbox.addLayout(Buttons(CloseButton(d))) return bool(d.exec_()) def update_wallet_name(self, name): self.wallet_name = str(name) def seed_img(self, is_seed=True): if is_seed: try: cseed = self.get_seed() except UserCancelled: return except InvalidPassword as e: self.d.show_error(str(e)) return if not cseed: self.d.show_message(_("This wallet has no seed")) return txt = cseed.upper() else: txt = self.txt.upper() img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono) bitmap = QBitmap.fromImage(img, Qt.MonoOnly) bitmap.fill(Qt.white) painter = QPainter() painter.begin(bitmap) QFontDatabase.addApplicationFont( os.path.join(os.path.dirname(__file__), 'SourceSansPro-Bold.otf')) if len(txt) < 102: fontsize = 15 linespace = 15 max_letters = 17 max_lines = 6 max_words = 3 else: fontsize = 12 linespace = 10 max_letters = 21 max_lines = 9 max_words = int(max_letters / 4) font = QFont('Source Sans Pro', fontsize, QFont.Bold) font.setLetterSpacing(QFont.PercentageSpacing, 100) font.setPixelSize(fontsize) painter.setFont(font) seed_array = txt.split(' ') for n in range(max_lines): nwords = max_words temp_seed = seed_array[:nwords] while len(' '.join(map(str, temp_seed))) > max_letters: nwords = nwords - 1 temp_seed = seed_array[:nwords] painter.drawText( QRect(0, linespace * n, self.SIZE[0], self.SIZE[1]), Qt.AlignHCenter, ' '.join(map(str, temp_seed))) del seed_array[:nwords] painter.end() img = bitmap.toImage() if (self.rawnoise == False): self.make_rawnoise() self.make_cypherseed(img, self.rawnoise, False, is_seed) return img def make_rawnoise(self, create_revealer=False): if not self.user_input: self.versioned_seed = self.gen_random_versioned_seed() assert self.versioned_seed w, h = self.SIZE rawnoise = QImage(w, h, QImage.Format_Mono) noise_map = self.get_noise_map(self.versioned_seed) for (x, y), pixel in noise_map.items(): rawnoise.setPixel(x, y, pixel) self.rawnoise = rawnoise if create_revealer: self.make_revealer() def make_calnoise(self): random.seed(self.calibration_noise) w, h = self.SIZE rawnoise = QImage(w, h, QImage.Format_Mono) for x in range(w): for y in range(h): rawnoise.setPixel(x, y, random.randint(0, 1)) self.calnoise = self.pixelcode_2x2(rawnoise) def make_revealer(self): revealer = self.pixelcode_2x2(self.rawnoise) revealer.invertPixels() revealer = QBitmap.fromImage(revealer) revealer = revealer.scaled(self.f_size, Qt.KeepAspectRatio) revealer = self.overlay_marks(revealer) self.filename_prefix = 'revealer_' revealer.save(self.get_path_to_revealer_file('.png')) self.toPdf(QImage(revealer)) QDesktopServices.openUrl( QUrl.fromLocalFile(self.get_path_to_revealer_file('.pdf'))) def make_cypherseed(self, img, rawnoise, calibration=False, is_seed=True): img = img.convertToFormat(QImage.Format_Mono) p = QPainter() p.begin(img) p.setCompositionMode(26) #xor p.drawImage(0, 0, rawnoise) p.end() cypherseed = self.pixelcode_2x2(img) cypherseed = QBitmap.fromImage(cypherseed) cypherseed = cypherseed.scaled(self.f_size, Qt.KeepAspectRatio) cypherseed = self.overlay_marks(cypherseed, True, calibration) if not is_seed: self.filename_prefix = 'custom_secret_' self.was = _('Custom secret') else: self.filename_prefix = self.wallet_name + '_seed_' self.was = self.wallet_name + ' ' + _('seed') if self.extension: self.ext_warning(self.c_dialog) if not calibration: self.toPdf(QImage(cypherseed)) QDesktopServices.openUrl( QUrl.fromLocalFile(self.get_path_to_revealer_file('.pdf'))) cypherseed.save(self.get_path_to_revealer_file('.png')) self.bcrypt(self.c_dialog) return cypherseed def calibration(self): img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono) bitmap = QBitmap.fromImage(img, Qt.MonoOnly) bitmap.fill(Qt.black) self.make_calnoise() img = self.overlay_marks( self.calnoise.scaledToHeight(self.f_size.height()), False, True) self.calibration_pdf(img) QDesktopServices.openUrl( QUrl.fromLocalFile(self.get_path_to_calibration_file())) return img def toPdf(self, image): printer = QPrinter() printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter) printer.setResolution(600) printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(self.get_path_to_revealer_file('.pdf')) printer.setPageMargins(0, 0, 0, 0, 6) painter = QPainter() painter.begin(printer) delta_h = round(image.width() / self.abstand_v) delta_v = round(image.height() / self.abstand_h) size_h = 2028 + ((int(self.calibration_h) * 2028 / (2028 - (delta_h * 2) + int(self.calibration_h))) / 2) size_v = 1284 + ((int(self.calibration_v) * 1284 / (1284 - (delta_v * 2) + int(self.calibration_v))) / 2) image = image.scaled(size_h, size_v) painter.drawImage(553, 533, image) wpath = QPainterPath() wpath.addRoundedRect(QRectF(553, 533, size_h, size_v), 19, 19) painter.setPen(QPen(Qt.black, 1)) painter.drawPath(wpath) painter.end() def calibration_pdf(self, image): printer = QPrinter() printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter) printer.setResolution(600) printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(self.get_path_to_calibration_file()) printer.setPageMargins(0, 0, 0, 0, 6) painter = QPainter() painter.begin(printer) painter.drawImage(553, 533, image) font = QFont('Source Sans Pro', 10, QFont.Bold) painter.setFont(font) painter.drawText(254, 277, _("Calibration sheet")) font = QFont('Source Sans Pro', 7, QFont.Bold) painter.setFont(font) painter.drawText(600, 2077, _("Instructions:")) font = QFont('Source Sans Pro', 7, QFont.Normal) painter.setFont(font) painter.drawText( 700, 2177, _("1. Place this paper on a flat and well iluminated surface.")) painter.drawText( 700, 2277, _("2. Align your Revealer borderlines to the dashed lines on the top and left." )) painter.drawText( 700, 2377, _("3. Press slightly the Revealer against the paper and read the numbers that best " "match on the opposite sides. ")) painter.drawText(700, 2477, _("4. Type the numbers in the software")) painter.end() def pixelcode_2x2(self, img): result = QImage(img.width() * 2, img.height() * 2, QImage.Format_ARGB32) white = qRgba(255, 255, 255, 0) black = qRgba(0, 0, 0, 255) for x in range(img.width()): for y in range(img.height()): c = img.pixel(QPoint(x, y)) colors = QColor(c).getRgbF() if colors[0]: result.setPixel(x * 2 + 1, y * 2 + 1, black) result.setPixel(x * 2, y * 2 + 1, white) result.setPixel(x * 2 + 1, y * 2, white) result.setPixel(x * 2, y * 2, black) else: result.setPixel(x * 2 + 1, y * 2 + 1, white) result.setPixel(x * 2, y * 2 + 1, black) result.setPixel(x * 2 + 1, y * 2, black) result.setPixel(x * 2, y * 2, white) return result def overlay_marks(self, img, is_cseed=False, calibration_sheet=False): border_color = Qt.white base_img = QImage(self.f_size.width(), self.f_size.height(), QImage.Format_ARGB32) base_img.fill(border_color) img = QImage(img) painter = QPainter() painter.begin(base_img) total_distance_h = round(base_img.width() / self.abstand_v) dist_v = round(total_distance_h) / 2 dist_h = round(total_distance_h) / 2 img = img.scaledToWidth(base_img.width() - (2 * (total_distance_h))) painter.drawImage(total_distance_h, total_distance_h, img) #frame around image pen = QPen(Qt.black, 2) painter.setPen(pen) #horz painter.drawLine(0, total_distance_h, base_img.width(), total_distance_h) painter.drawLine(0, base_img.height() - (total_distance_h), base_img.width(), base_img.height() - (total_distance_h)) #vert painter.drawLine(total_distance_h, 0, total_distance_h, base_img.height()) painter.drawLine(base_img.width() - (total_distance_h), 0, base_img.width() - (total_distance_h), base_img.height()) #border around img border_thick = 6 Rpath = QPainterPath() Rpath.addRect( QRectF((total_distance_h) + (border_thick / 2), (total_distance_h) + (border_thick / 2), base_img.width() - ((total_distance_h) * 2) - ((border_thick) - 1), (base_img.height() - ((total_distance_h)) * 2) - ((border_thick) - 1))) pen = QPen(Qt.black, border_thick) pen.setJoinStyle(Qt.MiterJoin) painter.setPen(pen) painter.drawPath(Rpath) Bpath = QPainterPath() Bpath.addRect( QRectF((total_distance_h), (total_distance_h), base_img.width() - ((total_distance_h) * 2), (base_img.height() - ((total_distance_h)) * 2))) pen = QPen(Qt.black, 1) painter.setPen(pen) painter.drawPath(Bpath) pen = QPen(Qt.black, 1) painter.setPen(pen) painter.drawLine(0, base_img.height() / 2, total_distance_h, base_img.height() / 2) painter.drawLine(base_img.width() / 2, 0, base_img.width() / 2, total_distance_h) painter.drawLine(base_img.width() - total_distance_h, base_img.height() / 2, base_img.width(), base_img.height() / 2) painter.drawLine(base_img.width() / 2, base_img.height(), base_img.width() / 2, base_img.height() - total_distance_h) #print code f_size = 37 QFontDatabase.addApplicationFont( os.path.join(os.path.dirname(__file__), 'DejaVuSansMono-Bold.ttf')) font = QFont("DejaVu Sans Mono", f_size - 11, QFont.Bold) font.setPixelSize(35) painter.setFont(font) if not calibration_sheet: if is_cseed: #its a secret painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine)) painter.drawLine(0, dist_v, base_img.width(), dist_v) painter.drawLine(dist_h, 0, dist_h, base_img.height()) painter.drawLine(0, base_img.height() - dist_v, base_img.width(), base_img.height() - (dist_v)) painter.drawLine(base_img.width() - (dist_h), 0, base_img.width() - (dist_h), base_img.height()) painter.drawImage( ((total_distance_h)) + 11, ((total_distance_h)) + 11, QImage(icon_path('actilectrum.png')).scaledToWidth( 2.1 * (total_distance_h), Qt.SmoothTransformation)) painter.setPen(QPen(Qt.white, border_thick * 8)) painter.drawLine( base_img.width() - ((total_distance_h)) - (border_thick * 8) / 2 - (border_thick / 2) - 2, (base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2, base_img.width() - ((total_distance_h)) - (border_thick * 8) / 2 - (border_thick / 2) - 2 - 77, (base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2) painter.setPen(QColor(0, 0, 0, 255)) painter.drawText( QRect( 0, base_img.height() - 107, base_img.width() - total_distance_h - border_thick - 11, base_img.height() - total_distance_h - border_thick), Qt.AlignRight, self.versioned_seed.version + '_' + self.versioned_seed.checksum) painter.end() else: # revealer painter.setPen(QPen(border_color, 17)) painter.drawLine(0, dist_v, base_img.width(), dist_v) painter.drawLine(dist_h, 0, dist_h, base_img.height()) painter.drawLine(0, base_img.height() - dist_v, base_img.width(), base_img.height() - (dist_v)) painter.drawLine(base_img.width() - (dist_h), 0, base_img.width() - (dist_h), base_img.height()) painter.setPen(QPen(Qt.black, 2)) painter.drawLine(0, dist_v, base_img.width(), dist_v) painter.drawLine(dist_h, 0, dist_h, base_img.height()) painter.drawLine(0, base_img.height() - dist_v, base_img.width(), base_img.height() - (dist_v)) painter.drawLine(base_img.width() - (dist_h), 0, base_img.width() - (dist_h), base_img.height()) logo = QImage(icon_path('revealer_c.png')).scaledToWidth( 1.3 * (total_distance_h)) painter.drawImage((total_distance_h) + (border_thick), ((total_distance_h)) + (border_thick), logo, Qt.SmoothTransformation) #frame around logo painter.setPen(QPen(Qt.black, border_thick)) painter.drawLine( total_distance_h + border_thick, total_distance_h + logo.height() + 3 * (border_thick / 2), total_distance_h + logo.width() + border_thick, total_distance_h + logo.height() + 3 * (border_thick / 2)) painter.drawLine( logo.width() + total_distance_h + 3 * (border_thick / 2), total_distance_h + (border_thick), total_distance_h + logo.width() + 3 * (border_thick / 2), total_distance_h + logo.height() + (border_thick)) #frame around code/qr qr_size = 179 painter.drawLine((base_img.width() - ((total_distance_h)) - (border_thick / 2) - 2) - qr_size, (base_img.height() - ((total_distance_h))) - ((border_thick * 8)) - (border_thick / 2) - 2, (base_img.width() / 2 + (total_distance_h / 2) - border_thick - (border_thick * 8) / 2) - qr_size, (base_img.height() - ((total_distance_h))) - ((border_thick * 8)) - (border_thick / 2) - 2) painter.drawLine( (base_img.width() / 2 + (total_distance_h / 2) - border_thick - (border_thick * 8) / 2) - qr_size, (base_img.height() - ((total_distance_h))) - ((border_thick * 8)) - (border_thick / 2) - 2, base_img.width() / 2 + (total_distance_h / 2) - border_thick - (border_thick * 8) / 2 - qr_size, ((base_img.height() - ((total_distance_h))) - (border_thick / 2) - 2)) painter.setPen(QPen(Qt.white, border_thick * 8)) painter.drawLine( base_img.width() - ((total_distance_h)) - (border_thick * 8) / 2 - (border_thick / 2) - 2, (base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2, base_img.width() / 2 + (total_distance_h / 2) - border_thick - qr_size, (base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2) painter.setPen(QColor(0, 0, 0, 255)) painter.drawText( QRect(((base_img.width() / 2) + 21) - qr_size, base_img.height() - 107, base_img.width() - total_distance_h - border_thick - 93, base_img.height() - total_distance_h - border_thick), Qt.AlignLeft, self.versioned_seed.get_ui_string_version_plus_seed()) painter.drawText( QRect( 0, base_img.height() - 107, base_img.width() - total_distance_h - border_thick - 3 - qr_size, base_img.height() - total_distance_h - border_thick), Qt.AlignRight, self.versioned_seed.checksum) # draw qr code qr_qt = self.paintQR( self.versioned_seed.get_ui_string_version_plus_seed() + self.versioned_seed.checksum) target = QRectF(base_img.width() - 65 - qr_size, base_img.height() - 65 - qr_size, qr_size, qr_size) painter.drawImage(target, qr_qt) painter.setPen(QPen(Qt.black, 4)) painter.drawLine(base_img.width() - 65 - qr_size, base_img.height() - 65 - qr_size, base_img.width() - 65 - qr_size, (base_img.height() - ((total_distance_h))) - ((border_thick * 8)) - (border_thick / 2) - 4) painter.drawLine(base_img.width() - 65 - qr_size, base_img.height() - 65 - qr_size, base_img.width() - 65, base_img.height() - 65 - qr_size) painter.end() else: # calibration only painter.end() cal_img = QImage(self.f_size.width() + 100, self.f_size.height() + 100, QImage.Format_ARGB32) cal_img.fill(Qt.white) cal_painter = QPainter() cal_painter.begin(cal_img) cal_painter.drawImage(0, 0, base_img) #black lines in the middle of border top left only cal_painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine)) cal_painter.drawLine(0, dist_v, base_img.width(), dist_v) cal_painter.drawLine(dist_h, 0, dist_h, base_img.height()) pen = QPen(Qt.black, 2, Qt.DashDotDotLine) cal_painter.setPen(pen) n = 15 cal_painter.setFont(QFont("DejaVu Sans Mono", 21, QFont.Bold)) for x in range(-n, n): #lines on bottom (vertical calibration) cal_painter.drawLine((((base_img.width()) / (n * 2)) * (x)) + (base_img.width() / 2) - 13, x + 2 + base_img.height() - (dist_v), (((base_img.width()) / (n * 2)) * (x)) + (base_img.width() / 2) + 13, x + 2 + base_img.height() - (dist_v)) num_pos = 9 if x > 9: num_pos = 17 if x < 0: num_pos = 20 if x < -9: num_pos = 27 cal_painter.drawText((((base_img.width()) / (n * 2)) * (x)) + (base_img.width() / 2) - num_pos, 50 + base_img.height() - (dist_v), str(x)) #lines on the right (horizontal calibrations) cal_painter.drawLine( x + 2 + (base_img.width() - (dist_h)), ((base_img.height() / (2 * n)) * (x)) + (base_img.height() / n) + (base_img.height() / 2) - 13, x + 2 + (base_img.width() - (dist_h)), ((base_img.height() / (2 * n)) * (x)) + (base_img.height() / n) + (base_img.height() / 2) + 13) cal_painter.drawText(30 + (base_img.width() - (dist_h)), ((base_img.height() / (2 * n)) * (x)) + (base_img.height() / 2) + 13, str(x)) cal_painter.end() base_img = cal_img return base_img def paintQR(self, data): if not data: return qr = qrcode.QRCode() qr.add_data(data) matrix = qr.get_matrix() k = len(matrix) border_color = Qt.white base_img = QImage(k * 5, k * 5, QImage.Format_ARGB32) base_img.fill(border_color) qrpainter = QPainter() qrpainter.begin(base_img) boxsize = 5 size = k * boxsize left = (base_img.width() - size) / 2 top = (base_img.height() - size) / 2 qrpainter.setBrush(Qt.black) qrpainter.setPen(Qt.black) for r in range(k): for c in range(k): if matrix[r][c]: qrpainter.drawRect(left + c * boxsize, top + r * boxsize, boxsize - 1, boxsize - 1) qrpainter.end() return base_img def calibration_dialog(self, window): d = WindowModalDialog(window, _("Revealer - Printer calibration settings")) d.setMinimumSize(100, 200) vbox = QVBoxLayout(d) vbox.addWidget( QLabel(''.join([ "<br/>", _("If you have an old printer, or want optimal precision"), "<br/>", _("print the calibration pdf and follow the instructions "), "<br/>", "<br/>", ]))) self.calibration_h = self.config.get('calibration_h') self.calibration_v = self.config.get('calibration_v') cprint = QPushButton(_("Open calibration pdf")) cprint.clicked.connect(self.calibration) vbox.addWidget(cprint) vbox.addWidget(QLabel(_('Calibration values:'))) grid = QGridLayout() vbox.addLayout(grid) grid.addWidget(QLabel(_('Right side')), 0, 0) horizontal = QLineEdit() horizontal.setText(str(self.calibration_h)) grid.addWidget(horizontal, 0, 1) grid.addWidget(QLabel(_('Bottom')), 1, 0) vertical = QLineEdit() vertical.setText(str(self.calibration_v)) grid.addWidget(vertical, 1, 1) vbox.addStretch() vbox.addSpacing(13) vbox.addLayout(Buttons(CloseButton(d), OkButton(d))) if not d.exec_(): return self.calibration_h = int(Decimal(horizontal.text())) self.config.set_key('calibration_h', self.calibration_h) self.calibration_v = int(Decimal(vertical.text())) self.config.set_key('calibration_v', self.calibration_v)
def smallPic(self): global IMAGE_SIZE IMAGE_SIZE = QSize(IMAGE_SIZE.width() - 10, IMAGE_SIZE.height() - 10) self.images_listWidget.setIconSize(IMAGE_SIZE)
def paintEvent(self, event, *args): """ Custom paint event """ self.mutex.lock() # Paint custom frame image on QWidget painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.TextAntialiasing, True) # Fill background black painter.fillRect(event.rect(), self.palette().window()) if self.current_image: # DRAW FRAME # Calculate new frame image size, maintaining aspect ratio pixSize = self.current_image.size() pixSize.scale(event.rect().size(), Qt.KeepAspectRatio) # Scale image scaledPix = self.current_image.scaled(pixSize, Qt.KeepAspectRatio, Qt.SmoothTransformation) # Calculate center of QWidget and Draw image center = self.centeredViewport(self.width(), self.height()) painter.drawImage(center, scaledPix) if self.transforming_clip: # Draw transform handles on top of video preview # Get framerate fps = get_app().project.get(["fps"]) fps_float = float(fps["num"]) / float(fps["den"]) # Determine frame # of clip start_of_clip = round(float(self.transforming_clip.data["start"]) * fps_float) position_of_clip = (float(self.transforming_clip.data["position"]) * fps_float) + 1 playhead_position = float(get_app().window.preview_thread.current_frame) clip_frame_number = round(playhead_position - position_of_clip) + start_of_clip + 1 # Get properties of clip at current frame raw_properties = json.loads(self.transforming_clip_object.PropertiesJSON(clip_frame_number)) # Get size of current video player player_width = self.rect().width() player_height = self.rect().height() # Determine original size of clip's reader source_width = self.transforming_clip.data['reader']['width'] source_height = self.transforming_clip.data['reader']['height'] source_size = QSize(source_width, source_height) # Determine scale of clip scale = self.transforming_clip.data['scale'] if scale == openshot.SCALE_FIT: source_size.scale(player_width, player_height, Qt.KeepAspectRatio) elif scale == openshot.SCALE_STRETCH: source_size.scale(player_width, player_height, Qt.IgnoreAspectRatio) elif scale == openshot.SCALE_CROP: width_size = QSize(player_width, round(player_width / (float(source_width) / float(source_height)))) height_size = QSize(round(player_height / (float(source_height) / float(source_width))), player_height) if width_size.width() >= player_width and width_size.height() >= player_height: source_size.scale(width_size.width(), width_size.height(), Qt.KeepAspectRatio) else: source_size.scale(height_size.width(), height_size.height(), Qt.KeepAspectRatio) # Get new source width / height (after scaling mode applied) source_width = source_size.width() source_height = source_size.height() # Init X/Y x = 0.0 y = 0.0 # Get scaled source image size (scale_x, scale_y) sx = float(raw_properties.get('scale_x').get('value')) sy = float(raw_properties.get('scale_y').get('value')) scaled_source_width = source_width * sx scaled_source_height = source_height * sy # Determine gravity of clip gravity = self.transforming_clip.data['gravity'] if gravity == openshot.GRAVITY_TOP_LEFT: x += self.centeredViewport(self.width(), self.height()).x() # nudge right y += self.centeredViewport(self.width(), self.height()).y() # nudge down elif gravity == openshot.GRAVITY_TOP: x = (player_width - scaled_source_width) / 2.0 # center y += self.centeredViewport(self.width(), self.height()).y() # nudge down elif gravity == openshot.GRAVITY_TOP_RIGHT: x = player_width - scaled_source_width # right x -= self.centeredViewport(self.width(), self.height()).x() # nudge left y += self.centeredViewport(self.width(), self.height()).y() # nudge down elif gravity == openshot.GRAVITY_LEFT: y = (player_height - scaled_source_height) / 2.0 # center x += self.centeredViewport(self.width(), self.height()).x() # nudge right elif gravity == openshot.GRAVITY_CENTER: x = (player_width - scaled_source_width) / 2.0 # center y = (player_height - scaled_source_height) / 2.0 # center elif gravity == openshot.GRAVITY_RIGHT: x = player_width - scaled_source_width # right y = (player_height - scaled_source_height) / 2.0 # center x -= self.centeredViewport(self.width(), self.height()).x() # nudge left elif gravity == openshot.GRAVITY_BOTTOM_LEFT: y = (player_height - scaled_source_height) # bottom x += self.centeredViewport(self.width(), self.height()).x() # nudge right y -= self.centeredViewport(self.width(), self.height()).y() # nudge up elif gravity == openshot.GRAVITY_BOTTOM: x = (player_width - scaled_source_width) / 2.0 # center y = (player_height - scaled_source_height) # bottom y -= self.centeredViewport(self.width(), self.height()).y() # nudge up elif gravity == openshot.GRAVITY_BOTTOM_RIGHT: x = player_width - scaled_source_width # right y = (player_height - scaled_source_height) # bottom x -= self.centeredViewport(self.width(), self.height()).x() # nudge left y -= self.centeredViewport(self.width(), self.height()).y() # nudge up # Track gravity starting coordinate self.gravity_point = QPointF(x, y) # Scale to fit in widget final_size = QSize(source_width, source_height) # Adjust x,y for location x_offset = raw_properties.get('location_x').get('value') y_offset = raw_properties.get('location_y').get('value') x += (scaledPix.width() * x_offset) y += (scaledPix.height() * y_offset) self.transform = QTransform() # Apply translate/move if x or y: self.transform.translate(x, y) # Apply scale if sx or sy: self.transform.scale(sx, sy) # Apply shear shear_x = raw_properties.get('shear_x').get('value') shear_y = raw_properties.get('shear_y').get('value') if shear_x or shear_y: self.transform.shear(shear_x, shear_y) # Apply rotation rotation = raw_properties.get('rotation').get('value') if rotation: origin_x = x - self.centeredViewport(self.width(), self.height()).x() + (scaled_source_width / 2.0) origin_y = y - self.centeredViewport(self.width(), self.height()).y() + (scaled_source_height / 2.0) self.transform.translate(origin_x, origin_y) self.transform.rotate(rotation) self.transform.translate(-origin_x, -origin_y) # Apply transform painter.setTransform(self.transform) # Draw transform corners and cernter origin circle # Corner size cs = 6.0 os = 12.0 # Calculate 4 corners coordinates self.topLeftHandle = QRectF(0.0, 0.0, cs/sx, cs/sy) self.topRightHandle = QRectF(source_width - (cs/sx), 0, cs/sx, cs/sy) self.bottomLeftHandle = QRectF(0.0, source_height - (cs/sy), cs/sx, cs/sy) self.bottomRightHandle = QRectF(source_width - (cs/sx), source_height - (cs/sy), cs/sx, cs/sy) # Draw 4 corners painter.fillRect(self.topLeftHandle, QBrush(QColor("#53a0ed"))) painter.fillRect(self.topRightHandle, QBrush(QColor("#53a0ed"))) painter.fillRect(self.bottomLeftHandle, QBrush(QColor("#53a0ed"))) painter.fillRect(self.bottomRightHandle, QBrush(QColor("#53a0ed"))) # Calculate 4 side coordinates self.topHandle = QRectF(0.0 + (source_width / 2.0) - (cs/sx/2.0), 0, cs/sx, cs/sy) self.bottomHandle = QRectF(0.0 + (source_width / 2.0) - (cs/sx/2.0), source_height - (cs/sy), cs/sx, cs/sy) self.leftHandle = QRectF(0.0, (source_height / 2.0) - (cs/sy/2.0), cs/sx, cs/sy) self.rightHandle = QRectF(source_width - (cs/sx), (source_height / 2.0) - (cs/sy/2.0), cs/sx, cs/sy) # Draw 4 sides (centered) painter.fillRect(self.topHandle, QBrush(QColor("#53a0ed"))) painter.fillRect(self.bottomHandle, QBrush(QColor("#53a0ed"))) painter.fillRect(self.leftHandle, QBrush(QColor("#53a0ed"))) painter.fillRect(self.rightHandle, QBrush(QColor("#53a0ed"))) # Calculate center coordinate self.centerHandle = QRectF((source_width / 2.0) - (os/sx), (source_height / 2.0) - (os/sy), os/sx*2.0, os/sy*2.0) # Draw origin painter.setBrush(QColor(83, 160, 237, 122)) painter.setPen(Qt.NoPen) painter.drawEllipse(self.centerHandle) # Draw translucent rectangle self.clipRect = QRectF(0, 0, final_size.width(), final_size.height()) # Remove transform painter.resetTransform() # End painter painter.end() self.mutex.unlock()
class Settings(object): l = logging.getLogger('Settings') def __init__(self, fileName = 'shadow.ini'): self.fileName = fileName self.tzIdx = 0 self.mainWindowSize = QSize(1200, 700) self.mainWindowPos = QPoint(240, 200) def load(self): self.l.debug('Loading local settings from {}'.format(self.fileName)) config = configparser.ConfigParser() config.read(self.fileName) if config.has_section('common'): commonSettings = config['common'] self.tzIdx = int(commonSettings.get('tz', 0)) # load main window position and size if config.has_section('mainWindow'): windowSettings = config['mainWindow'] # read position pos = windowSettings.get('pos', '{},{}'.format(self.mainWindowPos.x(), self.mainWindowPos.y())).split(',') self.mainWindowPos = QPoint(int(pos[0]), int(pos[1])) # read size size = windowSettings.get('size', '{},{}'.format(self.mainWindowSize.width(), self.mainWindowSize.height())).split(',') self.mainWindowSize = QSize(int(size[0]), int(size[1])) def dump(self, channel): self.l.debug('Saving local settings ...') config = configparser.ConfigParser() config.add_section('common') config.set('common', 'tz', str(self.tzIdx)) config.add_section('mainWindow') config.set('mainWindow', 'pos', '{},{}'.format(self.mainWindowPos.x(), self.mainWindowPos.y())) config.set('mainWindow', 'size', '{},{}'.format(self.mainWindowSize.width(), self.mainWindowSize.height())) config.write(channel) def save(self): with open(self.fileName, 'w') as configfile: self.dump(configfile) def setMainWindowPos(self, pos): self.mainWindowPos = pos def setMainWindowSize(self, size): self.mainWindowSize = size def getMainWindowPos(self): return self.mainWindowPos def getMainWindowSize(self): return self.mainWindowSize def getTzIndex(self): return self.tzIdx def setTzIdx(self, idx): self.tzIdx = idx
def setHomeWindowSize(size: QSize): setPreference(HOME_WIDTH, size.width()) setPreference(HOME_HEIGHT, size.height())
class TextButton(DemoItem): BUTTON_WIDTH = 180 BUTTON_HEIGHT = 19 LEFT, RIGHT = range(2) SIDEBAR, PANEL, UP, DOWN = range(4) ON, OFF, HIGHLIGHT, DISABLED = range(4) def __init__(self, text, align=LEFT, userCode=0, parent=None, type=SIDEBAR): super(TextButton, self).__init__(parent) # Prevent a circular import. from menumanager import MenuManager self._menu_manager = MenuManager.instance() self.menuString = text self.buttonLabel = text self.alignment = align self.buttonType = type self.userCode = userCode self.scanAnim = None self.bgOn = None self.bgOff = None self.bgHighlight = None self.bgDisabled = None self.state = TextButton.OFF self.setAcceptHoverEvents(True) self.setCursor(Qt.PointingHandCursor) # Calculate the button size. if type in (TextButton.SIDEBAR, TextButton.PANEL): self.logicalSize = QSize(TextButton.BUTTON_WIDTH, TextButton.BUTTON_HEIGHT) else: self.logicalSize = QSize(int((TextButton.BUTTON_WIDTH / 2.0) - 5), int(TextButton.BUTTON_HEIGHT * 1.5)) self._prepared = False def setMenuString(self, menu): self.menuString = menu def prepare(self): if not self._prepared: self.setupHoverText() self.setupScanItem() self.setupButtonBg() self._prepared = True def boundingRect(self): return QRectF(0, 0, self.logicalSize.width(), self.logicalSize.height()) def setupHoverText(self): if not self.buttonLabel: return textItem = DemoTextItem(self.buttonLabel, Colors.buttonFont(), Colors.buttonText, -1, self) textItem.setZValue(self.zValue() + 2) textItem.setPos(16, 0) def setupScanItem(self): if Colors.useButtonBalls: scanItem = ScanItem(self) scanItem.setZValue(self.zValue() + 1) self.scanAnim = DemoItemAnimation(scanItem) x = 1.0 y = 1.5 stop = TextButton.BUTTON_WIDTH - scanItem.boundingRect().width() - x if self.alignment == TextButton.LEFT: self.scanAnim.setDuration(2500) self.scanAnim.setKeyValueAt(0.0, QPointF(x, y)) self.scanAnim.setKeyValueAt(0.5, QPointF(x, y)) self.scanAnim.setKeyValueAt(0.7, QPointF(stop, y)) self.scanAnim.setKeyValueAt(1.0, QPointF(x, y)) scanItem.setPos(QPointF(x, y)) else: self.scanAnim.setKeyValueAt(0.0, QPointF(stop, y)) self.scanAnim.setKeyValueAt(0.5, QPointF(x, y)) self.scanAnim.setKeyValueAt(1.0, QPointF(stop, y)) scanItem.setPos(QPointF(stop, y)) def setState(self, state): self.state = state self.bgOn.setRecursiveVisible(state == TextButton.ON) self.bgOff.setRecursiveVisible(state == TextButton.OFF) self.bgHighlight.setRecursiveVisible(state == TextButton.HIGHLIGHT) self.bgDisabled.setRecursiveVisible(state == TextButton.DISABLED) if state == TextButton.DISABLED: self.setCursor(Qt.ArrowCursor) else: self.setCursor(Qt.PointingHandCursor) def setupButtonBg(self): self.bgOn = ButtonBackground(self.buttonType, True, True, self.logicalSize, self) self.bgOff = ButtonBackground(self.buttonType, False, False, self.logicalSize, self) self.bgHighlight = ButtonBackground(self.buttonType, True, False, self.logicalSize, self) self.bgDisabled = ButtonBackground(self.buttonType, True, True, self.logicalSize, self) self.setState(TextButton.OFF) def hoverEnterEvent(self, event): if not self.isEnabled() or self.state == TextButton.DISABLED: return if self.state == TextButton.OFF: self.setState(TextButton.HIGHLIGHT) if Colors.noAnimations and Colors.useButtonBalls: # Wait a bit in the beginning to enhance the effect. We have # to do this here so that the adaption can be dynamic. self.scanAnim.setDuration(1000) self.scanAnim.setKeyValueAt(0.2, self.scanAnim.posAt(0)) if (self._menu_manager.window.fpsMedian > 10 or Colors.noAdapt or Colors.noTimerUpdate): if Colors.useButtonBalls: self.scanAnim.play(True, True) def hoverLeaveEvent(self, event): if self.state == TextButton.DISABLED: return self.setState(TextButton.OFF) if Colors.noAnimations and Colors.useButtonBalls: self.scanAnim.stop() def mousePressEvent(self, event): if self.state == TextButton.DISABLED: return if self.state == TextButton.HIGHLIGHT or self.state == TextButton.OFF: self.setState(TextButton.ON) def mouseReleaseEvent(self, event): if self.state == TextButton.ON: self.setState(TextButton.OFF) if self.isEnabled() and self.boundingRect().contains(event.pos()): self._menu_manager.itemSelected(self.userCode, self.menuString) def animationStarted(self, _): if self.state == TextButton.DISABLED: return self.setState(TextButton.OFF)
class FeatureTableWidgetHHeader(QTableWidgetItem): def __init__(self, sigma, window_size, name=None): QTableWidgetItem.__init__(self) # init # ------------------------------------------------ self.sigma = sigma self.window_size = window_size self.pixmapSize = QSize(61, 61) if not name: self.setNameAndBrush(self.sigma) else: self.setText(name) @property def brushSize(self): return int(3.0*self.sigma + 0.5)*2 + 1 def setNameAndBrush(self, sigma, color=Qt.black): self.sigma = sigma self.setText(u"σ={:.1f}px".format(self.sigma)) total_window = (1 + 2 * int(self.sigma * self.window_size + 0.5) ) self.setToolTip( "sigma = {:.1f} pixels, window diameter = {:.1f}".format(self.sigma, total_window) ) font = QFont() font.setPointSize(10) font.setBold(True) self.setFont(font) self.setForeground(color) pixmap = QPixmap(self.pixmapSize) pixmap.fill(Qt.transparent) painter = QPainter() painter.begin(pixmap) painter.setRenderHint(QPainter.Antialiasing, True) painter.setPen(color) brush = QBrush(color) painter.setBrush(brush) painter.drawEllipse(QRect(old_div(self.pixmapSize.width(),2) - old_div(self.brushSize,2), old_div(self.pixmapSize.height(),2) - old_div(self.brushSize,2), self.brushSize, self.brushSize)) painter.end() self.setIcon(QIcon(pixmap)) self.setTextAlignment(Qt.AlignVCenter) def setIconAndTextColor(self, color): self.setNameAndBrush(self.sigma, color)
class SquircleRenderer(QObject): # QOpenGLFunctions """docstring for SquircleRenderer""" def __init__(self, parent=None): super(SquircleRenderer, self).__init__(parent) self.m_t = 0.0 self.m_program = None self.m_viewportSize = QSize() def setT(self, t): self.m_t = t def setViewportSize(self, size): self.m_viewportSize = size def setWin(self, win): self.win = win ver = QOpenGLVersionProfile() ver.setVersion(2, 1) self.m_context = self.win.openglContext() self.gl = self.m_context.versionFunctions(ver) @pyqtSlot() def paint(self): if not self.m_program: self.gl.initializeOpenGLFunctions() self.m_program = QOpenGLShaderProgram(self) self.m_program.addShaderFromSourceCode( QOpenGLShader.Vertex, "attribute highp vec4 vertices;" "varying highp vec2 coords;" "void main() {" " gl_Position = vertices;" " coords = vertices.xy;" "}", ) self.m_program.addShaderFromSourceCode( QOpenGLShader.Fragment, "uniform lowp float t;" "varying highp vec2 coords;" "void main() {" " lowp float i = 1. - (pow(abs(coords.x), 4.) + pow(abs(coords.y), 4.));" " i = smoothstep(t - 0.8, t + 0.8, i);" " i = floor(i * 20.) / 20.;" " gl_FragColor = vec4(coords * .5 + .5, i, i);" "}", ) self.m_program.bindAttributeLocation("vertices", 0) self.m_program.link() self.m_program.bind() self.m_program.enableAttributeArray(0) values = [(-1, -1), (1, -1), (-1, 1), (1, 1)] self.m_program.setAttributeArray(0, values) self.m_program.setUniformValue("t", self.m_t) # print("DATA:",self.m_viewportSize.width(), self.m_viewportSize.height(), self.m_t)#, self.gl.glViewport) self.gl.glViewport(0, 0, self.m_viewportSize.width(), self.m_viewportSize.height()) self.gl.glDisable(self.gl.GL_DEPTH_TEST) self.gl.glClearColor(0, 0, 0, 1) self.gl.glClear(self.gl.GL_COLOR_BUFFER_BIT) self.gl.glEnable(self.gl.GL_BLEND) self.gl.glBlendFunc(self.gl.GL_SRC_ALPHA, self.gl.GL_ONE) self.gl.glDrawArrays(self.gl.GL_TRIANGLE_STRIP, 0, 4) self.m_program.disableAttributeArray(0) self.m_program.release()
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 TextButton(DemoItem): BUTTON_WIDTH = 180 BUTTON_HEIGHT = 19 LEFT, RIGHT = range(2) SIDEBAR, PANEL, UP, DOWN = range(4) ON, OFF, HIGHLIGHT, DISABLED = range(4) def __init__(self, text, align=LEFT, userCode=0, parent=None, type=SIDEBAR): super(TextButton, self).__init__(parent) # Prevent a circular import. from menumanager import MenuManager self._menu_manager = MenuManager.instance() self.menuString = text self.buttonLabel = text self.alignment = align self.buttonType = type self.userCode = userCode self.scanAnim = None self.bgOn = None self.bgOff = None self.bgHighlight = None self.bgDisabled = None self.state = TextButton.OFF self.setAcceptHoverEvents(True) self.setCursor(Qt.PointingHandCursor) # Calculate the button size. if type in (TextButton.SIDEBAR, TextButton.PANEL): self.logicalSize = QSize(TextButton.BUTTON_WIDTH, TextButton.BUTTON_HEIGHT) else: self.logicalSize = QSize(int((TextButton.BUTTON_WIDTH / 2.0) - 5), int(TextButton.BUTTON_HEIGHT * 1.5)) self._prepared = False def setMenuString(self, menu): self.menuString = menu def prepare(self): if not self._prepared: self.setupHoverText() self.setupScanItem() self.setupButtonBg() self._prepared = True def boundingRect(self): return QRectF(0, 0, self.logicalSize.width(), self.logicalSize.height()) def setupHoverText(self): if not self.buttonLabel: return textItem = DemoTextItem(self.buttonLabel, Colors.buttonFont(), Colors.buttonText, -1, self) textItem.setZValue(self.zValue() + 2) textItem.setPos(16, 0) def setupScanItem(self): if Colors.useButtonBalls: scanItem = ScanItem(self) scanItem.setZValue(self.zValue() + 1) self.scanAnim = DemoItemAnimation(scanItem) x = 1.0 y = 1.5 stop = TextButton.BUTTON_WIDTH - scanItem.boundingRect().width( ) - x if self.alignment == TextButton.LEFT: self.scanAnim.setDuration(2500) self.scanAnim.setKeyValueAt(0.0, QPointF(x, y)) self.scanAnim.setKeyValueAt(0.5, QPointF(x, y)) self.scanAnim.setKeyValueAt(0.7, QPointF(stop, y)) self.scanAnim.setKeyValueAt(1.0, QPointF(x, y)) scanItem.setPos(QPointF(x, y)) else: self.scanAnim.setKeyValueAt(0.0, QPointF(stop, y)) self.scanAnim.setKeyValueAt(0.5, QPointF(x, y)) self.scanAnim.setKeyValueAt(1.0, QPointF(stop, y)) scanItem.setPos(QPointF(stop, y)) def setState(self, state): self.state = state self.bgOn.setRecursiveVisible(state == TextButton.ON) self.bgOff.setRecursiveVisible(state == TextButton.OFF) self.bgHighlight.setRecursiveVisible(state == TextButton.HIGHLIGHT) self.bgDisabled.setRecursiveVisible(state == TextButton.DISABLED) if state == TextButton.DISABLED: self.setCursor(Qt.ArrowCursor) else: self.setCursor(Qt.PointingHandCursor) def setupButtonBg(self): self.bgOn = ButtonBackground(self.buttonType, True, True, self.logicalSize, self) self.bgOff = ButtonBackground(self.buttonType, False, False, self.logicalSize, self) self.bgHighlight = ButtonBackground(self.buttonType, True, False, self.logicalSize, self) self.bgDisabled = ButtonBackground(self.buttonType, True, True, self.logicalSize, self) self.setState(TextButton.OFF) def hoverEnterEvent(self, event): if not self.isEnabled() or self.state == TextButton.DISABLED: return if self.state == TextButton.OFF: self.setState(TextButton.HIGHLIGHT) if Colors.noAnimations and Colors.useButtonBalls: # Wait a bit in the beginning to enhance the effect. We have # to do this here so that the adaption can be dynamic. self.scanAnim.setDuration(1000) self.scanAnim.setKeyValueAt(0.2, self.scanAnim.posAt(0)) if (self._menu_manager.window.fpsMedian > 10 or Colors.noAdapt or Colors.noTimerUpdate): if Colors.useButtonBalls: self.scanAnim.play(True, True) def hoverLeaveEvent(self, event): if self.state == TextButton.DISABLED: return self.setState(TextButton.OFF) if Colors.noAnimations and Colors.useButtonBalls: self.scanAnim.stop() def mousePressEvent(self, event): if self.state == TextButton.DISABLED: return if self.state == TextButton.HIGHLIGHT or self.state == TextButton.OFF: self.setState(TextButton.ON) def mouseReleaseEvent(self, event): if self.state == TextButton.ON: self.setState(TextButton.OFF) if self.isEnabled() and self.boundingRect().contains(event.pos()): self._menu_manager.itemSelected(self.userCode, self.menuString) def animationStarted(self, _): if self.state == TextButton.DISABLED: return self.setState(TextButton.OFF)
class nuser(QMainWindow): def __init__(self): super(nuser,self).__init__() loadUi('nUser.ui',self) self.tabWidget.tabBar().setVisible(False) self.handleButtons() style = open('themes/darkblue.css' , 'r') style = style.read() self.setStyleSheet(style) self.camOn=False self.video_size=QSize(601,341) self.image=None def handleButtons(self): self.homePage.clicked.connect(self.openHome) self.liveFPage.clicked.connect(self.openLFoot) self.oldFPage.clicked.connect(self.openOFoot) self.pathPage.clicked.connect(self.openTracker) self.logoutPage.clicked.connect(self.openLogout) self.darkO.clicked.connect(self.Dark_Orange_Theme) self.darkB.clicked.connect(self.Dark_Blue_Theme) self.darkG.clicked.connect(self.Dark_Gray_Theme) self.qdark.clicked.connect(self.QDark_Theme) self.showLive.clicked.connect(self.start_cam) self.pauseLive.clicked.connect(self.stop_cam) self.yes.clicked.connect(self.logoutYes) self.no.clicked.connect(self.openHome) self.showVid.clicked.connect(self.callPlayer) self.trackStart.clicked.connect(self.callTracker) #* self.showPlot.clicked.connect(self.displayOutput) #* self.trackVideoShow.clicked.connect(self.playTrackedVid) #* def openHome(self): self.stop_cam() self.tabWidget.setCurrentIndex(0) def openLFoot(self): self.tabWidget.setCurrentIndex(1) def openOFoot(self): self.stop_cam() self.show_Old_Vid() def openTracker(self): self.stop_cam() self.tabWidget.setCurrentIndex(3) conn = sqlite3.connect('capstoneSQLDB2.db') cur = conn.cursor() cur.execute("SELECT NAME FROM BLUEPRINTS") items=cur.fetchall() l=["BLUEPRINT"] self.blueprintSelect.clear() for i in items: l.append(i[0]) self.blueprintSelect.addItems(l) cur.close() def openLogout(self): self.stop_cam() self.tabWidget.setCurrentIndex(4) def show_Old_Vid(self): self.tabWidget.setCurrentIndex(2) conn = sqlite3.connect('capstoneSQLDB2.db') cur = conn.cursor() data=cur.execute('SELECT Code,Date,Time,Cam FROM VidHistory') if data: self.vidTable.setRowCount(0) self.vidTable.insertRow(0) for row , form in enumerate(data): for column , item in enumerate(form) : self.vidTable.setItem(row , column , QTableWidgetItem(str(item))) column += 1 row_position = self.vidTable.rowCount() self.vidTable.insertRow(row_position) cur.close() def Dark_Blue_Theme(self): style = open('themes/darkblue.css' , 'r') style = style.read() self.setStyleSheet(style) def Dark_Gray_Theme(self): style = open('themes/darkgray.css' , 'r') style = style.read() self.setStyleSheet(style) def Dark_Orange_Theme(self): style = open('themes/darkorange.css' , 'r') style = style.read() self.setStyleSheet(style) def QDark_Theme(self): style = open('themes/qdark.css' , 'r') style = style.read() self.setStyleSheet(style) def start_cam(self): if self.camBox.currentText() !="SELECT": self.camOn=True if self.camBox.currentText() =="WEB CAM (TEMP)": self.capture=cv2.VideoCapture(0,cv2.CAP_DSHOW) self.Vname="outputVideo\\"+str(time.time()).split(".")[0]+".mp4" i=0 while i<22000: i+=1 self.capture.set(cv2.CAP_PROP_FRAME_WIDTH,self.video_size.width()) self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT,self.video_size.height()) self.rec=cv2.VideoWriter(self.Vname,cv2.VideoWriter_fourcc(*'MJPG'),10, (self.video_size.width(),self.video_size.height())) self.timer =QTimer(self) self.timer.timeout.connect(self.update_frame) self.timer.start(0.005) def update_frame(self): ret,frame=self.capture.read() if ret==True: self.rec.write(frame) self.image=cv2.cvtColor(frame,1) qformat = QImage.Format_Indexed8 if len(self.image.shape) == 3: if (self.image.shape[2]) == 4: qformat = QImage.Format_RGBA8888 else: qformat = QImage.Format_RGB888 img = QImage(self.image, self.image.shape[1], self.image.shape[0], self.image.strides[0], qformat) img = img.rgbSwapped() self.imgLabel.setPixmap(QPixmap.fromImage(img)) self.imgLabel.setScaledContents(True) conn = sqlite3.connect('capstoneSQLDB2.db') cur = conn.cursor() loc="D:\\Projects\\CAPSTONE\\"+self.Vname cur.execute('SELECT Loc FROM VidHistory where loc=?',(loc,)) temp=cur.fetchone() if temp is None: cur.execute('SELECT code FROM LCODE') code=cur.fetchone() cur.execute('UPDATE LCODE SET code = code + 1 WHERE code = ?',(code[0],)) conn.commit() t=datetime.now() date=str(t.date()) time=str(t.time()) cur.execute('''INSERT INTO VidHistory (Code, Date, Time,Cam,Loc ) VALUES (?,?,?,?,?)''', (code[0],date,time,1,loc)) conn.commit() cur.close() def stop_cam(self): if self.camOn: self.capture.release() self.rec.release() self.camOn=False self.timer.stop() img=cv2.imread("reset.png") qformat = QImage.Format_RGB888 img = QImage(img, img.shape[1], img.shape[0],img.strides[0], qformat) img = img.rgbSwapped() self.imgLabel.setPixmap(QPixmap.fromImage(img)) self.imgLabel.setScaledContents(True) self.image=None def logoutYes(self): from login import Login self.window2 = Login() self.close() self.window2.show() def callPlayer(self): conn = sqlite3.connect('capstoneSQLDB2.db') cur = conn.cursor() code=self.lineEdit.text() cur.execute('SELECT Loc FROM VidHistory WHERE Code = ? ', (code,)) loc =cur.fetchone() if loc is not None: self.window3 = Player(loc[0]) self.window3.resize(640,480) self.window3.show() cur.close() def enableItems(self): self.listView_3.setEnabled(True) self.blueprintSelect.setEnabled(True) self.trackVideoShow.setEnabled(True) self.trackIDs.setEnabled(True) self.idList.setEnabled(True) self.showPlot.setEnabled(True) def disableItems(self): self.listView_3.setEnabled(False) self.blueprintSelect.setEnabled(False) self.trackVideoShow.setEnabled(False) self.trackIDs.setEnabled(False) self.idList.setEnabled(False) self.showPlot.setEnabled(False) pixmap = QPixmap('D:/Projects/CAPSTONE/reset.PNG') self.outputImg.setPixmap(pixmap) # ============================================================================= # img=cv2.imread("reset.png") # qformat = QImage.Format_RGB888 # img = QImage(img, img.shape[1], img.shape[0],img.strides[0], qformat) # img = img.rgbSwapped() # self.outputImg.setPixmap(QPixmap.fromImage(img)) # self.outputImg.setScaledContents(True) # ============================================================================= def callTracker(self): cam=self.camCode.text() vid=self.vidCode.text() if len(cam)==0 or len(vid)==0: self.trackStatus.setText("ENTER ALL MANDATORY FIELDS") self.disableItems() else: conn = sqlite3.connect('capstoneSQLDB2.db') cur = conn.cursor() cur.execute('SELECT loc FROM VidHistory WHERE Code=? AND Cam=?',(vid,cam,)) loc=cur.fetchone() cur.close() if loc is None: self.trackStatus.setText("ENTER VALID CODE COMBINATION") self.disableItems() else: self.trackStatus.setText("TRACKING IN PROGRESS") yoloTracker(YOLO(),loc[0],"D:/Projects/CAPSTONE/outputVid.mp4") self.trackStatus.setText("TRACKING SUCCESFULLY DONE") f=open("D:/Projects/CAPSTONE/cordinates.pkl",'rb') d=pickle.load(f) l=list(d.keys()) l.sort() self.idList.setRowCount(len(l)) for i in range(len(l)): self.idList.setItem(i,0, QTableWidgetItem(str(l[i]))) self.enableItems() self.playTrackedVid() def displayOutput(self): blueprint=self.blueprintSelect.currentText() if blueprint=='BLUEPRINT': self.trackStatus.setText("SELECT A BLUEPRINT") else: ids=self.trackIDs.text() if len(ids)==0: self.trackStatus.setText("ENTER PEOPLE IDS FOR TRACKING") else: f=open("D:/Projects/CAPSTONE/cordinates.pkl",'rb') d=pickle.load(f) l=list(d.keys()) l2=[] c=True ids=ids.split(",") for i in ids: if i.isdigit() and (int(i) in l): l2.append(int(i)) else: c=False break if c: self.trackStatus.setText("") conn = sqlite3.connect('capstoneSQLDB2.db') cur = conn.cursor() cur.execute('SELECT loc FROM blueprints WHERE name=?',(blueprint,)) loc=cur.fetchone() cur.close() plot_trackers(loc[0],l2) pixmap = QPixmap('D:/Projects/CAPSTONE/plotted.jpg') pixmap=pixmap.scaled(self.outputImg.size()) self.outputImg.setPixmap(pixmap) else: self.trackStatus.setText("ENTER VALID IDs!") def playTrackedVid(self): #loc="D:/Projects/CAPSTONE/output3_yolov3S.mp4" ##FOR TESTING ONLY loc="D:/Projects/CAPSTONE/outputVid.mp4" self.window4=Player(loc) self.window4.resize(1280,960) self.window4.show()