WordPy offline blogging tool

One of the things that I found the other day while I was surfing the web looking for information on Python was this WordPress Python library. Since I use WordPress for this site I thought that I’d play with it a bit. The result is this tutorial about using the WordPress library, PyGTK, and Glade.

The first think to do is download the library, extract it form the archive, and install it. Enter the following on the command line in the directory you downloaded the library to in order to install it:

The GUI

The first thing that we want to do is create the main GUI, here are the steps for that:

Note: These steps are very general if you have trouble following them post a question or read the two initial PyGTK/Glade tutorials in order to become familiar with Glade.

Add a window to the project. Set it’s name to be: ‘”wndMain” and its title to be: “WordPy”. Set it’s default height to be 350 and it’s default width to be 400.

Add a vertical box with five rows

In the first row add horizontal box with two columns, set it’s “Expand” option on the Packing tab to No. Then add a label that says “Title:” in the first column set it’s width to be 44, and in the second column add a Text Entry. Call the text entry: “enTitle”. Set the Text Entries padding to be 3.

In the second rod add a horizontal box with 13 items. make the first item A label, make it’s “Text:”, make it’s width 44, and make it’s X Align 0.00. Then make the next twelve items Buttons. The buttons will be used to mimic the buttons available when writing a wordpress post:

(b)(i)(link)(b-quote)(del)(ins)(img)(ul)(ol)(li)(code)(more)

Name each button using the following pattern: btnBold, btnItalic, etc, and add a clicked handler to each button.

In the forth row add a Text View. On the Widget tab set the Text View’s (not the Scrolled Window) name to be “txtPost” and its wrapping option to be “Word”.

Now add another Horizontal Button box to the final row. Set it’s layout to be “End” and its spacing to be 3. On the Packing tab set the padding to be 2 and Expand to No. Set the left-most buttons name to be btnSettings, its label to be “Settings”, and it’s icon to be the “Properties” icon. Set the other buttons name to be btnPost, it’s label to be “Post” and it’s icon to be the Up icon.

Add a handler for the “clicked” signal for each button. We will use this signal to perform actions when the user clicks on the button.

Add a handler for the wndMain destroy signal (not the destroy_event signal, the destroy signal)

That’s it for the main window, once you have completed all of those steps you should be left with something like this:

Note that I used icons for my buttons instead of using text. The icons are taken from the latest version of the gnome-icon-theme.

The next GUI element that we are going to create is the Settings dialog that will come up when you press the settings button:

Create a new dialog by pressing the dialog button. Select the Standard Button Layout with Cancel and Ok as your buttons. Set the dialogs name to be “dlgSettings” and its title to be “Settings”

Add a new table with three rows and two columns.

In the first row add a Label in the first column and set its label to be “URL:”. In the second column add a Text Entry and set it’s name to be “enURL”. On the packing tab set the Text Entries H Padding and V padding to both be 2

Replicate step three in row two, except set the Label’s label to be “Username” and the name of the Text Entry to be “enUsername”.

Replicate step three in row three, except set the Label’s label to be “Password” and the name of the Text Entry to be “enPassword”. Set the Text Visible option to No, this will display the invisible char instead of the actual text, making this function like a normal password entry.

After following those steps you should be left with something that resembles the following:

Finally we need to create the dialog that will be used when the user wants to insert a link into their post:

Add another dialog with Ok and Cancel buttons. Set the dialogs name to be dlgLink and it’s title to be “Insert Link”.

Add a vertical box with two rows, in the first row add a Label, set it’s label to be: “Enter the URL” and it’s X Allign to be 0.00. In the second row add an Text Entry and set it’s name to be enURL.

On the Widget tab of dlgLink dialog’s properties set the resizable property to No, and on the Common tab set the width to be 300.

your dlgLink should look something like this:

The Code – Connecting Glade with PyGTK

Here is the basic code that we use to start up our little WordPy Application and connect it with out glade file (it is the basic code that I used in my twoother tutorials):

This code simply shows our wndMain and connects all of our signals with internal functions. The next bit of code that I’m going to show is a simple holder class to hold our WordPressBlogSettings, the settings will be pickled into and from a file just to make using this easier. However the file is text readable so if you are concerned about your wordpress password you might not want to use this:

Now that we have that class done, we’ll add a WordPressBlogSettings member variable to our WordPy class in the __init__ function:

"""Get the name of the blog data file, sys.path[0] is the
path where the script is located"""
self.dat_file = os.path.join(sys.path[0], "Blog.dat")
self.BlogSettings = WordPressBlogSettings(self.dat_file)

