PyXR

c:\projects\bitpim\src \ playlist.py



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