Outils pour utilisateurs

Outils du site


issue49:tutopython

This time, we are going to finish our playlistmaker program. Last time, we got a good bit done, but we left some things incomplete. We can't save the playlist, we don't have the movement functions done, we can't select the file path to store the file in, and so on. However, there are a few things that we need to do before we start coding. First, we need to find an image for the logo for our application in the about box, and for when the application is minimized. You can dig around in the /usr/share/icons folder for an icon you like, or you can go on the web and get one, or create one yourself. Whatever you get, put it into your code folder with the glade file and the source code from last month. Name it logo.png. Next, we need to open the glade file from last month and make a few changes. First, using the MainWindow, go to the General tab, and scroll down until you find Icon. Using the browse tool, find your icon and select that. Now the text box should contain “logo.png”. Next, in the hierarchy box, select treeview1, go to the signal tab, and, under GtkTreeView | cursor-changed, add a handler for on_treeview1_cursor_changed. Remember, as I told you last month, to click off that to make the change stick. Finally, again in the hierarchy box, select txtFilename, and go to the signal tab. Scroll down until you find 'GtkWidget', and scroll down further until you get to 'key-press-event'. Add a handler for 'on_txtFilename_key_press_event'. Save your glade project and close glade.

Cette fois-ci, nous allons terminer notre programme de création de liste de lecture. La dernière fois, nous avions bien avancé, mais nous n'avons pas terminé certaines parties. Nous ne pouvons pas encore sauvegarder la liste de lecture, les fonctions de déplacement ne sont pas implémentées, nous ne pouvons pas choisir le chemin vers lequel on veut sauvegarder, etc. Cependant, nous devons faire certaines choses avant de commencer à coder. Tout d'abord, nous devons trouver une image pour le logo de notre application dans la boîte « À propos » et lorsque l'application est minimisée. Vous pouvez chercher une icône qui vous plaît dans le répertoire /usr/share/icons, ou aller sur le web en chercher une ou encore en créer une vous-même. Quel que soit le choix, placez cette image dans le répertoire contenant le code source et le fichier glade du mois dernier. Nommez-la logo.png. Ensuite, nous devons ouvrir le fichier glade du mois dernier et faire quelques changements.

Tout d'abord, avec la FenetrePrincipale, allez dans l'onglet Général et descendez jusqu'à trouver Icône. En utilisant l'outil de parcours de fichiers, trouvez votre icône et sélectionnez-la. Maintenant le champ de texte devrait contenir “logo.png”. Puis, dans la boîte de hiérarchie, choisissez treeview1, allez dans l'onglet Signaux et ajoutez un gestionnaire pour on_treeview1_cursor_changed dans la partie GtkTreeView | cursor-changed. Souvenez-vous que nous avons vu le mois dernier que vous devez cliquer à côté pour conserver vos modifications. Enfin, toujours dans la boîte hiérarchie, choisissez txtNomFichier et allez dans l'onglet Signaux. Descendez jusqu'à trouver GtkWidget et descendez encore jusqu'à key-press-event. Ajoutez un gestionnaire d'événement pour on_txtNomFichier_key_press_event. Sauvegardez votre projet glade et fermez glade.

Now it's time to complete our project. We'll start from where we left off using last month's code. The first thing I want to do is modify the code in class DialogueFichier. If you remember from last time, if the user clicked the 'Cancel' button, there was an error raised. We will fix that first. At the end of the routine, you have the code shown above. You might imagine, this simply looks at the value of each key that is pressed when the user is in the txtFilename text box, and compares it to the value 65293, which is the code that is assigned to the return key (enter key). If it matches, then it calls the SavePlaylist function. The user doesn't have to even click the button.

Maintenant il est temps de terminer notre projet. Nous commencerons à coder là où nous en étions resté le mois dernier.

La première chose que je veux faire est modifier le code de la classe DialogueFichier. Si vous vous souvenez de la dernière fois, si l'utilisateur cliquait le bouton Annuler, il se produisait une erreur. Nous allons commencer par corriger ça. À la fin de la routine, vous avez le code ci-dessus.

