1 # -*- coding: utf-8 -*-
3 Boîte de dialogue pour sélection des chansons.
10 from pygame
.locals import K_RETURN
11 from pgu
.gui
import FileDialog
12 import pgu
.gui
.basic
as basic
13 import pgu
.gui
.input as input
14 import pgu
.gui
.button
as button
15 import pgu
.gui
.pguglobals
as pguglobals
16 import pgu
.gui
.table
as table
17 import pgu
.gui
.area
as area
18 from pgu
.gui
.const
import *
19 from pgu
.gui
.dialog
import Dialog
20 from pgu
.gui
.app
import Desktop
22 from datetime
import timedelta
26 from xml
.etree
import ElementTree
27 from minwii
.musicxml
import musicXml2Song
29 INDEX_TXT
= 'index.txt'
30 PICTURE_ITEM_SIZE
= 64
32 def appEventFactory(app
, dlg
) :
33 # monkey patch de la méthode gestionnaire d'événements :
34 # l'ensemble du Desktop écoute les événements de la roulette de la souris
35 # et les redirige sur la liste déroulante.
36 def _appEvent(self
, e
) :
38 if dlg
.list.vscrollbar
:
39 if not hasattr(dlg
.list.vscrollbar
,'value'):
42 if e
.type == pygame
.locals.MOUSEBUTTONDOWN
:
43 if e
.button
== 4: #wheel up
44 dlg
.list.vscrollbar
._click
(-1)
46 elif e
.button
== 5: #wheel down
47 dlg
.list.vscrollbar
._click
(1)
49 return Desktop
.event(self
, e
)
51 return types
.MethodType(_appEvent
, app
)
53 class FileOpenDialog(FileDialog
):
57 def __init__(self
, path
):
59 if not path
: self
.curdir
= os
.getcwd()
60 else: self
.curdir
= path
61 self
.dir_img
= basic
.Image(
62 pguglobals
.app
.theme
.get(cls1
+'.folder', '', 'image'))
63 self
.soundfile_img
= basic
.Image(
64 pguglobals
.app
.theme
.get(cls1
+'.soundfile', '', 'image'))
65 td_style
= {'padding_left': 4,
69 self
.title
= basic
.Label("Ouvrir une chanson", cls
="dialog.title.label")
70 self
.body
= table
.Table()
71 self
.list = area
.List(width
=880, height
=375)
72 self
.input_dir
= input.Input()
73 self
.input_file
= input.Input()
74 self
._current
_sort
= 'alpha'
76 self
.button_ok
= button
.Button("Ouvrir")
77 self
.button_sort_alpha
= button
.Button("A-Z")
78 self
.button_sort_alpha
.connect(CLICK
, self
._set
_current
_sort
_, 'alpha')
79 self
.button_sort_num
= button
.Button("0-9")
80 self
.button_sort_num
.connect(CLICK
, self
._set
_current
_sort
_, 'num')
82 self
.body
.td(basic
.Label("Dossier"), style
=td_style
, align
=-1)
83 self
.body
.td(self
.input_dir
, style
=td_style
)
84 self
.body
.td(self
.button_sort_alpha
)
85 self
.body
.td(self
.button_sort_num
)
87 self
.body
.td(self
.list, colspan
=4, style
=td_style
)
88 self
.list.connect(CHANGE
, self
._item
_select
_changed
_, None)
89 #self.list.connect(CLICK, self._check_dbl_click_, None)
90 self
._last
_time
_click
= pygame
.time
.get_ticks()
91 self
.button_ok
.connect(CLICK
, self
._button
_okay
_clicked
_, None)
93 self
.body
.td(basic
.Label("Fichier"), style
=td_style
, align
=-1)
94 self
.body
.td(self
.input_file
, style
=td_style
)
95 self
.body
.td(self
.button_ok
, style
=td_style
, colspan
=2)
97 Dialog
.__init
__(self
, self
.title
, self
.body
)
101 self
.__regularEventMethod
= app
.event
102 app
.event
= appEventFactory(app
, self
)
104 def close(self
, w
=None) :
105 FileDialog
.close(self
, w
)
106 # retrait du monkey patch
108 app
.event
= self
.__regularEventMethod
111 def _list_dir_(self
):
112 self
.input_dir
.value
= self
.curdir
113 self
.input_dir
.pos
= len(self
.curdir
)
114 self
.input_dir
.vpos
= 0
118 for i
in os
.listdir(self
.curdir
):
119 if i
.startswith('.') : continue
120 if os
.path
.isdir(os
.path
.join(self
.curdir
, i
)): dirs
.append(i
)
121 else: files
.append(i
)
123 self
.input_file
.value
= "Dossier innacessible !"
130 self
.list.add(i
, image
=self
.dir_img
, value
=i
)
134 if not i
.endswith('.xml') :
136 filepath
= os
.path
.join(self
.curdir
, i
)
137 xmlFiles
.append(filepath
)
140 printableLines
= self
.getPrintableLines(xmlFiles
)
141 for l
in printableLines
:
142 imgpath
= os
.path
.splitext(os
.path
.join(self
.curdir
, l
[1]))[0] + '.jpg'
143 if os
.path
.exists(imgpath
) :
144 img
= pygame
.image
.load(imgpath
)
145 iw
, ih
= img
.get_width(), img
.get_height()
148 style
['width'] = PICTURE_ITEM_SIZE
149 style
['height'] = int(round(PICTURE_ITEM_SIZE
* float(ih
) / iw
))
151 style
['heigth'] = PICTURE_ITEM_SIZE
152 style
['width'] = int(round(PICTURE_ITEM_SIZE
* float(iw
) / ih
))
154 img
= basic
.Image(img
, style
=style
)
156 img
= self
.soundfile_img
157 self
.list.add(l
[0], value
= l
[1], image
= img
)
159 self
.list.set_vertical_scroll(0)
161 def getPrintableLines(self
, xmlFiles
) :
162 index
= self
.getUpdatedIndex(xmlFiles
)
168 printableLines
.append(('%s - %s / %s' % (l
[2], l
[3], l
[4]), l
[0]))
170 return printableLines
174 def getSongTitle(file) :
175 it
= ElementTree
.iterparse(file, ['start', 'end'])
177 title
= os
.path
.basename(file)
180 if el
.tag
== 'credit' :
182 if el
.tag
== 'credit-words' and creditFound
:
185 if el
.tag
== 'part-list' :
186 # au delà de ce tag : aucune chance de trouver un titre
191 def getSongMetadata(file) :
193 metadata
['title'] = FileOpenDialog
.getSongTitle(file).encode('iso-8859-1')
194 metadata
['mtime'] = str(os
.stat(file).st_mtime
)
195 metadata
['file'] = os
.path
.basename(file)
196 song
= musicXml2Song(file)
197 metadata
['distinctNotes'] = len(song
.distinctNotes
)
199 duration
= song
.duration
/ 1000.
200 duration
= int(round(duration
, 0))
201 duration
= timedelta(seconds
=duration
)
203 duration
= str(duration
) # p.ex. 0:03:05
204 duration
= duration
.split(':')
205 h
, m
, s
= [int(n
) for n
in duration
]
206 if h
: raise ValueError(h
)
207 duration
= ':'.join([str(n
).zfill(2) for n
in (m
, s
)])
210 duration
= srt(duration
)
212 metadata
['duration'] = duration
214 # histo = song.intervalsHistogram
215 # coeffInter = reduce(lambda a, b : a + b,
216 # [abs(k) * v for k, v in histo.items()])
218 # totInter = reduce(lambda a, b: a+b, histo.values())
219 # totInter = totInter - histo.get(0, 0)
220 # difficulty = int(round(float(coeffInter) / totInter, 0))
221 # metadata['difficulty'] = difficulty
225 def getUpdatedIndex(self
, xmlFiles
) :
226 indexTxtPath
= os
.path
.join(self
.curdir
, INDEX_TXT
)
229 if not os
.path
.exists(indexTxtPath
) :
230 musicXmlFound
= False
231 tmp
= tempfile
.TemporaryFile(mode
='r+')
232 for file in xmlFiles
:
234 metadata
= FileOpenDialog
.getSongMetadata(file)
236 except ValueError, e
:
238 if e
.args
and e
.args
[0] == 'not a musicxml file' :
241 line
= '%(file)s\t%(mtime)s\t%(title)s\t%(distinctNotes)d\t%(duration)s\n' % metadata
247 indexFile
= open(indexTxtPath
, 'w')
248 indexFile
.write(tmp
.read())
253 indexTxt
= open(indexTxtPath
, 'r')
255 # check if index is up to date, and update entries if so.
256 for l
in filter(None, indexTxt
.readlines()) :
257 parts
= l
.split('\t')
258 fileBaseName
, modificationTime
= parts
[0], parts
[1]
259 filePath
= os
.path
.join(self
.curdir
, fileBaseName
)
261 if not os
.path
.exists(filePath
) :
264 indexedFiles
[fileBaseName
] = l
265 currentMtime
= str(os
.stat(filePath
).st_mtime
)
267 # check modification time missmatch
268 if currentMtime
!= modificationTime
:
270 metadata
= FileOpenDialog
.getSongMetadata(filePath
)
272 except ValueError, e
:
274 if e
.args
and e
.args
[0] == 'not a musicxml file' :
277 metadata
= FileOpenDialog
.getSongMetadata(filePath
)
278 line
= '%(file)s\t%(mtime)s\t%(title)s\t%(distinctNotes)d\t%(duration)s\n' % metadata
279 indexedFiles
[fileBaseName
] = line
281 # check for new files.
282 for file in xmlFiles
:
283 fileBaseName
= os
.path
.basename(file)
284 if not indexedFiles
.has_key(fileBaseName
) :
286 metadata
= FileOpenDialog
.getSongMetadata(filePath
)
288 except ValueError, e
:
290 if e
.args
and e
.args
[0] == 'not a musicxml file' :
293 metadata
= FileOpenDialog
.getSongMetadata(file)
294 line
= '%(file)s\t%(mtime)s\t%(title)s\t%(distinctNotes)d\t%(duration)s\n' % metadata
295 indexedFiles
[fileBaseName
] = line
297 # ok, the index is up to date !
299 index
= indexedFiles
.values()
302 if self
._current
_sort
== 'alpha' :
304 da
= desacc(a
.split('\t')[2]).lower()
305 db
= desacc(b
.split('\t')[2]).lower()
308 elif self
._current
_sort
== 'num' :
310 da
= int(a
.split('\t')[3])
311 db
= int(b
.split('\t')[3])
319 def _set_current_sort_(self
, arg
) :
320 self
._current
_sort
= arg
324 def _check_dbl_click_(self
, arg
) :
325 if pygame
.time
.get_ticks() - self
._last
_time
_click
< 300 :
326 self
._button
_okay
_clicked
_(None)
328 self
._last
_time
_click
= pygame
.time
.get_ticks()
331 FileDialog
.event(self
, e
)
333 if e
.type == CLICK
and \
335 self
.list.rect
.collidepoint(e
.pos
) :
336 self
._check
_dbl
_click
_(e
)
338 if e
.type == KEYDOWN
and e
.key
== K_RETURN
:
339 self
._button
_okay
_clicked
_(None)
343 from unicodedata
import decomposition
344 from string
import printable
345 _printable
= dict([(c
, True) for c
in printable
])
346 isPrintable
= _printable
.has_key
348 def _recurseDecomposition(uc
):
349 deco
= decomposition(uc
).split()
354 if code
.startswith('<') :
356 c
= unichr(int(code
, 16))
357 subDeco
= decomposition(c
).split()
366 fullDeco
= u
''.join(filter(lambda c
: isPrintable(c
), fullDeco
))
369 def desacc(s
, encoding
='iso-8859-1') :
370 us
= s
.decode(encoding
, 'ignore')
373 ret
.append(_recurseDecomposition(uc
))