FC49 Python编程
出自Full Circle 中文项目主页
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.
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 FileDialog. 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.
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. 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. 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.
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. 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).
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]
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.
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.")
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. 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()
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.