class Card(QDialog): def __init__(self, title, instructions, questions, answers, parent=None): super().__init__(parent) self.questions = questions self.answers = answers self.index = 0 self.mode = POSING_QUESTION self.button_labels = ['答え', '次の質問'] self.last_question = len(self.questions) - 1 self.setWindowTitle('日本語の練習') self.title = Title(title, self) self.instructions = Instructions(instructions, self) self.playfield = Playfield(self.questions[self.index], self.answers[self.index], self) self.playfield.hide_answer() self.controls = Controls(self, self.button_labels[self.mode], self.move_forward, self.quit_me) self.layout = QVBoxLayout() self.set_layout() def set_layout(self): self.layout.takeAt(0) self.layout.setAlignment(Qt.AlignCenter) self.layout.addWidget(self.title) self.layout.addWidget(self.instructions) self.layout.addWidget(self.playfield) self.layout.addWidget(self.controls) self.setLayout(self.layout) @Slot() def move_forward(self): if self.mode == POSING_QUESTION: self.mode = SHOWING_ANSWER self.show_answer() else: self.mode = POSING_QUESTION self.pose_next_question() def pose_next_question(self): self.index += 1 self.playfield.setParent(None) self.playfield = Playfield(self.questions[self.index], self.answers[self.index], self) self.playfield.hide_answer() self.controls.set_button(self.button_labels[self.mode]) self.set_layout() def show_answer(self): self.playfield.show_answer() if self.index < self.last_question: self.controls.set_button(self.button_labels[self.mode]) else: self.controls.hide_button() @Slot() def quit_me(self): QCoreApplication.instance().quit()
class OrderableListItem(QWidget, Subscribable): """Available subscription types""" DELETED = 1 UPDATED = 2 """Registered subscription callbacks""" _subscriptions: dict[int, list[callable]] """The widgets layout""" _layout: QLayout """The items id""" id = 0 def __init__(self): # Init QWidget super().__init__() # Init Subscribable Subscribable.__init__( self, (OrderableListItem.DELETED, OrderableListItem.UPDATED)) self._layout = QVBoxLayout() self.setLayout(self._layout) # Set and increase id self.id = OrderableListItem.id OrderableListItem.id += 1 def delete(self): """Delete this item""" # Perform deletion subscriptions self._trigger_subscriptions(OrderableListItem.DELETED, item=self) # Delete all children while self._layout.count(): child: QLayoutItem = self._layout.takeAt(0) child_widget: QWidget = child.widget() child_widget.deleteLater() # Delete the item itself self.deleteLater() @abstractmethod def get_order_string(self): """Get the string that this item can be ordered by :returns str: The string to order this item by""" return ""
class ResultsList(QScrollArea): def __init__(self, parent=None): # List View of results # Only Title Search and Playlist Search use this QScrollArea.__init__(self) self.setParent(parent) # Make QScrollArea transparent self.setStyleSheet( "QScrollArea { background-color: transparent } .QFrame { background-color: transparent }" ) self.setWidgetResizable(True) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) # Add touch screen control to QScrollArea QScroller.grabGesture(self, QScroller.LeftMouseButtonGesture) # Set layout settings of the QScrollArea self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(0) # A separate QWidget is needed to properly use QScrollArea frame = QFrame() frame.setLayout(self.layout) self.setWidget(frame) def clearResults(self): # Clear the results in the list while self.layout.count(): item = self.layout.takeAt(0) if item.widget() is not None: item.widget().deleteLater() def addResults(self, results): # Add the results to the list # results (list): list of python dict representing results details (playlist or song search) self.results = results self.clearResults() if results: for i in self.results: item = self.ResultsListItem(i, self) self.layout.addWidget(item) self.layout.addStretch(1) else: # If the results are empty, display no result label = QLabel("没有结果/No Result") label.setStyleSheet("color: white") label.setAlignment(Qt.AlignCenter) font = QFont() font.setPointSize(35) label.setFont(font) self.layout.addWidget(label) class ResultsListItem(QToolButton): def __init__(self, result, parent=None): # An item of ResultsList # result: (dict) represents details (playlist or song search) QToolButton.__init__(self) self.setParent(parent) self.result = result # Button formatting self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.setFixedHeight(70) self.setAutoRaise(True) # TODO: change with global themes self.setStyleSheet( "QToolButton:pressed { background-color: rgba(255, 255, 255, 0.1)} QToolButton { background-color: rgba(255, 255, 255, 0.05); border: 1px solid white}" ) # Set layout self.layout = QHBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(0) # Depending on result type, add action when the item is clicked if result["type"] == "songs": self.formatTitle() self.clicked.connect(self.clickedSong) elif result["type"] == "playlists": self.formatPlaylist() self.clicked.connect(self.clickedPlaylist) self.setLayout(self.layout) def formatTitle(self): # Format the title for the ResultsList # Add space to the beginning of the ResultsListItem labelQueue = self.formattedLabel(QLabel("", self)) labelQueue.setFixedWidth(70) labelQueue.setAlignment(Qt.AlignCenter) self.layout.addWidget(labelQueue) # Artist name labelArtist = self.formattedLabel( QLabel(self.result["artist_name"])) labelArtist.setFixedWidth(300) self.layout.addWidget(labelArtist) # Song title labelTitle = self.formattedLabel( QLabel(self.result["song_title"])) self.layout.addWidget(labelTitle) # Add buttons for favourites and playlists # Favourite button self.favouriteButton = QToolButton() # Toggle Favourite Icon depending on DB if self.result["favourited"] == 0: self.favouriteButton.isFavourited = False self.favouriteButton.setIcon(QIcon("icons/star.svg")) else: self.favouriteButton.isFavourited = True self.favouriteButton.setIcon( QIcon("icons/star-yellow.svg")) self.favouriteButton.setIconSize(QSize(30, 30)) self.favouriteButton.setFixedSize(70, 70) self.favouriteButton.clicked.connect(self.clickedFavourite) self.layout.addWidget(self.favouriteButton) # Playlist button playlistButton = QToolButton() playlistButton.setIcon(QIcon("icons/music-player-2.svg")) playlistButton.setIconSize(QSize(30, 30)) playlistButton.setFixedSize(70, 70) playlistButton.clicked.connect(self.addToPlaylist) self.layout.addWidget(playlistButton) def formatPlaylist(self): # Add space to beginning of LisItem labelQueue = self.formattedLabel(QLabel("", self)) labelQueue.setFixedWidth(70) labelQueue.setAlignment(Qt.AlignCenter) self.layout.addWidget(labelQueue) # Playlist name labelPlaylist = self.formattedLabel( QLabel(self.result["playlist_name"])) self.layout.addWidget(labelPlaylist) # Remove playlist button btnRemove = QToolButton() btnRemove.setText("X") btnRemove.setFixedSize(70, 70) btnRemove.setStyleSheet("color: white") btnRemove.clicked.connect(self.removePlaylist) self.layout.addWidget(btnRemove) def formattedLabel(self, label): # Format a label font = QFont() font.setPixelSize(25) # TODO: change with global themes label.setStyleSheet("color: white") label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) label.setFont(font) return label def clickedSong(self): # Called when a song is clicked on # Add to queue self.window().songQueue.addSong(self.result) if len(self.window().songQueue.getQueue()) < 2: if not self.window().mediaPlayer.currentSong: # If this is the first song to queue, signal media player to start it self.window().mediaPlayer.skipSong() # Update scrolling text marquee self.window().mediaPlayer.updateMarquee() def clickedPlaylist(self): # Called when a playlist is clicked on self.window().content.addWidget( WindowSearch( "搜索全部/Search", self, self.window().content.currentWidget().counter + 1, self.result)) self.window().content.setCurrentIndex( self.window().content.currentWidget().counter + 1) def clickedFavourite(self): # Toggle Icon and DB state of song being favourited # For songs only if self.favouriteButton.isFavourited: self.favouriteButton.isFavourited = False self.favouriteButton.setIcon(QIcon("icons/star.svg")) else: self.favouriteButton.isFavourited = True self.favouriteButton.setIcon( QIcon("icons/star-yellow.svg")) DB.setFavouriteSong(self.result["song_id"]) def addToPlaylist(self): # Opens a popup for adding songs to playlists popup = PlaylistPopup(self.result, self.window()) popup.resize(1366, 768) popup.SIGNALS.CLOSE.connect(lambda: popup.close()) popup.show() def removePlaylist(self): # Remove a playlist DB.removePlaylist(self.result["playlist_id"]) self.window().content.currentWidget().results.results.remove( self.result) self.window().content.currentWidget().results.addResults( self.window().content.currentWidget().results.results)
class Card(QMainWindow): def __init__(self, title, instructions, questions, answers, parent=None): super().__init__(parent) self.questions = questions self.answers = answers self.index = 0 self.mode = POSING_QUESTION self.button_labels = ['答え', '次の質問'] self.last_question = len(self.questions) - 1 self.setWindowTitle('日本語の練習') self.title = Title(title, self) self.instructions = Instructions(instructions, self) self.playfield = Playfield(self.questions[self.index], self.answers[self.index], self) self.controls = Controls(self, self.button_labels[self.mode], self.move_forward, self.quit_me) self.statusbar = self.statusBar() # Layout and hide the initial answer self.layout = QVBoxLayout() self.central_widget = QWidget() self.playfield.hide_answer() self.set_layout() self.setCentralWidget(self.central_widget) self.card_count = QLabel(f"{self.get_card_count()} cards") self.statusbar.addPermanentWidget(self.card_count) def set_layout(self): self.layout.takeAt(0) self.layout.setAlignment(Qt.AlignCenter) self.layout.addWidget(self.title) self.layout.addWidget(self.instructions) self.layout.addWidget(self.playfield) self.layout.addWidget(self.controls) self.central_widget.setLayout(self.layout) def update_statusbar(self): self.card_count.setText(f"{self.get_card_count()} cards") @Slot() def move_forward(self): if self.mode == POSING_QUESTION: self.mode = SHOWING_ANSWER self.show_answer() else: self.mode = POSING_QUESTION self.pose_next_question() def pose_next_question(self): self.index += 1 self.playfield.setParent(None) self.playfield = Playfield(self.questions[self.index], self.answers[self.index], self) self.playfield.hide_answer() self.controls.set_button(self.button_labels[self.mode]) self.set_layout() self.update_statusbar() def show_answer(self): self.playfield.show_answer() if self.index < self.last_question: self.controls.set_button(self.button_labels[self.mode]) else: self.controls.hide_button() @Slot() def quit_me(self): QCoreApplication.instance().quit() def get_card_count(self): return f"{self.index + 1}/{self.last_question + 1}"
class QueueList(QScrollArea): def __init__(self, parent=None): QScrollArea.__init__(self) self.setParent(parent) # Make QScrollArea transparent self.setStyleSheet( "QScrollArea { background-color: transparent } .QFrame { background-color: transparent }" ) self.setWidgetResizable(True) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) # Add Touch Gestures to menu. QScroller.grabGesture(self, QScroller.LeftMouseButtonGesture) # Set layout settings self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(0) # A singular widget frame is needed to set the scroll area properly. frame = QFrame() frame.setLayout(self.layout) self.setWidget(frame) self.updateQueue() class QueueListItem(QToolButton): def __init__(self, song, parent=None): QToolButton.__init__(self) self.setParent(parent) self.song = song # Button formatting self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.setFixedHeight(70) self.setAutoRaise(True) # TODO: change with global themes self.setStyleSheet( "QToolButton:pressed { background-color: rgba(255, 255, 255, 0.1)} QToolButton { background-color: rgba(255, 255, 255, 0.05); border: 1px solid white}" ) # Set layout self.layout = QHBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(0) self.createItem() self.setLayout(self.layout) def createItem(self): # Create a song item # Add Space to the beginning of the item labelQueue = self.formattedLabel(QLabel("", self)) labelQueue.setFixedWidth(70) labelQueue.setAlignment(Qt.AlignCenter) self.layout.addWidget(labelQueue) # Artist name labelArtist = self.formattedLabel( QLabel(self.song["artist_name"])) labelArtist.setFixedWidth(300) self.layout.addWidget(labelArtist) # Song Title labelTitle = self.formattedLabel( QLabel(self.song["song_title"])) self.layout.addWidget(labelTitle) # Set font (icon) size font = QFont() font.setPointSize(48) # Add buttons for queue-specific actions # Move the song up the queue btnMoveUp = QToolButton() btnMoveUp.setText("▲") btnMoveUp.setFixedSize(70, 70) btnMoveUp.setStyleSheet("color: white") btnMoveUp.setFont(font) btnMoveUp.clicked.connect(self.moveUp) self.layout.addWidget(btnMoveUp) # Move the song down the queue btnMoveDown = QToolButton() btnMoveDown.setText("▼") btnMoveDown.setFixedSize(70, 70) btnMoveDown.setStyleSheet("color: white") btnMoveDown.setFont(font) btnMoveDown.clicked.connect(self.moveDown) self.layout.addWidget(btnMoveDown) # Move the song to the top of the queue btnMoveTop = QToolButton() btnMoveTop.setText("⍏") btnMoveTop.setFixedSize(70, 70) btnMoveTop.setStyleSheet("color: white") btnMoveTop.setFont(font) btnMoveTop.clicked.connect(self.moveTop) self.layout.addWidget(btnMoveTop) # Play the song immediately btnPlay = QToolButton() btnPlay.setText("➤") btnPlay.setFixedSize(70, 70) btnPlay.setStyleSheet("color: white") btnPlay.setFont(font) btnPlay.clicked.connect(self.playSong) self.layout.addWidget(btnPlay) # Remove the song from the queue btnRemove = QToolButton() btnRemove.setText("X") btnRemove.setFixedSize(70, 70) btnRemove.setStyleSheet("color: white") btnRemove.setFont(font) btnRemove.clicked.connect(self.removeSong) self.layout.addWidget(btnRemove) def formattedLabel(self, label): font = QFont() font.setPixelSize(25) # TODO: change with global themes label.setStyleSheet("color: white") label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) label.setFont(font) return label def moveUp(self): # Move the song up the queue self.window().songQueue.moveUp(self.song) self.window().content.queueList.updateQueue() def moveDown(self): # Move the song down the queue self.window().songQueue.moveDown(self.song) self.window().content.queueList.updateQueue() def moveTop(self): # Move the song to the top of the queue self.window().songQueue.moveTop(self.song) self.window().content.queueList.updateQueue() def playSong(self): # Play the song immediately self.window().songQueue.moveTop(self.song) self.window().overlayBottom.buttonAction("next") self.window().content.queueList.updateQueue() def removeSong(self): # Remove the song from the queue self.window().songQueue.removeSong(self.song) self.window().content.queueList.updateQueue() if len(self.window().mediaPlayer.songQueue.getQueue()) == 0: self.window().mediaPlayer.updateMarquee() def updateQueue(self): # "Refresh" the queue visually while self.layout.count(): item = self.layout.takeAt(0) if item.widget() is not None: item.widget().deleteLater() for i in self.window().songQueue.getQueue(): item = self.QueueListItem(i, self) self.layout.addWidget(item) if self.window().songQueue.getQueue(): self.layout.addStretch(1) else: label = QLabel("没有结果/No Result") label.setStyleSheet("color: white") label.setAlignment(Qt.AlignCenter) font = QFont() font.setPointSize(35) label.setFont(font) self.layout.addWidget(label)