class TeamChooserWidget(QWidget): def __init__(self, parent, on_next, league=None): super().__init__(parent) self.layout = QVBoxLayout() self.list_view = QListView() self.league = league self.model = TeamListModel(league) self.list_view.setModel(self.model) self.list_view.setSelectionMode(QAbstractItemView.MultiSelection) self.list_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.list_view.customContextMenuRequested.connect(self.list_context_menu) self.layout.addWidget(self.list_view) self.team_add = AddItemWidget(self, "New Team Name: ", self.add_team, unavailable_options=self.model.all_team_names) self.layout.addWidget(self.team_add) self.button_h_layout = QHBoxLayout() self.button_cancel = QPushButton("Cancel") self.button_cancel.clicked.connect(self.on_cancel) self.button_next = QPushButton("Next") self.button_next.clicked.connect(on_next) self.button_h_layout.addWidget(self.button_cancel) self.button_h_layout.addWidget(self.button_next) self.layout.addLayout(self.button_h_layout) self.setLayout(self.layout) self.new_teams = [] def add_team(self, name): self.new_teams.append(name) team = Team.create(name=name) self.model.add_team(team) def list_context_menu(self, pos): self.listMenu = QMenu() current_index = self.list_view.currentIndex() select = self.listMenu.addAction("Select") select.triggered.connect( lambda: self.list_view.selectionModel().select(current_index, QtCore.QItemSelectionModel.Select) ) deselect = self.listMenu.addAction("Deselect") deselect.triggered.connect( lambda: self.list_view.selectionModel().select(current_index, QtCore.QItemSelectionModel.Deselect) ) delete = self.listMenu.addAction("Delete") delete.setDisabled(current_index.data() not in self.new_teams) delete.triggered.connect(lambda: self.model.delete_team(current_index.row())) parentPosition = self.list_view.mapToGlobal(QtCore.QPoint(0, 0)) self.listMenu.move(parentPosition + pos) self.listMenu.show() def on_cancel(self): self.model.delete_added_teams() self.window().close() def get_selected_teams(self): teams = [] for i in self.list_view.selectedIndexes(): teams.append(self.model.get_team(i.row())) return teams
class AbstractSubItemListView(QFrame): """ THIS IS AN ABSTRACT CLASS, DO NOT INSTANTIATE """ def __init__(self, parent: QObject, image_cache: ImageCache): super().__init__(parent) self._image_cache = image_cache self._model = AbstractItemListModel(self, image_cache, True) self._item_delegate = ItemDelegate(self) self._list_view = QListView(self) self._list_view.setItemDelegate(self._item_delegate) self._list_view.setContextMenuPolicy(Qt.CustomContextMenu) self._layout_manager = QVBoxLayout(self) self._layout_ui() # Connect signals to slots self._list_view.customContextMenuRequested.connect(self._show_context_menu) def _layout_ui(self): self._list_view.setSpacing(22) self._layout_manager.addWidget(self._list_view) def model(self) -> AbstractItemListModel: return self._model def _add_item_to_playlist(self, index: QModelIndex): """ Given an index in the list, allows the user to add the selected item to a playlist of their choice """ selected_item = self._list_view.model().at(index.row()) add_dialog = AddToPlaylistDialog(self, selected_item) if add_dialog.exec() == QDialog.Accepted: # Insertion playlist has been accepted selected_playlist_id: int = add_dialog.get_selection() if type(selected_item) is Episode: add_episode_to_playlist(cursor, connection, selected_item.media_id, selected_item.episode_number, selected_item.name, selected_playlist_id) elif type(selected_item) is Song: add_song_to_playlist(cursor, connection, selected_item.media_id, selected_item.name, selected_playlist_id) elif type(selected_item) is ComedySpecial: add_comedy_special_to_playlist(cursor, connection, selected_item.media_id, selected_playlist_id) else: print("Attempting to add unexpected type to playlist") exit(1) @QtCore.pyqtSlot(QPoint) def _show_context_menu(self, pos: QPoint): """ Displays a context menu of user choices on a right-click :param pos: Location where user clicked on the screen """ index = self._list_view.indexAt(pos) if index.row() != -1: # Must be a valid index global_pos = self._list_view.mapToGlobal(pos) context_menu = QMenu(self) # User should have the ability to add to playlist add_action = QAction("Add to playlist") add_action.triggered.connect(lambda: self._add_item_to_playlist(index)) context_menu.addAction(add_action) context_menu.exec(global_pos) del context_menu
class MusicPlayer(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self.options = self.parent().options self.ffmpeg = self.options['paths']['ffmpeg_bin'] self.thumbnails = self.options['paths']['thumbnails'] self.thumb_width = self.options['thumbnail']['width'] self.thumb_height = self.options['thumbnail']['height'] self.jumping = False self.blocked = False self.durations = {} self.playback_value = 0 self.text = ["None", "Repeat", "Random"] self.playlist_list = [] self.values = [ QMediaPlaylist.Loop, QMediaPlaylist.CurrentItemInLoop, QMediaPlaylist.Random ] # Thumbnail widget self.image_label = QLabel() # Control widgets self.playButton = QToolButton(clicked=self.play) self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.stopButton = QToolButton(clicked=self.stop) self.stopButton.setIcon(self.style().standardIcon(QStyle.SP_MediaStop)) self.stopButton.setEnabled(False) self.playbackButton = QToolButton(clicked=self.playback_mode) self.playbackButton.setText(self.text[0]) self.nextButton = QToolButton(clicked=self.next_song) self.nextButton.setIcon(self.style().standardIcon( QStyle.SP_MediaSkipForward)) self.previousButton = QToolButton(clicked=self.previous_song) self.previousButton.setIcon(self.style().standardIcon( QStyle.SP_MediaSkipBackward)) self.muteButton = QToolButton(clicked=self.mute_clicked) self.muteButton.setIcon(self.style().standardIcon( QStyle.SP_MediaVolume)) self.volumeSlider = QSlider(Qt.Horizontal, sliderMoved=self.change_volume) self.volumeSlider.setRange(0, 100) self.volumeSlider.setPageStep(1) self.volumeSlider.setValue(50) # Player and playlist setup self.player = QMediaPlayer() self.player.setVolume(50) self.player.stateChanged.connect(self.waveform) self.playlist = QMediaPlaylist() self.playlist.setPlaybackMode(self.values[0]) self.playlist.setCurrentIndex(1) self.player.setPlaylist(self.playlist) self.playlistModel = PlaylistModel() self.playlistModel.setPlaylist(self.playlist) self.playlistView = QListView() self.playlistView.setModel(self.playlistModel) self.playlistView.setCurrentIndex( self.playlistModel.index(self.playlist.currentIndex(), 0)) self.playlistView.activated.connect(self.jump) self.playlistView.setContextMenuPolicy(Qt.CustomContextMenu) self.playlistView.customContextMenuRequested.connect( self.list_view_menu) self.playlist.currentIndexChanged.connect( lambda position: self.change_thumbnail(position)) song_search = QLineEdit() song_search.textChanged.connect(self.search) song_search.setClearButtonEnabled(True) # Playlist self.playlist_name = QComboBox() self.playlist_name.currentTextChanged.connect(self.switch_playlist) self.refresh_lists() self.up_button = QToolButton(clicked=self.move_up) self.up_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowUp)) self.down_button = QToolButton(clicked=self.move_down) self.down_button.setIcon(self.style().standardIcon( QStyle.SP_ArrowDown)) # Sound wave widget self.wave_graphic = WaveGraphic(self) #self.wave_graphic.hide() # Testing slider again self.slider = QSlider(Qt.Horizontal) self.slider.setRange(0, self.player.duration() / 1000) self.slider.sliderMoved.connect(self.seek) self.player.durationChanged.connect(self.durationChanged) self.player.positionChanged.connect(self.positionChanged) # Shortcuts QShortcut(QKeySequence(Qt.CTRL + Qt.Key_F), self, song_search.setFocus) # Layouts setup playlist_layout = QHBoxLayout() playlist_layout.addWidget(self.playlist_name) playlist_layout.addWidget(self.up_button) playlist_layout.addWidget(self.down_button) control_layout = QHBoxLayout() control_layout.setContentsMargins(0, 0, 0, 0) control_layout.addWidget(self.stopButton) control_layout.addWidget(self.previousButton) control_layout.addWidget(self.playButton) control_layout.addWidget(self.nextButton) control_layout.addWidget(self.muteButton) control_layout.addWidget(self.volumeSlider) control_layout.addWidget(self.playbackButton) display_layout = QVBoxLayout() display_layout.addWidget(song_search) display_layout.addWidget(self.playlistView) display_layout.addLayout(playlist_layout) music_layout = QVBoxLayout() music_layout.addWidget(self.image_label) music_layout.addWidget(self.slider) music_layout.addLayout(control_layout) main_layout = QHBoxLayout() main_layout.addLayout(music_layout) main_layout.addLayout(display_layout) main_2_layout = QVBoxLayout() main_2_layout.addLayout(main_layout) main_2_layout.addWidget(self.wave_graphic) self.setLayout(main_2_layout) def waveform(self, status): if status == QMediaPlayer.PlayingState: self.wave_graphic.start() elif status == QMediaPlayer.PausedState: self.wave_graphic.pause() else: self.wave_graphic.stop() def list_view_menu(self, point): menu = QMenu("Menu", self) recommend_action = QAction('&Recommend Songs', self) menu.addAction(recommend_action) # TODO: [FEATURE] add rename song recommend_action.triggered.connect( lambda: self.parent().call_download_manager(self.get_links())) rename_action = QAction('&Rename', self) menu.addAction(rename_action) # TODO: [FEATURE] add rename song rename_action.triggered.connect(lambda: print("rename")) # Show the context menu. menu.exec_(self.playlistView.mapToGlobal(point)) def get_links(self): title = self.playlistView.selectedIndexes()[0].data() link = getYoutubeURLFromSearch(title) if len(link) > 1: return youtube_recommendations(link) def move_up(self): index = self.playlistView.currentIndex().row() if index - 1 >= 0: item, above = self.playlist.media(index), self.playlist.media( index - 1) self.playlist.removeMedia(index) self.playlist.removeMedia(index - 1) self.playlist.insertMedia(index - 1, item) self.playlist.insertMedia(index, above) self.blocked = True self.playlistView.setCurrentIndex( self.playlistModel.index(index - 1, 0)) self.blocked = False self.stop() def move_down(self): index = self.playlistView.currentIndex().row() if index + 1 <= self.playlistModel.rowCount(): item, below = self.playlist.media(index), self.playlist.media( index + 1) self.playlist.removeMedia(index + 1) self.playlist.removeMedia(index) self.playlist.insertMedia(index, below) self.playlist.insertMedia(index + 1, item) self.blocked = True self.playlistView.setCurrentIndex( self.playlistModel.index(index + 1, 0)) self.blocked = False self.stop() def search(self, part_of_song): for index in range(self.playlistModel.rowCount()): item = self.playlistModel.data(self.playlistModel.index( index, 0)).lower() self.playlistView.setRowHidden(index, part_of_song.lower() not in item) def change_thumbnail(self, position=0): self.playlistView.setCurrentIndex(self.playlistModel.index( position, 0)) song = self.playlistView.selectedIndexes()[0].data() if self.wave_graphic.is_song_cached(song): self.wave_graphic.load_waves(song) else: wc_ = WaveConverter(song, self.wave_graphic.set_wav) wc_.convert() if self.playlistView.currentIndex().data() is None or self.blocked: return max_ratio = 0 img = None for item in listdir(self.thumbnails): if item.endswith('.jpg'): ratio = similar( item, self.playlistView.currentIndex().data().replace( '.mp3', '.jpg')) if ratio > max_ratio: max_ratio = ratio img = item if img: p = QPixmap(self.thumbnails + img) self.image_label.setPixmap( p.scaled(self.thumb_width, self.thumb_height, Qt.KeepAspectRatio)) def switch_playlist(self, current_text): self.playlist.clear() if current_text == "No Playlist": self.refresh() else: if read_playlist(current_text): songs = read_playlist(current_text).split('\n') for song in songs: self.playlist.addMedia( QMediaContent(QUrl.fromLocalFile(song))) def refresh(self): # Change it so it will go to same song. if self.playlist_name.currentText() != "No Playlist": return paths = fetch_options()['paths']['music_path'].split(';') current_songs = [ self.playlistModel.data(self.playlistModel.index(row, 0)) for row in range(self.playlistModel.rowCount()) ] for path in paths: if not path: continue for item in listdir(path): if isfile(join(path, item)) and item.endswith(".mp3") and ( item not in current_songs): self.playlist.addMedia( QMediaContent(QUrl.fromLocalFile(join(path, item)))) def refresh_lists(self): path = fetch_options()['paths']['playlist'] self.playlist_name.clear() self.playlist_list = ["No Playlist"] self.playlist_name.addItem("No Playlist") for item in listdir(path): if isfile(join(path, item)) and item.endswith(".lst"): self.playlist_list.append(item.split('.')[0]) self.playlist_name.addItem(item.split('.')[0]) def playback_mode(self): # Normal -> Loop -> Random def up_value(value, max_value=3): if value + 1 == max_value: return 0 return value + 1 self.playback_value = up_value(self.playback_value) self.playlist.setPlaybackMode(self.values[self.playback_value]) self.playbackButton.setText(self.text[self.playback_value]) def jump(self, index): if index.isValid() and not self.blocked: self.playlist.setCurrentIndex(index.row()) self.jumping = True self.play() def play(self): if self.blocked: return if self.player.state() != QMediaPlayer.PlayingState or self.jumping: self.player.play() self.playButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) self.jumping = False else: self.player.pause() self.playButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) self.stopButton.setEnabled(True) def change_volume(self, value): self.player.setVolume(value) def stop(self): if self.player.state() != QMediaPlayer.StoppedState: self.player.stop() self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.stopButton.setEnabled(False) def next_song(self): self.playlist.next() self.playlistView.setCurrentIndex( self.playlistModel.index(self.playlist.currentIndex(), 0)) def previous_song(self): self.playlist.previous() self.playlistView.setCurrentIndex( self.playlistModel.index(self.playlist.currentIndex(), 0)) def mute_clicked(self): self.player.setMuted(not self.player.isMuted()) self.muteButton.setIcon(self.style().standardIcon( QStyle.SP_MediaVolume if not self.player.isMuted() else QStyle. SP_MediaVolumeMuted)) def durationChanged(self, duration): duration /= 1000 self.slider.setMaximum(duration) def positionChanged(self, progress): progress /= 1000 if not self.slider.isSliderDown(): self.slider.setValue(progress) def seek(self, seconds): self.player.setPosition(seconds * 1000)
class PlaylistView(QFrame): """ Horizontal Scroll Area containing playlists that can be selected """ # Signals should_display_playlist = pyqtSignal( int) # Display playlist given playlist_id def __init__(self, parent: QObject, image_cache: ImageCache): super().__init__(parent) self.__item_delegate = PlaylistDelegate(self) self.__model = PlaylistModel(self, image_cache) self.__horizontal_list = QListView(self) self.__horizontal_list.setModel(self.__model) self.__horizontal_list.setItemDelegate(self.__item_delegate) self.__horizontal_list.verticalScrollBar().setEnabled(False) self.__horizontal_list.setContextMenuPolicy(Qt.CustomContextMenu) self.__layout_manager = QVBoxLayout(self) self.__layout_ui() # Connect signals to slots self.__horizontal_list.doubleClicked.connect( self.__playlist_double_clicked) self.__horizontal_list.customContextMenuRequested.connect( self.__show_context_menu) def __layout_ui(self): # Set up horizontal list self.__horizontal_list.setFlow(QListView.LeftToRight) self.__horizontal_list.setMinimumHeight(235) self.__horizontal_list.setSpacing(20) self.__layout_manager.addWidget(self.__horizontal_list) def model(self) -> PlaylistModel: return self.__model @QtCore.pyqtSlot(QModelIndex) def __playlist_double_clicked(self, index: QModelIndex): """ Slot that connects to the doubleClicked signal Should either display contents of the playlist or allow for playlist creation (index dependent) :index: Index that was clicked """ should_create_playlist = (index.row() != self.__model.rowCount() - 1) if should_create_playlist: # Didn't click last index (create playlist), should display contents self.should_display_playlist.emit( self.__model.at(index.row()).playlist_id) else: self.__model.create_new_playlist() @QtCore.pyqtSlot(QModelIndex) def __handle_playlist_rename(self, index: QModelIndex): """ Allows the user to rename the playlist at index """ playlist = self.__model.at(index.row()) rename_dialog = RenamePlaylistDialog(self, playlist) if rename_dialog.exec() == QDialog.Accepted: # Rename successfully requested new_playlist_name = rename_dialog.get_new_name() rename_playlist(cursor, connection, playlist.playlist_id, new_playlist_name) self.relayout() @QtCore.pyqtSlot(QPoint) def __show_context_menu(self, pos: QPoint): """ Displays a context menu of user choices on a right-click :param pos: Location where user clicked on the screen """ index = self.__horizontal_list.indexAt(pos) if index.row() != -1 and index.row() != self.__model.rowCount( ) - 1: # Must be a valid index and not create global_pos = self.__horizontal_list.mapToGlobal(pos) context_menu = QMenu(self) show_action = QAction("Show Playlist") show_action.triggered.connect( lambda: self.__playlist_double_clicked(index)) rename_action = QAction("Rename Playlist") rename_action.triggered.connect( lambda: self.__handle_playlist_rename(index)) del_action = QAction("Delete Playlist") del_action.triggered.connect( lambda: self.__horizontal_list.model().delete_playlist(index)) context_menu.addAction(show_action) context_menu.addSeparator() context_menu.addAction(rename_action) context_menu.addAction(del_action) context_menu.exec(global_pos) del context_menu def relayout(self): """ Refreshes the UI to reflect the state of the DB """ playlists = get_all_user_playlists(cursor) self.__model.update_playlist(playlists)