Outils pour utilisateurs

Outils du site


issue48:tuto-python

Correction Last month, in part 21, you were told to save what you have as “PlaylistMaker.glade”, but, in the code, it was referred to as: “playlistmaker.glade”. I’m sure you noticed that one has capitals and the other does not. The code will run only if you use both the call and file name with, or both without, the capitals.

Je propose de ne pas mettre ce paragraphe en VF : nous n'avions pas d'erreur de nom de fichier dans la VF ! On le supprime de notre version carrément, alors ?

To start off on the right foot, you need to have the playlistmaker.glade and playlistmaker.py from last month. If you don't, jump over to the last issue and get the goodies. Before we get to the code, let's take a look at what a playlist file is. There are multiple versions of play lists, and they all have different extensions. The one we will be creating will be a *.m3u type playlist. In its simplest form, it's just a text file that starts with “#EXTM3U”, and then has an entry for each song file you want to play - including the full path. There's also an extension that can be added before each entry that includes the length of the song, the album name the song comes from, the track number, and the song name. We'll bypass the extension for now and just concentrate on the basic version. Here is an example of a M3U playlist file.. . #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

Pour bien commencer, vous devez avoir les fichiers playlistmaker.glade et playlistmaker.py du mois dernier. Si ce n'est pas le cas, sautez sur le numéro précédent pour les récupérer. Avant de passer au code, nous allons jeter un œil à ce qu'est un fichier de liste de lecture. Il y a plusieurs versions des listes de lecture, qui ont toutes des extensions différentes. Le fichier que nous allons créer sera de type *.m3u. Dans sa forme la plus simple, c'est juste un fichier texte qui commence par « #EXTM3U “ et qui contient une entrée pour chaque fichier audio que vous voulez écouter - avec le chemin d'accès complet. Il y a aussi une extension qui peut être ajoutée avant chaque entrée contenant la longueur de la chanson, le nom de l'album d'où vient la chanson, le numéro de piste et le nom du morceau. Nous allons ignorer l'extension pour l'instant et se concentrer uniquement sur la version de base.

Voici un exemple d'un fichier de liste de lecture M3U. . #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

All path names are relative to the location of the playlist file. OK…now let's get to coding. Shown right is the opening of the source code from last month. Now, we need to create an event handler routine for each of our events that we have set up. Notice that on_MainWindow_destroy and on_tbtnQuit_clicked are already done for us, so we need to have only 10 more (shown top right). Just make stubs for now. We'll modify these stubbed routines in a few minutes. For now, this should get us up and running with an application, and we can test things as we go. But, we need to add one more line to the init routine before we can run the app. After the self.wTree line, add… self.SetEventDictionary()

Tous les noms de chemin sont relatifs à l'emplacement du fichier de liste de lecture.

Bien… Maintenant passons au code. Vous voyez à droite le début du code source du mois dernier.

Maintenant, nous devons créer une routine de gestion d'événement pour chacun des événements que nous avons mis en place. Notez que on_FenetrePrincipale_destroy et on_boBtnQuitter_clicked sont déjà faits pour nous, il n'en reste donc que dix autres à écrire (voir en haut à droite). Écrivons juste des ébauches pour l'instant.

Nous modifierons ces ébauches de routines dans quelques minutes. Pour l'instant, cela devrait nous permettre de démarrer l'application ; nous pourrons tester les choses au fur et à mesure que nous avançons. Nous devons quand même ajouter une ligne supplémentaire à la routine __init__ avant de pouvoir démarrer l'application. Après la ligne self.wTree, ajouter :

self.DicoEvenements()

Now, you can run the application, see the window, and click the Quit toolbar button to exit the application properly. Save the code as “playlistmaker-1a.py” and give it a try. Remember to save it in the same folder as the glade file we created last time, or copy the glade file into the folder you saved this code in. We also need to define a few variables for future use. Add these after the SetEventDictionary call in the init function. self.CurrentPath = ”“ self.CurrentRow = 0 self.RowCount = 0 Now, we will create a function that allows us to display a popup dialog box whenever we need to give some information to our user. There is a built-in set of routines that we will use, but we'll make a routine of our own to make it easier for us. It is the gtk.MessageDialog routine, and the syntax is as follows… gtk.MessageDialog(parent,flags,MessageType,Buttons,message)

Maintenant, vous pouvez exécuter l'application, voir la fenêtre, puis cliquer sur le bouton Quitter de la barre d'outils pour quitter l'application correctement. Enregistrez le code sous le nom « CreateurListeDeLecture-1a.py » et essayez-le. Souvenez-vous qu'il faut l'enregistrer dans le même dossier que le fichier glade nous avons créé la dernière fois ou bien copier le fichier glade dans le dossier dans lequel vous avez enregistré ce code.

Nous avons également besoin de définir quelques variables pour une utilisation future. Ajoutez ceci après l'appel à DicoEvenements() dans la fonction __init__.

self.CheminCourant = ""
self.LigneCourante = 0
self.NombreDeLignes = 0

Maintenant, nous allons créer une fonction qui nous permet d'afficher une boîte de dialogue à chaque fois que nous avons besoin de donner des informations au utilisateur. Il existe un ensemble de routines toutes faites que nous allons utiliser, mais nous allons faire une routine à nous pour nous faciliter les choses. C'est la routine gtk.MessageDialog et la syntaxe est la suivante :

gtk.MessageDialog (parent, drapeaux, MessageType, boutons, message)

Some discussion is needed before we go too much further. The message type can be one of the following… GTK_MESSAGE_INFO - Informational message GTK_MESSAGE_WARNING - Nonfatal warning message GTK_MESSAGE_QUESTION - Question requiring a choice GTK_MESSAGE_ERROR - Fatal error message And the button types are… GTK_BUTTONS_NONE - no buttons at all GTK_BUTTONS_OK - an OK button GTK_BUTTONS_CLOSE - a Close button GTK_BUTTONS_CANCEL - a Cancel button GTK_BUTTONS_YES_NO - Yes and No buttons GTK_BUTTONS_OK_CANCEL - OK and Cancel Buttons

Une discussion est nécessaire avant d'aller trop loin. Le type de message peut être l'un des suivants :

