window.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. """
  2. [Module documentaion here]
  3. @author:[author], [year]
  4. """
  5. from PyQt5 import QtWidgets
  6. from PyQt5.QtCore import QSize
  7. from path import Path
  8. from PyQt5.QtGui import QIcon
  9. from PyQt5.QtWidgets import QMainWindow, QListWidgetItem, QTableWidgetItem, QFileDialog, QMessageBox
  10. from core.logging_ import Logger
  11. from core.models import MusicFolder, TrackTag, Track
  12. from core.repositories import MusicFolderRepository, TrackRepository, SessionTrackRepository, SessionRepository, \
  13. TrackTagRepository, TagRepository
  14. from ui.qt.dlg_meta_editor import DlgMetaEditor
  15. from ui.qt.dlg_playlist import DlgPlaylist
  16. from ui.qt.dlg_select_playlist import DlgSelectPlaylist
  17. from ui.qt.main_ui import Ui_mainWindow
  18. from ui.qt.widgets.explorertable import ExplorerTable
  19. from ui.qt.widgets.playlist_table import PlaylistTable
  20. logger = Logger.get()
  21. class MainWindow(QMainWindow):
  22. def __init__(self, settings=None):
  23. super().__init__()
  24. self.settings = settings or {}
  25. self.selected_playlist = None
  26. self.playing_queue = []
  27. self.selected_track = None # /!\ the selected track is not necessarily the one being played!
  28. self.createWidgets()
  29. def createWidgets(self):
  30. self.ui = Ui_mainWindow()
  31. self.ui.setupUi(self)
  32. # Stack and menus
  33. self.ui.stack.setCurrentIndex(0)
  34. menu_items = {
  35. 0: (':/img/rsc/dance.png', 'Ma séance'),
  36. 1: (':/img/rsc/map.png', 'Explorer'),
  37. # 2: (':/img/rsc/calendar.png', 'Agenda'),
  38. 3: (':/img/rsc/settings.png', 'Paramètres'),
  39. }
  40. for i, item in menu_items.items():
  41. icon, lbl = item
  42. item = QListWidgetItem(QIcon(icon), lbl)
  43. item.index = i
  44. self.ui.menu.addItem(item)
  45. QtWidgets.qApp.focusChanged.connect(self.focus_changed)
  46. # Menu item clicked
  47. self.ui.menu.itemClicked.connect(self.menu_item_selected)
  48. # Page 1 - Homepage
  49. self.ui.sessionPlaylist.trackDoubleClicked.connect(self.play_playlist)
  50. self.ui.sessionBtnChange.clicked.connect(self.selectPlaylist)
  51. self.ui.btnSessionStart.clicked.connect(self.play_playlist)
  52. # Page 3 - explorer
  53. self.ui.explorerTable.populate()
  54. self.ui.explorerTable.trackSelected.connect(self.newTrackSelected)
  55. self.ui.explorerTable.trackDoubleClicked.connect(self.play_track)
  56. self.ui.btnExplorerRefresh.clicked.connect(self.refresh_explorer_tree)
  57. self.ui.explorerLineSearch.editingFinished.connect(self.explorerFiltersChanged)
  58. self.ui.explorerLineSearch.textChanged.connect(self.explorerFiltersChanged) # when search bar is cleared
  59. self.ui.explorerBtnSearch.clicked.connect(self.explorerFiltersChanged)
  60. self.populate_explorer_filter_tags()
  61. self.ui.explorerFilterTags.currentIndexChanged.connect(self.explorerFiltersChanged)
  62. self.ui.explorerTrackEdit.clicked.connect(self.showMetaEditor)
  63. self.ui.lineSearchTags.textChanged.connect(self.ui.explorerTrackTagsTable.filter)
  64. self.ui.explorerTrackTagsTable.tagChecked.connect(self.addTrackTags)
  65. self.ui.explorerTrackTagsTable.tagUnchecked.connect(self.removeTrackTags)
  66. self.ui.explorerTrackMetaStack.setCurrentIndex(0)
  67. self.ui.explorerTrackPlay.clicked.connect(self.explorerPlaySelected)
  68. self.ui.btnSelectPlaylist.clicked.connect(self.selectPlaylist)
  69. self.ui.explorerAddToPlaylist.clicked.connect(self.add_to_playlist)
  70. self.ui.explorerPlaylist.droppedEvent.connect(self.add_to_playlist)
  71. self.ui.explorerPlaylist.trackSelected.connect(self.newTrackSelected)
  72. self.ui.explorerPlaylist.trackDoubleClicked.connect(self.play_playlist)
  73. self.ui.btnPlaylistMoveToTop.clicked.connect(self.ui.explorerPlaylist.move_selected_to_top)
  74. self.ui.btnPlaylistMoveUp.clicked.connect(self.ui.explorerPlaylist.move_selected_up)
  75. self.ui.btnPlaylistMoveDown.clicked.connect(self.ui.explorerPlaylist.move_selected_down)
  76. self.ui.btnPlaylistMoveToBottom.clicked.connect(self.ui.explorerPlaylist.move_selected_to_bottom)
  77. self.ui.btnPlaylistRemove.clicked.connect(self.remove_from_playlist)
  78. # Page 5 - settings
  79. self.ui.settingsMusicFoldersTable.setColumnHidden(0, 1)
  80. self.ui.musicFoldersAddButton.clicked.connect(self.add_music_folder)
  81. self.ui.musicFoldersRemoveButton.clicked.connect(self.remove_music_folder)
  82. self.populate_music_folders_table()
  83. self.ui.tagsTableAddButton.clicked.connect(self.addTag)
  84. self.ui.tableTagsRemoveButton.clicked.connect(self.removeTag)
  85. self.ui.settingsTagsTableWidget.selectable = False
  86. self.ui.settingsTagsTableWidget.populate()
  87. self.ui.settingsTagsTableWidget.contentChanged.connect(self.ui.explorerTrackTagsTable.populate)
  88. # vlc frame
  89. self.ui.vlcFrame.trackStarted.connect(self.vlcTrackStarted)
  90. self.ui.vlcFrame.playlistEnded.connect(self.vlcPlaylistEnded)
  91. # Apply settings
  92. if self.settings:
  93. self.ui.vlcFrame.set_volume(int(self.settings['volume']))
  94. if self.settings['muted']:
  95. self.ui.vlcFrame.toggle_muted()
  96. playlist_id = self.settings['playlist']
  97. if playlist_id is not None and playlist_id > 0:
  98. playlist_repo = SessionRepository()
  99. playlist = playlist_repo.get_by_id(playlist_id)
  100. self.loadPlaylist(playlist)
  101. def focus_changed(self, old, new):
  102. if old is self.ui.explorerTrackNotepad:
  103. self.saveTrackNotes()
  104. def menu_item_selected(self, e):
  105. self.ui.stack.setCurrentIndex(e.index)
  106. def indexation_started(self):
  107. self.ui.statusbar.setStatusTip("Indexation en cours...")
  108. def indexation_ended(self):
  109. self.ui.statusbar.setStatusTip("Indexation terminée.")
  110. def refresh_explorer_tree(self, tracks=None):
  111. if tracks:
  112. self.ui.explorerTable.update_tracks(tracks)
  113. else:
  114. self.ui.explorerTable.populate()
  115. self.ui.explorerLineSearch.clear()
  116. def newTrackSelected(self, track=None):
  117. sender = self.sender()
  118. self.selected_track = None
  119. self.ui.explorerTrackMetaStack.setCurrentIndex(0)
  120. self.ui.explorerTrackPlay.setEnabled(False)
  121. self.ui.explorerAddToPlaylist.setEnabled(False)
  122. self.ui.btnPlaylistRemove.setEnabled(False)
  123. self.ui.explorerTrackNotepad.setText("")
  124. self.ui.frameMissingFileWarning.setVisible(False)
  125. if not track:
  126. return
  127. self.selected_track = track
  128. # track infos
  129. self.update_meta()
  130. self.ui.explorerTrackNotepad.setHtml(track.note)
  131. self.ui.explorerTrackTagsTable.populate(track)
  132. self.ui.explorerTrackMetaStack.setCurrentIndex(1)
  133. # Commands
  134. if self.selected_track.status == Track.STATUS_UNAVAILABLE:
  135. self.ui.frameMissingFileWarning.setVisible(True)
  136. else:
  137. self.ui.explorerTrackPlay.setEnabled(True)
  138. if type(sender) is ExplorerTable:
  139. self.ui.explorerAddToPlaylist.setEnabled(True)
  140. elif type(sender) is PlaylistTable:
  141. self.ui.btnPlaylistRemove.setEnabled(True)
  142. else:
  143. raise RuntimeError("Unknown sender")
  144. self.ui.explorerTable.viewport().repaint()
  145. self.ui.explorerPlaylist.viewport().repaint()
  146. self.ui.sessionPlaylist.viewport().repaint()
  147. def update_meta(self):
  148. if self.selected_track:
  149. title, artist, album, track_num = self.selected_track.title, \
  150. self.selected_track.artist, \
  151. self.selected_track.album, \
  152. self.selected_track.track_num
  153. else:
  154. title, artist, album, track_num = "", "", "", ""
  155. self.ui.explorerLblTrackTitle.setText(title)
  156. self.ui.explorerLblTrackArtist.setText(artist)
  157. self.ui.explorerLblTrackAlbum.setText(album)
  158. self.ui.explorerLblTrackNumber.setText(
  159. str(track_num if track_num is not None else "")
  160. )
  161. if self.selected_track:
  162. # refresh tables
  163. self.refresh_explorer_tree([self.selected_track])
  164. self.ui.explorerPlaylist.update()
  165. self.ui.sessionPlaylist.update()
  166. def populate_explorer_filter_tags(self):
  167. tag_repo = TagRepository()
  168. self.ui.explorerFilterTags.clear()
  169. self.ui.explorerFilterTags.addItem("Toutes les étiquettes", 0)
  170. self.ui.explorerFilterTags.setCurrentIndex(0)
  171. for tag in tag_repo.get_all():
  172. self.ui.explorerFilterTags.addItem(tag.label, tag.id)
  173. def populate_music_folders_table(self):
  174. music_folders = MusicFolderRepository().get_all()
  175. self.ui.settingsMusicFoldersTable.setRowCount(0)
  176. self.ui.settingsMusicFoldersTable.setRowCount(len(music_folders))
  177. music_folder_statuses = [
  178. ('Inconnu', ':/img/rsc/unknown.png'),
  179. ('Valide', ':/img/rsc/valid.png'),
  180. ('Inaccessible', ':/img/rsc/invalid.png')
  181. ]
  182. for i, music_folder in enumerate(music_folders):
  183. self.ui.settingsMusicFoldersTable.setItem(i, 0, QTableWidgetItem(music_folder.id))
  184. status_lbl, status_pic = music_folder_statuses[music_folder.status]
  185. self.ui.settingsMusicFoldersTable.setItem(i, 1, QTableWidgetItem(QIcon(status_pic), status_lbl))
  186. self.ui.settingsMusicFoldersTable.setItem(i, 2, QTableWidgetItem(music_folder.path))
  187. def addTag(self):
  188. self.ui.settingsTagsTableWidget.add()
  189. def removeTag(self):
  190. if QMessageBox.question(self, "Confirmer", "Êtes-vous sûr(e) de vouloir supprimer cette étiquette?") != QMessageBox.Yes:
  191. return
  192. self.ui.settingsTagsTableWidget.removeSelected()
  193. def explorerPlaySelected(self):
  194. track = self.ui.explorerTable.selected_track()
  195. if track is None:
  196. return
  197. self.play_track(track)
  198. def play_track(self, track):
  199. if not Path(track.path).exists():
  200. return
  201. logger.info("Start playing: %s" % track)
  202. self.ui.vlcFrame.load_track(track)
  203. self.ui.vlcFrame.play()
  204. def showMetaEditor(self):
  205. if not self.selected_track:
  206. return
  207. r = DlgMetaEditor.edit(self, self.selected_track)
  208. if r:
  209. self.update_meta()
  210. def add_music_folder(self):
  211. path = QFileDialog.getExistingDirectory(self, "Sélectionnez le dossier à ajouter")
  212. if not path:
  213. return
  214. path = Path(path)
  215. repo = MusicFolderRepository()
  216. music_folders = repo.get_all()
  217. # for folder in music_folders:
  218. # if path == Path(folder.path):
  219. # QMessageBox.warning(self, "Ajout invalide", "Ce dossier a déjà été ajouté")
  220. # return
  221. #
  222. # if is_subdir_of(path, Path(folder.path)):
  223. # QMessageBox.warning(self, "Ajout invalide", "Ce dossier est contenu dans un dossier existant")
  224. # return
  225. folder = MusicFolder(path=path)
  226. repo.create(folder, True)
  227. self.populate_music_folders_table()
  228. def remove_music_folder(self):
  229. pass
  230. def createOrEditPlaylist(self):
  231. r = DlgPlaylist.edit(self)
  232. def selectPlaylist(self):
  233. playlist = DlgSelectPlaylist.select(self)
  234. if playlist:
  235. self.loadPlaylist(playlist)
  236. def loadPlaylist(self, playlist):
  237. # home page
  238. self.ui.sessionPlaylist.setEnabled(True)
  239. self.ui.frameNotes.setEnabled(True)
  240. self.ui.sessionLblTitle.setText(playlist.name)
  241. self.ui.sessionPlaylist.populate(playlist)
  242. self.ui.frameNotes.set_playlist(playlist)
  243. # explorer page
  244. self.ui.explorerPlaylist.setEnabled(True)
  245. self.ui.explorerLblPlaylistTitle.setText(playlist.name)
  246. self.ui.explorerPlaylist.populate(playlist)
  247. self.selected_playlist = playlist
  248. def playlistItemDropped(self, event):
  249. if event.source() == ExplorerTable:
  250. self.add_to_playlist()
  251. def add_to_playlist(self):
  252. track = self.ui.explorerTable.selected_track()
  253. if track is None:
  254. return
  255. playlist = self.ui.explorerPlaylist.playlist
  256. if playlist is None:
  257. return
  258. track_repo = TrackRepository()
  259. track_repo.add_to_session(
  260. track.id,
  261. playlist.id
  262. )
  263. self.ui.explorerPlaylist.update()
  264. self.ui.sessionPlaylist.update()
  265. def remove_from_playlist(self):
  266. session_track = self.ui.explorerPlaylist.selected_session_track()
  267. if session_track is None:
  268. return
  269. session_track_repo = SessionTrackRepository()
  270. session_track_repo.delete(session_track, True)
  271. self.ui.explorerPlaylist.update()
  272. self.ui.sessionPlaylist.update()
  273. def play_playlist(self, track=None):
  274. if track.status == Track.STATUS_UNAVAILABLE:
  275. track = None
  276. playlist = self.selected_playlist
  277. track_repo = TrackRepository()
  278. tracks = track_repo.get_by_session_id(playlist.id)
  279. tracks = [t for t in tracks if t.status != Track.STATUS_UNAVAILABLE]
  280. if not tracks:
  281. return
  282. start_at = tracks.index(track) if track else 0
  283. logger.info("Start playing playlist: %s from index %s" % (playlist, start_at))
  284. self.ui.vlcFrame.load_playlist(playlist, tracks, start_at)
  285. self.ui.vlcFrame.play()
  286. def vlcTrackStarted(self, track):
  287. if self.selected_playlist:
  288. self.ui.explorerPlaylist.set_is_playing(track)
  289. self.ui.sessionPlaylist.set_is_playing(track)
  290. else:
  291. self.ui.explorerPlaylist.set_is_playing(None)
  292. self.ui.sessionPlaylist.set_is_playing(None)
  293. def vlcPlaylistEnded(self):
  294. self.ui.explorerPlaylist.set_is_playing(None)
  295. self.ui.sessionPlaylist.set_is_playing(None)
  296. def addTrackTags(self, tag_id):
  297. track_id = self.selected_track.id
  298. track_tag_repo = TrackTagRepository()
  299. tt = TrackTag(track_id=track_id, tag_id=tag_id)
  300. track_tag_repo.create(tt)
  301. track_tag_repo.commit()
  302. def removeTrackTags(self, tag_id):
  303. track_id = self.selected_track.id
  304. track_tag_repo = TrackTagRepository()
  305. track_tag_repo.query().filter(TrackTag.track_id == track_id).filter(TrackTag.tag_id == tag_id).delete()
  306. track_tag_repo.commit()
  307. def saveTrackNotes(self):
  308. if not self.selected_track:
  309. return
  310. note = self.ui.explorerTrackNotepad.toHtml()
  311. track_repo = TrackRepository()
  312. self.selected_track.note = note
  313. track_repo.commit()
  314. print('track notes saved')
  315. def explorerFiltersChanged(self):
  316. searchText = self.ui.explorerLineSearch.text()
  317. tag_id = int(self.ui.explorerFilterTags.currentData())
  318. self.ui.explorerTable.filterBy(tag_id, searchText)
  319. def filesIndexed(self, tracks):
  320. self.statusBar().showMessage(f"{len(tracks)} fichiers indexés", 3000)
  321. self.refresh_explorer_tree(tracks)
  322. def musicFolderStatusChanged(self, music_folder_id):
  323. self.populate_music_folders_table()
  324. def currentSettings(self):
  325. volume = self.ui.vlcFrame.volume
  326. playlist_id = self.selected_playlist.id if self.selected_playlist else None
  327. muted = self.ui.vlcFrame.is_muted
  328. return {
  329. "playlist": playlist_id,
  330. "volume": volume,
  331. "muted": muted,
  332. }
  333. def __del__(self):
  334. if self.qdb.isOpen():
  335. self.qdb.close()