# # Copyright (C) 2009 Anton Khirnov # # Nephilim is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Nephilim is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Nephilim. If not, see . # from PyQt5 import QtGui, QtWidgets, QtCore from ..plugin import Plugin from ..common import MIMETYPES, SongsMimeData class Playlist(Plugin): # public, const info = 'Shows the playlist.' # public, read-only o = None # private DEFAULTS = {'columns': ['track', 'title', 'artist', 'date', 'album', 'length'], 'header_state' : QtCore.QByteArray()} def _load(self): self.o = PlaylistWidget(self) def _unload(self): self.o = None def _get_dock_widget(self): return self._create_dock(self.o) class PlaylistWidget(QtWidgets.QWidget): plugin = None playlist = None toolbar = None def __init__(self, plugin): QtWidgets.QWidget.__init__(self) self.plugin = plugin self.playlist = PlaylistTree(self.plugin) self.toolbar = QtWidgets.QToolBar(self.plugin.name + ' toolbar', self) self.toolbar.addAction(QtGui.QIcon(':icons/shuffle.png'), 'Shuffle current playlist.', self.plugin.mpclient.shuffle) self.toolbar.addAction(QtGui.QIcon(':icons/delete.png'), 'Clear current playlist.', self.plugin.mpclient.clear) add_url = PlaylistAddURL(self.plugin.mpclient, self) add_url.setToolTip('Add an URL to current playlist.') self.toolbar.addWidget(add_url) self.setLayout(QtWidgets.QVBoxLayout()) self.layout().setSpacing(0) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().addWidget(self.toolbar) self.layout().addWidget(self.playlist) self.plugin.mpclient.playlist(self.playlist.fill) class PlaylistAddURL(QtWidgets.QLineEdit): ### PRIVATE #### _mpclient = None def __init__(self, mpclient, parent = None): QtWidgets.QLineEdit.__init__(self, parent) self._mpclient = mpclient self.returnPressed.connect(self._return_pressed) def _return_pressed(self): self._mpclient.add([self.text()]) self.clear() class PlaylistTree(QtWidgets.QTreeWidget): plugin = None ### PRIVATE ### # popup menu _menu = None # add same... menu _same_menu = None # currently playing song item (highlighted _cur_song = None def __init__(self, plugin): QtWidgets.QTreeWidget.__init__(self) self.plugin = plugin self.setSelectionMode(QtWidgets.QTreeWidget.ExtendedSelection) self.setAlternatingRowColors(True) self.setRootIsDecorated(False) # drag&drop self.viewport().setAcceptDrops(True) self.setDropIndicatorShown(True) self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop) columns = self.plugin.settings.value(self.plugin.name + '/columns') self.setColumnCount(len(columns)) self.setHeaderLabels(columns) self.header().restoreState(self.plugin.settings.value(self.plugin.name + '/header_state')) # menu self._menu = QtWidgets.QMenu() self._same_menu = self._menu.addMenu('Add same...') self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect(self._show_context_menu) self.itemActivated.connect(self._song_activated) self.header().geometriesChanged.connect(self._save_state) self.plugin.mpclient.playlist_changed.connect(lambda :self.plugin.mpclient.playlist(self.fill)) self.plugin.mpclient.connect_changed.connect(self._update_menu) self.plugin.mpclient.song_changed.connect(self._update_cur_song) self.plugin.mpclient.songpos_changed.connect(self._update_cur_song) def _save_state(self): self.plugin.settings.setValue(self.plugin.name + '/header_state', self.header().saveState()) def _song_activated(self, item): self.plugin.mpclient.play(item.song['id']) def _update_cur_song(self, song): if self._cur_song: self._cur_song.set_current(False) if not song: self._cur_song = None return self._cur_song = self.topLevelItem(int(song['pos'])) if self._cur_song: self._cur_song.set_current(True) def fill(self, songs): columns = self.plugin.settings.value(self.plugin.name + '/columns') self._cur_song = None self.clear() for song in songs: item = PlaylistSongItem(song) for i in range(len(columns)): item.setText(i, song['?' + columns[i]]) self.addTopLevelItem(item) self._update_cur_song(self.plugin.mpclient.cur_song) def keyPressEvent(self, event): if event.matches(QtGui.QKeySequence.Delete): ids = [] for item in self.selectedItems(): ids.append(item.song['id']) self.plugin.mpclient.delete(ids) else: QtWidgets.QTreeWidget.keyPressEvent(self, event) def mimeData(self, items): data = SongsMimeData() data.set_plistsongs([items[0].song['id']]) return data def dropMimeData(self, parent, index, data, action): if data.hasFormat(MIMETYPES['plistsongs']): if parent: index = self.indexOfTopLevelItem(parent) elif index >= self.topLevelItemCount(): index = self.topLevelItemCount() - 1 self.plugin.mpclient.move(data.plistsongs()[0], index) return True elif data.hasFormat(MIMETYPES['songs']): if parent: index = self.indexOfTopLevelItem(parent) self.plugin.mpclient.add(data.songs(), index) return True return False def supportedDropActions(self): return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction def mimeTypes(self): return [MIMETYPES['songs'], MIMETYPES['plistsongs']] def _update_menu(self): """Update popup menu. Invoked on (dis)connect.""" self._same_menu.clear() for tag in self.plugin.mpclient.tagtypes: self._same_menu.addAction(tag, lambda tag = tag: self._add_selected_same(tag)) def _add_selected_same(self, tag): """Adds all tracks in DB with tag 'tag' same as selected tracks.""" for it in self.selectedItems(): self.plugin.mpclient.findadd(tag, it.song['?' + tag]) def _show_context_menu(self, pos): if not self.indexAt(pos).isValid(): return self._menu.popup(self.mapToGlobal(pos)) class PlaylistSongItem(QtWidgets.QTreeWidgetItem): ### PUBLIC ### song = None def __init__(self, song): QtWidgets.QTreeWidgetItem.__init__(self) self.song = song def set_current(self, val): font = QtGui.QFont() if val: font.setBold(True) font.setItalic(True) for i in range(self.columnCount()): self.setFont(i, font)