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
25 from xml
.etree
import ElementTree
26 from minwii
.musicxml
import musicXml2Song
28 INDEX_TXT
= 'index.txt'
29 PICTURE_ITEM_SIZE
= 64
31 def appEventFactory(app
, dlg
) :
32 # monkey patch de la méthode gestionnaire d'événements :
33 # l'ensemble du Desktop écoute les événements de la roulette de la souris
34 # et les redirige sur la liste déroulante.
35 def _appEvent(self
, e
) :
37 if dlg
.list.vscrollbar
:
38 if not hasattr(dlg
.list.vscrollbar
,'value'):
41 if e
.type == pygame
.locals.MOUSEBUTTONDOWN
:
42 if e
.button
== 4: #wheel up
43 dlg
.list.vscrollbar
._click
(-1)
45 elif e
.button
== 5: #wheel down
46 dlg
.list.vscrollbar
._click
(1)
48 return Desktop
.event(self
, e
)
50 return types
.MethodType(_appEvent
, app
)
52 class FileOpenDialog(FileDialog
):
56 def __init__(self
, path
):
58 if not path
: self
.curdir
= os
.getcwd()
59 else: self
.curdir
= path
60 self
.dir_img
= basic
.Image(
61 pguglobals
.app
.theme
.get(cls1
+'.folder', '', 'image'))
62 self
.soundfile_img
= basic
.Image(
63 pguglobals
.app
.theme
.get(cls1
+'.soundfile', '', 'image'))
64 td_style
= {'padding_left': 4,
68 self
.title
= basic
.Label("Ouvrir une chanson", cls
="dialog.title.label")
69 self
.body
= table
.Table()
70 self
.list = area
.List(width
=880, height
=375)
71 self
.input_dir
= input.Input()
72 self
.input_file
= input.Input()
73 self
._current
_sort
= 'alpha'
75 self
.button_ok
= button
.Button("Ouvrir")
76 self
.button_sort_alpha
= button
.Button("A-Z")
77 self
.button_sort_alpha
.connect(CLICK
, self
._set
_current
_sort
_, 'alpha')
78 self
.button_sort_num
= button
.Button("0-9")
79 self
.button_sort_num
.connect(CLICK
, self
._set
_current
_sort
_, 'num')
81 self
.body
.td(basic
.Label("Dossier"), style
=td_style
, align
=-1)
82 self
.body
.td(self
.input_dir
, style
=td_style
)
83 self
.body
.td(self
.button_sort_alpha
)
84 self
.body
.td(self
.button_sort_num
)
86 self
.body
.td(self
.list, colspan
=4, style
=td_style
)
87 self
.list.connect(CHANGE
, self
._item
_select
_changed
_, None)
88 #self.list.connect(CLICK, self._check_dbl_click_, None)
89 self
._last
_time
_click
= pygame
.time
.get_ticks()
90 self
.button_ok
.connect(CLICK
, self
._button
_okay
_clicked
_, None)
92 self
.body
.td(basic
.Label("Fichier"), style
=td_style
, align
=-1)
93 self
.body
.td(self
.input_file
, style
=td_style
)
94 self
.body
.td(self
.button_ok
, style
=td_style
, colspan
=2)
96 Dialog
.__init
__(self
, self
.title
, self
.body
)
100 self
.__regularEventMethod
= app
.event
101 app
.event
= appEventFactory(app
, self
)
103 def close(self
, w
=None) :
104 FileDialog
.close(self
, w
)
105 # retrait du monkey patch
107 app
.event
= self
.__regularEventMethod
110 def _list_dir_(self
):
111 self
.input_dir
.value
= self
.curdir
112 self
.input_dir
.pos
= len(self
.curdir
)
113 self
.input_dir
.vpos
= 0
117 for i
in os
.listdir(self
.curdir
):
118 if i
.startswith('.') : continue
119 if os
.path
.isdir(os
.path
.join(self
.curdir
, i
)): dirs
.append(i
)
120 else: files
.append(i
)
122 self
.input_file
.value
= "Dossier innacessible !"
129 self
.list.add(i
, image
=self
.dir_img
, value
=i
)
133 if not i
.endswith('.xml') :
135 filepath
= os
.path
.join(self
.curdir
, i
)
136 xmlFiles
.append(filepath
)
139 printableLines
= self
.getPrintableLines(xmlFiles
)
140 for l
in printableLines
:
141 imgpath
= os
.path
.splitext(os
.path
.join(self
.curdir
, l
[1]))[0] + '.jpg'
142 if os
.path
.exists(imgpath
) :
143 img
= pygame
.image
.load(imgpath
)
144 iw
, ih
= img
.get_width(), img
.get_height()
147 style
['width'] = PICTURE_ITEM_SIZE
148 style
['height'] = int(round(PICTURE_ITEM_SIZE
* float(ih
) / iw
))
150 style
['heigth'] = PICTURE_ITEM_SIZE
151 style
['width'] = int(round(PICTURE_ITEM_SIZE
* float(iw
) / ih
))
153 img
= basic
.Image(img
, style
=style
)
155 img
= self
.soundfile_img
156 self
.list.add(l
[0], value
= l
[1], image
= img
)
158 self
.list.set_vertical_scroll(0)
160 def getPrintableLines(self
, xmlFiles
) :
161 index
= self
.getUpdatedIndex(xmlFiles
)
167 printableLines
.append(('%s - %s / %s' % (l
[2], l
[3], l
[4]), l
[0]))
169 return printableLines
173 def getSongTitle(file) :
174 it
= ElementTree
.iterparse(file, ['start', 'end'])
176 title
= os
.path
.basename(file)
179 if el
.tag
== 'credit' :
181 if el
.tag
== 'credit-words' and creditFound
:
184 if el
.tag
== 'part-list' :
185 # au delà de ce tag : aucune chance de trouver un titre
190 def getSongMetadata(file) :
192 metadata
['title'] = FileOpenDialog
.getSongTitle(file).encode('iso-8859-1')
193 metadata
['mtime'] = str(os
.stat(file).st_mtime
)
194 metadata
['file'] = os
.path
.basename(file)
195 song
= musicXml2Song(file)
196 metadata
['distinctNotes'] = len(song
.distinctNotes
)
198 histo
= song
.intervalsHistogram
199 coeffInter
= reduce(lambda a
, b
: a
+ b
,
200 [abs(k
) * v
for k
, v
in histo
.items()])
202 totInter
= reduce(lambda a
, b
: a
+b
, histo
.values())
203 totInter
= totInter
- histo
.get(0, 0)
204 difficulty
= int(round(float(coeffInter
) / totInter
, 0))
205 metadata
['difficulty'] = difficulty
209 def getUpdatedIndex(self
, xmlFiles
) :
210 indexTxtPath
= os
.path
.join(self
.curdir
, INDEX_TXT
)
213 if not os
.path
.exists(indexTxtPath
) :
214 musicXmlFound
= False
215 tmp
= tempfile
.TemporaryFile(mode
='r+')
216 for file in xmlFiles
:
218 metadata
= FileOpenDialog
.getSongMetadata(file)
220 except ValueError, e
:
222 if e
.args
and e
.args
[0] == 'not a musicxml file' :
225 line
= '%(file)s\t%(mtime)s\t%(title)s\t%(distinctNotes)d\t%(difficulty)d\n' % metadata
231 indexFile
= open(indexTxtPath
, 'w')
232 indexFile
.write(tmp
.read())
237 indexTxt
= open(indexTxtPath
, 'r')
239 # check if index is up to date, and update entries if so.
240 for l
in filter(None, indexTxt
.readlines()) :
241 parts
= l
.split('\t')
242 fileBaseName
, modificationTime
= parts
[0], parts
[1]
243 filePath
= os
.path
.join(self
.curdir
, fileBaseName
)
245 if not os
.path
.exists(filePath
) :
248 indexedFiles
[fileBaseName
] = l
249 currentMtime
= str(os
.stat(filePath
).st_mtime
)
251 # check modification time missmatch
252 if currentMtime
!= modificationTime
:
254 metadata
= FileOpenDialog
.getSongMetadata(filePath
)
256 except ValueError, e
:
258 if e
.args
and e
.args
[0] == 'not a musicxml file' :
261 metadata
= FileOpenDialog
.getSongMetadata(filePath
)
262 line
= '%(file)s\t%(mtime)s\t%(title)s\t%(distinctNotes)d\t%(difficulty)d\n' % metadata
263 indexedFiles
[fileBaseName
] = line
265 # check for new files.
266 for file in xmlFiles
:
267 fileBaseName
= os
.path
.basename(file)
268 if not indexedFiles
.has_key(fileBaseName
) :
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(file)
278 line
= '%(file)s\t%(mtime)s\t%(title)s\t%(distinctNotes)d\t%(difficulty)d\n' % metadata
279 indexedFiles
[fileBaseName
] = line
281 # ok, the index is up to date !
283 index
= indexedFiles
.values()
286 if self
._current
_sort
== 'alpha' :
288 da
= desacc(a
.split('\t')[2]).lower()
289 db
= desacc(b
.split('\t')[2]).lower()
292 elif self
._current
_sort
== 'num' :
294 da
= int(a
.split('\t')[3])
295 db
= int(b
.split('\t')[3])
303 def _set_current_sort_(self
, arg
) :
304 self
._current
_sort
= arg
308 def _check_dbl_click_(self
, arg
) :
309 if pygame
.time
.get_ticks() - self
._last
_time
_click
< 300 :
310 self
._button
_okay
_clicked
_(None)
312 self
._last
_time
_click
= pygame
.time
.get_ticks()
315 FileDialog
.event(self
, e
)
317 if e
.type == CLICK
and \
319 self
.list.rect
.collidepoint(e
.pos
) :
320 self
._check
_dbl
_click
_(e
)
322 if e
.type == KEYDOWN
and e
.key
== K_RETURN
:
323 self
._button
_okay
_clicked
_(None)
327 from unicodedata
import decomposition
328 from string
import printable
329 _printable
= dict([(c
, True) for c
in printable
])
330 isPrintable
= _printable
.has_key
332 def _recurseDecomposition(uc
):
333 deco
= decomposition(uc
).split()
338 if code
.startswith('<') :
340 c
= unichr(int(code
, 16))
341 subDeco
= decomposition(c
).split()
350 fullDeco
= u
''.join(filter(lambda c
: isPrintable(c
), fullDeco
))
353 def desacc(s
, encoding
='iso-8859-1') :
354 us
= s
.decode(encoding
, 'ignore')
357 ret
.append(_recurseDecomposition(uc
))