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 sizeHint(self): metrics = QFontMetricsF(self.editor.font()) size_hint = QSize(metrics.height(), metrics.height()) if size_hint.width() > 16: size_hint.setWidth(16) return size_hint
def sizeHint(self): s = QSize() if self._minimum_rows: s.setHeight(self.count() * self.sizeHintForRow(0) + self.frameWidth() * 2) else: s.setHeight(super().sizeHint().height()) s.setWidth(max(self.sizeHintForColumn(0) + self.frameWidth() * 2, self._minimum_width)) return s
def height_for_width(self, icon: QIcon, height: int) -> QSize: size = QSize() size.setWidth(height) size.setHeight(height) actual = icon.actualSize(size) if actual.height() != height: ratio = actual.width() / actual.height() width = int(ratio * height) size.setWidth(width) size.setHeight(height) return size
def overread(self,msize): moutdata = "" img = Snapshot.snapshot(width = msize.width(), height = msize.height()).scaled(msize.width(),msize.height(),Qt.IgnoreAspectRatio, Qt.SmoothTransformation) moutdata = moutdata + ";"+(hex(msize.width())[2:]).rjust(4,'0')+(hex(msize.height())[2:]).rjust(4,'0')+"\r\n" pos = QSize(0,0) for ypos in range(0,img.height()): qrgb = ";" for xpos in range(0,img.width()): data = img.pixel(xpos,ypos) pos.setWidth(pos.width()+1) qrgb = qrgb + (hex(((data & 0x00F80000) >> 8 ) | ((data & 0x0000FC00) >> 5 ) | ((data & 0x000000F8) >> 3 ))[2:]).rjust(4,'0') pos.setWidth(0) pos.setHeight(pos.height()+1) moutdata = moutdata + qrgb + "\r\n" return moutdata
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 requestImage(self, id, requestedSize): if self._recorder.width == 1024: size = QSize(self._recorder.width * 7 / 8, self._recorder.height * 7 / 8) elif self._recorder.width == 1280: size = QSize(self._recorder.width * 3 / 4, self._recorder.height * 3 / 4) else: size = QSize(self._recorder.width, self._recorder.height) if requestedSize.width() > 0: size.setWidth(requestedSize.width()) if requestedSize.height() > 0: size.setHeight(requestedSize.height()) image = VideoRecorder.instance().frame.scaled(size, Qt.KeepAspectRatio, Qt.SmoothTransformation) return image, size
def requestImage(self, id, requestedSize): image = self._snapshot.image if self._snapshot.width == 1024: size = QSize(self._snapshot.width * 7 / 8, self._snapshot.height * 7 / 8) elif self._snapshot.width == 1280: size = QSize(self._snapshot.width * 3 / 4, self._snapshot.height * 3 / 4) else: size = QSize(self._snapshot.width, self._snapshot.height) if requestedSize.width() > 0: size.setWidth(requestedSize.width()) if requestedSize.height() > 0: size.setHeight(requestedSize.height()) image = image.scaled(size, Qt.KeepAspectRatio, Qt.SmoothTransformation) return image, size
def _update_icon(self): icon_size = QSize(self.iconSize()) icon_size.setWidth(icon_size.width() - 2) icon_size.setHeight(icon_size.height() - 2) pix_map = QPixmap(icon_size) pix_map.fill(self._color) painter = QPainter() painter.begin(pix_map) border_color = QColor(Qt.black) border_color.setAlpha(128) painter.setPen(border_color) painter.drawRect(0, 0, pix_map.width() - 1, pix_map.height() - 1) painter.end() self.setIcon(QIcon(pix_map))
def sectionSizeFromContents(self, logicalIndex: int) -> QSize: if self._pd.headerModel: curLeafIndex = QModelIndex(self._pd.leafIndex(logicalIndex)) if curLeafIndex.isValid(): styleOption = QStyleOptionHeader( self.styleOptionForCell(logicalIndex)) s = QSize(self._pd.cellSize(curLeafIndex, self, styleOption)) curLeafIndex = curLeafIndex.parent() while curLeafIndex.isValid(): if self.orientation() == Qt.Horizontal: s.setHeight(s.height() + self._pd.cellSize( curLeafIndex, self, styleOption).height()) else: s.setWidth(s.width() + self._pd.cellSize( curLeafIndex, self, styleOption).width()) curLeafIndex = curLeafIndex.parent() return s return super().sectionSizeFromContents(logicalIndex)
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)
def construct_array(self): rec1 = QRect( self.start_x, self.start_y, self.actuator[0].w, self.row * self.actuator[0].h + (self.row - 1) * self.gap_h) self.rec.append(rec1) size = QSize() size.setHeight(self.actuator[1].h) size.setWidth(self.actuator[1].w) for i in range(0, self.row): for j in range(0, self.col): index = i * self.col + j + 1 x = self.start_x + \ self.actuator[index].w+self.gap_w + \ self.actuator[index].w*j+self.gap_w*j y = self.start_y + self.actuator[index].h * i + self.gap_h * i temp = QPoint(x, y) self.rec.append(QRect(temp, size))
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
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)
def plot_testing(self, x_batch, predict, list_widget): list_widget.clear() """ :param x_batch: :param predict: :return: """ for i in range(len(x_batch)): top_3_predict = numpy.argsort(predict[i])[::-1][:3] #print(predict) result_widget = ResultWidget() result_widget.label_number.setText(str(i + 1)) category_1 = self.CIFAR10_LABELS_LIST.__getitem__(top_3_predict[0]) value_1 = predict[i][top_3_predict[0]] category_2 = self.CIFAR10_LABELS_LIST.__getitem__(top_3_predict[1]) value_2 = predict[i][top_3_predict[1]] category_3 = self.CIFAR10_LABELS_LIST.__getitem__(top_3_predict[2]) value_3 = predict[i][top_3_predict[2]] result_widget.label_prediksi_1.setText(category_1 + " - " + str(value_1) + "") result_widget.label_prediksi_2.setText(category_2 + " - " + str(value_2) + "") result_widget.label_prediksi_3.setText(category_3 + " - " + str(value_3) + "") # result_widget.canvas_image. ax = result_widget.figure_image.add_subplot(111) ax.imshow( self.dataset.to_channel_last(x_batch[i], shape=(32, 32, 3))) ax.set_position([0.1, 0.2, 0.8, 0.8]) ax.set_axis_off() result_widget.canvas_image.draw() list_item_widget = QListWidgetItem(list_widget) # print(result_widget.sizeHint()) list_size = QSize() list_size.setHeight(100) list_size.setWidth(502) list_item_widget.setSizeHint(list_size) list_widget.addItem(list_item_widget) list_widget.setItemWidget(list_item_widget, result_widget)
def gif(self): if self.movie: # Movie is already created self.movie.start() return print("Creating movie for", str(self.iv_image.path)) self.movie = QMovie(str(self.iv_image.path)) image = QImage(str(self.iv_image.path)) if image.height() > imageviewer.settings.IMAGE_HEIGHT or image.width() > imageviewer.settings.IMAGE_WIDTH: # scale the movie if it doesn't fit in the frame # the frame is big enough we don't need the gif at full size to see what is going on image2 = image.scaled(imageviewer.settings.IMAGE_WIDTH, imageviewer.settings.IMAGE_HEIGHT, Qt.KeepAspectRatio) size = QSize() size.setHeight(image2.height()) size.setWidth(image2.width()) self.movie.setScaledSize(size) print("Scaled movie size") self.setMovie(self.movie) self.movie.start()
def paintEvent(self, event): self.drawLabel1.clear() self.drawLabel2.clear() size = self.widget_4.size() size1 = QSize() size2 = QSize() size2.setHeight(size.height() * 0.45) size2.setWidth(size.width()) size1.setHeight(size.height() * 0.45) size1.setHeight(size.height() * 0.45) size1.setWidth(size.width()) self.drawLabel1.resize(size1) self.drawLabel2.resize(size2) if not self.image1 is None: size1 = self.drawLabel1.size() qimage1 = QImage(self.image1.tobytes(), self.image1.shape[1], self.image1.shape[0], QImage.Format_RGB888) pixmap1 = QPixmap(qimage1) pixmap1 = pixmap1.scaled(size1.width(), size1.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation) self.drawLabel1.setPixmap(pixmap1) self.drawLabel1.setAlignment(Qt.AlignCenter) if not self.image2 is None: self.drawLabel2.clear() size2 = self.drawLabel2.size() qimage2 = QImage(self.image2.tobytes(), self.image2.shape[1], self.image2.shape[0], QImage.Format_RGB888) pixmap2 = QPixmap(qimage2) pixmap2 = pixmap2.scaled(size2.width(), size2.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation) self.drawLabel2.setPixmap(pixmap2) self.drawLabel2.setAlignment(Qt.AlignCenter) QMainWindow.paintEvent(self, event)
def getMinimumSizeForOrientation ( self, orientation ) -> QSize: isDefaultOrientation = orientation == self.editor.GetDefaultOrientation () contentMinSize = self.content.layout ().minimumSize () topArea = self.toolBarAreaManager.getWidget ( CToolBarAreaManagerArea.Top ) bottomArea = self.toolBarAreaManager.getWidget ( CToolBarAreaManagerArea.Bottom ) leftArea = self.toolBarAreaManager.getWidget ( CToolBarAreaManagerArea.Left ) rightArea = self.toolBarAreaManager.getWidget ( CToolBarAreaManagerArea.Right ) result = QSize ( 0, 0 ) if isDefaultOrientation: # Take width from left and right areas if we're switching to the editor's default orientation result.setWidth ( result.width () + leftArea.getLargestItemMinimumSize ().width () ) result.setWidth ( result.width () + rightArea.getLargestItemMinimumSize ().width () ) # Use top and bottom area to calculate min height result.setHeight ( result.height () + leftArea.getLargestItemMinimumSize ().height () ) result.setHeight ( result.height () + rightArea.getLargestItemMinimumSize ().height () ) # Add content min size result += contentMinSize # Take the area layout size hints into account. Expand the current result with the toolbar area layout's size hint. # We use size hint rather than minimum size since toolbar area item's size policy is set to preferred. result = result.expandedTo ( QSize ( topArea.layout ().sizeHint ().height (), leftArea.layout ().sizeHint ().width () ) ) result = result.expandedTo ( QSize ( bottomArea.layout ().sizeHint ().height (), rightArea.layout ().sizeHint ().width () ) ) else: # If we're not switching to the default orientation, then we need to use the top and bottom toolbar areas' width # since these areas will be placed at the left and right of the editor content in this case of adaptive layouts result.setWidth ( result.width () + topArea.getLargestItemMinimumSize ().width () ) result.setWidth ( result.width () + bottomArea.getLargestItemMinimumSize ().width () ) # We must also flip where we get toolbar area min height from result.setHeight ( result.height () + leftArea.getLargestItemMinimumSize ().height () ) result.setHeight ( result.height () + rightArea.getLargestItemMinimumSize ().height () ) # Add flipped content min size result += QSize ( contentMinSize.height (), contentMinSize.width () ) result = result.expandedTo ( QSize ( leftArea.layout ().sizeHint ().height (), topArea.layout ().sizeHint ().width () ) ) result = result.expandedTo ( QSize ( rightArea.layout ().sizeHint ().height (), bottomArea.layout ().sizeHint ().width () ) ) return result
class Widget(QWidget): """ Encapsulates a QWindow into a widget QWindow is preferred for rendering over a QWidget because there is more control over OpenGL. A QOpenGLWidget limitations impose undesirable effects onto WindowProxy. To use QOpenGLWidget, WindowProxy would need to draw to a QOffscreenSurface and blit the result onto the QOpenGLWidget at appropriate times. Attributes ---------- _size_hint : QSize The hint that this widget provides to Qt for sizing """ def __init__(self, window_type=Window): """ Instantiates a widget that creates a new object of window_type and encapsulates it Attributes ---------- window_type : class, optional The class type to encapsulated by this widget, default class is Window. """ super().__init__() self._size_hint = QSize(DEFAULT_WIDTH, DEFAULT_HEIGHT) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) window = window_type() container = QWidget.createWindowContainer(window, self) self.setLayout(QGridLayout()) self.layout().addWidget(container) def sizeHint(self): """ Overrides QWidget.sizeHint() to provide the user set size hint Returns ------- QSize Size hint to Qt """ return self._size_hint def set_size_hint(self, width, height): """ Set the preferred size for this widget The default size policy is QSizePolicy.Expanding. Therefore, Qt tries to make this widget as large as possible. Under this policy, Qt is allowed to shrink the widget below this size if necessary. Parameters ---------- width : int The desired width height : int The desired height References ---------- [1] https://doc.qt.io/qt-5/qsizepolicy.html#Policy-enum """ self._size_hint.setWidth(width) self._size_hint.setHeight(height)
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)
def sizeHint(self): """ Set a reasonable size for the list widget. """ s = QSize() s.setHeight(super(ListWidget, self).sizeHint().height()) s.setWidth(self.sizeHintForColumn(0)) return s
class FaceView(QQuickGLItem): requestUpdate = pyqtSignal() setFloatParam = pyqtSignal(str, float) def __init__(self, parent=None): super(FaceView, self).__init__(parent=parent) self._params = {} self._size = QSize() self.detector = FaceDetector() lip_faces = np.array([ [54, 55, 64], [55, 65, 64], [55, 56, 65], [56, 66, 65], [56, 57, 66], [57, 67, 66], [57, 58, 67], [58, 59, 67], [59, 60, 67], [59, 48, 60], ], dtype=np.uint8) lm_model = ColorModel(name='facelm_model', color=np.array([1.0, 0.3, 0.3], dtype=np.float32), faces=lip_faces, draw_point=False) self.lm_model = lm_model self.requestUpdate.connect(self._requestUpdate) self.setFloatParam.connect(self._setFloatParam) # Fixme: workaround... QTimer.singleShot(500, lambda: self.update()) def geometryChanged(self, newGeometry, oldGeometry): if not newGeometry.isEmpty(): if newGeometry.width() != self._size.width() or \ newGeometry.height() != self._size.height(): self._update_size(newGeometry.width(), newGeometry.height()) self.update() def _update_size(self, w=0, h=0): if self.provider is None: return if w <= 0 or h <= 0: w, h = self.width(), self.height() w, h = geo.scale(1.0, self.provider.aspect_ratio, w, h, True) self._size.setWidth(w) self._size.setHeight(h) self.setWidth(w) self.setHeight(h) def _update_frame(self, image): if image is None: return None _, shapes = self.detector.detect(image) if shapes.shape[0] > 0: v_ndc = geo.to_ndc(shapes[0].astype(np.float32), (image.shape[1], image.shape[0])) self.lm_model.vertices = v_ndc return image def _requestUpdate(self): self.update() def _setFloatParam(self, key, value): self._params[key] = value self.update()
class Button(QAbstractButton): clicked = pyqtSignal(int) leftClicked = pyqtSignal() middleClicked = pyqtSignal() rightClicked = pyqtSignal() activated = pyqtSignal() def __init__(self, label=None): super(Button, self).__init__() self._bgColor = QColor(51, 51, 51) self._activeBgColor = QColor(21, 21, 21) self._labelSize = QSize() self._size = QSize() self._iconSize = QSize() self._icon = None self._tooltip = None self._padding = 8 self._idlePixmap = None self._checkedPixmap = None if label is not None: self.setText(label) def sizeHint(self): self._update_size() return self._size def setIcon(self, icon): self._icon = icon actual_size = self._icon.actualSize(self.size()) self._iconSize.setWidth(actual_size.width()) self._iconSize.setHeight(actual_size.height()) self._update_icon_pixmaps() self.update() def setIconSize(self, size): self._iconSize.setWidth(size.width()) self._iconSize.setHeight(size.height()) self._update_icon_pixmaps() # self._updateSize() self.update() def set_tooltip(self, text): self._tooltip = text def resizeEvent(self, e): pass # self._updateSize() def mousePressEvent(self, e): if not self.isCheckable(): self.setDown(True) else: was_checked = self.isChecked() self.setChecked(not self.isChecked()) if not was_checked and self.isChecked(): self.activated.emit() def mouseReleaseEvent(self, e): if not self.isCheckable(): self.setDown(False) if e.button() == Qt.LeftButton: self.leftClicked.emit() elif e.button() == Qt.MiddleButton: self.middleClicked.emit() elif e.button() == Qt.RightButton: self.rightClicked.emit() self.clicked.emit(e.button()) def enterEvent(self, e): if self._tooltip is not None and len(self._tooltip) > 0: QTimer.singleShot(300, lambda t=self._tooltip: self._show_tooltip(t)) def paintEvent(self, e): p = QPainter(self) active = self.isDown() or self.isChecked() if active: bg_color = self._activeBgColor border_color = bg_color.lighter(250) else: bg_color = self._bgColor border_color = bg_color.lighter(150) p.setPen(border_color) draw_rect = e.rect().adjusted(0, 0, -1, -1) p.drawRect(draw_rect) p.fillRect(draw_rect.adjusted(1, 1, 0, 0), bg_color) icon_margin = 0 icon_present = self._icon is not None text_present = len(self.text()) > 0 if icon_present: if not text_present: draw_x = round(draw_rect.width() / 2 - self._iconSize.width() / 2) draw_y = round(draw_rect.height() / 2 - self._iconSize.height() / 2) else: draw_x = self._padding draw_y = self._padding icon_margin = draw_x + self._iconSize.width() if not active and self._idlePixmap is not None: p.drawPixmap(draw_x, draw_y, self._idlePixmap) elif active and self._checkedPixmap is not None: p.drawPixmap(draw_x, draw_y, self._checkedPixmap) if text_present: p.setPen(Qt.white) if not icon_present: draw_x = round(draw_rect.width() / 2 - self._labelSize.width() / 2) draw_y = round(draw_rect.height() / 2 + self._labelSize.height() / 3) else: draw_x = icon_margin + 8 draw_y = round(draw_rect.height() / 2 + self._labelSize.height() / 3) p.drawText(draw_x, draw_y, self.text()) def _update_icon_pixmaps(self): if self._icon is None: return self._idlePixmap = self._icon.pixmap(self._iconSize, QIcon.Normal, QIcon.Off) self._checkedPixmap = self._icon.pixmap(self._iconSize, QIcon.Normal, QIcon.On) def _update_size(self): width = 0 height = 0 if self._icon is not None: width = self._iconSize.width() + (self._padding * 2) height = self._iconSize.height() + (self._padding * 2) if len(self.text()) > 0: font_metrics = QFontMetrics(self.font()) self._labelSize.setWidth(font_metrics.width(self.text())) self._labelSize.setHeight(font_metrics.height()) width = width + self._labelSize.width() + self._padding if height == 0: height = self._labelSize.height() + self._padding * 2 self._size.setWidth(width) self._size.setHeight(height) def _show_tooltip(self, text): QToolTip.showText(self.mapToGlobal(QPoint(0, self.height())), text)
def sizeHint(self): s = QSize() s.setHeight(200) s.setWidth(150) return s
def sizeHint(self, option: QStyleOptionViewItem, index: QtCore.QModelIndex) -> QtCore.QSize: sz = QSize(self.thumbnails.thumbnail_size) sz.setHeight(sz.height()) sz.setWidth(sz.width()) return sz
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)
def updateActionRect(self): """ void QMenuPrivate::updateActionRects(const QRect &screen) const https://cep.xray.aps.anl.gov/software/qt4-x11-4.8.6-browser/da/d61/class_q_menu_private.html#acf93cda3ebe88b1234dc519c5f1b0f5d """ self._aRect = {} topmargin = 0 leftmargin = 0 rightmargin = 0 # qmenu.cpp Line 259: # init max_column_width = 0 dh = self.height() y = 0 style = self.style() opt = QStyleOption() opt.initFrom(self) hmargin = style.pixelMetric(QStyle.PM_MenuHMargin, opt, self) vmargin = style.pixelMetric(QStyle.PM_MenuVMargin, opt, self) icone = style.pixelMetric(QStyle.PM_SmallIconSize, opt, self) fw = style.pixelMetric(QStyle.PM_MenuPanelWidth, opt, self) deskFw = style.pixelMetric(QStyle.PM_MenuDesktopFrameWidth, opt, self) tearoffHeight = style.pixelMetric(QStyle.PM_MenuTearoffHeight, opt, self) if self.isTearOffEnabled() else 0 # for compatibility now - will have to refactor this away tabWidth = 0 maxIconWidth = 0 hasCheckableItems = False # ncols = 1 # sloppyAction = 0 for i in range(len(self.actions())): act = self.actions()[i] if act.isSeparator() or act.isVisible() is False: continue # ..and some members hasCheckableItems |= act.isCheckable() ic = act.icon() if ic.isNull() is False: maxIconWidth = max(maxIconWidth, icone + 4) # qmenu.cpp Line 291: # calculate size qfm = self.fontMetrics() previousWasSeparator = True # this is true to allow removing the leading separators for i in range(len(self.actions())): act = self.actions()[i] if act.isVisible() is False \ or (self.separatorsCollapsible() and previousWasSeparator and act.isSeparator()): # we continue, this action will get an empty QRect self._aRect[i] = QRect() continue previousWasSeparator = act.isSeparator() # let the style modify the above size.. opt = QStyleOptionMenuItem() self.initStyleOption(opt, act) fm = opt.fontMetrics sz = QSize() # sz = self.sizeHint().expandedTo(self.minimumSize()).expandedTo(self.minimumSizeHint()).boundedTo(self.maximumSize()) # calc what I think the size is.. if act.isSeparator(): sz = QSize(2, 2) else: s = act.text() if '\t' in s: t = s.index('\t') act.setText(s[t + 1:]) tabWidth = max(int(tabWidth), qfm.width(s[t + 1:])) else: seq = act.shortcut() if seq.isEmpty() is False: tabWidth = max(int(tabWidth), qfm.width(seq.toString())) sz.setWidth(fm.boundingRect(QRect(), Qt.TextSingleLine | Qt.TextShowMnemonic, s).width()) sz.setHeight(fm.height()) if not act.icon().isNull(): is_sz = QSize(icone, icone) if is_sz.height() > sz.height(): sz.setHeight(is_sz.height()) sz = style.sizeFromContents(QStyle.CT_MenuItem, opt, sz, self) if sz.isEmpty() is False: max_column_width = max(max_column_width, sz.width()) # wrapping if y + sz.height() + vmargin > dh - deskFw * 2: # ncols += 1 y = vmargin y += sz.height() # update the item self._aRect[i] = QRect(0, 0, sz.width(), sz.height()) pass # exit for max_column_width += tabWidth # finally add in the tab width sfcMargin = style.sizeFromContents(QStyle.CT_Menu, opt, QApplication.globalStrut(), self).width() - QApplication.globalStrut().width() min_column_width = self.minimumWidth() - (sfcMargin + leftmargin + rightmargin + 2 * (fw + hmargin)) max_column_width = max(min_column_width, max_column_width) # qmenu.cpp Line 259: # calculate position base_y = vmargin + fw + topmargin + tearoffHeight x = hmargin + fw + leftmargin y = base_y for i in range(len(self.actions())): if self._aRect[i].isNull(): continue if y + self._aRect[i].height() > dh - deskFw * 2: x += max_column_width + hmargin y = base_y self._aRect[i].translate(x, y) # move self._aRect[i].setWidth(max_column_width) # uniform width y += self._aRect[i].height() # update menu size s = self.sizeHint() self.resize(s)
def sizeHint(self): s = QSize() s.setHeight(super(CustomList, self).sizeHint().height()) s.setWidth(self.sizeHintForColumn(0) + 25) return s
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 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)
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