GTK_MESSAGE_INFO - message d'information GTK_MESSAGE_WARNING - message d'avertissement GTK_MESSAGE_QUESTION - question nécessitant un choix GTK_MESSAGE_ERROR - message d'erreur fatale

Et les types de boutons sont :

GTK_BUTTONS_NONE - aucun bouton GTK_BUTTONS_OK - un bouton OK GTK_BUTTONS_CLOSE - un bouton Fermer GTK_BUTTONS_CANCEL - un bouton Annuler GTK_BUTTONS_YES_NO - boutons Oui et Non GTK_BUTTONS_OK_CANCEL - boutons OK et Annuler

Normally, you would use the following code , or similar, to create the dialog, display it, wait for a response, and then destroy it. dlg = gtk.MessageDialog(None,0,gtk.MESSAGE_INFO,gtk.BUTTONS_OK,”This is a test message…“) response = dlg.run() dlg.destroy() However, if you want to display a message box to the user more than once or twice, that's a LOT of typing. The general rule of thumb is that if you write a series of lines-of-code more than once or twice, it's usually better to create a function and then call that. Think of it this way: If we want to display a message dialog to the user, say ten times in your application, that's 10 X 3 (or 30) lines of code. By making a function to do this for us (using the example I just presented), we would have 10 + 3 (or 13) lines of code to write. The more we call a dialog, the less code we actually have to type, and the more readable our code is. Our function (top right) will allow us to call any of the four message dialog types with just one routine using different parameters.

Normalement, vous utiliseriez le code suivant, ou du code similaire, pour créer la boîte de dialogue, l'afficher, attendre une réponse, puis le détruire.

dlg = gtk.MessageDialog (None, 0, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, "Ceci est un message de test ...")
reponse = dlg.run ()
dlg.destroy ()

Toutefois, si vous voulez afficher une boîte de message à l'utilisateur plus d'une ou deux fois, c'est beaucoup de dactylographie. La règle générale est que si vous écrivez une série de lignes de code plus d'une ou deux fois, il est généralement préférable de créer une fonction puis de l'appeler. Pensez-y de cette manière : si nous voulons afficher un message de dialogue pour l'utilisateur, disons dix fois dans l'application, cela représente 10 x 3 (soit 30) lignes de code. En faisant une fonction pour faire cela pour nous (en utilisant l'exemple que je viens de présenter), nous aurions 10 + 3 (soit 13) lignes de code à écrire. Plus nous appelons une boîte de dialogue, moins cela fait de code à taper, et plus lisible est notre code. Notre fonction (en haut à droite) nous permettra d'appeler l'un des quatre types de message de dialogue avec une seule routine en utilisant différents paramètres.

This is a very simple function that we would then call like this… self.MessageBox(“info”,”The button QUIT was clicked“) Notice that if we choose to use the MESSAGE_QUESTION type of dialog, there are two possible responses that will be returned by the message dialog - a “Yes” or a “No”. Whichever button the user clicks, we will receive the information back in our code. To use the question dialog, the call would be something like this… response = self.MessageBox(“question”,”Are you sure you want to do this now?“) if response == gtk.RESPONSE_YES: print “Yes was clicked” elif response == gtk.RESPONSE_NO: print “NO was clicked”

C'est une fonction très simple que nous pourrons ensuite appeler comme suit :

self.MessageBox("info", "Le bouton QUITTER a été cliqué")

Notez que si nous choisissons d'utiliser le type de dialogue MESSAGE_QUESTION, il y a deux réponses possibles qui seront retournées par la fenêtre de dialogue - un « oui » ou un « non ». Quel que soit le bouton cliqué par l'utilisateur, nous allons recevoir les informations de retour dans notre code. Pour utiliser la boîte de dialogue de question, l'appel ressemblera à ceci :

reponse = self.MessageBox(« question », « Êtes-vous sûr de vouloir faire cela maintenant ? »)
if reponse == gtk.RESPONSE_YES:
   print "clic sur oui"
elif reponse == gtk.RESPONSE_NO:
   print "clic sur non"

You can see how you can check the value of the button returned. So now, replace the “pass” call in each of our event handler routines with something like that shown below right. We won't keep it like this, but this gives you a visual indication that the buttons work the way we want. Save the code now as “playlistmaker-1b.py”, and test your program. Now we are going to create a function to set our widget references. This routine is going to be called only once, but it will make our code much more manageable and readable. Basically, we want to create local variables that reference the widgets in our glade window - so we can make calls to them whenever (if ever) we need to. Put this function (above right) below the SetEventDictionary function. Please notice that there is one thing that isn't referenced in our routine. That would be the treeview widget. We'll make that reference when we set up the treeview itself. Also of note is the last line of our routine. In order to use the status bar, we need to refer to it by its context id. We'll be using this later on. Next, let's set up the function that displays the “about” dialog when we click the About toolbar button. Again, there is a built-in routine to do this provided by the GTK library. Put this after the MessageBox function. Here's the code, below right.

Vous voyez comment vous pouvez vérifier la valeur du bouton cliqué. Alors maintenant, remplacez l'appel à « pass » dans chacune de nos routines de gestion d'événement par ce que vous voyez ci-dessous à droite.

Nous n'allons pas le garder comme ça, mais cela vous donne une indication visuelle que les boutons fonctionnent comme nous le voulons. Enregistrez maintenant le code sous « CréateurListeDeLecture-1b.py » et testez votre programme. Maintenant nous allons créer une fonction pour définir nos références de widgets. Cette routine va être appelée une seule fois, mais elle rendra notre code beaucoup plus maniable et lisible. En fait, nous voulons créer des variables locales qui font référence à des widgets dans la fenêtre glade - afin que nous puissions faire des appels à eux chaque fois que (et si jamais) nous en avons besoin. Mettez cette fonction (en haut à droite) en dessous de la fonction DicoEvenements.

Remarquez qu'il y a une chose qui n'est pas référencée dans notre routine. Il s'agit du widget treeview. Nous allons créer cette référence lorsque nous créerons l'arborescence elle-même. Notez également la dernière ligne de notre routine. Pour utiliser la barre d'état, il faut s'y référer par son id de contexte. Nous allons utiliser cela plus loin.