The Code – Settings Dialog

def show_dlgSettings(self, BlogSettings):
"""This function will show the BlogSettings dialog
It will return the result code from running the dlg.
BlogSettings - An instance of WordPressBlogSettings. Its
values will only be updated if the user presses the OK button
returns - The result from running the dlg"""
#init to cancel
result = gtk.RESPONSE_CANCEL
#load the dialog from the glade file
wTree = gtk.glade.XML(self.gladefile, "dlgSettings")
#Get the actual dialog widget
dlg = wTree.get_widget("dlgSettings")
#Get all of the Entry Widgets and set their text
enURL = wTree.get_widget("enURL")
enURL.set_text(BlogSettings.URL)
enUsername = wTree.get_widget("enUsername")
enUsername.set_text(BlogSettings.Username)
enPassword = wTree.get_widget("enPassword")
enPassword.set_text(BlogSettings.Password)
#run the dialog and store the response
result = dlg.run()
if (result==gtk.RESPONSE_OK):
#get the value of the entry fields
BlogSettings.URL = enURL.get_text()
BlogSettings.Username = enUsername.get_text()
BlogSettings.Password = enPassword.get_text()
#we are done with the dialog, destroy it
dlg.destroy()
#return the result
return result

Basically this function takes a WordPressBlogSettings object as a parameter and uses those settings to set the default text on the dialog. The dlgSettings is shown, and if the user clicks the Ok button then the WordPressSettings are updated.

To show the dialog and update the BlogSettings the following code is used:

def on_btnSettings_clicked(self, widget):
"""Called when the settings button is clicked. It will
show the dlgSettings dialog and let the user set the WordPress
blog settings."""
self.show_dlgSettings(self.BlogSettings)

Error Dialog

A quick helper function that I wrote for this project was a quick and easy way to show an error dialog:

def show_error_dlg(self, error_string):
"""This Function is used to show an error dialog when
an error occurs.
error_string - The error string that will be displayed
on the dialog.
"""
error_dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR
, message_format=error_string
, buttons=gtk.BUTTONS_OK)
error_dlg.run()
error_dlg.destroy()

It’s a simple function that accepts and error string as a parameter and then uses a gtk.MessageDialog to show the error.

Working with the gtk.TextView

I found working with the gtk.TextView object to be quite difficult, it uses an approach to Text controls that I really had not encountered before. Mainly the gtk.TextViewgtk.TextBuffer relationship. For a full understanding of the gtk.TextView/gtk.TextBuffer relationship you might want to read the GTK+ reference guide. Here is a brief excerpt that may explain how they two are related:

GTK+ has an extremely powerful framework for multiline text editing. The primary objects involved in the process are GtkTextBuffer, which represents the text being edited, and GtkTextView, a widget which can display a GtkTextBuffer. Each buffer can be displayed by any number of views…

Most text manipulation is accomplished with iterators, represented by a GtkTextIter. An iterator represents a position between two characters in the text buffer. GtkTextIter is a struct designed to be allocated on the stack; it’s guaranteed to be copiable by value and never contain any heap-allocated data. Iterators are not valid indefinitely; whenever the buffer is modified in a way that affects the number of characters in the buffer, all outstanding iterators become invalid. (Note that deleting 5 characters and then reinserting 5 still invalidates iterators, though you end up with the same number of characters you pass through a state with a different number).

Because of this, iterators can’t be used to preserve positions across buffer modifications. To preserve a position, the GtkTextMark object is ideal. You can think of a mark as an invisible cursor or insertion point; it floats in the buffer, saving a position. If the text surrounding the mark is deleted, the mark remains in the position the text once occupied; if text is inserted at the mark, the mark ends up either to the left or to the right of the new text, depending on its gravity. The standard text cursor in left-to-right languages is a mark with right gravity, because it stays to the right of inserted text.

So, a gtk.TextBuffer is the actual text, and it can be displayed in more then one gtkTextView objects which decide how the text will look. gtk.TextIter objects are used to iterate through the text, but they will not be valid forever since the text might change. To keep track of a position in the text “forever” gtk.TextMarks must be used. You can also use tags and things to format text in a gtk.TextView but we won’t be using them at all in this tutorial.

Because of the complicated relationship between all of these pyGTK objects I decided to write some helper functions to make working with the text easier.

The first thing we needed to do was get the gtk.TextView and gtk.TextBuffers that we will be working with. In order to do this we add the following code to the __init__ function:

