Factored mytwitter into two modules to be cleaner
This commit is contained in:
parent
2f2a4d926d
commit
bf23f6e3fc
429
mytwitter.py
429
mytwitter.py
|
@ -2,11 +2,11 @@
|
|||
#
|
||||
# Custom twitter client... mostly for learning Python
|
||||
|
||||
import sys, ConfigParser, os, re, subprocess
|
||||
import datetime, dateutil.tz
|
||||
import sys, ConfigParser, os, re
|
||||
import twitter
|
||||
import gtk, gtk.glade, gobject
|
||||
from urllib2 import HTTPError
|
||||
from twitterwidgets import TweetPane
|
||||
|
||||
|
||||
class MyTwitter():
|
||||
|
@ -227,431 +227,6 @@ class MyTwitter():
|
|||
### end class MyTwitter
|
||||
|
||||
|
||||
class TweetPane(gtk.ScrolledWindow):
|
||||
'''
|
||||
Box that holds all the TweetBoxes for a given feed
|
||||
|
||||
This box will not update itself, the parent should do that.
|
||||
|
||||
It will connect num_entries listeners to its parent's on_reply() and on_retweet()
|
||||
|
||||
It also gets some data from its parent, including num_entries
|
||||
'''
|
||||
|
||||
def __init__(self, list_name, num_entries=20, single_tweet=None):
|
||||
gtk.ScrolledWindow.__init__(self)
|
||||
|
||||
self.updated_once = False
|
||||
|
||||
self.single_tweet = single_tweet
|
||||
|
||||
self.list_name = list_name
|
||||
|
||||
self.tab_label = CloseTabLabel(self.list_name)
|
||||
|
||||
# These handle determining which tweets are unread
|
||||
self.last_tweet_read = None
|
||||
self.latest_tweet = None
|
||||
self.num_new_tweets = 0
|
||||
|
||||
self.tweets = []
|
||||
|
||||
self.num_entries = num_entries
|
||||
if self.single_tweet is not None:
|
||||
self.num_entries = 1
|
||||
|
||||
self.init_widgets()
|
||||
|
||||
|
||||
def init_widgets(self):
|
||||
self.tab_label.connect('label_clicked', self.set_tweets_read_callback)
|
||||
|
||||
tweet_box = gtk.VBox()
|
||||
viewport = gtk.Viewport()
|
||||
|
||||
# Build us some labels...
|
||||
for i in range(0, self.num_entries):
|
||||
self.tweets.append(TweetBox())
|
||||
tweet_box.pack_start(self.tweets[i], expand=False)
|
||||
self.tweets[i].connect('reply', self.on_tweet_reply)
|
||||
self.tweets[i].connect('retweet', self.on_retweet)
|
||||
self.tweets[i].connect('in-reply-to', self.on_tweet_reply_to)
|
||||
|
||||
viewport.add(tweet_box)
|
||||
|
||||
# Several different actions should mark the tweets as 'read'
|
||||
self.connect('focus', self.set_tweets_read_callback)
|
||||
viewport.connect('button_press_event', self.set_tweets_read_callback)
|
||||
self.connect('scroll-event', self.set_tweets_read_callback)
|
||||
self.connect('scroll-child', self.set_tweets_read_callback)
|
||||
|
||||
self.add(viewport)
|
||||
self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
|
||||
self.show_all()
|
||||
|
||||
|
||||
def update_window(self, raw_statuses, using_results=False):
|
||||
if using_results:
|
||||
statuses = self.statuses_from_results(raw_statuses)
|
||||
else:
|
||||
statuses = raw_statuses
|
||||
|
||||
if self.updated_once is False:
|
||||
self.updated_once = True
|
||||
|
||||
# If this is our first load of this list, don't treat anything as new!
|
||||
if self.last_tweet_read is None:
|
||||
self.last_tweet_read = statuses[0].id
|
||||
|
||||
# Keep count of the new tweets for posting a status message
|
||||
self.num_new_tweets = 0
|
||||
|
||||
for i in range(0, self.num_entries):
|
||||
read = True
|
||||
if i < len(statuses):
|
||||
if statuses[i].id > self.last_tweet_read:
|
||||
self.num_new_tweets += 1
|
||||
read = False
|
||||
self.tweets[i].set_status(statuses[i], read)
|
||||
else:
|
||||
self.tweets[i].clear_status()
|
||||
|
||||
self.latest_tweet = statuses[0].id
|
||||
|
||||
self.update_tab_label()
|
||||
|
||||
|
||||
# Update the label with the number of unread tweets
|
||||
def update_tab_label(self):
|
||||
pane_text = self.list_name
|
||||
if self.num_new_tweets > 0:
|
||||
pane_text += ' (' + str(self.num_new_tweets) + ')'
|
||||
self.tab_label.set_label_text(pane_text)
|
||||
|
||||
|
||||
def get_list_name(self):
|
||||
return self.list_name
|
||||
|
||||
|
||||
def set_tweets_read(self):
|
||||
self.last_tweet_read = self.latest_tweet
|
||||
self.num_new_tweets = 0
|
||||
self.update_tab_label()
|
||||
|
||||
|
||||
def set_tweets_read_callback(self, event, arg1=None, arg2=None):
|
||||
self.set_tweets_read()
|
||||
|
||||
|
||||
def get_tab_label(self):
|
||||
return self.tab_label
|
||||
|
||||
|
||||
def get_single_tweet(self):
|
||||
return self.single_tweet
|
||||
|
||||
|
||||
def updated_once(self):
|
||||
return self.updated_once
|
||||
|
||||
|
||||
def on_tweet_reply(self, widget):
|
||||
self.emit('tweet-reply', {'screen_name': widget.screen_name, 'id': widget.id})
|
||||
|
||||
|
||||
def on_retweet(self, widget):
|
||||
self.emit('tweet-retweet', {'id': widget.id})
|
||||
|
||||
|
||||
def on_tweet_reply_to(self, widget, data):
|
||||
self.emit('tweet-in-reply-to', data)
|
||||
|
||||
|
||||
# To keep things simple elsewhere and improve code reuse
|
||||
# we'll build a list of home-cooked Status objects out of results.
|
||||
# Why is this even necessary?
|
||||
# Why can't we have more consistency out of the Twitter API?
|
||||
def statuses_from_results(self, results):
|
||||
statuses = []
|
||||
for result in results.results:
|
||||
status = Status()
|
||||
status.id = result.id
|
||||
status.user = User()
|
||||
status.user.screen_name = result.from_user
|
||||
status.user.name = ""
|
||||
status.in_reply_to_screen_name = result.to_user
|
||||
# The Twitter Search API has different timestamps than the
|
||||
# REST API... balls
|
||||
# fixme:
|
||||
# Gotta be a cleaner way to do this, but I can't think of it
|
||||
# right now
|
||||
created_at = re.sub(',', '', result.created_at)
|
||||
created_split = re.split(' ', created_at)
|
||||
status.created_at = created_split[0] + ' ' + created_split[2] + ' ' + created_split[1] + ' ' + created_split[4] + ' ' + created_split[5] + ' ' + created_split[3]
|
||||
status.text = result.text
|
||||
statuses.append(status)
|
||||
return statuses
|
||||
|
||||
### end class TweetPane
|
||||
|
||||
# signals for TweetPane
|
||||
|
||||
gobject.signal_new("tweet-reply", TweetPane,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
|
||||
gobject.signal_new("tweet-retweet", TweetPane,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
|
||||
gobject.signal_new("tweet-in-reply-to", TweetPane,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
|
||||
|
||||
|
||||
|
||||
class TweetBox(gtk.VBox):
|
||||
|
||||
'''
|
||||
GUI for displaying one tweet and associated buttons
|
||||
|
||||
Also stores the data necessary for replying or retweeting (id, screen name)
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
gtk.VBox.__init__(self)
|
||||
|
||||
self.screen_name = None
|
||||
self.id = None
|
||||
self.in_reply_to_id = None
|
||||
self.in_reply_to_screen_name = None
|
||||
|
||||
self.init_widgets()
|
||||
|
||||
|
||||
def init_widgets(self):
|
||||
## Build the header
|
||||
self.header = gtk.Label()
|
||||
label_eb = gtk.EventBox()
|
||||
label_eb.add(self.header)
|
||||
self.pack_start(label_eb)
|
||||
|
||||
# Set the header's properties
|
||||
label_eb.modify_text(gtk.STATE_NORMAL,gtk.gdk.color_parse("#ffffff"))
|
||||
label_eb.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse("#8888ff"))
|
||||
self.header.set_alignment(0.0, 0.0)
|
||||
self.header.set_selectable(True)
|
||||
self.header.set_line_wrap(True)
|
||||
|
||||
## Build the text
|
||||
self.text = gtk.Label()
|
||||
text_align = gtk.Alignment()
|
||||
text_align.add(self.text)
|
||||
self.text_eb = gtk.EventBox()
|
||||
self.text_eb.add(text_align)
|
||||
self.pack_start(self.text_eb)
|
||||
|
||||
# Set the text's properties
|
||||
text_align.set_padding(2, 5, 10, 5)
|
||||
self.text.set_alignment(0.0, 0.0)
|
||||
self.text.set_selectable(True)
|
||||
self.text.set_line_wrap(True)
|
||||
if gtk.gtk_version[0] > 2 or (gtk.gtk_version[0] == 2 and gtk.gtk_version[1] >= 18):
|
||||
self.text.connect('activate-link', self.on_url_clicked)
|
||||
self.text.connect('button-press-event', self.on_mouse_clicked)
|
||||
|
||||
# Build the buttons
|
||||
button_box_align = gtk.Alignment()
|
||||
button_box_align.set_padding(0, 15, 0, 0)
|
||||
button_box = gtk.HBox()
|
||||
self.pack_start(button_box)
|
||||
|
||||
self.reply_to_button = gtk.Button("")
|
||||
self.reply_to_button.set_relief(gtk.RELIEF_NONE)
|
||||
button_box.pack_start(self.reply_to_button, expand=False)
|
||||
self.reply_to_button.connect("clicked", self.on_in_reply_to_clicked)
|
||||
|
||||
reply_button = gtk.Button("Reply")
|
||||
reply_button.set_relief(gtk.RELIEF_HALF)
|
||||
button_box.pack_end(reply_button, expand=False)
|
||||
reply_button.connect("clicked", self.on_reply_clicked)
|
||||
|
||||
retweet_button = gtk.Button("Retweet")
|
||||
retweet_button.set_relief(gtk.RELIEF_HALF)
|
||||
button_box.pack_end(retweet_button, expand=False)
|
||||
retweet_button.connect("clicked", self.on_retweet_clicked)
|
||||
|
||||
|
||||
def set_status(self, status, read=True):
|
||||
self.set_read(read)
|
||||
|
||||
timezone = dateutil.tz.gettz()
|
||||
time_format = "%Y.%m.%d %H:%M:%S %Z"
|
||||
|
||||
# Get the user object
|
||||
user = status.user
|
||||
|
||||
# Get user's data for retweeting / replying
|
||||
self.screen_name = user.screen_name
|
||||
self.id = status.id
|
||||
self.in_reply_to_id = status.in_reply_to_status_id
|
||||
self.in_reply_to_screen_name = status.in_reply_to_screen_name
|
||||
|
||||
# ... and a formatted timestamp
|
||||
timestamp = datetime.datetime.strptime(status.created_at, "%a %b %d %H:%M:%S +0000 %Y")
|
||||
timestamp = timestamp.replace(tzinfo=dateutil.tz.gettz('UTC'))
|
||||
timestring = timestamp.astimezone(timezone).strftime(time_format)
|
||||
|
||||
# Set the header
|
||||
self.header.set_markup(user.name + " (" + user.screen_name + ") " + timestring)
|
||||
|
||||
# and the text
|
||||
new_text = status.text
|
||||
new_text = re.sub(r'&([^;]*?)( |$)', r'&\1\2', new_text)
|
||||
if gtk.gtk_version[0] > 2 or (gtk.gtk_version[0] == 2 and gtk.gtk_version[1] >= 18):
|
||||
new_text = re.sub(r"(http://.*?)( |$)", r'<a href="\1">\1</a>\2', new_text)
|
||||
self.text.set_markup(new_text)
|
||||
|
||||
# If this is in reply to something, set appropriate label
|
||||
if self.in_reply_to_screen_name:
|
||||
self.reply_to_button.set_label('in reply to ' + self.in_reply_to_screen_name)
|
||||
|
||||
|
||||
|
||||
def clear_status(self):
|
||||
self.header.set_markup('')
|
||||
self.text.set_markup('')
|
||||
self.screen_name = None
|
||||
self.id = None
|
||||
self.set_read(True)
|
||||
|
||||
|
||||
def set_read(self, read=True):
|
||||
if read:
|
||||
self.text_eb.modify_bg(gtk.STATE_NORMAL,
|
||||
gtk.gdk.color_parse("#f2f1f0"))
|
||||
else:
|
||||
self.text_eb.modify_bg(gtk.STATE_NORMAL,
|
||||
gtk.gdk.color_parse("#dbffdb"))
|
||||
|
||||
|
||||
def on_reply_clicked(self, widget):
|
||||
self.emit('reply')
|
||||
|
||||
|
||||
def on_retweet_clicked(self, widget):
|
||||
self.emit('retweet')
|
||||
|
||||
|
||||
def on_in_reply_to_clicked(self, widget):
|
||||
self.emit('in-reply-to', {'id': self.in_reply_to_id, 'name': self.in_reply_to_screen_name})
|
||||
|
||||
|
||||
def on_mouse_clicked(self, widget, event):
|
||||
if event.button == 1:
|
||||
self.set_read(True)
|
||||
# fixme: call on_url_clicked if there is an active uri
|
||||
# Apparently, this must wait until pygtk 2.18
|
||||
|
||||
|
||||
def on_url_clicked(self, widget):
|
||||
# fixme: we're catching this signal just to debug why it doesn't get emitted
|
||||
# seems to be related to EventBox?
|
||||
print 'debug: on_url_clicked()'
|
||||
return True
|
||||
|
||||
|
||||
# end class TweetBox
|
||||
|
||||
# signals for TweetBox
|
||||
gobject.signal_new("reply", TweetBox,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, ())
|
||||
gobject.signal_new("retweet", TweetBox,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, ())
|
||||
gobject.signal_new("in-reply-to", TweetBox,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
|
||||
|
||||
|
||||
|
||||
class CloseTabLabel(gtk.EventBox):
|
||||
'''
|
||||
This class holds a label and a button with an 'I' in it. This button causes the CloseTabLabel
|
||||
to emit a clicked signal
|
||||
'''
|
||||
|
||||
def __init__(self, name=None):
|
||||
gtk.EventBox.__init__(self)
|
||||
self.init_widgets(name)
|
||||
|
||||
|
||||
# This code is still heinous, but at least it is
|
||||
# isolated to its own class
|
||||
def init_widgets(self, name):
|
||||
#create a custom tab for notebook containing a
|
||||
#label and a button with STOCK_ICON
|
||||
tabBox = gtk.HBox(False, 2)
|
||||
tabButton=gtk.Button()
|
||||
tabButton.connect('clicked', self.on_clicked)
|
||||
|
||||
self.label = gtk.Label(name)
|
||||
|
||||
#Add a picture on a button
|
||||
iconBox = gtk.HBox(False, 0)
|
||||
image = gtk.Image()
|
||||
image.set_from_stock(gtk.STOCK_CLOSE,gtk.ICON_SIZE_MENU)
|
||||
gtk.Button.set_relief(tabButton,gtk.RELIEF_NONE)
|
||||
settings = gtk.Widget.get_settings(tabButton)
|
||||
(w,h) = gtk.icon_size_lookup_for_settings(settings,gtk.ICON_SIZE_MENU)
|
||||
gtk.Widget.set_size_request(tabButton, w + 4, h + 4);
|
||||
iconBox.pack_start(image, True, False, 0)
|
||||
tabButton.add(iconBox)
|
||||
|
||||
tabBox.pack_start(self.label, False)
|
||||
tabBox.pack_start(tabButton, False)
|
||||
|
||||
self.connect('button-press-event', self.on_button_press)
|
||||
|
||||
# needed, otherwise even calling show_all on the notebook won't
|
||||
# make the hbox contents appear.
|
||||
tabBox.show_all()
|
||||
self.add(tabBox)
|
||||
|
||||
|
||||
def set_label_text(self, new_text):
|
||||
self.label.set_text(new_text)
|
||||
|
||||
|
||||
def on_clicked(self, event):
|
||||
self.emit('close-clicked')
|
||||
|
||||
|
||||
def on_button_press(self, event, direction):
|
||||
self.emit('label-clicked')
|
||||
|
||||
|
||||
### end class CloseTabLabel
|
||||
|
||||
# signals for CloseTabLabel
|
||||
gobject.signal_new("close-clicked", CloseTabLabel,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, ())
|
||||
gobject.signal_new("label-clicked", CloseTabLabel,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, ())
|
||||
|
||||
|
||||
|
||||
class Status():
|
||||
def __init__(self):
|
||||
self.user = User()
|
||||
self.id = None
|
||||
self.created_at = None
|
||||
|
||||
class User():
|
||||
def __init__(self):
|
||||
self.screen_name = None
|
||||
self.name = None
|
||||
|
||||
|
||||
# main
|
||||
|
|
429
twitterwidgets.py
Normal file
429
twitterwidgets.py
Normal file
|
@ -0,0 +1,429 @@
|
|||
import re
|
||||
import datetime, dateutil.tz
|
||||
import gtk, gobject
|
||||
|
||||
class TweetPane(gtk.ScrolledWindow):
|
||||
'''
|
||||
Box that holds all the TweetBoxes for a given feed
|
||||
|
||||
This box will not update itself, the parent should do that.
|
||||
|
||||
It will connect num_entries listeners to its parent's on_reply() and on_retweet()
|
||||
|
||||
It also gets some data from its parent, including num_entries
|
||||
'''
|
||||
|
||||
def __init__(self, list_name, num_entries=20, single_tweet=None):
|
||||
gtk.ScrolledWindow.__init__(self)
|
||||
|
||||
self.updated_once = False
|
||||
|
||||
self.single_tweet = single_tweet
|
||||
|
||||
self.list_name = list_name
|
||||
|
||||
self.tab_label = CloseTabLabel(self.list_name)
|
||||
|
||||
# These handle determining which tweets are unread
|
||||
self.last_tweet_read = None
|
||||
self.latest_tweet = None
|
||||
self.num_new_tweets = 0
|
||||
|
||||
self.tweets = []
|
||||
|
||||
self.num_entries = num_entries
|
||||
if self.single_tweet is not None:
|
||||
self.num_entries = 1
|
||||
|
||||
self.init_widgets()
|
||||
|
||||
|
||||
def init_widgets(self):
|
||||
self.tab_label.connect('label_clicked', self.set_tweets_read_callback)
|
||||
|
||||
tweet_box = gtk.VBox()
|
||||
viewport = gtk.Viewport()
|
||||
|
||||
# Build us some labels...
|
||||
for i in range(0, self.num_entries):
|
||||
self.tweets.append(TweetBox())
|
||||
tweet_box.pack_start(self.tweets[i], expand=False)
|
||||
self.tweets[i].connect('reply', self.on_tweet_reply)
|
||||
self.tweets[i].connect('retweet', self.on_retweet)
|
||||
self.tweets[i].connect('in-reply-to', self.on_tweet_reply_to)
|
||||
|
||||
viewport.add(tweet_box)
|
||||
|
||||
# Several different actions should mark the tweets as 'read'
|
||||
self.connect('focus', self.set_tweets_read_callback)
|
||||
viewport.connect('button_press_event', self.set_tweets_read_callback)
|
||||
self.connect('scroll-event', self.set_tweets_read_callback)
|
||||
self.connect('scroll-child', self.set_tweets_read_callback)
|
||||
|
||||
self.add(viewport)
|
||||
self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
|
||||
self.show_all()
|
||||
|
||||
|
||||
def update_window(self, raw_statuses, using_results=False):
|
||||
if using_results:
|
||||
statuses = self.statuses_from_results(raw_statuses)
|
||||
else:
|
||||
statuses = raw_statuses
|
||||
|
||||
if self.updated_once is False:
|
||||
self.updated_once = True
|
||||
|
||||
# If this is our first load of this list, don't treat anything as new!
|
||||
if self.last_tweet_read is None:
|
||||
self.last_tweet_read = statuses[0].id
|
||||
|
||||
# Keep count of the new tweets for posting a status message
|
||||
self.num_new_tweets = 0
|
||||
|
||||
for i in range(0, self.num_entries):
|
||||
read = True
|
||||
if i < len(statuses):
|
||||
if statuses[i].id > self.last_tweet_read:
|
||||
self.num_new_tweets += 1
|
||||
read = False
|
||||
self.tweets[i].set_status(statuses[i], read)
|
||||
else:
|
||||
self.tweets[i].clear_status()
|
||||
|
||||
self.latest_tweet = statuses[0].id
|
||||
|
||||
self.update_tab_label()
|
||||
|
||||
|
||||
# Update the label with the number of unread tweets
|
||||
def update_tab_label(self):
|
||||
pane_text = self.list_name
|
||||
if self.num_new_tweets > 0:
|
||||
pane_text += ' (' + str(self.num_new_tweets) + ')'
|
||||
self.tab_label.set_label_text(pane_text)
|
||||
|
||||
|
||||
def get_list_name(self):
|
||||
return self.list_name
|
||||
|
||||
|
||||
def set_tweets_read(self):
|
||||
self.last_tweet_read = self.latest_tweet
|
||||
self.num_new_tweets = 0
|
||||
self.update_tab_label()
|
||||
|
||||
|
||||
def set_tweets_read_callback(self, event, arg1=None, arg2=None):
|
||||
self.set_tweets_read()
|
||||
|
||||
|
||||
def get_tab_label(self):
|
||||
return self.tab_label
|
||||
|
||||
|
||||
def get_single_tweet(self):
|
||||
return self.single_tweet
|
||||
|
||||
|
||||
def updated_once(self):
|
||||
return self.updated_once
|
||||
|
||||
|
||||
def on_tweet_reply(self, widget):
|
||||
self.emit('tweet-reply', {'screen_name': widget.screen_name, 'id': widget.id})
|
||||
|
||||
|
||||
def on_retweet(self, widget):
|
||||
self.emit('tweet-retweet', {'id': widget.id})
|
||||
|
||||
|
||||
def on_tweet_reply_to(self, widget, data):
|
||||
self.emit('tweet-in-reply-to', data)
|
||||
|
||||
|
||||
# To keep things simple elsewhere and improve code reuse
|
||||
# we'll build a list of home-cooked Status objects out of results.
|
||||
# Why is this even necessary?
|
||||
# Why can't we have more consistency out of the Twitter API?
|
||||
def statuses_from_results(self, results):
|
||||
statuses = []
|
||||
for result in results.results:
|
||||
status = Status()
|
||||
status.id = result.id
|
||||
status.user = User()
|
||||
status.user.screen_name = result.from_user
|
||||
status.user.name = ""
|
||||
status.in_reply_to_screen_name = result.to_user
|
||||
# The Twitter Search API has different timestamps than the
|
||||
# REST API... balls
|
||||
# fixme:
|
||||
# Gotta be a cleaner way to do this, but I can't think of it
|
||||
# right now
|
||||
created_at = re.sub(',', '', result.created_at)
|
||||
created_split = re.split(' ', created_at)
|
||||
status.created_at = created_split[0] + ' ' + created_split[2] + ' ' + created_split[1] + ' ' + created_split[4] + ' ' + created_split[5] + ' ' + created_split[3]
|
||||
status.text = result.text
|
||||
statuses.append(status)
|
||||
return statuses
|
||||
|
||||
### end class TweetPane
|
||||
|
||||
# signals for TweetPane
|
||||
|
||||
gobject.signal_new("tweet-reply", TweetPane,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
|
||||
gobject.signal_new("tweet-retweet", TweetPane,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
|
||||
gobject.signal_new("tweet-in-reply-to", TweetPane,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
|
||||
|
||||
|
||||
|
||||
class TweetBox(gtk.VBox):
|
||||
|
||||
'''
|
||||
GUI for displaying one tweet and associated buttons
|
||||
|
||||
Also stores the data necessary for replying or retweeting (id, screen name)
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
gtk.VBox.__init__(self)
|
||||
|
||||
self.screen_name = None
|
||||
self.id = None
|
||||
self.in_reply_to_id = None
|
||||
self.in_reply_to_screen_name = None
|
||||
|
||||
self.init_widgets()
|
||||
|
||||
|
||||
def init_widgets(self):
|
||||
## Build the header
|
||||
self.header = gtk.Label()
|
||||
label_eb = gtk.EventBox()
|
||||
label_eb.add(self.header)
|
||||
self.pack_start(label_eb)
|
||||
|
||||
# Set the header's properties
|
||||
label_eb.modify_text(gtk.STATE_NORMAL,gtk.gdk.color_parse("#ffffff"))
|
||||
label_eb.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse("#8888ff"))
|
||||
self.header.set_alignment(0.0, 0.0)
|
||||
self.header.set_selectable(True)
|
||||
self.header.set_line_wrap(True)
|
||||
|
||||
## Build the text
|
||||
self.text = gtk.Label()
|
||||
text_align = gtk.Alignment()
|
||||
text_align.add(self.text)
|
||||
self.text_eb = gtk.EventBox()
|
||||
self.text_eb.add(text_align)
|
||||
self.pack_start(self.text_eb)
|
||||
|
||||
# Set the text's properties
|
||||
text_align.set_padding(2, 5, 10, 5)
|
||||
self.text.set_alignment(0.0, 0.0)
|
||||
self.text.set_selectable(True)
|
||||
self.text.set_line_wrap(True)
|
||||
if gtk.gtk_version[0] > 2 or (gtk.gtk_version[0] == 2 and gtk.gtk_version[1] >= 18):
|
||||
self.text.connect('activate-link', self.on_url_clicked)
|
||||
self.text.connect('button-press-event', self.on_mouse_clicked)
|
||||
|
||||
# Build the buttons
|
||||
button_box_align = gtk.Alignment()
|
||||
button_box_align.set_padding(0, 15, 0, 0)
|
||||
button_box = gtk.HBox()
|
||||
self.pack_start(button_box)
|
||||
|
||||
self.reply_to_button = gtk.Button("")
|
||||
self.reply_to_button.set_relief(gtk.RELIEF_NONE)
|
||||
button_box.pack_start(self.reply_to_button, expand=False)
|
||||
self.reply_to_button.connect("clicked", self.on_in_reply_to_clicked)
|
||||
|
||||
reply_button = gtk.Button("Reply")
|
||||
reply_button.set_relief(gtk.RELIEF_HALF)
|
||||
button_box.pack_end(reply_button, expand=False)
|
||||
reply_button.connect("clicked", self.on_reply_clicked)
|
||||
|
||||
retweet_button = gtk.Button("Retweet")
|
||||
retweet_button.set_relief(gtk.RELIEF_HALF)
|
||||
button_box.pack_end(retweet_button, expand=False)
|
||||
retweet_button.connect("clicked", self.on_retweet_clicked)
|
||||
|
||||
|
||||
def set_status(self, status, read=True):
|
||||
self.set_read(read)
|
||||
|
||||
timezone = dateutil.tz.gettz()
|
||||
time_format = "%Y.%m.%d %H:%M:%S %Z"
|
||||
|
||||
# Get the user object
|
||||
user = status.user
|
||||
|
||||
# Get user's data for retweeting / replying
|
||||
self.screen_name = user.screen_name
|
||||
self.id = status.id
|
||||
self.in_reply_to_id = status.in_reply_to_status_id
|
||||
self.in_reply_to_screen_name = status.in_reply_to_screen_name
|
||||
|
||||
# ... and a formatted timestamp
|
||||
timestamp = datetime.datetime.strptime(status.created_at, "%a %b %d %H:%M:%S +0000 %Y")
|
||||
timestamp = timestamp.replace(tzinfo=dateutil.tz.gettz('UTC'))
|
||||
timestring = timestamp.astimezone(timezone).strftime(time_format)
|
||||
|
||||
# Set the header
|
||||
self.header.set_markup(user.name + " (" + user.screen_name + ") " + timestring)
|
||||
|
||||
# and the text
|
||||
new_text = status.text
|
||||
new_text = re.sub(r'&([^;]*?)( |$)', r'&\1\2', new_text)
|
||||
if gtk.gtk_version[0] > 2 or (gtk.gtk_version[0] == 2 and gtk.gtk_version[1] >= 18):
|
||||
new_text = re.sub(r"(http://.*?)( |$)", r'<a href="\1">\1</a>\2', new_text)
|
||||
self.text.set_markup(new_text)
|
||||
|
||||
# If this is in reply to something, set appropriate label
|
||||
if self.in_reply_to_screen_name:
|
||||
self.reply_to_button.set_label('in reply to ' + self.in_reply_to_screen_name)
|
||||
|
||||
|
||||
|
||||
def clear_status(self):
|
||||
self.header.set_markup('')
|
||||
self.text.set_markup('')
|
||||
self.screen_name = None
|
||||
self.id = None
|
||||
self.set_read(True)
|
||||
|
||||
|
||||
def set_read(self, read=True):
|
||||
if read:
|
||||
self.text_eb.modify_bg(gtk.STATE_NORMAL,
|
||||
gtk.gdk.color_parse("#f2f1f0"))
|
||||
else:
|
||||
self.text_eb.modify_bg(gtk.STATE_NORMAL,
|
||||
gtk.gdk.color_parse("#dbffdb"))
|
||||
|
||||
|
||||
def on_reply_clicked(self, widget):
|
||||
self.emit('reply')
|
||||
|
||||
|
||||
def on_retweet_clicked(self, widget):
|
||||
self.emit('retweet')
|
||||
|
||||
|
||||
def on_in_reply_to_clicked(self, widget):
|
||||
self.emit('in-reply-to', {'id': self.in_reply_to_id, 'name': self.in_reply_to_screen_name})
|
||||
|
||||
|
||||
def on_mouse_clicked(self, widget, event):
|
||||
if event.button == 1:
|
||||
self.set_read(True)
|
||||
# fixme: call on_url_clicked if there is an active uri
|
||||
# Apparently, this must wait until pygtk 2.18
|
||||
|
||||
|
||||
def on_url_clicked(self, widget):
|
||||
# fixme: we're catching this signal just to debug why it doesn't get emitted
|
||||
# seems to be related to EventBox?
|
||||
print 'debug: on_url_clicked()'
|
||||
return True
|
||||
|
||||
|
||||
# end class TweetBox
|
||||
|
||||
# signals for TweetBox
|
||||
gobject.signal_new("reply", TweetBox,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, ())
|
||||
gobject.signal_new("retweet", TweetBox,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, ())
|
||||
gobject.signal_new("in-reply-to", TweetBox,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
|
||||
|
||||
|
||||
|
||||
class CloseTabLabel(gtk.EventBox):
|
||||
'''
|
||||
This class holds a label and a button with an 'I' in it. This button causes the CloseTabLabel
|
||||
to emit a clicked signal
|
||||
'''
|
||||
|
||||
def __init__(self, name=None):
|
||||
gtk.EventBox.__init__(self)
|
||||
self.init_widgets(name)
|
||||
|
||||
|
||||
# This code is still heinous, but at least it is
|
||||
# isolated to its own class
|
||||
def init_widgets(self, name):
|
||||
#create a custom tab for notebook containing a
|
||||
#label and a button with STOCK_ICON
|
||||
tabBox = gtk.HBox(False, 2)
|
||||
tabButton=gtk.Button()
|
||||
tabButton.connect('clicked', self.on_clicked)
|
||||
|
||||
self.label = gtk.Label(name)
|
||||
|
||||
#Add a picture on a button
|
||||
iconBox = gtk.HBox(False, 0)
|
||||
image = gtk.Image()
|
||||
image.set_from_stock(gtk.STOCK_CLOSE,gtk.ICON_SIZE_MENU)
|
||||
gtk.Button.set_relief(tabButton,gtk.RELIEF_NONE)
|
||||
settings = gtk.Widget.get_settings(tabButton)
|
||||
(w,h) = gtk.icon_size_lookup_for_settings(settings,gtk.ICON_SIZE_MENU)
|
||||
gtk.Widget.set_size_request(tabButton, w + 4, h + 4);
|
||||
iconBox.pack_start(image, True, False, 0)
|
||||
tabButton.add(iconBox)
|
||||
|
||||
tabBox.pack_start(self.label, False)
|
||||
tabBox.pack_start(tabButton, False)
|
||||
|
||||
self.connect('button-press-event', self.on_button_press)
|
||||
|
||||
# needed, otherwise even calling show_all on the notebook won't
|
||||
# make the hbox contents appear.
|
||||
tabBox.show_all()
|
||||
self.add(tabBox)
|
||||
|
||||
|
||||
def set_label_text(self, new_text):
|
||||
self.label.set_text(new_text)
|
||||
|
||||
|
||||
def on_clicked(self, event):
|
||||
self.emit('close-clicked')
|
||||
|
||||
|
||||
def on_button_press(self, event, direction):
|
||||
self.emit('label-clicked')
|
||||
|
||||
|
||||
### end class CloseTabLabel
|
||||
|
||||
# signals for CloseTabLabel
|
||||
gobject.signal_new("close-clicked", CloseTabLabel,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, ())
|
||||
gobject.signal_new("label-clicked", CloseTabLabel,
|
||||
gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE, ())
|
||||
|
||||
|
||||
|
||||
class Status():
|
||||
def __init__(self):
|
||||
self.user = User()
|
||||
self.id = None
|
||||
self.created_at = None
|
||||
|
||||
class User():
|
||||
def __init__(self):
|
||||
self.screen_name = None
|
||||
self.name = None
|
Reference in New Issue
Block a user