#!/usr/bin/env python2.5
# 
# Copyright (c) 2009 Daniel Would
# Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#
#

# ============================================================================
# Name        : witter.py
# Author      : Daniel Would
# Version     : 0.1
# Description : Witter
# ============================================================================

#This is the bunch of things I wound up importing
#I think I need them all.. 
import gtk
import gtk.glade 
import pygtk
import hildon
import urllib2
import urllib
import mimetools, mimetypes
import base64
import urlparse
import simplejson
import socket
import re
import string
import osso
import os
import webbrowser
import ConfigParser
import pycurl


#Initially I found I'd hang the whole interface if I was having network probs
#because by default there is an unlimited wait on connect so I set
#the timeout to 10 seconds afterwhich you get back a timeout error
# timeout in seconds
timeout = 10
socket.setdefaulttimeout(timeout)



#the main witter application
class Witter():
    #first an init method to set everything up    
    def __init__(self):
         #make the hildon program
        self.program = hildon.Program()
        self.program.__init__()

        osso_c = osso.Context("witter","0.1.0", False) 
        # set name of application: this shows in titlebar
        gtk.set_application_name("Witter")
        #Set the Glade file
        self.gladefile = "/usr/share/witter/witter.glade"  
        self.wTree = gtk.glade.XML(self.gladefile) 
        #map all the signals
        dic = { 
            "newTweet" : self.newTweet,
            "getTweets" : self.updateSelectedView,
            "storecreds" : self.store_creds,
            "on_timeline_clicked" : self.switchView,
            "on_mentions_clicked" : self.switchView,
            "on_direct_messages_clicked" : self.switchView,
            "on_search_clicked" : self.switchView,
            "on_trend_clicked" : self.switchView,
            "on_insert_clicked" : self.twitPic,
            "on_friends_clicked" : self.switchView,
        }
        self.wTree.signal_autoconnect( dic )
	self.textcolour="#FFFFFF"
        #
        #go read config file
        #
        self.readConfig()
        #being lazy this just uses basic auth and I am not doing anything
        #yet to store uid/pwd so for the moment just put info here
        
        
        #at one point I had the text different colours
        #I may do again
        self.namecolour = self.textcolour
        self.tweetcolour = self.textcolour
        
        self.defaultwidth = 790
        #default to colours above, but check if we're on fremantle and change
        #to appropriate colours if we are
        self.checkVersion()
        #This being a hildon app we start with a hildon.Window
        self.window = hildon.StackableWindow()

        #connect the delete event for closing the window
        self.window.connect("delete_event", self.quit)
        #add window to self  
        self.program.add_window(self.window)
        #reparent the vbox1 from glade to self.window
        self.vbox = self.wTree.get_widget("vbox1")
        pannedWindow = hildon.PannableArea()
        # hildon.hildon_pannable_area_new_full(mode, enabled, vel_min, vel_max, decel, sps)

        #self.scrolled_window = self.wTree.get_widget("scrolled_window")
        self.vbox.reparent(self.window)
        self.vbox.pack_end(pannedWindow)
        
        self.urlmenu = self.build_right_click_menu()
        # create a menu object by calling a method to deine it
        self.menu = self.create_m5_menu(self)
        # add the menu to the window
        self.window.set_app_menu(self.menu)
        #
        
        
        self.last_id=None
        self.last_dm_id=None
        self.last_mention_id=None
        self.last_public_id=None
        
        #self.urlmenu = gtk.Menu()
        # define a liststore we use this to store our tweets and some associated data
        # the fields are : Name,nameColour,Tweet+Timestamp,TweetColour,id, type
        self.liststore = gtk.ListStore(str, str, str, str, str, str)
        #then we want the same again to store dm's, mentions & pubilc timeline separately
        self.dmliststore = gtk.ListStore(str, str, str, str, str, str)
        self.mentionliststore = gtk.ListStore(str, str, str, str, str, str)
        self.publicliststore= gtk.ListStore(str, str, str, str, str, str)
        self.trendliststore= gtk.ListStore(str, str, str, str)
        self.friendsliststore = gtk.ListStore(str,str,str,str,str,str)
        self.searchliststore = gtk.ListStore(str,str,str,str,str,str)
        #we want auto-complete of @references 
        self.tweetText = self.wTree.get_widget("TweetText")
        tweetComplete = gtk.EntryCompletion()
        tweetComplete.set_model(self.friendsliststore)
        tweetComplete.set_text_column(0)
        tweetComplete.set_inline_completion(True)
        tweetComplete.set_minimum_key_length(2)
        self.tweetText.set_completion(tweetComplete)
        
        # create the TreeView using treestore this is the object which displays the
        # info stored in the liststore
        self.treeview = gtk.TreeView(self.liststore)
        self.treeview.set_model(self.liststore)
        # create the TreeViewColumn to display the data, I decided on two colums
        # one for name and the other for the tweet
        #self.tvcname = gtk.TreeViewColumn('Name')
        cell = gtk.CellRendererText()
        self.tvctweet = gtk.TreeViewColumn('Pango Markup', cell, markup=2)

        #self.tvctweet = gtk.TreeViewColumn('Tweet')
        # add the two tree view columns to the treeview
        #self.treeview.append_column(self.tvcname)
        self.treeview.append_column(self.tvctweet)
        # we need a CellRendererText to render the data
        
        # add the cell renderer to the columns
        #self.tvcname.pack_start(cell, True)
        #self.tvctweet.pack_start(cell,True)
        # set the cell "text" attribute to column 0 - retrieve text
        # from that column in liststore and treat it as the text to render
        # in this case it's the name of a tweeter
        #self.tvcname.add_attribute(self.cell, 'text', 0)
        # we then use the second field of our liststore to hold the colour for
        # the 'name' text
        #self.tvcname.add_attribute(self.cell, 'foreground', 1)
        # next we add a mapping to the tweet column, again the third field
        # in our list store is the tweet text
        self.tvctweet.add_attribute(cell, 'text',2)
        # and the fourth is the colour of the tweet text 
        #self.tvctweet.add_attribute(cell, 'foreground', 3)
        # we start up non-fullscreen, and we want the tweets to appear without
        # scrolling left-right (well I wanted that) so I set a wrap width for
        # the text being rendered
        cell.set_property('wrap-width', self.defaultwidth)
        # make it searchable (I found this in an example and thought I might use it
        # but currently I make no use of this setting
        self.treeview.set_search_column(2)
        self.treeview.set_rules_hint(True)
       
        self.treeview.set_property('enable-grid-lines',True)
        # Allow sorting on the column. This is cool because no matter what order
        # we load tweets in, we always get a view which is sorted by the tweet id which
        # always increments, so we get them in order
        
        self.liststore.set_sort_column_id(4,gtk.SORT_DESCENDING)
        self.dmliststore.set_sort_column_id(4,gtk.SORT_DESCENDING)
        self.mentionliststore.set_sort_column_id(4,gtk.SORT_DESCENDING)
        self.publicliststore.set_sort_column_id(4,gtk.SORT_DESCENDING)
        self.searchliststore.set_sort_column_id(4,gtk.SORT_DESCENDING)
        #want to order the friends list by name
        self.friendsliststore.set_sort_column_id(0,gtk.SORT_ASCENDING)
        # I don't want to accidentally be dragging and dropping rows out of order
        self.treeview.set_reorderable(False)
        #with all that done I add the treeview to the scrolled window
        pannedWindow.add_with_viewport(self.treeview)
        #self.treeview.connect("button-press-event", self.build_menu, None);
        selection = self.treeview.get_selection()
        selection.connect('changed', self.build_menu)

        # self.treeview.connect("changed", self.build_menu, None);
        self.treeview.tap_and_hold_setup(self.urlmenu, callback=gtk.tap_and_hold_menu_position_top)
        if (re.search("UserName",self.username)):
	     self.promptForCredentials() 
        
        
        
    def quit(self, *args):
        #this is our end method called when window is closed
        print "Stop Wittering"
        self.writeConfig()
        gtk.main_quit()
       
    def create_menu(self, widget):
        #a fairly standard menu create
        #I put in the same options as I have buttons
        # and linked to the same methods
        menu = gtk.Menu()
  
        menuItemGetTweets = gtk.MenuItem("Get Tweets")
        menuItemGetTweets.connect("activate", self.getTweets )
        menuItemTweet = gtk.MenuItem("Tweet")
        menuItemTweet.connect("activate",self.newTweet)
        menuItemTwitPic = gtk.MenuItem("TwitPic")
        menuItemTwitPic.connect("activate", self.selectImage)
        menuItemTrends = gtk.MenuItem("Trends")
        menuItemTrends.connect("activate", self.switchViewTo, "trends")
        menuItemPublic = gtk.MenuItem("Public")
        menuItemPublic.connect("activate", self.switchViewTo, "public")
        menuItemCreds = gtk.MenuItem("Set UID/PWD")
        menuItemCreds.connect("activate",self.promptForCredentials)
        menuItemInvert = gtk.MenuItem("Invert Text")
        menuItemInvert.connect("activate",self.flipTextColour)
        menuItemSeparator = gtk.SeparatorMenuItem()
        menuItemExit = gtk.MenuItem("Exit")
        menuItemExit.connect("activate", self.quit);
                
        menu.append(menuItemGetTweets)
        menu.append(menuItemTweet)
        menu.append(menuItemTwitPic)
        menu.append(menuItemTrends)
        menu.append(menuItemPublic)
        menu.append(menuItemSeparator)
        menu.append(menuItemCreds)
        menu.append(menuItemExit)
        
        menuItemFile = gtk.MenuItem("File")
        menuItemFile.set_submenu(menu)
        return menu

 
    def create_m5_menu(self, widget):
        #a fairly standard menu create
        #I put in the same options as I have buttons
        # and linked to the same methods
        menu = hildon.AppMenu()

        GetTweets = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
        command_id = "Get Tweets"
        GetTweets.set_label(command_id)
        # Attach callback to clicked signal
        GetTweets.connect("clicked", self.getTweets)
        GetTweets.show()
        menu.append(GetTweets)
        Tweets = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
        Tweets.set_label("Tweet")
        # Attach callback to clicked signal
        Tweets.connect("clicked", self.newTweet)
        Tweets.show()
        menu.append(Tweets)
        
        TwitPic = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
        TwitPic.set_label("TwitPic!")
        # Attach callback to clicked signal
        TwitPic.connect("clicked", self.selectImage)
        TwitPic.show()
        menu.append(TwitPic)
        
        Trends = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
        Trends.set_label("Trends")
        # Attach callback to clicked signal
        Trends.connect("clicked", self.switchViewTo, "trends")
        Trends.show()
        menu.append(Trends)
        
        Public = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
        Public.set_label("Public")
        # Attach callback to clicked signal
        Public.connect("clicked", self.switchViewTo, "public")
        Public.show()
        menu.append(Public)
        
        Creds = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
        Creds.set_label("Set UID/PWD")
        # Attach callback to clicked signal
        Creds.connect("clicked", self.promptForCredentials)
        Creds.show()
        menu.append(Creds)
        
        Invert = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
        Invert.set_label("Invert")
        # Attach callback to clicked signal
        Invert.connect("clicked", self.flipTextColour)
        Invert.show()
        menu.append(Invert)
        
        Exit = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
        Exit.set_label("Exit")
        # Attach callback to clicked signal
        Exit.connect("clicked", self.quit)
        Exit.show()
        menu.append(Exit)
        
        menu.show_all()
        return menu
 
    def run(self):     
        #this is the main execution method
        # we set things visible, connect a couple of event hooks to methods
        # specifically to handle switching in and our of fullscreen
        self.window.show_all()
        self.window.connect("key-press-event", self.on_key_press)
        self.window.connect("window-state-event", self.on_window_state_change)
        #this starts everything up
        gtk.main() 
        
    def updateSelectedView(self, *args):
        #call the get method for whichever liststore we're viewing
        if (self.treeview.get_model() == self.liststore):
            self.getTweets()
        elif (self.treeview.get_model() == self.dmliststore):
            self.getDMs()
        elif (self.treeview.get_model() == self.mentionliststore):
            self.getMentions()
        elif (self.treeview.get_model() == self.publicliststore):
            self.getPublic()
        elif (self.treeview.get_model() == self.trendliststore):
            self.getTrends()
        elif (self.treeview.get_model() == self.friendsliststore):
            self.getFriends()
        elif (self.treeview.get_model() == self.searchliststore):
            self.getSearch()
            
    def getTweets(self, *args):
        print "getting tweets"
        #Now for the main logic...fetching tweets
        #at the moment I'm just using basic auth. 
        #urllib2 provides all the HTTP handling stuff
        auth_handler = urllib2.HTTPBasicAuthHandler()
        #realm here is important. or at least it seemed to be
        #this info is on the login box if you go to the url in a browser
        auth_handler.add_password(realm='Twitter API',
                          uri='http://twitter.com/statuses/friends_timeline.json',
                          user=self.username,
                          passwd=self.password)
        #we create an 'opener' object with our auth_handler
        opener = urllib2.build_opener(auth_handler)
        # ...and install it globally so it can be used with urlopen.
        urllib2.install_opener(opener)
        #switch on whether this is an refresh or a first download
        try:
    
            if self.last_id == None:
                json = urllib2.urlopen('http://twitter.com/statuses/friends_timeline.json')
            else:
                #basically the twitter API will respond with just tweets newer than the ID we send
                json = urllib2.urlopen('http://twitter.com/statuses/friends_timeline.json?since_id='+str(self.last_id)+'L')
            #JSON is awesome stuff. we get given a long string of json encoded information
            #which contains all the tweets, with lots of info, we decode to a json object
            data = simplejson.loads(json.read())
            #then this line does all the hard work. Basicaly for evey top level object in the JSON
            #structure we call out getStatus method with the contents of the USER structure
            #and the values of top level values text/id/created_at
            [self.getStatus(x['user'],x['text'], x['id'], x['created_at'], "tweet") for x in data]
        except IOError, e:
            
            if hasattr(e, 'code'):
                if (e.code == 401):
                    reason = "Not authorised: check uid/pwd"
                else:
                    reason = ""
                dialog = gtk.Dialog("Server returned "+ str(e.code) + " " +reason,
                     self.window,
                     gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
                dialog.show()

          
        
    def getDMs(self, *args):
        print "getting DMs"
        #Now for the main logic...fetching tweets
        #at the moment I'm just using basic auth. 
        #urllib2 provides all the HTTP handling stuff
        auth_handler = urllib2.HTTPBasicAuthHandler()
        #realm here is important. or at least it seemed to be
        #this info is on the login box if you go to the url in a browser
        auth_handler.add_password(realm='Twitter API',
                          uri='http://twitter.com/direct_messages.json',
                          user=self.username,
                          passwd=self.password)
        #we create an 'opener' object with our auth_handler
        opener = urllib2.build_opener(auth_handler)
        # ...and install it globally so it can be used with urlopen.
        urllib2.install_opener(opener)
        try:
            #switch on whether this is an refresh or a first download
            if self.last_dm_id == None:
                json = urllib2.urlopen('http://twitter.com/direct_messages.json')
            else:
                json = urllib2.urlopen('http://twitter.com/direct_messages.json?since_id='+str(self.last_dm_id)+'L')
            #JSON is awesome stuff. we get given a long string of json encoded information
            #which contains all the tweets, with lots of info, we decode to a json object
            data = simplejson.loads(json.read())
            #then this line does all the hard work. Basicaly for evey top level object in the JSON
            #structure we call out getStatus method with the contents of the USER structure
            #and the values of top level values text/id/created_at
            [self.getStatus(x['sender'],x['text'], x['id'], x['created_at'], "dm") for x in data]
        except IOError, e:
            if hasattr(e, 'code'):
                if (e.code == 401):
                    reason = "Not authorised: check uid/pwd"
                else:
                    reason = ""
                dialog = gtk.Dialog("Server returned "+ str(e.code) + " " +reason,
                     self.window,
                     gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
                dialog.show()

    def getMentions(self, *args):
        print "getting Mentions"
        
        #Now for the main logic...fetching tweets
        #at the moment I'm just using basic auth. 
        #urllib2 provides all the HTTP handling stuff
        auth_handler = urllib2.HTTPBasicAuthHandler()
        #realm here is important. or at least it seemed to be
        #this info is on the login box if you go to the url in a browser
        auth_handler.add_password(realm='Twitter API',
                          uri='http://twitter.com/statuses/mentions.json',
                          user=self.username,
                          passwd=self.password)
        #we create an 'opener' object with our auth_handler
        opener = urllib2.build_opener(auth_handler)
        # ...and install it globally so it can be used with urlopen.
        urllib2.install_opener(opener)
        try:
            #switch on whether this is an refresh or a first download
            if self.last_mention_id == None:
                json = urllib2.urlopen('http://twitter.com/statuses/mentions.json')
            else:
                json = urllib2.urlopen('http://twitter.com/statuses/mentions.json?since_id='+str(self.last_mention_id)+'L')
            #JSON is awesome stuff. we get given a long string of json encoded information
            #which contains all the tweets, with lots of info, we decode to a json object
            data = simplejson.loads(json.read())
            #then this line does all the hard work. Basicaly for evey top level object in the JSON
            #structure we call out getStatus method with the contents of the USER structure
            #and the values of top level values text/id/created_at
            [self.getStatus(x['user'],x['text'], x['id'], x['created_at'], "mention") for x in data]
        except IOError, e:
            if hasattr(e, 'code'):
                if (e.code == 401):
                    reason = "Not authorised: check uid/pwd"
                else:
                    reason = ""
                dialog = gtk.Dialog("Server returned "+ str(e.code) + " " +reason,
                     self.window,
                     gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
                dialog.show()
        
    def getPublic(self, *args):
        print "getting Public timeline"
        #Now for the main logic...fetching tweets
        #at the moment I'm just using basic auth. 
        #urllib2 provides all the HTTP handling stuff
        auth_handler = urllib2.HTTPBasicAuthHandler()
        #realm here is important. or at least it seemed to be
        #this info is on the login box if you go to the url in a browser
        auth_handler.add_password(realm='Twitter API',
                          uri='http://twitter.com/statuses/public_timeline.json',
                          user=self.username,
                          passwd=self.password)
        #we create an 'opener' object with our auth_handler
        opener = urllib2.build_opener(auth_handler)
        # ...and install it globally so it can be used with urlopen.
        urllib2.install_opener(opener)
        try:
            #switch on whether this is an refresh or a first download
            if self.last_public_id == None:
                json = urllib2.urlopen('http://twitter.com/statuses/public_timeline.json')
            else:
                json = urllib2.urlopen('http://twitter.com/statuses/public_timeline.json?since_id='+str(self.last_public_id)+'L')
            #JSON is awesome stuff. we get given a long string of json encoded information
            #which contains all the tweets, with lots of info, we decode to a json object
            data = simplejson.loads(json.read())
            #then this line does all the hard work. Basicaly for evey top level object in the JSON
            #structure we call out getStatus method with the contents of the USER structure
            #and the values of top level values text/id/created_at
            [self.getStatus(x['user'],x['text'], x['id'], x['created_at'], "public") for x in data]
        except IOError, e:
            if hasattr(e, 'code'):
                if (e.code == 401):
                    reason = "Not authorised: check uid/pwd"
                else:
                    reason = ""
                dialog = gtk.Dialog("Server returned "+ str(e.code) + " " +reason,
                     self.window,
                     gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
                dialog.show()
            
    def getSearch(self, *args):
        print "performing search"
        
        #overloading the tweet text input as the search criteria
        tweet = self.wTree.get_widget("TweetText").get_text()
        
        #see if we have just an empty string (eg eroneous button press)
        if (tweet == ""):
            print "nothing to search"
            return
        #clear any previous stuff, currenlty we'll just get one page of search results
        self.trendliststore.clear()
        
        tweet = unicode(tweet).encode('utf-8')
        #then we need to urlencode so that we can use twitter chars like @ without
        #causing problems
        search = urllib.urlencode({ 'q' : tweet })
        
        #Now for the main logic...fetching tweets
        #at the moment I'm just using basic auth. 
        #urllib2 provides all the HTTP handling stuff
        auth_handler = urllib2.HTTPBasicAuthHandler()
        #realm here is important. or at least it seemed to be
        #this info is on the login box if you go to the url in a browser
        auth_handler.add_password(realm='Twitter API',
                          uri='http://search.twitter.com/search.json',
                          user=self.username,
                          passwd=self.password)
        #we create an 'opener' object with our auth_handler
        opener = urllib2.build_opener(auth_handler)
        # ...and install it globally so it can be used with urlopen.
        urllib2.install_opener(opener)
        
        try:
            json = urllib2.urlopen('http://search.twitter.com/search.json?'+search)
            
            #JSON is awesome stuff. we get given a long string of json encoded information
            #which contains all the tweets, with lots of info, we decode to a json object
            data = simplejson.loads(json.read())
            #then this line does all the hard work. Basicaly for evey top level object in the JSON
            #structure we call out getStatus method with the contents of the USER structure
            #and the values of top level values text/id/created_at
            print data
            results = data['results']
            [self.getStatus(x['from_user'],x['text'], x['id'], x['created_at'], "search") for x in results]
        except IOError, e:
            if hasattr(e, 'code'):
                if (e.code == 401):
                    reason = "Not authorised: check uid/pwd"
                else:
                    reason = ""
                dialog = gtk.Dialog("Server returned "+ str(e.code) + " " +reason,
                     self.window,
                     gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
                dialog.show()
            
    def getTrends(self, *args):
        print "getting Trending topics"
        #first clear the previous 10
        self.trendliststore.clear()
        #Now for the main logic...fetching tweets
        #at the moment I'm just using basic auth. 
        #urllib2 provides all the HTTP handling stuff
        auth_handler = urllib2.HTTPBasicAuthHandler()
        #realm here is important. or at least it seemed to be
        #this info is on the login box if you go to the url in a browser
        auth_handler.add_password(realm='Twitter API',
                          uri='http://search.twitter.com/trends.json',
                          user=self.username,
                          passwd=self.password)
        #we create an 'opener' object with our auth_handler
        opener = urllib2.build_opener(auth_handler)
        # ...and install it globally so it can be used with urlopen.
        urllib2.install_opener(opener)
        
        try:
            json = urllib2.urlopen('http://search.twitter.com/trends.json')
            #JSON is awesome stuff. we get given a long string of json encoded information
            #which contains all the tweets, with lots of info, we decode to a json object
            data = simplejson.loads(json.read())
            print data
            #then this line does all the hard work. Basicaly for evey top level object in the JSON
            #structure we call out getStatus method with the contents of the USER structure
            #and the values of top level values text/id/created_at
            trends = data['trends']
            [self.getTrend(x['name'], x['url']) for x in trends]
        except IOError, e:
            if hasattr(e, 'code'):
                if (e.code == 401):
                    reason = "Not authorised: check uid/pwd"
                else:
                    reason = ""
                dialog = gtk.Dialog("Server returned "+ str(e.code) + " " +reason,
                     self.window,
                     gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
                dialog.show()
            
    def getTrend(self, name, url):
        
        self.trendliststore.append([name, self.namecolour,"<span foreground=\"blue\">"+name+"</span> :"+url,self.tweetcolour])
        
        
    def getFriends(self, *args):
        print "getting Friends"
        #first clear the previous 10
        self.friendsliststore.clear()
        #Now for the main logic...fetching tweets
        #at the moment I'm just using basic auth. 
        #urllib2 provides all the HTTP handling stuff
        auth_handler = urllib2.HTTPBasicAuthHandler()
        #realm here is important. or at least it seemed to be
        #this info is on the login box if you go to the url in a browser
        auth_handler.add_password(realm='Twitter API',
                          uri='http://twitter.com/statuses/friends.json',
                          user=self.username,
                          passwd=self.password)
        #we create an 'opener' object with our auth_handler
        opener = urllib2.build_opener(auth_handler)
        # ...and install it globally so it can be used with urlopen.
        urllib2.install_opener(opener)
        
        try:
            json = urllib2.urlopen('http://twitter.com/statuses/friends.json')
            #JSON is awesome stuff. we get given a long string of json encoded information
            #which contains all the tweets, with lots of info, we decode to a json object
            data = simplejson.loads(json.read())
            
            #then this line does all the hard work. Basicaly for evey top level object in the JSON
            #structure we call out getStatus method with the contents of the USER structure
            #and the values of top level values text/id/created_at
            
            for x in data:
                #if we follow someone with no status then you get a key error on status
                try:
                    self.getStatus(x['screen_name'],x['status'], x['id'], x['created_at'], "friend") 
                except KeyError:
                    print  x
        except IOError, e:
            if hasattr(e, 'code'):
                if (e.code == 401):
                    reason = "Not authorised: check uid/pwd"
                else:
                    reason = ""
                dialog = gtk.Dialog("Server returned "+ str(e.code) + " " +reason,
                     self.window,
                     gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
                dialog.show()    
        
    def getStatus(self, user,data, id, created_at, type):
        #at this point user is another JSON structure of lots more values of which we are currently
        #only interested in screen_name
        #append to our list store the values from the JSON data we've been passed for a tweet
        # the funny #NXNXNX type values are colours I chose a slightly blue for the name
        # and black for the tweet. At some point I intend to do some alternating colours for
        # cell backgrounds to make the display clearer
        if (re.search("tweet", type)):
            data = data.replace("&","&amp;")
            self.liststore.append([ "@"+user['screen_name'],self.namecolour,"<span foreground=\"#0000FF\"><b>@"+user['screen_name']+"</b></span> : "+data+"\n<span size=\"xx-small\">posted on: "+created_at+"</span>",self.tweetcolour, id, type])
            #now we process the id, this is so we can do a refresh with just the posts since the latest one we have
            #if we haven't stored the most recent id then store this one
            if self.last_id == None:
                self.last_id=id
            else:
                #if we have an id stored, check if this one is 'newer' if so then store it
                if long(self.last_id) < long(id):
                    self.last_id=id
        elif (re.search("dm", type)):
            self.dmliststore.append([ "@"+user['screen_name'],self.namecolour,"<span foreground=\"#0000FF\"><b>@"+user['screen_name']+"</b></span> : "+data+"\n<span size=\"xx-small\">posted on: "+created_at+"</span>",self.tweetcolour, id, type])
            if self.last_dm_id == None:
                self.last_dm_id=id
            else:
                #if we have an id stored, check if this one is 'newer' if so then store it
                if long(self.last_dm_id) < long(id):
                    self.last_dm_id=id
        elif (re.search("mention", type)):
            self.mentionliststore.append([ "@"+user['screen_name'],self.namecolour,"<span foreground=\"#0000FF\"><b>@"+user['screen_name']+"</b></span> : "+data+"\n<span size=\"xx-small\">posted on: "+created_at+"</span>",self.tweetcolour, id, type])
            if self.last_mention_id == None:
                self.last_mention_id=id
            else:
                #if we have an id stored, check if this one is 'newer' if so then store it
                if long(self.last_mention_id) < long(id):
                    self.last_mention_id=id
        elif (re.search("public", type)):
            self.publicliststore.append([ "@"+user['screen_name'],self.namecolour,"<span foreground=\"#0000FF\"><b>@"+user['screen_name']+"</b></span> : "+data+"\n<span size=\"xx-small\">posted on: "+created_at+"</span>",self.tweetcolour, id, type])
            if self.last_public_id == None:
                self.last_public_id=id
            else:
                #if we have an id stored, check if this one is 'newer' if so then store it
                if long(self.last_public_id) < long(id):
                    self.last_public_id=id
        elif (re.search("friend", type)):
            self.friendsliststore.append([ "@"+user,self.namecolour,"<span foreground=\"#0000FF\"><b>@"+user+"</b></span> : "+data['text']+"\n<span size=\"xx-small\">posted on: "+data['created_at']+"</span>",self.tweetcolour, id, type])
        elif (re.search("search", type)):
            self.searchliststore.append([ "@"+user,self.namecolour,"<span foreground=\"#0000FF\"><b>@"+user+"</b></span> : "+data+"\n<span size=\"xx-small\">posted on: "+created_at+"</span>",self.tweetcolour, id, type])
            
                
    def newTweet(self, widget, *args):
        #The other main need of a twitter client
        #the ability to post an update
        #get the tweet text from the input box
        tweet = self.wTree.get_widget("TweetText").get_text()
        
        #see if we have just an empty string (eg eroneous button press)
        if (tweet == ""):
            return
        
        #we get the text in the input box then we construct the outbound tweet
        #first we need to encode for utf-8
        tweet = unicode(tweet).encode('utf-8')
        #then we need to urlencode so that we can use twitter chars like @ without
        #causing problems
        post = urllib.urlencode({ 'status' : tweet })
                
        #build the request with the url and our post data
        req = urllib2.Request('http://twitter.com/statuses/update.json', post)
        #setup the auth stuff
        auth_handler = urllib2.HTTPBasicAuthHandler()
        auth_handler.add_password(realm='Twitter API',
                              uri='http://twitter.com/statuses/update.json',
                              user=self.username,
                              passwd=self.password)
        opener = urllib2.build_opener(auth_handler)
        # ...and install it globally so it can be used with urlopen.
        urllib2.install_opener(opener)
        json = urllib2.urlopen(req)
        #opener.close()
        data = simplejson.loads(json.read())
        #message sent, I'm assuming a failure to send would not continue
        #in this method? so it's safe to remove the tweet line
        # what I don't want is to lose the tweet I typed if we didn't
        # sucessfully send it to twitter. that would be annoying (I'm looking
        # at you Mauku)
        self.wTree.get_widget("TweetText").set_text("")
        
    
    def on_window_state_change(self, widget, event, *args): 
        #this just sets a flag to keep track of what state we're in
       if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN: 
            self.window_in_fullscreen = True 
       else: 
            self.window_in_fullscreen = False 

    def on_key_press(self, widget, event, *args): 
        #this picks up the press of the full screen key and toggles
        #from one mode to the other
       if event.keyval == gtk.keysyms.F6: 
             # The "Full screen" hardware key has been pressed 
             if self.window_in_fullscreen: 
                 self.window.unfullscreen () 
                 #when we toggle off fullscreen set the cell render wrap
                 #to 500
                 self.cell.set_property('wrap-width',self.defaultwidth)
             else: 
                self.window.fullscreen () 
                #when we toggle into fullscreen set the cell render wrap
                #wider
                self.cell.set_property('wrap-width', 630)
                
    def build_right_click_menu(self, *args):
        #build the layout for the right click menu
        urlmenu = gtk.Menu()
        self.menuItemURL = gtk.MenuItem("URL actions")
        
        urlmenu.append(self.menuItemURL)
        self.menuItemURL.show()
        #regardless we should provide the option to follow/unfollow/reply to/dm user?
        self.menuItemUserAction = gtk.MenuItem("User Actions")
        
        urlmenu.append(self.menuItemUserAction)
        self.menuItemUserAction.show()
        #unfollow
        
         
        return urlmenu 
         
    def build_menu(self, widget, *args):
        #a fairly standard menu create
        #I put in the same options as I have buttons
        # and linked to the same methods
        self.menuItemURL.remove_submenu()
        self.menuItemUserAction.remove_submenu()
               
        treeselection = self.treeview.get_selection()
        select1, select2 = treeselection.get_selected_rows()
        #entry1, entry2 = self.treeview.get_selection().get_selected()
        #we might one day have more than on element selected, for now we get 1 row
        try:
            if select2 != None:
                for item in select2[0]:
                    #we want to access field 3 which has or Tweet in it
                    entry = select1.get_value(select1.get_iter(item), 2)
                    #and we might as well list the person who provided the url
                    name = select1.get_value(select1.get_iter(item), 0)
                if re.search("http", entry): 
                    #convert the string to chunks deliniated on space (we assume the url 
                    #has spaces around it 
                    L = string.split(entry)
                    for word in L :
                        #find the 'word' which is our url
                        if re.search("http", word):
                            url=word
                            menuUrls = gtk.Menu()
                            menuItemLaunchURL = gtk.MenuItem(url)
                            menuItemLaunchURL.connect("activate", self.openBrowser, url )
                            menuUrls.append(menuItemLaunchURL)
                            menuUrls.show()
                            self.menuItemURL.set_submenu(menuUrls)
                            
                    
                menuUserAct = gtk.Menu()        
                menuItemFollowUser = gtk.MenuItem("Follow: "+name)                 
                menuItemFollowUser.connect("activate", self.FollowUser, name)
                menuItemUnFollowUser = gtk.MenuItem("Unfollow: "+name)
                menuItemUnFollowUser.connect("activate", self.UnFollowUser, name)
                menuUserAct.append(menuItemFollowUser)
                menuUserAct.append(menuItemUnFollowUser)
                menuUserAct.show()
                self.menuItemUserAction.set_submenu(menuUserAct)
                
        except IndexError:
            print "nothing selected"
           
     
      
        self.urlmenu.show_all()
        
    
    def FollowUser(self, widget, name, *args  ):
        print "follow: " + name
        
        post = urllib.urlencode({ 'screen_name' : name })
                
        #build the request with the url and our post data
        req = urllib2.Request('http://twitter.com/friendships/create.json', post)
        
        auth_handler = urllib2.HTTPBasicAuthHandler()
        #realm here is important. or at least it seemed to be
        #this info is on the login box if you go to the url in a browser
        
        auth_handler.add_password(realm='Twitter API',
                          uri='http://twitter.com/friendships/create.json',
                          user=self.username,
                          passwd=self.password)
        #we create an 'opener' object with our auth_handler
        opener = urllib2.build_opener(auth_handler)
        # ...and install it globally so it can be used with urlopen.
        urllib2.install_opener(opener)
        #switch on whether this is an refresh or a first download
       
        json = urllib2.urlopen(req)
        #JSON is awesome stuff. we get given a long string of json encoded information
        #which contains all the tweets, with lots of info, we decode to a json object
        data = simplejson.loads(json.read())
        print data
        
    def UnFollowUser(self, widget, name, *args ):
        print "unfollow : " + name
        post = urllib.urlencode({ 'screen_name' : name })
                
        #build the request with the url and our post data
        req = urllib2.Request('http://twitter.com/friendships/destroy.json', post)
        
        auth_handler = urllib2.HTTPBasicAuthHandler()
        #realm here is important. or at least it seemed to be
        #this info is on the login box if you go to the url in a browser
        auth_handler.add_password(realm='Twitter API',
                          uri='http://twitter.com/friendships/destroy.json',
                          user=self.username,
                          passwd=self.password)
        #we create an 'opener' object with our auth_handler
        opener = urllib2.build_opener(auth_handler)
        # ...and install it globally so it can be used with urlopen.
        urllib2.install_opener(opener)
        #switch on whether this is an refresh or a first download
       
        json = urllib2.urlopen(req)
        #JSON is awesome stuff. we get given a long string of json encoded information
        #which contains all the tweets, with lots of info, we decode to a json object
        data = simplejson.loads(json.read())
        print data
    
    def openBrowser(self, widget, url, *args):      
        #open a url in a browser
        context = osso.Context("Witter", "1.0",False)
        if (self.maemo_ver==5):
            webbrowser.open_new(url)
        else:
            webbrowser.open(url, context=context)
        print "We tried to open a browser"
        
    def checkVersion(self):
        #we want to see if we're on fremantle or not as the default colour
        #scheme has changed
        #look for /etc/maemo_version
        print "checking for /proc/component_version"
        try:
            f = open('/proc/component_version', 'r')
            read_data = f.read()
            if (re.search("RX-51", read_data)):
                print "found n900"
                self.textcolour="#FFFFFF"
                self.tweetcolour= "#FFFFFF"
                self.namecolour="#FE00B8"
                self.defaultwidth=790
                self.maemo_ver=5
            else:
		self.textcolour="#000000"
                print "found"+ read_data
                self.maemo_ver=4
        except IOError:
            #couldn't find the file 
            print "Assuming pre-maemo5"
            self.maemo_ver=4
    
    def readConfig(self):
        try:
            config = ConfigParser.ConfigParser()
            config.readfp(open('/home/user/.witter'))

            user = config.get("credentials", "username");
            self.username=base64.b64decode(user)
            password = config.get("credentials", "password");
            self.password=base64.b64decode(password)
            try:
                self.textcolour=config.get("UI","textcolour")
            except ConfigParser.NoSectionError:
                print "no text colour setting"
        except IOError:
            #couldn't find the file set uid so we can prompt
	    #for creds
            self.username = "UserName"
            self.password = ""
            print "No config file, prompt for uid/pwd"
            
             
    def writeConfig(self):
        f = open('/home/user/.witter','w')
        f.write("[credentials]\n")
        f.write("username = "+base64.b64encode(self.username)+"\n")
        f.write("password = "+base64.b64encode(self.password)+"\n")    
        f.write("[UI]\n")
        f.write("textcolour = "+self.textcolour+"\n")
    def promptForCredentials(self, *args):
        dialog = self.wTree.get_widget("CredentialsDialog")
        dialog.set_title("Twitter Credentials")
        dialog.connect("response", self.gtk_widget_hide)
        dialog.show()
        
    def  store_creds(self, widget, *args):
        print "store_creds called"
        
        #store the values set
        self.username = self.wTree.get_widget("UserName").get_text()
        self.password = self.wTree.get_widget("Password").get_text()
        self.writeConfig()

    def  gtk_widget_hide(self, widget, *args):
        widget.hide()
        
    def reparent_loc(self, widget, newParent):
        widget.reparent(newParent)
        
    def switchViewTo(self, widget, type ):
        if (re.search("timeline",type)):
            self.treeview.set_model(self.liststore)
            
        elif (re.search("direct", type)):
            self.treeview.set_model(self.dmliststore)
            
        elif (re.search("mentions", type)):
            self.treeview.set_model(self.mentionliststore)
        elif (re.search("public", type)):
            self.treeview.set_model(self.publicliststore)
        elif (re.search("trends", type)):
            self.treeview.set_model(self.trendliststore) 
        elif (re.search("friends", type)):
            self.treeview.set_model(self.friendsliststore)
        elif (re.search("search", type)):
            self.treeview.set_model(self.searchliststore)    
            
    def switchView(self, widget):
        #switches the active liststore to display what the user wants
        print widget
        type = widget.get_label()
        print type
        self.switchViewTo(widget,type)
        

    def selectImage(self, widget):
        #bring up a file choser to let people select images
        imageChose = self.wTree.get_widget("filechooserdialog1")
                
        filter = gtk.FileFilter()
        filter.set_name("*.jpg")
        filter.add_pattern("*.jpg")
        imageChose.remove_filter(filter)
        imageChose.add_filter(filter)
        imageChose.set_filter(filter)
        imageChose.connect("response", self.gtk_widget_hide)
        imageChose.show()
        
    def flipTextColour(self, *args):
        #until I figure out how to obey theme colours, let the user
        #flip the colours in use.
        if (re.search("#000000", self.textcolour)):
            self.textcolour = "#FFFFFF"
            
        else:
            self.textcolour = "#000000"
        #reset all the values in the current list stores
        item = self.liststore.get_iter_first ()

        while ( item != None ):
            self.liststore.set_value(item,1,self.textcolour)
            self.liststore.set_value(item,3,self.textcolour)
            item = self.liststore.iter_next(item)
        
        item = self.dmliststore.get_iter_first ()

        while ( item != None ):
            self.dmliststore.set_value(item,1,self.textcolour)
            self.dmliststore.set_value(item,3,self.textcolour)
            item = self.dmliststore.iter_next(item)
        item = self.friendsliststore.get_iter_first ()

        while ( item != None ):
            self.friendsliststore.set_value(item,1,self.textcolour)
            self.friendsliststore.set_value(item,3,self.textcolour)
            item = self.friendsliststore.iter_next(item)
        
        item = self.mentionliststore.get_iter_first ()

        while ( item != None ):
            self.mentionliststore.set_value(item,1,self.textcolour)
            self.mentionliststore.set_value(item,3,self.textcolour)
            item = self.mentionliststore.iter_next(item)
        item = self.publicliststore.get_iter_first ()

        while ( item != None ):
            self.publicliststore.set_value(item,1,self.textcolour)
            self.publicliststore.set_value(item,3,self.textcolour)
            item = self.publicliststore.iter_next(item)
        
        item = self.searchliststore.get_iter_first ()

        while ( item != None ):
            self.searchliststore.set_value(item,1,self.textcolour)
            self.searchliststore.set_value(item,3,self.textcolour)
            item = self.searchliststore.iter_next(item)
            
        item = self.trendliststore.get_iter_first ()

        while ( item != None ):
            self.trendliststore.set_value(item,1,self.textcolour)
            self.trendliststore.set_value(item,3,self.textcolour)
            item = self.trendliststore.iter_next(item)
      
    def twitPic(self, widget, *args):
        print "twitPic"
        dialog = self.wTree.get_widget("filechooserdialog1")
        file = dialog.get_filename()
        
        try:
            fin = open(file, "rb")
            jpgImage = fin.read()
            tweet = self.wTree.get_widget("TweetText").get_text()
        
            #see if we have just an empty string (eg eroneous button press)
            if (tweet == ""):
                print "No tweet to go with image"
                return
            
            # upload binary file with pycurl by http post
            c = pycurl.Curl()
            c.setopt(c.POST, 1)
            c.setopt(c.URL, "http://twitpic.com/api/uploadAndPost")
            c.setopt(c.HTTPPOST, [("media", (c.FORM_FILE, file)), 
                                  ("username",self.username), 
                                  ("password",self.password),
                                  ("message",tweet)])
            #c.setopt(c.VERBOSE, 1)
            c.perform()
            c.close()
            print "posted TwitPic"

            
            #message sent, I'm assuming a failure to send would not continue
            #in this method? so it's safe to remove the tweet line
            # what I don't want is to lose the tweet I typed if we didn't
            # sucessfully send it to twitter. that would be annoying (I'm looking
            # at you Mauku)
            self.wTree.get_widget("TweetText").set_text("")
        except IOError:
            print "couldn't read file"
        print file
    
   
    
if __name__ == "__main__":  
    #this is just what initialises the app and calls run
    app = Witter() 
    app.run()         
    
    