Ensuite, nous allons mettre en place la fonction qui affiche le dialogue « à propos » quand on clique sur le bouton À propos de la barre d'outils. Encore une fois, ceci est une routine intégrée fournie par la bibliothèque GTK. Placez ceci après la fonction MessageBox. Voici le code, en bas à droite.

Save your code and then give it a try. You should see a pop-up box, centered in our application, that displays everything we have set. There are more attributes that you can set for the about box (which can be found at http://www.pygtk.org/docs/pygtk/class-gtkaboutdialog.html), but these are what I would consider a minimum set. Before we go on, we need to discuss exactly what will happen from here. The general idea is that the user will click on the “Add” toolbar button, we'll pop up a file dialog box to allow them to add files to the playlist, and then display the file information into our treeview widget. From there, they can add more files, delete single file entries, delete all file entries, move a file entry up, down, or to the top or down to the bottom of the treeview. Eventually, they'll set the path that the file will be saved to, provide a filename with a “m3u” extension, and click the save file button. While this seems simple enough, there's a lot that happens behind the scenes. The magic all happens in the treeview widget, so let's discuss that. This will get pretty deep, so you might want to read carefully, since an understanding of this will keep you from making mistakes later on.

Sauvegardez votre code, puis faites un essai. Vous devriez voir une fenêtre pop-up, centrée dans notre application, qui affiche ce que nous avons prévu. Il y a plusieurs attributs que vous pouvez définir pour la boîte à propos (qui peuvent être trouvés sur http://www.pygtk.org/docs/pygtk/class-gtkaboutdialog.html), mais ceux-ci sont ceux que je considère être un ensemble minimal.

Avant de poursuivre, nous devons discuter de ce qui se produira à partir d'ici. L'idée générale est que l'utilisateur clique sur le bouton « Ajouter » de la barre d'outils, nous afficherons alors une boîte de dialogue de fichier pour lui permettre d'ajouter des fichiers à la liste de lecture, puis nous afficherons les informations du fichier dans notre widget treeview. De là, il peut ajouter d'autres fichiers, supprimer un fichier unique, supprimer tous les fichiers, déplacer un fichier vers le haut ou le bas, ou bien tout en haut ou tout en bas de l'arborescence. Enfin, il va définir le chemin où le fichier sera enregistré, fournir un nom de fichier avec une extension « m3u », puis cliquer sur le bouton Sauvegarder. Bien que cela semble assez simple, il se passe beaucoup de choses en coulisses. La magie se produit dans le widget treeview, nous allons donc en discuter. Cela ira assez loin, alors lisez attentivement, car il faut le comprendre pour éviter de commettre des erreurs plus tard.

A treeview can be something as simple as a columnar list of data like a spreadsheet or database representation, or it could be more complex like a file-folder listing with parents and children, where the folder would be the parent and the files in that folder would be the children, or something even more complex. For this project, we'll use the first example, a columnar list. In the list, there will be three columns. One is for the name of the music file, one is for the extension of the file (mp3, ogg, wav, etc) and the final column is for the path. Combining this into a string (path, filename, extension) gives us the entry into the playlist we will be writing. You could, of course, add more columns as you wish, but for now, we'll deal with just three. A treeview is simply a visual storage container that holds and displays a model. The model is the actual “device” that holds and manipulates our data. There are two different pre-defined models that are used with a treeview, but you can certainly create your own. That having been said, for 98% of your work, one of the two pre-defined models will do what you need. The two types are GTKListStore and GTKTreeStore. As their names suggest, the ListStore model is usually used for lists, the TreeStore is used for Trees. For our application, we will be using a GTKListStore. The basic steps are: • Create a reference to the TreeView widget. • Add the columns. • Set the type of renderer to use. • Create the ListStore. • Set the model attribute in the Treeview to our model. • Fill in the data.

Une arborescence peut être quelque chose d'aussi simple qu'une liste à colonnes de données, comme dans une feuille de calcul ou une base de données, ou bien elle peut être plus complexe, comme une liste de fichiers/dossiers avec des parents et enfants, où le dossier serait le parent et les fichiers de ce dossier seraient les enfants, ou quelque chose d'encore plus complexe. Pour ce projet, nous allons utiliser le premier exemple, une liste à colonnes. Dans la liste, il y aura trois colonnes. Une pour le nom du fichier de musique, une pour l'extension du fichier (mp3, ogg, wav, etc.) et la dernière colonne pour le chemin d'accès. En combinant tout ça dans une chaîne (chemin d'accès, nom de fichier, extension) on obtient l'entrée que nous allons écrire dans la liste de lecture. Vous pourriez bien sûr ajouter d'autres colonnes si vous le souhaitez, mais pour l'instant nous allons nous contenter de trois.

Une arborescence est simplement un conteneur visuel de stockage qui détient et affiche un modèle. Le modèle est le véritable « dispositif » qui contient et manipule nos données. Il existe deux modèles prédéfinis qui sont utilisés avec un treeview, mais vous pouvez certainement créer le vôtre. Cela étant dit, pour 98 % de votre travail, l'un des deux modèles prédéfinis fera ce dont vous avez besoin. Les deux types sont GTKListStore et GTKTreeStore. Comme leur nom l'indique, le modèle ListStore est habituellement utilisé pour les listes, le TreeStore est utilisé pour les arbres. Pour notre application, nous allons utiliser un GTKListStore. Les étapes de base sont les suivantes :

• Créer une référence au widget TreeView. • Ajouter les colonnes. • Définir le type de moteur de rendu à utiliser. • Créer le ListStore. • Définir l'attribut de modèle dans l'arborescence de notre modèle. • Remplir les données.

The third step is to set up the type of renderer the column will use to display the data. This is simply a routine that is used to draw the data into the tree model. There are many different cell renderers that come with GTK, but most of the ones that you would normally use include GtkCellRenderText and GtkCellRendererToggle. So, let's create a function (shown above) that sets up our TreeView widget. We'll call it SetupupTreeview. First we'll define some variables for our columns, set the variable reference of the TreeView itself, add the columns, set up the ListStore, and set the model. Here's the code for the function. Put it after the SetWidgetReferences function. The variables cFName, cFType and cFPath define the column numbers. The variables sFName, sFType and sFPath will hold the column names in our displayed view. The seventh line sets the variable reference of the treeview widget as named in our glade file. Next we call a routine (next page, top right), which we'll create in just a moment, for each column we want. Then we define our GTKListStore with three text fields, and finally set the model attribute of our TreeView widget to our GTKListStore. Let's create the AddPlaylistColumn function next. Put it after the SetupTreeview function.

La troisième étape consiste à mettre en place le type de moteur de rendu que la colonne utilisera pour afficher les données. C'est tout simplement une routine qui est utilisée pour tracer les données dans le modèle de l'arbre. GTK fournit de nombreux moteurs de rendu de cellules différents, mais normalement vous utiliserez le plus souvent GtkCellRenderText et GtkCellRendererToggle.

Nous allons donc créer une fonction (ci-dessus) qui met en place notre widget TreeView. Nous allons l'appeler SetupTreeview. Nous allons d'abord définir quelques variables pour nos colonnes, définir la variable de référence du TreeView proprement dit, ajouter les colonnes, mettre en place le ListStore, et définir le modèle. Voici le code pour la fonction. Placez-le après la fonction ReferencesWidget.

Les variables cNomFic, cTypeFic et cCheminFic définissent les numéros de colonne. Les variables sNomFic, sTypeFic et sCheminFic contiennent les noms de colonnes de notre vue. La septième ligne définit la variable de référence du widget treeview tel qu'il figure dans notre fichier glade.

Ensuite nous appelons une routine (page suivante, en haut à droite), que nous allons créer dans un instant, pour chaque colonne que nous voulons. Puis, nous définissons notre GTKListStore avec trois champs de texte et, enfin, nous utilisons ce GTKListStore comme attribut de modèle de notre widget TreeView. Nous allons ensuite créer la fonction AjouterColonne. Placez-la après la fonction SetupTreeview.

Each column is created with this function. We pass in the title of the column (what's displayed on the top line of each column) and a columnID. In this case, the variables we set up earlier (sFName and cFname) will be passed here. We then create a column in our TreeView widget giving the title, what kind of cell renderer it will be using, and, finally, the id of the column. We then set the column to be resizable, set the sort id, and finally append the column into the TreeView. Add these two functions to your code. I choose to put them right after the SetWidgetReferences function, but you can put it anywhere within the PlayListCreator class. Add the following line after the call to SetWidgetReferences() in the init function to call the function. self.SetupTreeview() Save and run your program, and you will see that we now have three columns with headers in our TreeView widget.

Chaque colonne est créée avec cette fonction. Nous lui passons le titre de la colonne (ce qui est affiché sur la première ligne de chaque colonne) et un idColonne. Dans ce cas, nous utilisons les variables que nous avons créées plus tôt (sNomFic et cNomFic). Nous créons ensuite une colonne dans notre widget TreeView donnant le titre, le type de rendu de cellule et enfin l'id de la colonne. Nous indiquons ensuite que la colonne est redimensionnable, nous définissons l'id de tri et ajoutons enfin la colonne dans le TreeView.

Ajoutez ces deux fonctions à votre code. J'ai choisi de les mettre tout de suite après la fonction ReferencesWidget, mais vous pouvez les mettre n'importe où dans la classe CreateurListeDeLecture. Ajoutez la ligne suivante après l'appel à ReferencesWidget() dans la fonction __init__ pour appeler la fonction.

self.SetupTreeview ()

Enregistrez et exécutez votre programme et vous verrez que nous avons maintenant trois colonnes avec en-têtes dans notre widget TreeView.

There are so many things left to do. We have to have a way to get the music filenames from the user and put them into the TreeView as rows of data. We have to create our Delete, ClearAll, movement functions, save routine, and file path routines, plus a few “pretty” things that will make our application look more professional. Let's start with the Add routine. After all, that's the first button on our toolbar. When the user clicks the Add button, we want to pop up a “standard” open-file dialog that allows for multiple selections. Once the user has made their selection, we then want to take this data and add it into the treeview, as I stated above. So the first logical thing to do is work on the File Dialog. Again, GTK provides us a way to call a “standard” file dialog in code. We could hard code this as just lines in the on_tbtnAdd_clicked event handler, but let's make a separate class to handle this. While we are at it, we can make this class handle not only a file OPEN dialog, but a folder SELECT dialog as well. As before with the MessageBox function, you can pull this into a snippet file that has all kinds of reusable routines for later use. We'll start by defining a new class called FileDialog which will have only one function called ShowDialog. That function will take two parameters, one called 'which' (a '0' or a '1'), that designates whether we are creating an open-file or select-folder dialog, and the other is the path that should be used for the default view of the dialog called CurrentPath. Create this class just before our main code at the bottom of the source file. class FileDialog: def ShowDialog(self,which,CurrentPath):

Il reste tellement de choses à faire. Nous devons avoir un moyen d'obtenir les noms de fichiers de musique de l'utilisateur et un moyen de les mettre dans le TreeView sous forme de lignes de données. Nous devons créer nos fonctions Supprimer, Effacer tout, les fonctions de déplacement, la routine de sauvegarde et les routines de chemins de fichiers, plus quelques « jolies » choses qui donneront à notre application un aspect plus professionnel. Commençons par la routine Ajouter. Après tout, c'est le premier bouton sur notre barre d'outils. Lorsque l'utilisateur clique sur le bouton Ajouter, nous voulons faire apparaître une fenêtre de dialogue « standard » d'ouverture de fichier, qui permet des sélections multiples. Une fois que l'utilisateur a fait son choix, nous voulons ensuite prendre ces données et les ajouter dans l'arborescence, comme je l'ai indiqué ci-dessus. Ainsi, la première chose logique à faire est de travailler sur la boîte de dialogue Fichier. Encore une fois, GTK nous fournit un moyen d'appeler une boîte de dialogue « standard » de fichiers. Nous pourrions coder ça en dur simplement avec des lignes de code dans le gestionnaire d'événements on_boBtnAjouter_clicked, mais nous allons faire une classe distincte pour le gérer. Tant que nous y sommes, nous pouvons faire en sorte que cette classe gère non seulement un dialogue Ouvrir un fichier, mais aussi un dialogue Sélectionner un dossier. Comme auparavant avec la fonction MessageBox, vous pouvez l'extraire dans un fichier qui contient toutes sortes de routines réutilisables pour un usage ultérieur.

Nous allons commencer par définir une nouvelle classe appelée DialogueFichier qui a une seule fonction appelée AfficheDialogue. Cette fonction prendra deux paramètres, l'un appelé « type » (un '0' ou un '1'), qui précise si nous créons un dialogue d'ouverture de fichier ou de sélection de dossier, et l'autre, qui est le chemin à utiliser pour la vue par défaut de la boîte de dialogue appelée CheminCourant. Créez cette classe juste avant notre code principal à la fin du fichier source.

class DialogueFichier:

def AfficheDialogue(self,type,CheminCourant):

The first part of our code should be an IF statement if which == 0: # file chooser … else: # folder chooser … Before going any further, let's explore how the file/folder dialog is actually called and used. The syntax of the dialog is as follows gtk.FileChooserDialog(title,parent,action,buttons,backend) and returns a dialog object. Our first line (under if which == 0) will be the line shown below. As you can see, the title is “Select files to add…”, the parent is set to None. We are requesting a File Open type dialog (action), and we want a Cancel and an Open button, both using “stock” type icons. We are also setting the return codes of gtk.RESPONSE_CANCEL and gtk.RESPONSE_OK for when the user makes their selections. The call for our Folder Chooser under the Else clause is similar.

La première partie de notre code doit être une instruction IF

if type == 0: # choix de fichier

  ...

else: # choix de dossier

  ...

Avant d'aller plus loin, nous allons voir la façon dont la boîte de dialogue de fichier/dossier est effectivement appelée et utilisée. La syntaxe de la boîte de dialogue se présente comme suit :

gtk.FileChooserDialog(titre,parent,action,boutons,backend)

et retourne un objet fenêtre de dialogue. Notre première ligne (dans le cas où type vaut 0) sera la ligne ci-dessous.

Comme vous pouvez le voir, le titre est « Choisir les fichiers a ajouter… », le parent est défini sur none (aucun). Nous demandons une fenêtre de type ouverture de fichier (action) et nous voulons des boutons Annuler et Ouvrir, les deux utilisant des icônes de type « stock ». Nous réglons également les codes de retour de gtk.RESPONSE_CANCEL et gtk.RESPONSE_OK lorsque l'utilisateur fait ses choix. L'appel au sélecteur de dossier dans la clause else est similaire.

Basically, the only thing that changed between the two definitions are the title (shown above right) and the action type. So our code for the class should now be the code shown middle right. These set the default response to be the OK button, and then to turn on the multiple select feature so the user can select (you guessed it) multiple files to add. If we didn't set this, the dialog would only allow one file to be selected at a time, since set_select_multiple is set to False by default. Our next lines are setting the current path, and then displaying the dialog itself. Before we type in the code, let me explain why we want to deal with the current path. Every time you pop up a file dialog box, and you DON'T set a path, the default is to the folder where our application resides. So, let's say that the music files that the user would be looking for are in /media/music_files/, and are then broken down by genre, and further by artist, and further by album. Let's further assume that the user has installed our application in /home/user2/playlistmaker. Each time we pop up the dialog, the starting folder would be /home/user2/playlistmaker. Quickly, the user would become frustrated by this, wanting the last folder he was in to be the starting folder next time. Make sense? OK. So, bottom right are our next lines of code.

Fondamentalement, les seules choses qui ont changé entre les deux définitions sont le titre (ci-dessus à droite) et le type d'action. Donc le code de la classe devrait maintenant être le code affiché au milieu à droite.

Nous définissons la réponse par défaut à la touche OK, puis activons la fonctionnalité de sélection multiple pour que l'utilisateur puisse sélectionner (vous l'aurez deviné) plusieurs fichiers à ajouter. Si nous n'avions pas indiqué cela, la boîte de dialogue permettrait seulement de sélectionner un fichier à la fois, car set_select_multiple est réglé sur faux par défaut. Nos lignes suivantes règlent le chemin actuel, puis affichent la boîte de dialogue elle-même. Avant de taper le code, je vais vous expliquer pourquoi nous devons nous occuper du chemin courant. À chaque fois que vous faites apparaître une boîte de dialogue de fichier et que vous ne définissez pas un chemin, la valeur par défaut est le dossier où réside notre application. Ainsi, si les fichiers de musique que l'utilisateur utilise sont dans /media/musique/ ils sont ensuite triés par genre puis par artiste, et puis après par album. Supposons également que l'utilisateur a installé notre application dans /home/user2/createurListeDeLecture. Chaque fois que nous faisons apparaître le dialogue, le dossier de départ serait /home/user2/createurListeDeLecture. Rapidement, l'utilisateur devrait se sentir frustré par cela, préférant retrouver le dernier dossier dans lequel il était lorsqu'il démarre la prochaine fois. Vous comprenez ? Bien. Voici donc en bas à droite les lignes de code suivantes.

Here we check the responses sent back. If the user clicked the 'Open' button which sends back a gtk.RESPONSE_OK, we get the name or names of the files the user selected, set the current path to the folder we are in, destroy the dialog, and then return the data back to the calling routine. If, on the other hand, the user clicked on the 'Cancel' button, we simply destroy the dialog. I put the print statement in there just to show you that the button press worked. You can leave it or take it out. Notice that when we return from the Open button part of the routine, we are returning two sets of values. 'fileselection' is a list of the files selected by the user, as well as the CurrentPath. In order to get the routine to do something, add the following line under the on_tbtnAdd_click routine… fd = FileDialog() selectedfiles,self.CurrentPath = fd.ShowDialog(0,self.CurrentPath) Here we retrieve the two return values that are sent from our return call. For now, add the following code to see what the information returned will look like. for f in selectedfiles: print “User selected %s” % f print “Current path is %s” % self.CurrentPath

Ici, nous vérifions les réponses renvoyées. Si l'utilisateur a cliqué sur le bouton « Ouvrir » qui renvoie gtk.RESPONSE_OK, nous obtenons le nom ou les noms des fichiers que l'utilisateur a sélectionné, on définit le chemin d'accès courant vers le dossier où nous sommes, on détruit la boîte de dialogue, puis on renvoie les données à la routine appelante. Si, en revanche, l'utilisateur a cliqué sur le bouton « Annuler », il suffit de détruire la boîte de dialogue. Je mets l'instruction print là juste pour vous montrer que l'appui sur le bouton a fonctionné. Vous pouvez la laisser ou la retirer. Notez que lorsque nous sortons de la partie concernant le bouton Ouvrir dans cette routine, nous renvoyons deux ensembles de valeurs : selectionFichiers qui est une liste des fichiers sélectionnés par l'utilisateur, ainsi que le CheminCourant.

Afin que la routine fasse quelque chose, ajoutez la ligne suivante dans la routine on_boBtnAjouter_clicked :

fd = DialogueFichier ()

fichiersChoisis,self.CheminCourant = fd.AfficheDialogue(0,self.CheminCourant)

Ici on récupère les deux valeurs de retour qui sont renvoyées depuis le return. Pour le moment, ajoutez le code ci-dessous pour voir à quoi les informations retournées ressemblent :

for f in fichiersChoisis:

  print "Choix utilisateur : %s" % f

print “Chemin courant : %s” % self.CheminCourant

When you run the program, click on the 'Add' button. You'll see the file dialog. Now move to somewhere where you have some files and select them. You can hold down the [ctrl] key and click on multiple files to select them individually, or the [shift] key to select multiple contiguous files. Click on the 'Open' button, and look at the response in your terminal window. Please note that if you click on the 'Cancel' button right now, you'll get an error message. That's because the above code assumes that there are no files selected. Don't worry about that right now - we'll handle that in a little bit. I just wanted to let you see what comes back if the 'Open' button is pressed. One thing we should do is add a filter to our file-open dialog. Since we expect the user to normally select music files, we should (1) give the option to display only music files, and (2) give the option to show all files just-in-case. We do this by using the filefilter attributes of the dialog. Here's the code for that which should go in the which == 0 section right after the dialog set line. filter = gtk.FileFilter() filter.set_name(“Music Files”) filter.add_pattern(“*.mp3”) filter.add_pattern(“*.ogg”) filter.add_pattern(“*.wav”) dialog.add_filter(filter) filter = gtk.FileFilter() filter.set_name(“All files”) filter.add_pattern(“*”) dialog.add_filter(filter)

Lorsque vous exécutez le programme, cliquez sur le bouton « Ajouter ». Vous verrez la boîte de dialogue de fichier. Allez maintenant à un endroit où vous avez des fichiers et sélectionnez-les. Vous pouvez appuyer sur la touche [Ctrl] et cliquer sur plusieurs fichiers pour les sélectionner individuellement, ou sur la touche [Maj] pour sélectionner plusieurs fichiers contigus. Cliquez sur le bouton « Ouvrir », et examinez la réponse dans un terminal. Remarquez que si vous cliquez sur le bouton « Annuler » à ce moment, vous obtiendrez un message d'erreur. C'est parce que le code ci-dessus suppose qu'il n'y a pas de fichiers sélectionnés. Ne vous inquiétez pas pour l'instant, nous allons régler cela sous peu. Je voulais simplement vous permettre de voir ce qui revient si l'on appuie sur le bouton « Ouvrir ». Une chose que nous devrions faire est d'ajouter un filtre à notre fenêtre d'ouverture de fichier. Puisque nous attendons que l'utilisateur sélectionne normalement des fichiers de musique, nous devrions : 1) donner la possibilité d'afficher des fichiers de musique uniquement et, 2) donner la possibilité d'afficher tous les fichiers au cas où. Nous faisons cela en utilisant les attributs FileFilter de la boîte de dialogue. Voici le code pour cela, qu'il faut placer dans la partie « type == 0 » juste après la ligne créant le dialogue.

