#!/usr/bin/env python import webbrowser import psutil import sys import urllib.parse from datetime import datetime from log import DEBUG, EXCEPTION from typing import Callable, Dict, List, Optional, Tuple from PyQt5.QtCore import QEvent, QProcess, Qt, QTimer, QUrl from PyQt5.QtGui import QColor from PyQt5.QtWebEngineWidgets import QWebEngineView as QWebView from PyQt5.QtWidgets import ( QApplication, QDialog, QFileDialog, QInputDialog, QLabel, QListWidgetItem, QMainWindow, ) import dbconfig from dbconfig import Session import helpers import music from config import Config from models import (Base, Playdates, Playlists, PlaylistTracks, Settings, Tracks) from playlists import PlaylistTab from sqlalchemy.orm.exc import DetachedInstanceError from ui.dlg_search_database_ui import Ui_Dialog from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist from ui.main_window_ui import Ui_MainWindow class TrackData: def __init__(self, track): self.id = track.id self.title = track.title self.artist = track.artist self.duration = track.duration self.start_gap = track.start_gap self.fade_at = track.fade_at self.silence_at = track.silence_at self.path = track.path self.mtime = track.mtime self.lastplayed = track.lastplayed class Window(QMainWindow, Ui_MainWindow): def __init__(self, parent=None) -> None: super().__init__(parent) self.setupUi(self) self.timer: QTimer = QTimer() self.even_tick: bool = True self.playing: bool = False self.connect_signals_slots() self.disable_play_next_controls() self.music: music.Music = music.Music() self.current_track: Optional[Tracks] = None self.current_track_playlist_tab: Optional[PlaylistTab] = None self.info_tabs: Optional[Dict[str, QWebView]] = {} self.next_track: Optional[Tracks] = None self.next_track_playlist_tab: Optional[PlaylistTab] = None self.previous_track: Optional[Tracks] = None self.previous_track_position: Optional[int] = None self.spnVolume.setValue(Config.VOLUME_VLC_DEFAULT) self.set_main_window_size() self.lblSumPlaytime: QLabel = QLabel("") self.statusbar.addPermanentWidget(self.lblSumPlaytime) self.visible_playlist_tab: Callable[[], PlaylistTab] = \ self.tabPlaylist.currentWidget self.load_last_playlists() self.enable_play_next_controls() self.check_audacity() self.timer.start(Config.TIMER_MS) # def add_file(self) -> None: # # TODO: V2 enahancement to import tracks # dlg = QFileDialog() # dlg.setFileMode(QFileDialog.ExistingFiles) # dlg.setViewMode(QFileDialog.Detail) # dlg.setDirectory(Config.ROOT) # dlg.setNameFilter("Music files (*.flac *.mp3)") # if dlg.exec_(): # with Session() as session: # for fname in dlg.selectedFiles(): # track = create_track_from_file(session, fname) # # Add to playlist on screen # # If we don't specify "repaint=False", playlist will # # also be saved to database # self.visible_playlist_tab().insert_track(session, track) def set_main_window_size(self) -> None: """Set size of window from database""" with Session() as session: record = Settings.get_int_settings(session, "mainwindow_x") x = record.f_int or 1 record = Settings.get_int_settings(session, "mainwindow_y") y = record.f_int or 1 record = Settings.get_int_settings(session, "mainwindow_width") width = record.f_int or 1599 record = Settings.get_int_settings(session, "mainwindow_height") height = record.f_int or 981 self.setGeometry(x, y, width, height) return @staticmethod def kae(): with Session() as session: db = session.bind.engine.url.database print(f"kae(): {db=}") @staticmethod def check_audacity() -> None: """Offer to run Audacity if not running""" if not Config.CHECK_AUDACITY_AT_STARTUP: return if "audacity" in [i.name() for i in psutil.process_iter()]: return if helpers.ask_yes_no("Audacity not running", "Start Audacity?"): QProcess.startDetached(Config.AUDACITY_COMMAND, []) def clear_selection(self): """ Clear selected row""" if self.visible_playlist_tab(): self.visible_playlist_tab().clearSelection() def closeEvent(self, event: QEvent) -> None: """Don't allow window to close when a track is playing""" if self.music.playing(): DEBUG("closeEvent() ignored as music is playing") event.ignore() helpers.show_warning( "Track playing", "Can't close application while track is playing") else: DEBUG("closeEvent() accepted") with Session() as session: record = Settings.get_int_settings( session, "mainwindow_height") if record.f_int != self.height(): record.update(session, {'f_int': self.height()}) record = Settings.get_int_settings(session, "mainwindow_width") if record.f_int != self.width(): record.update(session, {'f_int': self.width()}) record = Settings.get_int_settings(session, "mainwindow_x") if record.f_int != self.x(): record.update(session, {'f_int': self.x()}) record = Settings.get_int_settings(session, "mainwindow_y") if record.f_int != self.y(): record.update(session, {'f_int': self.y()}) # Find a playlist tab (as opposed to an info tab) and # save column widths if self.current_track_playlist_tab: self.current_track_playlist_tab.close() elif self.next_track_playlist_tab: self.next_track_playlist_tab.close() event.accept() def connect_signals_slots(self) -> None: self.actionAdd_note.triggered.connect(self.create_note) self.action_Clear_selection.triggered.connect(self.clear_selection) self.actionClosePlaylist.triggered.connect(self.close_playlist_tab) self.actionEnable_controls.triggered.connect( self.enable_play_next_controls) self.actionExport_playlist.triggered.connect(self.export_playlist_tab) self.actionFade.triggered.connect(self.fade) self.actionMoveSelected.triggered.connect(self.move_selected) self.actionNewPlaylist.triggered.connect(self.create_playlist) self.actionOpenPlaylist.triggered.connect(self.open_playlist) self.actionPlay_next.triggered.connect(self.play_next) self.actionSearch_database.triggered.connect(self.search_database) self.actionSelect_next_track.triggered.connect(self.select_next_row) self.actionSelect_played_tracks.triggered.connect(self.select_played) self.actionSelect_previous_track.triggered.connect( self.select_previous_row) self.actionSelect_unplayed_tracks.triggered.connect( self.select_unplayed) self.actionSetNext.triggered.connect( lambda: self.tabPlaylist.currentWidget().set_selected_as_next()) self.actionSkip_next.triggered.connect(self.play_next) self.actionStop.triggered.connect(self.stop) self.btnAddNote.clicked.connect(self.create_note) self.btnDatabase.clicked.connect(self.search_database) self.btnFade.clicked.connect(self.fade) self.btnPlay.clicked.connect(self.play_next) self.btnSetNext.clicked.connect( lambda: self.tabPlaylist.currentWidget().set_selected_as_next()) self.btnSongInfo.clicked.connect(self.song_info_search) self.btnStop.clicked.connect(self.stop) self.spnVolume.valueChanged.connect(self.change_volume) self.tabPlaylist.tabCloseRequested.connect(self.close_tab) self.timer.timeout.connect(self.tick) def create_playlist(self) -> None: """Create new playlist""" dlg = QInputDialog(self) dlg.setInputMode(QInputDialog.TextInput) dlg.setLabelText("Playlist name:") dlg.resize(500, 100) ok = dlg.exec() if ok: with Session() as session: playlist = Playlists(session, dlg.textValue()) self.create_playlist_tab(session, playlist) def change_volume(self, volume: int) -> None: """Change player maximum volume""" DEBUG(f"change_volume({volume})") self.music.set_volume(volume) def close_playlist_tab(self) -> None: """Close active playlist tab""" self.close_tab(self.tabPlaylist.currentIndex()) def close_tab(self, index: int) -> None: """ Close tab unless it holds the curren or next track """ if hasattr(self.tabPlaylist.widget(index), 'playlist_id'): if self.tabPlaylist.widget(index) == ( self.current_track_playlist_tab): self.statusbar.showMessage( "Can't close current track playlist", 5000) return if self.tabPlaylist.widget(index) == self.next_track_playlist_tab: self.statusbar.showMessage( "Can't close next track playlist", 5000) return # It's OK to close this playlist so remove from open playlist list self.tabPlaylist.widget(index).close() # Close regardless of tab type self.tabPlaylist.removeTab(index) def create_note(self) -> None: """Call playlist to create note""" try: self.visible_playlist_tab().create_note() except AttributeError: # Just return if there's no visible playlist tab return def disable_play_next_controls(self) -> None: """ Disable "play next" keyboard controls """ DEBUG("disable_play_next_controls()") self.actionPlay_next.setEnabled(False) self.statusbar.showMessage("Play controls: Disabled", 0) def enable_play_next_controls(self) -> None: """ Enable "play next" keyboard controls """ DEBUG("enable_play_next_controls()") self.actionPlay_next.setEnabled(True) self.statusbar.showMessage("Play controls: Enabled", 0) def end_of_track_actions(self) -> None: """ Clean up after track played Actions required: - Set flag to say we're not playing a track - Reset current track - Tell playlist_tab track has finished - Reset current playlist_tab - Reset clocks - Update headers - Enable controls """ # Set flag to say we're not playing a track so that tick() # doesn't see player=None and kick off end-of-track actions self.playing = False # Reset current track if self.current_track: self.previous_track = self.current_track self.current_track = None # Tell playlist_tab track has finished and # reset current playlist_tab if self.current_track_playlist_tab: self.current_track_playlist_tab.play_stopped() self.current_track_playlist_tab = None # Reset clocks self.frame_fade.setStyleSheet("") self.frame_silent.setStyleSheet("") self.label_elapsed_timer.setText("00:00") self.label_end_timer.setText("00:00") self.label_fade_length.setText("0:00") self.label_fade_timer.setText("00:00") self.label_silence_length.setText("0:00") self.label_silent_timer.setText("00:00") self.label_track_length.setText("0:00") # Update headers self.update_headers() # Enable controls self.enable_play_next_controls() def export_playlist_tab(self) -> None: """Export the current playlist to an m3u file""" if not self.visible_playlist_tab(): return with Session() as session: playlist = Playlists.get_by_id( session, self.visible_playlist_tab().playlist_id) # Get output filename pathspec: Tuple[str, str] = QFileDialog.getSaveFileName( self, 'Save Playlist', directory=f"{playlist.name}.m3u", filter="M3U files (*.m3u);;All files (*.*)" ) if not pathspec: return path: str = pathspec[0] if not path.endswith(".m3u"): path += ".m3u" with open(path, "w") as f: # Required directive on first line f.write("#EXTM3U\n") for _, track in playlist.tracks.items(): f.write( "#EXTINF:" f"{int(track.duration / 1000)}," f"{track.title} - " f"{track.artist}" "\n" f"{track.path}" "\n" ) def fade(self) -> None: """Fade currently playing track""" DEBUG("musicmuster:fade()", True) self.stop_playing(fade=True) def load_last_playlists(self): """Load the playlists that we loaded at end of last session""" with Session() as session: for playlist in Playlists.get_open(session): self.create_playlist_tab(session, playlist) playlist.mark_open(session) def create_playlist_tab(self, session: Session, playlist: Playlists) -> None: """ Take the passed playlist database object, create a playlist tab and add tab to display. """ playlist_tab: PlaylistTab = PlaylistTab( musicmuster=self, session=session, playlist_id=playlist.id) idx: int = self.tabPlaylist.addTab(playlist_tab, playlist.name) self.tabPlaylist.setCurrentIndex(idx) def move_selected(self) -> None: """Move selected rows to another playlist""" with Session() as session: visible_tab = self.visible_playlist_tab() visible_tab_id = visible_tab.playlist_id source_playlist = None playlists = [] for playlist in Playlists.get_all(session): if playlist.id == visible_tab_id: source_playlist = playlist else: playlists.append(playlist) # Get destination playlist id dlg = SelectPlaylistDialog(self, playlists=playlists, session=session) dlg.exec() if not dlg.playlist: return destination_playlist = dlg.playlist # Update database for both source and destination playlists rows = visible_tab.get_selected_rows() source_playlist.move_track(session, rows, destination_playlist) # Update destination playlist_tab if visible (if not visible, it # will be re-populated when it is opened) destination_visible_playlist_tab = None for tab in range(self.tabPlaylist.count()): # Non-playlist tabs won't have a 'playlist_id' attribute if not hasattr(self.tabPlaylist.widget(tab), 'playlist_id'): continue if self.tabPlaylist.widget(tab).playlist_id == dlg.playlist.id: destination_visible_playlist_tab = ( self.tabPlaylist.widget(tab)) break if destination_visible_playlist_tab: # We need to commit to database for populate to work session.commit() destination_visible_playlist_tab.populate( session, dlg.playlist.id) # Update source playlist self.visible_playlist_tab().remove_rows(rows) def open_info_tabs(self) -> None: """ Ensure we have info tabs for next and current track titles """ title_list: List[str] = [] if self.previous_track: title_list.append(self.previous_track.title) if self.current_track: title_list.append(self.current_track.title) if self.next_track: title_list.append(self.next_track.title) for title in title_list: if title in self.info_tabs.keys(): # We already have a tab for this track continue if len(self.info_tabs) >= Config.MAX_INFO_TABS: # Find an unneeded info tab try: old_title = list( set(self.info_tabs.keys()) - set(title_list) )[0] except IndexError: DEBUG( f"ensure_info_tabs({title_list}): unable to add " f"{title=}" ) return # Assign redundant widget a new title widget = self.info_tabs[title] = self.info_tabs[old_title] idx = self.tabPlaylist.indexOf(widget) self.tabPlaylist.setTabText( idx, title[:Config.INFO_TAB_TITLE_LENGTH]) del self.info_tabs[old_title] else: # Create a new tab for this title widget = self.info_tabs[title] = QWebView() widget.setZoomFactor(Config.WEB_ZOOM_FACTOR) self.tabPlaylist.addTab( widget, title[:Config.INFO_TAB_TITLE_LENGTH]) txt = urllib.parse.quote_plus(title) url = Config.INFO_TAB_URL % txt widget.setUrl(QUrl(url)) def play_next(self) -> None: """ Play next track. Actions required: - If there is no next track set, return. - If there's currently a track playing, fade it. - Move next track to current track. - Update record of current track playlist_tab - If current track on different playlist_tab to last, reset last track playlist_tab colour - Set current track playlist_tab colour - Play (new) current track. - Tell database to record it as played - Tell playlist track is now playing - Disable play next controls - Update headers - Update clocks """ DEBUG( "musicmuster.play_next(), " f"next_track={self.next_track.title if self.next_track else None} " "current_track=" f"{self.current_track.title if self.current_track else None}", True ) # If there is no next track set, return. if not self.next_track: DEBUG("musicmuster.play_next(): no next track selected", True) return with Session() as session: # If there's currently a track playing, fade it. self.stop_playing(fade=True) # Move next track to current track. self.current_track = self.next_track self.next_track = None # If current track on different playlist_tab to last, reset # last track playlist_tab colour # Set current track playlist_tab colour if self.current_track_playlist_tab != self.next_track_playlist_tab: self.set_tab_colour(self.current_track_playlist_tab, QColor(Config.COLOUR_NORMAL_TAB)) # Update record of current track playlist_tab self.current_track_playlist_tab = self.next_track_playlist_tab self.next_track_playlist_tab = None # Set current track playlist_tab colour self.set_tab_colour(self.current_track_playlist_tab, QColor(Config.COLOUR_CURRENT_TAB)) # Play (new) current track self.music.play(self.current_track.path) # Tell database to record it as played Playdates(session, self.current_track.id) # Set last_played date Tracks.update_lastplayed(session, self.current_track.id) # Tell playlist track is now playing self.current_track_playlist_tab.play_started(session) # Disable play next controls self.disable_play_next_controls() # Update headers self.update_headers() # Update clocks self.label_track_length.setText( helpers.ms_to_mmss(self.current_track.duration) ) fade_at = self.current_track.fade_at silence_at = self.current_track.silence_at length = self.current_track.duration self.label_fade_length.setText( helpers.ms_to_mmss(silence_at - fade_at)) self.label_silence_length.setText( helpers.ms_to_mmss(length - silence_at)) def search_database(self) -> None: """Show dialog box to select and cue track from database""" with Session() as session: dlg = DbDialog(self, session) dlg.exec() def open_playlist(self) -> None: """Select and activate existing playlist""" with Session() as session: playlists = Playlists.get_closed(session) dlg = SelectPlaylistDialog(self, playlists=playlists, session=session) dlg.exec() if dlg.plid: p = Playlists.get_by_id(session=session, playlist_id=dlg.plid) p.mark_open(session) self.create_playlist_tab(session, p) def select_next_row(self) -> None: """Select next or first row in playlist""" self.visible_playlist_tab().select_next_row() def select_played(self) -> None: """Select all played tracks in playlist""" self.visible_playlist_tab().select_played_tracks() def select_previous_row(self) -> None: """Select previous or first row in playlist""" self.visible_playlist_tab().select_previous_row() def select_unplayed(self) -> None: """Select all unplayed tracks in playlist""" self.visible_playlist_tab().select_unplayed_tracks() def set_tab_colour(self, widget, colour) -> None: """ Find the tab containing the widget and set the text colour """ idx: int = self.tabPlaylist.indexOf(widget) self.tabPlaylist.tabBar().setTabTextColor(idx, colour) def song_info_search(self) -> None: """ Open browser tab for Wikipedia, searching for the first that exists of: - selected track - next track - current track """ title: Optional[str] = self.visible_playlist_tab().get_selected_title() if not title: if self.next_track: title = self.next_track.title if not title: if self.current_track: title = self.current_track.title if title: txt = urllib.parse.quote_plus(title) url = Config.INFO_TAB_URL % txt webbrowser.open(url, new=2) def stop(self) -> None: """Stop playing immediately""" DEBUG("musicmuster.stop()") self.stop_playing(fade=False) def stop_playing(self, fade=True) -> None: """ Stop playing current track Actions required: - Return if not playing - Stop/fade track - Reset playlist_tab colour - Run end-of-track actions """ DEBUG(f"musicmuster.stop_playing({fade=})", True) # Return if not playing if not self.music.playing(): DEBUG("musicmuster.stop_playing(): not playing", True) return # Stop/fade track self.previous_track_position = self.music.get_position() if fade: DEBUG("musicmuster.stop_playing(): fading music", True) self.music.fade() else: DEBUG("musicmuster.stop_playing(): stopping music", True) self.music.stop() # Reset playlist_tab colour if self.current_track_playlist_tab == self.next_track_playlist_tab: self.set_tab_colour(self.current_track_playlist_tab, QColor(Config.COLOUR_NEXT_TAB)) else: self.set_tab_colour(self.current_track_playlist_tab, QColor(Config.COLOUR_NORMAL_TAB)) # Run end-of-track actions self.end_of_track_actions() def this_is_the_next_track(self, playlist_tab: PlaylistTab, track: Tracks, session) -> None: """ This is notification from a playlist tab that it holds the next track to be played. Actions required: - Clear next track if on other tab - Reset tab colour if on other tab - Note next playlist tab - Set next playlist_tab tab colour - Note next track - Update headers - Populate ‘info’ tabs """ # Clear next track if on another tab if self.next_track_playlist_tab != playlist_tab: # We need to reset the ex-next-track playlist if self.next_track_playlist_tab: self.next_track_playlist_tab.clear_next(session) # Reset tab colour if on other tab if (self.next_track_playlist_tab != self.current_track_playlist_tab): self.set_tab_colour( self.next_track_playlist_tab, QColor(Config.COLOUR_NORMAL_TAB)) # Note next playlist tab self.next_track_playlist_tab = playlist_tab # Set next playlist_tab tab colour if it isn't the # currently-playing tab if (self.next_track_playlist_tab != self.current_track_playlist_tab): self.set_tab_colour( self.next_track_playlist_tab, QColor(Config.COLOUR_NEXT_TAB)) # Note next track self.next_track = TrackData(track) # Update headers self.update_headers() # Populate 'info' tabs self.open_info_tabs() def tick(self) -> None: """ Carry out clock tick actions. The Time of Day clock is updated every tick (500ms). All other timers are updated every second. As the timers have a one-second resolution, updating every 500ms can result in some timers updating and then, 500ms later, other timers updating. That looks odd. Actions required: - Update TOD clock - If track is playing, update track clocks time and colours - Else: run stop_track """ # Update TOD clock self.lblTOD.setText(datetime.now().strftime(Config.TOD_TIME_FORMAT)) self.even_tick = not self.even_tick if not self.even_tick: return # If track is playing, update track clocks time and colours if self.music.player and self.music.playing(): self.playing = True playtime: int = self.music.get_playtime() time_to_fade: int = (self.current_track.fade_at - playtime) time_to_silence: int = ( self.current_track.silence_at - playtime) time_to_end: int = (self.current_track.duration - playtime) # Elapsed time if time_to_end < 500: self.label_elapsed_timer.setText( helpers.ms_to_mmss(playtime) ) else: self.label_elapsed_timer.setText( helpers.ms_to_mmss(playtime) ) # Time to fade self.label_fade_timer.setText(helpers.ms_to_mmss(time_to_fade)) # If silent in the next 5 seconds, put warning colour on # time to silence box and enable play controls if time_to_silence <= 5500: self.frame_silent.setStyleSheet( f"background: {Config.COLOUR_ENDING_TIMER}" ) self.enable_play_next_controls() # Set warning colour on time to silence box when fade starts elif time_to_fade <= 500: self.frame_silent.setStyleSheet( f"background: {Config.COLOUR_WARNING_TIMER}" ) # Five seconds before fade starts, set warning colour on # time to silence box and enable play controls elif time_to_fade <= 5500: self.frame_fade.setStyleSheet( f"background: {Config.COLOUR_WARNING_TIMER}" ) self.enable_play_next_controls() else: self.frame_silent.setStyleSheet("") self.frame_fade.setStyleSheet("") self.label_silent_timer.setText( helpers.ms_to_mmss(time_to_silence) ) # Time to end self.label_end_timer.setText(helpers.ms_to_mmss(time_to_end)) else: if self.playing: self.stop_playing() def update_headers(self) -> None: """ Update last / current / next track headers If multiple tracks are played quickly in succession, it's possible for the self.{previous,current,next} track to not be in the session. Unlikely to happen in normal use so handle by blanking title. """ try: self.hdrPreviousTrack.setText( f"{self.previous_track.title} - {self.previous_track.artist}" ) except (AttributeError, DetachedInstanceError): self.hdrPreviousTrack.setText("") try: self.hdrCurrentTrack.setText( f"{self.current_track.title} - {self.current_track.artist}" ) except (AttributeError, DetachedInstanceError): self.hdrCurrentTrack.setText("") try: self.hdrNextTrack.setText( f"{self.next_track.title} - {self.next_track.artist}" ) except (AttributeError, DetachedInstanceError): self.hdrNextTrack.setText("") class DbDialog(QDialog): """Select track from database""" def __init__(self, parent, session): # review super().__init__(parent) self.session = session self.ui = Ui_Dialog() self.ui.setupUi(self) self.ui.btnAdd.clicked.connect(self.add_selected) self.ui.btnAddClose.clicked.connect(self.add_selected_and_close) self.ui.btnClose.clicked.connect(self.close) self.ui.matchList.itemDoubleClicked.connect(self.double_click) self.ui.matchList.itemSelectionChanged.connect(self.selection_changed) self.ui.radioTitle.toggled.connect(self.title_artist_toggle) self.ui.searchString.textEdited.connect(self.chars_typed) record = Settings.get_int_settings(self.session, "dbdialog_width") width = record.f_int or 800 record = Settings.get_int_settings(self.session, "dbdialog_height") height = record.f_int or 600 self.resize(width, height) def __del__(self): # review record = Settings.get_int_settings(self.session, "dbdialog_height") if record.f_int != self.height(): record.update(self.session, {'f_int': self.height()}) record = Settings.get_int_settings(self.session, "dbdialog_width") if record.f_int != self.width(): record.update(self.session, {'f_int': self.width()}) def add_selected(self): # review if not self.ui.matchList.selectedItems(): return item = self.ui.matchList.currentItem() track = item.data(Qt.UserRole) self.add_track(track) def add_selected_and_close(self): # review self.add_selected() self.close() def title_artist_toggle(self): # review """ Handle switching between searching for artists and searching for titles """ # Logic is handled already in chars_typed(), so just call that. self.chars_typed(self.ui.searchString.text()) def chars_typed(self, s): # review if len(s) > 0: if self.ui.radioTitle.isChecked(): matches = Tracks.search_titles(self.session, s) else: matches = Tracks.search_artists(self.session, s) self.ui.matchList.clear() if matches: for track in matches: t = QListWidgetItem() t.setText( f"{track.title} - {track.artist} " f"[{helpers.ms_to_mmss(track.duration)}]" ) t.setData(Qt.UserRole, track) self.ui.matchList.addItem(t) def double_click(self, entry): # review track = entry.data(Qt.UserRole) self.add_track(track) # Select search text to make it easier for next search self.select_searchtext() def add_track(self, track): # review # Add to playlist on screen self.parent().visible_playlist_tab().insert_track( self.session, track) # Select search text to make it easier for next search self.select_searchtext() def select_searchtext(self): # review self.ui.searchString.selectAll() self.ui.searchString.setFocus() def selection_changed(self): # review if not self.ui.matchList.selectedItems(): return item = self.ui.matchList.currentItem() track = item.data(Qt.UserRole) self.ui.dbPath.setText(track.path) class SelectPlaylistDialog(QDialog): def __init__(self, parent=None, playlists=None, session=None): super().__init__(parent) if playlists is None: return self.ui = Ui_dlgSelectPlaylist() self.ui.setupUi(self) self.ui.lstPlaylists.itemDoubleClicked.connect(self.list_doubleclick) self.ui.buttonBox.accepted.connect(self.open) self.ui.buttonBox.rejected.connect(self.close) self.session = session self.plid = None record = Settings.get_int_settings( self.session, "select_playlist_dialog_width") width = record.f_int or 800 record = Settings.get_int_settings( self.session, "select_playlist_dialog_height") height = record.f_int or 600 self.resize(width, height) # for (plid, plname) in [(a.id, a.name) for a in playlists]: # p = QListWidgetItem() # p.setText(plname) # p.setData(Qt.UserRole, plid) # self.ui.lstPlaylists.addItem(p) for playlist in playlists: p = QListWidgetItem() p.setText(playlist.name) p.setData(Qt.UserRole, playlist) self.ui.lstPlaylists.addItem(p) def __del__(self): # review record = Settings.get_int_settings( self.session, "select_playlist_dialog_height") if record.f_int != self.height(): record.update(self.session, {'f_int': self.height()}) record = Settings.get_int_settings( self.session, "select_playlist_dialog_width") if record.f_int != self.width(): record.update(self.session, {'f_int': self.width()}) def list_doubleclick(self, entry): # review self.playlist = entry.data(Qt.UserRole) self.accept() def open(self): # review if self.ui.lstPlaylists.selectedItems(): item = self.ui.lstPlaylists.currentItem() self.playlist = item.data(Qt.UserRole) self.accept() if __name__ == "__main__": try: Base.metadata.create_all(dbconfig.engine) app = QApplication(sys.argv) win = Window() win.show() sys.exit(app.exec()) except Exception: EXCEPTION("Unhandled Exception caught by musicmuster.main()")