Posts Tagged 'GTK'
2011-08-12

In the movies, when it is time to detonate the explosives, the person setting off the KABOOM has a sweet remote control with either a two part switch or with two separate switches. Damn, I love a good movie explosion.

The two part switch allows the user to:

  1. activate the explosive
  2. blow shit up

Because the switch has two components, activate and detonate, it is uncommon for there to be accidental explosions. This is a damn fine safety measure and when adding the ability to block an identi.ca user with heybuddy, I wanted a similar safety precaution; blocking someone by accident should not be possible. So that is what I did.

To accomplish this, it was necessary to do the following

  • create a disabled button that brings the hammer down!
  • create a checkbox that will activate the button

In GTK parlance, to disable a button, one needs to set the button sensitivity to false. Since I am using Python, this is accomplished thusly

widget.set_sensitive(False)

To show this idea as a small working bit of code, I wrote a quick Python-GTK app that almost does the bare minimum.

Enter the Python

#!/usr/bin/env python

import gtk
class application:
  def __init__(self):
    #I want to keep track of click counts
    self.click_count=0
    '''build the UI'''
    #make the window
    winder gtk.Window(gtk.WINDOW_TOPLEVEL)
    winder.connect("destroy",self.quit)
    #winder.set_default_size(300,300)
    winder.set_title("Super Trigger")
    #make some boxes
    vbox gtk.VBox()
    hbox gtk.HBox()
    #make some buttons
    self.button gtk.Button("Click Me")
    self.checkbutton gtk.CheckButton(label="Enable Button")
    #add the buttons to the hbox
    hbox.pack_start(self.button,False,False)
    hbox.pack_start(self.checkbutton,False,False)
    #add the hbox to the vbox
    vbox.pack_start(hbox,False,False)
    #create a Label
    self.label gtk.Label()
    #add the label to the vbox
    vbox.pack_start(self.label)
    #add the vbox to the window
    winder.add(vbox)
    
    #disable the button
    self.button.set_sensitive(False)
    
    #connect the buttons
    self.checkbutton.connect('toggled'self.toggle_clicked )
    self.button.connect('clicked'self.button_clicked)
    #show it all!
    winder.show_all()
    
  def toggle_clicked(selfwidget):
    #is the checkbutton active?
    active widget.get_active()
    self.button.set_sensitive(active)     
  
  def button_clicked(self,widget):
    #increment the click count
    self.click_count+=1
    #make a string 
    string "clicks: %d" %(self.click_count)
    #set the label to the string 
    self.label.set_text(string)
    
  def run(self):
    gtk.main()
    
  def quit(self,widget=None):
    gtk.main_quit()
  
if __name__=="__main__":
  application()
  a.run()

It may not be the best solution, but it is a solution and it works.

Well that's it for now. Quit reading, and go write some code.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
2 plus 7
Answer:
required
2010-08-16

Starting with version 2.18, the GTK library allowed for adding links to Labels, and this is exactly how I handled adding clickable URLs and names in heybuddy. Unfortunately, the version of GTK on Maemo devices is too old to use links in Labels.

Another unfortunate problem with links in Labels is that no GTK theme seems to have set a custom link color and all themes are stuck with the default bright blue link color. Egads! It sure would be nice if the user could set the color of links in heybuddy.

After searching and searching, I couldn't find an example of how to set a custom color for links in a GTK Label so I first needed to write some code to experiment with. The end result was a very simple example of exactly what I needed: a custom link color in a GTK Label.

Enter the Python

#!/usr/bin/env python
import gtk

gtk.Window()
gtk.Label()
"go to <a href='http://www.jezra.net'>jezra.net</a>"
l.set_markup(m)

style='''
style "label" {
  GtkLabel::link-color="#ff6600"
}
widget_class "*GtkLabel" style "label"
'''

gtk.rc_parse_string(style)

w.add(l)
w.show_all()
gtk.main()

shazaam! Custom color, baby!