filtre = gtk.FileFilter() filtre.set_name(“Fichiers musicaux”) filtre.add_pattern(“*.mp3”) filtre.add_pattern(“*.ogg”) filtre.add_pattern(“*.wav”) dialogue.add_filter(filtre) filtre = gtk.FileFilter() filtre.set_name(“Tous les fichiers”) filtre.add_pattern(“*”) dialogue.add_filter(filtre)

We are setting up two “groups”, one for music files (filter.set_name(“Music Files”)), and the other for all files. We use a pattern to define the types of files we want. I have defined three patterns, but you can add or delete any that you wish. I put the music filter first, since that's what we will assume the user is going to be mainly concerned with. So the steps are… • Define a filter variable. • Set the name. • Add a pattern. • Add the filter to the dialog. You can have as many or as few filters as you wish. Also notice that once you have added the filter to the dialog, you can re-use the variable for the filter. Back in the on_tbtnAdd_clicked routine, comment out the last lines we added and replace them with this one line. self.AddFilesToTreeview(selectedfiles) so our routine now looks like the code shown on the next page.

Nous mettons en place deux « groupes », l'un pour les fichiers de musique (filtre.set_name(“Fichiers musicaux”)), et l'autre pour tous les fichiers. Nous utilisons un motif pour définir les types de fichiers que nous voulons. J'ai défini trois motifs, mais vous pouvez ajouter ou supprimer tous ceux que vous souhaitez. Je mets le filtre pour la musique en premier, puisque c'est ce qui intéresse principalement l'utilisateur. Ainsi, les étapes sont :

