Library: improved filtering.
[nephilim.git] / nephilim / plugins / Library.py
blob50079ada94974e434ac79e0aa23ccda18f0e55ba
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
23 class Library(Plugin):
24 # public, const
25 info = 'Display MPD database as a tree.'
27 # public, read-only
28 o = None
30 # private
31 DEFAULTS = {'grouping' : QtCore.QStringList(['albumartist', 'album'])}
33 def _load(self):
34 self.o = LibraryWidget(self)
35 def _unload(self):
36 self.o = None
38 def _get_dock_widget(self):
39 return self._create_dock(self.o)
41 def fill_library(self):
42 if not self.o:
43 return
44 self.o.fill_library()
46 class SettingsWidgetLibrary(Plugin.SettingsWidget):
47 taglist = None
48 def __init__(self, plugin):
49 Plugin.SettingsWidget.__init__(self, plugin)
50 self.settings.beginGroup(self.plugin.name)
52 tags_enabled = self.settings.value('grouping').toStringList()
53 tags = self.plugin.mpclient.tagtypes()
54 self.taglist = QtGui.QListWidget(self)
55 self.taglist.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
56 for tag in [tag for tag in tags_enabled if tag in tags]:
57 it = QtGui.QListWidgetItem(tag)
58 it.setCheckState(QtCore.Qt.Checked)
59 self.taglist.addItem(it)
60 for tag in [tag for tag in tags if tag not in tags_enabled]:
61 it = QtGui.QListWidgetItem(tag)
62 it.setCheckState(QtCore.Qt.Unchecked)
63 self.taglist.addItem(it)
65 self.setLayout(QtGui.QVBoxLayout())
66 self._add_widget(self.taglist, label = 'Group', tooltip = 'Checked items and their order determines,\n'
67 'by what tags will songs be grouped in Library. Use drag and drop to change the\n'
68 'order of tags.')
70 self.settings.endGroup()
72 def save_settings(self):
73 self.settings.beginGroup(self.plugin.name)
75 tags = QtCore.QStringList()
76 for i in range(self.taglist.count()):
77 it = self.taglist.item(i)
78 if it.checkState() == QtCore.Qt.Checked:
79 tags.append(it.text())
80 self.settings.setValue('grouping', QtCore.QVariant(tags))
82 self.settings.endGroup()
83 self.plugin.fill_library()
85 def get_settings_widget(self):
86 return self.SettingsWidgetLibrary(self)
88 class LibraryWidget(QtGui.QWidget):
89 library_view = None
90 library_model = None
91 search_txt = None
92 grouping = None
93 settings = None
94 plugin = None
95 logger = None
96 filtered_items = None
97 filter = None
99 class LibrarySongItem(QtGui.QStandardItem):
100 # public
101 "Song path"
102 path = None
104 class LibraryModel(QtGui.QStandardItemModel):
105 def fill(self, songs, grouping):
106 self.clear()
108 tree = [{},self.invisibleRootItem()]
109 for song in songs:
110 cur_item = tree
111 for part in grouping:
112 tag = song[part]
113 if not tag:
114 tag = 'Unknown'
115 if tag in cur_item[0]:
116 cur_item = cur_item[0][tag]
117 else:
118 it = QtGui.QStandardItem(tag)
119 it.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled)
120 cur_item[1].appendRow(it)
121 cur_item[0][tag] = [{}, it]
122 cur_item = cur_item[0][tag]
123 it = LibraryWidget.LibrarySongItem('%s%02d %s'%(song['disc'] + '/' if 'disc' in song else '',
124 song['tracknum'], song['title']))
125 it.path = song['file']
126 it.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled)
127 cur_item[1].appendRow(it)
129 self.sort(0, QtCore.Qt.AscendingOrder)
131 class LibraryView(QtGui.QTreeView):
132 def __init__(self):
133 QtGui.QTreeView.__init__(self)
135 self.setAlternatingRowColors(True)
136 self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
137 self.setUniformRowHeights(True)
138 self.setHeaderHidden(True)
140 def selectedItems(self):
141 ret = []
142 for index in self.selectedIndexes():
143 ret.append(self.model().itemFromIndex(index))
145 return ret
147 def __init__(self, plugin):
148 QtGui.QWidget.__init__(self)
149 self.plugin = plugin
150 self.logger = plugin.logger
151 self.settings = QtCore.QSettings()
152 self.filter = QtCore.QString()
153 self.filtered_items = []
154 self.settings.beginGroup(self.plugin.name)
156 self.grouping = QtGui.QLabel()
158 self.search_txt = QtGui.QLineEdit()
159 self.search_txt.setToolTip('Filter library')
160 self.search_txt.textChanged.connect(self.filter_library)
161 self.search_txt.returnPressed.connect(self.add_filtered)
163 #construct the library
164 self.library_model = self.LibraryModel()
165 self.fill_library()
167 self.library_view = self.LibraryView()
168 self.library_view.setModel(self.library_model)
169 self.library_view.activated.connect(self.add_selection)
171 self.setLayout(QtGui.QVBoxLayout())
172 self.layout().setSpacing(2)
173 self.layout().setMargin(0)
174 self.layout().addWidget(self.grouping)
175 self.layout().addWidget(self.search_txt)
176 self.layout().addWidget(self.library_view)
178 self.plugin.mpclient.connect_changed.connect(self.fill_library)
179 self.plugin.mpclient.db_updated.connect(self.fill_library)
181 def fill_library(self):
182 self.logger.info('Refreshing library.')
183 self.grouping.setText(self.settings.value('grouping').toStringList().join('/'))
184 self.library_model.fill(self.plugin.mpclient.library(), self.settings.value('grouping').toStringList())
186 @QtCore.pyqtSlot(QtCore.QString)
187 def filter_library(self, text):
188 """Hide all items that don't contain text."""
189 to_hide = []
190 to_show = []
191 filtered_items = []
192 if not text: # show all items
193 to_show = self.library_model.findItems('*', QtCore.Qt.MatchWildcard|QtCore.Qt.MatchRecursive)
194 elif self.filter and text.contains(self.filter, QtCore.Qt.CaseInsensitive):
195 for item in self.filtered_items:
196 if item.text().contains(text, QtCore.Qt.CaseInsensitive):
197 filtered_items.append(item)
198 while item:
199 to_show.append(item)
200 item = item.parent()
201 else:
202 while item:
203 to_hide.append(item)
204 item = item.parent()
205 else:
206 for item in self.library_model.findItems('*', QtCore.Qt.MatchWildcard|QtCore.Qt.MatchRecursive):
207 if item.text().contains(text, QtCore.Qt.CaseInsensitive):
208 filtered_items.append(item)
209 while item:
210 to_show.append(item)
211 item = item.parent()
212 else:
213 while item:
214 to_hide.append(item)
215 item = item.parent()
216 for item in to_hide:
217 self.library_view.setRowHidden(item.row(), self.library_model.indexFromItem(item.parent()), True)
218 for item in to_show:
219 self.library_view.setRowHidden(item.row(), self.library_model.indexFromItem(item.parent()), False)
221 if len(filtered_items) < 5:
222 for item in filtered_items:
223 while item:
224 item = item.parent()
225 self.library_view.setExpanded(self.library_model.indexFromItem(item), True)
227 self.filtered_items = filtered_items
228 self.filter = text
230 def add_filtered(self):
231 self.add_items(self.filtered_items)
232 self.search_txt.clear()
234 def add_selection(self):
235 self.add_items(self.library_view.selectedItems())
237 def add_items(self, items):
238 paths = []
239 for item in items:
240 self.item_to_playlist(item, paths)
241 self.plugin.mpclient.add(paths)
243 def item_to_playlist(self, item, add_queue):
244 if isinstance(item, self.LibrarySongItem):
245 add_queue.append(item.path)
246 else:
247 for i in range(item.rowCount()):
248 self.item_to_playlist(item.child(i), add_queue)