Now quit reading, and go make things colorful.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
1 plus 8
Answer:
required
2010-07-16

Quite a few of my coding projects have a large text field where the user is expected to do a lot of typing. Actually, it would appear that none of my projects have a large text field. I suppose I should make markdowner and scripsi official jezra.net projects. Anyway, both of the previously mentioned apps have a large text field where the user types and types and types( hey, they are text editing programs ).

  • scripsi is a special purpose dual paned text editor written in Vala
  • markdowner is a special purpose text editor written in Python

Both applications use the GTK+ toolkit to create the user interface.

The Problem


When a GTK TextView widget is used to create a large text input area, if the user's cursor travels past the viewable area of the TextView, which happens when typing to the bottom of the screen, the cursor is below the visible area and the user will need to stop typing and scroll the TextView until it is visible. If the user were a typing ninja then I suppose they wouldn't care about typing without knowing what text is showing up in the TextView. Well, I am not a typing ninja.

The Solution


There is probably a more elegant solution to the problem, but I couldn't find it so you are stuck with what I figured out, and this is my solution:

When the text changes, get the coordinates of the cursor and scroll to those coordinates. This will ensure that the cursor is always visible to the user. How would you like to see a working example in Vala?

Enter the Vala

/* woohoo */
using Gtk;
public static void mainstring[] args) {
  //initialize gtk
  Gtk.init(ref args);
  //make a window
  Window window new Window();
  //make a textbuffer
  TextBuffer textBuffer new TextBuffer(null);
  //make a textview with our buffer
  TextView textViewnew TextView.with_buffer(textBuffer);
  //let the textview wrap at word or char
  textView.set_wrap_mode(WrapMode.WORD_CHAR);
  //we need to know when the text buffer changes
  textBuffer.changed.connect( ()=>{
    //make a textiterator
    TextIter iter TextIter();
    //what is the cursors x coordinate?
    int textView.virtual_cursor_x;
    //what is the cursors y coordinate?
    int textView.virtual_cursor_y;
    //get the text iter at the cursor's coordinates
    textView.get_iter_at_location(out iter,x,y);
    //scroll to the cursor's text iter
    textView.scroll_to_iter(iter,0,false,0,0);
  });
  //make a scrolledwindow to hold the textviw
  ScrolledWindow scroll new ScrolledWindow (nullnull);
  //set the scrollwindow's scrolling policy
  scroll.set_policy (PolicyType.AUTOMATICPolicyType.AUTOMATIC);
  //add the textview to the scollwindow
  scroll.add (textView);
  //add the scrollwindow to our window
  window.add(scroll);
  //show all the widgets
  window.show_all();
  //run the gtk main loop
  Gtk.main();
}

Save the above code in a file named "cursor.vala" and compile with

valac --pkg=gtk+-2.0 cursor.vala -o cursor

Now I need to port the code to Python and implement the change in markdowner.

Now quit reading, and go fix a problem with a dirty hack.

Comments
2010-07-16 x1101:
Have you considered combining the two apps, and simply having different modes? I know, I know you hate features, but just a thought for the betterment for jezracorp
2010-07-16 jezra:
combining the apps would require rewriting markdowner in Vala (because I would prefer to have the app in Vala) and I'm too lazy to port the markdown library to Vala.
2010-07-25 Windigo:
Just a note - raw Markdown is getting pushed to the planet through your RSS feed. Not necessarily a bad thing, but it always gives me the feeling I'm watching you write in your underwear. :s

Also, I'm still working on that FOSScon blog post; depending on how things go, you might even see it today!
2010-10-20 jezra:
yea, this is crap and unnecessary when using ScrollWindow.add(child).
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
8 plus 9
Answer:
required
2010-06-30

Recently, while writing code for heybuddy, I needed to:

  • display a tray icon
  • hide the window when "close window" is selected
  • show the window when the tray icon is clicked