• Définir une variable de filtre. • Régler le nom. • Ajouter un motif. • Ajouter le filtre à la boîte de dialogue.

Vous pouvez avoir autant ou aussi peu de filtres que vous le souhaitez. Notez également qu'une fois que vous avez ajouté le filtre à la boîte de dialogue, vous pouvez réutiliser la variable de filtre.

Retournez dans la routine on_boBtnAjouter_clicked, commentez les dernières lignes que nous avons ajoutées et remplacez-les par cette seule ligne.

self.AjouterFichiers(fichiersChoisis)

Ainsi notre routine ressemble maintenant au code affiché sur la page suivante.

So, when we get the response back from file dialog, we will send the list containing the selected files to this routine. Once here, we set up a counter variable (how many files we are adding), then parse the list. Remember that each entry contains the fully qualified filename with path and extension. We'll want to split the filename into path, filename, and extension. First we get the very last 'period' from the filename and assume that is the beginning of the extension and assign its position in the string to extStart. Next we find the very last '/' in the filename to determine the beginning of the filename. Then we break up the string into extension, filename and file path. We then stuff these values into a list named 'data' and append this into our playlist ListStore. We increment the counter since we have done all the work. Finally we increment the variable RowCount which holds the total number of rows in our ListStore, and then we print a message to the status bar. Now you can run the application and see the data in the TreeView. As always, the full code can be found at http://pastebin.com/JtrhuE71. Next time, we'll finalize our application, filling in the missing routines, etc.

