Module playlist
[hide private]
[frames] | no frames]

Source Code for Module playlist

  1  ### BITPIM 
  2  ### 
  3  ### Copyright (C) 2005 Joe Pham <djpham@netzero.net> 
  4  ### 
  5  ### This program is free software; you can redistribute it and/or modify 
  6  ### it under the terms of the BitPim license as detailed in the LICENSE file. 
  7  ### 
  8  ### $Id: playlist.py 4209 2007-05-02 23:06:05Z djpham $ 
  9  """ 
 10  Code to handle Playlist items. 
 11   
 12  The playlist data includes 2 components: the list of available songs, and 
 13  the playlist items. 
 14   
 15  The format of the Playlist items is standardized.  It is a list of dict which 
 16  has the following standard fields: 
 17   
 18  name: string=the name of the play list 
 19  type: string=the type of this play list.  Current supported types are mp3 and wma. 
 20  songs: [ 'song name', ... ] 
 21   
 22  To implement Playlist read/write for a phone module: 
 23  1. Add 2 entries into Profile._supportedsyncs: 
 24          ... 
 25          ('playlist', 'read', 'OVERWRITE'), 
 26          ('playlist', 'write', 'OVERWRITE'), 
 27  2. Implement the following 2 methods in your Phone class: 
 28      def getplaylist(self, result) 
 29      def saveplaylist(self, result, merge) 
 30   
 31  The result dict should have: 
 32  results[playlist.masterlist_key]=['song name 1', 'song name 2', ...] 
 33  results[playlist.playlist_key=[playlist.PlaylistEntry, playlist.PlaylistEntry, ..] 
 34   
 35  """ 
 36   
 37  import wx 
 38  import wx.gizmos as gizmos 
 39   
 40  import database 
 41  import helpids 
 42  import guihelper 
 43  import widgets 
 44   
 45  # module constants-------------------------------------------------------------- 
 46  playlist_key='playlist' 
 47  masterlist_key='masterlist' 
 48  playlists_list='playlists' 
 49  mp3_type='mp3' 
 50  wma_type='wma' 
 51  playlist_type=(mp3_type, wma_type) 
 52   
 53  #------------------------------------------------------------------------------- 
