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 __init__(self, parent): super().__init__(parent) # 创建滚动区域 self.scrollArea = ScrollArea(self) self.scrollWidget = ScrollWidget(self) # 创建搜索框 self.searchLineEdit = SearchLineEdit(self) # 创建按钮 self.__createButtons() # 初始化界面 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 __createWidgets(self): """ 创建小部件 """ self.delayTimer = QTimer(self) # 创建滚动区域和抬头 self.scrollArea = ScrollArea(self) self.scrollWidget = QWidget() self.editAlbumInfoLabel = QLabel('编辑专辑信息', self) # 上半部分 self.albumCover = AlbumCoverWindow(self.cover_path, (170, 170), self.scrollWidget) self.albumNameLineEdit = LineEdit(self.albumName, self.scrollWidget) self.albumSongerLineEdit = LineEdit(self.songer, self.scrollWidget) self.tconLineEdit = LineEdit(self.tcon, self.scrollWidget) self.albumNameLabel = QLabel('专辑标题', self.scrollWidget) self.albumSongerLabel = QLabel('专辑歌手', self.scrollWidget) self.tconLabel = QLabel('类型', self.scrollWidget) # 下半部分 self.songInfoWidget_list = [] for songInfo in self.songInfo_list: songInfoWidget = SongInfoWidget(songInfo, self.scrollWidget) songInfoWidget.isTrackNumEmptySig.connect(self.__trackNumEmptySlot) self.songInfoWidget_list.append(songInfoWidget) self.saveButton = PerspectivePushButton('保存', self) self.cancelButton = PerspectivePushButton('取消', self)
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 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 NavigationWidget(BasicNavigationWidget): """ 侧边导航窗口 """ def __init__(self, parent): super().__init__(parent) # 创建滚动区域 self.scrollArea = ScrollArea(self) self.scrollWidget = ScrollWidget(self) # 创建搜索框 self.searchLineEdit = SearchLineEdit(self) # 创建按钮 self.__createButtons() # 初始化界面 self.__initWidget() def __createButtons(self): """实例化按钮 """ self.showBarButton = ToolButton( r'resource\images\navigationBar\黑色最大化导航栏.png', parent=self) self.musicGroupButton = PushButton( r'resource\images\navigationBar\黑色我的音乐.png', '我的音乐', self.scrollWidget, (400, 60), (60, 62)) self.historyButton = PushButton( r'resource\images\navigationBar\黑色最近播放.png', '最近播放的内容', self.scrollWidget, (400, 62), (60, 62)) self.playingButton = PushButton( r'resource\images\navigationBar\黑色导航栏正在播放.png', '正在播放', self.scrollWidget, (400, 62), (60, 62)) self.playlistButton = PushButton( r'resource\images\navigationBar\黑色播放列表.png', '播放列表', self.scrollWidget, (340, 60)) self.createPlaylistButton = CreatePlaylistButton(self.scrollWidget) self.settingButton = PushButton( r'resource\images\navigationBar\黑色设置按钮.png', '设置', self, (400, 62), (60, 62)) # 创建播放列表名字按钮 self.playlistName_list = self.getPlaylistNames() self.playlistNameButton_list = [ PushButton(r'resource\images\navigationBar\黑色我喜欢_60_62.png', i, self.scrollWidget, (400, 62), (60, 62)) for i in self.playlistName_list ] # 设置当前按钮 self.currentButton = self.musicGroupButton # todo:设置可选中的按钮列表 self._selectableButton_list = [ self.musicGroupButton, self.historyButton, self.playingButton, self.playlistButton, self.settingButton ] + self.playlistNameButton_list # todo:设置可选中的按钮名字列表 self._selectableButtonName_list = [ 'musicGroupButton', 'historyButton', 'playingButton', 'playlistButton', 'settingButton' ] + self.playlistName_list def __initWidget(self): """ 初始化小部件 """ self.resize(400, 800) self.setAttribute(Qt.WA_StyledBackground) self.setSelectedButton(self.musicGroupButton) # 将按钮的点击信号连接到槽函数 self._connectButtonClickedSigToSlot() # 初始化布局 self.__initLayout() def __initLayout(self): """ 初始化布局 """ self.scrollArea.move(0, 162) self.scrollArea.setWidget(self.scrollWidget) # 将按钮添加到滚动区域 self.historyButton.move(0, 62) self.showBarButton.move(0, 40) self.playingButton.move(0, 124) self.playlistButton.move(0, 186) self.searchLineEdit.move(15, 108) self.createPlaylistButton.move(340, 186) self.settingButton.move(0, self.height() - 187) self.__addPlaylistNameButtonsToScrollWidget() # 调整滚动区域的高度 self.__adjustScrollWidgetHeight() def resizeEvent(self, e): """ 调整小部件尺寸 """ self.scrollArea.resize(self.width(), self.height() - 347) self.scrollWidget.resize(self.width(), self.scrollWidget.height()) self.settingButton.move(0, self.height() - 62 - 115 - 10) def paintEvent(self, e): """ 绘制分隔符 """ painter = QPainter(self) pen = QPen(QColor(0, 0, 0, 30)) painter.setPen(pen) painter.drawLine(15, self.settingButton.y() - 1, self.width() - 15, self.settingButton.y() - 1) def getPlaylistNames(self): """ 扫描播放列表名字 """ if not os.path.exists('Playlists'): os.mkdir('Playlists') playlists = [ i[:-5] for i in os.listdir('Playlists') if i.endswith('.json') ] return playlists def __addPlaylistNameButtonsToScrollWidget(self): """ 将播放列表名字按钮添加到滚动部件上 """ for index, button in enumerate(self.playlistNameButton_list): button.move(0, 246 + index * 62) button.show() def __adjustScrollWidgetHeight(self): """ 调整滚动部件的高度 """ buttonHeight = 246 + 62 * len(self.playlistName_list) height = self.height() - 346 if self.height() - \ 346 > buttonHeight else buttonHeight self.scrollWidget.resize(400, height) def updateWindow(self): """ 更新界面 """ # 扫描播放列表 playlistName_list = self.getPlaylistNames() if playlistName_list == self.playlistName_list: return # 删除旧按钮 for i in range(len(self.playlistNameButton_list)): button = self.playlistNameButton_list.pop() button.deleteLater() # 创建新按钮 self.playlistName_list = playlistName_list self.playlistNameButton_list = [ PushButton(r'resource\images\navigationBar\黑色我喜欢_60_62.png', i, self.scrollWidget, (400, 62), (60, 62)) for i in playlistName_list ] # 移动按钮 self.__addPlaylistNameButtonsToScrollWidget() self.__adjustScrollWidgetHeight() self.update()
class SubAlbumInfoEditPanel(QWidget): """ 专辑信息编辑面板主界面 """ saveInfoSig = pyqtSignal(dict) adjustHeightSig = pyqtSignal() MAXHEIGHT = 755 def __init__(self, albumInfo: dict, parent): super().__init__(parent) self.albumInfo = albumInfo self.tcon = self.albumInfo['tcon'] # type:str self.songer = self.albumInfo['songer'] # type:str self.albumName = self.albumInfo['album'] # type:str self.cover_path = self.albumInfo['cover_path'] # type:str self.songInfo_list = self.albumInfo['songInfo_list'] # type:list self.saveErrorHappened = False self.newAlbumCoverPath = None # 创建小部件 self.__createWidgets() # 初始化 self.__initWidget() def __createWidgets(self): """ 创建小部件 """ self.delayTimer = QTimer(self) # 创建滚动区域和抬头 self.scrollArea = ScrollArea(self) self.scrollWidget = QWidget() self.editAlbumInfoLabel = QLabel('编辑专辑信息', self) # 上半部分 self.albumCover = AlbumCoverWindow(self.cover_path, (170, 170), self.scrollWidget) self.albumNameLineEdit = LineEdit(self.albumName, self.scrollWidget) self.albumSongerLineEdit = LineEdit(self.songer, self.scrollWidget) self.tconLineEdit = LineEdit(self.tcon, self.scrollWidget) self.albumNameLabel = QLabel('专辑标题', self.scrollWidget) self.albumSongerLabel = QLabel('专辑歌手', self.scrollWidget) self.tconLabel = QLabel('类型', self.scrollWidget) # 下半部分 self.songInfoWidget_list = [] for songInfo in self.songInfo_list: songInfoWidget = SongInfoWidget(songInfo, self.scrollWidget) songInfoWidget.isTrackNumEmptySig.connect(self.__trackNumEmptySlot) self.songInfoWidget_list.append(songInfoWidget) self.saveButton = PerspectivePushButton('保存', self) self.cancelButton = PerspectivePushButton('取消', self) # 创建gif # self.loadingLabel = QLabel(self) # self.movie = QMovie( # 'resource\\images\\loading_gif\\loading.gif', parent=self) def __initWidget(self): """ 初始化小部件 """ self.setFixedWidth(936) self.setMaximumHeight(self.MAXHEIGHT) self.setAttribute(Qt.WA_StyledBackground) # self.loadingLabel.setAttribute(Qt.WA_TranslucentBackground) # self.loadingLabel.setMovie(self.movie) self.scrollArea.setWidget(self.scrollWidget) self.songInfoWidgetNum = len(self.songInfoWidget_list) # type:int # 初始化定时器 self.delayTimer.setInterval(300) self.delayTimer.timeout.connect(self.__showFileDialog) # 设置滚动区域的大小 if self.songInfoWidgetNum <= 4: self.scrollArea.resize(931, 216 + self.songInfoWidgetNum * 83) else: self.scrollArea.resize(931, 595) # 初始化布局 self.__initLayout() # 信号连接到槽 self.__connectSignalToSlot() # 分配ID self.scrollArea.setObjectName('infoEditScrollArea') self.editAlbumInfoLabel.setObjectName('editAlbumInfo') # 设置阴影和层叠样式 self.__setShadowEffect() self.__setQss() def __initLayout(self): """ 初始化布局 """ self.editAlbumInfoLabel.move(30, 30) self.scrollArea.move(2, 62) self.albumCover.move(30, 13) self.albumNameLabel.move(225, 7) self.albumSongerLabel.move(578, 7) self.tconLabel.move(225, 77) self.albumNameLineEdit.move(225, 36) self.albumSongerLineEdit.move(578, 36) self.tconLineEdit.move(225, 106) for i, songInfoWidget in enumerate(self.songInfoWidget_list): songInfoWidget.move(0, songInfoWidget.height() * i + 216) self.scrollWidget.resize(931, self.songInfoWidgetNum * 83 + 216) self.albumNameLineEdit.resize(327, 40) self.albumSongerLineEdit.resize(326, 40) self.tconLineEdit.resize(327, 40) self.saveButton.resize(168, 40) self.cancelButton.resize(168, 40) self.resize(936, self.scrollArea.y() + self.scrollArea.height() + 98) self.saveButton.move(563, self.height() - 16 - self.saveButton.height()) self.cancelButton.move(735, self.height() - 16 - self.saveButton.height()) def __setShadowEffect(self): """ 添加阴影 """ self.shadowEffect = QGraphicsDropShadowEffect(self) self.shadowEffect.setBlurRadius(50) self.shadowEffect.setOffset(0, 5) self.setGraphicsEffect(self.shadowEffect) def __setQss(self): """ 设置层叠样式表 """ with open(r'resource\css\infoEditPanel.qss', encoding='utf-8') as f: self.setStyleSheet(f.read()) def paintEvent(self, event): """ 绘制背景和阴影 """ # 创建画笔 painter = QPainter(self) # 绘制边框 pen = QPen(QColor(0, 153, 188)) painter.setPen(pen) painter.drawRect(0, 0, self.width() - 1, self.height() - 1) def __trackNumEmptySlot(self, isShowErrorMsg: bool): """ 如果曲目为空则禁用保存按钮 """ self.saveButton.setEnabled(not isShowErrorMsg) if not self.sender().bottomErrorLabel.isVisible(): # 获取曲目为空的小部件的index senderIndex = self.songInfoWidget_list.index(self.sender()) # 调整面板高度 self.__adjustWidgetPos(senderIndex, isShowErrorMsg) def __saveErrorSlot(self, songInfoWidget): """ 保存歌曲失败 """ self.saveErrorHappened = True if not songInfoWidget.bottomErrorLabel.isVisible(): senderIndex = self.songInfoWidget_list.index(songInfoWidget) self.__adjustWidgetPos(senderIndex, True) def __adjustWidgetPos(self, senderIndex, isShowErrorMsg: bool): """ 调整小部件位置 """ # 调整面板高度 deltaHeight = 54 if isShowErrorMsg else -54 if self.height() == self.MAXHEIGHT: if deltaHeight < 0: height = self.scrollWidget.height() + deltaHeight if height < 600: self.resize(936, height + 155) self.adjustHeightSig.emit() self.scrollArea.resize(931, height) elif self.MAXHEIGHT - abs(deltaHeight) < self.height( ) < self.MAXHEIGHT: if deltaHeight > 0: self.resize(936, self.MAXHEIGHT) self.adjustHeightSig.emit() self.scrollArea.resize(931, 600) else: self.__adjustHeight(deltaHeight) elif self.height() <= self.MAXHEIGHT - abs(deltaHeight): self.__adjustHeight(deltaHeight) self.scrollWidget.resize(931, self.scrollWidget.height() + deltaHeight) self.saveButton.move(563, self.height() - 16 - self.saveButton.height()) self.cancelButton.move(735, self.height() - 16 - self.saveButton.height()) # 调整后面的小部件的位置 for songInfoWidget in self.songInfoWidget_list[senderIndex + 1:]: songInfoWidget.move(0, songInfoWidget.y() + deltaHeight) def __adjustHeight(self, deltaHeight): """ 调整高度 """ self.resize(936, self.height() + deltaHeight) self.adjustHeightSig.emit() self.scrollArea.resize(931, self.scrollArea.height() + deltaHeight) def saveAlbumInfo(self): """ 保存专辑信息 """ # 禁用小部件 # self.__setWidgetEnable(False) # 显示动图 # self.__showLoadingGif() # 更新标签信息 self.albumInfo['album'] = self.albumNameLineEdit.text() self.albumInfo['songer'] = self.albumSongerLineEdit.text() self.albumInfo['tcon'] = self.tconLineEdit.text() for songInfo, songInfoWidget in zip(self.songInfo_list, self.songInfoWidget_list): album_list = adjustAlbumName(self.albumNameLineEdit.text()) songInfo['album'] = album_list[0] songInfo['modifiedAlbum'] = album_list[-1] songInfo['songName'] = songInfoWidget.songNameLineEdit.text() songInfo['songer'] = songInfoWidget.songerLineEdit.text() songInfo['tcon'] = self.tconLineEdit.text() # 根据后缀名选择曲目标签的写入方式 songInfo['tracknumber'] = songInfoWidget.trackNumLineEdit.text() # 实例化标签卡 id_card = File(songInfo['songPath']) modifySongInfo(id_card, songInfo) try: id_card.save() except MutagenError: self.__saveErrorSlot(songInfoWidget) songInfoWidget.setSaveSongInfoErrorMsgHidden(False) break if not self.saveErrorHappened: self.saveAlbumCover() self.saveInfoSig.emit(self.albumInfo) self.parent().deleteLater() self.saveErrorHappened = False # 保存失败时重新启用编辑框 # self.__setWidgetEnable(True) # self.loadingLabel.hide() # self.movie.stop() def __connectSignalToSlot(self): """ 信号连接到槽 """ self.saveButton.clicked.connect(self.saveAlbumInfo) self.cancelButton.clicked.connect(self.parent().deleteLater) self.albumCover.clicked.connect(self.delayTimer.start) """ self.saveAlbumInfoThread.saveErrorSig.connect( lambda index:self.__saveErrorSlot(self.songInfoWidget_list[index])) """ def __showFileDialog(self): """ 显示专辑图片选取对话框 """ self.delayTimer.stop() path, filterType = QFileDialog.getOpenFileName( self, '打开', './', '所有文件(*.png;*.jpg;*.jpeg;*jpe;*jiff)') if path: # 复制图片到封面文件夹下 if os.path.abspath(self.cover_path) == path: return # 暂存图片地址并刷新图片 self.newAlbumCoverPath = path self.albumCover.setAlbumCover(path) def saveAlbumCover(self): """ 保存新专辑封面 """ if not self.newAlbumCoverPath: return with open(self.newAlbumCoverPath, 'rb') as f: picData = f.read() # 给专辑中的所有文件写入同一张封面 for songInfo in self.songInfo_list: writeAlbumCover(songInfo['songPath'], self.newAlbumCoverPath) # 更换文件夹下的封面图片 if self.newAlbumCoverPath == os.path.abspath(self.cover_path): return # 判断文件格式后修改后缀名 newSuffix = getPicSuffix(picData) # 如果封面路径是默认专辑封面,就修改封面路径 if self.cover_path == 'resource\\images\\未知专辑封面_200_200.png': self.cover_path = 'resource\\Album_Cover\\{0}\\{0}{1}'.format( self.albumInfo['modifiedAlbum'], newSuffix) with open(self.cover_path, 'wb') as f: f.write(picData) oldName, oldSuffix = os.path.splitext(self.cover_path) if newSuffix != oldSuffix: os.rename(self.cover_path, oldName + newSuffix) self.cover_path = oldName + newSuffix self.albumInfo['cover_path'] = self.cover_path def __setWidgetEnable(self, isEnable: bool): """ 设置编辑框是否启用 """ self.albumCover.setEnabled(isEnable) self.saveButton.setEnabled(isEnable) self.tconLineEdit.setEnabled(isEnable) self.albumNameLineEdit.setEnabled(isEnable) self.albumSongerLineEdit.setEnabled(isEnable) for songInfoWidget in self.songInfoWidget_list: songInfoWidget.setLineEditEnable(isEnable) # 更新样式 self.setStyle(QApplication.style()) def __showLoadingGif(self): """ 显示正在加载动画 """ self.loadingLabel.resize(77, 77) self.loadingLabel.move( int(self.width() / 2 - self.loadingLabel.width() / 2), int(self.height() / 2 - self.loadingLabel.height() / 2)) self.loadingLabel.raise_() self.loadingLabel.show() self.movie.start()