Ainsi, lorsque nous aurons la réponse au retour de la fenêtre de sélection de fichiers, nous enverrons la liste contenant les fichiers sélectionnés à cette routine. Une fois ici, nous créons une variable de compteur (le nombre de fichiers que nous ajoutons), puis analysons la liste. Rappelez-vous que chaque entrée contient le nom de fichier complet avec le chemin et l'extension. Nous allons devoir fractionner le nom du fichier en chemin, nom de fichier et extension. Nous récupérons d'abord le tout dernier « . » dans le nom de fichier et supposons que c'est le début de l'extension, et nous affectons sa position dans la chaîne à debutExt. Nous trouvons ensuite le tout dernier « / » dans le nom du fichier pour déterminer le début du nom de fichier. Puis, nous découpons la chaîne en extension, nom de fichier et chemin du fichier. Nous plaçons ensuite ces valeurs dans une liste nommée « data » et ajoutons ceci dans listeLecture. Nous incrémentons le compteur puisque nous avons fait tout le travail. Enfin on incrémente la variable NombreDeLignes qui contient le nombre total de lignes dans listeLecture et nous affichons un message dans la barre d'état.

Maintenant vous pouvez lancer l'application et voir les données dans l'arborescence.

Comme toujours, le code complet peut être trouvé à http://pastebin.com/wTCcGDSW.