Now that we have the gtk.TextView and the gtk.TextBuffer we can start creating the helper functions. The first function will get the start and end gtk.TextIter objects that represent the selection:

def get_selection_iters(self):
"""This function gets the start and end selection
iters from the text view. If there is no selection
the current position of the cursor will be returned.
Returns - start,end - gtk.TextIter objects"""
#init
start = None
end = None
#First check to see that the text buffer is valid
if (not self.txtBuffer):
self.show_error_dlg("Text buffer not available")
return start,end
#Get the selection bounds
bounds = self.txtBuffer.get_selection_bounds();
if (bounds):
#If there is a selection we are done
start,end = bounds
else:
#There is no selection so just get the cursor mark
cursor_mark = self.txtBuffer.get_insert()
"""Set start and end to be gtk.TextIter objects at the
position of the cursor mark"""
start = self.txtBuffer.get_iter_at_mark(cursor_mark)
end = self.txtBuffer.get_iter_at_mark(cursor_mark)
return start, end

The first thing this function does is initialize our returns values (start and end) to None, just so that the caller function can tell whether the function succeeded or not. The next thing we do is attempt to get the current selection using gtk.TextBuffer.get_selection_bounds() function. If there is no selection then we use the gtk.TextBuffer.get_insert() function to get the gtk.TextMark that is at the current position of the cursor. Then we use that gtk.TextMark to set the start and end gtk.TextIter object and we’re done.

The next two helper functions make use of the get_selection_iters() function. The first is used to insert text into the gtk.TextView, and the second is used to wrap the currently selected text in the gtk.TextView with a start tag and an end tag. First the insert_text() function:

def insert_text(self, text):
"""This function inserts text into the text buffer
self.txtBuffer at the current selection. If text is
selected it will be overwritten, otherwise it will simply be
inserted at the cursor position
text - The text to be inserted in the buffer
"""
start, end = self.get_selection_iters();
if ((not start)or(not end)):
self.show_error_dlg("Error inserting text")
return;
#Delete the selected text (start and end will be equal after)
self.txtBuffer.delete(start,end)
#Save a mark at the start position since after we insert
#the text start will be invalid
start_mark = self.txtBuffer.create_mark(None, start, True)
#Insert, end will be set to the end insert position
self.txtBuffer.insert(end,text)
start = self.txtBuffer.get_iter_at_mark(start_mark)
#select the text, use end as the first param so that
#it will be the cursor position
self.txtBuffer.select_range(end,start)
#delete the start mark
self.txtBuffer.delete_mark(start_mark)

The comments in the code should help explain what is happening in this function, but I will explain it briefly. First we get the start and end gtk.TextIter objects representing the current selection. Then we delete that selection and create a gtk.TextMark at the start location. The gtk.TextMark is created with left gravity, which means that when text is inserted at it’s position it will stay to the left of the text rather then being pushed to the right.

Then we insert the text at the end position, which will insert the text and update the position of the end gtk.TextIter to point at the end of the inserted text. Next we get the start gtk.TextIter based off of our start_mark and select all of the text that we just inserted. Finally we delete the gtk.TextMark that we created and the function is done.

The next text helper function that we will need is a function to wrap text, it’s similar to the insert_text() function except this function will wrap any selected text with two tags. If no text is selected then the two tags will be inserted at the cursors position.

def wrap_selection(self, start_tag, end_tag):
"""This fucntion is used to wrap the currently selected
text in the gtk.TextView with start_tag and end_tag. If
there is no selection start_tag and end_tag will be
inserted at the cursor position
start_tag - The text that will go at the start of the
selection.
end_tag - The text that will go at the end of the
selection."""
start, end = self.get_selection_iters();
if ((not start)or(not end)):
self.show_error_dlg("Error inserting text")
return;
#Create a mark at the start and end
start_mark = self.txtBuffer.create_mark(None,start, True)
end_mark = self.txtBuffer.create_mark(None, end, False)
#Insert the start_tag
self.txtBuffer.insert(start, start_tag)
#Get the end iter again
end = self.txtBuffer.get_iter_at_mark(end_mark)
#Insert the end tag
self.txtBuffer.insert(end, end_tag)
#Get the start and end iters
start = self.txtBuffer.get_iter_at_mark(start_mark)
end = self.txtBuffer.get_iter_at_mark(end_mark)
#Select the text
self.txtBuffer.select_range(end,start)
#Delete the gtk.TextMark objects
self.txtBuffer.delete_mark(start_mark)
self.txtBuffer.delete_mark(end_mark)

