class CollapsibleBox(QWidget): def __init__(self, title="", parent=None): super(CollapsibleBox, self).__init__(parent) self.toggle_button = QToolButton(text=title, checkable=True, checked=False) self.toggle_button.setStyleSheet("QToolButton { border: none; }") self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setArrowType(Qt.RightArrow) self.toggle_button.pressed.connect(self.on_pressed) self.toggle_animation = QParallelAnimationGroup(self) self.content_area = QScrollArea(maximumHeight=0, minimumHeight=0) self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.content_area.setFrameShape(QFrame.NoFrame) lay = QVBoxLayout(self) lay.setSpacing(0) lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(self.toggle_button) lay.addWidget(self.content_area) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self.content_area, b"maximumHeight")) @pyqtSlot() def on_pressed(self): checked = self.toggle_button.isChecked() self.toggle_button.setArrowType( Qt.DownArrow if not checked else Qt.RightArrow) self.toggle_animation.setDirection( QAbstractAnimation.Forward if not checked else QAbstractAnimation. Backward) self.toggle_animation.start() def setContentLayout(self, layout): lay = self.content_area.layout() del lay self.content_area.setLayout(layout) collapsed_height = self.sizeHint().height( ) - self.content_area.maximumHeight() content_height = layout.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(500) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(500) content_animation.setStartValue(0) content_animation.setEndValue(content_height)
def step_ani(self, is_back=False): title_move_ani, title_fade_ani = self.fade_to_left_ani(self.title) content1_move_ani, content1_fade_ani = self.fade_to_left_ani( self.content1) content2_move_ani, content2_fade_ani = self.fade_to_left_ani( self.content2) form_move_ani, form_fade_ani = self.fade_to_left_ani(self.form) # 动画队列 ani_group = QParallelAnimationGroup(self) ani_group.addAnimation(title_move_ani) ani_group.addAnimation(title_fade_ani) ani_group.addAnimation(content1_move_ani) ani_group.addAnimation(content1_fade_ani) ani_group.addAnimation(content2_move_ani) ani_group.addAnimation(content2_fade_ani) ani_group.addAnimation(form_move_ani) ani_group.addAnimation(form_fade_ani) if is_back: ani_group.setDirection(ani_group.Backward) ani_group.start() return ani_group
def __init__(self): super(Robot, self).__init__() self.setFlag(self.ItemHasNoContents) self.torsoItem = RobotTorso(self) self.headItem = RobotHead(self.torsoItem) self.upperLeftArmItem = RobotLimb(self.torsoItem) self.lowerLeftArmItem = RobotLimb(self.upperLeftArmItem) self.upperRightArmItem = RobotLimb(self.torsoItem) self.lowerRightArmItem = RobotLimb(self.upperRightArmItem) self.upperRightLegItem = RobotLimb(self.torsoItem) self.lowerRightLegItem = RobotLimb(self.upperRightLegItem) self.upperLeftLegItem = RobotLimb(self.torsoItem) self.lowerLeftLegItem = RobotLimb(self.upperLeftLegItem) settings = ( # Item Position Rotation Scale # x y start end (self.headItem, 0, -18, 20, -20, 1.1), (self.upperLeftArmItem, -15, -10, 190, 180, 0), (self.lowerLeftArmItem, 30, 0, 50, 10, 0), (self.upperRightArmItem, 15, -10, 300, 310, 0), (self.lowerRightArmItem, 30, 0, 0, -70, 0), (self.upperRightLegItem, 10, 32, 40, 120, 0), (self.lowerRightLegItem, 30, 0, 10, 50, 0), (self.upperLeftLegItem, -10, 32, 150, 80, 0), (self.lowerLeftLegItem, 30, 0, 70, 10, 0), (self.torsoItem, 0, 0, 5, -20, 0), ) animation = QParallelAnimationGroup(self) for item, pos_x, pos_y, start_rot, end_rot, scale in settings: item.setPos(pos_x, pos_y) rot_animation = QPropertyAnimation(item, b"rotation") rot_animation.setStartValue(start_rot) rot_animation.setEndValue(end_rot) rot_animation.setEasingCurve(QEasingCurve.SineCurve) rot_animation.setDuration(2000) animation.addAnimation(rot_animation) if scale > 0: scale_animation = QPropertyAnimation(item, b"scale") scale_animation.setEndValue(scale) scale_animation.setEasingCurve(QEasingCurve.SineCurve) scale_animation.setDuration(2000) animation.addAnimation(scale_animation) animation.setLoopCount(-1) animation.start()
def __init__(self): super(Robot, self).__init__() self.setFlag(self.ItemHasNoContents) self.torsoItem = RobotTorso(self) self.headItem = RobotHead(self.torsoItem) self.upperLeftArmItem = RobotLimb(self.torsoItem) self.lowerLeftArmItem = RobotLimb(self.upperLeftArmItem) self.upperRightArmItem = RobotLimb(self.torsoItem) self.lowerRightArmItem = RobotLimb(self.upperRightArmItem) self.upperRightLegItem = RobotLimb(self.torsoItem) self.lowerRightLegItem = RobotLimb(self.upperRightLegItem) self.upperLeftLegItem = RobotLimb(self.torsoItem) self.lowerLeftLegItem = RobotLimb(self.upperLeftLegItem) settings = ( # Item Position Rotation Scale # x y start end (self.headItem, 0, -18, 20, -20, 1.1), (self.upperLeftArmItem, -15, -10, 190, 180, 0), (self.lowerLeftArmItem, 30, 0, 50, 10, 0), (self.upperRightArmItem, 15, -10, 300, 310, 0), (self.lowerRightArmItem, 30, 0, 0, -70, 0), (self.upperRightLegItem, 10, 32, 40, 120, 0), (self.lowerRightLegItem, 30, 0, 10, 50, 0), (self.upperLeftLegItem, -10, 32, 150, 80, 0), (self.lowerLeftLegItem, 30, 0, 70, 10, 0), (self.torsoItem, 0, 0, 5, -20, 0), ) animation = QParallelAnimationGroup(self) for item, pos_x, pos_y, start_rot, end_rot, scale in settings: item.setPos(pos_x, pos_y) rot_animation = QPropertyAnimation(item, b"rotation") rot_animation.setStartValue(start_rot) rot_animation.setEndValue(end_rot) rot_animation.setEasingCurve(QEasingCurve.SineCurve) rot_animation.setDuration(2000) animation.addAnimation(rot_animation) if scale > 0: scale_animation = QPropertyAnimation(item, b"scale") scale_animation.setEndValue(scale) scale_animation.setEasingCurve(QEasingCurve.SineCurve) scale_animation.setDuration(2000) animation.addAnimation(scale_animation) animation.setLoopCount(-1) animation.start()
def __init__(self, parent=None): super(myobj, self).__init__(parent) self.color = QColor(Qt.lightGray) animation = QParallelAnimationGroup(self) rot_animation = QPropertyAnimation(self, b'rotation') rot_animation.setStartValue(-200) rot_animation.setEndValue(600) rot_animation.setEasingCurve(QEasingCurve.SineCurve) rot_animation.setDuration(3000) animation.addAnimation(rot_animation) animation.setLoopCount(-1) animation.start()
def Show(self): print("Initiating animation") animation_group = QParallelAnimationGroup(self) for w in (self.LoginText, self.UploadButton, self.PassText): start_pos = w.pos() end_pos = w.pos() + QPoint(0, -250) animation = QPropertyAnimation( self, propertyName=b"pos", targetObject=w, startValue=start_pos, endValue=end_pos, duration=900, ) animation_group.addAnimation(animation) animation_group.start(QAnimationGroup.DeleteWhenStopped) print("Aniamtion initiated") self.ui.show()
def step_ani(self, is_back=False): img_ani = QPropertyAnimation(self.img, b'geometry', self) img_ani.setStartValue(QRect(0, 0, self.img_width, self.img_height)) img_ani.setEndValue( QRect(0, -self.img_height * 2, self.img_width * 2, self.img_height * 2)) img_ani.setDuration(200) img_ani.setEasingCurve(QEasingCurve.OutCurve) title_ani = QPropertyAnimation(self.title, b'pos', self) title_ani.setStartValue(QPoint(self.title_x, self.title_y)) title_ani.setEndValue(QPoint(244, 78)) title_ani.setDuration(200) title_ani.setEasingCurve(QEasingCurve.OutCurve) content1_ani = QPropertyAnimation(self.content1, b'pos', self) content1_ani.setStartValue(QPoint(self.content1_x, self.content1_y)) content1_ani.setEndValue(QPoint(244, 112)) content1_ani.setDuration(200) content1_ani.setEasingCurve(QEasingCurve.OutCurve) content2_ani = QPropertyAnimation(self.content2, b'pos', self) content2_ani.setStartValue(QPoint(self.content2_x, self.content2_y)) content2_ani.setEndValue(QPoint(244, 134)) content2_ani.setDuration(200) content2_ani.setEasingCurve(QEasingCurve.OutCurve) # 动画队列 ani_group = QParallelAnimationGroup(self) ani_group.addAnimation(img_ani) ani_group.addAnimation(title_ani) ani_group.addAnimation(content1_ani) ani_group.addAnimation(content2_ani) if is_back: ani_group.setDirection(ani_group.Backward) ani_group.start() return ani_group
class SmallestPlayModeInterface(QWidget): """ 最小播放模式界面 """ CYCLE_LEFT_SHIFT = 0 CYCLE_RIGHT_SHIFT = 1 def __init__(self, playlist: list, parent=None): super().__init__(parent) self.playlist = playlist self.currentIndex = 0 self.shiftLeftTime = 0 self.shiftRightTime = 0 self.songInfoCard_list = [] self.__unCompleteShift_list = [] # 创建按钮 self.playButton = PlayButton( [ r"app\resource\images\smallest_play_mode\播放_45_45.png", r"app\resource\images\smallest_play_mode\暂停_45_45.png", ], self, ) self.lastSongButton = SmallestPlayModeButton( r"app\resource\images\smallest_play_mode\上一首_45_45.png", self) self.nextSongButton = SmallestPlayModeButton( r"app\resource\images\smallest_play_mode\下一首_45_45.png", self) self.exitSmallestModeButton = BasicCircleButton( r"app\resource\images\playing_interface\最小模式播放_47_47.png", self) self.progressBar = QSlider(Qt.Horizontal, self) self.aniGroup = QParallelAnimationGroup(self) # 创建歌曲信息卡 self.__createSongInfoCards() # 初始化 self.__initWidget() def __initWidget(self): """ 初始化界面 """ self.resize(350, 350) self.setMinimumSize(206, 197) self.setAttribute(Qt.WA_TranslucentBackground) self.setObjectName("smallestModeInterface") self.progressBar.setObjectName("smallestModeSlider") self.progressBar.installEventFilter(self) self.aniGroup.finished.connect(self.__switchSongInfoCard) self.__setQss() def __createSongInfoCards(self): """ 创建歌曲信息卡 """ # 创建三个歌曲信息卡,分别为last, current, next self.songInfoCard_list = [SongInfoCard(parent=self) for i in range(3)] # 引用当前歌曲卡 self.lastSongInfoCard = self.songInfoCard_list[0] # type:SongInfoCard self.curSongInfoCard = self.songInfoCard_list[1] # type:SongInfoCard self.nextSongInfoCard = self.songInfoCard_list[2] # type:SongInfoCard # 创建动画 self.songInfoCardAni_list = [ QPropertyAnimation(self.songInfoCard_list[i], b"geometry") for i in range(3) ] self.lastSongInfoCardAni = self.songInfoCardAni_list[0] self.curSongInfoCardAni = self.songInfoCardAni_list[1] self.nextSongInfoCardAni = self.songInfoCardAni_list[2] # 将动画添加到动画组中 for ani in self.songInfoCardAni_list: self.aniGroup.addAnimation(ani) # 初始化歌曲卡 for i in range(3): self.songInfoCard_list[i].move((i - 1) * self.width(), self.height() - 106) if self.playlist: self.curSongInfoCard.updateCard(self.playlist[0]) if len(self.playlist) >= 2: self.nextSongInfoCard.updateCard(self.playlist[1]) def resizeEvent(self, e): """ 改变窗口大小时调整按钮位置和标签宽度 """ self.progressBar.resize(self.width(), 5) self.progressBar.move(0, self.height() - 5) self.exitSmallestModeButton.move( self.width() - 7 - self.exitSmallestModeButton.width(), self.height() - 7 - self.exitSmallestModeButton.height(), ) self.playButton.move( int(self.width() / 2 - self.playButton.width() / 2), int(self.height() / 2 - self.playButton.height() / 2), ) self.lastSongButton.move(self.playButton.x() - 75, self.playButton.y()) self.nextSongButton.move(self.playButton.x() + 75, self.playButton.y()) # 所有歌曲信息卡设置位置 for i in range(3): self.songInfoCard_list[i].resize( self.width(), self.songInfoCard_list[i].height()) self.curSongInfoCard.move(0, self.height() - 106) self.lastSongInfoCard.move(-self.width(), self.height() - 106) self.nextSongInfoCard.move(self.width(), self.height() - 106) # 高度太小时隐藏歌曲信息卡 if self.height() <= 320: if self.curSongInfoCard.isVisible(): self.curSongInfoCard.aniHide() self.lastSongInfoCard.hide() self.nextSongInfoCard.hide() elif self.height() > 320: if not self.curSongInfoCard.isVisible(): self.curSongInfoCard.aniShow() else: self.curSongInfoCard.show() self.lastSongInfoCard.show() self.nextSongInfoCard.show() def __setQss(self): """ 设置层叠样式 """ with open(r"app\resource\css\playInterface.qss", encoding="utf-8") as f: self.setStyleSheet(f.read()) def eventFilter(self, obj, e: QEvent): """ 过滤事件 """ if obj == self.progressBar: if e.type() in [ QEvent.MouseButtonPress, QEvent.MouseButtonDblClick ]: return True return super().eventFilter(obj, e) def __cycleLeftShift(self): """ 循环左移 """ self.loopMode = self.CYCLE_LEFT_SHIFT self.__setAnimation(self.curSongInfoCardAni, self.curSongInfoCard, -self.width()) self.__setAnimation(self.nextSongInfoCardAni, self.nextSongInfoCard, 0) self.aniGroup.removeAnimation(self.lastSongInfoCardAni) self.aniGroup.start() def __cycleRightShift(self): """ 循环右移 """ self.loopMode = self.CYCLE_RIGHT_SHIFT self.__setAnimation(self.curSongInfoCardAni, self.curSongInfoCard, self.width()) self.__setAnimation(self.lastSongInfoCardAni, self.lastSongInfoCard, 0) self.aniGroup.removeAnimation(self.nextSongInfoCardAni) self.aniGroup.start() def __setAnimation(self, animation: QPropertyAnimation, songInfoCard, endX): """ 设置动画 """ animation.setEasingCurve(QEasingCurve.OutQuart) animation.setTargetObject(songInfoCard) animation.setDuration(500) # 设置起始值 animation.setStartValue( QRect( songInfoCard.x(), songInfoCard.y(), songInfoCard.width(), songInfoCard.height(), )) # 设置结束值 animation.setEndValue( QRect(endX, songInfoCard.y(), songInfoCard.width(), songInfoCard.height())) def __switchSongInfoCard(self): """ 交换对底层歌曲卡对象的引用 """ # 循环左移 if self.loopMode == self.CYCLE_LEFT_SHIFT: self.__resetRef(moveDirection=0) # 更新动画组 self.aniGroup.addAnimation(self.lastSongInfoCardAni) # 移动底层对象 self.songInfoCard_list[[2, 0, 1][self.shiftLeftTime]].move( self.width(), self.height() - 106) # 更新下标 self.currentIndex += 1 if self.currentIndex != len(self.playlist) - 1: # 更新歌曲信息卡 self.updateCards() # 循环右移 elif self.loopMode == self.CYCLE_RIGHT_SHIFT: self.__resetRef(moveDirection=1) # 更新动画组 self.aniGroup.addAnimation(self.nextSongInfoCardAni) # 移动底层对象 self.songInfoCard_list[[0, 2, 1][self.shiftRightTime]].move( -self.width(), self.height() - 106) # 更新下标 self.currentIndex -= 1 if self.currentIndex != 0: self.updateCards() # 完成未完成的移位动作 if self.__unCompleteShift_list: index = self.__unCompleteShift_list.pop(0) self.__completeShift(index) def updateCards(self): """ 更新三个歌曲信息卡 """ self.curSongInfoCard.updateCard(self.playlist[self.currentIndex]) if self.currentIndex >= 1: self.lastSongInfoCard.updateCard(self.playlist[self.currentIndex - 1]) if self.currentIndex <= len(self.playlist) - 2: self.nextSongInfoCard.updateCard(self.playlist[self.currentIndex + 1]) def __resetRef(self, moveDirection=0): """ 设置变量对底层对象的引用,moveDirection = 0 代表左移,moveDirection = 1 代表右移 """ # 循环左移 if moveDirection == 0: self.shiftLeftTime = (self.shiftLeftTime + 1) % 3 self.shiftRightTime = (self.shiftRightTime - 1) % 3 if self.shiftLeftTime == 0: self.__resetRefIndex(0, 1, 2) elif self.shiftLeftTime == 1: self.__resetRefIndex(1, 2, 0) elif self.shiftLeftTime == 2: self.__resetRefIndex(2, 0, 1) # 循环右移 elif moveDirection == 1: self.shiftLeftTime = (self.shiftLeftTime - 1) % 3 self.shiftRightTime = (self.shiftRightTime + 1) % 3 if self.shiftRightTime == 0: self.__resetRefIndex(0, 1, 2) elif self.shiftRightTime == 1: self.__resetRefIndex(2, 0, 1) elif self.shiftRightTime == 2: self.__resetRefIndex(1, 2, 0) def __resetRefIndex(self, lastIndex, curIndex, nextIndex): """ refsetFunc的子函数 """ self.curSongInfoCard = self.songInfoCard_list[curIndex] self.lastSongInfoCard = self.songInfoCard_list[lastIndex] self.nextSongInfoCard = self.songInfoCard_list[nextIndex] def setCurrentIndex(self, index): """ 更新当前下标并移动和更新歌曲信息卡 """ if self.playlist: # 新的下标大于当前下标时,歌曲卡左移 if index != self.currentIndex: if self.aniGroup.state() != QAbstractAnimation.Running: self.__completeShift(index) else: self.__unCompleteShift_list.append(index) elif index == self.currentIndex: self.updateCards() self.needToEmitSignal = True def setPlaylist(self, playlist, isResetIndex: bool = True): """ 更新播放列表 """ self.playlist = playlist self.currentIndex = 0 if isResetIndex else self.currentIndex if playlist: self.curSongInfoCard.updateCard(self.playlist[0]) self.curSongInfoCard.show() if len(self.playlist) > 1: self.nextSongInfoCard.updateCard(self.playlist[1]) else: self.curSongInfoCard.hide() def __completeShift(self, index): """ 完成移位,只在调用setCurrentIndex时调用 """ if index > self.currentIndex: self.currentIndex = index - 1 self.nextSongInfoCard.updateCard(self.playlist[index]) self.__cycleLeftShift() elif index < self.currentIndex: self.currentIndex = index + 1 self.lastSongInfoCard.updateCard(self.playlist[index]) self.__cycleRightShift()
class QTile(QGraphicsObject): colorMap = { Tetris.I: QColor("#53bbf4"), Tetris.J: QColor("#e25fb8"), Tetris.L: QColor("#ffac00"), Tetris.O: QColor("#ecff2e"), Tetris.S: QColor("#97eb00"), Tetris.T: QColor("#ff85cb"), Tetris.Z: QColor("#ff5a48") } def __init__(self, qTetris: 'QTetris', tetrimino: Tetris.Tetrimino, tile: Tetris.Tile): super(QTetris.QTile, self).__init__() tile.delegate = self self.color = self.colorMap[type(tetrimino)] self.qTetris = qTetris self.moveAnimation = QParallelAnimationGroup() self.dropAnimation = QPropertyAnimation(self, b'pos') self.collapseAnimation = QPropertyAnimation(self, b'pos') self.shiftAnimation = QPropertyAnimation(self, b'pos') self.collapseAnimation.finished.connect( lambda tl=tile: tile.delegate.disappeared(tl)) self.qTetris.scene.addItem(self) self.setPos(QPointF(0, 4)) self.moved(tile) def moved(self, tile: Tetris.Tile): translation = QPropertyAnimation(self, b'pos') start, end = self.pos(), QPointF(tile.row, tile.column) curve, speed, delay = QEasingCurve.OutBack, 1 / 50, -1 self.animate(translation, start, end, curve, speed, delay) rotation = QPropertyAnimation(self, b'rotation') start, end = self.rotation(), tile.rotation curve, speed, delay = QEasingCurve.OutBack, 1, -1 self.animate(rotation, start, end, curve, speed, delay) rotation.setDuration(translation.duration()) self.moveAnimation.clear() self.moveAnimation.addAnimation(translation) self.moveAnimation.addAnimation(rotation) self.moveAnimation.start() def dropped(self, tile: Tetris.Tile): start, end = self.pos(), QPointF(tile.row, tile.column) curve, speed, delay = QEasingCurve.OutBounce, 1 / 50, 0 self.animate(self.dropAnimation, start, end, curve, speed, delay) def collapsed(self, tile: Tetris.Tile): start, end = self.pos(), QPointF( tile.row, tile.column + 2 * tile.tetris.num_columns) curve, speed, delay = QEasingCurve.InOutExpo, 1 / 50, 800 if self.dropAnimation.state() == QAbstractAnimation.Running: start = self.dropAnimation.endValue() self.animate(self.collapseAnimation, start, end, curve, speed, delay) def shifted(self, tile: Tetris.Tile): start, end = self.pos(), QPointF(tile.row, tile.column) curve, speed, delay = QEasingCurve.OutBounce, 1 / 100, 1200 if self.dropAnimation.state() == QAbstractAnimation.Running: start = self.dropAnimation.endValue() self.animate(self.shiftAnimation, start, end, curve, speed, delay) def disappeared(self, tile: Tetris.Tile): self.qTetris.scene.removeItem(self) def paint(self, painter: QPainter, styleOption: QStyleOptionGraphicsItem, widget: QWidget = None): pen = QPen() pen.setWidthF(0.05) pen.setColor(Qt.darkGray) painter.setPen(pen) brush = QBrush() brush.setColor(self.color) brush.setStyle(Qt.SolidPattern) painter.setBrush(brush) topLeft = QPointF(0, 0) bottomRight = QPointF(1, 1) rectangle = QRectF(topLeft, bottomRight) rectangle.translate(-0.5, -0.5) painter.drawRect(rectangle) @staticmethod def animate(animation: QPropertyAnimation, start: Union[QPointF, int, float], end: Union[QPointF, int, float], curve: QEasingCurve = QEasingCurve.Linear, speed: float = 1 / 50, delay: int = -1): animation.setStartValue(start) animation.setEndValue(end) animation.setEasingCurve(curve) if type(start) == type(end) == QPointF: distance = (end - start).manhattanLength() else: distance = abs(end - start) animation.setDuration(round(distance / speed)) if delay == 0: animation.start() if delay > 0: QTimer.singleShot(delay, animation.start) def boundingRect(self): topLeft = QPointF(0, 0) bottomRight = QPointF(1, 1) rectangle = QRectF(topLeft, bottomRight) rectangle.translate(-0.5, -0.5) return rectangle
class Spoiler(QWidget): class Orientation(Enum): HORIZONTAL = 1 VERTICAL = 2 def __init__(self, orientation=Orientation.HORIZONTAL, animationDuration=120, parent=None): QWidget.__init__(self, parent) self.opened = False self.orientation = orientation self.animationDuration = animationDuration self.mainLayout = None self.animator = QParallelAnimationGroup() if orientation is self.Orientation.HORIZONTAL: self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setMaximumWidth(0) self.setMinimumWidth(0) # let the entire widget grow and shrink with its content self.animator.addAnimation(QPropertyAnimation(self)) self.animator.addAnimation( QPropertyAnimation(self, b"minimumWidth")) self.animator.addAnimation( QPropertyAnimation(self, b"maximumWidth")) elif orientation is self.Orientation.VERTICAL: self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setMaximumHeight(0) self.setMinimumHeight(0) # let the entire widget grow and shrink with its content self.animator.addAnimation(QPropertyAnimation(self)) self.animator.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.animator.addAnimation( QPropertyAnimation(self, b"maximumHeight")) def open(self): self.animator.setDirection(QAbstractAnimation.Forward) self.animator.start() self.opened = True def close(self): self.animator.setDirection(QAbstractAnimation.Backward) self.animator.start() self.opened = False def isOpened(self): return self.opened def setContentLayout(self, contentLayout): self.setLayout(contentLayout) if self.orientation is self.Orientation.HORIZONTAL: collapsedSize = self.maximumWidth() contentSize = contentLayout.sizeHint().width() elif self.orientation is self.Orientation.VERTICAL: collapsedSize = self.maximumHeight() contentSize = contentLayout.sizeHint().height() i = 0 while i < self.animator.animationCount(): animation = self.animator.animationAt(i) animation.setDuration(self.animationDuration) animation.setStartValue(collapsedSize) animation.setEndValue(collapsedSize + contentSize) i += 1
class RevolutionSliderEditor(ElementEditorInterface): def __init__(self): ElementEditorInterface.__init__(self) self.class_name = "RevolutionSliderEditor" self.display_name = "RevolutionSlider" self.tag_name = "RevolutionSlider" self.version = "1.0" self.icon = QImage(":/revolution.png") self.changed = False self.setAutoFillBackground(True) grid = QGridLayout() self.id = QLineEdit() self.id.setMaximumWidth(200) self.adminlabel = QLineEdit() self.adminlabel.setMaximumWidth(200) titleLabel = QLabel("Slider Module") fnt = titleLabel.font() fnt.setPointSize(16) fnt.setBold(True) titleLabel.setFont(fnt) close = FlatButton(":/images/close_normal.png", ":/images/close_hover.png") close.setToolTip("Close Editor") addSlide = QPushButton("Add Slide") addSlide.setMaximumWidth(120) self.list = QTableWidget(0, 2, self) self.list.verticalHeader().hide() self.list.setSelectionMode(QAbstractItemView.SingleSelection) self.list.setSelectionBehavior(QAbstractItemView.SelectRows) self.list.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Stretch) self.list.setToolTip("Double click to edit item") labels = ["", "Name"] self.list.setHorizontalHeaderLabels(labels) grid.addWidget(titleLabel, 0, 0) grid.addWidget(close, 0, 2, 1, 1, Qt.AlignRight) grid.addWidget(addSlide, 1, 0) grid.addWidget(self.list, 2, 0, 1, 3) grid.addWidget(QLabel("Id"), 4, 0) grid.addWidget(self.id, 5, 0) grid.addWidget(QLabel("Admin Label"), 6, 0) grid.addWidget(self.adminlabel, 7, 0) self.setLayout(grid) addSlide.clicked.connect(self.addSlide) self.adminlabel.textChanged.connect(self.contentChanged) self.id.textChanged.connect(self.contentChanged) close.clicked.connect(self.closeEditor) self.list.cellDoubleClicked.connect(self.tableDoubleClicked) self.installEventFilter(self) def closeEditor(self): if self.changed: if self.content: self.content.removeSlides() self.content.adminlabel = self.adminlabel.text() #self.content.text = html.escape(self.html.toPlainText()) for i in range(self.list.rowCount()): item = self.list.item(i, 1) slide = item.data(Qt.UserRole) self.content.addSlide(slide) self.close.emit() def registerContenType(self): qmlRegisterType(RevolutionSlider, 'RevolutionSlider', 1, 0, 'RevolutionSlider') qmlRegisterType(Slide, 'RevolutionSlider', 1, 0, 'Slide') def getImportString(self): return "import RevolutionSlider 1.0\n" def pluginStyles(self): return "<link href=\"assets/plugins/revolution-slider/css/settings.css\" rel=\"stylesheet\" type=\"text/css\"/>\n" def pluginScripts(self): script = "<script type=\"text/javascript\" src=\"assets/plugins/revolution-slider/js/jquery.themepunch.plugins.min.js\"></script>\n" script += "<script type=\"text/javascript\" src=\"assets/plugins/revolution-slider/js/jquery.themepunch.revolution.min.js\"></script>\n" script += "<script type=\"text/javascript\" src=\"assets/js/slider_revolution.js\"></script>\n" return script def installAssets(self, assets_path): assets = QDir(assets_path) assets.mkdir("plugins") assets.cd("plugins") assets.mkdir("revolution-slider") assets.cd("revolution-slider") assets.mkdir("css") assets.mkdir("js") assets.mkdir("assets") QFile.copy(":/css", assets_path + "/plugins/revolution-slider/css") QFile.copy(":/js", assets_path + "/js") QFile.copy(":/js/plugins", assets_path + "/plugins/revolution-slider/js") QFile.copy(":/assets", assets_path + "/plugins/revolution-slider/assets") def getDefaultContent(self): return RevolutionSlider() def setContent(self, content): self.content = content if content: #self.adminlabel.setText(content.adminlabel) self.changed = False self.list.setRowCount(0) for slide in content._items: self.addListItem(slide) self.changed = False def getContent(self): return self.content def addSlide(self): slide = Slide() self.addListItem(slide) self.contentChanged() self.tableDoubleClicked(self.list.rowCount() - 1) def addListItem(self, slide): rows = self.list.rowCount() self.list.setRowCount(rows + 1) tcb = TableCellButtons() tcb.setItem(slide) tcb.deleteItem.connect(self.deleteSlide) tcb.editItem.connect(self.editSlide) self.list.setCellWidget(rows, 0, tcb) self.list.setRowHeight(rows, tcb.sizeHint().height()) titleItem = QTableWidgetItem(slide.title) titleItem.setFlags(titleItem.flags() ^ Qt.ItemIsEditable) titleItem.setData(Qt.UserRole, slide) self.list.setItem(rows, 1, titleItem) def tableDoubleClicked(self, row): item = self.list.item(row, 1) slide = item.data(Qt.UserRole) self.editor = SlideEditor() self.editor.setSite(self.site) self.editor.setSlide(slide) self.editor.closes.connect(self.editorClosed) self.animate(item) def animate(self, item): self.row = item.row() # create a cell widget to get the right position in the table self.sourcewidget = QWidget() self.list.setCellWidget(self.row, 1, self.sourcewidget) pos = self.sourcewidget.mapTo(self, QPoint(0, 0)) self.editor.setParent(self) self.editor.move(pos) self.editor.resize(self.sourcewidget.size()) self.editor.show() self.animationgroup = QParallelAnimationGroup() self.animx = QPropertyAnimation() self.animx.setDuration(300) self.animx.setStartValue(pos.x()) self.animx.setEndValue(0) self.animx.setTargetObject(self.editor) self.animx.setPropertyName("x".encode("utf-8")) self.animationgroup.addAnimation(self.animx) self.animy = QPropertyAnimation() self.animy.setDuration(300) self.animy.setStartValue(pos.y()) self.animy.setEndValue(0) self.animy.setTargetObject(self.editor) self.animy.setPropertyName("y".encode("utf-8")) self.animationgroup.addAnimation(self.animy) self.animw = QPropertyAnimation() self.animw.setDuration(300) self.animw.setStartValue(self.sourcewidget.size().width()) self.animw.setEndValue(self.size().width()) self.animw.setTargetObject(self.editor) self.animw.setPropertyName("width".encode("utf-8")) self.animationgroup.addAnimation(self.animw) self.animh = QPropertyAnimation() self.animh.setDuration(300) self.animh.setStartValue(self.sourcewidget.size().height()) self.animh.setEndValue(self.size().height()) self.animh.setTargetObject(self.editor) self.animh.setPropertyName("height".encode("utf-8")) self.animationgroup.addAnimation(self.animh) self.animationgroup.finished.connect(self.animationFineshedZoomIn) self.animationgroup.start() def animationFineshedZoomIn(self): pass def editorClosed(self): pos = self.sourcewidget.mapTo(self, QPoint(0, 0)) # correct end values in case of resizing the window self.animx.setStartValue(pos.x()) self.animy.setStartValue(pos.y()) self.animw.setStartValue(self.sourcewidget.size().width()) self.animh.setStartValue(self.sourcewidget.size().height()) self.animationgroup.setDirection(QAbstractAnimation.Backward) #self.animationgroup.finished()), this, SLOT(animationFineshedZoomIn())) #connect(m_animationgroup, SIGNAL(finished()), this, SLOT(animationFineshedZoomOut())) self.animationgroup.start() item = self.list.item(self.row, 1) item.setData(Qt.UserRole, self.editor.slide) item.setText(self.editor.slide.title) if self.editor.changed: self.contentChanged() def animationFineshedZoomOut(self): #delete m_animationgroup #delete m_editor #self.editor = None pass def deleteSlide(self, slide): for row in range(self.list.rowCount()): item = self.list.item(row, 1) m = item.data(Qt.UserRole) if m == slide: self.list.removeRow(row) self.contentChanged() break def editSlide(self, slide): for row in range(self.list.rowCount()): item = self.list.item(row, 1) m = item.data(Qt.UserRole) if m == slide: self.list.selectRow(row) self.tableDoubleClicked(row) break
class AngleSet(QWidget): acted = pyqtSignal(int, BtnSource) def __init__(self, source, parent=None): super().__init__(parent) self.source = source self.active = 0 self.is_limited = False self.limit = 4 if self.source is BtnSource.HUNDREDS else 10 self.setMinimumSize(100, 105 + self.limit * 50) self.anim_gp = QParallelAnimationGroup() self.anim_gp.finished.connect(self.post_animation) y_offset = 55 self.digits = [] self.digits_pos = [idx for idx in range(self.limit)] for digit in range(self.limit): tmp_btn = AngleButton(digit, self) tmp_btn.move(0, y_offset) y_offset = y_offset + 50 tmp_btn.pressed.connect(self.switch_active) self.digits.append(tmp_btn) @pyqtSlot(int) def switch_active(self, target=None): if target is None: target = -1 for index in range(len(self.digits)): self.active = target self.digits[index].activate(False) return for index in range(len(self.digits)): cur = self.digits[index] if cur.index == self.active: cur.activate(False) if cur.index == target: cur.activate(True) self.active = target self.acted.emit(self.active, self.source) self.anim_set(target) def anim_set(self, target): self.anims = [] self.anim_gp.clear() diff = self.digits_pos.index(target) if not diff: return positions = self.digits_pos[diff:] + self.digits_pos[:diff] duration = diff * 50 * 1.5 self.loopers = [] y_offset = 55 + self.limit * 50 for position in positions: digit = self.digits[position] new_pos = positions.index(digit.index) cur_pos = self.digits_pos.index(digit.index) if new_pos > cur_pos: dummy = DummyButton(digit.index, self.limit, self) dummy.move(0, y_offset) y_offset = y_offset + 50 dummy.show() self.loopers.append(dummy) anim = QPropertyAnimation(dummy, b"pos", dummy) anim.setStartValue(dummy.pos()) anim.setEndValue( QPoint(dummy.pos().x(), dummy.pos().y() - diff * 50)) self.anims.append(anim) anim = QPropertyAnimation(digit, b"pos", digit) anim.setStartValue(digit.pos()) anim.setEndValue( QPoint(digit.pos().x(), digit.pos().y() - diff * 50)) self.anims.append(anim) digit.disable(True) for anim in self.anims: anim.setDuration(duration) anim.setEasingCurve(QEasingCurve.OutQuad) self.anim_gp.addAnimation(anim) self.anim_gp.start() self.digits_pos = positions @pyqtSlot() def post_animation(self): for looper in self.loopers: self.digits[looper.index].move(looper.pos()) looper.setParent(None) for digit in self.digits: digit.disable(False) self.parent().rectify() def reset(self): for digit in self.digits: digit.activate(False) self.active = -1 self.anim_set(0)
class AlbumCardViewer(QWidget): """ 定义一个专辑卡视图 """ playSignal = pyqtSignal(list) nextPlaySignal = pyqtSignal(list) albumNumChanged = pyqtSignal(int) saveAlbumInfoSig = pyqtSignal(dict, dict) addAlbumToPlayingSignal = pyqtSignal(list) # 将专辑添加到正在播放 switchToAlbumInterfaceSig = pyqtSignal(dict) selectionModeStateChanged = pyqtSignal(bool) checkedAlbumCardNumChanged = pyqtSignal(int) addAlbumToNewCustomPlaylistSig = pyqtSignal(list) # 将专辑添加到新建的播放列表 addAlbumToCustomPlaylistSig = pyqtSignal(str, list) # 将专辑添加到已存在的自定义播放列表 showLabelNavigationInterfaceSig = pyqtSignal(list, str) # 显示标签导航界面 def __init__(self, albumInfo_list: list, parent=None): super().__init__(parent) self.albumInfo_list = albumInfo_list # 初始化网格的列数 self.columnNum = 1 self.albumCard_list = [] # type:List[AlbumCard] self.albumCardDict_list = [] self.checkedAlbumCard_list = [] self.currentGroupDict_list = [] self.groupTitle_dict = {} # 记录首字母或年份及其对应的第一个分组 # 由键值对 "albumName.songer":albumCard组成的字典 self.albumSonger2AlbumCard_dict = {} # type:Dict[str,AlbumCard] # 初始化标志位 self.isInSelectionMode = False self.isAllAlbumCardsChecked = False # 设置当前排序方式 self.sortMode = "添加时间" # 分组标签列表 self.groupTitle_list = [] # 实例化滚动部件的竖直布局 self.scrollWidgetVBoxLayout = QVBoxLayout() # 实例化滚动区域和滚动区域的窗口 self.__createGuideLabel() self.scrollArea = ScrollArea(self) self.scrollWidget = QWidget() self.albumBlurBackground = AlbumBlurBackground(self.scrollWidget) # 创建专辑卡并将其添加到布局中 self.__createAlbumCards() # 创建线程 self.saveAlbumInfoObject = SaveInfoObject() self.saveInfoThread = QThread(self.parent()) self.saveAlbumInfoObject.moveToThread(self.saveInfoThread) # 初始化小部件 self.__initWidget() def __initWidget(self): """ 初始化小部件 """ self.resize(1270, 760) # 隐藏磨砂背景 self.albumBlurBackground.hide() # 设置导航标签的可见性 self.guideLabel.raise_() self.guideLabel.setHidden(bool(self.albumCard_list)) # 初始化滚动条 self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scrollWidget.setObjectName("scrollWidget") self.__connectSignalToSlot() self.__initLayout() self.__setQss() def __createGuideLabel(self): """ 创建导航标签 """ self.guideLabel = QLabel("这里没有可显示的内容。请尝试其他筛选器。", self) self.guideLabel.setStyleSheet( "color: black; font: 25px 'Microsoft YaHei'") self.guideLabel.resize(500, 26) self.guideLabel.move(35, 286) def __createAlbumCards(self): """ 将专辑卡添加到窗口中 """ # 创建并行动画组 self.hideCheckBoxAniGroup = QParallelAnimationGroup(self) self.hideCheckBoxAni_list = [] for albumInfo in self.albumInfo_list: self.__createOneAlbumCard(albumInfo) def __createOneAlbumCard(self, albumInfo: dict): """ 创建一个专辑卡 """ # 实例化专辑卡和动画 albumCard = AlbumCard(albumInfo, self) # 创建动画 hideCheckBoxAni = QPropertyAnimation(albumCard.checkBoxOpacityEffect, b"opacity") self.hideCheckBoxAniGroup.addAnimation(hideCheckBoxAni) self.hideCheckBoxAni_list.append(hideCheckBoxAni) # 将含有专辑卡及其信息的字典插入列表 album = albumInfo["album"] self.albumCard_list.append(albumCard) self.albumCardDict_list.append({ "albumCard": albumCard, "albumName": album, "year": albumInfo["year"][:4], "songer": albumInfo["songer"], "firstLetter": pinyin.get_initial(album[0])[0].upper(), }) self.albumSonger2AlbumCard_dict[albumInfo["album"] + "." + albumInfo["songer"]] = albumCard # 专辑卡信号连接到槽函数 albumCard.playSignal.connect(self.playSignal) albumCard.nextPlaySignal.connect(self.nextPlaySignal) albumCard.saveAlbumInfoSig.connect(self.__saveAlbumInfoSlot) albumCard.deleteCardSig.connect(self.showDeleteOneCardPanel) albumCard.addToPlayingSignal.connect(self.addAlbumToPlayingSignal) albumCard.switchToAlbumInterfaceSig.connect( self.switchToAlbumInterfaceSig) albumCard.checkedStateChanged.connect( self.__albumCardCheckedStateChangedSlot) albumCard.showBlurAlbumBackgroundSig.connect( self.__showBlurAlbumBackground) albumCard.hideBlurAlbumBackgroundSig.connect( self.albumBlurBackground.hide) albumCard.addAlbumToCustomPlaylistSig.connect( self.addAlbumToCustomPlaylistSig) albumCard.addAlbumToNewCustomPlaylistSig.connect( self.addAlbumToNewCustomPlaylistSig) albumCard.showAlbumInfoEditPanelSig.connect( self.__showAlbumInfoEditPanelSlot) def __connectSignalToSlot(self): """ 将信号连接到槽函数 """ # 动画完成隐藏复选框 self.hideCheckBoxAniGroup.finished.connect(self.__hideAllCheckBox) def __initLayout(self): """ 初始化布局 """ # 按照添加时间分组 self.sortByAddTime() self.scrollWidgetVBoxLayout.setSpacing(30) # 顶部留出工具栏的位置 self.scrollWidgetVBoxLayout.setContentsMargins(10, 245, 0, 120) self.scrollWidget.setLayout(self.scrollWidgetVBoxLayout) self.scrollArea.setWidget(self.scrollWidget) def resizeEvent(self, event): """ 根据宽度调整网格的列数 """ super().resizeEvent(event) self.scrollArea.resize(self.size()) # 如果第一次超过1337就调整网格的列数 if self.width() >= 1790 and self.columnNum != 8: self.__updateColumnNum(8) if 1570 <= self.width() < 1790 and self.columnNum != 7: self.__updateColumnNum(7) elif 1350 <= self.width() < 1570 and self.columnNum != 6: self.__updateColumnNum(6) elif 1130 < self.width() < 1350 and self.columnNum != 5: self.__updateColumnNum(5) elif 910 < self.width() <= 1130 and self.columnNum != 4: self.__updateColumnNum(4) elif 690 < self.width() <= 910: self.__updateColumnNum(3) elif self.width() <= 690: self.__updateColumnNum(2) # 调整滚动条 self.scrollArea.verticalScrollBar().move(-1, 40) self.scrollArea.verticalScrollBar().resize( self.scrollArea.verticalScrollBar().width(), self.height() - 156) def __updateColumnNum(self, columnNum: int): """ 更新网格列数 """ self.columnNum = columnNum for currentGroup_dict in self.currentGroupDict_list: gridLayout = currentGroup_dict["gridLayout"] # type:GridLayout gridLayout.updateColumnNum(columnNum, 210, 290) self.__adjustScrollWidgetSize() def __adjustScrollWidgetSize(self): """ 调整滚动部件的高度 """ rowCount = sum([ currentGroup_dict["gridLayout"].rowCount() for currentGroup_dict in self.currentGroupDict_list ]) containerCount = len(self.currentGroupDict_list) self.scrollWidget.resize( self.width(), 310 * rowCount + 60 * containerCount * (self.sortMode != "添加日期") + 120 + 245, ) def __removeContainerFromVBoxLayout(self): """ 从竖直布局中移除专辑卡容器 """ for currentGroup_dict in self.currentGroupDict_list: # 将专辑卡从每个网格布局中移除 currentGroup_dict["gridLayout"].removeAllWidgets() self.scrollWidgetVBoxLayout.removeWidget( currentGroup_dict["container"]) currentGroup_dict["container"].deleteLater() currentGroup_dict["gridLayout"].deleteLater() self.currentGroupDict_list = [] def __addContainterToVBoxLayout(self): """ 将当前的分组添加到箱式布局中 """ for currentGroup_dict in self.currentGroupDict_list: self.scrollWidgetVBoxLayout.addWidget( currentGroup_dict["container"], 0, Qt.AlignTop) def __addAlbumCardToGridLayout(self): """ 将专辑卡添加到每一个网格布局中 """ for currentGroup_dict in self.currentGroupDict_list: for index, albumCard in enumerate( currentGroup_dict["albumCard_list"]): row = index // self.columnNum column = index - row * self.columnNum currentGroup_dict["gridLayout"].addWidget( albumCard, row, column) currentGroup_dict["gridLayout"].setAlignment(Qt.AlignLeft) def sortByAddTime(self): """ 按照添加时间分组 """ self.sortMode = "添加时间" # 创建一个包含所有歌曲卡的网格布局 container = QWidget() gridLayout = GridLayout() gridLayout.setVerticalSpacing(20) gridLayout.setHorizontalSpacing(10) container.setLayout(gridLayout) # 清空分组标签列表 self.groupTitle_list.clear() # 从竖直布局中移除小部件 self.__removeContainerFromVBoxLayout() # 构造一个包含布局和小部件列表字典的列表 self.addTimeGroup_list = [{ "container": container, "gridLayout": gridLayout, "albumCard_list": self.albumCard_list, }] # 创建一个对当前分组列表引用的列表 self.currentGroupDict_list = self.addTimeGroup_list # 将专辑卡添加到布局中 self.__addAlbumCardToGridLayout() self.__addContainterToVBoxLayout() self.__adjustScrollWidgetSize() def sortByFirstLetter(self): """ 按照专辑名的首字母进行分组排序 """ self.sortMode = "A到Z" # 将专辑卡从旧布局中移除 self.__removeContainerFromVBoxLayout() self.groupTitle_list.clear() # 创建分组 firstLetter_list = [] self.firsetLetterGroupDict_list = [] # 将专辑卡添加到分组中 for albumCard_dict in self.albumCardDict_list: # 获取专辑卡的专辑名首字母(有可能不是字母) firstLetter = albumCard_dict["firstLetter"] firstLetter = firstLetter if 65 <= ord( firstLetter) <= 90 else "..." # 如果首字母属于不在列表中就将创建分组(仅限于A-Z和...) if firstLetter not in firstLetter_list: # firstLetter_list的首字母顺序和firsetLetterGroupDict_list保持一致 firstLetter_list.append(firstLetter) group = GroupBox(firstLetter) gridLayout = GridLayout() group.setLayout(gridLayout) gridLayout.setVerticalSpacing(20) gridLayout.setHorizontalSpacing(10) self.firsetLetterGroupDict_list.append({ "container": group, "firstLetter": firstLetter, "gridLayout": gridLayout, "albumCard_list": [], }) self.groupTitle_list.append(group.title()) # 将专辑卡添加到分组中 index = firstLetter_list.index(firstLetter) self.firsetLetterGroupDict_list[index]["albumCard_list"].append( albumCard_dict["albumCard"]) # 排序列表 self.firsetLetterGroupDict_list.sort( key=lambda item: item["firstLetter"]) # 将...分组移到最后 if "..." in firstLetter_list: unique_group = self.firsetLetterGroupDict_list.pop(0) self.firsetLetterGroupDict_list.append(unique_group) # 将专辑加到分组的网格布局中 self.currentGroupDict_list = self.firsetLetterGroupDict_list # 将专辑卡添加到网格布局中并将容器添加到竖直布局中 self.__addAlbumCardToGridLayout() self.__addContainterToVBoxLayout() self.__adjustScrollWidgetSize() self.__getFirstLetterFirstGroupBox() self.__connectGroupBoxSigToSlot("letterGridLayout") def sortByYear(self): """ 按照专辑的年份进行分组排序 """ self.sortMode = "发行年份" self.groupTitle_list.clear() self.groupTitle_dict.clear() # 将专辑卡从旧布局中移除 self.__removeContainerFromVBoxLayout() # 创建分组 year_list = [] self.yearGroupDict_list = [] # 将专辑加到分组中 for albumCard_dict in self.albumCardDict_list: year = albumCard_dict["year"] year = "未知" if year == "未知年份" else year # 如果年份不在年份列表中就创建分组 if year not in year_list: year_list.append(year) # 实例化分组和网格布局 group = GroupBox(year) gridLayout = GridLayout() group.setLayout(gridLayout) gridLayout.setVerticalSpacing(20) gridLayout.setHorizontalSpacing(10) self.yearGroupDict_list.append({ "year": year, "container": group, "albumCard_list": [], "gridLayout": gridLayout, }) self.groupTitle_list.append(group.title()) self.groupTitle_dict[year] = group # 将专辑卡添加到分组中 index = year_list.index(year) self.yearGroupDict_list[index]["albumCard_list"].append( albumCard_dict["albumCard"]) # 按照年份从进到远排序 self.groupTitle_list.sort(reverse=True) self.yearGroupDict_list.sort(key=lambda item: item["year"], reverse=True) # 检测是否含有未知分组,有的话将其移到最后一个 if "未知" in year_list: unique_group = self.yearGroupDict_list.pop(0) self.yearGroupDict_list.append(unique_group) # 将专辑加到分组的网格布局中 self.currentGroupDict_list = self.yearGroupDict_list self.__addAlbumCardToGridLayout() self.__addContainterToVBoxLayout() self.__adjustScrollWidgetSize() self.__connectGroupBoxSigToSlot("listLayout") def sortBySonger(self): """ 按照专辑的专辑进行分组排序 """ self.sortMode = "歌手" # 将专辑卡从旧布局中移除 self.groupTitle_list.clear() self.__removeContainerFromVBoxLayout() # 创建列表 songer_list = [] self.songerGroupDict_list = [] # 将专辑加到分组中 for albumCard_dict in self.albumCardDict_list: songer = albumCard_dict["songer"] if songer not in songer_list: songer_list.append(songer) group = GroupBox(songer) gridLayout = GridLayout() group.setLayout(gridLayout) gridLayout.setVerticalSpacing(20) gridLayout.setHorizontalSpacing(10) self.songerGroupDict_list.append({ "songer": songer, "container": group, "albumCard_list": [], "gridLayout": gridLayout, }) # 点击分组的标题时显示导航界面 self.groupTitle_list.append(group.title()) # 将专辑卡添加到分组中 index = songer_list.index(songer) self.songerGroupDict_list[index]["albumCard_list"].append( albumCard_dict["albumCard"]) # 排序列表 self.songerGroupDict_list.sort( key=lambda item: pinyin.get_initial(item["songer"])[0].lower()) # 将专辑加到分组的网格布局中 self.currentGroupDict_list = self.songerGroupDict_list self.__addAlbumCardToGridLayout() self.__addContainterToVBoxLayout() self.__adjustScrollWidgetSize() self.__getFirstLetterFirstGroupBox() self.__connectGroupBoxSigToSlot("letterGridLayout") def __setQss(self): """ 设置层叠样式 """ with open("app\\resource\\css\\albumCardViewer.qss", encoding="utf-8") as f: self.setStyleSheet(f.read()) def findAlbumCardByAlbumInfo(self, albumInfo: dict) -> AlbumCard: """ 通过albumInfo获取对AlbumCard实例的引用 """ return self.albumCard_list[self.albumInfo_list.index(albumInfo)] def findAlbumCardByName(self, albumName: str, songerName: str) -> AlbumCard: """ 通过名字查找专辑卡 """ albumCard = self.albumSonger2AlbumCard_dict.get( albumName + "." + songerName, None) return albumCard def __albumCardCheckedStateChangedSlot(self, albumCard: AlbumCard, isChecked: bool): """ 专辑卡选中状态改变对应的槽函数 """ # 如果专辑信息不在选中的专辑信息列表中且对应的专辑卡变为选中状态就将专辑信息添加到列表中 if albumCard not in self.checkedAlbumCard_list and isChecked: self.checkedAlbumCard_list.append(albumCard) self.checkedAlbumCardNumChanged.emit( len(self.checkedAlbumCard_list)) # 如果专辑信息已经在列表中且该专辑卡变为非选中状态就弹出该专辑信息 elif albumCard in self.checkedAlbumCard_list and not isChecked: self.checkedAlbumCard_list.pop( self.checkedAlbumCard_list.index(albumCard)) self.checkedAlbumCardNumChanged.emit( len(self.checkedAlbumCard_list)) # 如果先前不处于选择模式那么这次发生选中状态改变就进入选择模式 if not self.isInSelectionMode: # 所有专辑卡进入选择模式 self.__setAllAlbumCardSelectionModeOpen(True) # 发送信号要求主窗口隐藏播放栏 self.selectionModeStateChanged.emit(True) # 更新标志位 self.isInSelectionMode = True else: if not self.checkedAlbumCard_list: # 所有专辑卡退出选择模式 self.__setAllAlbumCardSelectionModeOpen(False) # 发送信号要求主窗口显示播放栏 self.selectionModeStateChanged.emit(False) # 更新标志位 self.isInSelectionMode = False def __setAllAlbumCardSelectionModeOpen(self, isOpenSelectionMode: bool): """ 设置所有专辑卡是否进入选择模式 """ for albumCard in self.albumCard_list: albumCard.setSelectionModeOpen(isOpenSelectionMode) # 退出选择模式时开启隐藏所有复选框的动画 if not isOpenSelectionMode: self.__startHideCheckBoxAni() def __startHideCheckBoxAni(self): """ 开始隐藏复选框动画 """ for ani in self.hideCheckBoxAni_list: ani.setStartValue(1) ani.setEndValue(0) ani.setDuration(140) self.hideCheckBoxAniGroup.start() def __hideAllCheckBox(self): """ 隐藏所有复选框 """ for albumCard in self.albumCard_list: albumCard.checkBox.hide() def unCheckAlbumCards(self): """ 取消所有已处于选中状态的专辑卡的选中状态 """ checkedAlbumCard_list_copy = self.checkedAlbumCard_list.copy() for albumCard in checkedAlbumCard_list_copy: albumCard.setChecked(False) def setAllAlbumCardCheckedState(self, isAllChecked: bool): """ 设置所有的专辑卡checked状态 """ if self.isAllAlbumCardsChecked == isAllChecked: return self.isAllAlbumCardsChecked = isAllChecked for albumCard in self.albumCard_list: albumCard.setChecked(isAllChecked) def __showBlurAlbumBackground(self, pos: QPoint, picPath: str): """ 显示磨砂背景 """ # 将全局坐标转为窗口坐标 pos = self.scrollWidget.mapFromGlobal(pos) self.albumBlurBackground.setBlurAlbum(picPath) self.albumBlurBackground.move(pos.x() - 31, pos.y() - 16) self.albumBlurBackground.show() def updateOneAlbumCardSongInfo(self, newSongInfo: dict): """ 更新一个专辑卡的一首歌的信息 """ key = newSongInfo["album"] + "." + newSongInfo["songer"] if key in self.albumSonger2AlbumCard_dict.keys(): albumInfo = self.albumSonger2AlbumCard_dict[key].albumInfo for i, songInfo in enumerate(albumInfo["songInfo_list"]): if songInfo["songPath"] == newSongInfo["songPath"]: albumInfo["songInfo_list"][i] = newSongInfo.copy() return albumInfo return {} def updateAllAlbumCards(self, albumInfo_list: list): """ 更新所有专辑卡 """ oldAlbumInfo_list = [ albumCard.albumInfo for albumCard in self.albumCard_list ] if albumInfo_list == oldAlbumInfo_list: return # 将专辑卡从布局中移除 self.__removeContainerFromVBoxLayout() # 根据具体情况增减专辑卡 newCardNum = len(albumInfo_list) oldCardNum = len(self.albumCard_list) deltaNum = newCardNum - oldCardNum if deltaNum < 0: for i in range(oldCardNum - 1, newCardNum - 1, -1): albumCard = self.albumCard_list.pop() self.hideCheckBoxAni_list.pop() self.albumCardDict_list.pop() self.hideCheckBoxAniGroup.takeAnimation(i) albumCard.deleteLater() elif deltaNum > 0: for albumInfo in albumInfo_list[oldCardNum:]: self.__createOneAlbumCard(albumInfo) QApplication.processEvents() # 更新部分专辑卡 self.albumInfo_list = albumInfo_list iterRange = range(oldCardNum) if deltaNum > 0 else range(newCardNum) for i in iterRange: albumInfo = albumInfo_list[i] album = albumInfo["album"] self.albumCard_list[i].updateWindow(albumInfo) QApplication.processEvents() self.albumCardDict_list[i] = { "albumCard": self.albumCard_list[i], "albumName": album, "year": albumInfo["year"][:4], "songer": albumInfo["songer"], "firstLetter": pinyin.get_initial(album)[0].upper(), } # 重新排序专辑卡 self.setSortMode(self.sortMode) # 根据当前专辑卡数决定是否显示导航标签 self.guideLabel.setHidden(bool(albumInfo_list)) # 更新 "专辑名.歌手名":专辑卡 字典 self.albumSonger2AlbumCard_dict = {} for albumCard in self.albumCard_list: albumInfo = albumCard.albumInfo self.albumSonger2AlbumCard_dict[albumInfo["album"] + "." + albumInfo["songer"]] = albumCard if deltaNum != 0: self.albumNumChanged.emit(newCardNum) def setSortMode(self, sortMode: str): """ 排序专辑卡 """ self.sortMode = sortMode if sortMode == "添加时间": self.sortByAddTime() elif sortMode == "A到Z": self.sortByFirstLetter() elif sortMode == "发行年份": self.sortByYear() elif sortMode == "歌手": self.sortBySonger() else: raise Exception(f'排序依据"{sortMode}"不存在') def showDeleteOneCardPanel(self, albumInfo: dict): """ 显示删除一个专辑卡的对话框 """ title = "是否确定要删除此项?" content = f"""如果删除"{albumInfo['album']}",它将不再位于此设备上。""" deleteCardPanel = DeleteCardPanel(title, content, self.window()) deleteCardPanel.deleteCardSig.connect( lambda: self.__deleteAlbumCards([albumInfo])) deleteCardPanel.exec_() def showDeleteMultiAlbumCardPanel(self, albumInfo_list: list): """ 显示删除多个专辑卡的对话框 """ title = "确定要删除这些项?" content = "如果你删除这些专辑,它们将不再位于此设备上。" deleteCardPanel = DeleteCardPanel(title, content, self.window()) deleteCardPanel.deleteCardSig.connect( lambda: self.__deleteAlbumCards(albumInfo_list)) deleteCardPanel.exec() def __deleteAlbumCards(self, albumInfo_list: list): """ 删除一个专辑卡 """ for albumInfo in albumInfo_list: self.albumInfo_list.remove(albumInfo) self.updateAllAlbumCards(self.albumInfo_list) def __connectGroupBoxSigToSlot(self, layout): """ 分组框信号连接到槽函数 """ for group_dict in self.currentGroupDict_list: group_dict["container"].titleClicked.connect( lambda: self.showLabelNavigationInterfaceSig.emit( self.groupTitle_list, layout)) def scrollToLabel(self, label: str): """ 滚动到label指定的位置 """ group = self.groupTitle_dict[label] self.scrollArea.verticalScrollBar().setValue(group.y() - 245) def __getFirstLetterFirstGroupBox(self): """ 获取首字母对应的第一个分组框 """ letter_list = [] self.groupTitle_dict.clear() for group_dict in self.currentGroupDict_list: group = group_dict["container"] letter = pinyin.get_initial(group.title())[0].upper() letter = "..." if not 65 <= ord(letter) <= 90 else letter # 将字母对应的第一个分组框添加到字典中 if letter not in letter_list: letter_list.append(letter) self.groupTitle_dict[letter] = group def __showAlbumInfoEditPanelSlot(self, albumInfoEditPanel): """ 显示专辑信息编辑界面信号 """ self.saveAlbumInfoObject.saveErrorSig.connect( albumInfoEditPanel.saveErrorSlot) self.saveAlbumInfoObject.saveCompleteSig.connect( albumInfoEditPanel.saveCompleteSlot) def __saveAlbumInfoSlot(self, oldAlbumInfo: dict, newAlbumInfo: dict): """ 保存专辑信息槽函数 """ # 更新字典 self.albumSonger2AlbumCard_dict.pop(oldAlbumInfo["album"] + "." + oldAlbumInfo["songer"]) self.albumSonger2AlbumCard_dict[ newAlbumInfo["album"] + "." + newAlbumInfo["songer"]] = self.sender() self.saveAlbumInfoObject.saveAlbumInfoSlot( newAlbumInfo["songInfo_list"]) self.saveAlbumInfoSig.emit(oldAlbumInfo, newAlbumInfo)
class BubbleLabel(QWidget): BackgroundColor = QColor(195, 195, 195) BorderColor = QColor(150, 150, 150) def __init__(self, *args, **kwargs): text = kwargs.pop("text", "") super(BubbleLabel, self).__init__(*args, **kwargs) # 设置无边框置顶 self.setWindowFlags(Qt.Window | Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint) # 设置最小宽度和高度 self.setMinimumWidth(200) self.setMinimumHeight(48) self.setAttribute(Qt.WA_TranslucentBackground, True) layout = QVBoxLayout(self) # 左上右下的边距(下方16是因为包括了三角形) layout.setContentsMargins(8, 8, 8, 16) self.label = QLabel(self) layout.addWidget(self.label) self.setText(text) # 获取屏幕高宽 self._desktop = QApplication.instance().desktop() def setText(self, text): self.label.setText(text) def text(self): return self.label.text() def stop(self): self.hide() self.animationGroup.stop() self.close() def show(self): super(BubbleLabel, self).show() # 窗口开始位置 startPos = QPoint( self._desktop.screenGeometry().width() - self.width() - 100, self._desktop.availableGeometry().height() - self.height()) endPos = QPoint( self._desktop.screenGeometry().width() - self.width() - 100, self._desktop.availableGeometry().height() - self.height() * 3 - 5) print(startPos, endPos) self.move(startPos) # 初始化动画 self.initAnimation(startPos, endPos) def initAnimation(self, startPos, endPos): # 透明度动画 opacityAnimation = QPropertyAnimation(self, b"opacity") opacityAnimation.setStartValue(1.0) opacityAnimation.setEndValue(0.0) # 设置动画曲线 opacityAnimation.setEasingCurve(QEasingCurve.InQuad) opacityAnimation.setDuration(4000) # 在4秒的时间内完成 # 往上移动动画 moveAnimation = QPropertyAnimation(self, b"pos") moveAnimation.setStartValue(startPos) moveAnimation.setEndValue(endPos) moveAnimation.setEasingCurve(QEasingCurve.InQuad) moveAnimation.setDuration(5000) # 在5秒的时间内完成 # 并行动画组(目的是让上面的两个动画同时进行) self.animationGroup = QParallelAnimationGroup(self) self.animationGroup.addAnimation(opacityAnimation) self.animationGroup.addAnimation(moveAnimation) self.animationGroup.finished.connect(self.close) # 动画结束时关闭窗口 self.animationGroup.start() def paintEvent(self, event): super(BubbleLabel, self).paintEvent(event) painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) # 抗锯齿 rectPath = QPainterPath() # 圆角矩形 triPath = QPainterPath() # 底部三角形 height = self.height() - 8 # 往上偏移8 rectPath.addRoundedRect(QRectF(0, 0, self.width(), height), 5, 5) x = self.width() / 5 * 4 triPath.moveTo(x, height) # 移动到底部横线4/5处 # 画三角形 triPath.lineTo(x + 6, height + 8) triPath.lineTo(x + 12, height) rectPath.addPath(triPath) # 添加三角形到之前的矩形上 # 边框画笔 painter.setPen( QPen(self.BorderColor, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) # 背景画刷 painter.setBrush(self.BackgroundColor) # 绘制形状 painter.drawPath(rectPath) # 三角形底边绘制一条线保证颜色与背景一样 painter.setPen( QPen(self.BackgroundColor, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) painter.drawLine(x, height, x + 12, height) def windowOpacity(self): return super(BubbleLabel, self).windowOpacity() def setWindowOpacity(self, opacity): super(BubbleLabel, self).setWindowOpacity(opacity) # 由于opacity属性不在QWidget中需要重新定义一个 opacity = pyqtProperty(float, windowOpacity, setWindowOpacity)
class QtBubbleLabel(QWidget): BackgroundColor = QColor(195, 195, 195) BorderColor = QColor(150, 150, 150) def __init__(self, *args, **kwargs): super(QtBubbleLabel, self).__init__(*args, **kwargs) self.setWindowFlags(Qt.Window | Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint) self.setMinimumWidth(200) self.setMinimumHeight(48) self.setAttribute(Qt.WA_TranslucentBackground, True) layout = QVBoxLayout(self) layout.setContentsMargins(8, 8, 8, 16) self.label = QLabel(self) layout.addWidget(self.label) self._desktop = QApplication.instance().desktop() self.animationGroup = QParallelAnimationGroup(self) def setText(self, text): self.label.setText(text) def text(self): return self.label.text() def stop(self): self.hide() self.animationGroup.stop() self.animationGroup.clear() self.close() def show(self): super(QtBubbleLabel, self).show() x = self.parent().geometry().x() y = self.parent().geometry().y() x2 = self.parent().size().width() y2 = self.parent().size().height() startPos = QPoint(x + int(x2 / 2) - int(self.width() / 2), y + int(y2 / 2)) endPos = QPoint(x + int(x2 / 2) - int(self.width() / 2), y + int(y2 / 2) - self.height() * 3 - 5) self.move(startPos) # 初始化动画 self.initAnimation(startPos, endPos) def initAnimation(self, startPos, endPos): # 透明度动画 opacityAnimation = QPropertyAnimation(self, b"opacity") opacityAnimation.setStartValue(1.0) opacityAnimation.setEndValue(0.0) # 设置动画曲线 opacityAnimation.setEasingCurve(QEasingCurve.InQuad) opacityAnimation.setDuration(3000) # 在4秒的时间内完成 # 往上移动动画 moveAnimation = QPropertyAnimation(self, b"pos") moveAnimation.setStartValue(startPos) moveAnimation.setEndValue(endPos) moveAnimation.setEasingCurve(QEasingCurve.InQuad) moveAnimation.setDuration(4000) # 在5秒的时间内完成 # 并行动画组(目的是让上面的两个动画同时进行) self.animationGroup.addAnimation(opacityAnimation) self.animationGroup.addAnimation(moveAnimation) self.animationGroup.finished.connect(self.close) # 动画结束时关闭窗口 self.animationGroup.start() def paintEvent(self, event): super(QtBubbleLabel, self).paintEvent(event) painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) # 抗锯齿 rectPath = QPainterPath() # 圆角矩形 height = self.height() - 8 # 往上偏移8 rectPath.addRoundedRect(QRectF(0, 0, self.width(), height), 5, 5) x = self.width() / 5 * 4 # 边框画笔 painter.setPen( QPen(self.BorderColor, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) # 背景画刷 painter.setBrush(self.BackgroundColor) # 绘制形状 painter.drawPath(rectPath) def windowOpacity(self): return super(QtBubbleLabel, self).windowOpacity() def setWindowOpacity(self, opacity): super(QtBubbleLabel, self).setWindowOpacity(opacity) opacity = pyqtProperty(float, windowOpacity, setWindowOpacity) def ShowMsg(self, text): self.stop() self.setText(text) self.setStyleSheet("color:black") self.show() @staticmethod def ShowMsgEx(owner, text): data = QtBubbleLabel(owner) data.setText(text) data.setStyleSheet("color:black") data.show() def ShowError(self, text): self.stop() self.setText(text) self.setStyleSheet("color:red") self.show() @staticmethod def ShowErrorEx(owner, text): data = QtBubbleLabel(owner) data.setText(text) data.setStyleSheet("color:red") data.show()
class Expander(QWidget): expanded = pyqtSignal(object) clicked = pyqtSignal() def __init__(self, header, svg): QWidget.__init__(self) self.svg = svg self.is_expanded = False self.text = header self.icon = QLabel() self.hyper = QLabel() self.setColors() self.setCursor(Qt.PointingHandCursor) self.setAttribute(Qt.WA_Hover, True) self.setAutoFillBackground(True) vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(self.icon) hbox.addSpacing(5) hbox.addWidget(self.hyper) hbox.addStretch() hbox.setContentsMargins(4, 4, 4, 4) vbox.addLayout(hbox) self.content = QWidget() self.content.setStyleSheet("background-color: " + self.palette().base().color().name()) self.content.setMaximumHeight(0) vbox.addWidget(self.content) vbox.setContentsMargins(0, 0, 0, 0) self.setLayout(vbox) self.hyper.linkActivated.connect(self.buttonClicked) self.anim = QParallelAnimationGroup() self.height_anim = QPropertyAnimation(self.content, "maximumHeight".encode("utf-8")) self.color_anim = QPropertyAnimation(self, "color".encode("utf-8")) self.height_anim.setDuration(200) self.color_anim.setDuration(200) self.anim.addAnimation(self.height_anim) self.anim.addAnimation(self.color_anim) def setColors(self): self.label_normal_color = self.palette().link().color().name() self.label_hovered_color = self.palette().highlight().color().name() self.label_selected_color = self.palette().highlightedText().color( ).name() self.normal_color = self.palette().base().color().name() self.selected_color = self.palette().highlight().color() self.hovered_color = self.palette().alternateBase().color() self.normal_icon = QPixmap(self.createIcon(self.svg, self.normal_color)) self.hovered_icon = QPixmap( self.createIcon(self.svg, self.label_hovered_color)) self.selected_icon = QPixmap( self.createIcon(self.svg, self.label_hovered_color)) self.icon.setPixmap(self.normal_icon) self.color = self.normal_color self.hyper.setText("<a style=\"color: " + self.label_normal_color + " text-decoration: none\" href=\"#\">" + self.text + "</a>") def createIcon(self, source, hilite_color): temp = QDir.tempPath() file = QFile(source) file.open(QIODevice.ReadOnly | QIODevice.Text) data = str(file.readAll(), encoding="utf-8") file.close() out = os.path.join(temp, hilite_color + ".svg") with open(out, "w") as fp: fp.write(data.replace("#ff00ff", hilite_color)) return out def setExpanded(self, value): if value == self.is_expanded: return if value: self.is_expanded = True pal = self.palette() pal.setColor(QPalette.Background, self.selected_color) self.setPalette(pal) self.icon.setPixmap(self.selected_icon) self.hyper.setText("<a style=\"color: " + self.label_selected_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") else: self.is_expanded = False pal = self.palette() pal.setColor(QPalette.Background, QColor(self.normal_color)) self.setPalette(pal) self.icon.setPixmap(self.normal_icon) self.hyper.setText("<a style=\"color: " + self.label_normal_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") if self.is_expanded: self.expandContent() else: self.collapseContent() self.expanded.emit(self.is_expanded) def addLayout(self, layout): self.content.setLayout(layout) @pyqtProperty('QColor') def color(self): return Qt.black @color.setter def color(self, color): pal = self.palette() pal.setColor(QPalette.Background, QColor(color)) self.setPalette(pal) def mouseReleaseEvent(self, me): if me.button() != Qt.LeftButton or me.y() > 32: return self.setExpanded(not self.is_expanded) if self.is_expanded: self.clicked.emit() def expandContent(self): if self.content.layout(): self.height_anim.setEndValue( self.content.layout().sizeHint().height()) else: self.height_anim.setEndValue(0) self.height_anim.setStartValue(0) self.color_anim.setStartValue(self.normal_color) self.color_anim.setEndValue(self.selected_color) self.anim.start() def collapseContent(self): if self.content.layout(): self.height_anim.setStartValue( self.content.layout().sizeHint().height()) else: self.height_anim.setStartValue(0) self.height_anim.setEndValue(0) self.color_anim.setStartValue(self.selected_color) self.color_anim.setEndValue(self.normal_color) self.anim.start() def buttonClicked(self): self.setExpanded(not self.is_expanded) if self.is_expanded: self.clicked.emit() def enterEvent(self, event): if not self.is_expanded: pal = self.palette() pal.setColor(QPalette.Background, QColor(self.hovered_color)) self.setPalette(pal) self.icon.setPixmap(self.hovered_icon) self.hyper.setText("<a style=\"color: " + self.label_hovered_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") QWidget.enterEvent(self, event) def leaveEvent(self, event): if not self.is_expanded: pal = self.palette() pal.setColor(QPalette.Background, QColor(self.normal_color)) self.setPalette(pal) self.icon.setPixmap(self.normal_icon) self.hyper.setText("<a style=\"color: " + self.label_normal_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") QWidget.leaveEvent(self, event)
class GroupWidget(QWidget): def __init__(self, parent=None, title='', animation_duration=300): """ References: # Adapted from c++ version http://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt """ super(GroupWidget, self).__init__(parent=parent) self.animation_duration = animation_duration self.toggle_animation = QParallelAnimationGroup() self.content_area = QScrollArea() self.header_line = QFrame() self.toggle_button = QToolButton() self.main_layout = QGridLayout() toggle_button = self.toggle_button toggle_button.setStyleSheet("QToolButton { border: none; }") toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) toggle_button.setArrowType(Qt.RightArrow) toggle_button.setText(str(title)) toggle_button.setCheckable(True) toggle_button.setChecked(False) header_line = self.header_line header_line.setFrameShape(QFrame.HLine) header_line.setFrameShadow(QFrame.Sunken) header_line.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.content_area.setStyleSheet( "QScrollArea { background-color: white; border: none; }") self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) # start out collapsed self.content_area.setMaximumHeight(0) self.content_area.setMinimumHeight(0) # let the entire widget grow and shrink with its content toggle_animation = self.toggle_animation toggle_animation.addAnimation( QPropertyAnimation(self, bytes("minimumHeight", "utf-8"))) toggle_animation.addAnimation( QPropertyAnimation(self, bytes("maximumHeight", "utf-8"))) toggle_animation.addAnimation( QPropertyAnimation(self.content_area, bytes("maximumHeight", "utf-8"))) # don't waste space main_layout = self.main_layout main_layout.setVerticalSpacing(0) main_layout.setContentsMargins(0, 0, 0, 0) row = 0 main_layout.addWidget(self.toggle_button, row, 0, 1, 1, Qt.AlignLeft) main_layout.addWidget(self.header_line, row, 2, 1, 1) row += 1 main_layout.addWidget(self.content_area, row, 0, 1, 3) self.setLayout(self.main_layout) def start_animation(checked): arrow_type = Qt.DownArrow if checked else Qt.RightArrow direction = QAbstractAnimation.Forward if checked else QAbstractAnimation.Backward toggle_button.setArrowType(arrow_type) self.toggle_animation.setDirection(direction) self.toggle_animation.start() self.toggle_button.clicked.connect(start_animation) def set_content_layout(self, content_layout): # Not sure if this is equivalent to self.contentArea.destroy() self.content_area.destroy() self.content_area.setLayout(content_layout) collapsed_height = self.sizeHint().height( ) - self.content_area.maximumHeight() content_height = content_layout.sizeHint().height() for i in range(self.toggle_animation.animationCount() - 1): spoiler_animation = self.toggle_animation.animationAt(i) spoiler_animation.setDuration(self.animation_duration) spoiler_animation.setStartValue(collapsed_height) spoiler_animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(self.animation_duration) content_animation.setStartValue(0) content_animation.setEndValue(content_height)
class CollapsibleBox(QWidget): def __init__(self, title="", parent=None): super(CollapsibleBox, self).__init__(parent) self.toggle_button = QToolButton(text=title, checkable=True, checked=False) self.toggle_button.setStyleSheet("QToolButton {border: none;\ border: 1px solid #FF17365D;\ border-top-left-radius: 15px;\ border-top-right-radius: 15px;\ background-color: #FF17365D;\ padding: 5px 0px;\ color: rgb(255, 255, 255);\ max-height: 30px;\ font-size: 14px;\ }\ QToolButton:hover {\ background-color: lightgreen;\ color: black;\ }") self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setArrowType(Qt.RightArrow) self.toggle_button.pressed.connect(self.on_pressed) self.toggle_animation = QParallelAnimationGroup(self) self.content_area = QScrollArea(maximumHeight=0, minimumHeight=0) self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) lay = QVBoxLayout(self) lay.setSpacing(0) lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(self.toggle_button) lay.addWidget(self.content_area) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self.content_area, b"maximumHeight")) @QtCore.pyqtSlot() def on_pressed(self): checked = self.toggle_button.isChecked() self.toggle_button.setArrowType( Qt.DownArrow if not checked else Qt.RightArrow) self.toggle_animation.setDirection( QAbstractAnimation.Forward if not checked else QAbstractAnimation. Backward) self.toggle_animation.start() def clear_layout(self, layout): try: for i in reversed(range(layout.count())): widgetToRemove = layout.itemAt(i).widget() layout.removeWidget(widgetToRemove) widgetToRemove.setPArent(None) except AttributeError: pass def setContentLayout(self, layout): lay = self.content_area.layout() self.clear_layout(lay) self.content_area.setLayout(layout) collapsed_height = (self.sizeHint().height() - self.content_area.maximumHeight()) content_height = layout.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(500) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(500) content_animation.setStartValue(0) content_animation.setEndValue(content_height)
class PopupPost(QMainWindow, Ui_PopupPost): def __init__(self, read_the_weibo): super().__init__( None, Qt.ToolTip | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) self.setupUi(self) self._close_timer = QTimer(self) self._close_timer.setSingleShot(True) self._close_timer.timeout.connect(self.close) self._read_the_weibo = read_the_weibo def setupUi(self, popup_post): super().setupUi(popup_post) self.setAttribute(Qt.WA_TranslucentBackground) desktop = QApplication.desktop() self.move(desktop.width() - 50 - self.width(), desktop.height() - 300 - self.height()) # 因为带浏览器的透明窗口不能渲染,浏览器放在独立窗口 self.content_view = WeiboWebView(self) self.content_view.setWindowOpacity(0) # 淡出淡入动画 self._window_fade_anim = QPropertyAnimation(self, b'windowOpacity', self) self._window_fade_anim.setDuration(300) self._window_fade_anim.setStartValue(0) self._window_fade_anim.setEndValue(0.5) self._content_fade_anim = QPropertyAnimation(self.content_view, b'windowOpacity', self) self._content_fade_anim.setDuration(300) self._content_fade_anim.setStartValue(0) self._content_fade_anim.setEndValue(0.7) self._fade_anim_group = QParallelAnimationGroup(self) self._fade_anim_group.addAnimation(self._window_fade_anim) self._fade_anim_group.addAnimation(self._content_fade_anim) self._fade_anim_group.finished.connect(self._on_anim_finish) def moveEvent(self, event): logger.debug('moveEvent') # 如果在这里更新,content_widget位置和尺寸不对 QTimer.singleShot(0, self._update_content_view_geometry) def resizeEvent(self, event): logger.debug('resizeEvent') # 如果在这里更新,content_widget位置和尺寸不对 QTimer.singleShot(0, self._update_content_view_geometry) def _update_content_view_geometry(self): pos = self.mapToGlobal(self.content_widget.pos()) size = self.content_widget.size() logger.debug('content_widget: (%d, %d) %d x %d', pos.x(), pos.y(), size.width(), size.height()) self.content_view.setGeometry(pos.x(), pos.y(), size.width(), size.height()) def show_post(self, post): """ 显示一条微博 :param post: 微博 """ self.show() self.content_view.show_post(post) # 淡入 self._fade_anim_group.setDirection(QPropertyAnimation.Forward) self._fade_anim_group.start() # 如果只弹窗不发声则过一段时间自动关闭 if not self._read_the_weibo.speak_post: content_len = len(post.content) if post.is_repost: content_len += len(post.original_post.content) self._close_timer.start(int((3 + content_len * 0.1) * 1000)) def closeEvent(self, event): """ 取消关闭事件,改成淡出、隐藏窗口 """ logger.debug('closeEvent') event.ignore() self._close_timer.stop() if (self._fade_anim_group.state() != QPropertyAnimation.Running or self._fade_anim_group.direction() != QPropertyAnimation.Backward): self._read_the_weibo.on_popup_post_close() # 淡出 self._fade_anim_group.setDirection(QPropertyAnimation.Backward) self._fade_anim_group.start() def _on_anim_finish(self): if self._fade_anim_group.direction() == QPropertyAnimation.Backward: logger.debug('_on_anim_finish, direction = Backward') self.content_view.hide() self.hide() self._read_the_weibo.on_popup_post_hide() else: logger.debug('_on_anim_finish, direction = Forward')
class PlaylistCardInterface(QWidget): """ 播放列表卡界面 """ playSig = pyqtSignal(list) nextToPlaySig = pyqtSignal(list) deletePlaylistSig = pyqtSignal(dict) renamePlaylistSig = pyqtSignal(dict, dict) selectionModeStateChanged = pyqtSignal(bool) def __init__(self, playlists: list, parent=None): super().__init__(parent) self.columnNum = 1 self.sortMode = 'modifiedTime' self.playlists = deepcopy(playlists) self.playlistCard_list = [] self.playlistCardDict_list = [] # type:list[dict] self.checkedPlaylistCard_list = [] self.isInSelectionMode = False self.isAllPlaylistCardChecked = False # 创建小部件 self.__createWidgets() # 初始化 self.__initWidget() def __createWidgets(self): """ 创建小部件 """ # 创建磨砂背景 self.scrollArea = ScrollArea(self) self.scrollWidget = QWidget(self) self.gridLayout = GridLayout() self.blurBackground = BlurBackground(self.scrollWidget) # 创建播放列表卡 self.__createPlaylistCards() # 创建白色遮罩 self.whiteMask = QWidget(self) self.sortModeLabel = QLabel('排序依据:', self) self.playlistLabel = QLabel('播放列表', self) self.sortModeButton = QPushButton('修改日期', self) self.createPlaylistButton = ThreeStateButton( { 'normal': r'resource\images\playlist_card_interface\newPlaylist_normal.png', 'hover': r'resource\images\playlist_card_interface\newPlaylist_hover.png', 'pressed': r'resource\images\playlist_card_interface\newPlaylist_pressed.png' }, self, (129, 19)) # 创建导航标签 self.guideLabel = QLabel('这里没有可显示的内容。请尝试其他筛选器。', self) self.guideLabel.setStyleSheet( "color: black; font: 25px 'Microsoft YaHei'") self.guideLabel.resize(500, 26) self.guideLabel.move(44, 196) # 创建排序菜单 self.sortModeMenu = AeroMenu(parent=self) self.sortByModifiedTimeAct = QAction( '修改时间', self, triggered=lambda: self.__sortPlaylist('modifiedTime')) self.sortByAToZAct = QAction( 'A到Z', self, triggered=lambda: self.__sortPlaylist('AToZ')) self.sortAct_list = [self.sortByModifiedTimeAct, self.sortByAToZAct] # 创建选择状态栏 self.selectionModeBar = SelectionModeBar(self) # 记录当前的排序方式 self.currentSortAct = self.sortByModifiedTimeAct def __createPlaylistCards(self): """ 创建播放列表卡 """ # 创建并行动画组 self.hideCheckBoxAniGroup = QParallelAnimationGroup(self) self.hideCheckBoxAni_list = [] for playlist in self.playlists: self.__createOnePlaylistCard(playlist) def __createOnePlaylistCard(self, playlist: dict): """ 创建一个播放列表卡 """ playlistCard = PlaylistCard(playlist, self) self.playlistCard_list.append(playlistCard) self.playlistCardDict_list.append({ 'playlistCard': playlistCard, 'playlist': playlist }) # 创建动画 hideCheckBoxAni = QPropertyAnimation( playlistCard.checkBoxOpacityEffect, b'opacity') self.hideCheckBoxAniGroup.addAnimation(hideCheckBoxAni) self.hideCheckBoxAni_list.append(hideCheckBoxAni) # 信号连接到槽 playlistCard.showBlurBackgroundSig.connect(self.__showBlurBackground) playlistCard.hideBlurBackgroundSig.connect(self.blurBackground.hide) playlistCard.renamePlaylistSig.connect(self.__showRenamePlaylistPanel) playlistCard.deleteCardSig.connect(self.__showDeleteCardPanel) playlistCard.playSig.connect(self.playSig) playlistCard.checkedStateChanged.connect( self.__playlistCardCheckedStateChangedSlot) playlistCard.nextToPlaySig.connect(self.nextToPlaySig) def __initWidget(self): """ 初始化小部件 """ # 隐藏小部件 self.blurBackground.hide() self.selectionModeBar.hide() self.guideLabel.setHidden(bool(self.playlistCard_list)) # 初始化滚动条 self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 将动作添加到菜单中 self.sortModeMenu.addActions(self.sortAct_list) # 分配ID和属性 self.scrollWidget.setObjectName('scrollWidget') self.playlistLabel.setObjectName('playlistLabel') self.sortModeLabel.setObjectName('sortModeLabel') self.sortModeButton.setObjectName('sortModeButton') self.sortModeMenu.setObjectName('sortModeMenu') self.sortModeMenu.setProperty('modeNumber', '2') self.__setQss() self.__initLayout() self.resize(1270, 760) self.__connectSignalToSlot() def __initLayout(self): """ 初始化布局 """ self.scrollArea.move(0, 0) self.playlistLabel.move(30, 54) self.sortModeLabel.move(190, 135) self.sortModeButton.move(264, 130) self.createPlaylistButton.move(30, 135) self.selectionModeBar.move( 0, self.height() - self.selectionModeBar.height()) # 设置布局的间距和外边距 self.gridLayout.setVerticalSpacing(20) self.gridLayout.setHorizontalSpacing(10) self.gridLayout.setContentsMargins(15, 175, 15, 120) self.scrollArea.setWidget(self.scrollWidget) self.scrollWidget.setLayout(self.gridLayout) # 如果没有播放列表就直接返回 if not self.playlistCard_list: return # 按照修改日期排序播放列表 self.__sortPlaylist('modifiedTime') def __setQss(self): """ 设置层叠样式 """ with open(r'resource\css\playlistCardInterface.qss', encoding='utf-8') as f: self.setStyleSheet(f.read()) def resizeEvent(self, e): """ 调整小部件尺寸和位置 """ super().resizeEvent(e) self.scrollArea.resize(self.size()) self.whiteMask.resize(self.width() - 15, 175) self.scrollArea.verticalScrollBar().resize(4, self.height() - 116) self.scrollArea.verticalScrollBar().move(-1, 40) self.selectionModeBar.resize(self.width(), self.selectionModeBar.height()) if self.width() < 641 and self.columnNum != 1: self.__setColumnNum(1) elif 641 <= self.width() < 954 and self.columnNum != 2: self.__setColumnNum(2) elif 954 <= self.width() < 1267 and self.columnNum != 3: self.__setColumnNum(3) elif 1267 <= self.width() < 1580 and self.columnNum != 4: self.__setColumnNum(4) elif 1580 <= self.width() < 1893 and self.columnNum != 5: self.__setColumnNum(5) elif self.width() >= 1893 and self.columnNum != 6: self.__setColumnNum(6) def __setColumnNum(self, columnNum: int): """ 设置网格列数 """ self.columnNum = columnNum self.gridLayout.updateColumnNum(columnNum, 298, 288) self.scrollWidget.resize(self.width(), 175 + self.gridLayout.rowCount() * 298 + 120) def __sortPlaylist(self, key): """ 排序播放列表 """ self.sortMode = key if key == 'modifiedTime': self.sortModeButton.setText('修改时间') self.currentSortAct = self.sortByModifiedTimeAct self.playlistCardDict_list.sort( key=self.__sortPlaylistByModifiedTime, reverse=True) else: self.sortModeButton.setText('A到Z') self.currentSortAct = self.sortByAToZAct self.playlistCardDict_list.sort(key=self.__sortPlaylistByAToZ, reverse=False) # 先将小部件布局中移除 self.gridLayout.removeAllWidgets() # 将小部件添加到布局中 for index, playlistCard_dict in enumerate(self.playlistCardDict_list): row = index // self.columnNum column = index - self.columnNum * row playlistCard = playlistCard_dict['playlistCard'] self.gridLayout.addWidget(playlistCard, row, column, Qt.AlignLeft) def __showBlurBackground(self, pos: QPoint, playlistCoverPath: str): """ 显示磨砂背景 """ # 将全局坐标转换为窗口坐标 pos = self.scrollWidget.mapFromGlobal(pos) self.blurBackground.setBlurPic(playlistCoverPath, 40) self.blurBackground.move(pos.x() - 30, pos.y() - 20) self.blurBackground.show() def __showSortModeMenu(self): """ 显示排序方式菜单 """ # 设置默认选中动作 self.sortModeMenu.setDefaultAction(self.currentSortAct) actIndex = self.sortAct_list.index(self.currentSortAct) self.sortModeMenu.exec( self.mapToGlobal( QPoint(self.sender().x(), self.sender().y() - 37 * actIndex - 1))) def __playlistCardCheckedStateChangedSlot(self, playlistCard: PlaylistCard, isChecked: bool): """ 播放列表卡选中状态改变槽函数 """ # 如果专辑信息不在选中的专辑信息列表中且对应的专辑卡变为选中状态就将专辑信息添加到列表中 if playlistCard not in self.checkedPlaylistCard_list and isChecked: self.checkedPlaylistCard_list.append(playlistCard) self.__checkPlaylistCardNumChangedSlot( len(self.checkedPlaylistCard_list)) # 如果专辑信息已经在列表中且该专辑卡变为非选中状态就弹出该专辑信息 elif playlistCard in self.checkedPlaylistCard_list and not isChecked: self.checkedPlaylistCard_list.pop( self.checkedPlaylistCard_list.index(playlistCard)) self.__checkPlaylistCardNumChangedSlot( len(self.checkedPlaylistCard_list)) # 如果先前不处于选择模式那么这次发生选中状态改变就进入选择模式 if not self.isInSelectionMode: # 所有专辑卡进入选择模式 self.__setAllPlaylistCardSelectionModeOpen(True) # 发送信号要求主窗口隐藏播放栏 self.selectionModeStateChanged.emit(True) self.selectionModeBar.show() # 更新标志位 self.isInSelectionMode = True else: if not self.checkedPlaylistCard_list: # 所有专辑卡退出选择模式 self.__setAllPlaylistCardSelectionModeOpen(False) # 发送信号要求主窗口显示播放栏 self.selectionModeBar.hide() self.selectionModeStateChanged.emit(False) # 更新标志位 self.isInSelectionMode = False def __setAllPlaylistCardSelectionModeOpen(self, isOpenSelectionMode: bool): """ 设置所有播放列表卡是否进入选择模式 """ for playlistCard in self.playlistCard_list: playlistCard.setSelectionModeOpen(isOpenSelectionMode) # 退出选择模式时开启隐藏所有复选框的动画 if not isOpenSelectionMode: self.__startHideCheckBoxAni() def __startHideCheckBoxAni(self): """ 开始隐藏复选框动画 """ for ani in self.hideCheckBoxAni_list: ani.setStartValue(1) ani.setEndValue(0) ani.setDuration(140) self.hideCheckBoxAniGroup.start() def __hideAllCheckBox(self): """ 隐藏所有复选框 """ for playlistCard in self.playlistCard_list: playlistCard.checkBox.hide() def __unCheckPlaylistCards(self): """ 取消所有已处于选中状态的播放列表卡的选中状态 """ checkedPlaylistCard_list_copy = self.checkedPlaylistCard_list.copy() for playlistCard in checkedPlaylistCard_list_copy: playlistCard.setChecked(False) # 更新按钮的图标为全选 self.selectionModeBar.checkAllButton.setCheckedState(True) def setAllPlaylistCardCheckedState(self, isAllChecked: bool): """ 设置所有的专辑卡checked状态 """ if self.isAllPlaylistCardChecked == isAllChecked: return self.isAllPlaylistCardChecked = isAllChecked for playlistCard in self.playlistCard_list: playlistCard.setChecked(isAllChecked) def __checkPlaylistCardNumChangedSlot(self, num: int): """ 选中的歌曲卡数量改变对应的槽函数 """ self.selectionModeBar.setPartButtonHidden(num > 1) self.selectionModeBar.move( 0, self.height() - self.selectionModeBar.height()) def __checkAllButtonSlot(self): """ 全选/取消全选按钮槽函数 """ self.setAllPlaylistCardCheckedState(not self.isAllPlaylistCardChecked) def __sortPlaylistByModifiedTime(self, playlistCard_dict: dict) -> str: return playlistCard_dict['playlist']['modifiedTime'] def __sortPlaylistByAToZ(self, playlistCard_dict: dict) -> str: return pinyin.get_initial( playlistCard_dict['playlist']['playlistName'])[0].lower() def addOnePlaylistCard(self, playlist: dict): """ 添加一个播放列表卡 """ self.__createOnePlaylistCard(playlist) self.playlists.append(playlist) self.guideLabel.hide() # 向布局添加小部件 self.gridLayout.appendWidget(self.playlistCard_list[-1]) self.scrollWidget.resize(self.width(), 175 + self.gridLayout.rowCount() * 298 + 120) # 按照当前排序方式重新排序播放列表卡 self.__sortPlaylist(self.sortMode) def __showRenamePlaylistPanel(self, oldPlaylist: dict, playlistCard: PlaylistCard = None): """ 显示重命名播放列表面板 """ playlistCard = self.sender( ) if not playlistCard else playlistCard # type:PlaylistCard renamePlaylistPanel = RenamePlaylistPanel(oldPlaylist, self.window()) renamePlaylistPanel.renamePlaylistSig.connect( lambda oldPlaylist, newPlaylist: self.__renamePlaylistSlot( oldPlaylist, newPlaylist, playlistCard)) renamePlaylistPanel.exec() def __renamePlaylistSlot(self, oldPlaylist: dict, newPlaylist: dict, playlistCard: PlaylistCard): """ 重命名播放列表槽函数 """ playlistCard.updateWindow(newPlaylist) # 重新排序播放列表卡 index = self.playlists.index(oldPlaylist) self.playlists[index] = newPlaylist index = self.getIndexByPlaylist(oldPlaylist) self.playlistCardDict_list[index]['playlist'] = newPlaylist self.__sortPlaylist(self.sortMode) # 发送信号 self.renamePlaylistSig.emit(oldPlaylist, newPlaylist) def __showDeleteCardPanel(self, playlist: dict, playlistCard: PlaylistCard = None): """ 显示删除播放列表卡对话框 """ playlistCard = self.sender() if not playlistCard else playlistCard title = '是否确定要删除此项?' content = f"""如果删除"{playlist['playlistName']}",它将不再位于此设备上。""" deleteCardPanel = DeleteCardPanel(title, content, self.window()) deleteCardPanel.deleteCardSig.connect( lambda: self.__deleteOnePlaylistCard(playlistCard, playlist)) deleteCardPanel.exec() def __deleteOnePlaylistCard(self, playlistCard: PlaylistCard, playlist: dict): """ 删除一个播放列表卡 """ # 从布局中移除播放列表卡 self.gridLayout.removeWidget(playlistCard) # 从列表中弹出小部件 self.playlists.remove(playlist) self.playlistCard_list.remove(playlistCard) self.playlistCardDict_list.pop( self.getIndexByPlaylistCard(playlistCard)) # 删除播放列表卡 playlistCard.deleteLater() # 调整高度 self.scrollWidget.resize(self.width(), 175 + self.gridLayout.rowCount() * 298 + 120) # 删除json文件并发送删除播放列表的信号 remove(f'Playlists\\{playlist["playlistName"]}.json') self.deletePlaylistSig.emit(playlist) # 如果没有专辑卡就显示导航标签 self.guideLabel.setHidden(bool(self.playlistCard_list)) def __deleteMultiPlaylistCards(self, playlistCard_list: list, playlists: list): """ 删除多个播放列表卡 """ for playlistCard, playlist in zip(playlistCard_list, playlists): self.__deleteOnePlaylistCard(playlistCard, playlist) def __emitCheckedPlaylists(self): """ 发送选中的播放列表中的歌曲 """ # 发送播放列表 playlist = [] for playlistCard in self.checkedPlaylistCard_list: playlist.extend(playlistCard.songInfo_list) # 取消所有播放列表卡的选中 self.__unCheckPlaylistCards() if self.sender() is self.selectionModeBar.playButton: self.playSig.emit(playlist) elif self.sender() is self.selectionModeBar.nextToPlayButton: self.nextToPlaySig.emit(playlist) def __selectionBarRenameButtonSlot(self): """ 选择栏重命名按钮的槽函数 """ playlistCard = self.checkedPlaylistCard_list[0] self.__unCheckPlaylistCards() self.__showRenamePlaylistPanel(playlistCard.playlist, playlistCard) def __selectionModeBarDeleteButtonSlot(self): """ 选择栏删除按钮槽函数 """ if len(self.checkedPlaylistCard_list) == 1: playlistCard = self.checkedPlaylistCard_list[0] # 取消所有歌曲卡的选中 self.__unCheckPlaylistCards() self.__showDeleteCardPanel(playlistCard.playlist, playlistCard) else: title = '确定要删除这些项?' content = f"若删除这些播放列表,它们将不再位于此设备上。" playlistCard_list = self.checkedPlaylistCard_list[:] playlists = [ playlistCard.playlist for playlistCard in playlistCard_list ] # 取消所有歌曲卡的选中 self.__unCheckPlaylistCards() # 显示删除对话框 deleteCardPanel = DeleteCardPanel(title, content, self.window()) deleteCardPanel.deleteCardSig.connect( lambda: self.__deleteMultiPlaylistCards( playlistCard_list, playlists)) deleteCardPanel.exec() def addSongsToPlaylist(self, playlistName: str, songInfo_list: list) -> dict: """ 将歌曲添加到播放列表中,返回修改后的播放列表 """ # 直接修改播放列表卡字典中的播放列表 index = self.getIndexByPlaylistName(playlistName) playlistCard_dict = self.playlistCardDict_list[index] playlist = playlistCard_dict['playlist'] # 更新播放列表 playlist['modifiedTime'] = QDateTime.currentDateTime().toString( Qt.ISODate) playlist['songInfo_list'] = songInfo_list + \ playlist['songInfo_list'] playlistCard_dict['playlistCard'].updateWindow(playlist) # 更新json文件 with open(f'Playlists\\{playlistName}.json', 'w', encoding='utf-8') as f: dump(playlist, f) return playlist def getIndexByPlaylistName(self, playlistName: str) -> int: """ 通过播放列表名字获取播放列表在播放列表卡字典列表中的下标 """ for index, playlistCard_dict in enumerate(self.playlistCardDict_list): if playlistCard_dict['playlist']['playlistName'] == playlistName: return index raise Exception(f'指定的播放列表"{playlistName}"不存在') def getIndexByPlaylistCard(self, playlistCard: PlaylistCard) -> int: """ 通过播放列表卡获取播放列表在播放列表卡字典列表中的下标 """ for index, playlistCard_dict in enumerate(self.playlistCardDict_list): if playlistCard_dict['playlistCard'] is playlistCard: return index raise Exception(f'指定的播放列表卡"{playlistCard.playlistName}"不存在') def getIndexByPlaylist(self, playlist: dict) -> int: """ 通过播放列表获取播放列表在播放列表卡字典列表中的下标 """ for index, playlistCard_dict in enumerate(self.playlistCardDict_list): if playlistCard_dict['playlist'] == playlist: return index raise Exception(f"""指定的播放列表"{playlist['playlistName']}"不存在""") def exitSelectionMode(self): """ 退出选择模式 """ self.__unCheckPlaylistCards() def __connectSignalToSlot(self): """ 将信号连接到槽 """ self.sortModeButton.clicked.connect(self.__showSortModeMenu) self.selectionModeBar.cancelButton.clicked.connect( self.__unCheckPlaylistCards) self.selectionModeBar.checkAllButton.clicked.connect( self.__checkAllButtonSlot) self.selectionModeBar.playButton.clicked.connect( self.__emitCheckedPlaylists) self.selectionModeBar.nextToPlayButton.clicked.connect( self.__emitCheckedPlaylists) self.selectionModeBar.renameButton.clicked.connect( self.__selectionBarRenameButtonSlot) self.selectionModeBar.deleteButton.clicked.connect( self.__selectionModeBarDeleteButtonSlot)
class Spoiler(QWidget): def __init__(self, title="", animationDuration=300, parent=None): QWidget.__init__(self, parent) self.mainLayout = QGridLayout() self.toggleButton = QToolButton() self.toggleAnimation = QParallelAnimationGroup() self.contentArea = QScrollArea() self.headerLine = QFrame() self.animationDuration = animationDuration self.toggleButton.setStyleSheet("QToolButton { border: none; }") self.toggleButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggleButton.setArrowType(Qt.ArrowType.RightArrow) self.toggleButton.setText(title) self.toggleButton.setCheckable(True) self.toggleButton.setChecked(False) self.headerLine.setFrameShape(QFrame.HLine) self.headerLine.setFrameShadow(QFrame.Sunken) self.headerLine.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.contentArea.setStyleSheet( "QScrollArea { background-color: white; border: none; }") self.contentArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.contentArea.setMaximumHeight(0) self.contentArea.setMinimumHeight(0) # let the entire widget grow and shrink with its content self.toggleAnimation.addAnimation(QPropertyAnimation()) self.toggleAnimation.addAnimation(QPropertyAnimation(self)) self.toggleAnimation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggleAnimation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggleAnimation.addAnimation( QPropertyAnimation(self.contentArea, b"maximumHeight")) # don't waste space self.mainLayout.setVerticalSpacing(0) self.mainLayout.setContentsMargins(0, 0, 0, 0) row = 0 self.mainLayout.addWidget(self.toggleButton, 0, 0, 1, 1, Qt.AlignLeft) self.mainLayout.addWidget(self.headerLine, 0, 2, 1, 1) self.mainLayout.addWidget(self.contentArea, 1, 0, 1, 3) self.setLayout(self.mainLayout) self.toggleButton.clicked[bool].connect(self.expandShrinkContent) def expandShrinkContent(self, checked): if checked: self.toggleButton.setArrowType(Qt.ArrowType.DownArrow) self.toggleAnimation.setDirection(QAbstractAnimation.Forward) else: self.toggleButton.setArrowType(Qt.ArrowType.RightArrow) self.toggleAnimation.setDirection(QAbstractAnimation.Backward) self.toggleAnimation.start() def setContentLayout(self, contentLayout): self.contentArea.setLayout(contentLayout) collapsedHeight = self.sizeHint().height( ) - self.contentArea.maximumHeight() contentHeight = contentLayout.sizeHint().height() i = 0 while i < self.toggleAnimation.animationCount(): i += 1 spoilerAnimation = self.toggleAnimation.animationAt(i) if spoilerAnimation is not None: spoilerAnimation.setDuration(self.animationDuration) spoilerAnimation.setStartValue(collapsedHeight) spoilerAnimation.setEndValue(collapsedHeight + contentHeight) contentAnimation = self.toggleAnimation.animationAt( self.toggleAnimation.animationCount() - 1) contentAnimation.setDuration(self.animationDuration) contentAnimation.setStartValue(0) contentAnimation.setEndValue(contentHeight)
class ContentEditor(AnimateableEditor): contentChanged = pyqtSignal(object) preview = pyqtSignal(object) def __init__(self, win, site, content): AnimateableEditor.__init__(self) self.win = win self.site = site self.content = content self.is_new = False self.editor = None self.undoStack = QUndoStack() self.changed = False self.setAutoFillBackground(True) self.previewLink = HyperLink("") self.vbox = QVBoxLayout() self.layout = QGridLayout() self.titleLabel = QLabel() fnt = self.titleLabel.font() fnt.setPointSize(20) fnt.setBold(True) self.titleLabel.setFont(fnt) self.script = QPushButton("Page Script") self.title = QLineEdit() self.source = QLineEdit() self.source.setPlaceholderText("*.qml") self.excerpt = QLineEdit() self.date = QLineEdit() self.labelPermalink = QLabel("Permalink") self.labelTitle = QLabel("Title") self.labelAuthor = QLabel("Author") self.labelKeyword = QLabel("Keywords") self.labelLayout = QLabel("Layout") self.labelMenu = QLabel("Menu") self.author = QLineEdit() self.keywords = QLineEdit() self.menus = QComboBox() self.layouts = QComboBox() self.layouts.setMaximumWidth(100) for menu in self.site.menus.menus: self.menus.addItem(menu.name) for root, dirs, files in os.walk( os.path.join(self.site.source_path, "layouts")): for file in files: self.layouts.addItem(Path(file).stem) for root, dirs, files in os.walk( os.path.join(Generator.themesPath(), self.site.theme, "layouts")): for file in files: self.layouts.addItem(Path(file).stem) self.close = FlatButton(":/images/close_normal.png", ":/images/close_hover.png") self.close.setToolTip("Close Content Editor") self.undo = FlatButton(":/images/undo_normal.png", ":/images/undo_hover.png", "", ":/images/undo_disabled.png") self.redo = FlatButton(":/images/redo_normal.png", ":/images/redo_hover.png", "", ":/images/redo_disabled.png") self.undo.setToolTip("Undo") self.redo.setToolTip("Redo") self.undo.setEnabled(False) self.redo.setEnabled(False) hbox = QHBoxLayout() hbox.addWidget(self.undo) hbox.addWidget(self.redo) hbox.addWidget(self.close) self.scroll = QScrollArea() self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.scroll.setWidgetResizable(True) self.scroll.installEventFilter(self) self.layout.addWidget(self.titleLabel, 0, 0) self.layout.addWidget(self.previewLink, 0, 1) self.layout.addLayout(hbox, 0, 3) self.layout.addWidget(self.labelTitle, 1, 0) self.layout.addWidget(self.title, 2, 0) self.layout.addWidget(self.labelPermalink, 1, 1) self.layout.addWidget(self.source, 2, 1) self.layout.addWidget(self.labelAuthor, 3, 0) self.layout.addWidget(self.author, 4, 0) self.layout.addWidget(self.labelKeyword, 3, 1) self.layout.addWidget(self.keywords, 4, 1) self.layout.addWidget(self.labelMenu, 3, 2) self.layout.addWidget(self.menus, 4, 2) self.layout.addWidget(self.labelLayout, 3, 3) self.layout.addWidget(self.layouts, 4, 3) self.layout.addWidget(self.scroll, 7, 0, 1, 4) self.layout.addWidget(self.script, 8, 0, 1, 4) self.vbox.addLayout(self.layout) self.setLayout(self.vbox) if self.content.content_type == ContentType.POST: self.previewLink.setText("view post") self.excerptLabel = QLabel("Excerpt") self.layout.addWidget(self.excerptLabel, 5, 0) self.layout.addWidget(self.excerpt, 6, 0, 1, 2) self.datelabel = QLabel("Date") self.layout.addWidget(self.datelabel, 5, 2) self.layout.addWidget(self.date, 6, 2, 1, 2) self.filename = self.site.source_path + "/posts/" + content.source else: self.previewLink.setText("view page") self.filename = self.site.source_path + "/pages/" + content.source self.load() self.close.clicked.connect(self.closeEditor) self.title.editingFinished.connect(self.titleFinished) self.title.textChanged.connect(self.titleChanged) self.source.editingFinished.connect(self.sourceChanged) self.excerpt.editingFinished.connect(self.excerptChanged) self.date.editingFinished.connect(self.dateChanged) self.author.editingFinished.connect(self.authorChanged) self.keywords.editingFinished.connect(self.keywordsChanged) self.menus.currentTextChanged.connect(self.menuChanged) self.layouts.currentTextChanged.connect(self.layoutChanged) self.undoStack.canUndoChanged.connect(self.canUndoChanged) self.undoStack.canRedoChanged.connect(self.canRedoChanged) self.undoStack.undoTextChanged.connect(self.undoTextChanged) self.undoStack.redoTextChanged.connect(self.redoTextChanged) self.undo.clicked.connect(self.undoAction) self.redo.clicked.connect(self.redoAction) self.previewLink.clicked.connect(self.previewPage) self.script.clicked.connect(self.scriptClicked) def scriptClicked(self): self.editor = Plugins.element_plugins["TextEditor"] self.editor.setContent(None) self.editor.setText(self.content.script) self.editor.setCaption("Page Script") self.editor.close.connect(self.scriptEditorClose) self.animate(self.scroll.widget().placeholder) def scriptEditorClose(self): if self.editor and self.editor.changed: self.content.script = self.editor.getText() self.editChanged("Update Script") self.editor.close.disconnect() self.editorClosed() def previewPage(self): self.preview.emit(self.content) def rowEdit(self, re): from widgets.rowpropertyeditor import RowPropertyEditor self.row_editor = re self.editor = RowPropertyEditor() self.editor.setRow(re.row) self.editor.close.connect(self.rowEditorClose) self.animate(re) def rowEditorClose(self): if self.editor and self.editor.changed: self.row_editor.load(self.editor.row) self.editChanged("Update Row") self.editor.close.disconnect() self.editorClosed() def canUndoChanged(self, can): self.undo.setEnabled(can) def canRedoChanged(self, can): self.redo.setEnabled(can) def undoTextChanged(self, text): self.undo.setToolTip("Undo " + text) def redoTextChanged(self, text): self.redo.setToolTip("Redo " + text) def undoAction(self): self.undoStack.undo() def redoAction(self): self.undoStack.redo() def menuChanged(self, menu): if menu != self.content.menu: self.content.menu = menu self.contentChanged.emit(self.content) self.editChanged("Menu Changed") def layoutChanged(self, layout): if layout != self.content.layout: self.content.layout = layout self.contentChanged.emit(self.content) self.editChanged("Layout Changed") def keywordsChanged(self): if self.keywords.text() != self.content.keywords: self.content.keywords = self.keywords.text() self.contentChanged.emit(self.content) self.editChanged("Keywords Changed") def authorChanged(self): if self.author.text() != self.content.author: self.content.author = self.author.text() self.contentChanged.emit(self.content) self.editChanged("Author Changed") def excerptChanged(self): if self.excerpt.text() != self.content.excerpt: self.content.excerpt = self.excerpt.text() self.contentChanged.emit(self.content) self.editChanged("Excerpt Changed") def dateChanged(self): if self.date.text() != self.content.date.toString("dd.MM.yyyy"): self.content.date = QDate.fromString(self.date.text(), "dd.MM.yyyy") self.contentChanged.emit(self.content) self.editChanged("Date Changed") def sourceChanged(self): if self.source.text() != self.content.source: oldname = self.filename self.content.source = self.source.text() if self.content.content_type == ContentType.PAGE: self.filename = self.site.source_path + "/pages/" + self.content.source else: self.filename = self.site.source_path + "/posts/" + self.content.source self.contentChanged.emit(self.content) renameCommand = RenameContentCommand(self, oldname, self.filename, "content file renamed") self.undoStack.push(renameCommand) def titleChanged(self, title): if self.is_new: source = title.lower().replace(" ", "_") + ".qml" self.source.setText(source) def titleFinished(self): if self.title.text() != self.content.title: if self.is_new: self.sourceChanged() self.content.title = self.title.text() self.contentChanged.emit(self.content) self.editChanged("Titel Changed") def sectionEdit(self, se): self.section_editor = se self.editor = SectionPropertyEditor() self.editor.setSection(se.section) self.editor.close.connect(self.sectionEditorClose) self.animate(se) def sectionEditorClose(self): if self.editor.changed: self.section_editor.setSection(self.editor.section) self.editChanged("Update Section") self.editor.close.disconnect() self.editorClosed() def load(self): from widgets.sectioneditor import SectionEditor self.content = self.site.loadContent(self.content.source, self.content.content_type) self.is_new = not self.content.title self.title.setText(self.content.title) self.source.setText(self.content.source) self.author.setText(self.content.author) self.keywords.setText(self.content.keywords) self.menus.setCurrentText(self.content.menu) self.layouts.setCurrentText(self.content.layout) if self.content.content_type == ContentType.POST: self.excerpt.setText(self.content.excerpt) self.date.setText(self.content.date.toString("dd.MM.yyyy")) pe = PageEditor() self.scroll.setWidget(pe) for item in self.content.items: if isinstance(item, Section): se = SectionEditor(item.fullwidth) se.load(item) pe.addSection(se) # todo other types def siteLoaded(self, site): self.site = site if self.content.contentType == ContentType.PAGE: for c in self.site.pages: if c.source == self.content.source: self.title.setText(c.title) else: for c in self.site.posts: if c.source == self.content.source: self.excerpt.setText(c.excerpt) self.title.setText(c.title) def closeEditor(self): if self.editor: self.editor.closeEditor() self.closes.emit() def elementEdit(self, ee): self.element_editor = ee plugin_name = "" if ee.type: plugin_name = Plugins.getElementPluginByTagname(ee.type) if plugin_name: self.editor = Plugins.element_plugins[plugin_name] else: self.editor = Plugins.element_plugins["TextEditor"] self.editor.setCaption("Text Module") self.editor.site = self.site self.editor.setContent(ee.getContent()) self.editor.close.connect(self.editorClose) self.animate(ee) def animate(self, widget): self.sourcewidget = widget pos = widget.mapTo(self.scroll, QPoint(0, 0)) self.editor.setParent(self.scroll) self.editor.move(pos) self.editor.resize(widget.size()) self.editor.show() self.animationgroup = QParallelAnimationGroup() self.animx = QPropertyAnimation() self.animx.setDuration(300) self.animx.setStartValue(pos.x()) self.animx.setEndValue(0) self.animx.setTargetObject(self.editor) self.animx.setPropertyName("x".encode("utf-8")) self.animationgroup.addAnimation(self.animx) self.animy = QPropertyAnimation() self.animy.setDuration(300) self.animy.setStartValue(pos.y()) self.animy.setEndValue(0) self.animy.setTargetObject(self.editor) self.animy.setPropertyName("y".encode("utf-8")) self.animationgroup.addAnimation(self.animy) self.animw = QPropertyAnimation() self.animw.setDuration(300) self.animw.setStartValue(widget.size().width()) self.animw.setEndValue(self.scroll.size().width()) self.animw.setTargetObject(self.editor) self.animw.setPropertyName("width".encode("utf-8")) self.animationgroup.addAnimation(self.animw) self.animh = QPropertyAnimation() self.animh.setDuration(300) self.animh.setStartValue(widget.size().height()) self.animh.setEndValue(self.scroll.size().height()) self.animh.setTargetObject(self.editor) self.animh.setPropertyName("height".encode("utf-8")) self.animationgroup.addAnimation(self.animh) self.animationgroup.finished.connect(self.animationFineshedZoomIn) self.animationgroup.start() def animationFineshedZoomIn(self): self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.title.setEnabled(False) self.author.setEnabled(False) self.keywords.setEnabled(False) self.menus.setEnabled(False) self.layouts.setEnabled(False) self.labelAuthor.setEnabled(False) self.labelKeyword.setEnabled(False) self.labelMenu.setEnabled(False) self.labelLayout.setEnabled(False) self.labelTitle.setEnabled(False) self.labelPermalink.setEnabled(False) self.previewLink.hide() self.undo.hide() self.redo.hide() self.close.hide() self.source.setEnabled(False) if self.content.content_type == ContentType.POST: self.excerpt.setEnabled(False) self.excerptLabel.setEnabled(False) self.animationgroup.finished.disconnect(self.animationFineshedZoomIn) def editorClose(self): if self.editor.changed: self.element_editor.setContent(self.editor.getContent()) self.editChanged("Update Element") self.editor.close.disconnect() self.editorClosed() def editorClosed(self): self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) pos = self.sourcewidget.mapTo(self.scroll, QPoint(0, 0)) # correct end values in case of resizing the window self.animx.setStartValue(pos.x()) self.animy.setStartValue(pos.y()) self.animw.setStartValue(self.sourcewidget.size().width()) self.animh.setStartValue(self.sourcewidget.size().height()) self.animationgroup.setDirection(QAbstractAnimation.Backward) self.animationgroup.finished.connect(self.animationFineshedZoomOut) self.animationgroup.start() def animationFineshedZoomOut(self): from widgets.rowpropertyeditor import RowPropertyEditor from widgets.sectionpropertyeditor import SectionPropertyEditor self.title.setEnabled(True) self.source.setEnabled(True) self.author.setEnabled(True) self.keywords.setEnabled(True) self.menus.setEnabled(True) self.layouts.setEnabled(True) self.labelAuthor.setEnabled(True) self.labelKeyword.setEnabled(True) self.labelMenu.setEnabled(True) self.labelLayout.setEnabled(True) self.labelTitle.setEnabled(True) self.labelPermalink.setEnabled(True) self.previewLink.show() self.undo.show() self.redo.show() self.close.show() if self.content.content_type == ContentType.POST: self.excerpt.setEnabled(True) self.excerptLabel.setEnabled(True) del self.animationgroup self.editor.hide() # parent has to be set to NULL, otherwise the plugin will be dropped by parent self.editor.setParent(None) # only delete Row- and SectionPropertyEditor the other editors are plugins if isinstance(self.editor, RowPropertyEditor): del self.editor elif isinstance(self.editor, SectionPropertyEditor): self.editor.close.disconnect(self.sectionEditorClose) del self.editor self.editor = None def editChanged(self, text): changeCommand = ChangeContentCommand(self.win, self, text) self.undoStack.push(changeCommand) def save(self): self.content.save(self.filename) def contentRenamed(self, name): self.filename = name base = os.path.basename(name) self.source.setText(base) self.content.source = base
class Preferences(QDialog): # Signal to warn that the window is closed settingsClosed = pyqtSignal() def __init__(self, parent=None): super(Preferences, self).__init__(parent) # Main container # This contains a grid main_box = QVBoxLayout(self) main_box.setContentsMargins(200, 50, 200, 100) # The grid contains two containers # left container and right container grid = QGridLayout() # Left Container left_container = QVBoxLayout() left_container.setContentsMargins(0, 0, 0, 0) # General group_gral = QGroupBox(self.tr("General")) box_gral = QVBoxLayout(group_gral) # Updates btn_updates = QPushButton(self.tr("Check for updates")) box_gral.addWidget(btn_updates) # Language group_language = QGroupBox(self.tr("Language")) box = QVBoxLayout(group_language) # Find .qm files in language path available_langs = file_manager.get_files_from_folder( settings.LANGUAGE_PATH) languages = ["English"] + available_langs self._combo_lang = QComboBox() box.addWidget(self._combo_lang) self._combo_lang.addItems(languages) self._combo_lang.currentIndexChanged[int].connect( self._change_lang) if PSetting.LANGUAGE: self._combo_lang.setCurrentText(PSetting.LANGUAGE) box.addWidget(QLabel(self.tr("(Requires restart)"))) # Add widgets left_container.addWidget(group_gral) left_container.addWidget(group_language) left_container.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) # Right Container right_container = QVBoxLayout() right_container.setContentsMargins(0, 0, 0, 0) # Editor editor_group = QGroupBox(self.tr("Editor Configurations")) box_editor = QHBoxLayout(editor_group) # Current line self._highlight_current_line = QCheckBox( self.tr("Highlight Current Line")) self._highlight_current_line.setChecked( PSetting.HIGHLIGHT_CURRENT_LINE) self._highlight_current_line.stateChanged[int].connect( self.__current_line_value_changed) box_editor.addWidget(self._highlight_current_line) # Matching paren self._matching_paren = QCheckBox(self.tr("Matching Parenthesis")) self._matching_paren.setChecked( PSetting.MATCHING_PARENTHESIS) self._matching_paren.stateChanged[int].connect( self.__set_enabled_matching_parenthesis) box_editor.addWidget(self._matching_paren) # Font group font_group = QGroupBox(self.tr("Font")) font_grid = QGridLayout(font_group) font_grid.addWidget(QLabel(self.tr("Family")), 0, 0) self._combo_font = QFontComboBox() self._combo_font.setCurrentFont(PSetting.FONT) font_grid.addWidget(self._combo_font, 0, 1) font_grid.addWidget(QLabel(self.tr("Point Size")), 1, 0) self._combo_font_size = QComboBox() fdb = QFontDatabase() combo_sizes = fdb.pointSizes(PSetting.FONT.family()) current_size_index = combo_sizes.index( PSetting.FONT.pointSize()) self._combo_font_size.addItems([str(f) for f in combo_sizes]) self._combo_font_size.setCurrentIndex(current_size_index) font_grid.addWidget(self._combo_font_size, 1, 1) right_container.addWidget(editor_group) right_container.addWidget(font_group) right_container.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) # Add widgets grid.addLayout(left_container, 0, 0) grid.addLayout(right_container, 0, 1) main_box.addLayout(grid) # Button close and reset hbox = QHBoxLayout() hbox.setSpacing(20) hbox.addItem(QSpacerItem(1, 0, QSizePolicy.Expanding)) btn_cancel = QPushButton(self.tr("Back")) hbox.addWidget(btn_cancel) btn_reset = QPushButton(self.tr("Reset Configurations")) hbox.addWidget(btn_reset) main_box.addLayout(hbox) # Overlay self.overlay = overlay_widget.OverlayWidget(self) self.overlay.hide() # Effect and animations self.effect = QGraphicsOpacityEffect() self.setGraphicsEffect(self.effect) duration, x = 180, 150 # Animation duration # Animation start # Opacity animation self.opacity_animation_s = QPropertyAnimation(self.effect, b"opacity") self.opacity_animation_s.setDuration(duration) self.opacity_animation_s.setStartValue(0.0) self.opacity_animation_s.setEndValue(1.0) # X animation self.x_animation_s = QPropertyAnimation(self, b"geometry") self.x_animation_s.setDuration(duration) self.x_animation_s.setStartValue(QRect(x, 0, parent.width(), parent.height())) self.x_animation_s.setEndValue(QRect(0, 0, parent.width(), parent.height())) # Animation end # Opacity animation self.opacity_animation_e = QPropertyAnimation(self.effect, b"opacity") self.opacity_animation_e.setDuration(duration) self.opacity_animation_e.setStartValue(1.0) self.opacity_animation_e.setEndValue(0.0) # X animation self.x_animation_e = QPropertyAnimation(self, b"geometry") self.x_animation_e.setDuration(duration) self.x_animation_e.setStartValue(QRect(0, 0, parent.width(), parent.height())) self.x_animation_e.setEndValue(QRect(-x, 0, parent.width(), parent.height())) # Group animation start self.group_animation_s = QParallelAnimationGroup() self.group_animation_s.addAnimation(self.opacity_animation_s) self.group_animation_s.addAnimation(self.x_animation_s) # Group animation end self.group_animation_e = QParallelAnimationGroup() self.group_animation_e.addAnimation(self.opacity_animation_e) self.group_animation_e.addAnimation(self.x_animation_e) # Connections self.group_animation_e.finished.connect( self._on_group_animation_finished) btn_cancel.clicked.connect(self.close) btn_reset.clicked.connect(self._reset_settings) btn_updates.clicked.connect(self._check_for_updates) # self.thread.finished.connect(self._on_thread_finished) self._combo_font.currentFontChanged.connect( self._change_font) self._combo_font_size.currentTextChanged.connect( self._change_font_size) def __current_line_value_changed(self, value): qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) qs.setValue('highlight_current_line', value) PSetting.HIGHLIGHT_CURRENT_LINE = value def __set_enabled_matching_parenthesis(self, value): qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) qs.setValue("matching_parenthesis", value) PSetting.MATCHING_PARENTHESIS = value def _change_font(self, font): # FIXME: un quilombo esto central = Pireal.get_service("central") mcontainer = central.get_active_db() if mcontainer is not None: query_widget = mcontainer.query_container.currentWidget() if query_widget is not None: weditor = query_widget.get_editor() if weditor is not None: qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) weditor.set_font(font) qs.setValue("font", font) def _change_font_size(self, size): # FIXME: un quilombo esto font = self._combo_font.currentFont() font.setPointSize(int(size)) central = Pireal.get_service("central") mcontainer = central.get_active_db() if mcontainer is not None: query_widget = mcontainer.query_container.currentWidget() if query_widget is not None: weditor = query_widget.get_editor() if weditor is not None: qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) weditor.set_font(font) qs.setValue("font", font) def showEvent(self, event): super(Preferences, self).showEvent(event) self.group_animation_s.start() def resizeEvent(self, event): self.overlay.resize(self.size()) event.accept() def done(self, result): self.res = result self.group_animation_e.start() def _on_group_animation_finished(self): super(Preferences, self).done(self.res) self.settingsClosed.emit() def _check_for_updates(self): # Thread self._thread = QThread() self._updater = updater.Updater() self._updater.moveToThread(self._thread) self._thread.started.connect(self._updater.check_updates) self._updater.finished.connect(self.__on_thread_update_finished) # Show overlay widget self.overlay.show() # Start thread self._thread.start() def __on_thread_update_finished(self): # Hide overlay widget self.overlay.hide() self._thread.quit() msg = QMessageBox(self) if not self._updater.error: if self._updater.version: version = self._updater.version msg.setWindowTitle(self.tr("New version available!")) msg.setText(self.tr("Check the web site to " "download <b>Pireal {}</b>".format( version))) download_btn = msg.addButton(self.tr("Download!"), QMessageBox.YesRole) msg.addButton(self.tr("Cancel"), QMessageBox.RejectRole) msg.exec_() r = msg.clickedButton() if r == download_btn: webbrowser.open_new( "http://centaurialpha.github.io/pireal") else: msg.setWindowTitle(self.tr("Information")) msg.setText(self.tr("Last version installed")) msg.addButton(self.tr("Ok"), QMessageBox.AcceptRole) msg.exec_() else: msg.critical(self, self.tr("Error"), self.tr("Connection error")) self._thread.deleteLater() self._updater.deleteLater() def _reset_settings(self): """ Remove all settings """ msg = QMessageBox(self) msg.setWindowTitle(self.tr("Reset Settings")) msg.setText(self.tr("Are you sure you want to clear all settings?")) msg.setIcon(QMessageBox.Question) msg.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msg.addButton(self.tr("Yes"), QMessageBox.YesRole) msg.exec_() r = msg.clickedButton() if r == yes_btn: QSettings(settings.SETTINGS_PATH, QSettings.IniFormat).clear() self.close() def _change_lang(self, index): lang = self._combo_lang.itemText(index) qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) qs.setValue('language', lang)
class SlidingStackedWidget(QStackedWidget): animationEasingCurve = QtDynamicProperty('animationEasingCurve', int) animationDuration = QtDynamicProperty('animationDuration', int) verticalMode = QtDynamicProperty('verticalMode', bool) wrap = QtDynamicProperty('wrap', bool) animationFinished = pyqtSignal() LeftToRight, RightToLeft, TopToBottom, BottomToTop, Automatic = list(range(5)) def __init__(self, parent=None): super(SlidingStackedWidget, self).__init__(parent) self.animationEasingCurve = QEasingCurve.Linear self.animationDuration = 250 self.verticalMode = False self.wrap = False self._active = False self._animation_group = QParallelAnimationGroup() self._animation_group.finished.connect(self._SH_AnimationGroupFinished) def slideInNext(self): next_index = self.currentIndex() + 1 if self.wrap or next_index < self.count(): self.slideInIndex(next_index % self.count(), direction=self.BottomToTop if self.verticalMode else self.RightToLeft) def slideInPrev(self): previous_index = self.currentIndex() - 1 if self.wrap or previous_index >= 0: self.slideInIndex(previous_index % self.count(), direction=self.TopToBottom if self.verticalMode else self.LeftToRight) def slideInIndex(self, index, direction=Automatic): self.slideInWidget(self.widget(index), direction) def slideInWidget(self, widget, direction=Automatic): if self.indexOf(widget) == -1 or widget is self.currentWidget(): return if self._active: return self._active = True prev_widget = self.currentWidget() next_widget = widget if direction == self.Automatic: if self.indexOf(prev_widget) < self.indexOf(next_widget): direction = self.BottomToTop if self.verticalMode else self.RightToLeft else: direction = self.TopToBottom if self.verticalMode else self.LeftToRight width = self.frameRect().width() height = self.frameRect().height() # the following is important, to ensure that the new widget has correct geometry information when sliding in the first time next_widget.setGeometry(0, 0, width, height) if direction in (self.TopToBottom, self.BottomToTop): offset = QPoint(0, height if direction == self.TopToBottom else -height) elif direction in (self.LeftToRight, self.RightToLeft): offset = QPoint(width if direction == self.LeftToRight else -width, 0) # re-position the next widget outside of the display area prev_widget_position = prev_widget.pos() next_widget_position = next_widget.pos() next_widget.move(next_widget_position - offset) next_widget.show() next_widget.raise_() prev_widget_animation = QPropertyAnimation(prev_widget, b"pos") prev_widget_animation.setDuration(self.animationDuration) prev_widget_animation.setEasingCurve(QEasingCurve(self.animationEasingCurve)) prev_widget_animation.setStartValue(prev_widget_position) prev_widget_animation.setEndValue(prev_widget_position + offset) next_widget_animation = QPropertyAnimation(next_widget, b"pos") next_widget_animation.setDuration(self.animationDuration) next_widget_animation.setEasingCurve(QEasingCurve(self.animationEasingCurve)) next_widget_animation.setStartValue(next_widget_position - offset) next_widget_animation.setEndValue(next_widget_position) self._animation_group.clear() self._animation_group.addAnimation(prev_widget_animation) self._animation_group.addAnimation(next_widget_animation) self._animation_group.start() def _SH_AnimationGroupFinished(self): prev_widget_animation = self._animation_group.animationAt(0) next_widget_animation = self._animation_group.animationAt(1) prev_widget = prev_widget_animation.targetObject() next_widget = next_widget_animation.targetObject() self.setCurrentWidget(next_widget) prev_widget.hide() # this may have been done already by QStackedWidget when changing the current widget above -Dan prev_widget.move(prev_widget_animation.startValue()) # move the out-shifted widget back to its original position self._animation_group.clear() self._active = False self.animationFinished.emit()
class CollapsibleWidget(QWidget): def __init__(self, title="", parent=None, animation_duration=300): """ References: # Adapted from c++ version http://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt """ super(CollapsibleWidget, self).__init__(parent) self.title = title self.toggle_button = QToolButton() self.toggle_animation = QParallelAnimationGroup(self) self.content_area = QScrollArea() self.animation_duration = animation_duration self._init_base_ui() def _init_base_ui(self): self.toggle_button.setStyleSheet("QToolButton { border: none; }") self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setArrowType(Qt.RightArrow) self.toggle_button.pressed.connect(self.on_pressed) self.toggle_button.setText(str(self.title)) self.toggle_button.setCheckable(True) self.toggle_button.setChecked(False) self.content_area.setMaximumHeight(0) self.content_area.setMinimumHeight(0) self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.content_area.setFrameShape(QFrame.NoFrame) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self.content_area, b"maximumHeight")) layout = QVBoxLayout(self) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.toggle_button) layout.addWidget(self.content_area) def on_pressed(self): checked = self.toggle_button.isChecked() self.toggle_button.setArrowType( Qt.DownArrow if not checked else Qt.RightArrow) self.toggle_animation.setDirection( QAbstractAnimation.Forward if not checked else QAbstractAnimation. Backward) self.toggle_animation.start() def set_content_layout(self, layout): initial_layout = self.content_area.layout() del initial_layout self.content_area.setLayout(layout) collapsed_height = (self.sizeHint().height() - self.content_area.maximumHeight()) content_height = layout.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(self.animation_duration) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(self.animation_duration) content_animation.setStartValue(0) content_animation.setEndValue(content_height) def set_content_widget(self, widget): initial_layout = self.content_area.layout() del initial_layout self.content_area.setWidget(widget) collapsed_height = (self.sizeHint().height() - self.content_area.maximumHeight()) content_height = widget.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(self.animation_duration) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(self.animation_duration) content_animation.setStartValue(0) content_animation.setEndValue(content_height)
class Expander(QWidget): expanded = pyqtSignal(object) clicked = pyqtSignal() def __init__(self, header, normal_icon="", hovered_icon="", selected_icon=""): QWidget.__init__(self) self.is_expanded = False self.text = header self.label_normal_color = self.palette().link().color().name() self.label_hovered_color = self.palette().highlight().color().name() self.label_selected_color = self.palette().highlightedText().color().name() self.normal_color = self.palette().base().color().name() self.selected_color = self.palette().highlight().color() self.hovered_color = self.palette().alternateBase().color() self.setCursor(Qt.PointingHandCursor) if normal_icon: self.normal_icon = QImage(normal_icon) if hovered_icon: self.hovered_icon = QImage(hovered_icon) if selected_icon: self.selected_icon = QImage(selected_icon) self.setAttribute(Qt.WA_Hover, True) self.color = self.normal_color self.setAutoFillBackground(True) vbox = QVBoxLayout() hbox = QHBoxLayout() self.icon = QLabel() self.icon.setPixmap(QPixmap.fromImage(self.normal_icon)) self.hyper = QLabel() self.hyper.setText("<a style=\"color: " + self.label_normal_color + " text-decoration: none\" href=\"#\">" + self.text + "</a>") hbox.addWidget(self.icon) hbox.addSpacing(5) hbox.addWidget(self.hyper) hbox.addStretch() hbox.setContentsMargins(8, 8, 8, 8) vbox.addLayout(hbox) self.content = QWidget() self.content.setStyleSheet("background-color: " + self.palette().base().color().name()) self.content.setMaximumHeight(0) vbox.addWidget(self.content) vbox.setContentsMargins(0, 0, 0, 0) self.setLayout(vbox) self.hyper.linkActivated.connect(self.buttonClicked) self.anim = QParallelAnimationGroup() self.height_anim = QPropertyAnimation(self.content, "maximumHeight".encode("utf-8")) self.color_anim = QPropertyAnimation(self, "color".encode("utf-8")) self.height_anim.setDuration(200) self.color_anim.setDuration(200) self.anim.addAnimation(self.height_anim) self.anim.addAnimation(self.color_anim) def setExpanded(self, value): if value == self.is_expanded: return if value: self.is_expanded = True pal = self.palette() pal.setColor(QPalette.Background, self.selected_color) self.setPalette(pal) self.icon.setPixmap(QPixmap.fromImage(self.selected_icon)) self.hyper.setText("<a style=\"color: " + self.label_selected_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") else: self.is_expanded = False pal = self.palette() pal.setColor(QPalette.Background, QColor(self.normal_color)) self.setPalette(pal) self.icon.setPixmap(QPixmap.fromImage(self.normal_icon)) self.hyper.setText("<a style=\"color: " + self.label_normal_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") if self.is_expanded: self.expandContent() else: self.collapseContent() self.expanded.emit(self.is_expanded) def addLayout(self, layout): self.content.setLayout(layout) @pyqtProperty('QColor') def color(self): return Qt.black @color.setter def color(self, color): pal = self.palette() pal.setColor(QPalette.Background, QColor(color)) self.setPalette(pal) def mouseReleaseEvent(self, me): self.setExpanded(not self.is_expanded) if self.is_expanded: self.clicked.emit() def expandContent(self): if self.content.layout(): self.height_anim.setEndValue(self.content.layout().sizeHint().height()) else: self.height_anim.setEndValue(0) self.height_anim.setStartValue(0) self.color_anim.setStartValue(self.normal_color) self.color_anim.setEndValue(self.selected_color) self.anim.start() def collapseContent(self): if self.content.layout(): self.height_anim.setStartValue(self.content.layout().sizeHint().height()) else: self.height_anim.setStartValue(0) self.height_anim.setEndValue(0) self.color_anim.setStartValue(self.selected_color) self.color_anim.setEndValue(self.normal_color) self.anim.start() def buttonClicked(self): self.setExpanded(not self.is_expanded) if self.is_expanded: self.clicked.emit() def enterEvent(self, event): if not self.is_expanded: pal = self.palette() pal.setColor(QPalette.Background, QColor(self.hovered_color)) self.setPalette(pal) self.icon.setPixmap(QPixmap.fromImage(self.hovered_icon)) self.hyper.setText("<a style=\"color: " + self.label_hovered_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") QWidget.enterEvent(self, event) def leaveEvent(self, event): if not self.is_expanded: pal = self.palette() pal.setColor(QPalette.Background, QColor(self.normal_color)) self.setPalette(pal) self.icon.setPixmap(QPixmap.fromImage(self.normal_icon)) self.hyper.setText("<a style=\"color: " + self.label_normal_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") QWidget.leaveEvent(self, event)
class SongInfoCardChute(QWidget): """ 歌曲卡滑槽,有三个歌曲卡在循环滑动,当前歌曲卡切换时就切换歌曲 当前下标为0时,不能换到上一首, 当前下标为len-1时,不能换到下一首 """ # 当前歌曲切换信号 currentIndexChanged = pyqtSignal([int], [str]) # 显示和隐藏播放栏信号 showPlayBarSignal = pyqtSignal() hidePlayBarSignal = pyqtSignal() # 切换到专辑界面 switchToAlbumInterfaceSig = pyqtSignal(str, str) def __init__(self, parent=None, playlist=None): super().__init__(parent) self.shiftLeftTime = 0 self.shiftRightTime = 0 # 引用播放列表 self.playlist = playlist self.currentIndex = 0 self.songInfoCard_list = [] # 设置发送信号标志位 self.needToEmitSignal = True # 创建并行动画组 self.parallelAniGroup = QParallelAnimationGroup(self) # 记录有待完成的动画 self.__unCompleteShift_list = [] # 初始化 self.__initWidget() self.now = QDateTime.currentDateTime().toMSecsSinceEpoch() def __initWidget(self): """ 初始化小部件 """ self.resize(1300, 970) self.setAttribute(Qt.WA_TranslucentBackground) self.__createWidgets() for songInfoCard in self.songInfoCard_list: songInfoCard.resize(self.width(), 136) # 将信号连接到槽函数 self.parallelAniGroup.finished.connect(self.__switchSongInfoCard) for songInfoCard in self.songInfoCard_list: songInfoCard.showPlayBarSignal.connect(self.__showPlayBar) songInfoCard.hidePlayBarSignal.connect(self.__hidePlayBar) songInfoCard.switchToAlbumInterfaceSig.connect( self.switchToAlbumInterfaceSig) def __createWidgets(self): """ 创建小部件 """ # 创建三个歌曲信息卡,分别为last, current, next self.songInfoCard_list = [SongInfoCard(self) for i in range(3)] # 引用当前歌曲卡 self.lastSongInfoCard = self.songInfoCard_list[0] self.curSongInfoCard = self.songInfoCard_list[1] self.nextSongInfoCard = self.songInfoCard_list[2] # 创建动画 self.songInfoCardAni_list = [ QPropertyAnimation(self.songInfoCard_list[i], b'geometry') for i in range(3) ] self.lastSongInfoCardAni = self.songInfoCardAni_list[0] self.curSongInfoCardAni = self.songInfoCardAni_list[1] self.nextSongInfoCardAni = self.songInfoCardAni_list[2] # 将动画添加到动画组中 for ani in self.songInfoCardAni_list: self.parallelAniGroup.addAnimation(ani) # 先隐藏所有歌曲信息卡并设置位置 for i in range(3): self.songInfoCard_list[i].move((i - 1) * self.width(), self.height() - 204) self.songInfoCard_list[i].hide() # 初始化歌曲卡 if self.playlist: self.curSongInfoCard.updateCard(self.playlist[0]) self.curSongInfoCard.show() if len(self.playlist) >= 2: self.nextSongInfoCard.show() self.nextSongInfoCard.updateCard(self.playlist[1]) def mousePressEvent(self, e: QMouseEvent): """ 按钮按下时记录鼠标位置 """ super().mousePressEvent(e) self.mousePressPosX = e.pos().x() self.lastMousePosX = e.pos().x() # 记下按下的时间 self.mousePressTime = QDateTime.currentDateTime().toMSecsSinceEpoch() def mouseMoveEvent(self, e: QMouseEvent): """ 鼠标按下可拖动歌曲信息卡 """ for songInfoCard in self.songInfoCard_list: songInfoCard.move( songInfoCard.x() - (self.lastMousePosX - e.pos().x()), songInfoCard.y()) # 更新鼠标位置 self.lastMousePosX = e.pos().x() def mouseReleaseEvent(self, e: QMouseEvent): """ 鼠标松开时重新设置歌曲卡位置 """ # 记下松开的时间 self.mouseReleaseTime = QDateTime.currentDateTime().toMSecsSinceEpoch() mouseDeltaTime = self.mouseReleaseTime - self.mousePressTime # self.mouseDeltaX > 0代表向右移动 self.mouseDeltaX = self.lastMousePosX - self.mousePressPosX # 设置默认循环移位方式 self.loopMode = SongInfoCardLoopMode.NO_LOOP if self.playlist: if len(self.playlist) == 1: # 只有1个歌曲卡时不管移动距离是多少都把该歌曲卡放回原位 self.__restoreCardPosition() # 播放列表长度大于等于2 elif len(self.playlist) >= 2: # 下标为0时右移或者下标为len-1左移都恢复原状 if (self.currentIndex == 0 and self.mouseDeltaX > 0) or ( self.currentIndex == len(self.playlist) - 1 and self.mouseDeltaX < 0): self.__restoreCardPosition() else: if mouseDeltaTime < 360 and abs(self.mouseDeltaX) >= 120: self.__cycleShift() else: if abs(self.mouseDeltaX) < int(self.width() / 2): self.__restoreCardPosition() else: self.__cycleShift() def __cycleLeftShift(self): """ 循环左移 """ self.loopMode = SongInfoCardLoopMode.CYCLE_LEFT_SHIFT self.__setAnimation(self.curSongInfoCardAni, self.curSongInfoCard, -self.width()) self.__setAnimation(self.nextSongInfoCardAni, self.nextSongInfoCard, 0) self.parallelAniGroup.removeAnimation(self.lastSongInfoCardAni) self.parallelAniGroup.start() # 发送更新背景信号 self.currentIndexChanged[str].emit( self.nextSongInfoCard.albumCoverPath) def __cycleRightShift(self): """ 循环右移 """ self.loopMode = SongInfoCardLoopMode.CYCLE_RIGHT_SHIFT self.__setAnimation(self.curSongInfoCardAni, self.curSongInfoCard, self.width()) self.__setAnimation(self.lastSongInfoCardAni, self.lastSongInfoCard, 0) self.parallelAniGroup.removeAnimation(self.nextSongInfoCardAni) self.parallelAniGroup.start() # 发送更新背景信号 self.currentIndexChanged[str].emit( self.lastSongInfoCard.albumCoverPath) def __switchSongInfoCard(self): """ 交换对底层歌曲卡对象的引用 """ # 循环左移 if self.loopMode == SongInfoCardLoopMode.CYCLE_LEFT_SHIFT: self.__resetRef(moveDirection=0) # 更新动画组 self.parallelAniGroup.addAnimation(self.lastSongInfoCardAni) # 移动底层对象 self.__moveObject( self.songInfoCard_list[[2, 0, 1][self.shiftLeftTime]], self.width()) # 更新下标 self.currentIndex += 1 if self.currentIndex != len(self.playlist) - 1: # 更新歌曲信息卡 self.updateCards() self.nextSongInfoCard.show() else: self.nextSongInfoCard.hide() # 根据标志位决定是否发送下标更新信号 if self.needToEmitSignal: self.currentIndexChanged[int].emit(self.currentIndex) else: self.needToEmitSignal = True # 循环右移 elif self.loopMode == SongInfoCardLoopMode.CYCLE_RIGHT_SHIFT: self.__resetRef(moveDirection=1) # 更新动画组 self.parallelAniGroup.addAnimation(self.nextSongInfoCardAni) # 移动底层对象 self.__moveObject( self.songInfoCard_list[[0, 2, 1][self.shiftRightTime]], -self.width()) # 更新下标 self.currentIndex -= 1 if self.currentIndex != 0: self.updateCards() self.lastSongInfoCard.show() else: self.lastSongInfoCard.hide() # 根据标志位决定是否发送下标更新信号 if self.needToEmitSignal: self.currentIndexChanged[int].emit(self.currentIndex) else: self.needToEmitSignal = True # 完成未完成的移位动作 if self.__unCompleteShift_list: index = self.__unCompleteShift_list.pop(0) self.needToEmitSignal = False self.__completeShift(index) def __cycleShift(self): """ 三卡片移动 """ # 播放播放列表的上一首(右移歌曲卡) if self.mouseDeltaX > 0: self.__cycleRightShift() # 播放播放列表的下一首(左移歌曲卡) elif self.mouseDeltaX < 0: self.__cycleLeftShift() def __setAnimation(self, animation: QPropertyAnimation, songInfoCard, endX): """ 设置动画 """ animation.setEasingCurve(QEasingCurve.OutQuart) animation.setTargetObject(songInfoCard) animation.setDuration(500) # 设置起始值 animation.setStartValue( QRect(songInfoCard.x(), songInfoCard.y(), songInfoCard.width(), songInfoCard.height())) # 设置结束值 animation.setEndValue( QRect(endX, songInfoCard.y(), songInfoCard.width(), songInfoCard.height())) def __restoreCardPosition(self): """ 恢复卡片位置 """ self.__setAnimation(self.curSongInfoCardAni, self.curSongInfoCard, 0) self.__setAnimation(self.nextSongInfoCardAni, self.nextSongInfoCard, self.width()) self.__setAnimation(self.lastSongInfoCardAni, self.lastSongInfoCard, -self.width()) self.parallelAniGroup.start() def updateCards(self): """ 更新三个歌曲信息卡 """ if self.curSongInfoCard: self.curSongInfoCard.updateCard(self.playlist[self.currentIndex]) if self.lastSongInfoCard and self.currentIndex >= 1: self.lastSongInfoCard.updateCard(self.playlist[self.currentIndex - 1]) if self.nextSongInfoCard and self.currentIndex <= len( self.playlist) - 2: self.nextSongInfoCard.updateCard(self.playlist[self.currentIndex + 1]) def setCurrentIndex(self, index): """ 更新当前下标并移动和更新歌曲信息卡 """ # 移位完成后不发送信号 self.needToEmitSignal = False if self.playlist: # 新的下标大于当前下标时,歌曲卡左移 if index != self.currentIndex: if self.parallelAniGroup.state() != QAbstractAnimation.Running: self.__completeShift(index) else: self.__unCompleteShift_list.append(index) elif index == self.currentIndex: self.updateCards() self.needToEmitSignal = True def setPlaylist(self, playlist, isResetIndex: bool = True): """ 更新播放列表 """ self.playlist = playlist self.currentIndex = 0 if isResetIndex else self.currentIndex self.lastSongInfoCard.hide() if playlist: self.curSongInfoCard.updateCard(self.playlist[self.currentIndex]) self.currentIndexChanged[str].emit( self.curSongInfoCard.albumCoverPath) self.curSongInfoCard.show() if len(self.playlist) == 1: self.nextSongInfoCard.hide() else: self.nextSongInfoCard.show() self.nextSongInfoCard.updateCard( self.playlist[self.currentIndex + 1]) else: self.curSongInfoCard.hide() self.nextSongInfoCard.hide() def resizeEvent(self, e): """ 改变窗口大小时也改变歌曲卡的大小 """ super().resizeEvent(e) for i in range(3): self.songInfoCard_list[i].resize(self.width(), 136) self.songInfoCard_list[i].adjustText() self.curSongInfoCard.move(0, self.height() - 204) self.lastSongInfoCard.move(-self.width(), self.height() - 204) self.nextSongInfoCard.move(self.width(), self.height() - 204) def __moveObject(self, songInfoCardObj, x): """ 移动底层对象 """ songInfoCardObj.hide() songInfoCardObj.move(x, self.height() - 204) songInfoCardObj.show() def __resetRef(self, moveDirection=0): """ 设置变量对底层对象的引用,moveDirection = 0 代表左移,moveDirection = 1 代表右移 """ # 循环左移 if moveDirection == 0: self.shiftLeftTime = (self.shiftLeftTime + 1) % 3 self.shiftRightTime = (self.shiftRightTime - 1) % 3 if self.shiftLeftTime == 0: self.__resetRefIndex(0, 1, 2) elif self.shiftLeftTime == 1: self.__resetRefIndex(1, 2, 0) elif self.shiftLeftTime == 2: self.__resetRefIndex(2, 0, 1) # 循环右移 elif moveDirection == 1: self.shiftLeftTime = (self.shiftLeftTime - 1) % 3 self.shiftRightTime = (self.shiftRightTime + 1) % 3 if self.shiftRightTime == 0: self.__resetRefIndex(0, 1, 2) elif self.shiftRightTime == 1: self.__resetRefIndex(2, 0, 1) elif self.shiftRightTime == 2: self.__resetRefIndex(1, 2, 0) def __resetRefIndex(self, lastIndex, curIndex, nextIndex): """ refsetFunc的子函数 """ self.curSongInfoCard = self.songInfoCard_list[curIndex] self.lastSongInfoCard = self.songInfoCard_list[lastIndex] self.nextSongInfoCard = self.songInfoCard_list[nextIndex] def __showPlayBar(self): """ 显示播放栏 """ self.showPlayBarSignal.emit() for songInfoCard in self.songInfoCard_list: songInfoCard.isPlayBarVisible = True def __hidePlayBar(self): """ 隐藏播放栏 """ self.hidePlayBarSignal.emit() for songInfoCard in self.songInfoCard_list: songInfoCard.isPlayBarVisible = False def stopSongInfoCardTimer(self): """ 停止歌曲信息卡的计时器 """ for songInfoCard in self.songInfoCard_list: songInfoCard.timer.stop() def startSongInfoCardTimer(self): """ 打开歌曲信息卡的计时器 """ self.curSongInfoCard.timer.start() def __completeShift(self, index): """ 完成移位,只在调用setCurrentIndex时调用 """ if index > self.currentIndex: self.currentIndex = index - 1 self.nextSongInfoCard.updateCard(self.playlist[index]) self.__cycleLeftShift() elif index < self.currentIndex: self.currentIndex = index + 1 self.lastSongInfoCard.updateCard(self.playlist[index]) self.__cycleRightShift()
class RubberBandButton(QPushButton): def __init__(self, *args, **kwargs): super(RubberBandButton, self).__init__(*args, **kwargs) self.setFlat(True) self.setCursor(Qt.PointingHandCursor) self._width = 0 self._height = 0 self._bgcolor = QColor(Qt.green) def paintEvent(self, event): self._initAnimate() painter = QStylePainter(self) painter.setRenderHint(QPainter.Antialiasing, True) painter.setRenderHint(QPainter.HighQualityAntialiasing, True) painter.setRenderHint(QPainter.SmoothPixmapTransform, True) painter.setBrush(QColor(self._bgcolor)) painter.setPen(QColor(self._bgcolor)) painter.drawEllipse(QRectF( (self.minimumWidth() - self._width) / 2, (self.minimumHeight() - self._height) / 2, self._width, self._height )) # 绘制本身的文字和图标 options = QStyleOptionButton() options.initFrom(self) size = options.rect.size() size.transpose() options.rect.setSize(size) options.features = QStyleOptionButton.Flat options.text = self.text() options.icon = self.icon() options.iconSize = self.iconSize() painter.drawControl(QStyle.CE_PushButton, options) event.accept() def _initAnimate(self): if hasattr(self, '_animate'): return self._width = self.minimumWidth() * 7 / 8 self._height = self.minimumHeight() * 7 / 8 # self._width=175 # self._height=175 wanimate = QPropertyAnimation(self, b'rWidth') wanimate.setEasingCurve(QEasingCurve.OutElastic) wanimate.setDuration(700) wanimate.valueChanged.connect(self.update) wanimate.setKeyValueAt(0, self._width) # wanimate.setKeyValueAt(0.1, 180) # wanimate.setKeyValueAt(0.2, 185) # wanimate.setKeyValueAt(0.3, 190) # wanimate.setKeyValueAt(0.4, 195) wanimate.setKeyValueAt(0.5, self._width + 6) # wanimate.setKeyValueAt(0.6, 195) # wanimate.setKeyValueAt(0.7, 190) # wanimate.setKeyValueAt(0.8, 185) # wanimate.setKeyValueAt(0.9, 180) wanimate.setKeyValueAt(1, self._width) hanimate = QPropertyAnimation(self, b'rHeight') hanimate.setEasingCurve(QEasingCurve.OutElastic) hanimate.setDuration(700) hanimate.setKeyValueAt(0, self._height) # hanimate.setKeyValueAt(0.1, 170) # hanimate.setKeyValueAt(0.3, 165) hanimate.setKeyValueAt(0.5, self._height - 6) # hanimate.setKeyValueAt(0.7, 165) # hanimate.setKeyValueAt(0.9, 170) hanimate.setKeyValueAt(1, self._height) self._animate = QParallelAnimationGroup(self) self._animate.addAnimation(wanimate) self._animate.addAnimation(hanimate) def enterEvent(self, event): super(RubberBandButton, self).enterEvent(event) self._animate.stop() self._animate.start() @pyqtProperty(int) def rWidth(self): return self._width @rWidth.setter def rWidth(self, value): self._width = value @pyqtProperty(int) def rHeight(self): return self._height @rHeight.setter def rHeight(self, value): self._height = value @pyqtProperty(QColor) def bgColor(self): return self._bgcolor @bgColor.setter def bgColor(self, color): self._bgcolor = QColor(color)
class CoffeeFundWindow(QWidget): signal_back = pyqtSignal() signal_coffee = pyqtSignal() signal_account = pyqtSignal() def __init__(self, parent=None): super().__init__() self.cards = {} # type: Dict[GuiCards, QWidget] """ Python's GC will clean up QPropertyAnimations as soon as it leaves the button handler, therefore they will appear not to work. Use members to store the animations. see http://stackoverflow.com/a/6953965 """ self.slide_in_animation = None self.slide_out_animation = None self.animation_group = None self.setObjectName("coffeeFundWindow") """ Store the position and size of visible/hidden cards for the animation sequences """ self.hidden_geometry = None self.visible_geometry = None layout = QStackedLayout() layout.setStackingMode(QStackedLayout.StackAll) card_remove = RemoveCard() self.cards[GuiCards.RemoveCard] = card_remove layout.addWidget(card_remove) card_choose_action = ChooseActionCard() self.cards[GuiCards.ChooseAction] = card_choose_action layout.addWidget(card_choose_action) card_account = AccountCard() self.cards[GuiCards.AccountInfo] = card_account layout.addWidget(card_account) # keep this as last initialized card, the last card will be shown on startup! card_start = StartCard() self.cards[GuiCards.Start] = card_start layout.addWidget(card_start) self.setLayout(layout) self.setWindowTitle("Kaffeekasse") layout.setCurrentWidget(card_start) self.active_card = None card_choose_action.button_account.clicked.connect(self.signal_account) card_choose_action.button_coffee.clicked.connect(self.signal_coffee) card_account.button_back.clicked.connect(self.signal_back) def set_card_hidden(self, card: QWidget): card.setGeometry(self.hidden_geometry) def show_start(self): self.show_card(GuiCards.Start) def show_account(self, name, value): self.cards[GuiCards.AccountInfo].set_user_name(name) self.cards[GuiCards.AccountInfo].set_balance(value) self.show_card(GuiCards.AccountInfo) def show_choose_action(self, name: str): self.cards[GuiCards.ChooseAction].set_user_name(name) self.show_card(GuiCards.ChooseAction) def show_remove(self): self.show_card(GuiCards.RemoveCard) def show_card(self, card_id: GuiCards): if self.active_card is None: self.active_card = self.cards[GuiCards.Start] if self.active_card == self.cards[card_id]: return if self.visible_geometry is None or self.hidden_geometry is None: self.visible_geometry = self.active_card.geometry() # type: QRect self.hidden_geometry = QRect(self.visible_geometry.x(), self.visible_geometry.height() * 1.5, self.visible_geometry.width(), self.visible_geometry.height()) for key in self.cards.keys(): if key != self.active_card: self.set_card_hidden(self.cards[key]) card_to_show = self.cards[card_id] self.start_card_switch(card_to_show) self.active_card = self.cards[card_id] self.layout().setCurrentWidget(self.active_card) def start_card_switch(self, card_to_show): self.slide_out_animation = QPropertyAnimation(self.active_card, "geometry") self.slide_out_animation.setDuration(ANIMATION_DURATION) self.slide_out_animation.setEasingCurve(QEasingCurve.OutCubic) self.slide_out_animation.setStartValue(self.visible_geometry) self.slide_out_animation.setEndValue(self.hidden_geometry) self.set_card_hidden(card_to_show) self.slide_in_animation = QPropertyAnimation(card_to_show, "geometry") self.slide_in_animation.setDuration(ANIMATION_DURATION) self.slide_in_animation.setEasingCurve(QEasingCurve.InCubic) self.slide_in_animation.setStartValue(self.hidden_geometry) self.slide_in_animation.setEndValue(self.visible_geometry) self.animation_group = QParallelAnimationGroup() self.animation_group.addAnimation(self.slide_out_animation) self.animation_group.addAnimation(self.slide_in_animation) self.animation_group.start()
class AlbumCardViewer(QWidget): """ 定义一个专辑卡视图 """ playSignal = pyqtSignal(list) nextPlaySignal = pyqtSignal(list) albumNumChanged = pyqtSignal(int) saveAlbumInfoSig = pyqtSignal(dict, dict) addAlbumToPlayingSignal = pyqtSignal(list) # 将专辑添加到正在播放 switchToAlbumInterfaceSig = pyqtSignal(dict) selectionModeStateChanged = pyqtSignal(bool) checkedAlbumCardNumChanged = pyqtSignal(int) addAlbumToNewCustomPlaylistSig = pyqtSignal(list) # 将专辑添加到新建的播放列表 addAlbumToCustomPlaylistSig = pyqtSignal(str, list) # 将专辑添加到已存在的自定义播放列表 def __init__(self, albumInfo_list: list, parent=None): super().__init__(parent) self.albumInfo_list = albumInfo_list # 初始化网格的列数 self.columnNum = 1 self.albumCardDict_list = [] self.albumCard_list = [] self.checkedAlbumCard_list = [] self.currentGroupDict_list = [] # 初始化标志位 self.isInSelectionMode = False self.isAllAlbumCardsChecked = False # 设置当前排序方式 self.sortMode = '添加时间' # 实例化滚动部件的竖直布局 self.scrollWidgetVBoxLayout = QVBoxLayout() # 实例化滚动区域和滚动区域的窗口 self.__createGuideLabel() self.scrollArea = ScrollArea(self) self.scrollWidget = QWidget() self.albumBlurBackground = AlbumBlurBackground(self.scrollWidget) # 创建专辑卡并将其添加到布局中 self.__createAlbumCards() # 初始化小部件 self.__initWidget() def __initWidget(self): """ 初始化小部件 """ self.resize(1270, 760) # 隐藏磨砂背景 self.albumBlurBackground.hide() # 设置导航标签的可见性 self.guideLabel.raise_() self.guideLabel.setHidden(bool(self.albumCard_list)) # 初始化滚动条 self.scrollArea.setHorizontalScrollBarPolicy( Qt.ScrollBarAlwaysOff) self.scrollWidget.setObjectName('scrollWidget') self.__connectSignalToSlot() self.__initLayout() self.__setQss() def __createGuideLabel(self): """ 创建导航标签 """ self.guideLabel = QLabel('这里没有可显示的内容。请尝试其他筛选器。', self) self.guideLabel.setStyleSheet( "color: black; font: 25px 'Microsoft YaHei'") self.guideLabel.resize(500, 26) self.guideLabel.move(35, 286) def __createAlbumCards(self): """ 将专辑卡添加到窗口中 """ # 创建并行动画组 self.hideCheckBoxAniGroup = QParallelAnimationGroup(self) self.hideCheckBoxAni_list = [] for albumInfo in self.albumInfo_list: self.__createOneAlbumCard(albumInfo) def __createOneAlbumCard(self, albumInfo: dict): """ 创建一个专辑卡 """ # 实例化专辑卡和动画 albumCard = AlbumCard(albumInfo, self) # 创建动画 hideCheckBoxAni = QPropertyAnimation( albumCard.checkBoxOpacityEffect, b'opacity') self.hideCheckBoxAniGroup.addAnimation(hideCheckBoxAni) self.hideCheckBoxAni_list.append(hideCheckBoxAni) # 将含有专辑卡及其信息的字典插入列表 album = albumInfo['album'] self.albumCard_list.append(albumCard) self.albumCardDict_list.append({'albumCard': albumCard, 'albumName': album, 'year': albumInfo['year'][:4], 'songer': albumInfo['songer'], 'firstLetter': pinyin.get_initial(album[0])[0].upper()}) # 专辑卡信号连接到槽函数 albumCard.playSignal.connect(self.playSignal) albumCard.nextPlaySignal.connect(self.nextPlaySignal) albumCard.saveAlbumInfoSig.connect(self.saveAlbumInfoSig) albumCard.deleteCardSig.connect(self.showDeleteOneCardPanel) albumCard.addToPlayingSignal.connect( self.addAlbumToPlayingSignal) albumCard.switchToAlbumInterfaceSig.connect( self.switchToAlbumInterfaceSig) albumCard.checkedStateChanged.connect( self.__albumCardCheckedStateChangedSlot) albumCard.showBlurAlbumBackgroundSig.connect( self.__showBlurAlbumBackground) albumCard.hideBlurAlbumBackgroundSig.connect( self.albumBlurBackground.hide) albumCard.addAlbumToCustomPlaylistSig.connect( self.addAlbumToCustomPlaylistSig) albumCard.addAlbumToNewCustomPlaylistSig.connect( self.addAlbumToNewCustomPlaylistSig) def __connectSignalToSlot(self): """ 将信号连接到槽函数 """ # 动画完成隐藏复选框 self.hideCheckBoxAniGroup.finished.connect(self.__hideAllCheckBox) def __initLayout(self): """ 初始化布局 """ # 按照添加时间分组 self.sortByAddTime() self.scrollWidgetVBoxLayout.setSpacing(30) self.scrollWidgetVBoxLayout.setContentsMargins(10, 245, 0, 120) self.scrollWidget.setLayout(self.scrollWidgetVBoxLayout) self.scrollArea.setWidget(self.scrollWidget) def resizeEvent(self, event): """ 根据宽度调整网格的列数 """ super().resizeEvent(event) self.scrollArea.resize(self.size()) # 如果第一次超过1337就调整网格的列数 if self.width() >= 1790 and self.columnNum != 8: self.__updateColumnNum(8) if 1570 <= self.width() < 1790 and self.columnNum != 7: self.__updateColumnNum(7) elif 1350 <= self.width() < 1570 and self.columnNum != 6: self.__updateColumnNum(6) elif 1130 < self.width() < 1350 and self.columnNum != 5: self.__updateColumnNum(5) elif 910 < self.width() <= 1130 and self.columnNum != 4: self.__updateColumnNum(4) elif 690 < self.width() <= 910: self.__updateColumnNum(3) elif self.width() <= 690: self.__updateColumnNum(2) # 调整滚动条 self.scrollArea.verticalScrollBar().move(-1, 40) self.scrollArea.verticalScrollBar().resize( self.scrollArea.verticalScrollBar().width(), self.height() - 156) def __updateColumnNum(self, columnNum: int): """ 更新网格列数 """ self.columnNum = columnNum for currentGroup_dict in self.currentGroupDict_list: gridLayout = currentGroup_dict['gridLayout'] # type:GridLayout gridLayout.updateColumnNum(columnNum, 210, 290) self.__adjustScrollWidgetSize() def __adjustScrollWidgetSize(self): """ 调整滚动部件的高度 """ rowCount = sum([currentGroup_dict['gridLayout'].rowCount() for currentGroup_dict in self.currentGroupDict_list]) containerCount = len(self.currentGroupDict_list) self.scrollWidget.resize( self.width(), 310 * rowCount + 60 * containerCount * (self.sortMode != '添加日期') + 120 + 245) def __removeContainerFromVBoxLayout(self): """ 从竖直布局中移除专辑卡容器 """ for currentGroup_dict in self.currentGroupDict_list: # 将专辑卡从每个网格布局中移除 currentGroup_dict['gridLayout'].removeAllWidgets() self.scrollWidgetVBoxLayout.removeWidget( currentGroup_dict['container']) currentGroup_dict['container'].deleteLater() currentGroup_dict['gridLayout'].deleteLater() self.currentGroupDict_list = [] def __addContainterToVBoxLayout(self): """ 将当前的分组添加到箱式布局中 """ for currentGroup_dict in self.currentGroupDict_list: self.scrollWidgetVBoxLayout.addWidget( currentGroup_dict['container'], 0, Qt.AlignTop) def __addAlbumCardToGridLayout(self): """ 将专辑卡添加到每一个网格布局中 """ for currentGroup_dict in self.currentGroupDict_list: for index, albumCard in enumerate(currentGroup_dict['albumCard_list']): row = index // self.columnNum column = index - row * self.columnNum currentGroup_dict['gridLayout'].addWidget( albumCard, row, column) currentGroup_dict['gridLayout'].setAlignment(Qt.AlignLeft) def sortByAddTime(self): """ 按照添加时间分组 """ self.sortMode = '添加时间' # 创建一个包含所有歌曲卡的网格布局 container = QWidget() gridLayout = GridLayout() gridLayout.setVerticalSpacing(20) gridLayout.setHorizontalSpacing(10) container.setLayout(gridLayout) # 从竖直布局中移除小部件 self.__removeContainerFromVBoxLayout() # 构造一个包含布局和小部件列表字典的列表 self.addTimeGroup_list = [ {'container': container, 'gridLayout': gridLayout, 'albumCard_list': self.albumCard_list}] # 创建一个对当前分组列表引用的列表 self.currentGroupDict_list = self.addTimeGroup_list # 将专辑卡添加到布局中 self.__addAlbumCardToGridLayout() self.__addContainterToVBoxLayout() self.__adjustScrollWidgetSize() def sortByFirstLetter(self): """ 按照专辑名的首字母进行分组排序 """ self.sortMode = 'A到Z' # 将专辑卡从旧布局中移除 self.__removeContainerFromVBoxLayout() # 创建分组 firstLetter_list = [] self.firsetLetterGroupDict_list = [] # 将专辑卡添加到分组中 for albumCard_dict in self.albumCardDict_list: # 获取专辑卡的专辑名首字母(有可能不是字母) firstLetter = albumCard_dict['firstLetter'] firstLetter = firstLetter if 65 <= ord( firstLetter) <= 90 else '...' # 如果首字母属于不在列表中就将创建分组(仅限于A-Z和...) if firstLetter not in firstLetter_list: # firstLetter_list的首字母顺序和firsetLetterGroupDict_list保持一致 firstLetter_list.append(firstLetter) group = GroupBox(firstLetter) gridLayout = GridLayout() group.setLayout(gridLayout) gridLayout.setVerticalSpacing(20) gridLayout.setHorizontalSpacing(10) self.firsetLetterGroupDict_list.append( {'container': group, 'firstLetter': firstLetter, 'gridLayout': gridLayout, 'albumCard_list': []}) # 将专辑卡添加到分组中 index = firstLetter_list.index(firstLetter) self.firsetLetterGroupDict_list[index]['albumCard_list'].append( albumCard_dict['albumCard']) # 排序列表 self.firsetLetterGroupDict_list.sort( key=lambda item: item['firstLetter']) # 将...分组移到最后 if '...' in firstLetter_list: unique_group = self.firsetLetterGroupDict_list.pop(0) self.firsetLetterGroupDict_list.append(unique_group) # 将专辑加到分组的网格布局中 self.currentGroupDict_list = self.firsetLetterGroupDict_list # 将专辑卡添加到网格布局中并将容器添加到竖直布局中 self.__addAlbumCardToGridLayout() self.__addContainterToVBoxLayout() self.__adjustScrollWidgetSize() def sortByYear(self): """ 按照专辑的年份进行分组排序 """ self.sortMode = '发行年份' # 将专辑卡从旧布局中移除 self.__removeContainerFromVBoxLayout() # 创建分组 year_list = [] self.yearGroupDict_list = [] # 将专辑加到分组中 for albumCard_dict in self.albumCardDict_list: year = albumCard_dict['year'] year = '未知' if year == '未知年份' else year # 如果年份不在年份列表中就创建分组 if year not in year_list: year_list.append(year) # 实例化分组和网格布局 group = GroupBox(year) gridLayout = GridLayout() group.setLayout(gridLayout) gridLayout.setVerticalSpacing(20) gridLayout.setHorizontalSpacing(10) self.yearGroupDict_list.append( {'container': group, 'year': year, 'gridLayout': gridLayout, 'albumCard_list': []}) # 将专辑卡添加到分组中 index = year_list.index(year) self.yearGroupDict_list[index]['albumCard_list'].append( albumCard_dict['albumCard']) # 按照年份从进到远排序 self.yearGroupDict_list.sort( key=lambda item: item['year'], reverse=True) # 检测是否含有未知分组,有的话将其移到最后一个 if '未知' in year_list: unique_group = self.yearGroupDict_list.pop(0) self.yearGroupDict_list.append(unique_group) # 将专辑加到分组的网格布局中 self.currentGroupDict_list = self.yearGroupDict_list self.__addAlbumCardToGridLayout() self.__addContainterToVBoxLayout() self.__adjustScrollWidgetSize() def sortBySonger(self): """ 按照专辑的专辑进行分组排序 """ self.sortMode = '歌手' # 将专辑卡从旧布局中移除 self.__removeContainerFromVBoxLayout() # 创建列表 songer_list = [] self.songerGroupDict_list = [] # 将专辑加到分组中 for albumCard_dict in self.albumCardDict_list: songer = albumCard_dict['songer'] if songer not in songer_list: songer_list.append(songer) group = GroupBox(songer) gridLayout = GridLayout() group.setLayout(gridLayout) gridLayout.setVerticalSpacing(20) gridLayout.setHorizontalSpacing(10) self.songerGroupDict_list.append( {'container': group, 'songer': songer, 'gridLayout': gridLayout, 'albumCard_list': []}) # 将专辑卡添加到分组中 index = songer_list.index(songer) self.songerGroupDict_list[index]['albumCard_list'].append( albumCard_dict['albumCard']) # 排序列表 self.songerGroupDict_list.sort(key=lambda item: item['songer'].lower()) # 将专辑加到分组的网格布局中 self.currentGroupDict_list = self.songerGroupDict_list self.__addAlbumCardToGridLayout() self.__addContainterToVBoxLayout() self.__adjustScrollWidgetSize() def __setQss(self): """ 设置层叠样式 """ with open('resource\\css\\albumCardViewer.qss', encoding='utf-8') as f: self.setStyleSheet(f.read()) def findAlbumCardByAlbumInfo(self, albumInfo: dict) -> AlbumCard: """ 通过albumInfo获取对AlbumCard实例的引用 """ for albumCard in self.albumCard_list: if albumCard.albumInfo == albumInfo: return albumCard return None def findAlbumCardByName(self, albumName: str, songerName: str) -> AlbumCard: """ 通过名字查找专辑卡 """ for albumCard in self.albumCard_list: if albumCard.albumInfo['album'] == albumName: # 如果歌手名也相同就直接返回,否则在歌曲列表中寻找 if albumCard.albumInfo['songer'] == songerName: return albumCard else: for songInfo in albumCard.albumInfo['songInfo_list']: if songInfo['songer'] == songerName: return albumCard return None def __albumCardCheckedStateChangedSlot(self, albumCard: AlbumCard, isChecked: bool): """ 专辑卡选中状态改变对应的槽函数 """ # 如果专辑信息不在选中的专辑信息列表中且对应的专辑卡变为选中状态就将专辑信息添加到列表中 if albumCard not in self.checkedAlbumCard_list and isChecked: self.checkedAlbumCard_list.append(albumCard) self.checkedAlbumCardNumChanged.emit( len(self.checkedAlbumCard_list)) # 如果专辑信息已经在列表中且该专辑卡变为非选中状态就弹出该专辑信息 elif albumCard in self.checkedAlbumCard_list and not isChecked: self.checkedAlbumCard_list.pop( self.checkedAlbumCard_list.index(albumCard)) self.checkedAlbumCardNumChanged.emit( len(self.checkedAlbumCard_list)) # 如果先前不处于选择模式那么这次发生选中状态改变就进入选择模式 if not self.isInSelectionMode: # 所有专辑卡进入选择模式 self.__setAllAlbumCardSelectionModeOpen(True) # 发送信号要求主窗口隐藏播放栏 self.selectionModeStateChanged.emit(True) # 更新标志位 self.isInSelectionMode = True else: if not self.checkedAlbumCard_list: # 所有专辑卡退出选择模式 self.__setAllAlbumCardSelectionModeOpen(False) # 发送信号要求主窗口显示播放栏 self.selectionModeStateChanged.emit(False) # 更新标志位 self.isInSelectionMode = False def __setAllAlbumCardSelectionModeOpen(self, isOpenSelectionMode: bool): """ 设置所有专辑卡是否进入选择模式 """ for albumCard in self.albumCard_list: albumCard.setSelectionModeOpen(isOpenSelectionMode) # 退出选择模式时开启隐藏所有复选框的动画 if not isOpenSelectionMode: self.__startHideCheckBoxAni() def __startHideCheckBoxAni(self): """ 开始隐藏复选框动画 """ for ani in self.hideCheckBoxAni_list: ani.setStartValue(1) ani.setEndValue(0) ani.setDuration(140) self.hideCheckBoxAniGroup.start() def __hideAllCheckBox(self): """ 隐藏所有复选框 """ for albumCard in self.albumCard_list: albumCard.checkBox.hide() def unCheckAlbumCards(self): """ 取消所有已处于选中状态的专辑卡的选中状态 """ checkedAlbumCard_list_copy = self.checkedAlbumCard_list.copy() for albumCard in checkedAlbumCard_list_copy: albumCard.setChecked(False) def setAllAlbumCardCheckedState(self, isAllChecked: bool): """ 设置所有的专辑卡checked状态 """ if self.isAllAlbumCardsChecked == isAllChecked: return self.isAllAlbumCardsChecked = isAllChecked for albumCard in self.albumCard_list: albumCard.setChecked(isAllChecked) def __showBlurAlbumBackground(self, pos: QPoint, picPath: str): """ 显示磨砂背景 """ # 将全局坐标转为窗口坐标 pos = self.scrollWidget.mapFromGlobal(pos) self.albumBlurBackground.setBlurAlbum(picPath) self.albumBlurBackground.move(pos.x() - 31, pos.y() - 16) self.albumBlurBackground.show() def updateOneAlbumCardSongInfo(self, newSongInfo: dict): """ 更新一个专辑卡的一首歌的信息 """ for albumCard in self.albumCard_list: albumInfo = albumCard.albumInfo if albumInfo['album'] == newSongInfo['album'] and albumInfo['songer'] == newSongInfo['songer']: for i, songInfo in enumerate(albumInfo['songInfo_list']): if songInfo['songPath'] == newSongInfo['songPath']: albumInfo['songInfo_list'][i] = newSongInfo.copy() return albumInfo return {} def updateAllAlbumCards(self, albumInfo_list: list): """ 更新所有专辑卡 """ oldAlbumInfo_list = [ albumCard.albumInfo for albumCard in self.albumCard_list] if albumInfo_list == oldAlbumInfo_list: return # 将专辑卡从布局中移除 self.__removeContainerFromVBoxLayout() # 根据具体情况增减专辑卡 newCardNum = len(albumInfo_list) oldCardNum = len(self.albumCard_list) deltaNum = newCardNum - oldCardNum if deltaNum < 0: for i in range(oldCardNum - 1, newCardNum - 1, -1): albumCard = self.albumCard_list.pop() self.hideCheckBoxAni_list.pop() self.albumCardDict_list.pop() self.hideCheckBoxAniGroup.takeAnimation(i) albumCard.deleteLater() elif deltaNum > 0: for albumInfo in albumInfo_list[oldCardNum:]: self.__createOneAlbumCard(albumInfo) QApplication.processEvents() # 更新部分专辑卡 self.albumInfo_list = albumInfo_list iterRange = range(oldCardNum) if deltaNum > 0 else range(newCardNum) for i in iterRange: albumInfo = albumInfo_list[i] album = albumInfo['album'] self.albumCard_list[i].updateWindow(albumInfo) QApplication.processEvents() self.albumCardDict_list[i] = {'albumCard': self.albumCard_list[i], 'albumName': album, 'year': albumInfo['year'][:4], 'songer': albumInfo['songer'], 'firstLetter': pinyin.get_initial(album)[0].upper()} # 重新排序专辑卡 self.setSortMode(self.sortMode) # 根据当前专辑卡数决定是否显示导航标签 self.guideLabel.setHidden(bool(albumInfo_list)) if deltaNum != 0: self.albumNumChanged.emit(newCardNum) def setSortMode(self, sortMode: str): """ 排序专辑卡 """ self.sortMode = sortMode if sortMode == '添加时间': self.sortByAddTime() elif sortMode == 'A到Z': self.sortByFirstLetter() elif sortMode == '发行年份': self.sortByYear() elif sortMode == '歌手': self.sortBySonger() else: raise Exception(f'排序依据"{sortMode}"不存在') def showDeleteOneCardPanel(self, albumInfo: dict): """ 显示删除一个专辑卡的对话框 """ title = '是否确定要删除此项?' content = f"""如果删除"{albumInfo['album']}",它将不再位于此设备上。""" deleteCardPanel = DeleteCardPanel(title, content, self.window()) deleteCardPanel.deleteCardSig.connect( lambda: self.__deleteAlbumCards([albumInfo])) deleteCardPanel.exec_() def showDeleteMultiAlbumCardPanel(self, albumInfo_list: list): """ 显示删除多个专辑卡的对话框 """ title = '确定要删除这些项?' content = "如果你删除这些专辑,它们将不再位于此设备上。" deleteCardPanel = DeleteCardPanel(title, content, self.window()) deleteCardPanel.deleteCardSig.connect( lambda: self.__deleteAlbumCards(albumInfo_list)) deleteCardPanel.exec() def __deleteAlbumCards(self, albumInfo_list: list): """ 删除一个专辑卡 """ for albumInfo in albumInfo_list: self.albumInfo_list.remove(albumInfo) self.updateAllAlbumCards(self.albumInfo_list)
class PlayingInterface(QWidget): """ 正在播放界面 """ nextSongSig = pyqtSignal() # 点击下一首或者上一首按钮时由主界面的播放列表决定下一首的Index lastSongSig = pyqtSignal() switchPlayStateSig = pyqtSignal() randomPlayAllSignal = pyqtSignal() removeMediaSignal = pyqtSignal(int) # 点击歌曲卡或者滑动歌曲信息卡滑槽时直接设置新的index,index由自己决定 currentIndexChanged = pyqtSignal(int) switchToAlbumInterfaceSig = pyqtSignal(str, str) # 发出进入最小模式的信号 smallestModeStateChanged = pyqtSignal(bool) # 退出全屏信号 exitFullScreenSig = pyqtSignal() def __init__(self, playlist: list = None, parent=None): super().__init__(parent) self.playlist = playlist.copy() self.currentIndex = 0 self.isPlaylistVisible = False # 创建小部件 self.blurPixmap = None self.blurBackgroundPic = QLabel(self) self.blurCoverThread = BlurCoverThread(self) self.songInfoCardChute = SongInfoCardChute(self, self.playlist) self.parallelAniGroup = QParallelAnimationGroup(self) self.songInfoCardChuteAni = QPropertyAnimation(self.songInfoCardChute, b"geometry") self.playBar = PlayBar(self) self.songListWidget = SongListWidget(self.playlist, self) self.smallestModeInterface = SmallestPlayModeInterface(playlist, self) self.playBarAni = QPropertyAnimation(self.playBar, b"geometry") self.songListWidgetAni = QPropertyAnimation(self.songListWidget, b"geometry") self.guideLabel = QLabel("在这里,你将看到正在播放的歌曲以及即将播放的歌曲。", self) self.randomPlayAllButton = ThreeStatePushButton( { "normal": r"app\resource\images\playing_interface\全部随机播放_normal.png", "hover": r"app\resource\images\playing_interface\全部随机播放_hover.png", "pressed": r"app\resource\images\playing_interface\全部随机播放_pressed.png", }, " 随机播放你收藏中的所有内容", (30, 22), self) # 创建定时器 self.showPlaylistTimer = QTimer(self) self.hidePlaylistTimer = QTimer(self) # 初始化 self.__initWidget() def __initWidget(self): """ 初始化小部件 """ self.resize(1100, 870) self.currentSmallestModeSize = QSize(340, 340) self.setAttribute(Qt.WA_StyledBackground) self.guideLabel.move(45, 62) self.randomPlayAllButton.move(45, 117) self.playBar.move(0, self.height() - self.playBar.height()) # 隐藏部件 self.smallestModeInterface.hide() self.randomPlayAllButton.hide() self.guideLabel.hide() self.playBar.hide() # 设置层叠样式 self.setObjectName("playingInterface") self.guideLabel.setObjectName("guideLabel") self.randomPlayAllButton.setObjectName("randomPlayAllButton") self.__setQss() # 开启磨砂线程 if self.playlist: self.startBlurThread( self.songInfoCardChute.curSongInfoCard.albumCoverPath) # 将信号连接到槽 self.__connectSignalToSlot() # 初始化动画 self.playBarAni.setDuration(350) self.songListWidgetAni.setDuration(350) self.songListWidgetAni.setEasingCurve(QEasingCurve.InOutQuad) self.playBarAni.setEasingCurve(QEasingCurve.InOutQuad) self.parallelAniGroup.addAnimation(self.playBarAni) self.parallelAniGroup.addAnimation(self.songInfoCardChuteAni) # 初始化定时器 self.showPlaylistTimer.setInterval(120) self.hidePlaylistTimer.setInterval(120) self.showPlaylistTimer.timeout.connect(self.showPlayListTimerSlot) self.hidePlaylistTimer.timeout.connect(self.hidePlayListTimerSlot) def __setQss(self): """ 设置层叠样式 """ with open(r"app\resource\css\playInterface.qss", encoding="utf-8") as f: self.setStyleSheet(f.read()) def setBlurPixmap(self, blurPixmap): """ 设置磨砂pixmap """ self.blurPixmap = blurPixmap # 更新背景 self.__resizeBlurPixmap() def __resizeBlurPixmap(self): """ 调整背景图尺寸 """ maxWidth = max(self.width(), self.height()) if self.blurPixmap: self.blurBackgroundPic.setPixmap( self.blurPixmap.scaled( maxWidth, maxWidth, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation, )) def startBlurThread(self, albumCoverPath): """ 开启磨砂线程 """ blurRadius = [6, 40][self.smallestModeInterface.isVisible()] self.blurCoverThread.setTargetCover(albumCoverPath, blurRadius=blurRadius) self.blurCoverThread.start() def resizeEvent(self, e): """ 改变尺寸时也改变小部件的大小 """ super().resizeEvent(e) self.__resizeBlurPixmap() self.songInfoCardChute.resize(self.size()) self.blurBackgroundPic.setFixedSize(self.size()) self.playBar.resize(self.width(), self.playBar.height()) self.songListWidget.resize(self.width(), self.height() - 382) self.smallestModeInterface.resize(self.size()) if self.isPlaylistVisible: self.playBar.move(0, 190) self.songListWidget.move(0, 382) self.songInfoCardChute.move(0, 258 - self.height()) else: self.playBar.move(0, self.height() - self.playBar.height()) self.songListWidget.move(0, self.height()) def showPlayBar(self): """ 显示播放栏 """ # 只在播放栏不可见的时候显示播放栏和开启动画 if not self.playBar.isVisible(): self.playBar.show() self.songInfoCardChuteAni.setDuration(450) self.songInfoCardChuteAni.setEasingCurve(QEasingCurve.OutCubic) self.songInfoCardChuteAni.setStartValue( self.songInfoCardChute.rect()) self.songInfoCardChuteAni.setEndValue( QRect(0, -self.playBar.height() + 68, self.width(), self.height())) self.songInfoCardChuteAni.start() def hidePlayBar(self): """ 隐藏播放栏 """ if self.playBar.isVisible() and not self.isPlaylistVisible: self.playBar.hide() self.songInfoCardChuteAni.setEasingCurve(QEasingCurve.OutCirc) self.songInfoCardChuteAni.setStartValue( QRect(0, -self.playBar.height() + 68, self.width(), self.height())) self.songInfoCardChuteAni.setEndValue( QRect(0, 0, self.width(), self.height())) self.songInfoCardChuteAni.start() def showPlaylist(self): """ 显示播放列表 """ if self.songListWidgetAni.state() != QAbstractAnimation.Running: self.songInfoCardChuteAni.setDuration(350) self.songInfoCardChuteAni.setEasingCurve(QEasingCurve.InOutQuad) self.songInfoCardChuteAni.setStartValue( QRect(0, self.songInfoCardChute.y(), self.width(), self.height())) self.songInfoCardChuteAni.setEndValue( QRect(0, 258 - self.height(), self.width(), self.height())) self.playBarAni.setStartValue( QRect(0, self.playBar.y(), self.width(), self.playBar.height())) self.playBarAni.setEndValue( QRect(0, 190, self.width(), self.playBar.height())) self.songListWidgetAni.setStartValue( QRect( self.songListWidget.x(), self.songListWidget.y(), self.songListWidget.width(), self.songListWidget.height(), )) self.songListWidgetAni.setEndValue( QRect( self.songListWidget.x(), 382, self.songListWidget.width(), self.songListWidget.height(), )) if self.sender() == self.playBar.showPlaylistButton: self.playBar.pullUpArrowButton.timer.start() self.playBar.show() self.parallelAniGroup.start() self.blurBackgroundPic.hide() self.showPlaylistTimer.start() def showPlayListTimerSlot(self): """ 显示播放列表定时器溢出槽函数 """ self.showPlaylistTimer.stop() self.songListWidgetAni.start() self.isPlaylistVisible = True def hidePlayListTimerSlot(self): """ 显示播放列表定时器溢出槽函数 """ self.hidePlaylistTimer.stop() self.parallelAniGroup.start() def hidePlaylist(self): """ 隐藏播放列表 """ if self.parallelAniGroup.state() != QAbstractAnimation.Running: self.songInfoCardChuteAni.setDuration(350) self.songInfoCardChuteAni.setEasingCurve(QEasingCurve.InOutQuad) self.songInfoCardChuteAni.setStartValue( QRect(0, self.songInfoCardChute.y(), self.width(), self.height())) self.songInfoCardChuteAni.setEndValue( QRect(0, -self.playBar.height() + 68, self.width(), self.height())) self.playBarAni.setStartValue( QRect(0, 190, self.width(), self.playBar.height())) self.playBarAni.setEndValue( QRect( 0, self.height() - self.playBar.height(), self.width(), self.playBar.height(), )) self.songListWidgetAni.setStartValue( QRect( self.songListWidget.x(), self.songListWidget.y(), self.songListWidget.width(), self.songListWidget.height(), )) self.songListWidgetAni.setEndValue( QRect( self.songListWidget.x(), self.height(), self.songListWidget.width(), self.songListWidget.height(), )) if self.sender() == self.playBar.showPlaylistButton: self.playBar.pullUpArrowButton.timer.start() # self.parallelAniGroup.start() self.songListWidgetAni.start() self.hidePlaylistTimer.start() self.blurBackgroundPic.show() self.isPlaylistVisible = False def showPlaylistButtonSlot(self): """ 显示或隐藏播放列表 """ if not self.isPlaylistVisible: self.showPlaylist() else: self.hidePlaylist() def setCurrentIndex(self, index): """ 更新播放列表下标 """ # 下标大于等于0时才更新 if self.currentIndex != index and index > -1: # 在播放列表的最后一首歌被移除时不更新样式 if index >= len(self.playlist): return if self.smallestModeInterface.isVisible(): self.smallestModeInterface.setCurrentIndex(index) self.currentIndex = index self.songListWidget.setCurrentIndex(index) self.songInfoCardChute.setCurrentIndex(index) def setPlaylist(self, playlist: list, isResetIndex: bool = True): """ 更新播放列表 Parameters ---------- playlist: list 播放列表,每一个元素都是songInfo字典 isResetIndex: bool 是否将下标重置为0 """ self.playlist = deepcopy(playlist) self.currentIndex = 0 if isResetIndex else self.currentIndex if playlist: self.songInfoCardChute.setPlaylist(self.playlist, isResetIndex) self.smallestModeInterface.setPlaylist(self.playlist, isResetIndex) self.songListWidget.updateSongCards(self.playlist) # 如果小部件不可见就显示 if playlist and not self.songListWidget.isVisible(): self.__setGuideLabelHidden(True) def __settleDownPlayBar(self): """ 定住播放栏 """ self.songInfoCardChute.stopSongInfoCardTimer() def __startSongInfoCardTimer(self): """ 重新打开歌曲信息卡的定时器 """ if not self.playBar.volumeSlider.isVisible(): # 只有音量滑动条不可见才打开计时器 self.songInfoCardChute.startSongInfoCardTimer() def __songListWidgetCurrentChangedSlot(self, index): """ 歌曲列表当前下标改变插槽 """ self.currentIndex = index self.songInfoCardChute.setCurrentIndex(index) self.currentIndexChanged.emit(index) def __songInfoCardChuteCurrentChangedSlot(self, index): """ 歌曲列表当前下标改变插槽 """ self.currentIndex = index self.songListWidget.setCurrentIndex(index) self.currentIndexChanged.emit(index) def __removeSongFromPlaylist(self, index): """ 从播放列表中移除选中的歌曲 """ lastSongRemoved = False if self.currentIndex > index: self.currentIndex -= 1 self.songInfoCardChute.currentIndex -= 1 elif self.currentIndex == index: # 如果被移除的是最后一首需要将当前下标-1 if index == self.songListWidget.currentIndex + 1: self.currentIndex -= 1 self.songInfoCardChute.currentIndex -= 1 lastSongRemoved = True else: self.songInfoCardChute.setCurrentIndex(self.currentIndex) self.removeMediaSignal.emit(index) # 如果播放列表为空,隐藏小部件 if len(self.playlist) == 0: self.__setGuideLabelHidden(False) # 如果被移除的是最后一首就将当前播放歌曲置为被移除后的播放列表最后一首 """ if lastSongRemoved: self.currentIndexChanged.emit(self.currentIndex) """ def clearPlaylist(self): """ 清空歌曲卡 """ self.playlist.clear() self.songListWidget.clearSongCards() # 显示随机播放所有按钮 self.__setGuideLabelHidden(False) def __setGuideLabelHidden(self, isHidden): """ 设置导航标签和随机播放所有按钮的可见性 """ self.randomPlayAllButton.setHidden(isHidden) self.guideLabel.setHidden(isHidden) self.songListWidget.setHidden(not isHidden) if isHidden: # 隐藏导航标签时根据播放列表是否可见设置磨砂背景和播放栏的可见性 self.blurBackgroundPic.setHidden(self.isPlaylistVisible) self.playBar.setHidden(not self.isPlaylistVisible) else: # 显示导航标签时隐藏磨砂背景 self.blurBackgroundPic.hide() self.playBar.hide() # 最后再显示歌曲信息卡 self.songInfoCardChute.setHidden(not isHidden) def updateOneSongCard(self, oldSongInfo: dict, newSongInfo): """ 更新一个歌曲卡 """ self.songListWidget.updateOneSongCard(oldSongInfo, newSongInfo) self.playlist = self.songListWidget.playlist self.songInfoCardChute.playlist = self.playlist def updateMultiSongCards(self, oldSongInfo_list: list, newSongInfo_list: list): """ 更新多个歌曲卡 """ self.songListWidget.updateMultiSongCards(oldSongInfo_list, newSongInfo_list) self.playlist = self.songListWidget.playlist self.songInfoCardChute.playlist = self.playlist def showSmallestModeInterface(self): """ 显示最小播放模式界面 """ self.exitFullScreenSig.emit() # 记录下正常尺寸 self.currentGeometry = self.window().geometry() # type:QRect # 更新磨砂半径 self.blurCoverThread.setTargetCover( self.blurCoverThread.albumCoverPath, 40, (350, 350)) self.blurCoverThread.start() self.playBar.hide() self.songListWidget.hide() self.songInfoCardChute.hide() self.blurBackgroundPic.show() # 先更新歌曲信息卡再显示界面 self.smallestModeInterface.setCurrentIndex(self.currentIndex) self.smallestModeInterface.show() # 发出隐藏标题栏按钮的信号 self.smallestModeStateChanged.emit(True) self.window().setMinimumSize(206, 197) self.window().setGeometry( self.currentGeometry.x() + self.currentGeometry.width() - self.currentSmallestModeSize.width(), self.currentGeometry.y(), self.currentSmallestModeSize.width(), self.currentSmallestModeSize.height(), ) def __hideSmallestModeInterface(self): """ 隐藏最小播放模式界面 """ # 记录下最小播放模式的尺寸 self.currentSmallestModeSize = self.window().size() # type:QSize # 更新磨砂半径 self.blurCoverThread.setTargetCover( self.blurCoverThread.albumCoverPath, 6, (450, 450)) self.blurCoverThread.start() self.smallestModeInterface.hide() self.window().setMinimumSize(1030, 850) self.window().setGeometry(self.currentGeometry) # 发出显示标题栏按钮的信号 self.smallestModeStateChanged.emit(False) self.blurBackgroundPic.setHidden(self.isPlaylistVisible) self.playBar.show() self.songListWidget.show() self.songInfoCardChute.show() def __connectSignalToSlot(self): """ 将信号连接到槽 """ self.blurCoverThread.blurDone.connect(self.setBlurPixmap) # 更新背景封面和下标 self.songInfoCardChute.currentIndexChanged[int].connect( self.__songInfoCardChuteCurrentChangedSlot) self.songInfoCardChute.currentIndexChanged[str].connect( self.startBlurThread) # 显示和隐藏播放栏 self.songInfoCardChute.showPlayBarSignal.connect(self.showPlayBar) self.songInfoCardChute.hidePlayBarSignal.connect(self.hidePlayBar) # 将播放栏的信号连接到槽 self.playBar.lastSongButton.clicked.connect(self.lastSongSig) self.playBar.nextSongButton.clicked.connect(self.nextSongSig) self.playBar.playButton.clicked.connect(self.switchPlayStateSig) self.playBar.pullUpArrowButton.clicked.connect( self.showPlaylistButtonSlot) self.playBar.showPlaylistButton.clicked.connect( self.showPlaylistButtonSlot) self.playBar.smallPlayModeButton.clicked.connect( self.showSmallestModeInterface) self.playBar.enterSignal.connect(self.__settleDownPlayBar) self.playBar.leaveSignal.connect(self.__startSongInfoCardTimer) # 将歌曲列表的信号连接到槽函数 self.songListWidget.currentIndexChanged.connect( self.__songListWidgetCurrentChangedSlot) self.songListWidget.removeItemSignal.connect( self.__removeSongFromPlaylist) self.randomPlayAllButton.clicked.connect(self.randomPlayAllSignal) # 将最小化播放界面的信号连接到槽函数 self.smallestModeInterface.lastSongButton.clicked.connect( self.lastSongSig) self.smallestModeInterface.nextSongButton.clicked.connect( self.nextSongSig) self.smallestModeInterface.playButton.clicked.connect( self.switchPlayStateSig) self.smallestModeInterface.exitSmallestModeButton.clicked.connect( self.__hideSmallestModeInterface) # 切换到专辑界面 self.songInfoCardChute.switchToAlbumInterfaceSig.connect( self.switchToAlbumInterfaceSig) self.songListWidget.switchToAlbumInterfaceSig.connect( self.switchToAlbumInterfaceSig)
class BasicSongCard(QWidget): """ 歌曲卡基类 """ clicked = pyqtSignal(int) doubleClicked = pyqtSignal(int) playButtonClicked = pyqtSignal(int) addSongToPlayingSig = pyqtSignal(dict) checkedStateChanged = pyqtSignal(int, bool) addSongsToCustomPlaylistSig = pyqtSignal(str, list) def __init__(self, songInfo: dict, songCardType, parent=None): """ 实例化歌曲卡 Parameters ---------- songInfo: dict 歌曲信息字典 songCardType: `~SongCardType` 歌曲卡类型 parent: 父级 """ super().__init__(parent) self._getInfo(songInfo) self.__resizeTime = 0 self.__songCardType = songCardType # 歌曲卡类型 self.__SongNameCard = [SongTabSongNameCard, TrackNumSongNameCard][songCardType.value] # 初始化各标志位 self.isSongExist = True self.isPlaying = False self.isSelected = False self.isChecked = False self.isInSelectionMode = False self.isDoubleClicked = False # 记录songCard对应的item的下标 self.itemIndex = None # 创建小部件 if self.__songCardType == SongCardType.SONG_TAB_SONG_CARD: self.songNameCard = self.__SongNameCard(self.songName, self) elif self.__songCardType == SongCardType.ALBUM_INTERFACE_SONG_CARD: self.songNameCard = self.__SongNameCard(self.songName, self.tracknumber, self) self.__referenceWidgets() # 初始化小部件列表 self.__scaleableLabelTextWidth_list = [] # 可拉伸的标签的文本的宽度列表 self.__scaleableWidgetMaxWidth_list = [] # 可拉伸部件的最大宽度列表 self.__dynamicStyleLabel_list = [] self.__scaleableWidget_list = [] self.__clickableLabel_list = [] self.__labelSpacing_list = [] self.__label_list = [] self.__widget_list = [] # 存放所有的小部件 # 创建动画组和动画列表 self.aniGroup = QParallelAnimationGroup(self) self.__aniWidget_list = [] self.__deltaX_list = [] self.ani_list = [] # 安装事件过滤器 self.installEventFilter(self) # 信号连接到槽 self.playButton.clicked.connect(self.playButtonSlot) self.addToButton.clicked.connect(self.__showAddToMenu) self.checkBox.stateChanged.connect(self.checkedStateChangedSlot) def _getInfo(self, songInfo: dict): """ 从歌曲信息字典中获取信息 """ self.songInfo = songInfo self.songPath = songInfo.get("songPath", "") # type:str self.songName = songInfo.get("songName", "未知歌曲") # type:str self.songer = songInfo.get("songer", "未知歌手") # type:str self.album = songInfo.get("album", "未知专辑") # type:str self.year = songInfo.get("year", "未知年份") # type:str self.tcon = songInfo.get("tcon", "未知流派") # type:str self.duration = songInfo.get("duration", "0:00") # type:str self.tracknumber = songInfo.get("tracknumber", "0") # type:str def setScalableWidgets(self, scaleableWidget_list: list, scalebaleWidgetWidth_list: list, fixedWidth=0): """ 设置可随着歌曲卡的伸缩而伸缩的标签 Parameters ---------- scaleableWidget_list : 随着歌曲卡的伸缩而伸缩的小部件列表,要求第一个元素为歌名卡,后面的元素都是label scaleableWidgetWidth_list : 与可伸缩小部件相对应的小部件初始长度列表 fixedWidth : 为其他不可拉伸的小部件保留的宽度 """ if self.__scaleableWidgetMaxWidth_list: return self.__checkIsLengthEqual(scaleableWidget_list, scalebaleWidgetWidth_list) # 必须先将所有标签添加到列表中后才能调用这个函数 if not self.__label_list: raise Exception("必须先调用addLabels函数将标签添加到窗口中") # 歌名卡默认可拉伸 self.__scaleableWidget_list = scaleableWidget_list self.__scaleableWidgetMaxWidth_list = scalebaleWidgetWidth_list # 计算初始宽度 initWidth = (sum(self.__scaleableWidgetMaxWidth_list) + sum(self.__labelSpacing_list) + fixedWidth) self.resize(initWidth, 60) self.setFixedHeight(60) def addLabels(self, label_list: list, labelSpacing_list: list): """ 往歌曲卡中添加除了歌曲名卡之外的标签,只能初始化一次标签列表 Paramerter ---------- label_list : 歌名卡后的标签列表 labelSpacing_list : 每个标签的前置空白 """ if self.__label_list: return self.__checkIsLengthEqual(label_list, labelSpacing_list) self.__label_list = label_list self.__labelSpacing_list = labelSpacing_list self.__widget_list = [self.songNameCard] + self.__label_list # 移动小部件 for i in range(len(label_list)): label_list[i] def setDynamicStyleLabels(self, label_list: list): """ 设置需要动态更新样式的标签列表 """ self.__dynamicStyleLabel_list = label_list def setClickableLabels(self, clickableLabel_list: list): """ 设置可点击的标签列表 """ self.__clickableLabel_list = clickableLabel_list # 分配ID for label in self.__clickableLabel_list: label.setObjectName("clickableLabel") def setSelected(self, isSelected: bool): """ 设置选中状态 """ self.isSelected = isSelected if isSelected: self.setWidgetState("selected-leave") self.setCheckBoxBtLabelState("selected") else: self.songNameCard.setWidgetHidden(True) self.setWidgetState("notSelected-leave") state = "notSelected-play" if self.isPlaying else "notSelected-notPlay" self.setCheckBoxBtLabelState(state) self.setStyle(QApplication.style()) def setPlay(self, isPlay: bool): """ 设置播放状态并更新样式 """ self.isPlaying = isPlay self.isSelected = isPlay # 判断歌曲文件是否存在 self.isSongExist = os.path.exists(self.songPath) if isPlay: self.isSelected = True self.setCheckBoxBtLabelState("selected") self.setWidgetState("selected-leave") else: self.setCheckBoxBtLabelState("notSelected-notPlay") self.setWidgetState("notSelected-leave") self.songNameCard.setPlay(isPlay, self.isSongExist) self.setStyle(QApplication.style()) def setCheckBoxBtLabelState(self, state: str): """ 设置复选框、按钮和标签动态属性 Parameters ---------- state: str 复选框、按钮和标签的状态,可以是: * `notSelected-notPlay` * `notSelected-play` * `selected` """ self.songNameCard.setCheckBoxBtLabelState(state, self.isSongExist) for label in self.__dynamicStyleLabel_list: label.setProperty("state", state) def setWidgetState(self, state: str): """ 设置按钮组窗口和自己的状态 Parameters ---------- state: str 窗口状态,可以是: * `notSelected-leave` * `notSelected-enter` * `notSelected-pressed` * `selected-leave` * `selected-enter` * `selected-pressed` """ self.songNameCard.setButtonGroupState(state) self.setProperty("state", state) def setAnimation(self, aniWidget_list: list, deltaX_list: list): """ 设置小部件的动画 Parameters ---------- aniWidget_list : 需要设置动画的小部件列表 deltaX_list : 和aniWidget_list相对应的动画位置偏移量列表 """ self.__checkIsLengthEqual(aniWidget_list, deltaX_list) self.__aniWidget_list = aniWidget_list self.__deltaX_list = deltaX_list # 清空动画组的内容 self.ani_list.clear() self.aniGroup.clear() self.__ani_list = [ QPropertyAnimation(widget, b"geometry") for widget in self.__aniWidget_list ] # 初始化动画 for ani in self.__ani_list: ani.setDuration(400) ani.setEasingCurve(QEasingCurve.OutQuad) self.aniGroup.addAnimation(ani) self.getAniTargetX_list() def getAniTargetX_list(self): """ 计算动画的初始值 """ self.__aniTargetX_list = [] for deltaX, widget in zip(self.__deltaX_list, self.__aniWidget_list): self.__aniTargetX_list.append(deltaX + widget.x()) def eventFilter(self, obj, e: QEvent): """ 安装监听 """ if obj == self: if e.type() == QEvent.Enter: self.songNameCard.checkBox.show() self.songNameCard.buttonGroup.setHidden(self.isInSelectionMode) state = "selected-enter" if self.isSelected else "notSelected-enter" self.setWidgetState(state) self.setStyle(QApplication.style()) elif e.type() == QEvent.Leave: # 不处于选择模式下时,如果歌曲卡没被选中而鼠标离开窗口就隐藏复选框和按钮组窗口 if not self.isSelected: self.songNameCard.buttonGroup.hide() self.songNameCard.checkBox.setHidden( not self.isInSelectionMode) state = "selected-leave" if self.isSelected else "notSelected-leave" self.setWidgetState(state) self.setStyle(QApplication.style()) elif e.type() == QEvent.MouseButtonPress: state = "selected-pressed" if self.isSelected else "notSelected-pressed" if e.button() == Qt.LeftButton: self.isSelected = True self.setWidgetState(state) self.setStyle(QApplication.style()) elif e.type() == QEvent.MouseButtonRelease and e.button( ) == Qt.LeftButton: self.setWidgetState("selected-leave") self.setCheckBoxBtLabelState("selected") # 鼠标松开时将设置标签为白色 self.setStyle(QApplication.style()) elif e.type() == QEvent.MouseButtonDblClick: self.isDoubleClicked = True return super().eventFilter(obj, e) def mousePressEvent(self, e): """ 鼠标按下时移动小部件 """ super().mousePressEvent(e) # 移动小部件 if self.aniGroup.state() == QAbstractAnimation.Stopped: for deltaX, widget in zip(self.__deltaX_list, self.__aniWidget_list): widget.move(widget.x() + deltaX, widget.y()) else: self.aniGroup.stop() # 强制停止还未结束的动画 for targetX, widget in zip(self.__aniTargetX_list, self.__aniWidget_list): widget.move(targetX, widget.y()) def mouseReleaseEvent(self, e: QMouseEvent): """ 鼠标松开时开始动画 """ for ani, widget, deltaX in zip(self.__ani_list, self.__aniWidget_list, self.__deltaX_list): ani.setStartValue( QRect(widget.x(), widget.y(), widget.width(), widget.height())) ani.setEndValue( QRect(widget.x() - deltaX, widget.y(), widget.width(), widget.height())) self.aniGroup.start() if e.button() == Qt.LeftButton: self.clicked.emit(self.itemIndex) # 左键点击时才发送信号 if self.isDoubleClicked and e.button() == Qt.LeftButton: self.isDoubleClicked = False if not self.isPlaying: # 发送点击信号 self.aniGroup.finished.connect(self.__aniFinishedSlot) def __aniFinishedSlot(self): """ 动画完成时发出双击信号 """ self.doubleClicked.emit(self.itemIndex) self.aniGroup.disconnect() def setClickableLabelCursor(self, cursor): """ 设置可点击标签的光标样式 """ for label in self.__clickableLabel_list: label.setCursor(cursor) def __referenceWidgets(self): """ 引用小部件 """ self.buttonGroup = self.songNameCard.buttonGroup self.playButton = self.songNameCard.playButton self.addToButton = self.songNameCard.addToButton self.checkBox = self.songNameCard.checkBox def playButtonSlot(self): """ 播放按钮按下时更新样式 """ self.playButtonClicked.emit(self.itemIndex) def checkedStateChangedSlot(self): """ 复选框选中状态改变对应的槽函数 """ self.isChecked = self.checkBox.isChecked() self.setSelected(self.isChecked) # 只要点击了复选框就进入选择模式,由父级控制退出选择模式 self.checkBox.show() self.setSelectionModeOpen(True) # 发出选中状态改变信号 self.checkedStateChanged.emit(self.itemIndex, self.isChecked) def setSelectionModeOpen(self, isOpenSelectionMode: bool): """ 设置是否进入选择模式, 处于选择模式下复选框一直可见,按钮不管是否处于选择模式都不可见 """ if self.isInSelectionMode == isOpenSelectionMode: return # 更新标志位 self.isInSelectionMode = isOpenSelectionMode # 设置按钮和复选框的可见性 self.checkBox.setHidden(not isOpenSelectionMode) self.buttonGroup.setHidden(True) def setChecked(self, isChecked: bool): """ 设置歌曲卡选中状态 """ self.checkBox.setChecked(isChecked) def updateSongCard(self): """ 更新歌曲卡 """ # 必须被子类重写 raise NotImplementedError def __checkIsLengthEqual(self, list_1: list, list_2: list): """ 检查输入的两个列表的长度是否相等,不相等则引发错误 """ if len(list_1) != len(list_2): raise Exception("两个列表的长度必须一样") def resizeEvent(self, e): """ 改变窗口大小时移动标签 """ self.__resizeTime += 1 if self.__resizeTime > 1: # 分配多出来的宽度 deltaWidth = self.width() - self.__originalWidth self.__originalWidth = self.width() equalWidth = int(deltaWidth / len(self.__scaleableWidget_list)) self.__scaleableWidgetMaxWidth_list = [ i + equalWidth for i in self.__scaleableWidgetMaxWidth_list ] else: self.__originalWidth = self.width() # 调整小部件宽度 self.adjustWidgetWidth() # 移动标签 x = self.songNameCard.width() for label, spacing in zip(self.__label_list, self.__labelSpacing_list): # 如果标签是可变长度的,就将width设置为其最大可变宽度 if label in self.__scaleableWidget_list: index = self.__scaleableWidget_list.index(label) width = self.__scaleableWidgetMaxWidth_list[index] else: width = label.width() label.move(x + spacing, 20) x = width + label.x() # 更新动画目标移动位置 self.getAniTargetX_list() def adjustWidgetWidth(self): """ 调整小部件宽度 """ # 计算标签的宽度 self.__getScaleableLabelTextWidth() self.songNameCard.resize(self.__scaleableWidgetMaxWidth_list[0], 60) for i in range(1, len(self.__scaleableWidget_list)): label = self.__scaleableWidget_list[i] textWidth = self.__scaleableLabelTextWidth_list[i - 1] maxLabelWidth = self.__scaleableWidgetMaxWidth_list[i] width = maxLabelWidth if textWidth > maxLabelWidth else textWidth label.setFixedWidth(width) def __getScaleableLabelTextWidth(self): """ 计算可拉伸的标签的文本宽度 """ fontMetrics = QFontMetrics(QFont("Microsoft YaHei", 9)) self.__scaleableLabelTextWidth_list = [ fontMetrics.width(label.text()) for label in self.__scaleableWidget_list[1:] ] def __showAddToMenu(self): """ 显示添加到菜单 """ addToMenu = AddToMenu(parent=self) addToGlobalPos = self.mapToGlobal(QPoint(0, 0)) + QPoint( self.addToButton.x() + self.buttonGroup.x(), 0) x = addToGlobalPos.x() + self.addToButton.width() + 5 y = addToGlobalPos.y() + int(self.addToButton.height() / 2 - (13 + 38 * addToMenu.actionCount()) / 2) addToMenu.playingAct.triggered.connect( lambda: self.addSongToPlayingSig.emit(self.songInfo)) addToMenu.addSongsToPlaylistSig.connect( lambda name: self.addSongsToCustomPlaylistSig.emit( name, [self.songInfo])) addToMenu.exec(QPoint(x, y)) @property def widget_list(self) -> list: """ 返回窗口内的所有小部件组成的列表 """ return self.__widget_list @property def label_list(self) -> list: """ 返回窗口内所有标签组成的列表 """ return self.__label_list
class CarouselEditor(ElementEditorInterface): def __init__(self): ElementEditorInterface.__init__(self) self.class_name = "CarouselEditor" self.display_name = QCoreApplication.translate("CarouselEditor", "Carousel") self.tag_name = "Carousel" self.version = "1.0" self.icon = QImage(":/carousel.png") self.changed = False #self.editor = 0 self.setAutoFillBackground(True) grid = QGridLayout() self.id = QLineEdit() self.id.setMaximumWidth(200) self.adminlabel = QLineEdit() self.adminlabel.setMaximumWidth(200) titleLabel = QLabel(QCoreApplication.translate("CarouselEditor", "Carousel Module")) fnt = titleLabel.font() fnt.setPointSize(16) fnt.setBold(True) titleLabel.setFont(fnt) close = FlatButton(":/images/close_normal.png", ":/images/close_hover.png") close.setToolTip(QCoreApplication.translate("general", "Close Editor")) addSlide = QPushButton(QCoreApplication.translate("CarouselEditor", "Add Slide")) addSlide.setMaximumWidth(120) self.list = QTableWidget(0, 2, self) self.list.verticalHeader().hide() self.list.setSelectionMode(QAbstractItemView.SingleSelection) self.list.setSelectionBehavior(QAbstractItemView.SelectRows) self.list.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch ) self.list.setToolTip(QCoreApplication.translate("CarouselEditor", "Double click to edit item")) labels = ["", "Name"] self.list.setHorizontalHeaderLabels(labels) grid.addWidget(titleLabel, 0, 0) grid.addWidget(close, 0, 2, 1, 1, Qt.AlignRight) grid.addWidget(addSlide, 1, 0) grid.addWidget(self.list, 2, 0, 1, 3) grid.addWidget(QLabel("Id"), 4, 0) grid.addWidget(self.id, 5, 0) grid.addWidget(QLabel(QCoreApplication.translate("CarouselEditor", "Admin Label")), 6, 0) grid.addWidget(self.adminlabel, 7, 0) self.setLayout(grid) addSlide.clicked.connect(self.addSlide) self.adminlabel.textChanged.connect(self.contentChanged) self.id.textChanged.connect(self.contentChanged) close.clicked.connect(self.closeEditor) self.list.cellDoubleClicked.connect(self.tableDoubleClicked) self.installEventFilter(self) def closeEditor(self): if self.changed: if self.content: self.content.id = self.id.text() self.content.adminlabel = self.adminlabel.text() self.close.emit() def setContent(self, content): self.content = content if content: self.id.setText(content.id) self.adminlabel.setText(content.adminlabel) self.changed = False def getContent(self): return self.content def registerContenType(self): qmlRegisterType(Carousel, 'Carousel', 1, 0, 'Carousel') qmlRegisterType(Slide, 'Carousel', 1, 0, 'Slide') def getImportString(self): return "import Carousel 1.0\n" def getDefaultContent(self): return Carousel() def addSlide(self): slide = Slide() self.addListItem(slide) self.contentChanged() self.tableDoubleClicked(self.list.rowCount() - 1) def addListItem(self, slide): rows = self.list.rowCount() self.list.setRowCount(rows + 1) tcb = TableCellButtons() tcb.setItem(slide) tcb.deleteItem.connect(self.deleteSlide) tcb.editItem.connect(self.editSlide) self.list.setCellWidget(rows, 0, tcb) self.list.setRowHeight(rows, tcb.sizeHint().height()) titleItem = QTableWidgetItem(slide.title) titleItem.setFlags(titleItem.flags() ^ Qt.ItemIsEditable) titleItem.setData(Qt.UserRole, slide) self.list.setItem(rows, 1, titleItem) def tableDoubleClicked(self, row): item = self.list.item(row, 1) slide = item.data(Qt.UserRole) self.editor = SlideEditor() #self.editor.setSite(self.site) self.editor.setSlide(slide) self.editor.closes.connect(self.editorClosed) self.animate(item) def animate(self, item): self.row = item.row() # create a cell widget to get the right position in the table self.sourcewidget = QWidget() self.list.setCellWidget(self.row, 1, self.sourcewidget) pos = self.sourcewidget.mapTo(self, QPoint(0,0)) self.editor.setParent(self) self.editor.move(pos) self.editor.resize(self.sourcewidget.size()) self.editor.show() self.animationgroup = QParallelAnimationGroup() self.animx = QPropertyAnimation() self.animx.setDuration(300) self.animx.setStartValue(pos.x()) self.animx.setEndValue(0) self.animx.setTargetObject(self.editor) self.animx.setPropertyName("x".encode("utf-8")) self.animationgroup.addAnimation(self.animx) self.animy = QPropertyAnimation() self.animy.setDuration(300) self.animy.setStartValue(pos.y()) self.animy.setEndValue(0) self.animy.setTargetObject(self.editor) self.animy.setPropertyName("y".encode("utf-8")) self.animationgroup.addAnimation(self.animy) self.animw = QPropertyAnimation() self.animw.setDuration(300) self.animw.setStartValue(self.sourcewidget.size().width()) self.animw.setEndValue(self.size().width()) self.animw.setTargetObject(self.editor) self.animw.setPropertyName("width".encode("utf-8")) self.animationgroup.addAnimation(self.animw) self.animh = QPropertyAnimation() self.animh.setDuration(300) self.animh.setStartValue(self.sourcewidget.size().height()) self.animh.setEndValue(self.size().height()) self.animh.setTargetObject(self.editor) self.animh.setPropertyName("height".encode("utf-8")) self.animationgroup.addAnimation(self.animh) self.animationgroup.finished.connect(self.animationFineshedZoomIn) self.animationgroup.start() def animationFineshedZoomIn(self): pass def editorClosed(self): pos = self.sourcewidget.mapTo(self, QPoint(0,0)) # correct end values in case of resizing the window self.animx.setStartValue(pos.x()) self.animy.setStartValue(pos.y()) self.animw.setStartValue(self.sourcewidget.size().width()) self.animh.setStartValue(self.sourcewidget.size().height()) self.animationgroup.setDirection(QAbstractAnimation.Backward) #self.animationgroup.finished()), this, SLOT(animationFineshedZoomIn())); #connect(m_animationgroup, SIGNAL(finished()), this, SLOT(animationFineshedZoomOut())); self.animationgroup.start() item = self.list.item(self.row, 1) item.setData(Qt.UserRole, self.editor.slide) item.setText(self.editor.slide.title) if self.editor.changed: self.contentChanged() def animationFineshedZoomOut(self): #delete m_animationgroup #delete m_editor #self.editor = None pass def deleteSlide(self, slide): for row in range(self.list.rowCount()): item = self.list.item(row, 1) m = item.data(Qt.UserRole) if m == slide: self.list.removeRow(row) self.contentChanged() break def editSlide(self, slide): for row in range(self.list.rowCount()): item = self.list.item(row, 1) m = item.data(Qt.UserRole) if m == slide: self.list.selectRow(row) self.tableDoubleClicked(row) break