collectiontree.py

Go to the documentation of this file.
00001 from __future__ import with_statement
00002 
00003 import gtk,gtk.glade,gobject
00004 import pickle
00005 
00006 import sqlite3,time,sys
00007 
00008 from plai import config
00009 from plai.track import Track
00010 from plai import popup
00011 
00012 
00013 class CollectionTree:
00014         """The tree on the left side of the window.
00015         Contains tracks grouped by artist then album."""
00016         def __init__(self,widget):
00017                 """Pass me a gtk.TreeView to be in."""
00018                 self._view_view=widget
00019 
00020                 self._view_view.set_headers_visible(False)
00021                 #self._view.set_enable_tree_lines(True)
00022                 self._view_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
00023 
00024                 self._store_store=gtk.TreeStore(gobject.TYPE_STRING,gobject.TYPE_PYOBJECT)
00025                 self._view_view.set_model(self._store_store)
00026         
00027                 renderer=gtk.CellRendererText()
00028                 column=gtk.TreeViewColumn('',renderer,text=0)
00029 
00030                 self._view_view.append_column(column)
00031                 
00032                 targets=[('foo',gtk.TARGET_SAME_APP,42)]
00033                 self._view_view.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,targets,gtk.gdk.ACTION_COPY)
00034                 self._view_view.connect('drag-data-get',self._DragData_DragData)
00035 
00036                 # DISABLED because it seems to only do one track, and also causes
00037                 # collection tracks to be deleted if drag collection to collection.
00038                 # Connect to receive drag moves so users can remove playlist entries
00039                 # by dragging them "back" onto the collection.
00040 #               self._view.enable_model_drag_dest(targets,gtk.gdk.ACTION_COPY|gtk.gdk.ACTION_MOVE)
00041 #               self._view.connect('drag-data-received',lambda a,context,c,d,e,f,time: context.finish(True,True,time))
00042                 
00043                 self._querypattern_querypattern='%'
00044                 self.RunQueryRunQuery('')
00045                 
00046                 self._view_view.connect('button-press-event',self._onClick_onClick)
00047                 self._view_view.connect('row-expanded',self._onExpand_onExpand)
00048         
00049         
00050         def _executeSQL(self,query,params):
00051                 """Run the SQL 'query' with dictionary 'params' substituted in.
00052                 Returns the database object, which you can iterate over."""
00053                 con=sqlite3.Connection(config.sqlfname)
00054                 # we are guaranteed this by the to8Bit() in plaibuildlib
00055                 con.text_factory = lambda x: unicode(x,'latin1')
00056                 db=con.cursor()
00057                 db.row_factory=sqlite3.Row # get named rows        
00058                 db.execute(query,params)
00059                 return db
00060                 
00061         
00062         def RunQuery(self,text):
00063                 """Query the database for tracks matching text and display the results."""
00064                 
00065                 self._querypattern_querypattern='%'+text+'%'
00066 
00067                 self._store_store.clear()
00068                                           
00069                 for onesong in [True,False]:
00070                         # The last bit of sorting ensures we always pick up the artist with the long version (with "The").
00071                         query='select distinct sortartist,artist from collection where '\
00072                                    '(artist like :pattern or album like :pattern or title like :pattern) '\
00073                                    'and rowid '+('' if onesong else 'not ')+'in (select track from onesongs) '\
00074                                    'order by sortartist,length(artist) desc'
00075                                                                                 
00076                         params={'pattern':self._querypattern_querypattern}
00077 
00078                         # Turn results into an actual array. Maybe slows us down, but makes
00079                         # testing for emptiness much cleaner.
00080                         results=[x for x in self._executeSQL_executeSQL(query,params)]
00081                         
00082                         parent=None
00083                         if onesong and results!=[]:
00084                                 parent=self._store_store.append(None,('(one song)','onesong'))
00085                                 
00086                         lastsortartist=None
00087                         
00088                         for result in results:
00089                                 if result['sortartist']!=lastsortartist:        # Don't append new artists when they are variations on what we just did.
00090                                         lastsortartist=result['sortartist']
00091                                         it=self._store_store.append(parent,(result['artist'],'artist'))
00092                                         # make a dummy child and stash the sortartist in it
00093                                         self._store_store.append(it,(None,result['sortartist']))
00094 
00095                 
00096         def _appendexpanded(self,its,ret):
00097                 """Helper function called from GetSelectedTracks. Recurses through the tree."""
00098                 for it in its:
00099                         if self._store_store.iter_n_children(it)==0:
00100                                 ret.append(it)
00101                         else:
00102                                 self._FillInTreeBelow_FillInTreeBelow(it)
00103                                 newits=[]
00104                                 for n in range(0,self._store_store.iter_n_children(it)):
00105                                         newits.append(self._store_store.iter_nth_child(it,n))
00106                                 self._appendexpanded_appendexpanded(newits,ret)
00107         
00108         
00109         def GetSelectedTracks(self):
00110                 """Returns a list of track objects representing what is selected."""
00111                 (junk,paths)=self._view_view.get_selection().get_selected_rows()
00112                 its=[self._store_store.get_iter(p) for p in paths]
00113                 
00114                 exp=[]                          
00115                 self._appendexpanded_appendexpanded(its,exp)
00116                 
00117                 return [self._store_store.get_value(it,1) for it in exp]
00118                 
00119                 
00120         def _DragData(self,widget,context,sel,info,time):
00121                 """Callback for drag and drop. Represents the drag data as a pickled
00122                 list of Track objects."""
00123                 sel.set(sel.target,8,pickle.dumps(self.GetSelectedTracksGetSelectedTracks(),pickle.HIGHEST_PROTOCOL))
00124                 
00125                 
00126         def _onClick(self,widget,event):
00127                 """Handler for clicks on collection. Responds to right-click by
00128                 popping-up the context menu."""
00129                 if event.button==3:
00130                         p=self._view_view.get_path_at_pos(int(event.x),int(event.y))
00131                         if p:
00132                                 (path,col,x,y)=p
00133                                 it=self._store_store.get_iter(path)
00134                                 istrack=self._store_store.iter_n_children(it)==0
00135                                 if istrack:
00136                                         track=self._store_store.get_value(it,1)
00137                                         popup.ShowPopup(event,track)
00138 
00139 
00140         def _onExpand(self,view,it,path):
00141                 """Callback for expanding a row in the tree."""
00142                 self._FillInTreeBelow_FillInTreeBelow(it)                                       
00143                                         
00144                                         
00145         def _FillInTreeBelow(self,it):
00146                 """Call me if you want to be sure that that the contents of the store
00147                 beneath 'it' exist."""
00148                 
00149                 # if it already has non-None children we don't need to populate it
00150                 if self._store_store.get_value(self._store_store.iter_children(it),0): return
00151                 
00152                 nodetype=self._store_store.get_value(it,1)      #the string 'artist' or 'album'
00153                 
00154                 # we always stash the sortartist here
00155                 sortartist=self._store_store.get_value(self._store_store.iter_children(it),1)
00156                 if nodetype=='album': album=self._store_store.get_value(it,0)
00157                                 
00158                 if nodetype=='artist':             
00159                         query='select distinct album from collection where \
00160                                    sortartist==:sortartist and (artist like :pattern or album like :pattern or title like :pattern)\
00161                                order by album'
00162                         params={'sortartist':sortartist,'pattern':self._querypattern_querypattern}
00163                 else:
00164                         query='select fname,title,artist,length from collection where \
00165                                    sortartist==:sortartist and album==:album and (artist like :pattern or album like :pattern or title like :pattern)\
00166                                    order by fname'
00167                         params={'album':album,'sortartist':sortartist,'pattern':self._querypattern_querypattern}
00168                                                         
00169                 for result in self._executeSQL_executeSQL(query,params):
00170                         if nodetype=='artist':
00171                                 child=self._store_store.append(it,(result['album'],'album'))
00172                                 self._store_store.append(child,(None,sortartist))       # create a dummy and stash the sortartist in it
00173                         else:   # we are inserting actual tracks
00174                                 self._store_store.append(it,(result['title'],Track(result['fname'],result['title'],result['artist'],album,result['length'])))
00175                 
00176                 # remove the dummy as now we have filled this all in
00177                 self._store_store.remove(self._store_store.iter_children(it))
00178                         
00179                                 
00180                                 

Generated on Mon Aug 6 21:24:19 2007 for plai by  doxygen 1.5.1