La prochaine fois, nous allons finaliser notre application, en remplissant les routines manquantes, etc.

CODE PAGE 7

#!/usr/bin/env python
import sys
from mutagen.mp3 import MP3
try:
  import pygtk
  pygtk.require("2.0")
except:
  pass
try:
  import gtk
  import gtk.glade
except:
  sys.exit(1)

puis la définition de la classe

class CreateurListeDeLecture:
  def __init__(self):
      self.gladefile = "CreateurListeDeLecture.glade"
      self.wTree = gtk.glade.XML(self.gladefile,"FenetrePrincipale")

et la routine principale

if __name__ == "__main__": 
createurLDL = CreateurListeDeLecture() 
gtk.main()

Ensuite, nous avons le dictionnaire qui devrait se trouver après la routine __init__.

def DicoEvenements(self): 
  dict = {"on_MainWindow_destroy": gtk.main_quit, 
    "on_boBtnQuitter_clicked": gtk.main_quit, 
    "on_boBtnAjouter_clicked": self.on_boBtnAjouter_clicked, 
    "on_boBtnSupprimer_clicked": self.on_boBtnSupprimer_clicked, 
    "on_boBtnEffacer_clicked": self.on_boBtnEffacer_clicked, 
    "on_boBtnHaut_clicked": self.on_boBtnHaut_clicked, 
    "on_boBtnMonter_clicked": self.on_boBtnMonter_clicked, 
    "on_boBtnDescendre_clicked": self.on_boBtnDescendre_clicked, 
    "on_boBtnBas_clicked":self.on_boBtnBas_clicked,
    "on_boBtnAPropos_clicked": self.on_boBtnAPropos_clicked, 
    "on_btnNomRepertoire_clicked": self.on_btnNomRepertoire_clicked, 
    "on_btnSauvegarderListe_clicked": self.on_btnSauvegarderListe_clicked} 
  self.wTree.signal_autoconnect(dict) 

CODE PAGE 8

  def on_boBtnAjouter_clicked(self,widget): 
      pass 
  def on_boBtnSupprimer_clicked(self,widget): 
      pass 
  def on_boBtnEffacer_clicked(self,widget): 
      pass 
  def on_boBtnHaut_clicked(self,widget): 
      pass 
  def on_boBtnMonter_clicked(self,widget): 
      pass 
  def on_boBtnDescendre_clicked(self,widget): 
      pass 
  def on_boBtnBas_clicked(self,widget): 
      pass 
  def on_boBtnAPropos_clicked(self,widget): 
      pass 
  def on_btnNomRepertoire_clicked(self,widget): 
      pass 
  def on_btnSauvegarderListe_clicked(self,widget): 
      pass 

CODE PAGE 9 (haut)

  def MessageBox(self,niveau,texte):
      if niveau == "info":
          dlg = gtk.MessageDialog(None,0,gtk.MESSAGE_INFO,gtk.BUTTONS_OK,texte)
      elif niveau == "warning":
          dlg = gtk.MessageDialog(None,0,gtk.MESSAGE_WARNING,gtk.BUTTONS_OK,texte)
      elif niveau == "error":
          dlg = gtk.MessageDialog(None,0,gtk.MESSAGE_ERROR,gtk.BUTTONS_OK,texte)
      elif niveau == "question":
          dlg = gtk.MessageDialog(None,0,gtk.MESSAGE_QUESTION,gtk.BUTTONS_YES_NO,texte)
      if niveau == "question":
          resp = dlg.run()
          dlg.destroy()
          return resp
      else:
          resp = dlg.run()
          dlg.destroy()

CODE PAGE 9 (bas)

  def on_boBtnAjouter_clicked(self,widget): 
      self.MessageBox("info","Clic sur bouton Ajouter...")
  def on_boBtnSupprimer_clicked(self,widget):
      self.MessageBox("info","Clic sur bouton Supprimer...")
  def on_boBtnEffacer_clicked(self,widget):
      self.MessageBox("info","Clic sur bouton Effacer...")
  def on_boBtnHaut_clicked(self,widget):
      self.MessageBox("info","Clic sur bouton Haut...")
  def on_boBtnMonter_clicked(self,widget):
      self.MessageBox("info","Clic sur bouton Monter...")
  def on_boBtnDescendre_clicked(self,widget):
      self.MessageBox("info","Clic sur bouton Descendre...")
  def on_boBtnBas_clicked(self,widget):
      self.MessageBox("info","Clic sur bouton Bas...")
  def on_boBtnAPropos_clicked(self,widget):
      self.MessageBox("info","Clic sur bouton À propos...")
  def on_btnNomRepertoire_clicked(self,widget):
      self.MessageBox("info","Clic sur bouton NomRepertoire...")
  def on_btnSauvegarderListe_clicked(self,widget):
      self.MessageBox("info","Clic sur bouton SauvegarderListe...")