If you understood the insert_text() function then you will probably understand the wrap_selection() function as they use very similar techniques. The first thing is to get the start and end selection gtk.TextIters, then create gtk.TextMark objects at both locations. We create the gtk.TextMark objects at both locations and with different orientations so that when we insert the text we will still know where the original positions were (relative to the text we inserted) are so that we can select everything once we are done.

Then we insert the start_tag, re-get the end gtk.TextIter (since our original is no longer valid after we inserted the text) and insert the end_tag. After that we simply re-get both the start and end gtk.TextIter objects and then select all the text in between the start_tag and end_tag inclusively.

The Code Ã¢Â€Â“ The easy buttons

After we have the two text helper functions you can see how easy it is to populate most of the on_clicked() handlers for our buttons. Except for on_btnImage_clicked() and on_btnLink_clicked() here are all of the button clicked handlers.

Pretty straight forward except for maybe the on_btnIns_clicked() and on_btnDel_clicked() functions which make use of the built-in datetime module and the strftime function to format the current date in the correct manner (YYYYMMDD). The following line needs to be added to the start of the code:

import datetime

The Code Ã¢Â€Â“ Link Button, Image button, and gtk.FileChooserDialog

Now we need to get the on_btnLink_clicked() and the on_btnImage_clicked() functions working. First off I’ll show you the on_btnLink_clicked() since it’s the simplest of the two:

def on_btnLink_clicked(self, widget):
"""Called when the link button is clicked"""
#load the dialog from the glade file
wTree = gtk.glade.XML(self.gladefile, "dlgLink")
#Get the actual dialog widget
dlg = wTree.get_widget("dlgLink")
enURL = wTree.get_widget("enURL")
enURL.set_text("HTTP://")
"""Get the selection not and reselect becuase somethimes
the dialog will remove the selection"""
start, end = self.get_selection_iters()
#run the dialog
if (dlg.run()==gtk.RESPONSE_OK):
#Reset the selection
if ((start)and(end)):
#Select the text
self.txtBuffer.select_range(end,start)
#Wrap the selection with the value of the entry fields
self.wrap_selection("<a href=\"%s\">" % enURL.get_text(),"</a>")
dlg.destroy()

Next we get the URL text entry widget and set it’s default text to be “HTTP:\\”. After that we get the current selection, the reason that we do this is because sometimes I noticed that showing the dialog caused the selection to be removed from the gtk.TextView. So the quick and easy way to get around this (since the helper function was already written) was to simply get the selection before the dialog is visible for any length of time, and then reset the selection once the used has exited the dialog.

Then we have to run the dialog and wait to see if the used clicks the Ok or Cancel button. If they click Cancel nothing happens, we destroy the dialog and the function is over. If, however, they click Ok we will then reset the selection, and wrap it with the link tags.

The next thing we are going to do is work on letting the user add an image to their blog post. To do this we will respond to the on_btnImage_clicked function. The first thing we need to do is let the user browse for an image. We will use a gtk.FileChooserDialog, for more information on using a gtk.FileChooserDialog see section 16.6 in the PyGTK Tutorial, where much of the following code is taken:

def browse_for_image(self):
"""This function is used to browse for an image.
The path to the image will be returned if the user
selects one, however a blank string will be returned
if they cancel or do not select one."""
file_open = gtk.FileChooserDialog(title="Select Image"
, action=gtk.FILE_CHOOSER_ACTION_OPEN
, buttons=(gtk.STOCK_CANCEL
, gtk.RESPONSE_CANCEL
, gtk.STOCK_OPEN
, gtk.RESPONSE_OK))
"""Create and add the Images filter"""
filter = gtk.FileFilter()
filter.set_name("Images")
filter.add_mime_type("image/png")
filter.add_mime_type("image/jpeg")
filter.add_mime_type("image/gif")
filter.add_pattern("*.png")
filter.add_pattern("*.jpg")
filter.add_pattern("*.gif")
file_open.add_filter(filter)
"""Create and add the 'all files' filter"""
filter = gtk.FileFilter()
filter.set_name("All files")
filter.add_pattern("*")
file_open.add_filter(filter)
"""Init the return value"""
result = ""
if file_open.run() == gtk.RESPONSE_OK:
result = file_open.get_filename()
file_open.destroy()
return result

The browse_for_image() function will show the following dialog:

The first thing we do in the function is create our gtk.FileChooserDialog. When constructing the gtk.FileChooseDialog we set it’s title, the fact that it is a File Open dialog, and what buttons it will have (Ok and Cancel). Then we use two gtk.FileFilter‘s to control what sort of files the user will be allowed to browse for. The first file filter sets up the common image types that they will be allowed to browse for and the second filter lets them browse for any file type.

Then the gtk.FileChooserDialog is shown and if the user presses the Ok button the full path to the file that they selected is returned, otherwise an empty string (“”) is returned.

This function simply creates the wordpress client that we need when working with the wordpress library. It’s important to note that the blog settings must be correct for the client to be created properly.

To include the wordpress library the follow code is added to the top of the program:

With all the helpers created this function really isn’t that difficult. First we try to get the wordpress client, if we cannot we let the user know that their settings are probably incorrect. If we get the client correctly we then browse for an image file.

If we get an image file returned we attempt to upload it to our blog, if that succeeds we insert image HTML code, if it fails in any way we inform the user.

Posting!

Well we’re almost done now, all we need to do is upload the post to the blog. We do this in the on_btnPost_clicked() function:

So we start off by getting the wordpress cleint, and if that works we get the post’s title and the post’s text. Then we attempt to upload the post, if we get an exception then the post has failed and we let the user know using our show_error_dlg() function. If we do not get an exception then we show the user a success dialog.

The End

Well that’s it for the WordPy tutorial, I hope to improve on WordPy and get it working as an actual application that can be installed and used by users. If anyone is interested in this please let me know.

58 thoughts on “WordPy offline blogging tool”

Comment navigation

Hello, I just found your blog via Jono’s blog. Seems really good from what I have read so far.

It’s particularly relevant for me as I have just this last week started learning python myself and have been writing a similar journal (although I am not a programmer by trade, just a humble web designer) .

Using PyGTK for this wordpress app is really cool, I’ve downloaded a copy of the source and will take a gander.

Hi. Great tutorial! However, I tried to post from WordPy and I received the following message: “Error publishing post”. Would be interesting to provide more details about the error because I really don’t know what happened.

Great point! I have updated the code so that when errors occur it outputs more error information to the user. Thanks for the tip! You can download the newext version here, or from the links in the tutorial.

Great Tutorials, this might sound funny. I haven’t sat down and done them but they give a good overview of the process one would go through. Great that you started simple, and have built up in sufficient steps that I can follow. Often programming examples start small & simple and then jump in complexity leaving the beginner wondering how to make the transistion from novice to expert.

I look forward to future writings on this topic. Not sure where you’re heading next but perhaps packaging ones application for installations? (honestly I haven’t looked on the net so could be something suitable already).

Thanks for the compliments! The mean a lot since I am trying to pace the tutorials so that beginners won’t get left behind, most of this happens by accident I think since I’m trying to learn python at the same time!

Packaging is definitely something that I want to tackle, and will probably be the next tutorial or the one after that.

Great tutorials! The explanations given are really clear and thorough, and the whole thing is very easy to follow. I also like that the code you use demonstrates good style and technique. It also seems GTK is a really neato toolkit.

P.S. Sorry for the weird email, but I just don’t give out info on the first date.

Unfortunately, the “wordpress” library is exactly that: corresponding to no known standard other than whatever wordpress happens to implement. It’s neither Blogger nor MetaWeblog. It’s a mishmash of both plus some Moveable Type thrown in for good measure.

I really can’t figure out why using width-request and not “Default width” for a window.

Furthermore, the value that we put in width-request is pixels value? It is a delta compared to what (as if we put 0, it is not a null size)? I think this a pure gtk question but I do not find any relevant info about that

I completely agree with Christopher B. It really is inspiring to see carefully considered typography playing a central role in a siteÃ¢Â€Â™s design. Thanks also for compiling the appetising list of resources that I am very much looking forward to reading.

Well, yesterday I was reading the following tutorial by Mark Mruss: Ã¢Â€ÂœWordPy offline blogging toolÃ¢Â€Â and I decided port this application to Maemo. I removed the Glade connection and did that it only

Great post! Congratulations! but i have a question… From these files you can create a form offline (with multiple fields) to send subsequently to a site in php / mysql?
Is I use a Nokia N900 (Maemo 5) and sometimes do not have Internet access, but I don’t can lose of the formulary information. What do you think?
Thanks very much!

I really can’t figure out why using width-request and not “Default width” for a window.

Furthermore, the value that we put in width-request is pixels value? It is a delta compared to what (as if we put 0, it is not a null size)? I think this a pure gtk question but I do not find any relevant info about that