Although getting this done was easy enough to do, finding out how to do it was a bit of a pain, so I thought it would be best if I shared my Python/GTK test code and save future developers from some pain.

Enter the Python

#!/usr/bin/env python
import gtk
class Win (gtk.Window):
    def __init__(self):
        gtk.Window.__init__(self)
        self.set_title("Close to tray")
        self.connect("delete-event"self.delete_event)
        #create a virtical box to hold some widgets
        vbox gtk.VBox(False)
        #make a quit button
        quitbutton gtk.Button("Quit")
        quitbutton.connect("clicked",self.quit)
        #add the quitbutton to the vbox
        vbox.pack_start(quitbutton)
        #create a label to say *something* and add to the vbox
        label=gtk.Label("This is a test of 'close to tray' functionality")
        vbox.pack_start(label)
        #add the vbox to the Window
        self.add(vbox)
        #show all of the stuff
        self.show_all()
        #make a status icon
        self.statusicon gtk.status_icon_new_from_stock(gtk.STOCK_GOTO_TOP)
        self.statusicon.connect('activate'self.status_clicked )
        self.statusicon.set_tooltip("the window is visible")
        #start the gtk main loop
        gtk.main()
    
    def quit(self,button):
        #quit the gtk main loop
        gtk.main_quit()
    
    def delete_event(self,window,event):
        #don't delete; hide instead
        self.hide_on_delete()
        self.statusicon.set_tooltip("the window is hidden")
        return True
        
    def status_clicked(self,status):
        #unhide the window
        self.show_all()
        self.statusicon.set_tooltip("the window is visible")
        
if __name__=="__main__":
    win Win()

