class SongInfoCard(QWidget): """ 歌曲信息卡 """ showPlayBarSignal = pyqtSignal() hidePlayBarSignal = pyqtSignal() switchToAlbumInterfaceSig = pyqtSignal(str, str) def __init__(self, parent=None, songInfo: dict = None): super().__init__(parent) self.setSongInfo(songInfo) self.timer = QTimer(self) self.albumCoverLabel = QLabel(self) self.songNameLabel = ClickableLabel(parent=self) self.songerAlbumLabel = ClickableLabel(parent=self) # 初始化标志位 self.isPlayBarVisible = False # 初始化 self.__initWidget() def __initWidget(self): """ 初始化小部件 """ self.resize(1307, 136) self.setFixedHeight(136) self.setMinimumWidth(400) self.albumCoverLabel.move(30, 0) self.albumCoverLabel.setFixedSize(136, 136) self.setAttribute(Qt.WA_TranslucentBackground) self.updateCard(self.songInfo) # 初始化定时器 self.timer.setInterval(3000) self.timer.timeout.connect(self.timerSlot) # 分配ID self.songNameLabel.setObjectName("songNameLabel") self.songerAlbumLabel.setObjectName("songerAlbumLabel") # 设置属性 self.songNameLabel.setProperty("state", "normal") self.songerAlbumLabel.setProperty("state", "normal") # self.__initLayout() self.__setQss() # 安装事件过滤器 self.songNameLabel.installEventFilter(self) self.songerAlbumLabel.installEventFilter(self) # 信号连接到槽 def slot(): return self.switchToAlbumInterfaceSig.emit(self.album, self.songerName) self.songNameLabel.clicked.connect(slot) self.songerAlbumLabel.clicked.connect(slot) def __initLayout(self): """ 初始化布局 """ self.songNameLabel.move(186, 30) self.songerAlbumLabel.move(186, 82) def setSongInfo(self, songInfo: dict): """ 设置歌曲信息 """ self.songInfo = songInfo if not self.songInfo: self.songInfo = {} self.album = self.songInfo.get("album", "未知专辑") self.songName = self.songInfo.get("songName", "未知歌名") self.songerName = self.songInfo.get("songer", "未知歌手") self.albumCoverPath = getCoverPath(self.songInfo.get("modifiedAlbum")) def updateCard(self, songInfo: dict): """ 更新歌曲信息卡 """ self.setSongInfo(songInfo) self.songNameLabel.setText(self.songName) self.songerAlbumLabel.setText(self.songerName + " • " + self.album) self.albumCoverLabel.setPixmap( QPixmap(self.albumCoverPath).scaled( 136, 136, Qt.KeepAspectRatio, Qt.SmoothTransformation ) ) # 调整文本 self.adjustText() def __setQss(self): """ 设置层叠样式 """ with open( r"app\resource\css\playInterfaceSongInfoCard.qss", encoding="utf-8" ) as f: self.setStyleSheet(f.read()) def eventFilter(self, obj, e: QEvent): """ 重写事件过滤器 """ if obj in [self.songNameLabel, self.songerAlbumLabel]: if e.type() == QEvent.MouseButtonPress: self.songNameLabel.setProperty("state", "pressed") self.songerAlbumLabel.setProperty("state", "pressed") self.setStyle(QApplication.style()) elif e.type() == QEvent.Enter: self.songNameLabel.setProperty("state", "hover") self.songerAlbumLabel.setProperty("state", "hover") self.setStyle(QApplication.style()) elif e.type() in [QEvent.MouseButtonRelease, QEvent.Leave]: self.songNameLabel.setProperty("state", "normal") self.songerAlbumLabel.setProperty("state", "normal") self.setStyle(QApplication.style()) return super().eventFilter(obj, e) def enterEvent(self, e): """ 鼠标进入时打开计时器并显示播放栏 """ if not self.isPlayBarVisible: if not self.timer.isActive(): # 显示播放栏 self.timer.start() self.showPlayBarSignal.emit() else: # 重置定时器 self.timer.stop() self.timer.start() def timerSlot(self): """ 定时器溢出时隐藏播放栏 """ self.timer.stop() self.hidePlayBarSignal.emit() def adjustText(self): """ 根据文本长度决定是否插入换行符 """ maxWidth = self.width() - 232 # 设置专辑名歌手名标签的长度 fontMetrics = QFontMetrics(QFont("Microsoft YaHei", 13)) songerAlbumWidth = sum( [fontMetrics.width(i) for i in self.songerAlbumLabel.text()] ) self.songerAlbumLabel.setFixedWidth(min(maxWidth, songerAlbumWidth)) # 调整专辑名标签 fontMetrics = QFontMetrics(QFont("Microsoft YaHei", 21, 75)) newSongName_list = list(self.songName) # type:list totalWidth = 0 isWrap = False for i in range(len(self.songName)): totalWidth += fontMetrics.width(self.songName[i]) if totalWidth > maxWidth: newSongName_list.insert(i, "\n") isWrap = True break if isWrap: self.songNameLabel.setText("".join(newSongName_list)) self.songNameLabel.move(186, 6) self.songerAlbumLabel.move(186, 101) self.songNameLabel.setFixedSize(maxWidth, 83) else: self.songNameLabel.move(186, 26) self.songerAlbumLabel.move(186, 82) self.songNameLabel.setFixedSize(totalWidth, 46) self.songNameLabel.setText(self.songName)
class AlbumCard(PerspectiveWidget): """ 定义包含专辑歌手名的窗口 """ playSignal = pyqtSignal(list) deleteCardSig = pyqtSignal(dict) nextPlaySignal = pyqtSignal(list) addToPlayingSignal = pyqtSignal(list) # 将专辑添加到正在播放 hideBlurAlbumBackgroundSig = pyqtSignal() saveAlbumInfoSig = pyqtSignal(dict, dict) switchToAlbumInterfaceSig = pyqtSignal(dict) checkedStateChanged = pyqtSignal(QWidget, bool) addAlbumToNewCustomPlaylistSig = pyqtSignal(list) # 将专辑添加到新建的播放列表 addAlbumToCustomPlaylistSig = pyqtSignal(str, list) # 将专辑添加到已存在的自定义播放列表 showBlurAlbumBackgroundSig = pyqtSignal(QPoint, str) # 发送专辑卡全局坐标 showAlbumInfoEditPanelSig = pyqtSignal(AlbumInfoEditPanel) # 发送显示专辑信息面板信号 def __init__(self, albumInfo: dict, parent): super().__init__(parent, True) self.albumInfo = deepcopy(albumInfo) self.songInfo_list = self.albumInfo.get("songInfo_list") # type:list self.picPath = self.albumInfo.get("cover_path") # type:str # 初始化标志位 self.isChecked = False self.isInSelectionMode = False # 创建小部件 self.__createWidgets() # 初始化 self.__initWidget() def __createWidgets(self): """ 创建小部件 """ # 实例化专辑名和歌手名 self.albumNameLabel = ClickableLabel(self.albumInfo["album"], self) self.songerNameLabel = ClickableLabel(self.albumInfo["songer"], self) # 实例化封面和按钮 self.albumPic = QLabel(self) self.playButton = BlurButton( self, (30, 65), r"app\resource\images\album_tab_interface\播放按钮_70_70.png", self.picPath, ) self.addToButton = BlurButton( self, (100, 65), r"app\resource\images\album_tab_interface\添加到按钮_70_70.png", self.picPath, ) # 创建复选框 self.checkBox = CheckBox(self, forwardTargetWidget=self.albumPic) # 创建动画和窗口特效 self.checkBoxOpacityEffect = QGraphicsOpacityEffect(self) def __initWidget(self): """ 初始化小部件 """ self.setFixedSize(210, 290) self.setAttribute(Qt.WA_StyledBackground) self.albumPic.setFixedSize(200, 200) self.playButton.move(35, 70) self.addToButton.move(105, 70) self.albumPic.setPixmap( QPixmap(self.picPath).scaled( 200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation ) ) # 给小部件添加特效 self.checkBox.setGraphicsEffect(self.checkBoxOpacityEffect) # 隐藏按钮 self.playButton.hide() self.addToButton.hide() # 设置鼠标光标 self.songerNameLabel.setCursor(Qt.PointingHandCursor) # 设置部件位置 self.__initLayout() # 分配ID和属性 self.setObjectName("albumCard") self.albumNameLabel.setObjectName("albumName") self.songerNameLabel.setObjectName("songerName") self.setProperty("isChecked", "False") self.albumNameLabel.setProperty("isChecked", "False") self.songerNameLabel.setProperty("isChecked", "False") # 将信号连接到槽函数 self.playButton.clicked.connect( lambda: self.playSignal.emit(self.songInfo_list) ) self.addToButton.clicked.connect(self.__showAddToMenu) self.checkBox.stateChanged.connect(self.__checkedStateChangedSlot) def __initLayout(self): """ 初始化布局 """ self.albumPic.move(5, 5) self.albumNameLabel.move(5, 213) self.songerNameLabel.move(5, 239) self.checkBox.move(178, 8) self.checkBox.hide() self.__adjustLabel() def enterEvent(self, e): """ 鼠标进入窗口时显示磨砂背景和按钮 """ # 显示磨砂背景 albumCardPos = self.mapToGlobal(QPoint(0, 0)) # type:QPoint self.showBlurAlbumBackgroundSig.emit(albumCardPos, self.picPath) # 处于选择模式下按钮不可见 self.playButton.setHidden(self.isInSelectionMode) self.addToButton.setHidden(self.isInSelectionMode) def leaveEvent(self, e): """ 鼠标离开时隐藏磨砂背景和按钮 """ # 隐藏磨砂背景 self.hideBlurAlbumBackgroundSig.emit() self.addToButton.hide() self.playButton.hide() def contextMenuEvent(self, event: QContextMenuEvent): """ 显示右击菜单 """ # 创建菜单 menu = AlbumCardContextMenu(parent=self) menu.playAct.triggered.connect(lambda: self.playSignal.emit(self.songInfo_list)) menu.nextToPlayAct.triggered.connect( lambda: self.nextPlaySignal.emit(self.songInfo_list) ) menu.addToMenu.playingAct.triggered.connect( lambda: self.addToPlayingSignal.emit(self.songInfo_list) ) menu.editInfoAct.triggered.connect(self.showAlbumInfoEditPanel) menu.selectAct.triggered.connect(self.__selectActSlot) menu.addToMenu.addSongsToPlaylistSig.connect( lambda name: self.addAlbumToCustomPlaylistSig.emit(name, self.songInfo_list) ) menu.addToMenu.newPlayList.triggered.connect( lambda: self.addAlbumToNewCustomPlaylistSig.emit(self.songInfo_list) ) menu.deleteAct.triggered.connect( lambda: self.deleteCardSig.emit(self.albumInfo) ) menu.exec(event.globalPos()) def __adjustLabel(self): """ 根据专辑名的长度决定是否换行和添加省略号 """ newText, isWordWrap = autoWrap(self.albumNameLabel.text(), 22) if isWordWrap: # 添加省略号 index = newText.index("\n") fontMetrics = QFontMetrics(QFont("Microsoft YaHei", 10, 75)) secondLineText = fontMetrics.elidedText( newText[index + 1 :], Qt.ElideRight, 200 ) newText = newText[: index + 1] + secondLineText self.albumNameLabel.setText(newText) # 给歌手名添加省略号 fontMetrics = QFontMetrics(QFont("Microsoft YaHei", 10, 25)) newSongerName = fontMetrics.elidedText( self.songerNameLabel.text(), Qt.ElideRight, 200 ) self.songerNameLabel.setText(newSongerName) self.songerNameLabel.adjustSize() self.albumNameLabel.adjustSize() self.songerNameLabel.move( 5, self.albumNameLabel.y() + self.albumNameLabel.height() - 4 ) def mouseReleaseEvent(self, e): """ 鼠标松开发送切换到专辑界面信号或者取反选中状态 """ super().mouseReleaseEvent(e) if e.button() == Qt.LeftButton: if self.isInSelectionMode: self.setChecked(not self.isChecked) else: # 不处于选择模式时且鼠标松开事件不是复选框发来的才发送切换到专辑界面的信号 self.switchToAlbumInterfaceSig.emit(self.albumInfo) def updateWindow(self, newAlbumInfo: dict): """ 更新专辑卡窗口信息 """ if newAlbumInfo == self.albumInfo: return self.albumInfo = deepcopy(newAlbumInfo) self.songInfo_list = self.albumInfo["songInfo_list"] self.picPath = newAlbumInfo["cover_path"] self.albumPic.setPixmap( QPixmap(self.picPath).scaled( 200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation ) ) self.albumNameLabel.setText(newAlbumInfo["album"]) self.songerNameLabel.setText(newAlbumInfo["songer"]) self.playButton.setBlurPic(newAlbumInfo["cover_path"], 30) self.addToButton.setBlurPic(newAlbumInfo["cover_path"], 30) self.__adjustLabel() def showAlbumInfoEditPanel(self): """ 显示专辑信息编辑面板 """ oldAlbumInfo = deepcopy(self.albumInfo) infoEditPanel = AlbumInfoEditPanel(self.albumInfo, self.window()) infoEditPanel.saveInfoSig.connect( lambda newAlbumInfo: self.__saveAlbumInfoSlot(oldAlbumInfo, newAlbumInfo) ) self.showAlbumInfoEditPanelSig.emit(infoEditPanel) infoEditPanel.setStyle(QApplication.style()) infoEditPanel.exec_() def __saveAlbumInfoSlot(self, oldAlbumInfo: dict, newAlbumInfo: dict): """ 保存专辑信息并更新界面 """ newAlbumInfo_copy = deepcopy(newAlbumInfo) # self.updateWindow(newAlbumInfo) self.saveAlbumInfoSig.emit(oldAlbumInfo, newAlbumInfo_copy) self.albumInfo["songInfo_list"].sort( key=lambda songInfo: int(songInfo["tracknumber"]) ) # 更新专辑封面 self.updateAlbumCover(newAlbumInfo["cover_path"]) def updateAlbumCover(self, coverPath: str): """ 更新专辑封面 """ self.picPath = coverPath self.albumPic.setPixmap( QPixmap(self.picPath).scaled( 200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation ) ) self.playButton.setBlurPic(coverPath, 30) self.addToButton.setBlurPic(coverPath, 30) def __checkedStateChangedSlot(self): """ 复选框选中状态改变对应的槽函数 """ self.isChecked = self.checkBox.isChecked() # 发送信号 self.checkedStateChanged.emit(self, self.isChecked) # 更新属性和背景色 self.setProperty("isChecked", str(self.isChecked)) self.albumNameLabel.setProperty("isChecked", str(self.isChecked)) self.songerNameLabel.setProperty("isChecked", str(self.isChecked)) self.setStyle(QApplication.style()) def setChecked(self, isChecked: bool): """ 设置歌曲卡的选中状态 """ self.checkBox.setChecked(isChecked) def setSelectionModeOpen(self, isOpenSelectionMode: bool): """ 设置是否进入选择模式, 处于选择模式下复选框一直可见,按钮不可见 """ if self.isInSelectionMode == isOpenSelectionMode: return # 进入选择模式时显示复选框 if isOpenSelectionMode: self.checkBoxOpacityEffect.setOpacity(1) self.checkBox.show() self.isInSelectionMode = isOpenSelectionMode def __selectActSlot(self): """ 右击菜单选择动作对应的槽函数 """ self.setSelectionModeOpen(True) self.setChecked(True) def __showAddToMenu(self): """ 显示添加到菜单 """ addToMenu = AddToMenu(parent=self) addToGlobalPos = self.mapToGlobal(QPoint(0, 0)) + QPoint( self.addToButton.x(), self.addToButton.y() ) 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.addToPlayingSignal.emit(self.songInfo_list) ) addToMenu.newPlayList.triggered.connect( lambda: self.addAlbumToNewCustomPlaylistSig.emit(self.songInfo_list) ) addToMenu.addSongsToPlaylistSig.connect( lambda name: self.addAlbumToCustomPlaylistSig.emit(name, self.songInfo_list) ) addToMenu.exec(QPoint(x, y))
class SongCard(QWidget): """ 歌曲卡 """ clicked = pyqtSignal(int) switchToAlbumInterfaceSig = pyqtSignal(str, str) # 发送专辑名和歌手名 def __init__(self, songInfo: dict, parent=None): super().__init__(parent) self.__getInfo(songInfo) self.__resizeTime = 0 # 记录播放状态 self.isPlaying = False self.__currentState = "leave-notPlay" # 记录下每个小部件所占的最大宽度 self.__maxSongNameCardWidth = 420 self.__maxSongerLabelWidth = 284 self.__maxAlbumLabelWidth = 284 # 记录songCard对应的item的下标 self.itemIndex = None # 创建小部件 self.songNameCard = SongNameCard(songInfo["songName"], self) self.songerLabel = ClickableLabel(songInfo["songer"], self, False) self.albumLabel = ClickableLabel(songInfo["album"], self, False) self.yearLabel = QLabel(songInfo["year"], self) self.durationLabel = QLabel(songInfo["duration"], self) self.buttonGroup = self.songNameCard.buttonGroup self.playButton = self.songNameCard.playButton self.addToButton = self.songNameCard.addToButton self.__label_list = [ self.songerLabel, self.albumLabel, self.yearLabel, self.durationLabel, ] # 创建动画 self.__createAnimations() # 初始化 self.__initWidget() def __initWidget(self): """ 初始化小部件 """ self.__getLabelWidth() self.resize(1234, 60) self.resize(1234, 60) self.setFixedHeight(60) self.albumLabel.setCursor(Qt.PointingHandCursor) self.songerLabel.setCursor(Qt.PointingHandCursor) self.setAttribute(Qt.WA_StyledBackground) # 分配ID和属性 self.setObjectName("songCard") self.albumLabel.setObjectName("clickableLabel") self.songerLabel.setObjectName("clickableLabel") self.setDynamicProperty(self.__currentState) # self.__setQss() # 安装事件过滤器 self.installEventFilter(self) # 信号连接到槽 self.playButton.clicked.connect(lambda: self.clicked.emit(self.itemIndex)) self.albumLabel.clicked.connect( lambda: self.switchToAlbumInterfaceSig.emit( self.albumLabel.text(), self.songerLabel.text() ) ) def __setQss(self): """ 设置层叠样式 """ with open(r"app\resource\css\playInterfaceSongCard.qss", encoding="utf-8") as f: self.setStyleSheet(f.read()) def __getLabelWidth(self): """ 计算标签的长度 """ fontMetrics = QFontMetrics(QFont("Microsoft YaHei", 9)) self.songerWidth = fontMetrics.width(self.songInfo["songer"]) self.albumWidth = fontMetrics.width(self.songInfo["album"]) def setDynamicProperty(self, state: str): """ 设置动态属性,总共4状态,分别为enter-notPlay、enter-play、leave-notPlay、leave-play """ self.yearLabel.setProperty("state", state) self.albumLabel.setProperty("state", state) self.songerLabel.setProperty("state", state) self.durationLabel.setProperty("state", state) if state.endswith("play"): self.isPlaying = True self.songNameCard.setPlay(True) if state.startswith("enter"): self.setProperty("state", "hover") self.songNameCard.buttonGroup.setProperty("state", "hover") else: self.setProperty("state", "leave") self.songNameCard.buttonGroup.setProperty("state", "leave") self.setStyle(QApplication.style()) def resizeEvent(self, e): """ 改变窗口大小时移动标签 """ # super().resizeEvent(e) self.__resizeTime += 1 if self.__resizeTime == 1: self.originalWidth = self.width() width = self.width() - 246 # 计算各个标签所占的最大宽度 self.__maxSongNameCardWidth = int(42 / 99 * width) self.__maxSongerLabelWidth = int((width - self.__maxSongNameCardWidth) / 2) self.__maxAlbumLabelWidth = self.__maxSongerLabelWidth # 如果实际尺寸大于可分配尺寸,就调整大小 self.__adjustWidgetWidth() elif self.__resizeTime > 1: deltaWidth = self.width() - self.originalWidth self.originalWidth = self.width() # 分配多出来的宽度 threeEqualWidth = int(deltaWidth / 3) self.__maxSongNameCardWidth += threeEqualWidth self.__maxSongerLabelWidth += threeEqualWidth self.__maxAlbumLabelWidth += deltaWidth - 2 * threeEqualWidth self.__adjustWidgetWidth() # 移动标签 self.durationLabel.move(self.width() - 45, 20) self.yearLabel.move(self.width() - 190, 20) self.songerLabel.move(self.__maxSongNameCardWidth + 26, 20) self.albumLabel.move(self.songerLabel.x() + self.__maxSongerLabelWidth + 15, 20) # 更新动画目标移动位置 self.__getAniTargetX_list() def eventFilter(self, obj, e: QEvent): """ 更新样式 """ if obj == self: if e.type() in [QEvent.Enter, QEvent.MouseButtonRelease]: self.songNameCard.setWidgetHidden(False) state = "enter-play" if self.isPlaying else "enter-notPlay" self.setDynamicProperty(state) elif e.type() == QEvent.Leave: self.songNameCard.setWidgetHidden(True) state = "leave-play" if self.isPlaying else "leave-notPlay" self.setDynamicProperty(state) elif e.type() == QEvent.MouseButtonPress: self.setProperty("state", "pressed") self.songNameCard.buttonGroup.setProperty("state", "pressed") self.setStyle(QApplication.style()) 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: if not self.isPlaying: self.aniGroup.finished.connect(self.aniFinishedSlot) def aniFinishedSlot(self): """ 动画完成时更新样式 """ # 发送点击信号 self.setDynamicProperty("enter-play") self.songNameCard.checkBox.hide() self.clicked.emit(self.itemIndex) # 动画完成后需要断开连接,为下一次样式更新做准备 self.aniGroup.disconnect() def __adjustWidgetWidth(self): """ 调整小部件宽度 """ # if self.songNameCard.songNameWidth + 41 > self.__maxSongNameCardWidth: self.songNameCard.resize(self.__maxSongNameCardWidth, 60) if self.songerWidth > self.__maxSongerLabelWidth: self.songerLabel.setFixedWidth(self.__maxSongerLabelWidth) else: self.songerLabel.setFixedWidth(self.songerWidth) if self.albumWidth > self.__maxAlbumLabelWidth: self.albumLabel.setFixedWidth(self.__maxAlbumLabelWidth) else: self.albumLabel.setFixedWidth(self.albumWidth) def __createAnimations(self): """ 创建动画 """ self.aniGroup = QParallelAnimationGroup(self) self.__deltaX_list = [13, 5, -3, -11, -13] self.__aniWidget_list = [ self.songNameCard, self.songerLabel, self.albumLabel, self.yearLabel, self.durationLabel, ] 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 setPlay(self, isPlay: bool): """ 设置歌曲卡的播放状态 """ self.isPlaying = isPlay self.songNameCard.setPlay(isPlay) if isPlay: self.setDynamicProperty("enter-play") else: self.setDynamicProperty("leave-notPlay") def updateSongCard(self, songInfo: dict): """ 更新歌曲卡信息 """ self.resize(self.size()) self.__getInfo(songInfo) self.songNameCard.setSongName(songInfo["songName"]) self.songerLabel.setText(songInfo["songer"]) self.albumLabel.setText(songInfo["album"]) self.yearLabel.setText(songInfo["year"]) self.durationLabel.setText(songInfo["duration"]) # 调整宽度 self.__getLabelWidth() songerWidth = ( self.songerWidth if self.songerWidth <= self.__maxSongerLabelWidth else self.__maxSongerLabelWidth ) albumWidth = ( self.albumWidth if self.albumWidth <= self.__maxAlbumLabelWidth else self.__maxAlbumLabelWidth ) self.songerLabel.setFixedWidth(songerWidth) self.albumLabel.setFixedWidth(albumWidth) def __getInfo(self, songInfo: dict): """ 从歌曲信息中分离信息 """ self.songInfo = songInfo self.year = songInfo["year"] # type:str self.songer = songInfo["songer"] # type:str self.album = songInfo["album"] # type:str self.duration = songInfo["duration"] # type:str self.songName = songInfo["songName"] # type:str
class BasicPlaylistPanel(QWidget): """ 播放列表面板基类 """ def __init__(self, parent=None): super().__init__(parent) # 创建小部件 self.__createWidgets() # 初始化 self.__initWidget() def __createWidgets(self): """ 创建小部件 """ self.iconPic = QLabel(self) self.lineEdit = LineEdit(parent=self) self.cancelLabel = ClickableLabel("取消", self) self.button = PerspectivePushButton(parent=self) self.playlistExistedLabel = QLabel("此名称已经存在。请尝试其他名称。", self) def __initWidget(self): """ 初始化小部件 """ self.resize(586, 644) self.button.resize(313, 48) self.setAttribute(Qt.WA_StyledBackground) self.setShadowEffect() self.playlistExistedLabel.hide() self.iconPic.setPixmap( QPixmap( r"app\resource\images\createPlaylistPanel\playList_icon.png")) # 分配ID self.setObjectName("basicPlaylistPanel") self.cancelLabel.setObjectName("cancelLabel") # 信号连接到槽函数 self.cancelLabel.clicked.connect(self.parent().deleteLater) def setShadowEffect(self): """ 添加阴影 """ self.shadowEffect = QGraphicsDropShadowEffect(self) self.shadowEffect.setBlurRadius(60) self.shadowEffect.setOffset(0, 5) self.setGraphicsEffect(self.shadowEffect) def paintEvent(self, e): """ 绘制边框 """ pen = QPen(QColor(172, 172, 172)) pen.setWidth(2) painter = QPainter(self) painter.setPen(pen) painter.drawRect(0, 0, self.width() - 1, self.height() - 1) def _setQss(self): """ 设置层叠样式 """ with open("app\\resource\\css\\playlistPanel.qss", encoding="utf-8") as f: self.setStyleSheet(f.read()) def _isPlaylistExist(self, playlistName) -> bool: """ 检测播放列表是否已经存在,如果已存在就显示提示标签 """ # 扫描播放列表文件夹下的播放列表名字 if not os.path.exists("app\\Playlists"): os.mkdir("app\\Playlists") playlistName_list = [ os.path.splitext(i)[0] for i in os.listdir("app\\Playlists") ] isExist = playlistName in playlistName_list # 如果播放列表名字已存在显示提示标签 self.playlistExistedLabel.setVisible(isExist) self.button.setEnabled(not isExist) return isExist
class SettingInterface(QWidget): """ 设置界面 """ crawlComplete = pyqtSignal() selectedFoldersChanged = pyqtSignal(list) def __init__(self, parent=None): super().__init__(parent) # 读入数据 self.__readConfig() # 创建小部件 self.__createWidgets() # 初始化界面 self.__initWidget() def __createWidgets(self): """ 创建小部件 """ # 实例化滚动区域 self.all_h_layout = QHBoxLayout(self) self.scrollArea = QScrollArea(self) self.widget = QWidget() # 实例化标签 self.appLabel = QLabel("应用", self.widget) self.playLabel = QLabel("播放", self.widget) self.settingLabel = QLabel("设置", self.widget) self.colorModeLabel = QLabel("模式", self.widget) self.mediaInfoLabel = QLabel("媒体信息", self.widget) self.loginLabel = ClickableLabel("登录", self.widget) self.getMetaDataCheckBox = QCheckBox("关", self.widget) self.darkColorButton = QRadioButton("深色", self.widget) self.lightColorButton = QRadioButton("浅色", self.widget) self.equalizerLabel = ClickableLabel("均衡器", self.widget) self.musicInThisPCLabel = QLabel("此PC上的音乐", self.widget) self.selectFolderLabel = ClickableLabel("选择查找音乐的位置", self.widget) self.getMetaDataLabel = QLabel("自动检索并更新缺失的专辑封面和元数据", self.widget) def __initWidget(self): """ 初始化小部件 """ self.resize(1000, 800) self.widget.resize(self.width(), 800) self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 将信号连接到槽函数 self.getMetaDataCheckBox.stateChanged.connect( self.checkBoxStatedChangedSlot) self.selectFolderLabel.clicked.connect( self.__showSelectSongFolderPanel) self.lightColorButton.clicked.connect(self.__colorModeChangeSlot) self.darkColorButton.clicked.connect(self.__colorModeChangeSlot) # 设置鼠标光标 self.selectFolderLabel.setCursor(Qt.PointingHandCursor) self.equalizerLabel.setCursor(Qt.PointingHandCursor) self.loginLabel.setCursor(Qt.PointingHandCursor) # 分配ID self.appLabel.setObjectName("titleLabel") self.playLabel.setObjectName("titleLabel") self.colorModeLabel.setObjectName("titleLabel") self.settingLabel.setObjectName("settingLabel") self.mediaInfoLabel.setObjectName("titleLabel") self.loginLabel.setObjectName("clickableLabel") self.musicInThisPCLabel.setObjectName("titleLabel") self.equalizerLabel.setObjectName("clickableLabel") self.selectFolderLabel.setObjectName("clickableLabel") # 根据是否有选中目录来设置爬虫复选框的启用与否 self.__updateCheckBoxEnabled() # 设置选中的主题颜色 if self.config.get("color-mode", "light-color") == "light-color": self.lightColorButton.setChecked(True) else: self.darkColorButton.setChecked(True) # 初始化布局和样式 self.__initLayout() self.__setQss() def __initLayout(self): """ 初始化布局 """ self.playLabel.move(30, 247) self.colorModeLabel.move(30, 483) self.settingLabel.move(30, 63) self.equalizerLabel.move(30, 292) self.mediaInfoLabel.move(30, 350) self.darkColorButton.move(30, 572) self.getMetaDataLabel.move(30, 392) self.lightColorButton.move(30, 533) self.selectFolderLabel.move(30, 188) self.musicInThisPCLabel.move(30, 140) self.getMetaDataCheckBox.move(30, 423) self.appLabel.move(self.width() - 400, 140) self.loginLabel.move(self.width() - 400, 188) self.scrollArea.setWidget(self.widget) self.all_h_layout.addWidget(self.scrollArea) self.all_h_layout.setContentsMargins(0, 0, 0, 0) def __updateCheckBoxEnabled(self): """ 根据是否有选中目录来设置爬虫复选框的启用与否 """ if self.config.get("selected-folders"): self.getMetaDataCheckBox.setEnabled(True) else: self.getMetaDataCheckBox.setEnabled(False) def checkBoxStatedChangedSlot(self): """ 复选框状态改变对应的槽函数 """ if self.getMetaDataCheckBox.isChecked(): self.getMetaDataCheckBox.setText("开") self.getMetaDataCheckBox.setEnabled((False)) # 创建一个爬虫线程 self.__createCrawlThread() else: self.getMetaDataCheckBox.setEnabled(True) self.getMetaDataCheckBox.setText("关") def __createCrawlThread(self): """ 创建一个爬虫线程 """ self.getMetaDataThread = GetMetaDataThread( self.config["selected-folders"]) self.stateToolTip = StateTooltip("正在爬取专辑信息", "正在启动浏览器...", self.window()) # 信号连接到槽 self.getMetaDataThread.crawlSignal.connect(self.__updateStateToolTip) self.stateToolTip.closedSignal.connect(self.__stopCrawlThread) # 启用线程 self.stateToolTip.show() self.getMetaDataThread.start() def __updateStateToolTip(self, crawlState: str): """ 根据爬取进度更新进度提示框 """ if crawlState == "酷狗爬取完成": self.stateToolTip.setTitle("正在爬取流派信息") elif crawlState == "全部完成": self.stateToolTip.setState(True) # 摧毁线程 self.__stopCrawlThread() else: self.stateToolTip.setContent(crawlState) def __stopCrawlThread(self): """ 退出爬虫线程 """ self.getMetaDataThread.stop() self.getMetaDataThread.quit() self.getMetaDataThread.deleteLater() self.__updateSongInfo() def __updateSongInfo(self): """ 更新歌曲信息 """ self.getMetaDataCheckBox.setCheckState(Qt.Unchecked) # 发送爬取完成的信号 self.crawlComplete.emit() def __colorModeChangeSlot(self): """ 主题颜色改变时更新Json文件 """ if self.sender() == self.lightColorButton: self.config["color-mode"] = "light-color" else: self.config["color-mode"] = "dark-color" # self.updateConfig(self.config) def __setQss(self): """ 设置层叠样式 """ with open("app\\resource\\css\\settingInterface.qss", encoding="utf-8") as f: self.setStyleSheet(f.read()) def resizeEvent(self, e): self.appLabel.move(self.width() - 400, 140) self.loginLabel.move(self.width() - 400, 188) self.widget.resize(self.width(), self.widget.height()) super().resizeEvent(e) def __showSelectSongFolderPanel(self): """ 显示歌曲文件夹选择面板 """ selectSongFolderPanel = SelectSongFolderPanel( self.config["selected-folders"], self.window()) # 如果歌曲文件夹选择面板更新了json文件那么自己也得更新 selectSongFolderPanel.updateSelectedFoldersSig.connect( self.__updateSelectedFolders) selectSongFolderPanel.exec_() def __updateSelectedFolders(self, selectedFolders: list): """ 更新选中的歌曲文件夹列表 """ if self.config["selected-folders"] == selectedFolders: return self.config["selected-folders"] = selectedFolders # 更新配置文件 self.updateConfig({"selected-folders": selectedFolders}) # 发送更新歌曲文件夹列表的信号 self.selectedFoldersChanged.emit(selectedFolders) def __readConfig(self): """ 读入配置文件数据 """ self.__checkConfigDir() try: with open("app\\config\\config.json", encoding="utf-8") as f: self.config = load(f) # type:dict except: self.config = {"selected-folders": []} # 检查文件夹是否存在,不存在则从配置中移除 for folder in self.config["selected-folders"].copy(): if not os.path.exists(folder): self.config["selected-folders"].remove(folder) # 根据是否有选中目录来设置爬虫复选框的启用与否 if hasattr(self, "getMetaDataCheckBox"): self.__updateCheckBoxEnabled() def getConfig(self, configName: str, default=None): """ 获取配置 Parameters ---------- configName : str 配置名称 default : Any 配置的默认值 """ return self.config.get(configName, default) def updateConfig(self, config: dict): """ 更新并保存配置数据 Parameters ---------- config : dict 配置信息字典 """ self.__checkConfigDir() self.config.update(config) with open("app\\config\\config.json", "w", encoding="utf-8") as f: dump(self.config, f) def __checkConfigDir(self): """ 检查配置文件夹是否存在,不存在则创建 """ if not os.path.exists("app\\config"): os.mkdir("app\\config")