Comme vous pouvez le supposer, cela regarde simplement la valeur de chaque touche enfoncée lorsque l'utilisateur se trouve dans le champ de texte txtNomFichier et la compare à la valeur 65293, qui est le code attribué à la touche Entrée. Si cela correspond, alors il appelle la fonction SauvegarderListe. L'utilisateur n'a même pas besoin de cliquer sur le bouton.

Now on to new code. Let's deal with the toolbar button ClearAll. When the user clicks this button, we want the treeview and the ListStore to be cleared. This is a simple one-liner that we can put into the on_tbtnClearAll_clicked routine. def on_tbtnClearAll_clicked(self,widget): self.playList.clear() We are simply telling the playList ListStore to clear itself. That was easy. Now we'll deal with the Delete toolbar button. Much harder, but once we get into it, you'll understand. First we have to discuss how we get a selection from the treeview widget and the ListStore. This is complicated, so go slowly. In order to get data back from the ListStore, we first have to get a gtk.TreeSelection which is a helper object that manages the selection within a treeview. Then we use that helper object to retrieve the model type, and an iterator that contains the selected rows.

Maintenant passons au code. Occupons-nous du bouton Effacer de la barre d'outils. Lorsque l'utilisateur clique sur ce bouton, on veut effacer la liste arborescente et ListStore. Cela se fait en une ligne, que l'on peut placer dans la routine on_boBtnEffacer_clicked.

def on_boBtnEffacer_clicked(self,widget):

 self.playList.clear()

Nous disons simplement à la liste de lecture ListStore de s'effacer. C'était facile. Maintenant occupons-nous du bouton Supprimer de la barre d'outils. C'est plus difficile, mais une fois terminé vous allez comprendre.

D'abord nous devons parler de la façon dont nous récupérons une sélection depuis la liste arborescente et ListStore. C'est un peu compliqué, alors allons doucement. Pour récupérer des données depuis ListStore, nous devons d'abord récupérer un objet gtk.TreeSelection qui nous aidera à gérer la sélection à l'intérieur d'un treeview. Ensuite, on utilise cet objet pour récupérer le type de modèle et un itérateur qui contient les lignes sélectionnées.

I know that you are thinking “What the heck is an iterator?” Well you already have used them and don't even know it. Think about the following code (above right) from the AddFilesToTreeview function from last month. Look at the 'for' statement portion. We use an iterator to walk through the list called FileList. Basically, in this case, the iterator simply goes through each entry in the list returning each item separately. What we are going to do is create an iterator, fill that with the selected rows in the treeview, and use that like a list. So the code (middle right) for on_tbtnDelete_clicked will be. The first line creates the TreeSelection object. We use that to get the rows selected (which is only one because we didn't set the model to support multiple selections), fill that into a list called iters, and then walk it removing (like the .clear method). We also decrement the variable RowCount, and then display the number of files in the status bar.

Je sais que vous pensez : « Mais bon sang, qu'est-ce qu'un itérateur ? ». Eh bien, vous en avez déjà utilisé sans même le savoir. Regardez le code suivant (ci-dessus à droite) provenant de la fonction AjouterFichiers du mois dernier.

Regardez la boucle for. On utilise un itérateur pour parcourir la liste ListeFichiers. Dans ce cas, l'itérateur passe tout simplement d'une entrée de la liste à la suivante, renvoyant chaque élément séparément. Nous allons créer un itérateur, le remplir avec les lignes de la vue arborescente sélectionnées et l'utiliser comme une liste. Voici donc le code (au milieu à droite) pour on_boBtnSupprimer.