The basics of what needs to be done:

  1. connect the Window's "delete-event" signal to a function
  2. in the function, have the window "hide_on_delete()"
  3. return True ( if True is not returned, the Window's children will be destroyed and everything will go to crap )

So there you have it, an easy way to "close to tray" or whatever you want to call it.

Now quit reading, and go ease a developer's pain.

Comments
2010-08-17 cs.ati:
Thank U! This is exactly what I need. U saved me a lot of time. Thanks again!
2010-08-17 jezra:
You are very welcome.
2010-12-09 Guilherme:
THANKS!!! That's just awesome! I thought I would spent a lot of time doing this, but then I found you and now 'm really relieved.
2016-01-25 nurul irfan:
it works, even in PyGObject. the return true is the most important. thanks.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
8 minus 3
Answer:
required
2010-01-21
Sometimes, some things sort of get stuck in my head. The most recent concept to get stuck in my head was the number of elements in immediate proximity to a specific element in a grid. It started with two implementations of the Game of Life, one written in Vala using SDL and one written in Python using Clutter.

Do you know what else deals with the relationship of elements in a grid? MineSweeper!

MineSweeper happens to be one of my favorite computer games. At this point, it should be noted that there are few implementations of minesweeper for Linux. Of the two versions that I found, one requires Gnome and one Required KDE. Seriously? Why does a simple game need to require Gnome or KDE libs? Oh well, I guess I'll just have to write my own.

Enter the Vala:
/* Copyright 2010 jezra lickter licensed GPL version 3 http://www.gnu.org/licenses/gpl-3.0.html compile with: valac --pkg gtk+-2.0 main.vala -o sweeper */ using Gtk; using Gdk; public class Cell:EventBox  {     signal void flip_cell();     signal void flag_cell();     public int row;     public int col;     private Button button;     private Label label;     const string ONE"blue";     const string TWO"dark green";     const string THREE"red";     const string FOUR"purple";     const string FIVE"orange";     const string SIX"yellow";     private Color red;     construct     {         button new Button();         //box = new Box();     }     public Cell(int rint c)     {         Color.parse("red",out red);         row=r;         col=c;         //create the cell         button.set_relief(ReliefStyle.HALF);         button.event.connect( (obj,event)=> { parse_event(event); } );         //box.pack_start(button, true, true, 0);         add(button);         label=new Label(null);         set_size_request(30,30);         //add(box);     }     public bool parse_event(Gdk.Event event)     {                  if(event.button.type == Gdk.EventType.BUTTON_PRESS)         {             if (event.button.button==1// && event.button.button<=3)             {                 flip_cell();             }else ifevent.button.button==)             {                 flag_cell();              }             return true;         }else{             return false;         }     }     public void flag()     {         button.set_label("*");     }     public void end_flag()     {         if(button.get_label()!="*")         {             show_label("*");         }     }          public void show_label(string str)     {         string color;         switch(str)         {             case "1":                 color ONE;                 break;             case "2":                 color TWO;                 break;               case "3":                 color THREE;                 break;             case "4":                 color FOUR;                 break;             case "5":                 color FIVE;                 break;             case "6":                 color SIX;                 break;             /*case "7":                 color = SEVEN;                 break; */             default:                 color "black";                 break;         }         label.set_markup("<span foreground=\"%s\">%s</span>".printf(color,str)      );         remove(button);         add(label);         label.show();     }     public void show_baddy()     {         modify_bg(StateType.NORMALred );         label.set_markup("<span background=\"red\" font_weight=\"heavy\">*</span>");         remove(button);         add(label);         //box.pack_start(label,true,true,0);         label.show();     }          public void disable()     {         button.set_state(Gtk.StateType.INSENSITIVE);     } } public class Application:Gtk.Window {     MainLoop loop;     bool[,] mineArray;     bool[,] flippedArray;     private Button startButton;     private ComboBox levelCombo;     private Table cellTable;     private int rows {get;set;}     private int cols {get;set;}     private int mines;     private Cell[,] cells;     private VBox vbox;     private bool game_playing;     private bool game_needs_new_table;     //define the small/medium/large values     const int small_rows=6;     const int small_cols=10;     const int small_mines=10;     const int medium_rows=10;     const int medium_cols=16;     const int medium_mines=26;     const int large_rows=12;     const int large_cols=20;     const int large_mines=40;     private int required_flips;     private int flip_count;     construct     {         type=Gtk.WindowType.TOPLEVEL;         set_resizable(false);         game_playing=false;         game_needs_new_table=false;         //we default to small         rows small_rows;         cols small_cols;         mines small_mines;     }          public Application()     {         destroy.connect(quit);         vbox new VBox(false,0);         HBox hbox new HBox(false,0);         startButton new Button.with_label("Start");         levelCombo new ComboBox.text();         levelCombo.append_text("Small");         levelCombo.append_text("Medium");         levelCombo.append_text("Large");         //levelCombo.append_text("Custom");         levelCombo.set_active(0);         levelCombo.changed.connectlevel_changed );                  Label dif new Label("Size:");         startButton.clicked.connectstart_game );         hbox.pack_start(startButton,false,false,0);         hbox.pack_start(dif,false,false,0);         hbox.pack_start(levelCombo,false,false,0);         hbox.set_spacing(5);         vbox.pack_start(hbox,false,false,0);         add(vbox);         show_all();         //create the first table         new_table();         cellTable.set_sensitive(false);     }          private void level_changed()     {         string val levelCombo.get_active_text();         switch(val)         {             case "Small":                 rows small_rows;                 cols small_cols;                 mines small_mines;                 break;             case "Medium":                 rows medium_rows;                 cols medium_cols;                 mines medium_mines;                 break;             case "Large":                 rows large_rows;                 cols large_cols;                 mines large_mines;                 break;         }         game_needs_new_table=true;         start_game();         //we should probably resize the window     }          public void start_game()     {         if(game_needs_new_table)         {             new_table();         }         game_playing=true;         //disable the start button         startButton.set_sensitive(false);         cellTable.set_sensitive(true);     }     public void end_game()     {         //disable unflipped cells         for(int r=0r<rowsr++)         {             for(int c=0c<colsc++)             {                 if(!flippedArray[r,c])                 {                     //if this is a mine, mark it as such                     if(mineArray[r,c])                     {                         cells[r,c].end_flag();                     }                     cells[r,c].disable();                 }             }         }         game_needs_new_table=true;         //enable the start button         game_playing false;         startButton.set_sensitive(true);     }          public void new_table()     {         vbox.remove(cellTable);         cellTable make_table();         vbox.pack_startcellTable,true,true,);         cellTable.show_all();     }          private Table make_table()     {         //reset the required flips         required_flips rows*cols mines;         //reset the flip_count         flip_count=0;         mineArray new bool[rows,cols];         flippedArray new bool[rows,cols];         cells new Cell[rows,cols];         Table table new Table(rows,cols,true);         forint r=0;r<rows;r++ )         {             for int c=0c<colsc++ )             {                 flippedArray[r,c]=false;                 mineArray[r,c]=false;                 cells[r,c] = new Cell(r,c);                 cells[r,c].flip_cell.connect( (cell)=>{ flip_cell(cell); } );                 cells[r,c].flag_cell.connect( (cell)=>{ flag_cell(cell); } );                 //add the cell to the table lrtb                 table.attach(cells[r,c],c,c+1,r,r+1,AttachOptions.FILL ,AttachOptions.FILL ,0,0);                    }         }            //make the mines         int minecount=0;         int randomrow;         int randomcol;         Rand rand new Rand();         while(minecount<mines)         {             //pick a random row             randomrowrand.int_range(0,rows);             //pick a random col             randomcol rand.int_range(0,cols);             if(!mineArray[randomrow,randomcol])             {                     mineArray[randomrow,randomcol]=true;                     minecount++;             }         }         return table;     }          public void flag_cellCell )     {         c.flag();     }          private void flip_cell(Cell c)     {         //consider this flipped         flippedArray[c.row,c.col]=true;         if(mineArray[c.row,c.col])         {             c.show_baddy();             end_game();         }else{             detect_neighboring_mines(c.row,c.col);             flip_count+=1;             if(flip_count==required_flips)             {                 //we did it!                 end_game();             }         }     }     private void detect_neighboring_mines(int rint c)     {         string label="";         int mine_count 0;         int r_min r-1;         int r_max r+1;         int c_min c-1;         int c_max c+1;         if(r==0)         {             r_min=r;         }         else if (== rows-1)         {             r_max=r;         }         if c==)         {             c_min=c;         }         else if (== cols-1)         {             c_max=c;          }            int tr r_min;         int tc c_min;         while(tr <= r_max)         {             tc c_min;             while(tc <= c_max)             {                 if tr!=|| tc!=)                 {                     if mineArray[tr,tc])                     {                         mine_count+=1;                     }                 }                 tc+=1;             }             tr+=1;         }         if(mine_count>0)         {             label mine_count.to_string();         }else{             expand_from_empty_cell(r,c);         }         cells[r,c].show_labellabel );     }          private void expand_from_empty_cell(int rint c)     {         int r_min r-1;         int r_max r+1;         int c_min c-1;         int c_max c+1;         if(r==0)         {             r_min=r;         }         else if (== rows-1)         {             r_max=r;         }         if c==)         {             c_min=c;         }         else if (== cols-1)         {             c_max=c;          }            int tr r_min;         int tc c_min;         while(tr <= r_max)         {             tc c_min;             while(tc <= c_max)             {                 if tr!=|| tc!=)                 {                     if ( !flippedArray[tr,tc])                     {                         //check this cell                         flip_cell(cells[tr,tc]);                         //detect_neighboring_mines(tr,tc);                     }                 }                 tc+=1;             }             tr+=1;         }     }          public void run()     {         loop new MainLoop();         loop.run();     }     public void quit()     {         loop.quit();     } } public static void mainstring[] args) {     Gtk.init(ref args);     Application app new Application();     app.run(); }

YUCK! That is some ugly code. Normally, when I write some code, I will first write the steps I want the code to take as comments and I will then write the actual code of the app. Obviously, that was not the case this time. The passion took me.

Requirements
GTK
GDK
Vala

Compiling
valac --pkg gtk+-2.0 main.vala -o sweeper


Screenshots




The game is over when all of the non-mine squares have been selected. Yes, I know that there is no timer, or flag counter and honestly, I don't care; I just want to play.

Now stop reading, and go sweep something.
Comments
2010-01-21 Julian Aloofi:
Your blog is slowly becoming one of the most valuable resources for Vala code examples ^^
At least for newbies like me. Thanks for sharing the code!
2010-01-21 jezra:
I wasn't aware that you are delving into the world of Vala. Good for you! How has the experience been so far?
2010-01-22 Julian Aloofi:
I was just messing around with it a bit, but it looks like it's developing really well.
I really like the language, but currently I'm more focussed on others. Vala still is in an early stage I think, and breaks source compability too often for me :(
2010-02-16 fadereu:
Also, check out the game of GO.
2011-07-10 ray:
Very useful code example. Perhaps the compiler is a little tighter now, but needed to make flip_cell() and flag_cell() public, then add "return" before parse_event() in Cell constructor, to get it to compile. It works, but get a container_remove assertion failed. Finally, had problems with lack of CRs, when copying your code. However, I am very grateful to you, for the example.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
6 minus 4
Answer:
required
2009-05-19
For a while now, after starting my computer and launching a terminal, I've been maximizing and undecorating the terminal window. Unfortunately, I was unable to find a way to use command line arguments to launch my current favorite terminal: Sakura, in maximized and undecorated mode. To put it simply, I had three options:
  • Try to find another terminal app that does what I need
  • Edit the Sakura soure code to add the maximize and undecorate option
  • Code my own app

I elected for "code my own app" and I called it muterm because it is a (m)aximized (u)ndecorated (term)inal emulator. This would also be a good introduction for anyone interested in adding a terminal emulator to a Vala project.
Here is my main.vala code:
using Gtk; using Vte; private class Term {     private Term()     {         //start with a gtk window         Window w;         //we also need a vte terminal         Terminal term;         //create our new window         new Window(Gtk.WindowType.TOPLEVEL);         //create the new terminal         term new Terminal();         //connect exiting on the terminal with quiting the app         term.child_exited.connect ( (t)=> { Gtk.main_quit(); } );         //fork a command to run when the terminal is started         term.fork_command(null,null,null,nulltruetrue,true);         //add the terminal to the window         w.add(term);         //maximize the window         w.maximize();         //undecorate the window         w.set_decorated(false);         //try to set the icon         try{             w.set_icon_from_file("/usr/share/pixmaps/gnome-term.png");         }catch(Error er)         {             //we don't really need to print this error             stdout.printf(er.message);         }         //show our window and all children of our window         w.show_all();     }     private void run()     {         //start the gtk mainloop         Gtk.main();     }     private static void main(string[] args)     {         Term t;         Gtk.init(ref args);         new Term();         t.run();     } }

The code is compiled with:
valac --pkg gtk+-2.0 --pkg vte main.vala -o muterm

It took me a while to realize that I didn't need to have values for all of the Vte.Terminal.fork_command() function. Howerver, I should allow passing a directory as an arguement and have muterm start in that directory as the `pwd`. Copying and pasting text in the terminal would also be a great idea. Maybe, we'll see what the future holds.

I'd show a screenshot, but really, how different do terminals really look?
Comments
2009-05-25 jezra:
Backspace wasn't being handled correctly when using "screen", so I needed to add the following after creating the new VTE Terminal instance:
term.set_backspace_binding(TerminalEraseBinding.ASCII_BACKSPACE);
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
6 minus 1
Answer:
required
subscribe
 
2016
2015
2014
2013
2012
2011
2010
December
November
October
September
August
July
June
May
April
March
February
January
2009
December
November
October
September
August
July
June
May
April
March
February
January
2008