X-Git-Url: https://scm.cri.ensmp.fr/git/minwii.git/blobdiff_plain/dec65f326821db9871750e207cdd5c430632f3b3..128d1283372aa86d58d346569a08a97d64f5acf6:/src/minwii/widgets/songfilebrowser.py diff --git a/src/minwii/widgets/songfilebrowser.py b/src/minwii/widgets/songfilebrowser.py index 99f81da..4c5b55c 100755 --- a/src/minwii/widgets/songfilebrowser.py +++ b/src/minwii/widgets/songfilebrowser.py @@ -6,21 +6,108 @@ $Id$ $URL$ """ +import pygame +from pygame.locals import K_RETURN from pgu.gui import FileDialog +import pgu.gui.basic as basic +import pgu.gui.input as input +import pgu.gui.button as button +import pgu.gui.pguglobals as pguglobals +import pgu.gui.table as table +import pgu.gui.area as area +from pgu.gui.const import * +from pgu.gui.dialog import Dialog +from pgu.gui.app import Desktop +import types +from datetime import timedelta + import os +import tempfile from xml.etree import ElementTree +from minwii.musicxml import musicXml2Song + +INDEX_TXT = 'index.txt' +PICTURE_ITEM_SIZE = 64 + +def appEventFactory(app, dlg) : + # monkey patch de la méthode gestionnaire d'événements : + # l'ensemble du Desktop écoute les événements de la roulette de la souris + # et les redirige sur la liste déroulante. + def _appEvent(self, e) : + + if dlg.list.vscrollbar: + if not hasattr(dlg.list.vscrollbar,'value'): + return False + + if e.type == pygame.locals.MOUSEBUTTONDOWN: + if e.button == 4: #wheel up + dlg.list.vscrollbar._click(-1) + return True + elif e.button == 5: #wheel down + dlg.list.vscrollbar._click(1) + return True + return Desktop.event(self, e) + + return types.MethodType(_appEvent, app) class FileOpenDialog(FileDialog): def __init__(self, path): - FileDialog.__init__(self, - title_txt="Ouvrir une chanson", - button_txt="Ouvrir", - path=path, - ) + cls1 = 'filedialog' + if not path: self.curdir = os.getcwd() + else: self.curdir = path + self.dir_img = basic.Image( + pguglobals.app.theme.get(cls1+'.folder', '', 'image')) + self.soundfile_img = basic.Image( + pguglobals.app.theme.get(cls1+'.soundfile', '', 'image')) + td_style = {'padding_left': 4, + 'padding_right': 4, + 'padding_top': 2, + 'padding_bottom': 2} + self.title = basic.Label("Ouvrir une chanson", cls="dialog.title.label") + self.body = table.Table() + self.list = area.List(width=880, height=375) + self.input_dir = input.Input() + self.input_file = input.Input() + self._current_sort = 'alpha' + self._list_dir_() + self.button_ok = button.Button("Ouvrir") + self.button_sort_alpha = button.Button("A-Z") + self.button_sort_alpha.connect(CLICK, self._set_current_sort_, 'alpha') + self.button_sort_num = button.Button("0-9") + self.button_sort_num.connect(CLICK, self._set_current_sort_, 'num') + self.body.tr() + self.body.td(basic.Label("Dossier"), style=td_style, align=-1) + self.body.td(self.input_dir, style=td_style) + self.body.td(self.button_sort_alpha) + self.body.td(self.button_sort_num) + self.body.tr() + self.body.td(self.list, colspan=4, style=td_style) + self.list.connect(CHANGE, self._item_select_changed_, None) + #self.list.connect(CLICK, self._check_dbl_click_, None) + self._last_time_click = pygame.time.get_ticks() + self.button_ok.connect(CLICK, self._button_okay_clicked_, None) + self.body.tr() + self.body.td(basic.Label("Fichier"), style=td_style, align=-1) + self.body.td(self.input_file, style=td_style) + self.body.td(self.button_ok, style=td_style, colspan=2) + self.value = None + Dialog.__init__(self, self.title, self.body) + + # monkey patch + app = pguglobals.app + self.__regularEventMethod = app.event + app.event = appEventFactory(app, self) + def close(self, w=None) : + FileDialog.close(self, w) + # retrait du monkey patch + app = pguglobals.app + app.event = self.__regularEventMethod + + def _list_dir_(self): self.input_dir.value = self.curdir self.input_dir.pos = len(self.curdir) @@ -41,25 +128,247 @@ class FileOpenDialog(FileDialog): files.sort() for i in dirs: self.list.add(i, image=self.dir_img, value=i) - + + xmlFiles = [] for i in files: - if not i.endswith('.xml') : + if not i.endswith('.xml') : continue filepath = os.path.join(self.curdir, i) - self.list.add(FileOpenDialog.getSongTitle(filepath), value=i) + xmlFiles.append(filepath) + + if xmlFiles : + printableLines = self.getPrintableLines(xmlFiles) + for l in printableLines : + imgpath = os.path.splitext(os.path.join(self.curdir, l[1]))[0] + '.jpg' + if os.path.exists(imgpath) : + img = pygame.image.load(imgpath) + iw, ih = img.get_width(), img.get_height() + style = {} + if iw > ih : + style['width'] = PICTURE_ITEM_SIZE + style['height'] = int(round(PICTURE_ITEM_SIZE * float(ih) / iw)) + else : + style['heigth'] = PICTURE_ITEM_SIZE + style['width'] = int(round(PICTURE_ITEM_SIZE * float(iw) / ih)) + + img = basic.Image(img, style=style) + else : + img = self.soundfile_img + self.list.add(l[0], value = l[1], image = img) self.list.set_vertical_scroll(0) + def getPrintableLines(self, xmlFiles) : + index = self.getUpdatedIndex(xmlFiles) + + printableLines = [] + for l in index : + l = l.strip() + l = l.split('\t') + printableLines.append(('%s - %s / %s' % (l[2], l[3], l[4]), l[0])) + + return printableLines + + @staticmethod def getSongTitle(file) : it = ElementTree.iterparse(file, ['start', 'end']) creditFound = False + title = os.path.basename(file) for evt, el in it : if el.tag == 'credit' : creditFound = True if el.tag == 'credit-words' and creditFound: - return el.text.encode('iso-8859-1') + title = el.text + break if el.tag == 'part-list' : - # plus de chance de trouver un titre - return os.path.basename(file) \ No newline at end of file + # au delà de ce tag : aucune chance de trouver un titre + break + return title + + @staticmethod + def getSongMetadata(file) : + metadata = {} + metadata['title'] = FileOpenDialog.getSongTitle(file).encode('iso-8859-1') + metadata['mtime'] = str(os.stat(file).st_mtime) + metadata['file'] = os.path.basename(file) + song = musicXml2Song(file) + metadata['distinctNotes'] = len(song.distinctNotes) + + duration = song.duration / 1000. + duration = int(round(duration, 0)) + duration = timedelta(seconds=duration) + try : + duration = str(duration) # p.ex. 0:03:05 + duration = duration.split(':') + h, m, s = [int(n) for n in duration] + if h : raise ValueError(h) + duration = ':'.join([str(n).zfill(2) for n in (m, s)]) + except : + raise + duration = srt(duration) + + metadata['duration'] = duration + + # histo = song.intervalsHistogram + # coeffInter = reduce(lambda a, b : a + b, + # [abs(k) * v for k, v in histo.items()]) + # + # totInter = reduce(lambda a, b: a+b, histo.values()) + # totInter = totInter - histo.get(0, 0) + # difficulty = int(round(float(coeffInter) / totInter, 0)) + # metadata['difficulty'] = difficulty + + return metadata + + def getUpdatedIndex(self, xmlFiles) : + indexTxtPath = os.path.join(self.curdir, INDEX_TXT) + index = [] + + if not os.path.exists(indexTxtPath) : + musicXmlFound = False + tmp = tempfile.TemporaryFile(mode='r+') + for file in xmlFiles : + try : + metadata = FileOpenDialog.getSongMetadata(file) + musicXmlFound = True + except ValueError, e : + print e + if e.args and e.args[0] == 'not a musicxml file' : + continue + + line = '%(file)s\t%(mtime)s\t%(title)s\t%(distinctNotes)d\t%(duration)s\n' % metadata + index.append(line) + tmp.write(line) + + if musicXmlFound : + tmp.seek(0) + indexFile = open(indexTxtPath, 'w') + indexFile.write(tmp.read()) + indexFile.close() + tmp.close() + else : + indexedFiles = {} + indexTxt = open(indexTxtPath, 'r') + + # check if index is up to date, and update entries if so. + for l in filter(None, indexTxt.readlines()) : + parts = l.split('\t') + fileBaseName, modificationTime = parts[0], parts[1] + filePath = os.path.join(self.curdir, fileBaseName) + + if not os.path.exists(filePath) : + continue + + indexedFiles[fileBaseName] = l + currentMtime = str(os.stat(filePath).st_mtime) + + # check modification time missmatch + if currentMtime != modificationTime : + try : + metadata = FileOpenDialog.getSongMetadata(filePath) + musicXmlFound = True + except ValueError, e : + print e + if e.args and e.args[0] == 'not a musicxml file' : + continue + + metadata = FileOpenDialog.getSongMetadata(filePath) + line = '%(file)s\t%(mtime)s\t%(title)s\t%(distinctNotes)d\t%(duration)s\n' % metadata + indexedFiles[fileBaseName] = line + + # check for new files. + for file in xmlFiles : + fileBaseName = os.path.basename(file) + if not indexedFiles.has_key(fileBaseName) : + try : + metadata = FileOpenDialog.getSongMetadata(filePath) + musicXmlFound = True + except ValueError, e : + print e + if e.args and e.args[0] == 'not a musicxml file' : + continue + + metadata = FileOpenDialog.getSongMetadata(file) + line = '%(file)s\t%(mtime)s\t%(title)s\t%(distinctNotes)d\t%(duration)s\n' % metadata + indexedFiles[fileBaseName] = line + + # ok, the index is up to date ! + + index = indexedFiles.values() + + + if self._current_sort == 'alpha' : + def s(a, b) : + da = desacc(a.split('\t')[2]).lower() + db = desacc(b.split('\t')[2]).lower() + return cmp(da, db) + + elif self._current_sort == 'num' : + def s(a, b) : + da = int(a.split('\t')[3]) + db = int(b.split('\t')[3]) + return cmp(da, db) + else : + s = cmp + + index.sort(s) + return index + + def _set_current_sort_(self, arg) : + self._current_sort = arg + self.list.clear() + self._list_dir_() + + def _check_dbl_click_(self, arg) : + if pygame.time.get_ticks() - self._last_time_click < 300 : + self._button_okay_clicked_(None) + else : + self._last_time_click = pygame.time.get_ticks() + + def event(self, e) : + FileDialog.event(self, e) + + if e.type == CLICK and \ + e.button == 1 and \ + self.list.rect.collidepoint(e.pos) : + self._check_dbl_click_(e) + + if e.type == KEYDOWN and e.key == K_RETURN : + self._button_okay_clicked_(None) + + +# utils +from unicodedata import decomposition +from string import printable +_printable = dict([(c, True) for c in printable]) +isPrintable = _printable.has_key + +def _recurseDecomposition(uc): + deco = decomposition(uc).split() + fullDeco = [] + if deco : + while (deco) : + code = deco.pop() + if code.startswith('<') : + continue + c = unichr(int(code, 16)) + subDeco = decomposition(c).split() + if subDeco : + deco.extend(subDeco) + else : + fullDeco.append(c) + fullDeco.reverse() + else : + fullDeco.append(uc) + + fullDeco = u''.join(filter(lambda c : isPrintable(c), fullDeco)) + return fullDeco + +def desacc(s, encoding='iso-8859-1') : + us = s.decode(encoding, 'ignore') + ret = [] + for uc in us : + ret.append(_recurseDecomposition(uc)) + return u''.join(ret) \ No newline at end of file