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 pyqtSlot
as Slot
21 from ..plugin
import Plugin
22 from ..common
import MIMETYPES
, SongsMimeData
24 class Library(Plugin
):
26 info
= 'Display MPD database as a tree.'
32 DEFAULTS
= {'foldings' : ['albumartist/album'], 'cur_folding' : 0}
35 self
.o
= LibraryWidget(self
)
39 def _get_dock_widget(self
):
40 return self
._create
_dock
(self
.o
)
42 def fill_library(self
):
47 class LibraryWidget(QtGui
.QWidget
):
59 def __init__(self
, plugin
):
60 QtGui
.QWidget
.__init
__(self
)
62 self
.logger
= plugin
.logger
63 self
.settings
= QtCore
.QSettings()
65 self
.filtered_items
= []
66 self
.settings
.beginGroup(self
.plugin
.name
)
69 self
.foldings
= LibraryFolding(self
.plugin
, self
)
70 self
.foldings
.activated
.connect(self
.fill_library
)
71 del_folding
= QtGui
.QPushButton(QtGui
.QIcon(':icons/delete.png'), '')
72 del_folding
.setToolTip('Delete current folding pattern.')
73 del_folding
.clicked
.connect(lambda :self
.foldings
.removeItem(self
.foldings
.currentIndex()))
74 folding_layout
= QtGui
.QHBoxLayout()
75 folding_layout
.addWidget(self
.foldings
, stretch
= 1)
76 folding_layout
.addWidget(del_folding
)
78 self
.search_txt
= QtGui
.QLineEdit()
79 self
.search_txt
.setToolTip('Filter library')
80 self
.search_txt
.textChanged
.connect(self
.filter_library
)
81 self
.search_txt
.returnPressed
.connect(self
.add_filtered
)
83 #construct the library
84 self
.library_model
= LibraryModel()
87 self
.library_view
= LibraryView()
88 self
.library_view
.setModel(self
.library_model
)
89 self
.library_view
.activated
.connect(lambda : self
.add_indices(self
.library_view
.selectedIndexes()))
91 self
.setLayout(QtGui
.QVBoxLayout())
92 self
.layout().setSpacing(2)
93 self
.layout().setMargin(0)
94 self
.layout().addLayout(folding_layout
)
95 self
.layout().addWidget(self
.search_txt
)
96 self
.layout().addWidget(self
.library_view
)
98 self
.plugin
.mpclient
.db_updated
.connect(self
.fill_library
)
100 def fill_library(self
):
101 self
.logger
.info('Refreshing library.')
102 self
.plugin
.mpclient
.database(lambda songs
: self
.library_model
.fill(songs
, self
.foldings
.currentText().split('/')))
105 def filter_library(self
, text
):
106 """Hide all items that don't contain text."""
111 if not text
: # show all items
112 to_show
= self
.library_model
.findItems('*', QtCore
.Qt
.MatchWildcard|QtCore
.Qt
.MatchRecursive
)
113 elif self
.filter and self
.filter in text
:
114 for item
in self
.filtered_items
:
115 if text
in item
.text().lower():
116 filtered_items
.append(item
)
125 for item
in self
.library_model
.findItems('*', QtCore
.Qt
.MatchWildcard|QtCore
.Qt
.MatchRecursive
):
126 if text
in item
.text().lower():
127 filtered_items
.append(item
)
136 self
.library_view
.setRowHidden(item
.row(), self
.library_model
.indexFromItem(item
.parent()), True)
138 self
.library_view
.setRowHidden(item
.row(), self
.library_model
.indexFromItem(item
.parent()), False)
140 if len(filtered_items
) < 5:
141 for item
in filtered_items
:
144 self
.library_view
.setExpanded(self
.library_model
.indexFromItem(item
), True)
146 self
.filtered_items
= filtered_items
150 def add_filtered(self
):
151 self
.add_indices([self
.library_model
.indexFromItem(index
) for index
in self
.filtered_items
])
152 self
.search_txt
.clear()
154 def add_indices(self
, indices
):
156 for song
in self
.library_model
.walk_tree(indices
):
158 self
.plugin
.mpclient
.add(paths
)
160 class LibrarySongItem(QtGui
.QStandardItem
):
165 class LibraryModel(QtGui
.QStandardItemModel
):
166 def fill(self
, songs
, folding
):
169 tree
= [{},self
.invisibleRootItem()]
177 if tag
in cur_item
[0]:
178 cur_item
= cur_item
[0][tag
]
180 it
= QtGui
.QStandardItem(tag
)
181 it
.setFlags(QtCore
.Qt
.ItemIsSelectable|QtCore
.Qt
.ItemIsEnabled
)
182 cur_item
[1].appendRow(it
)
183 cur_item
[0][tag
] = [{}, it
]
184 cur_item
= cur_item
[0][tag
]
185 it
= LibrarySongItem('%s%02d %s'%(song
['disc'] + '/' if 'disc' in song
else '',
186 song
['tracknum'] if 'tracknum' in song
else 0,
188 it
.path
= song
['?file']
189 it
.setFlags(QtCore
.Qt
.ItemIsSelectable|QtCore
.Qt
.ItemIsEnabled
)
190 cur_item
[1].appendRow(it
)
192 self
.sort(0, QtCore
.Qt
.AscendingOrder
)
194 def walk_tree(self
, indices
):
195 """Returns a generator over all songs that are children of indices."""
196 for index
in indices
:
197 if self
.hasChildren(index
):
198 for song
in self
.walk_tree([self
.index(i
, 0, index
) for i
in range(self
.rowCount(index
))]):
201 yield self
.itemFromIndex(index
).path
203 def flags(self
, index
):
204 return (QtCore
.Qt
.ItemIsDragEnabled
if index
.isValid() else 0) | QtGui
.QStandardItemModel
.flags(self
, index
)
207 return MIMETYPES
['songs']
209 def mimeData(self
, indices
):
210 data
= SongsMimeData()
213 for song
in self
.walk_tree(indices
):
216 data
.set_songs(songs
)
219 class LibraryView(QtGui
.QTreeView
):
221 QtGui
.QTreeView
.__init
__(self
)
223 self
.setAlternatingRowColors(True)
224 self
.setSelectionMode(QtGui
.QAbstractItemView
.ExtendedSelection
)
225 self
.setUniformRowHeights(True)
226 self
.setHeaderHidden(True)
227 self
.setDragEnabled(True)
229 class LibraryFolding(QtGui
.QComboBox
):
237 def __init__(self
, plugin
, parent
):
238 QtGui
.QComboBox
.__init
__(self
, parent
)
239 self
.setEditable(True)
240 self
.setToolTip('Current folding pattern.')
242 self
._plugin
= plugin
244 # fill with stored folding patterns, ensure at least an empty string
245 self
._settings
= QtCore
.QSettings()
246 self
._settings
.beginGroup(self
._plugin
.name
)
247 for item
in self
._settings
.value('foldings'):
251 self
.setCurrentIndex(int(self
._settings
.value('cur_folding')))
253 # connect signals to slots
254 self
.currentIndexChanged
.connect( self
._update
_foldings
)
255 self
.model().rowsInserted
.connect(self
._update
_foldings
)
256 self
.model().rowsRemoved
.connect( self
._update
_foldings
)
257 self
.model().rowsMoved
.connect( self
._update
_foldings
)
261 def _update_foldings(self
):
263 for i
in xrange(self
.count()):
264 foldings
.append(self
.itemText(i
))
265 self
._settings
.setValue('foldings', foldings
)
266 self
._settings
.setValue('cur_folding', self
.currentIndex())