CODE PAGE 10 (haut)

  def ReferencesWidgets(self): 
      self.txtNomFicher = self.wTree.get_widget("txtNomFicher")
      self.txtChemin = self.wTree.get_widget("txtChemin")
      self.boBtnAjouter = self.wTree.get_widget("boBtnAjouter")
      self.boBtnSupprimer = self.wTree.get_widget("boBtnSupprimer")
      self.boBtnEffacer = self.wTree.get_widget("boBtnEffacer")
      self.boBtnQuitter = self.wTree.get_widget("boBtnQuitter")
      self.boBtnAPropos = self.wTree.get_widget("boBtnAPropos")
      self.boBtnHaut = self.wTree.get_widget("boBtnHaut")
      self.boBtnMonter = self.wTree.get_widget("boBtnMonter")
      self.boBtnDescendre = self.wTree.get_widget("boBtnDescendre")
      self.boBtnBas = self.wTree.get_widget("boBtnBas")
      self.btnNomRepertoire = self.wTree.get_widget("btnNomRepertoire")
      self.btnSauvegarderListe = self.wTree.get_widget("btnSauvegarderListe")
      self.sbar = self.wTree.get_widget("statusbar3")
      self.context_id = self.sbar.get_context_id("Statusbar")

puis ajoutez un appel à ceci juste après l'appel à self.DicoEvenements() dans la routine __init__.

      self.ReferencesWidgets()

CODE PAGE 10 (bas)

  def AfficherAPropos(self): 
      apropos = gtk.AboutDialog() 
      apropos.set_program_name("Createur de liste de lecture") 
      apropos.set_version("1.0") 
      apropos.set_copyright("(c) 2011 by Greg Walters") 
      apropos.set_comments("Ecrit pour le Full Circle Magazine") 
      apropos.set_website("http://thedesignatedgeek.com") 
      apropos.run() 
      apropos.destroy() 

Maintenant, commentez (ou retirez simplement) l'appel à MessageBox dans la routine on_boBtnAPropos_clicked, et remplacez-le par un appel à la fonction AfficherAPropos. Cela devrait ressembler à :

def on_boBtnAPropos_clicked(self,widget):
      #self.MessageBox("info","Clic sur bouton APropos...")
      self.AfficherAPropos()

CODE PAGE 11

  def SetupTreeview(self):
      self.cNomFic = 0
      self.cTypeFic = 1
      self.cCheminFic = 2
      self.sNomFic = "NomFichier"
      self.sTypeFic = "Type"
      self.sCheminFic = "Dossier"
      self.treeview = self.wTree.get_widget("treeview1")
      self.AjouterColonne(self.sNomFic,self.cNomFic)
      self.AjouterColonne(self.sTypeFic,self.cTypeFic)
      self.AjouterColonne(self.sCheminFic,self.cCheminFic)
      self.listeLecture = gtk.ListStore(str,str,str)
      self.treeview.set_model(self.listeLecture)
      self.treeview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)

CODE PAGE 12

  def AjouterColonne(self,titre,idColonne): 
      colonne = gtk.TreeViewColumn(titre,gtk.CellRendererText(),text=idColonne) 
      colonne.set_resizable(True) 
      colonne.set_sort_column_id(idColonne) 
      self.treeview.append_column(colonne) 

CODE PAGE 13 (haut)

          dialog = gtk.FileChooserDialog("Choisir le repertoire de sauvegarde...",None, 
                              gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, 
                             (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, 
                              gtk.STOCK_OPEN, gtk.RESPONSE_OK)) 

CODE PAGE 13 (milieu)

class DialogueFichier:
  def AfficheDialogue(self,type,CheminCourant):
      if type == 0: # choix de fichiers
          #gtk.FileChooserDialog(titre,parent,action,boutons,backend)
          dialog = gtk.FileChooserDialog("Choisir les fichiers a ajouter...",None,
                             gtk.FILE_CHOOSER_ACTION_OPEN,
                             (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                              gtk.STOCK_OPEN, gtk.RESPONSE_OK))
      else:          # choix de repertoire
          dialog = gtk.FileChooserDialog("Choisir le repertoire de sauvegarde...",None,
                              gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
                             (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                              gtk.STOCK_OPEN, gtk.RESPONSE_OK))

Les deux lignes suivantes seront (en dehors de l'instruction if/else) :

      dialog.set_default_response(gtk.RESPONSE_OK)
      dialog.set_select_multiple(True)

CODE PAGE 13 (bas)

      if CheminCourant != "":
          dialog.set_current_folder(CheminCourant)
      reponse = dialog.run()

Ensuite, nous devons gérer la réponse venant du dialogue.

      if reponse == gtk.RESPONSE_OK:
          selectionFichiers = dialog.get_filenames()
          CheminCourant = dialog.get_current_folder()
          dialog.destroy()
          return (selectionFichiers,CheminCourant)
      elif reponse == gtk.RESPONSE_CANCEL:
          print 'Annulation, aucun fichier choisi'
          dialog.destroy()

CODE PAGE 15

  def on_boBtnAjouter_clicked(self,widget):
      fd = DialogueFichier()
      fichiersChoisis,self.CheminCourant = fd.AfficheDialogue(0,self.CheminCourant)        
      self.AjouterFichiers(fichiersChoisis)
      

Nous devons maintenant créer la fonction à laquelle nous venons de faire appel. Placez cette fonction après la routine on_btnSauvegarderListe_clicked.

  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
      self.NombreDeLignes += compteur
      self.sbar.push(self.context_id,"%s fichiers ajoutes sur un total de %d" % (compteur,self.NombreDeLignes))
issue48/tuto-python.txt · Dernière modification : 2011/06/16 08:37 de auntiee