La première ligne crée l'objet TreeSelection. On l'utilise pour récupérer les lignes sélectionnées (il n'y en a qu'une car notre modèle n'est pas réglé pour offrir la sélection multiple), remplir une liste nommée iter avec, et la parcourir en enlevant chaque élément (comme la méthode .clear). On décrémente également la variable NombreDeLignes, puis on affiche le nombre de fichiers dans la barre d'état.

Now, before we get to the move functions, let's deal with the save-file-path function. We'll use our FileDialog class as before. We'll do all the code (bottom right)for this in the on_btnGetFolder_clicked routine. The only thing really different from before is the last line of this code. We are putting the name of the path returned by the FileDialog into the textbox that we set up previously using the set_text method. Remember that the data returned to us is in the form of a list, even though there is only one entry. That's why we use 'filepath[0]'. Let's do the file-save function. We can safely do that before we deal with the move functions. We'll create a function called SavePlaylist. The first thing we need to do (above right) is check to see if there is anything in the txtPath text box. Next we need to check to see if there is a filename in the txtFilename text box. For both of those instances, we use the .get_text() method of the text box.

Maintenant, avant de passer aux fonctions de déplacement, occupons-nous de la fonction de sauvegarde du chemin des fichiers. On utilisera notre classe DialogueFichier comme précédemment. On placera tout le code pour faire cela (en bas à droite) dans la routine on_boBtnNomRepertoire_clicked.

La seule chose vraiment différente par rapport à avant est la dernière ligne de ce code. On place le nom du chemin retourné par la fenêtre de dialogue dans le champ de texte que l'on a précédemment initialisé avec la méthode set_text. Souvenez-vous que les données nous sont renvoyées sous forme de liste, même s'il n'y a qu'un seul élément. C'est pourquoi on utilise chemin[0].

Écrivons la fonction de sauvegarde de fichier. On peut faire ça avant de passer aux fonctions de déplacement. Nous allons créer une fonction SauvegarderListe. La première chose à faire (ci-dessus à droite) est de vérifier s'il y a quelque chose dans le champ de texte txtChemin. Ensuite nous devons vérifier s'il y a un nom de fichier dans le champ de texte txtNomFichier. Pour ces deux valeurs, on utilise la méthode get_text() du champ de texte.

Now that we know that we have a path (fp) and a filename (fn), we can open the file, print our M3U header, and walk the playList. The path is stored (if you will remember) in column 2, the filename in column 0, and the extension in column 1. We simply (right) create a string and then write it to the file and finally close the file. We can now start work on the move functions. Let's start with the Move To Top routine. Like we did when we wrote the delete function, we get the selection and then the selected row. Next we have to step through the rows to get two variables. We will call them path1 and path2. Path2, in this case will be set to 0, which is the “target” row. Path1 is the row the user has selected. We finally use the model.move_before() method to move the selected row up to row 0, effectively pushing everything down. We'll put the code (below right) directly in the on_tbtnMoveToTop_clicked routine.

Maintenant que l'on a un chemin (cf) et un nom de fichier (nf), on peut ouvrir le fichier, imprimer notre en-tête M3U et parcourir la liste de lecture. Le chemin est stocké (si vous vous souvenez) dans la colonne 2, le nom du fichier dans la colonne 0 et l'extension dans la colonne 1. On crée simplement (à droite) une chaîne, puis on l'écrit dans le fichier et enfin on ferme le fichier.

On peut maintenant commencer à travailler sur les fonctions de déplacement. Commençons par la routine Haut. Comme nous l'avons fait en écrivant la fonction Supprimer, on récupère la sélection puis la ligne sélectionnée. Ensuite on doit parcourir les lignes pour récupérer 2 variables. Nous les appellerons chemin1 et chemin2. chemin2 sera réglé à 0 dans ce cas, car c'est la ligne de « destination ». chemin1 est la ligne que l'utilisateur a sélectionnée. On utilise enfin la méthode modele.move_before() pour déplacer la ligne sélectionnée sur la ligne 0, en poussant d'office tout vers le bas. Nous placerons le code (ci-dessous à droite) directement dans la routine on_boBtnHaut_clicked.

For the MoveToBottom function, we will use almost exactly the same code as the MoveToTop routine, but, in place of the model.move_before() method, we will use the model.move_after() method, and, instead of setting path2 to 0, we set it to self.RowCount-1. Now you understand why we have a RowCount variable. Remember the counts are zero based, so we have to use RowCount-1 (above right). Now let's take a look at what it will take to do the MoveUp routine. Once again, it is fairly similar to the last two functions we created. This time, we get path1 which is the selected row and then assign that row number–1 to path2. Then IF path2 (the target row) is greater than or equal to 0, we use the model.swap() method (second down, right).

Pour la fonction Bas, nous utiliserons presque le même code que pour la routine Haut, mais au lieu d'utiliser la méthode modele.moveBefore(), nous utiliserons la méthode modele.moveAfter() et, au lieu de régler chemin2 à 0, on le réglera à self.NombreDeLignes-1. Maintenant vous comprenez à quoi sert la variable NombreDeLignes. Souvenez-vous que les lignes sont numérotées à partir de 0, donc il faut utiliser NombreDeLignes-1 (ci-dessus à droite).

Maintenant regardons ce que donne la fonction Monter. À nouveau, elle est très ressemblante aux deux fonctions que nous venons de créer. Cette fois-ci, on a chemin1 qui contient la ligne sélectionnée, et on règle chemin2 à NumeroLigne-1. Ensuite, SI chemin2 (la ligne de destination) est supérieur ou égal à 0, on utilise la méthode modele.swap() (deuxième en bas à droite).

The same thing applies for the MoveDown function. This time however, we check to see if path2 is LESS than or equal to the value of self.RowCount-1 (third down, right). Now let's make some changes to the abilities of our play list. In last month's article, I showed you the basic format of the play list file (bottom). However, I did say that there was an extended format as well. In the extended format, there is an extra line that can be added to the file before each song file entry that contains extra information about the song. The format of this line is as follows… #EXTINF:[Length of song in seconds],[Artist Name] – [Song Title]

C'est la même chose pour la fonction Descendre. Cette fois-ci, on vérifie que chemin2 est plus PETIT ou égal à self.NombreDeLignes-1 (troisième en bas à droite).

Maintenant, modifions quelques fonctionnalités de notre liste de lecture. Dans l'article du mois dernier, je vous ai montré le format de base d'une liste de lecture (en bas).

Cependant, je vous ai indiqué qu'il y avait aussi un format étendu. Dans le format étendu, il y a une ligne supplémentaire que l'on peut ajouter au fichier avant chaque chanson, contenant des informations supplémentaires sur la chanson. Le format de cette ligne est le suivant :

#EXTINF:[longueur de la chanson en secondes],[Nom de l'artiste] – [Titre de la chanson]

You might have wondered why we included the mutagen library from the beginning since we never used it. Well, we will now. To refresh your memory, the mutagen library is for accessing ID3 tag information from inside of MP3 files. To get the full discussion about this, please refer to issue 35 of Full Circle which has my part 9 of this series. We'll create a function to deal with the reading of the MP3 file and return the Artist name, the Song Title, and the length of the song in seconds, which are the three things we need for the extended information line. Put the function after the ShowAbout function within the PlaylistCreator class (next page, top right). Again, to refresh your memory, I'll walk through the code. First we clear the three return variables so that if anything happens they are blank upon return. We then pass in the filename of the MP3 file we are going to look at. Next we pull the keys into (yes, you guessed it) an iterator, and walk through that iterator looking for two specific tags. They are 'TPE1' which is the artist name, and 'TIT2' which is the song title. Now, if the key doesn't exist, we would get an error, so we wrap each get call with a 'try|except' statement. We then pull the song length from the audio.info.length attribute, and return the whole shebang.

Vous vous demandiez peut-être pourquoi on a inclus la bibliothèque mutagen depuis le début alors qu'on ne l'a jamais utilisée. Eh bien, nous allons l'utiliser maintenant. Pour vous rafraîchir la mémoire, la bibliothèque mutagen permet d'avoir accès aux informations des balises ID3 des fichiers MP3. Pour lire la discussion complète là-dessus, reportez-vous au numéro 35 du Full Circle qui contient la partie 9 de cette série. Nous créerons une fonction pour gérer la lecture d'un fichier MP3 et renvoyer le nom de l'artiste, le titre de la chanson et sa longueur en secondes, qui sont les trois informations dont nous avons besoin pour la ligne des informations étendues. Placez cette fonction après la fonction APropos dans la classe CreateurListeDeLecture (page suivante, en haut à droite).

À nouveau, pour vous rafraîchir la mémoire, je vais parcourir le code. Tout d'abord nous effaçons les trois variables de retour pour qu'elles soient renvoyées vides si quelque chose se passe de travers. Ensuite on passe le nom du fichier MP3 que nous allons examiner. Puis on place les clés dans (vous l'avez deviné) un itérateur et on parcourt cet itérateur en cherchant les deux balises spécifiques. Ce sont TPE1 pour le nom de l'artiste et TIT2 pour le titre de la chanson. Si jamais la clé n'existe pas, on obtiendra une erreur, donc on entoure chaque appel avec une instruction try|except. Ensuite on va chercher la longueur de la chanson dans l'attribut audio.info.length et on retourne tout ça.

Now, we will want to modify the SavePlaylist function to support the extended information line. While we are there, let's check to see if the filename exists, and, if so, flag the user and exit the routine. Also, to make things a bit easier for the user, since we don't support any other filetype, let's automatically append the extension '.m3u' to the path and filename if it doesn't exist. First add an import line at the top of the code importing os.path between the sys import and the mutagen import (bottom right). Just like in the AddFilesToTreeview function, we will use the 'rfind' method to find the position of the last period ('.') in the filename fn. If there isn't one, the return value is set to -1. So we check to see if the return value is -1, and, if so, we append the extension and then put the filename back in the text box just to be nice. if os.path.exists(fp + “/” + fn): self.MessageBox(“error”,“The file already exists. Please select another.”)

On va maintenant modifier la fonction SauvegarderListe pour qu'elle supporte la ligne d'informations étendues. Tant que nous y sommes, vérifions si le nom de fichier existe et, si c'est le cas, prévenons l'utilisateur et sortons de la routine. Aussi, pour rendre les choses un peu plus faciles pour l'utilisateur et, puisqu'on ne supporte aucun autre type de fichier, ajoutons automatiquement l'extension .m3u au chemin et au nom de fichier si elle n'y est pas déjà. Commençons par ajouter une ligne « import os.path » au début du code entre les import de sys et de mutagen. (en bas à droite).

Tout comme pour la fonction AjouterFichiers, nous utiliserons la méthode rfind pour trouver la position du dernier point (« . ») dans le nom du fichier nf. S'il n'y en a pas, la valeur renvoyée sera -1. Donc nous vérifions si la valeur retournée est -1 et si c'est le cas on ajoute l'extension et on replace le nom du fichier dans le champ de texte pour être sympa.

if os.path.exists(cf + “/” + nf):

 self.MessageBox("erreur","Le fichier existe deja. Choisissez un autre nom.")

Next, we want to wrap the rest of the function with an IF|ELSE clause (top right) so if the file already exists, we simply fall out of the routine. We use os.path.exists(filename) to do this check. The rest of the code is mostly the save as before, but let's look at it anyway. Line 2 opens the file we are going to write. Line 3 puts the M3U header in. Line 4 sets up for a walk through the playList ListStore. Line 5 creates the filename from the three columns of the ListStore. Line 6 calls GetMP3Info and stores the return values into variables. Line 7 then checks to see if we have values in all three variables. If so, we write the extended information line in line 8, otherwise we don't try. Line 9 writes the filename line as before. Line 10 closes the file gracefully, and line 11 pops up the message box letting the user know the process is all done.

Ensuite on veut entourer le reste de la fonction dans une clause IF|ELSE (en haut à droite) pour que, si le fichier existe déjà, on puisse simplement sortir de la routine. On utilise os.path.exists(nom du fichier) pour cette vérification.

Le reste du code sert principalement à sauvegarder comme précédemment, mais regardons-le quand même.

La ligne 2 ouvre le fichier dans lequel nous allons écrire. La ligne 3 y place l'en-tête M3U. La ligne 4 règle un parcours à travers la liste de lecture ListStore. La ligne 5 crée le nom du fichier à partir des trois colonnes de ListStore. La ligne 6 appelle RecupererInfoMP3 et stocke les valeurs renvoyées dans des variables. La ligne 7 vérifie ensuite si nous avons des valeurs dans toutes ces variables. Si c'est le cas, on écrit la ligne d'informations étendues à la ligne 8, sinon on n'essaie pas. La ligne 9 écrit la ligne du nom du fichier comme précédemment. La ligne 10 ferme gentiment le fichier et la ligne 11 affiche un message à l'utilisateur indiquant que tout est terminé.

Go ahead and save your code and give it a test drive. At this point about the only thing that should be added would be some tool tips for our controls when the user hovers the mouse pointer over them. It adds that professional flair (below). Let's create a function to do that now. We are using the widget references we set up earlier, and then setting the text for the tooltip via the (you guessed it) set_tooltip_text attribute. Next we need to add the call to the routine. Back in the init routine, after the self.SetWidgetReferences line, add: self.SetupToolTops()

Allez, sauvegardez votre code et essayez-le.

À ce stade, la seule chose qu'on pourrait encore ajouter serait des bulles d'aide lorsque l'utilisateur survole nos contrôles avec sa souris. Cela y ajoute cet air professionnel (ci-dessous). Créons maintenant une fonction pour faire cela.

Nous utilisons le widget references que nous avons réglé plus haut, puis on règle le texte pour la bulle d'aide avec (vous l'aurez deviné) l'attribut set_tooltip_text. Ensuite on doit ajouter l'appel à la routine. Retournez dans la routine __init__, après la ligne self.ReferencesWidgets, ajoutez :

self.SetupBullesAide()

Last, but certainly not least, we want to put our logo into our About box. Just like everything else there, there's an attribute for that. Add the following line to the ShowAbout routine. about.set_logo(gtk.gdk.pixbuf_new_from_file(“logo.png”)) That's about it. You now have a fully functioning program that looks good, and does a wonderful job of creating a playlist for your music files. The full source code, including the glade file we created last month, can be found at pastebin: http://pastebin.com/tQJizcwT Until next time, enjoy your new found skills.

Enfin et surtout (!), on veut placer notre logo dans la boîte APropos. Comme tout le reste ici, il y a un attribut pour faire cela. Ajoutez la ligne suivante à la routine APropos :

apropos.set_logo(gtk.gdk.pixbuf_new_from_file(“logo.png”))

Et voilà. Vous avez maintenant une application complète, fonctionnelle et jolie, qui fait un travail merveilleux de création de liste de lecture pour vos fichiers de musique.

Le code complet, incluant le fichier glade que nous avons créé le mois dernier, est disponible ici : http://pastebin.com/ZfZ69zVJ

Profitez des nouveaux talents que vous vous êtes découverts, jusqu'à la prochaine fois.

CODE PAGE 7

elif reponse == gtk.RESPONSE_CANCEL: 
    print 'Annulation, aucun fichier choisi' 
    dialogue.destroy()

Remarquez que nous ne renvoyons rien. C'est ce qui causait l'erreur. Pour réparer cela, ajoutez la ligne suivante après la ligne dialog.destroy().

Return ([],"")

Ainsi il n'y aura plus d'erreur. Ensuite, ajoutons le gestionnaire d'événement que nous avons créé dans glade pour le champ de texte. Dans notre dictionnaire, ajoutez la ligne suivante.

"on_txtNomFichierFilename_key_press_event": self.txtNomFichierKeyPress,

Vous vous souvenez que cela crée une fonction pour gérer l'appui sur les touches du clavier. Créons maintenant la fonction.

def txtNomFichierKeyPress(self,widget,data):
   if data.keyval == 65293: # valeur de la touche Entree
      self.SauveListeLecture()

CODE PAGE 8 haut

def AjouterFichiers(self,ListeFichiers):
    compteur = 0
    for f in ListeFichiers: 
        debutExt = f.rfind(".") 
        debutnomFic = f.rfind("/") 
        extension = f[debutExt+1:] 
        nomFic = f[debutnomFic+1:debutExt] 
        cheminFic = f[:debutnomFic] 
        data = [nomFic,extension,cheminFic] 
        self.listeLecture.append(data) 
        compteur += 1

CODE PAGE 8 milieu

def on_boBtnSupprimer_clicked(self,widget):
    sel = self.treeview.get_selection()
    (modele,lignes) = sel.get_selected_rows()
    iter=[]
    for ligne in lignes:
      iter.append(self.listeLecture.get_iter(ligne))
    for i in iter:
      if i is not None:
        self.listeLecture.remove(i)
        self.NombreDeLignes -= 1
    self.sbar.push(self.context_id,"%d fichiers dans la liste." % (self.NombreDeLignes))

CODE PAGE 8 bas

def on_btnNomRepertoire_clicked(self,widget):
      fd = DialogueFichier()
      cheminFichier,self.CheminCourant = fd.AfficheDialogue(1,self.CheminCourant)
      self.txtChemin.set_text(cheminFichier[0])

CODE PAGE 9 haut

def SauveListeLecture(self):
      cf = self.txtChemin.get_text()     # recuperer le chemin dans le champ de texte
      nf = self.txtNomFichier.get_text() # recuperer le nom du fichier dans le champ de texte

Maintenant on vérifie les valeurs :

      if cf == "":                     # SI le chemin est vide
          self.MessageBox("erreur","Veuillez fournir un chemin pour la liste de lecture.")
      elif nf == "":                   # SI le nom de fichier est vide
          self.MessageBox("erreur","Veuillez fournir un nom pour le fichier liste de lecture.")
      else:                            # Sinon, on peut continuer

CODE PAGE 9 milieu

fic = open(cf + "/" + nf,"w")  # ouvrir le fichier
fic.writelines('#EXTM3U\n')    # afficher l'en-tete M3U
for ligne in self.listeLecture:
    fic.writelines("%s/%s.%s\n" % (ligne[2],ligne[0],ligne[1])) # ecrit les donnees
fic.close   # referme le fichier

Enfin, on affiche un message informant l'utilisateur que le fichier est sauvegardé.

self.MessageBox("info","La liste de lecture est sauvegardee !") 

On doit maintenant appeler cette routine depuis notre routine de gestion d'événement on_btnSauvegarderListe_clicked.

def on_btnSauvegarderListe_clicked(self,widget): 
    self.SauveListeLecture()

Sauvegardez votre code et testez-le. Votre liste de lecture devrait être sauvegardée correctement et ressembler à l'exemple que je vous ai montré le mois dernier.

CODE PAGE 9 bas

def on_boBtnHaut_clicked(self,widget):
        sel = self.treeview.get_selection()
        (modele,lignes) = sel.get_selected_rows()
        for chemin1 in lignes:
            chemin2 = 0
        iter1=modele.get_iter(chemin1)
        iter2 = modele.get_iter(chemin2)
        modele.move_before(iter1,iter2)

CODE PAGE 10 haut

def on_boBtnBas_clicked(self,widget):
      sel = self.treeview.get_selection()
      (modele,lignes) = sel.get_selected_rows()
      for chemin1 in lignes:
          chemin2 = self.NombreDeLignes-1
      iter1=modele.get_iter(chemin1)
      iter2 = modele.get_iter(chemin2)
      modele.move_after(iter1,iter2)

CODE PAGE 10 milieu haut

def on_boBtnMonter_clicked(self,widget):
      sel = self.treeview.get_selection()
      (modele,lignes) = sel.get_selected_rows()
      for chemin1 in lignes:
          chemin2 = (chemin1[0]-1,)
      if chemin2[0] >= 0:
          iter1=modele.get_iter(chemin1)
          iter2 = modele.get_iter(chemin2)
          modele.swap(iter1,iter2)

CODE PAGE 10 milieu bas

def on_boBtnDescendre_clicked(self,widget):
      sel = self.treeview.get_selection()
      (modele,lignes) = sel.get_selected_rows()
      for chemin1 in lignes:
          chemin2 = (chemin1[0]+1,)
      iter1=modele.get_iter(chemin1)
      if chemin2[0] <= self.NombreDeLignes-1:
          iter2 = modele.get_iter(chemin2)
          modele.swap(iter1,iter2)

CODE PAGE 10 bas

(inchangé)

#EXTM3U Adult Contemporary/Chris Rea/Collection/02 - On The Beach.mp3 Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11 - Looking For The Summer.mp3

CODE PAGE 11 haut

def RecupererInfoMP3(self,nomFichier):
      artiste = ''
      titre = ''
      longueurChanson = 0
      audio = MP3(nomFichier)
      cles = audio.keys()
      for cle in cles:
          try:
              if cle == "TPE1":         # Artiste
                  artiste = audio.get(cle)
          except:
              artiste = ''
          try:
              if cle == "TIT2":         # Titre de la chanson
                  titre = audio.get(cle)
          except:
              titre = ''
          longueurChanson = audio.info.length    # longueur de la chanson
      return (artiste,titre,longueurChanson)

CODE PAGE 11 bas

import os.path

Ensuite continuez et commentez la fonction SauveListeLecture actuelle et nous allons la remplacer.

 def SavePlaylist(self):
      fp = self.txtPath.get_text()     # Get the file path from the text box
      fn = self.txtFilename.get_text() # Get the filename from the text box
      if fp == "":  # IF filepath is blank...
          self.MessageBox("error","Please provide a filepath for the playlist.")
      elif fn == "": # IF filename is blank...
          self.MessageBox("error","Please provide a filename for the playlist file.")
      else:  # Otherwise

Jusqu'ici la routine est la même. Voici où les changements commencent.

      debutExt = nf.rfind(".") # cherche le debut de l'extension
      if debutExt == -1:
          nf += '.m3u' # ajouter une extension s'il n'y en a pas
          self.txtNomFichier.set_text(nf) # remplace le nom de fichier dans le champ de texte

CODE PAGE 12 haut

else:
          fic = open(cf + "/" + nf,"w")  # ouvre le fichier
          fic.writelines('#EXTM3U\n')    # affiche l'en-tete M3U
          for ligne in self.listeLecture:
             nomFic = "%s/%s.%s" % (ligne[2],ligne[0],ligne[1])
             artiste,titre,longueurChanson = self.RecupererInfoMP3(nomFic)
             if longueurChanson > 0 and (artiste != '' and titre != ''):
                fic.writelines("#EXTINF:%d,%s - %s\n" % (longueurChanson,artiste,titre))
             fic.writelines("%s\n" % nomFic)
          fic.close  # referme le fichier
          self.MessageBox("info","Liste de lecture sauvegardee !")

CODE PAGE 12 bas

def SetupBullesAide(self):
      self.boBtnAjouter.set_tooltip_text("Ajoute un ou des fichier(s) a la liste de lecture.")
      self.boBtnAPropos.set_tooltip_text("Affiche les informations sur le programme.")
      self.boBtnSupprimer.set_tooltip_text("Supprime l'entree selectionnee de la liste.")
      self.boBtnEffacer.set_tooltip_text("Supprime toutes les entrees de la liste.")
      self.boBtnQuitter.set_tooltip_text("Quitte le programme.")
      self.boBtnHaut.set_tooltip_text("Deplace l'entree selectionne tout en haut de la liste.")
      self.boBtnMonter.set_tooltip_text("Remonte l'entree selectionnee dans la liste.")
      self.boBtnDescendre.set_tooltip_text("Descend l'entree selectionne dans la liste.")
      self.boBtnBas.set_tooltip_text("Deplace l'entree selectionnee tout en bas de la liste.")
      self.btnNomRepertoire.set_tooltip_text("Choisis le repertoire de sauvegarde de la liste.")
      self.btnSauvegarderListe.set_tooltip_text("Sauvegarde la liste.")
      self.txtNomFichier.set_tooltip_text("Entrez ici le nom du fichier a sauvegarder. L'extension .m3u sera ajoutee pour vous si vous l'oubliez.")
issue49/tutopython.txt · Dernière modification : 2011/07/06 12:19 de peji