54 -class MasterListDataObject(database.basedataobject):
55 _knownproperties=[] 56 _knownlistproperties=database.basedataobject._knownlistproperties.copy() 57 _knownlistproperties.update({ 'masterlist': ['name'] })
58 - def __init__(self, data=None):
59 if data is None or not isinstance(data, (list, tuple)): 60 return 61 self.update({'masterlist': [{ 'name': x } for x in data] })
62 masterlistobjectfactory=database.dataobjectfactory(MasterListDataObject) 63 64 #-------------------------------------------------------------------------------
65 -class PlaylistDataObject(database.basedataobject):
66 _knownproperties=[] 67 _knownlistproperties=database.basedataobject._knownlistproperties.copy() 68 _knownlistproperties.update( { 'playlist': ['name'] })
69 - def __init__(self, data=None):
70 if data is None or not isinstance(data, (list, tuple)): 71 return 72 self.update({'playlist': [{'name': x} for x in data]})
73 playlistobjectfactory=database.dataobjectfactory(PlaylistDataObject) 74 75 #-------------------------------------------------------------------------------
76 -class PlaylistEntryDataObject(database.basedataobject):
77 _knownproperties=['type'] 78 _knownlistproperties=database.basedataobject._knownlistproperties.copy() 79 _knownlistproperties.update({ 'songs': ['name']})
80 - def __init__(self, data=None):
81 if data is None or not isinstance(data, PlaylistEntry): 82 return 83 self.update(data.get_db_dict())
84 playlistentryobjectfactory=database.dataobjectfactory(PlaylistEntryDataObject) 85 86 #-------------------------------------------------------------------------------
87 -class PlaylistEntry(object):
88 - def __init__(self):
89 self._data={ 'serials': [] }
90
91 - def get(self):
92 return copy.deepcopy(self._data, {})
93 - def set(self, d):
94 self._data={} 95 self._data.update(d)
96
97 - def get_db_dict(self):
98 return { 'type': self.pl_type, 99 'songs': [{ 'name': x } for x in self.songs] }
100 - def set_db_dict(self, d):
101 # name needs to set separately 102 self.pl_type=d.get('type', None) 103 self.songs=[x['name'] for x in d.get('songs', [])]
104
105 - def _set_or_del(self, key, v, v_list=[]):
106 if v is None or v in v_list: 107 if self._data.has_key(key): 108 del self._data[key] 109 else: 110 self._data[key]=v
111
112 - def _get_name(self):
113 return self._data.get('name', '')
114 - def _set_name(self, v):
115 self._set_or_del('name', v, [''])
116 name=property(fget=_get_name, fset=_set_name) 117
118 - def _get_type(self):
119 return self._data.get('type', '')
120 - def _set_type(self, v):
121 self._set_or_del('type', v, [''])
122 pl_type=property(fget=_get_type, fset=_set_type) 123
124 - def _get_songs(self):
125 return self._data.get('songs', [])
126 - def _set_songs(self, v):
127 self._set_or_del('songs', v, [[]])
128 songs=property(fget=_get_songs, fset=_set_songs)
129 130 #-------------------------------------------------------------------------------
131 -class PlaylistWidget(wx.Panel, widgets.BitPimWidget):
132
133 - def __init__(self, mainwindow, parent):
134 super(PlaylistWidget, self).__init__(parent, -1) 135 self._mw=mainwindow 136 self._data=[] 137 self._master=[] 138 self.ignoredirty=False 139 self.dirty=False 140 # main box sizer 141 vbs=wx.BoxSizer(wx.VERTICAL) 142 # horizontal sizer for the main contents 143 hbs=wx.BoxSizer(wx.HORIZONTAL) 144 # the list box 145 self._item_list=gizmos.EditableListBox(self, -1, 'Play Lists:', 146 style=gizmos.EL_ALLOW_NEW|\ 147 gizmos.EL_ALLOW_EDIT|\ 148 gizmos.EL_ALLOW_DELETE) 149 self._item_list.GetUpButton().Show(False) 150 self._item_list.GetDownButton().Show(False) 151 self._item_list_w=self._item_list.GetListCtrl() 152 hbs.Add(self._item_list, 1, wx.EXPAND|wx.ALL, border=5) 153 hbs.Add(wx.StaticLine(self, -1, style=wx.LI_VERTICAL), 0, 154 wx.EXPAND|wx.ALL, 5) 155 # the detailed panel 156 hbs1=wx.BoxSizer(wx.HORIZONTAL) 157 # the playlist 158 _vbs1=wx.BoxSizer(wx.VERTICAL) 159 self._pl_list=gizmos.EditableListBox(self, -1, "Play List Content:", 160 style=gizmos.EL_ALLOW_DELETE) 161 self._pl_list_w=self._pl_list.GetListCtrl() 162 _vbs1.Add(self._pl_list, 1, wx.EXPAND, 0) 163 self._count_lbl=wx.StaticText(self, -1, '') 164 _vbs1.Add(self._count_lbl, 0, wx.EXPAND|wx.TOP, 5) 165 hbs1.Add(_vbs1, 1, wx.EXPAND|wx.ALL, 5) 166 _add_btn=wx.Button(self, -1, '<-Add') 167 hbs1.Add(_add_btn, 0, wx.ALL, 5) 168 self._master_list=gizmos.EditableListBox(self, -1, 'Available Songs:', style=0) 169 self._master_list_w=self._master_list.GetListCtrl() 170 self._master_list.GetUpButton().Show(False) 171 self._master_list.GetDownButton().Show(False) 172 hbs1.Add(self._master_list, 1, wx.EXPAND|wx.ALL, 5) 173 hbs.Add(hbs1, 3, wx.EXPAND|wx.ALL, 5) 174 # the bottom buttons 175 hbs1=wx.BoxSizer(wx.HORIZONTAL) 176 self._save_btn=wx.Button(self, wx.ID_SAVE) 177 self._revert_btn=wx.Button(self, wx.ID_REVERT_TO_SAVED) 178 help_btn=wx.Button(self, wx.ID_HELP) 179 hbs1.Add(self._save_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 180 hbs1.Add(help_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 181 hbs1.Add(self._revert_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 182 # all done 183 vbs.Add(hbs, 1, wx.EXPAND|wx.ALL, 5) 184 vbs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) 185 vbs.Add(hbs1, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 186 self.SetSizer(vbs) 187 self.SetAutoLayout(True) 188 vbs.Fit(self) 189 # event handlers 190 wx.EVT_LIST_ITEM_SELECTED(self._item_list, self._item_list_w.GetId(), 191 self.OnPlaylistSelected) 192 wx.EVT_LIST_BEGIN_LABEL_EDIT(self._item_list, self._item_list_w.GetId(), 193 self.OnStartLabelChanged) 194 wx.EVT_LIST_END_LABEL_EDIT(self._item_list, self._item_list_w.GetId(), 195 self.OnLabelChanged) 196 wx.EVT_BUTTON(self, _add_btn.GetId(), self.OnAdd2Playlist) 197 wx.EVT_BUTTON(self, self._save_btn.GetId(), self.OnSave) 198 wx.EVT_BUTTON(self, self._revert_btn.GetId(), self.OnRevert) 199 wx.EVT_LIST_DELETE_ITEM(self._item_list, self._item_list_w.GetId(), 200 self.OnMakeDirty) 201 wx.EVT_LIST_DELETE_ITEM(self._pl_list, self._pl_list_w.GetId(), 202 self.OnMakeDirty) 203 wx.EVT_BUTTON(self, wx.ID_HELP, 204 lambda _: wx.GetApp().displayhelpid(helpids.ID_TAB_PLAYLIST)) 205 wx.EVT_BUTTON(self._pl_list, self._pl_list.GetUpButton().GetId(), 206 self._OnUpDown) 207 wx.EVT_BUTTON(self._pl_list, self._pl_list.GetDownButton().GetId(), 208 self._OnUpDown) 209 # populate data 210 self._populate() 211 # turn on dirty flag 212 self.setdirty(False)
213
214 - def setdirty(self, val):
215 if self.ignoredirty: 216 return 217 self.dirty=val 218 self._item_list.Enable(not self.dirty) 219 self._save_btn.Enable(self.dirty) 220 self._revert_btn.Enable(self.dirty)
221
222 - def _clear(self, clear_master=True):
223 self._item_list_w.DeleteAllItems() 224 self._pl_list_w.DeleteAllItems() 225 if clear_master: 226 self._master_list_w.DeleteAllItems()
227
228 - def _populate_master(self):
229 self._master_list.SetStrings(self._master)
230 - def _populate_pl_list(self):
231 self._item_list_w.DeleteAllItems() 232 if self._data: 233 self._item_list.SetStrings([e.name for e in self._data]) 234 else: 235 self._item_list.SetStrings([])
236 - def _name2idx(self, name):
237 for i,e in enumerate(self._data): 238 if e.name==name: 239 return i
240 - def _populate_each(self, name):
241 self._pl_list_w.DeleteAllItems() 242 self._count_lbl.SetLabel('') 243 if name is None: 244 return 245 self.ignoredirty=True 246 _list_idx=self._name2idx(name) 247 if _list_idx is not None: 248 self._pl_list.SetStrings(self._data[_list_idx].songs) 249 self._count_lbl.SetLabel('Playlist Size: %d'%len(self._data[_list_idx].songs)) 250 self.ignoredirty=False 251 if not self.dirty: 252 self.setdirty(False)
253
254 - def _populate(self):
255 self._populate_master() 256 self._populate_pl_list()
257
258 - def populate(self, dict):
259 self._data=dict.get(playlist_key, []) 260 self._master=dict.get(masterlist_key, []) 261 self._clear() 262 self._populate()
263
264 - def _save_to_db(self, dict):
265 # first, save the master list of songs. 266 db_rr={ masterlist_key: MasterListDataObject(dict.get(masterlist_key, [])) } 267 database.ensurerecordtype(db_rr, masterlistobjectfactory) 268 self._mw.database.savemajordict(masterlist_key, db_rr) 269 # now, save the list of playlists 270 _pl_list=dict.get(playlist_key, []) 271 db_rr={ playlists_list: PlaylistDataObject([x.name for x in _pl_list]) } 272 database.ensurerecordtype(db_rr, playlistobjectfactory) 273 self._mw.database.savemajordict(playlists_list, db_rr) 274 # save the playlist entries 275 db_rr={ } 276 for e in _pl_list: 277 db_rr[e.name]=PlaylistEntryDataObject(e) 278 database.ensurerecordtype(db_rr, playlistentryobjectfactory) 279 self._mw.database.savemajordict(playlist_key, db_rr)
280
281 - def populatefs(self, dict):
282 self._save_to_db(dict) 283 return dict
284
285 - def getfromfs(self, result):
286 _master_dict=self._mw.database.getmajordictvalues(masterlist_key, 287 masterlistobjectfactory) 288 _master_dict=_master_dict.get(masterlist_key, {}) 289 result.update( { masterlist_key: \ 290 [x['name'] for x in _master_dict.get(masterlist_key, [])] }) 291 _pl_list_dict=self._mw.database.getmajordictvalues(playlists_list, 292 playlistobjectfactory) 293 _pl_list_dict=_pl_list_dict.get(playlists_list, {}) 294 _pl_entries_dict=self._mw.database.getmajordictvalues(playlist_key, 295 playlistentryobjectfactory) 296 _pl_list=[] 297 for e in _pl_list_dict.get(playlist_key, []): 298 _pl_entry=_pl_entries_dict.get(e['name'], None) 299 if _pl_entry: 300 _entry=PlaylistEntry() 301 _entry.name=e['name'] 302 _entry.type=_pl_entry['type'] 303 _entry.songs=[x['name'] for x in _pl_entry.get('songs', [])] 304 _pl_list.append(_entry) 305 result.update({playlist_key: _pl_list }) 306 return result
307 308 # called from various widget update callbacks
309 - def _OnUpDown(self, evt):
310 self.OnMakeDirty() 311 evt.Skip()
312 - def OnMakeDirty(self, _=None):
313 """A public function you can call that will set the dirty flag""" 314 if self.dirty or self.ignoredirty: 315 # already dirty, no need to make it worse 316 return 317 self.setdirty(True)
318
319 - def OnPlaylistSelected(self, evt):
320 self._populate_each(evt.GetLabel()) 321 evt.Skip()
322 - def OnDirty(self, _):
323 self.setdirty(True)
324
325 - def _change_playlist_name(self, new_name):
326 for e in self._data: 327 if e.name==self._old_name: 328 e.name=new_name
329 - def _add_playlist_name(self, new_name):
330 _entry=PlaylistEntry() 331 _entry.name=new_name 332 self._data.append(_entry)
333
334 - def OnStartLabelChanged(self, evt):
335 self._old_name=evt.GetLabel()
336 - def OnLabelChanged(self, evt):
337 _new_name=evt.GetLabel() 338 if _new_name: 339 self.setdirty(True) 340 if self._old_name: 341 self._change_playlist_name(_new_name) 342 else: 343 self._add_playlist_name(_new_name) 344 evt.Skip()
345 346 # kinda redundant given that the playlist does not support the add/delet controls
347 - def GetDeleteInfo(self):
348 return guihelper.ART_DEL_TODO, "Delete Playlist"
349
350 - def GetAddInfo(self):
351 return guihelper.ART_ADD_TODO, "Add Playlist"
352
353 - def OnAdd2Playlist(self, _):
354 _pl_idx=self._item_list_w.GetNextItem(-1, state=wx.LIST_STATE_SELECTED) 355 _master_idx=self._master_list_w.GetNextItem(-1, state=wx.LIST_STATE_SELECTED) 356 if _pl_idx==-1 or _master_idx==-1: 357 # no selection 358 return 359 _entry_idx=self._name2idx(self._item_list_w.GetItemText(_pl_idx)) 360 if _entry_idx is not None: 361 self.setdirty(True) 362 self._pl_list.SetStrings(self._pl_list.GetStrings()+\ 363 [self._master_list_w.GetItemText(_master_idx)])
364
365 - def _build_playlist(self):
366 _pl_list=[] 367 for _name in self._item_list.GetStrings(): 368 if _name: 369 _idx=self._name2idx(_name) 370 if _idx is not None: 371 _pl_list.append(self._data[_idx]) 372 return _pl_list
373
374 - def OnSave(self, _):
375 # save the current playlist 376 _pl_idx=self._item_list_w.GetNextItem(-1, state=wx.LIST_STATE_SELECTED) 377 if _pl_idx!=-1: 378 _entry_idx=self._name2idx(self._item_list_w.GetItemText(_pl_idx)) 379 if _entry_idx is not None: 380 self._data[_entry_idx].songs=self._pl_list.GetStrings() 381 # create data dicts & save them to db 382 self._save_to_db({ masterlist_key: self._master_list.GetStrings(), 383 playlist_key: self._build_playlist() }) 384 self.setdirty(False)
385
386 - def OnRevert(self, _):
387 self._item_list.Enable() 388 _pl_idx=self._item_list_w.GetNextItem(-1, state=wx.LIST_STATE_SELECTED) 389 # discard all changes 390 _res={} 391 self.getfromfs(_res) 392 self.populate(_res) 393 if _pl_idx!=wx.NOT_FOUND: 394 self._item_list_w.SetItemState(_pl_idx, wx.LIST_STATE_SELECTED, 395 wx.LIST_MASK_STATE) 396 self.setdirty(False)
397
398 - def getdata(self, dict):
399 dict[masterlist_key]=self._master_list.GetStrings() 400 dict[playlist_key]=self._build_playlist() 401 return dict
402