Initial commit from version 1e-99
[beatscape.git] / fileselect.py
1 import pygame
2 import cPickle
3 import os, os.path, re, math
4
5 from keyfile import *
6 from fx import *
7 import event
8
9 fileroot = "songs"
10 songcache = 'songcache.pickle'
11 possave = 0
12
13 def mksongcache():
14         songs = FileNode()
15         songs.crawl(fileroot)
16         f = open(songcache,'w')
17         cPickle.dump(songs,f)
18         f.close()
19         return songs
20
21 filetile = pygame.image.load("gfx/filetile.png")
22
23 class SelectWidget(FX):
24         color = (240,240,255)
25         basecolor = (200,0,0)
26         fadetime = 700
27         size = (400,30)
28
29         OFF = 0
30         ON = 1
31         FADING = 2
32
33         def __init__(self,text,location=(0,0)):
34                 FX.__init__(self)
35                 f = pygame.font.Font('font/Nano.ttf',16)
36                 self.text = f.render(text,True,self.color)
37                 self.surface = pygame.Surface(self.size).convert_alpha()
38                 self.surface.fill( (0,0,0,0) )
39                 self.surface.blit(filetile,(0,0))
40                 self.surface.blit(self.text, (9,7))
41                 self.pointlist = [(10,0),(self.size[0]-2,0),(self.size[0]-2,self.size[1]-2),(0,self.size[1]-2),(0,10)]
42                 self.state = self.OFF
43                 self.location = location
44
45         def off(self):
46                 if self.state == self.ON:
47                         self.state = self.FADING
48                         self.fade_t = pygame.time.get_ticks()
49
50         def on(self):
51                 self.state = self.ON
52
53         def draw(self,t):
54                 self.screen.blit(self.surface,self.location)
55                 color = self.basecolor
56                 if self.state == self.ON:
57                         color = self.color
58                 if self.state == self.FADING:
59                         dt = pygame.time.get_ticks() - self.fade_t
60                         if dt < self.fadetime:
61                                 alpha = math.exp(-0.003*dt)
62                                 color = [(1.0-alpha) * self.basecolor[n] + alpha * self.color[n] for n in range(0,3)]
63                         else:
64                                 self.state = self.OFF
65                 pointlist = [(x[0] + self.location[0],x[1] + self.location[1]) for x in self.pointlist]
66                 pygame.draw.lines(self.screen,color,True,pointlist,2)
67
68 # This would look a lot less ugly if only there were something like
69 # perl's cmp and <=> operators.
70 # FUCK YOU PYTHON WHY DOES THIS HAVE TO BE SO COMPLICATED!?!?!?!
71 def WTFsort(a,b):
72         if a.type == a.DIRECTORY and b.type == b.DIRECTORY:
73                 if a.name > b.name:
74                         return 1
75                 elif a.name < b.name:
76                         return -1
77                 else:
78                         return 0
79         elif a.type == a.DIRECTORY and b.type == b.FILE:
80                 return 1
81         elif a.type == a.FILE and b.type == b.DIRECTORY:
82                 return -1
83         else:
84                 if a.info['player'] > b.info['player']:
85                         return 1
86                 elif a.info['player'] < b.info['player']:
87                         return -1
88                 else:
89                         if not 'playlevel' in a.info:
90                                 return -1
91                         if not 'playlevel' in b.info:
92                                 return 1
93                         if a.info['playlevel'] > b.info['playlevel']:
94                                 return 1
95                         elif a.info['playlevel'] < b.info['playlevel']:
96                                 return -1
97                         else:
98                                 if not 'total' in a.info:
99                                         return -1
100                                 if not 'total' in b.info:
101                                         return 1
102                                 if a.info['total'] > b.info['total']:
103                                         return 1
104                                 elif a.info['total'] < b.info['total']:
105                                         return -1
106                                 else:
107                                         return 0
108
109 filematch = re.compile('\.(bms|bme|sm)$',re.I)
110
111 class FileNode:
112         DIRECTORY = 0
113         FILE = 1
114
115         def __init__(self):
116                 self.children = []
117                 self.open = False
118                 self.info = None
119                 self.widget = None
120
121         def crawl(self,path):
122                 print "Crawling " + path
123                 self.path = path
124                 if os.path.isdir(path):
125                         self.type = self.DIRECTORY
126                         self.name = os.path.basename(path)
127                         files = filter(lambda x: os.path.isdir(os.path.join(path,x)) or filematch.search(x), os.listdir(path))
128                         for f in files:
129                                 c = FileNode()
130                                 if c.crawl(os.path.join(path,f)):
131                                         self.children.append(c)
132                         self.children.sort(WTFsort)
133                 elif os.path.isfile(path):
134                         self.type = self.FILE
135                         self.info = kf_info(path)
136                         self.name = self.info['title']
137                 else:
138                         print path + " isn't a file or a directory. Ignoring."
139                         return False
140                 return True
141
142         def update(self):
143                 for c in self.children:
144                         if not os.path.exists(c.path):
145                                 self.children.remove(c)
146                         else:
147                                 c.update()
148
149         def add(self,child):
150                 self.children.append(child)
151
152         def tolist(self,depth=0):
153                 self.depth = depth
154                 l = [self]
155                 if self.open:
156                         for c in self.children:
157                                 r = c.tolist(depth+1)
158                                 for i in r:
159                                         l.append(i)
160                 return l
161
162         def getwidget(self):
163                 if not self.widget:
164                         self.widget = SelectWidget(self.name)
165                 return self.widget
166
167         def nukewidgets(self):
168                 self.widget = None
169                 for c in self.children:
170                         c.nukewidgets()
171
172 class FileSelect:
173
174         def __init__(self):
175                 self.screen = pygame.display.get_surface()
176                 try:
177                         f = open(songcache,'r')
178                         self.songs = cPickle.load(f)
179                         f.close()
180                 except IOError:
181                         self.songs = mksongcache()
182                 except cPickle.UnpicklingError:
183                         self.songs = mksongcache()
184
185                 self.period = 428
186                 self.songlist = self.songs.tolist()
187                 try:
188                         self.position = self.songlist.index(self.songs.current)
189                 except AttributeError:
190                         self.position = 0
191                 except ValueError:
192                         self.position = 0
193
194                 self.fslabel = Text('file select','font/Nano.ttf',30,(330,30),(255,0,0))
195                 self.fadebottom = pygame.Surface((640,200)).convert_alpha()
196                 for n in range(0,200):
197                         alpha = int((1.0 - math.exp(-n/30.0)) * 255.0)
198                         color = (0,0,0,alpha)
199                         pygame.draw.line(self.fadebottom,color,(0,n),(640,n))
200                 self.labels = {}
201                 self.olabels = {}
202                 self.olabels['title'] = Text('title','font/Nano.ttf',12, (20,320),(255,130,130))
203                 self.labels['title'] = Text('','font/Nano.ttf',18, (30,330),(230,230,255))
204                 self.olabels['artist'] = Text('artist','font/Nano.ttf',12, (20,360),(255,130,130))
205                 self.labels['artist'] = Text('','font/Nano.ttf',16, (30,370),(230,230,255))
206                 self.olabels['genre'] = Text('genre','font/Nano.ttf',12, (20,390),(255,130,130))
207                 self.labels['genre'] = Text('','font/Nano.ttf',16, (30,400),(230,230,255))
208                 self.olabels['playlevel'] = Text('difficulty','font/Nano.ttf',12, (20,420),(255,130,130))
209                 self.labels['playlevel'] = Text('','font/Nano.ttf',16, (30,430),(230,230,255))
210                 self.olabels['bpm'] = Text('BPM','font/Nano.ttf',12, (20,450),(255,130,130))
211                 self.labels['bpm'] = Text('','font/Nano.ttf',16, (30,460),(230,230,255))
212                 self.calcsongwidgets()
213
214         def draw(self,t):
215                 self.screen.fill( (30,0,0) )
216                 l = len(self.songlist)
217                 for nu in range(self.position-4,self.position+8):
218                         n = nu % l
219                         self.songlist[n].getwidget().draw(t)
220
221                 self.screen.blit(self.fadebottom,(0,280))
222                 for k in self.olabels.keys():
223                         self.olabels[k].draw(t)
224                 for k in self.labels.keys():
225                         self.labels[k].draw(t)
226                 self.fslabel.draw(t)
227
228         def calcsongwidgets(self):
229                 l = len(self.songlist)
230                 for nu in range(self.position-4,self.position+8):
231                         n = nu % l
232                         w = self.songlist[n].getwidget()
233                         w.location = (10 + self.songlist[n].depth * 20, 150 - (self.position - nu)*30)
234                         if n == self.position:
235                                 w.on()
236                                 if self.songlist[n].type == FileNode.DIRECTORY:
237                                         for tag in ('artist','genre','playlevel','bpm'):
238                                                 self.labels[tag].settext('')
239                                         self.labels['title'].settext(self.songlist[n].name)
240                                 else:
241                                         for tag in ('title','artist','genre','bpm'):
242                                                 self.labels[tag].settext(self.songlist[n].info[tag])
243                                         try:
244                                                 playlevel = int(self.songlist[n].info['playlevel'])
245                                                 self.labels['playlevel'].settext(("*" * playlevel) + "(" + str(playlevel) + ")")
246                                         except ValueError:
247                                                 self.labels['playlevel'].settext(str(self.songlist[n].info['playlevel']))
248                         else:
249                                 w.off()
250
251         def run(self):
252                 global possave
253                 decided = 0
254                 pygame.mixer.music.load('snd/0x01.ogg')
255                 pygame.mixer.music.set_volume(0.75)
256                 pygame.mixer.music.play(-1)
257                 stab = pygame.mixer.Sound('snd/stab.ogg')
258                 stab.set_volume(0.25)
259                 start_t = pygame.time.get_ticks()
260
261                 while decided == 0:
262                         t = pygame.time.get_ticks() - start_t
263                         act = event.getaction()
264                         while act:
265                                 if act == event.CANCEL:
266                                         return ['mainmenu']
267                                 elif act == event.DOWN:
268                                         self.position += 1
269                                         self.calcsongwidgets()
270                                 elif act == event.UP:
271                                         self.position -= 1
272                                         self.calcsongwidgets()
273                                 elif act == event.OK:
274                                         if self.songlist[self.position].type == FileNode.DIRECTORY:
275                                                 self.songlist[self.position].open = not self.songlist[self.position].open
276                                                 self.songlist = self.songs.tolist()
277                                                 self.calcsongwidgets()
278                                         else:
279                                                 decided = self.position
280                                 act = event.getaction()
281
282                         self.position = self.position % len(self.songlist)
283                         self.draw(t)
284                         pygame.display.flip()
285
286                 pygame.mixer.music.fadeout(1500)
287                 stab.play()
288
289                 self.songs.nukewidgets()
290                 self.songs.current = self.songlist[self.position]
291                 f = open(songcache,'w')
292                 cPickle.dump(self.songs,f)
293                 f.close()
294
295                 #possave = self.position
296                 return ['play',self.songlist[decided].path]