0001 ### BITPIM 0002 ### 0003 ### Copyright (C) 2005 Joe Pham <djpham@netzero.net> 0004 ### 0005 ### This program is free software; you can redistribute it and/or modify 0006 ### it under the terms of the BitPim license as detailed in the LICENSE file. 0007 ### 0008 ### $Id: playlist.py 4209 2007-05-02 23:06:05Z djpham $ 0009 """ 0010 Code to handle Playlist items. 0011 0012 The playlist data includes 2 components: the list of available songs, and 0013 the playlist items. 0014 0015 The format of the Playlist items is standardized. It is a list of dict which 0016 has the following standard fields: 0017 0018 name: string=the name of the play list 0019 type: string=the type of this play list. Current supported types are mp3 and wma. 0020 songs: [ 'song name', ... ] 0021 0022 To implement Playlist read/write for a phone module: 0023 1. Add 2 entries into Profile._supportedsyncs: 0024 ... 0025 ('playlist', 'read', 'OVERWRITE'), 0026 ('playlist', 'write', 'OVERWRITE'), 0027 2. Implement the following 2 methods in your Phone class: 0028 def getplaylist(self, result) 0029 def saveplaylist(self, result, merge) 0030 0031 The result dict should have: 0032 results[playlist.masterlist_key]=['song name 1', 'song name 2', ...] 0033 results[playlist.playlist_key=[playlist.PlaylistEntry, playlist.PlaylistEntry, ..] 0034 0035 """ 0036 0037 import wx 0038 import wx.gizmos as gizmos 0039 0040 import database 0041 import helpids 0042 import guihelper 0043 import widgets 0044 0045 # module constants-------------------------------------------------------------- 0046 playlist_key='playlist' 0047 masterlist_key='masterlist' 0048 playlists_list='playlists' 0049 mp3_type='mp3' 0050 wma_type='wma' 0051 playlist_type=(mp3_type, wma_type) 0052 0053 #------------------------------------------------------------------------------- 0054 class MasterListDataObject(database.basedataobject): 0055 _knownproperties=[] 0056 _knownlistproperties=database.basedataobject._knownlistproperties.copy() 0057 _knownlistproperties.update({ 'masterlist': ['name'] }) 0058 def __init__(self, data=None): 0059 if data is None or not isinstance(data, (list, tuple)): 0060 return 0061 self.update({'masterlist': [{ 'name': x } for x in data] }) 0062 masterlistobjectfactory=database.dataobjectfactory(MasterListDataObject) 0063 0064 #------------------------------------------------------------------------------- 0065 class PlaylistDataObject(database.basedataobject): 0066 _knownproperties=[] 0067 _knownlistproperties=database.basedataobject._knownlistproperties.copy() 0068 _knownlistproperties.update( { 'playlist': ['name'] }) 0069 def __init__(self, data=None): 0070 if data is None or not isinstance(data, (list, tuple)): 0071 return 0072 self.update({'playlist': [{'name': x} for x in data]}) 0073 playlistobjectfactory=database.dataobjectfactory(PlaylistDataObject) 0074 0075 #------------------------------------------------------------------------------- 0076 class PlaylistEntryDataObject(database.basedataobject): 0077 _knownproperties=['type'] 0078 _knownlistproperties=database.basedataobject._knownlistproperties.copy() 0079 _knownlistproperties.update({ 'songs': ['name']}) 0080 def __init__(self, data=None): 0081 if data is None or not isinstance(data, PlaylistEntry): 0082 return 0083 self.update(data.get_db_dict()) 0084 playlistentryobjectfactory=database.dataobjectfactory(PlaylistEntryDataObject) 0085 0086 #------------------------------------------------------------------------------- 0087 class PlaylistEntry(object): 0088 def __init__(self): 0089 self._data={ 'serials': [] } 0090 0091 def get(self): 0092 return copy.deepcopy(self._data, {}) 0093 def set(self, d): 0094 self._data={} 0095 self._data.update(d) 0096 0097 def get_db_dict(self): 0098 return { 'type': self.pl_type, 0099 'songs': [{ 'name': x } for x in self.songs] } 0100 def set_db_dict(self, d): 0101 # name needs to set separately 0102 self.pl_type=d.get('type', None) 0103 self.songs=[x['name'] for x in d.get('songs', [])] 0104 0105 def _set_or_del(self, key, v, v_list=[]): 0106 if v is None or v in v_list: 0107 if self._data.has_key(key): 0108 del self._data[key] 0109 else: 0110 self._data[key]=v 0111 0112 def _get_name(self): 0113 return self._data.get('name', '') 0114 def _set_name(self, v): 0115 self._set_or_del('name', v, ['']) 0116 name=property(fget=_get_name, fset=_set_name) 0117 0118 def _get_type(self): 0119 return self._data.get('type', '') 0120 def _set_type(self, v): 0121 self._set_or_del('type', v, ['']) 0122 pl_type=property(fget=_get_type, fset=_set_type) 0123 0124 def _get_songs(self): 0125 return self._data.get('songs', []) 0126 def _set_songs(self, v): 0127 self._set_or_del('songs', v, [[]]) 0128 songs=property(fget=_get_songs, fset=_set_songs) 0129 0130 #------------------------------------------------------------------------------- 0131 class PlaylistWidget(wx.Panel, widgets.BitPimWidget): 0132 0133 def __init__(self, mainwindow, parent): 0134 super(PlaylistWidget, self).__init__(parent, -1) 0135 self._mw=mainwindow 0136 self._data=[] 0137 self._master=[] 0138 self.ignoredirty=False 0139 self.dirty=False 0140 # main box sizer 0141 vbs=wx.BoxSizer(wx.VERTICAL) 0142 # horizontal sizer for the main contents 0143 hbs=wx.BoxSizer(wx.HORIZONTAL) 0144 # the list box 0145 self._item_list=gizmos.EditableListBox(self, -1, 'Play Lists:', 0146 style=gizmos.EL_ALLOW_NEW|\ 0147 gizmos.EL_ALLOW_EDIT|\ 0148 gizmos.EL_ALLOW_DELETE) 0149 self._item_list.GetUpButton().Show(False) 0150 self._item_list.GetDownButton().Show(False) 0151 self._item_list_w=self._item_list.GetListCtrl() 0152 hbs.Add(self._item_list, 1, wx.EXPAND|wx.ALL, border=5) 0153 hbs.Add(wx.StaticLine(self, -1, style=wx.LI_VERTICAL), 0, 0154 wx.EXPAND|wx.ALL, 5) 0155 # the detailed panel 0156 hbs1=wx.BoxSizer(wx.HORIZONTAL) 0157 # the playlist 0158 _vbs1=wx.BoxSizer(wx.VERTICAL) 0159 self._pl_list=gizmos.EditableListBox(self, -1, "Play List Content:", 0160 style=gizmos.EL_ALLOW_DELETE) 0161 self._pl_list_w=self._pl_list.GetListCtrl() 0162 _vbs1.Add(self._pl_list, 1, wx.EXPAND, 0) 0163 self._count_lbl=wx.StaticText(self, -1, '') 0164 _vbs1.Add(self._count_lbl, 0, wx.EXPAND|wx.TOP, 5) 0165 hbs1.Add(_vbs1, 1, wx.EXPAND|wx.ALL, 5) 0166 _add_btn=wx.Button(self, -1, '<-Add') 0167 hbs1.Add(_add_btn, 0, wx.ALL, 5) 0168 self._master_list=gizmos.EditableListBox(self, -1, 'Available Songs:', style=0) 0169 self._master_list_w=self._master_list.GetListCtrl() 0170 self._master_list.GetUpButton().Show(False) 0171 self._master_list.GetDownButton().Show(False) 0172 hbs1.Add(self._master_list, 1, wx.EXPAND|wx.ALL, 5) 0173 hbs.Add(hbs1, 3, wx.EXPAND|wx.ALL, 5) 0174 # the bottom buttons 0175 hbs1=wx.BoxSizer(wx.HORIZONTAL) 0176 self._save_btn=wx.Button(self, wx.ID_SAVE) 0177 self._revert_btn=wx.Button(self, wx.ID_REVERT_TO_SAVED) 0178 help_btn=wx.Button(self, wx.ID_HELP) 0179 hbs1.Add(self._save_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0180 hbs1.Add(help_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0181 hbs1.Add(self._revert_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0182 # all done 0183 vbs.Add(hbs, 1, wx.EXPAND|wx.ALL, 5) 0184 vbs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) 0185 vbs.Add(hbs1, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0186 self.SetSizer(vbs) 0187 self.SetAutoLayout(True) 0188 vbs.Fit(self) 0189 # event handlers 0190 wx.EVT_LIST_ITEM_SELECTED(self._item_list, self._item_list_w.GetId(), 0191 self.OnPlaylistSelected) 0192 wx.EVT_LIST_BEGIN_LABEL_EDIT(self._item_list, self._item_list_w.GetId(), 0193 self.OnStartLabelChanged) 0194 wx.EVT_LIST_END_LABEL_EDIT(self._item_list, self._item_list_w.GetId(), 0195 self.OnLabelChanged) 0196 wx.EVT_BUTTON(self, _add_btn.GetId(), self.OnAdd2Playlist) 0197 wx.EVT_BUTTON(self, self._save_btn.GetId(), self.OnSave) 0198 wx.EVT_BUTTON(self, self._revert_btn.GetId(), self.OnRevert) 0199 wx.EVT_LIST_DELETE_ITEM(self._item_list, self._item_list_w.GetId(), 0200 self.OnMakeDirty) 0201 wx.EVT_LIST_DELETE_ITEM(self._pl_list, self._pl_list_w.GetId(), 0202 self.OnMakeDirty) 0203 wx.EVT_BUTTON(self, wx.ID_HELP, 0204 lambda _: wx.GetApp().displayhelpid(helpids.ID_TAB_PLAYLIST)) 0205 wx.EVT_BUTTON(self._pl_list, self._pl_list.GetUpButton().GetId(), 0206 self._OnUpDown) 0207 wx.EVT_BUTTON(self._pl_list, self._pl_list.GetDownButton().GetId(), 0208 self._OnUpDown) 0209 # populate data 0210 self._populate() 0211 # turn on dirty flag 0212 self.setdirty(False) 0213 0214 def setdirty(self, val): 0215 if self.ignoredirty: 0216 return 0217 self.dirty=val 0218 self._item_list.Enable(not self.dirty) 0219 self._save_btn.Enable(self.dirty) 0220 self._revert_btn.Enable(self.dirty) 0221 0222 def _clear(self, clear_master=True): 0223 self._item_list_w.DeleteAllItems() 0224 self._pl_list_w.DeleteAllItems() 0225 if clear_master: 0226 self._master_list_w.DeleteAllItems() 0227 0228 def _populate_master(self): 0229 self._master_list.SetStrings(self._master) 0230 def _populate_pl_list(self): 0231 self._item_list_w.DeleteAllItems() 0232 if self._data: 0233 self._item_list.SetStrings([e.name for e in self._data]) 0234 else: 0235 self._item_list.SetStrings([]) 0236 def _name2idx(self, name): 0237 for i,e in enumerate(self._data): 0238 if e.name==name: 0239 return i 0240 def _populate_each(self, name): 0241 self._pl_list_w.DeleteAllItems() 0242 self._count_lbl.SetLabel('') 0243 if name is None: 0244 return 0245 self.ignoredirty=True 0246 _list_idx=self._name2idx(name) 0247 if _list_idx is not None: 0248 self._pl_list.SetStrings(self._data[_list_idx].songs) 0249 self._count_lbl.SetLabel('Playlist Size: %d'%len(self._data[_list_idx].songs)) 0250 self.ignoredirty=False 0251 if not self.dirty: 0252 self.setdirty(False) 0253 0254 def _populate(self): 0255 self._populate_master() 0256 self._populate_pl_list() 0257 0258 def populate(self, dict): 0259 self._data=dict.get(playlist_key, []) 0260 self._master=dict.get(masterlist_key, []) 0261 self._clear() 0262 self._populate() 0263 0264 def _save_to_db(self, dict): 0265 # first, save the master list of songs. 0266 db_rr={ masterlist_key: MasterListDataObject(dict.get(masterlist_key, [])) } 0267 database.ensurerecordtype(db_rr, masterlistobjectfactory) 0268 self._mw.database.savemajordict(masterlist_key, db_rr) 0269 # now, save the list of playlists 0270 _pl_list=dict.get(playlist_key, []) 0271 db_rr={ playlists_list: PlaylistDataObject([x.name for x in _pl_list]) } 0272 database.ensurerecordtype(db_rr, playlistobjectfactory) 0273 self._mw.database.savemajordict(playlists_list, db_rr) 0274 # save the playlist entries 0275 db_rr={ } 0276 for e in _pl_list: 0277 db_rr[e.name]=PlaylistEntryDataObject(e) 0278 database.ensurerecordtype(db_rr, playlistentryobjectfactory) 0279 self._mw.database.savemajordict(playlist_key, db_rr) 0280 0281 def populatefs(self, dict): 0282 self._save_to_db(dict) 0283 return dict 0284 0285 def getfromfs(self, result): 0286 _master_dict=self._mw.database.getmajordictvalues(masterlist_key, 0287 masterlistobjectfactory) 0288 _master_dict=_master_dict.get(masterlist_key, {}) 0289 result.update( { masterlist_key: \ 0290 [x['name'] for x in _master_dict.get(masterlist_key, [])] }) 0291 _pl_list_dict=self._mw.database.getmajordictvalues(playlists_list, 0292 playlistobjectfactory) 0293 _pl_list_dict=_pl_list_dict.get(playlists_list, {}) 0294 _pl_entries_dict=self._mw.database.getmajordictvalues(playlist_key, 0295 playlistentryobjectfactory) 0296 _pl_list=[] 0297 for e in _pl_list_dict.get(playlist_key, []): 0298 _pl_entry=_pl_entries_dict.get(e['name'], None) 0299 if _pl_entry: 0300 _entry=PlaylistEntry() 0301 _entry.name=e['name'] 0302 _entry.type=_pl_entry['type'] 0303 _entry.songs=[x['name'] for x in _pl_entry.get('songs', [])] 0304 _pl_list.append(_entry) 0305 result.update({playlist_key: _pl_list }) 0306 return result 0307 0308 # called from various widget update callbacks 0309 def _OnUpDown(self, evt): 0310 self.OnMakeDirty() 0311 evt.Skip() 0312 def OnMakeDirty(self, _=None): 0313 """A public function you can call that will set the dirty flag""" 0314 if self.dirty or self.ignoredirty: 0315 # already dirty, no need to make it worse 0316 return 0317 self.setdirty(True) 0318 0319 def OnPlaylistSelected(self, evt): 0320 self._populate_each(evt.GetLabel()) 0321 evt.Skip() 0322 def OnDirty(self, _): 0323 self.setdirty(True) 0324 0325 def _change_playlist_name(self, new_name): 0326 for e in self._data: 0327 if e.name==self._old_name: 0328 e.name=new_name 0329 def _add_playlist_name(self, new_name): 0330 _entry=PlaylistEntry() 0331 _entry.name=new_name 0332 self._data.append(_entry) 0333 0334 def OnStartLabelChanged(self, evt): 0335 self._old_name=evt.GetLabel() 0336 def OnLabelChanged(self, evt): 0337 _new_name=evt.GetLabel() 0338 if _new_name: 0339 self.setdirty(True) 0340 if self._old_name: 0341 self._change_playlist_name(_new_name) 0342 else: 0343 self._add_playlist_name(_new_name) 0344 evt.Skip() 0345 0346 # kinda redundant given that the playlist does not support the add/delet controls 0347 def GetDeleteInfo(self): 0348 return guihelper.ART_DEL_TODO, "Delete Playlist" 0349 0350 def GetAddInfo(self): 0351 return guihelper.ART_ADD_TODO, "Add Playlist" 0352 0353 def OnAdd2Playlist(self, _): 0354 _pl_idx=self._item_list_w.GetNextItem(-1, state=wx.LIST_STATE_SELECTED) 0355 _master_idx=self._master_list_w.GetNextItem(-1, state=wx.LIST_STATE_SELECTED) 0356 if _pl_idx==-1 or _master_idx==-1: 0357 # no selection 0358 return 0359 _entry_idx=self._name2idx(self._item_list_w.GetItemText(_pl_idx)) 0360 if _entry_idx is not None: 0361 self.setdirty(True) 0362 self._pl_list.SetStrings(self._pl_list.GetStrings()+\ 0363 [self._master_list_w.GetItemText(_master_idx)]) 0364 0365 def _build_playlist(self): 0366 _pl_list=[] 0367 for _name in self._item_list.GetStrings(): 0368 if _name: 0369 _idx=self._name2idx(_name) 0370 if _idx is not None: 0371 _pl_list.append(self._data[_idx]) 0372 return _pl_list 0373 0374 def OnSave(self, _): 0375 # save the current playlist 0376 _pl_idx=self._item_list_w.GetNextItem(-1, state=wx.LIST_STATE_SELECTED) 0377 if _pl_idx!=-1: 0378 _entry_idx=self._name2idx(self._item_list_w.GetItemText(_pl_idx)) 0379 if _entry_idx is not None: 0380 self._data[_entry_idx].songs=self._pl_list.GetStrings() 0381 # create data dicts & save them to db 0382 self._save_to_db({ masterlist_key: self._master_list.GetStrings(), 0383 playlist_key: self._build_playlist() }) 0384 self.setdirty(False) 0385 0386 def OnRevert(self, _): 0387 self._item_list.Enable() 0388 _pl_idx=self._item_list_w.GetNextItem(-1, state=wx.LIST_STATE_SELECTED) 0389 # discard all changes 0390 _res={} 0391 self.getfromfs(_res) 0392 self.populate(_res) 0393 if _pl_idx!=wx.NOT_FOUND: 0394 self._item_list_w.SetItemState(_pl_idx, wx.LIST_STATE_SELECTED, 0395 wx.LIST_MASK_STATE) 0396 self.setdirty(False) 0397 0398 def getdata(self, dict): 0399 dict[masterlist_key]=self._master_list.GetStrings() 0400 dict[playlist_key]=self._build_playlist() 0401 return dict 0402
Generated by PyXR 0.9.4