song: raise KeyError instead of returning empty string
[nephilim.git] / nephilim / plugins / Library.py
blob0d7e8e17944c7a1fb9e93958ecf43c9f2b76d1d1
2 # Copyright (C) 2009 Anton Khirnov <wyskas@gmail.com>
4 # Nephilim is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # Nephilim is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Nephilim. If not, see <http://www.gnu.org/licenses/>.
18 from PyQt4 import QtGui, QtCore
19 from PyQt4.QtCore import QVariant
21 from ..plugin import Plugin
22 from ..common import MIMETYPES, SongsMimeData
24 class Library(Plugin):
25 # public, const
26 info = 'Display MPD database as a tree.'
28 # public, read-only
29 o = None
31 # private
32 DEFAULTS = {'grouping' : ['albumartist', 'album']}
34 def _load(self):
35 self.o = LibraryWidget(self)
36 def _unload(self):
37 self.o = None
39 def _get_dock_widget(self):
40 return self._create_dock(self.o)
42 def fill_library(self):
43 if not self.o:
44 return
45 self.o.fill_library()
47 class SettingsWidgetLibrary(Plugin.SettingsWidget):
48 taglist = None
49 def __init__(self, plugin):
50 Plugin.SettingsWidget.__init__(self, plugin)
51 self.settings.beginGroup(self.plugin.name)
53 tags_enabled = self.settings.value('grouping').toStringList()
54 tags = self.plugin.mpclient.tagtypes
55 self.taglist = QtGui.QListWidget(self)
56 self.taglist.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
57 for tag in [tag for tag in tags_enabled if tag in tags]:
58 it = QtGui.QListWidgetItem(tag)
59 it.setCheckState(QtCore.Qt.Checked)
60 self.taglist.addItem(it)
61 for tag in [tag for tag in tags if tag not in tags_enabled]:
62 it = QtGui.QListWidgetItem(tag)
63 it.setCheckState(QtCore.Qt.Unchecked)
64 self.taglist.addItem(it)
66 self.setLayout(QtGui.QVBoxLayout())
67 self._add_widget(self.taglist, label = 'Group', tooltip = 'Checked items and their order determines,\n'
68 'by what tags will songs be grouped in Library. Use drag and drop to change the\n'
69 'order of tags.')
71 self.settings.endGroup()
73 def save_settings(self):
74 self.settings.beginGroup(self.plugin.name)
76 tags = []
77 for i in range(self.taglist.count()):
78 it = self.taglist.item(i)
79 if it.checkState() == QtCore.Qt.Checked:
80 tags.append(it.text())
81 self.settings.setValue('grouping', QtCore.QVariant(tags))
83 self.settings.endGroup()
84 self.plugin.fill_library()
86 def get_settings_widget(self):
87 return self.SettingsWidgetLibrary(self)
89 class LibraryWidget(QtGui.QWidget):
90 library_view = None
91 library_model = None
92 search_txt = None
93 grouping = None
94 settings = None
95 plugin = None
96 logger = None
97 filtered_items = None
98 filter = None
101 class LibrarySongItem(QtGui.QStandardItem):
102 # public
103 "Song path"
104 path = None
106 class LibraryModel(QtGui.QStandardItemModel):
107 def fill(self, songs, grouping):
108 self.clear()
110 tree = [{},self.invisibleRootItem()]
111 for song in songs:
112 cur_item = tree
113 for part in grouping:
114 try:
115 tag = song[part]
116 except KeyError:
117 tag = 'Unknown'
118 if tag in cur_item[0]:
119 cur_item = cur_item[0][tag]
120 else:
121 it = QtGui.QStandardItem(tag)
122 it.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled)
123 cur_item[1].appendRow(it)
124 cur_item[0][tag] = [{}, it]
125 cur_item = cur_item[0][tag]
126 it = LibraryWidget.LibrarySongItem('%s%02d %s'%(song['disc'] + '/' if 'disc' in song else '',
127 song['tracknum'] if 'tracknum' in song else 0,
128 song['?title']))
129 it.path = song['?file']
130 it.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled)
131 cur_item[1].appendRow(it)
133 self.sort(0, QtCore.Qt.AscendingOrder)
135 def walk_tree(self, indices):
136 """Returns a generator over all songs that are children of indices."""
137 for index in indices:
138 if self.hasChildren(index):
139 for song in self.walk_tree([self.index(i, 0, index) for i in range(self.rowCount(index))]):
140 yield song
141 else:
142 yield self.itemFromIndex(index).path
144 def flags(self, index):
145 return (QtCore.Qt.ItemIsDragEnabled if index.isValid() else 0) | QtGui.QStandardItemModel.flags(self, index)
147 def mimeTypes(self):
148 return MIMETYPES['songs']
150 def mimeData(self, indices):
151 data = SongsMimeData()
153 songs = []
154 for song in self.walk_tree(indices):
155 songs.append(song)
157 data.set_songs(songs)
158 return data
160 class LibraryView(QtGui.QTreeView):
161 def __init__(self):
162 QtGui.QTreeView.__init__(self)
164 self.setAlternatingRowColors(True)
165 self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
166 self.setUniformRowHeights(True)
167 self.setHeaderHidden(True)
168 self.setDragEnabled(True)
170 def __init__(self, plugin):
171 QtGui.QWidget.__init__(self)
172 self.plugin = plugin
173 self.logger = plugin.logger
174 self.settings = QtCore.QSettings()
175 self.filter = ''
176 self.filtered_items = []
177 self.settings.beginGroup(self.plugin.name)
179 self.grouping = QtGui.QLabel()
181 self.search_txt = QtGui.QLineEdit()
182 self.search_txt.setToolTip('Filter library')
183 self.search_txt.textChanged.connect(self.filter_library)
184 self.search_txt.returnPressed.connect(self.add_filtered)
186 #construct the library
187 self.library_model = self.LibraryModel()
188 self.fill_library()
190 self.library_view = self.LibraryView()
191 self.library_view.setModel(self.library_model)
192 self.library_view.activated.connect(lambda : self.add_indices(self.library_view.selectedIndexes()))
194 self.setLayout(QtGui.QVBoxLayout())
195 self.layout().setSpacing(2)
196 self.layout().setMargin(0)
197 self.layout().addWidget(self.grouping)
198 self.layout().addWidget(self.search_txt)
199 self.layout().addWidget(self.library_view)
201 self.plugin.mpclient.connect_changed.connect(self.fill_library)
202 self.plugin.mpclient.db_updated.connect(self.fill_library)
204 def fill_library(self):
205 self.logger.info('Refreshing library.')
206 self.grouping.setText('/'.join(self.settings.value('grouping').toStringList()))
207 self.library_model.fill(self.plugin.mpclient.library(), self.settings.value('grouping').toStringList())
209 @QtCore.pyqtSlot(unicode)
210 def filter_library(self, text):
211 """Hide all items that don't contain text."""
212 to_hide = []
213 to_show = []
214 filtered_items = []
215 text = text.lower()
216 if not text: # show all items
217 to_show = self.library_model.findItems('*', QtCore.Qt.MatchWildcard|QtCore.Qt.MatchRecursive)
218 elif self.filter and self.filter in text:
219 for item in self.filtered_items:
220 if text in item.text().lower():
221 filtered_items.append(item)
222 while item:
223 to_show.append(item)
224 item = item.parent()
225 else:
226 while item:
227 to_hide.append(item)
228 item = item.parent()
229 else:
230 for item in self.library_model.findItems('*', QtCore.Qt.MatchWildcard|QtCore.Qt.MatchRecursive):
231 if text in item.text().lower():
232 filtered_items.append(item)
233 while item:
234 to_show.append(item)
235 item = item.parent()
236 else:
237 while item:
238 to_hide.append(item)
239 item = item.parent()
240 for item in to_hide:
241 self.library_view.setRowHidden(item.row(), self.library_model.indexFromItem(item.parent()), True)
242 for item in to_show:
243 self.library_view.setRowHidden(item.row(), self.library_model.indexFromItem(item.parent()), False)
245 if len(filtered_items) < 5:
246 for item in filtered_items:
247 while item:
248 item = item.parent()
249 self.library_view.setExpanded(self.library_model.indexFromItem(item), True)
251 self.filtered_items = filtered_items
252 self.filter = text
254 @QtCore.pyqtSlot()
255 def add_filtered(self):
256 self.add_indices([self.library_model.indexFromItem(index) for index in self.filtered_items])
257 self.search_txt.clear()
259 def add_indices(self, indices):
260 paths = []
261 for song in self.library_model.walk_tree(indices):
262 paths.append(song)
263 self.plugin.mpclient.add(paths)