def actually_show_text(self, data): text_item = QGraphicsTextItem() text_item.setZValue(5) if len(data) >= 8 and data[7] != None: text_item.setDefaultTextColor(QColor(data[7])) else: text_item.setDefaultTextColor(QColor("#FFFFFF")) text_item.setX(data[2]) text_item.setY(data[3]) text_item.setTextWidth(data[4]) text_item.setPlainText(data[5]) temp_font = text_item.font() temp_font.setPointSize(data[6]) text_item.setFont(temp_font) self.addItem(text_item) self.texts[data[1]] = text_item self.call_next_action()
class Video(QMainWindow): def __init__(self): super(Video, self).__init__() self.resize(1920, 1080) # ITEM self._item = QGraphicsVideoItem() self._textItem = QGraphicsTextItem() self._view = QGraphicsView() self._scene = QGraphicsScene() self._view.resize(1920, 1080) self._view.setScene(self._scene) self._scene.addItem(self._item) self._scene.addItem(self._textItem) self._textItem.setPlainText('SRT TEXT') self._textItem.setDefaultTextColor(Qt.red) font = self._textItem.font() font.setPixelSize(50) self._textItem.setFont(font) self._view.show() self._item.setSize(QSizeF(1920, 1080)) self._player = QMediaPlayer(self) self._player.setMedia( QMediaContent( QUrl.fromLocalFile( '/Users/huangkai/Documents/PycharmProjects/AllTest/Qt插入背景/AddVideos/Videos/yellow.mov' ))) self._player.setVideoOutput(self._item) self._player.play() self.setCentralWidget(self._view) self._item.setPos(400, 500) # BUTTON self._btn = QPushButton(self) self._btn.resize(100, 50) self._btn.move(500, 500) self._btn.setText('test') self._btn.clicked.connect(self._change_text) def _change_text(self): self._textItem.setPlainText('Fighting')
def _setBold(label: QGraphicsTextItem, isBold: bool) -> None: originalFontCopy = label.font() originalFontCopy.setBold(isBold) label.setFont(originalFontCopy)
class QmyMainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) #调用父类构造函数,创建窗体 self.ui = Ui_MainWindow() #创建UI对象 self.ui.setupUi(self) #构造UI界面 self.player = QMediaPlayer(self) #创建视频播放器 self.player.setNotifyInterval(1000) #信息更新周期, ms scene = QGraphicsScene(self) self.ui.graphicsView.setScene(scene) self.videoItem = QGraphicsVideoItem() #视频显示画面 self.videoItem.setSize(QSizeF(320, 220)) self.videoItem.setFlag(QGraphicsItem.ItemIsMovable) self.videoItem.setFlag(QGraphicsItem.ItemIsSelectable) self.videoItem.setFlag(QGraphicsItem.ItemIsFocusable) scene.addItem(self.videoItem) self.player.setVideoOutput(self.videoItem) #设置视频显示图形项 self.textItem = QGraphicsTextItem("面朝大海,春暖花开") #弹幕文字 font = self.textItem.font() font.setPointSize(20) self.textItem.setFont(font) self.textItem.setDefaultTextColor(Qt.red) self.textItem.setPos(100, 220) self.textItem.setFlag(QGraphicsItem.ItemIsMovable) self.textItem.setFlag(QGraphicsItem.ItemIsSelectable) self.textItem.setFlag(QGraphicsItem.ItemIsFocusable) scene.addItem(self.textItem) self.ui.btnText.setCheckable(True) #弹幕文字按钮 self.ui.btnText.setChecked(True) self.__duration = "" self.__curPos = "" self.player.stateChanged.connect(self.do_stateChanged) self.player.positionChanged.connect(self.do_positionChanged) self.player.durationChanged.connect(self.do_durationChanged) ## ==============自定义功能函数======================== ## ==============event处理函数========================== def closeEvent(self, event): #窗体关闭时 # 窗口关闭时不能自动停止播放,需手动停止 if (self.player.state() == QMediaPlayer.PlayingState): self.player.stop() ## ==========由connectSlotsByName()自动连接的槽函数============ @pyqtSlot() ##打开文件 def on_btnOpen_clicked(self): curPath = QDir.currentPath() #获取系统当前目录 ## curPath=os.getcwd() title = "选择视频文件" filt = "视频文件(*.wmv *.avi);;所有文件(*.*)" fileName, flt = QFileDialog.getOpenFileName(self, title, curPath, filt) if (fileName == ""): return fileInfo = QFileInfo(fileName) baseName = fileInfo.fileName() ## baseName=os.path.basename(fileName) self.ui.LabCurMedia.setText(baseName) curPath = fileInfo.absolutePath() QDir.setCurrent(curPath) #重设当前目录 media = QMediaContent(QUrl.fromLocalFile(fileName)) self.player.setMedia(media) #设置播放文件 self.player.play() @pyqtSlot() ##播放 def on_btnPlay_clicked(self): self.player.play() @pyqtSlot() ##暂停 def on_btnPause_clicked(self): self.player.pause() @pyqtSlot() ##停止 def on_btnStop_clicked(self): self.player.stop() @pyqtSlot() ##全屏 def on_btnFullScreen_clicked(self): self.videoWidget.setFullScreen(True) @pyqtSlot() ##静音按钮 def on_btnSound_clicked(self): mute = self.player.isMuted() self.player.setMuted(not mute) if mute: self.ui.btnSound.setIcon(QIcon(":/icons/images/volumn.bmp")) else: self.ui.btnSound.setIcon(QIcon(":/icons/images/mute.bmp")) @pyqtSlot(int) ##音量调节 def on_sliderVolumn_valueChanged(self, value): self.player.setVolume(value) @pyqtSlot(int) ##播放进度调节 def on_sliderPosition_valueChanged(self, value): self.player.setPosition(value) @pyqtSlot() ##放大 def on_btnZoomIn_clicked(self): sc = self.videoItem.scale() self.videoItem.setScale(sc + 0.1) @pyqtSlot() ##缩小 def on_btnZoomOut_clicked(self): sc = self.videoItem.scale() self.videoItem.setScale(sc - 0.1) @pyqtSlot(bool) ##弹幕 def on_btnText_clicked(self, checked): self.textItem.setVisible(checked) ## =============自定义槽函数=============================== def do_stateChanged(self, state): isPlaying = (state == QMediaPlayer.PlayingState) self.ui.btnPlay.setEnabled(not isPlaying) self.ui.btnPause.setEnabled(isPlaying) self.ui.btnStop.setEnabled(isPlaying) def do_durationChanged(self, duration): self.ui.sliderPosition.setMaximum(duration) secs = duration / 1000 #秒 mins = secs / 60 #分钟 secs = secs % 60 #余数秒 self.__duration = "%d:%d" % (mins, secs) self.ui.LabRatio.setText(self.__curPos + "/" + self.__duration) def do_positionChanged(self, position): if (self.ui.sliderPosition.isSliderDown()): return #如果正在拖动滑条,退出 self.ui.sliderPosition.setSliderPosition(position) secs = position / 1000 #秒 mins = secs / 60 #分钟 secs = secs % 60 #余数秒 self.__curPos = "%d:%d" % (mins, secs) self.ui.LabRatio.setText(self.__curPos + "/" + self.__duration)
class Scene_Base(QGraphicsScene): def __init__(self, window): super().__init__() self.setSceneRect(0, 0, window.game_width, window.game_height) self.window = window self.init_fields() self.create_fade() self.dialog_box = None self.setup() def init_fields(self): self.actions = [] self.current_action = -1 self.dialog_box = None self.target_text = None self.current_dialog_text = None self.current_dialog_text_offset = 0 self.background = None self.fading_box = None self.fade_dir = 0 self.next_scene = None self.buttons = [] self.current_id = 1 self.texts = {} self.images = {} self.moving_images = [] self.updatable_images = [] self.vaxx = None self.entry = None self.pathogen = None self.tempPic = None def create_fade(self, opc=1): self.fading_box = QGraphicsRectItem(0, 0, self.width(), self.height()) self.fading_box.setBrush(QColor(0, 0, 0)) self.fading_box.setOpacity(opc) self.fading_box.setZValue(100) self.addItem(self.fading_box) # ============================================== # * Setup # # Overwrite with child classes to add actions. # ============================================== def setup(self): pass # ============================================== # * Update # # Updates the scene. Handles input, waiting, etc. # ============================================== def update(self): self.update_fade() self.update_dialog() self.update_dialog_text() self.update_moving_images() self.update_animated_images() def update_fade(self): if self.fading_box is not None: if self.fade_dir == 0 and self.fading_box.opacity() > 0: self.fading_box.setOpacity(self.fading_box.opacity() - 0.02) if self.fading_box.opacity() <= 0: self.removeItem(self.fading_box) self.fading_box = None elif self.fade_dir == 1 and self.fading_box.opacity() < 1: self.fading_box.setOpacity(self.fading_box.opacity() + 0.02) if self.fading_box.opacity() >= 1: if self.next_scene is not None: self.window.goto_scene(self.next_scene) else: self.actually_close_game() def update_dialog(self): if self.dialog_box is not None: if self.dialog_box.opacity() < 0.5: self.dialog_box.setOpacity(self.dialog_box.opacity() + 0.04) if self.dialog_box.opacity() >= 0.5: self.dialog_box.setOpacity(0.5) if self.actions[0][0] == 0: self.actually_show_dialog(self.actions[0]) def update_dialog_text(self): if self.current_dialog_text is not None: curr_text = self.current_dialog_text.toPlainText() if curr_text != self.target_text: if self.current_dialog_text_offset < 3: self.current_dialog_text_offset += 1 else: self.current_dialog_text_offset = 0 self.current_dialog_text.setPlainText(self.target_text[:len(curr_text) + 1]) def update_moving_images(self): if len(self.moving_images) > 0: index = 0 new_data = [] for image in self.moving_images: pic_id = image[0] item = self.images[pic_id] item.setX(item.x() + image[1]) item.setY(item.y() + image[2]) image[3] -= 1 if image[3] > 0: new_data.append(image) elif image[4] is not None: image[4]() index += 1 self.moving_images = new_data def update_animated_images(self): if len(self.updatable_images) > 0: index = 0 for image in self.updatable_images: image[2] += 1 if image[2] > image[4]: image[2] = 0 image[3] += 1 if image[3] >= len(image[1]): image[3] = 0 image[0].setPixmap(QPixmap(image[1][image[3]])) # ============================================== # * Mouse Events # # Handle mouse input. # ============================================== def mouseMoveEvent(self, mouseEvent): pass def mousePressEvent(self, mouseEvent): self.when_mouse_pressed(mouseEvent) def mouseDoubleClickEvent(self, mouseEvent): self.when_mouse_pressed(mouseEvent) def when_mouse_pressed(self, mouseEvent): if self.current_action == 0 and mouseEvent.button() == 1: if self.current_dialog_text is not None: if self.current_dialog_text.toPlainText() != self.target_text: self.current_dialog_text.setPlainText(self.target_text) else: self.removeItem(self.current_dialog_text) self.call_next_action() else: super(Scene_Base, self).mousePressEvent(mouseEvent) # ============================================== # * Action Management # ============================================== def finish_action(self): if len(self.actions) > 0: self.actions.pop(0) else: self.wait_for_button_press() def check_if_first(self): if len(self.actions) == 1: self.perform_next_action() def perform_next_action(self): if len(self.actions) > 0: action = self.actions[0] self.current_action = action_type = action[0] if action_type is -1: self.actually_hide_dialog_box() elif action_type is 0: self.actually_show_dialog(action) elif action_type is 1: self.actually_show_button(action) elif action_type is 2: self.actually_set_background(action) elif action_type is 3: self.actually_goto_scene(action) elif action_type is 4: self.actually_close_game() elif action_type is 5: self.actually_play_song(action) elif action_type is 6: self.actually_wait_for_button_press() elif action_type is 7: self.actually_remove_all_buttons() elif action_type is 8: self.actually_show_text(action) elif action_type is 9: self.actually_hide_text(action) elif action_type is 10: self.actually_show_image(action) elif action_type is 11: self.actually_hide_image(action) elif action_type is 12: self.actually_move_image(action) elif action_type is 50: self.actually_play_sound(action) def call_next_action(self): self.finish_action() self.perform_next_action() # ============================================== # * Actual Implementations # ============================================== def actually_show_dialog(self, data): if self.dialog_box is None: self.create_dialog_box() else: self.current_dialog_text = QGraphicsTextItem() self.current_dialog_text.setZValue(20) self.current_dialog_text.setDefaultTextColor(QColor(255, 255, 255)) temp_font = self.current_dialog_text.font() temp_font.setPointSize(data[2]) self.current_dialog_text.setFont(temp_font) self.addItem(self.current_dialog_text) self.current_dialog_text.setX(self.dialog_box.x() + 10) self.current_dialog_text.setY(self.dialog_box.y() + 10) self.current_dialog_text.setTextWidth(self.dialog_box.boundingRect().width() - 20) self.target_text = data[1] self.current_dialog_text_offset = 0 def create_dialog_box(self): self.dialog_box = QGraphicsRectItem(0, 0, self.width() - 20, self.height() / 4) self.dialog_box.setBrush(QColor(0, 0, 0)) self.dialog_box.setX(10) self.dialog_box.setY(self.height() - self.dialog_box.boundingRect().height() - 10) self.dialog_box.setZValue(15); self.dialog_box.setOpacity(0) self.addItem(self.dialog_box) def actually_hide_dialog_box(self): if self.dialog_box is not None: self.removeItem(self.dialog_box) self.dialog_box = None def actually_show_button(self, data): button = Button(self, data[3], data[4], data[5], data[6], data[7], data[8], data[9]) button.setX(data[1]) button.setY(data[2]) self.buttons.append(button) self.addItem(button) self.call_next_action() def actually_set_background(self, data): if self.background is not None: self.removeItem(self.background) self.background = None self.background = QGraphicsPixmapItem(QPixmap(data[1]).scaled(self.window.game_width, self.window.game_height)) self.background.setZValue(-10) self.addItem(self.background) self.call_next_action() def actually_goto_scene(self, data): self.next_scene = data[1] self.fade_dir = 1 self.create_fade(0) def actually_close_game(self): self.window.close_game() def actually_play_song(self, data): AudioPlayer.play_song(data[1]) self.call_next_action() def actually_wait_for_button_press(self): self.current_action = -1 def actually_remove_all_buttons(self): for b in self.buttons: self.removeItem(b) self.buttons = [] self.call_next_action() def actually_show_text(self, data): text_item = QGraphicsTextItem() text_item.setZValue(5) if len(data) >= 8 and data[7] != None: text_item.setDefaultTextColor(QColor(data[7])) else: text_item.setDefaultTextColor(QColor("#FFFFFF")) text_item.setX(data[2]) text_item.setY(data[3]) text_item.setTextWidth(data[4]) text_item.setPlainText(data[5]) temp_font = text_item.font() temp_font.setPointSize(data[6]) text_item.setFont(temp_font) self.addItem(text_item) self.texts[data[1]] = text_item self.call_next_action() def actually_hide_text(self, data): self.removeItem(self.texts[data[1]]) self.texts[data[1]] = None self.call_next_action() def actually_show_image(self, data): image = None if isinstance(data[2], list): image = QGraphicsPixmapItem(QPixmap(data[2][0])) self.updatable_images.append([image, data[2], 0, 0, data[5]]) else: image = QGraphicsPixmapItem(QPixmap(data[2])) image.setX(data[3]) image.setY(data[4]) image.setZValue(0) self.addItem(image) self.images[data[1]] = image self.call_next_action() def actually_hide_image(self, data): self.removeItem(self.images[data[1]]) self.images[data[1]] = None self.call_next_action() def actually_move_image(self, data): image_id = data[1] image = self.images[image_id] duration = data[4] x_speed = (data[2] - image.x()) / duration y_speed = (data[3] - image.y()) / duration callback = self.call_next_action if data[5] else None image_data = [image_id, x_speed, y_speed, duration, callback] self.moving_images.append(image_data) if not data[5]: self.call_next_action() def actually_play_sound(self, data): AudioPlayer.play_sound_effect(data[1]) self.call_next_action() # ============================================== # * Setup Calls # ============================================== def add_call(self, data): self.actions.append(data) self.check_if_first() # ============================================== # * Extended Calls # ============================================== # ============================================== # Adds a dialog to the game. # # Ex: # self.add_dialog("Hello World!") # ============================================== def add_dialog(self, msg, fontSize=20): self.add_call([0, msg, fontSize]) # ============================================== # Hides the dialog box # # Ex: # self.hide_dialog_box() # ============================================== def hide_dialog_box(self): self.add_call([-1]) # ============================================== # Adds a button to the game. # After adding all buttons, be sure to call "self.wait_for_button_press". # Check scenes/scene_titlescreen.py for example of "font" and "buttonColors" # # Ex: # self.add_button(30, 30, 200, 200, "My Button!", self.another_function) # ============================================== def add_button(self, x, y, w, h, name, action, font=None, buttonColors=None, textColors=None): self.add_call([1, x, y, w, h, name, font, buttonColors, textColors, action]) # ============================================== # Sets the current background. # # Ex: # self.set_background("images/Background1.png") # ============================================== def set_background(self, path): self.add_call([2, path]) # ============================================== # Changes the game to the provided scene. # # Ex: # self.goto_scene(scenes.my_other_scene.My_Other_Scene) # ============================================== def goto_scene(self, scene): self.add_call([3, scene]) # ============================================== # Closes the game. # # Ex: # self.close_game() # ============================================== def close_game(self): self.add_call([4]) # ============================================== # Plays a song. # # Ex: # self.play_song("audio/testmusic2.mp3") # ============================================== def play_song(self, path): self.add_call([5, path]) # ============================================== # Plays a sound effect once. # # Ex: # self.play_sound("audio/testmusic2.mp3") # ============================================== def play_sound(self, path): self.add_call([50, path]) # ============================================== # Once all buttons are created, this will wait for the player to press one. # # Ex: # self.wait_for_button_press() # ============================================== def wait_for_button_press(self): self.add_call([6]) # ============================================== # Removes all buttons from the screen. # # Ex: # self.remove_all_buttons() # ============================================== def remove_all_buttons(self): self.add_call([7]) # ============================================== # Based on the value provided, there is a chance it will return True. # # Ex: # if self.generate_random_chance(30): # # there is a 30% chance of this happening # else: # # there is a 70% chance of this happening # ============================================== def generate_random_chance(self, val): return random.randint(0, 100) <= val # ============================================== # Shows text on the screen (not in dialog). # This function returns an ID that can be used in self.hide_text. # # Ex: # text_id = self.show_text(10, 10, 200, "Hello Screen!", 40, "#FF44CC") # ============================================== def show_text(self, x, y, w, text, size=30, color=None): new_id = self.current_id self.current_id += 1 self.add_call([8, new_id, x, y, w, text, size, color]) return new_id # ============================================== # Hides the text connected to the ID. # # Ex: # self.hide_text(text_id) # ============================================== def hide_text(self, text_id): self.add_call([9, text_id]) # ============================================== # Shows a picture on the screen. # This function returns an ID that can be used in other functions. # # Ex: # pic_id = self.show_picture("images/TestImage1.png", 30, 30) # # ---------------------------------------------- # # Using an array of strings will create an animation. # In that case, the last argument is the frame-change frequency of the animation. # # Ex: # pic_id = self.show_picture(["images/p1_frame_0.png", "images/p1_frame_1.png"], 100, 100, 30) # ============================================== def show_picture(self, path, x, y, animation_speed=20): new_id = self.current_id self.current_id += 1 self.add_call([10, new_id, path, x, y, animation_speed]) return new_id # ============================================== # Removes the picture connected to the provided ID. # # Ex: # self.hide_picture(pic_id) # ============================================== def hide_picture(self, image_id): self.add_call([11, image_id]) # ============================================== # Moves the picture to new coordinates over a duration of time. # # Ex: # self.move_picture(pic_id, 90, 30, 120) # ============================================== def move_picture(self, image_id, x, y, duration, wait_until_finished=True): self.add_call([12, image_id, x, y, duration, wait_until_finished]) #=============================================== # Sets the vaccination status # True/False, 50% chance #=============================================== def set_vaxx(self): if self.generate_random_chance(50): Scene_Base.vaxx = True else: Scene_Base.vaxx = False #=============================================== # Sets the entry point # 0 is for blood (cut) # 1 is for stomach (mouth) #=============================================== def set_entry(self): if self.generate_random_chance(50): Scene_Base.entry = 0 else: Scene_Base.entry = 1 # ============================================== # Gets and sets global values # # Ex: # self.set_value("Health", 100) # # self.add_value("Health", -1) # # if self.get_value("Health") <= 30: # self.add_dialog("Player is less than 30 health!") # ============================================== def set_value(self, name, value): Scene_Base.GLOBAL_VARS[name] = value def add_value(self, name, value): Scene_Base.GLOBAL_VARS[name] += value def get_value(self, name): return Scene_Base.GLOBAL_VARS[name]
def createGraphics(self): """ Create the graphical representation of the FMU's inputs and outputs """ def variableColor(variable): if variable.type == 'Real': return QColor.fromRgb(0, 0, 127) elif variable.type in ['Integer', 'Enumeration']: return QColor.fromRgb(255, 127, 0) elif variable.type == 'Boolean': return QColor.fromRgb(255, 0, 255) elif variable.type == 'String': return QColor.fromRgb(0, 128, 0) else: return QColor.fromRgb(0, 0, 0) inputVariables = [] outputVariables = [] maxInputLabelWidth = 0 maxOutputLabelWidth = 0 textItem = QGraphicsTextItem() fontMetrics = QFontMetricsF(textItem.font()) for variable in self.modelDescription.modelVariables: if variable.causality == 'input': inputVariables.append(variable) elif variable.causality == 'output': outputVariables.append(variable) for variable in inputVariables: maxInputLabelWidth = max(maxInputLabelWidth, fontMetrics.width(variable.name)) for variable in outputVariables: maxOutputLabelWidth = max(maxOutputLabelWidth, fontMetrics.width(variable.name)) from math import floor scene = QGraphicsScene() self.ui.graphicsView.setScene(scene) group = QGraphicsItemGroup() scene.addItem(group) group.setPos(200.5, -50.5) lh = 15 # line height w = max(150., maxInputLabelWidth + maxOutputLabelWidth + 20) h = max(50., 10 + lh * max(len(inputVariables), len(outputVariables))) block = QGraphicsRectItem(0, 0, w, h, group) block.setPen(QColor.fromRgb(0, 0, 255)) pen = QPen() pen.setWidthF(1) font = QFont() font.setPixelSize(10) # inputs y = floor((h - len(inputVariables) * lh) / 2 - 2) for variable in inputVariables: text = QGraphicsTextItem(variable.name, group) text.setDefaultTextColor(QColor.fromRgb(0, 0, 255)) text.setFont(font) text.setX(3) text.setY(y) polygon = QPolygonF([ QPointF(-13.5, y + 4), QPointF(1, y + 11), QPointF(-13.5, y + 18) ]) path = QPainterPath() path.addPolygon(polygon) path.closeSubpath() contour = QGraphicsPathItem(path, group) contour.setPen(QPen(Qt.NoPen)) contour.setBrush(variableColor(variable)) y += lh # outputs y = floor((h - len(outputVariables) * lh) / 2 - 2) for variable in outputVariables: text = QGraphicsTextItem(variable.name, group) text.setDefaultTextColor(QColor.fromRgb(0, 0, 255)) text.setFont(font) text.setX(w - 3 - text.boundingRect().width()) text.setY(y) polygon = QPolygonF([ QPointF(w, y + 0 + 7.5), QPointF(w + 7, y + 3.5 + 7.5), QPointF(w, y + 7 + 7.5) ]) path = QPainterPath() path.addPolygon(polygon) path.closeSubpath() contour = QGraphicsPathItem(path, group) pen = QPen() pen.setColor(variableColor(variable)) pen.setJoinStyle(Qt.MiterJoin) contour.setPen(pen) y += lh
class Block(QGraphicsRectItem): """ Entity-block with text & four connection ports """ def __init__(self, name='Untitled', parent=None): super(Block, self).__init__(None) self.parent = parent w = 60.0 h = 40.0 self.__base_color = QColor(158, 94, 155) # Properties of the rectangle: self.setPen(QtGui.QPen(self.__base_color, 2)) # self.setPen(QtGui.QPen(QtCore.Qt.blue, 2)) self.setBrush(QtGui.QBrush(self.__base_color)) self.setFlags(self.ItemIsSelectable | self.ItemIsMovable) self.setCursor(QCursor(QtCore.Qt.PointingHandCursor)) # Label: self.label = QGraphicsTextItem(name, self) self.label.setFont(QFont()) self.name = name # Create corner for resize: self.sizer = HandleItem(self) self.sizer.setPos(w, h) self.sizer.posChangeCallbacks.append( self.changeSize) # Connect the callback self.sizer.setFlag(self.sizer.ItemIsSelectable, True) # Inputs and outputs of the block: self.ports = [] self.ports.append(PortItem('a', self)) self.ports.append(PortItem('b', self)) self.ports.append(PortItem('c', self)) self.ports.append(PortItem('d', self)) # Update size: self.changeSize(w, h) def get_text(self): return self.label.toPlainText() def get_random_port(self): i = random.randint(0, len(self.ports) - 1) if i in range(0, 5): return self.ports[i] def delete(self): msg = QMessageBox() msg.setIcon(QMessageBox.Question) msg.setText("Вы действительно хотите удалить блок?") msg.setInformativeText("Это действие нельзя отменить") msg.setWindowTitle("Подтвердите действие") msg.setDetailedText("При удалении блока удалятся все его соединения!") msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) confirm = msg.exec_() if confirm == QMessageBox.Ok: self.parent.remove_node(self) def edit_parameters(self): text, ok = QInputDialog.getText(None, 'Параметры', 'Текст блока:', text=self.label.toPlainText()) if ok: self.label.setPlainText(text) def contextMenuEvent(self, event): menu = QMenu() action_delete = menu.addAction('Удалить') action_params = menu.addAction('Параметры') action_delete.triggered.connect(self.delete) action_params.triggered.connect(self.edit_parameters) menu.exec_(event.screenPos()) def changeSize(self, w, h): """ Resize block function """ # Limit the block size: metric = QFontMetrics(self.label.font()) width = metric.width(self.name) height = metric.height() if h < height + 5: h = height + 5 if w < width + 5: w = width + 5 self.setRect(0.0, 0.0, w, h) # center label: rect = self.label.boundingRect() lw, lh = rect.width(), rect.height() lx = (w - lw) / 2 ly = (h - lh) / 2 self.label.setPos(lx, ly) # Update port positions: self.ports[0].setPos(0, h / 2) self.ports[1].setPos(w / 2, 0) self.ports[2].setPos(w, h / 2) self.ports[3].setPos(w / 2, h) return w, h
class LinkItem(QGraphicsObject): """ A Link item in the canvas that connects two :class:`.NodeItem`\s in the canvas. The link curve connects two `Anchor` items (see :func:`setSourceItem` and :func:`setSinkItem`). Once the anchors are set the curve automatically adjusts its end points whenever the anchors move. An optional source/sink text item can be displayed above the curve's central point (:func:`setSourceName`, :func:`setSinkName`) """ #: Z value of the item Z_VALUE = 0 def __init__(self, *args): self.__boundingRect = None QGraphicsObject.__init__(self, *args) self.setFlag(QGraphicsItem.ItemHasNoContents, True) self.setAcceptedMouseButtons(Qt.RightButton | Qt.LeftButton) self.setAcceptHoverEvents(True) self.setZValue(self.Z_VALUE) self.sourceItem = None self.sourceAnchor = None self.sinkItem = None self.sinkAnchor = None self.curveItem = LinkCurveItem(self) self.sourceIndicator = LinkAnchorIndicator(self) self.sinkIndicator = LinkAnchorIndicator(self) self.sourceIndicator.hide() self.sinkIndicator.hide() self.linkTextItem = QGraphicsTextItem(self) self.__sourceName = "" self.__sinkName = "" self.__dynamic = False self.__dynamicEnabled = False self.hover = False self.prepareGeometryChange() self.__boundingRect = None def setSourceItem(self, item, anchor=None): """ Set the source `item` (:class:`.NodeItem`). Use `anchor` (:class:`.AnchorPoint`) as the curve start point (if ``None`` a new output anchor will be created using ``item.newOutputAnchor()``). Setting item to ``None`` and a valid anchor is a valid operation (for instance while mouse dragging one end of the link). """ if item is not None and anchor is not None: if anchor not in item.outputAnchors(): raise ValueError("Anchor must be belong to the item") if self.sourceItem != item: if self.sourceAnchor: # Remove a previous source item and the corresponding anchor self.sourceAnchor.scenePositionChanged.disconnect( self._sourcePosChanged) if self.sourceItem is not None: self.sourceItem.removeOutputAnchor(self.sourceAnchor) self.sourceItem = self.sourceAnchor = None self.sourceItem = item if item is not None and anchor is None: # Create a new output anchor for the item if none is provided. anchor = item.newOutputAnchor() # Update the visibility of the start point indicator. self.sourceIndicator.setVisible(bool(item)) if anchor != self.sourceAnchor: if self.sourceAnchor is not None: self.sourceAnchor.scenePositionChanged.disconnect( self._sourcePosChanged) self.sourceAnchor = anchor if self.sourceAnchor is not None: self.sourceAnchor.scenePositionChanged.connect( self._sourcePosChanged) self.__updateCurve() def setSinkItem(self, item, anchor=None): """ Set the sink `item` (:class:`.NodeItem`). Use `anchor` (:class:`.AnchorPoint`) as the curve end point (if ``None`` a new input anchor will be created using ``item.newInputAnchor()``). Setting item to ``None`` and a valid anchor is a valid operation (for instance while mouse dragging one and of the link). """ if item is not None and anchor is not None: if anchor not in item.inputAnchors(): raise ValueError("Anchor must be belong to the item") if self.sinkItem != item: if self.sinkAnchor: # Remove a previous source item and the corresponding anchor self.sinkAnchor.scenePositionChanged.disconnect( self._sinkPosChanged) if self.sinkItem is not None: self.sinkItem.removeInputAnchor(self.sinkAnchor) self.sinkItem = self.sinkAnchor = None self.sinkItem = item if item is not None and anchor is None: # Create a new input anchor for the item if none is provided. anchor = item.newInputAnchor() # Update the visibility of the end point indicator. self.sinkIndicator.setVisible(bool(item)) if self.sinkAnchor != anchor: if self.sinkAnchor is not None: self.sinkAnchor.scenePositionChanged.disconnect( self._sinkPosChanged) self.sinkAnchor = anchor if self.sinkAnchor is not None: self.sinkAnchor.scenePositionChanged.connect( self._sinkPosChanged) self.__updateCurve() def setFont(self, font): """ Set the font for the channel names text item. """ if font != self.font(): self.linkTextItem.setFont(font) self.__updateText() def font(self): """ Return the font for the channel names text. """ return self.linkTextItem.font() def setChannelNamesVisible(self, visible): """ Set the visibility of the channel name text. """ self.linkTextItem.setVisible(visible) def setSourceName(self, name): """ Set the name of the source (used in channel name text). """ if self.__sourceName != name: self.__sourceName = name self.__updateText() def sourceName(self): """ Return the source name. """ return self.__sourceName def setSinkName(self, name): """ Set the name of the sink (used in channel name text). """ if self.__sinkName != name: self.__sinkName = name self.__updateText() def sinkName(self): """ Return the sink name. """ return self.__sinkName def _sinkPosChanged(self, *arg): self.__updateCurve() def _sourcePosChanged(self, *arg): self.__updateCurve() def __updateCurve(self): self.prepareGeometryChange() self.__boundingRect = None if self.sourceAnchor and self.sinkAnchor: source_pos = self.sourceAnchor.anchorScenePos() sink_pos = self.sinkAnchor.anchorScenePos() source_pos = self.curveItem.mapFromScene(source_pos) sink_pos = self.curveItem.mapFromScene(sink_pos) # Adaptive offset for the curve control points to avoid a # cusp when the two points have the same y coordinate # and are close together delta = source_pos - sink_pos dist = math.sqrt(delta.x()**2 + delta.y()**2) cp_offset = min(dist / 2.0, 60.0) # TODO: make the curve tangent orthogonal to the anchors path. path = QPainterPath() path.moveTo(source_pos) path.cubicTo(source_pos + QPointF(cp_offset, 0), sink_pos - QPointF(cp_offset, 0), sink_pos) self.curveItem.setPath(path) self.sourceIndicator.setPos(source_pos) self.sinkIndicator.setPos(sink_pos) self.__updateText() else: self.setHoverState(False) self.curveItem.setPath(QPainterPath()) def __updateText(self): self.prepareGeometryChange() self.__boundingRect = None if self.__sourceName or self.__sinkName: if self.__sourceName != self.__sinkName: text = u"{0} \u2192 {1}".format(self.__sourceName, self.__sinkName) else: # If the names are the same show only one. # Is this right? If the sink has two input channels of the # same type having the name on the link help elucidate # the scheme. text = self.__sourceName else: text = "" self.linkTextItem.setPlainText(text) path = self.curveItem.path() if not path.isEmpty(): center = path.pointAtPercent(0.5) angle = path.angleAtPercent(0.5) brect = self.linkTextItem.boundingRect() transform = QTransform() transform.translate(center.x(), center.y()) transform.rotate(-angle) # Center and move above the curve path. transform.translate(-brect.width() / 2, -brect.height()) self.linkTextItem.setTransform(transform) def removeLink(self): self.setSinkItem(None) self.setSourceItem(None) self.__updateCurve() def setHoverState(self, state): if self.hover != state: self.prepareGeometryChange() self.__boundingRect = None self.hover = state self.sinkIndicator.setHoverState(state) self.sourceIndicator.setHoverState(state) self.curveItem.setHoverState(state) def hoverEnterEvent(self, event): # Hover enter event happens when the mouse enters any child object # but we only want to show the 'hovered' shadow when the mouse # is over the 'curveItem', so we install self as an event filter # on the LinkCurveItem and listen to its hover events. self.curveItem.installSceneEventFilter(self) return QGraphicsObject.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): # Remove the event filter to prevent unnecessary work in # scene event filter when not needed self.curveItem.removeSceneEventFilter(self) return QGraphicsObject.hoverLeaveEvent(self, event) def sceneEventFilter(self, obj, event): if obj is self.curveItem: if event.type() == QEvent.GraphicsSceneHoverEnter: self.setHoverState(True) elif event.type() == QEvent.GraphicsSceneHoverLeave: self.setHoverState(False) return QGraphicsObject.sceneEventFilter(self, obj, event) def boundingRect(self): if self.__boundingRect is None: self.__boundingRect = self.childrenBoundingRect() return self.__boundingRect def shape(self): return self.curveItem.shape() def setEnabled(self, enabled): """ Reimplemented from :class:`QGraphicsObject` Set link enabled state. When disabled the link is rendered with a dashed line. """ # This getter/setter pair override a property from the base class. # They should be renamed to e.g. setLinkEnabled/linkEnabled self.curveItem.setLinkEnabled(enabled) def isEnabled(self): return self.curveItem.isLinkEnabled() def setDynamicEnabled(self, enabled): """ Set the link's dynamic enabled state. If the link is `dynamic` it will be rendered in red/green color respectively depending on the state of the dynamic enabled state. """ if self.__dynamicEnabled != enabled: self.__dynamicEnabled = enabled if self.__dynamic: self.__updatePen() def isDynamicEnabled(self): """ Is the link dynamic enabled. """ return self.__dynamicEnabled def setDynamic(self, dynamic): """ Mark the link as dynamic (i.e. it responds to :func:`setDynamicEnabled`). """ if self.__dynamic != dynamic: self.__dynamic = dynamic self.__updatePen() def isDynamic(self): """ Is the link dynamic. """ return self.__dynamic def __updatePen(self): self.prepareGeometryChange() self.__boundingRect = None if self.__dynamic: if self.__dynamicEnabled: color = QColor(0, 150, 0, 150) else: color = QColor(150, 0, 0, 150) normal = QPen(QBrush(color), 2.0) hover = QPen(QBrush(color.darker(120)), 2.1) else: normal = QPen(QBrush(QColor("#9CACB4")), 2.0) hover = QPen(QBrush(QColor("#7D7D7D")), 2.1) self.curveItem.setCurvePenSet(normal, hover)
class LabelItem(QGraphicsRectItem): def __init__(self, text='', parent=None): super(LabelItem, self).__init__(None) self.parent = parent self.text = text # Properties of the rectangle: self.setPen(QtGui.QPen(QtCore.Qt.lightGray)) self.setBrush(QtGui.QBrush(QColor(234, 193, 72))) self.setFlags(self.ItemIsSelectable | self.ItemIsMovable) self.setCursor(QCursor(QtCore.Qt.PointingHandCursor)) # Label: self.label = QGraphicsTextItem(self.text, self) metric = QFontMetrics(self.label.font()) self.width = metric.width(self.text) self.height = metric.height() self.setRect(0.0, 0.0, self.width + 1, self.height + 1) def contextMenuEvent(self, event): menu = QMenu() pa = menu.addAction('Параметры') pa.triggered.connect(self.edit_parameters) drop = menu.addAction('Удалить связь') drop.triggered.connect(self.remove_connection) menu.exec_(event.screenPos()) def remove_connection(self): self.parent.delete_connection() def set_text(self, text): self.parent.arrow_text = str(text) self.text = str(text) self.label.setPlainText(text) metric = QFontMetrics(self.label.font()) width = metric.width(self.text) height = metric.height() if self.height < height + 5: self.height = height + 5 if self.width < width + 5: self.width = width + 5 self.setRect(0.0, 0.0, self.width, self.height) # center label: rect = self.label.boundingRect() lw, lh = rect.width(), rect.height() lx = (self.width - lw) / 2 ly = (self.height - lh) / 2 self.label.setPos(lx + 1, ly) def edit_parameters(self): text, ok = QInputDialog.getText(None, 'Параметры', 'Отношение:', text=self.text) if ok: self.set_text(text) def change_size(self, w, h): """ Resize block function """ # Limit the block size: metric = QFontMetrics(self.label.font()) width = metric.width(self.text) height = metric.height() if h < height + 5: self.height += 5 if w < width + 5: self.width += 5 self.setRect(0.0, 0.0, self.width, self.height) # center label: rect = self.label.boundingRect() lw, lh = rect.width(), rect.height() lx = (w - lw) / 2 ly = (h - lh) / 2 self.label.setPos(lx, ly) return w, h
class ItemConfigDialog(QMainWindow, Ui_MainWindow): """ Окно настроек элемента """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setupUi(self) self.text_aligns = [ ('center', 'По центру'), ('left', 'По левому краю'), ('right', 'По правому краю'), ] self.text_align = 'center' for align, align_text in self.text_aligns: self.textPosH.addItem(align_text) self.item: QGraphicsRectItem = QGraphicsRectItem() self.main_view: QGraphicsView = QGraphicsView() self.fontSelect.currentFontChanged.connect(self.render_text) self.fontSize.valueChanged.connect(self.render_text) self.textPosH.currentTextChanged.connect(self.render_text) self.exText.textChanged.connect(self.render_text) self.font_color = QColor('black') self.scene = QGraphicsScene() self.graphicsView.setScene(self.scene) self.text = QGraphicsTextItem() self.buttonBox.accepted.connect(self.accepted) self.colorPicker.clicked.connect(self.select_color) def show(self): super(ItemConfigDialog, self).show() self.text = QGraphicsTextItem() self.name.setText(self.item.toolTip()) font = QFont() font_name = self.item.data(int(const.ITEM_DATA_KEY_FONT)) if font_name: font.fromString(font_name) font_size = self.item.data(int(const.ITEM_DATA_KEY_FONT_SIZE)) if font_size: font.setPointSize(font_size) else: font.setPointSize(12) font_color = self.item.data(int(const.ITEM_DATA_KEY_FONT_COLOR)) if font_color: self.font_color.setRgb(*font_color) self.text.setFont(font) self.text_align = self.item.data(int(const.ITEM_DATA_KEY_FONT_ALIGN)) self.render_text() self.render_item() def select_color(self): color = QColorDialog.getColor() if color.isValid(): self.font_color = color self.render_text() def render_text(self, event=None): font = self.fontSelect.currentFont() font.setPointSize(self.fontSize.value()) self.text.setFont(font) self.text.setTextWidth(self.item.rect().width()) self.text.setDefaultTextColor(self.font_color) for align, align_text in self.text_aligns: if align_text == self.textPosH.currentText(): self.text_align = align self.text.setHtml(f"<div align='{self.text_align}'>{self.exText.text()}</div>") def render_item(self): self.scene.clear() pos_from = self.main_view.mapFromScene(self.item.sceneBoundingRect()) pos = self.main_view.mapToScene(pos_from) point = pos.boundingRect() width, height = point.width(), point.height() img = QImage(width, height, QImage.Format_ARGB32_Premultiplied) painter = QPainter(img) painter.setRenderHint(QPainter.Antialiasing) self.item.scene().render(painter, source=QRectF(point.x(), point.y(), width, height)) painter.end() self.scene.addItem(QGraphicsPixmapItem(QPixmap(img))) self.scene.addItem(self.text) self.graphicsView.fitInView(QRectF(0, 0, width, height), Qt.KeepAspectRatio) self.scene.update() def accepted(self): self.item.setData(int(const.ITEM_DATA_KEY_FONT), self.text.font().toString().split(',')[0]) self.item.setData(int(const.ITEM_DATA_KEY_FONT_SIZE), self.text.font().pointSize()) self.item.setData(int(const.ITEM_DATA_KEY_FONT_ALIGN), self.text_align) self.item.setData(int(const.ITEM_DATA_KEY_FONT_COLOR), self.font_color.getRgb()) self.item.setToolTip(self.name.text()) self.item.scene().update() self.close()