682 lines
23 KiB
Python
Executable File
682 lines
23 KiB
Python
Executable File
#!/usr/bin/python
|
|
#
|
|
# Custom twitter client... mostly for learning Python
|
|
|
|
import os, re, shelve
|
|
import gtk, gtk.glade, gobject
|
|
from urllib2 import HTTPError,URLError
|
|
from twitterwidgets import TweetPane
|
|
from threading import enumerate,Condition
|
|
import apithreads
|
|
import config
|
|
|
|
|
|
class Hrafn():
|
|
""" Display Tweets, post to twitter """
|
|
|
|
def __init__(self, resize):
|
|
self.resize = resize
|
|
|
|
self.lists = {}
|
|
self.lists_cond = Condition()
|
|
|
|
if config.config.get('global', 'trayicon') == '1':
|
|
self.use_trayicon = True
|
|
if config.config.get('global', 'taskbar_when_minimized') == '1':
|
|
self.taskbar_min = True
|
|
else:
|
|
self.taskbar_min = False
|
|
else:
|
|
self.use_trayicon = False
|
|
self.taskbar_min = True
|
|
|
|
# Init the glade stuff here, so we don't have a race condition with
|
|
# the lists-ready signal
|
|
self.init_user_interface('./ui/default.glade')
|
|
self.first_account_item = None
|
|
|
|
# And init the DB stuff here
|
|
db_file = os.path.expanduser(config.config.get('global', 'dbfile'))
|
|
self.db = shelve.open(db_file)
|
|
|
|
if not self.db.has_key('tokens'):
|
|
self.db['tokens'] = []
|
|
|
|
if not self.db.has_key('active_page'):
|
|
self.db['active_page'] = 0
|
|
|
|
self.num_entries = int(config.config.get('global', 'entries'))
|
|
self.refresh_time = int(config.config.get('global', 'refreshtime'))
|
|
|
|
if not self.db.has_key('active_user'):
|
|
self.db['active_user'] = None
|
|
|
|
# Now set up the accounts and their corresponding APIs
|
|
self.accounts = {}
|
|
for token in self.db['tokens']:
|
|
api = apithreads.CustomApi(token, self)
|
|
self.add_account(api)
|
|
|
|
self.username = self.db['active_user']
|
|
|
|
self.minimized = False
|
|
|
|
try:
|
|
self.api = self.accounts[self.username]
|
|
except KeyError:
|
|
self.api = None
|
|
|
|
if not self.db.has_key('open_tabs'):
|
|
self.db['open_tabs'] = []
|
|
|
|
# refresh_time is in minutes... convert to seconds here
|
|
self.refresh_time *= 60
|
|
|
|
self.reply_id = None
|
|
|
|
# Load up all the programmatic GUI stuff
|
|
self.init_widgets()
|
|
|
|
|
|
def init_user_interface(self, path_to_skin):
|
|
self.widget_tree=gtk.glade.XML(path_to_skin, "window")
|
|
self.widget_tree.signal_autoconnect(self)
|
|
|
|
# Get widgets from glade
|
|
self.window = self.widget_tree.get_widget('window')
|
|
self.tweet_notebook = self.widget_tree.get_widget('tweet_notebook')
|
|
self.view_menu = self.widget_tree.get_widget('view_menu')
|
|
self.accounts_menu = self.widget_tree.get_widget('accounts_menu')
|
|
self.update_entry = self.widget_tree.get_widget('update_entry')
|
|
self.update_count = self.widget_tree.get_widget('update_count')
|
|
self.status_bar = self.widget_tree.get_widget('status_bar')
|
|
self.search_entry = self.widget_tree.get_widget('search_entry')
|
|
self.following_button = self.widget_tree.get_widget('following_button')
|
|
self.at_button = self.widget_tree.get_widget('at_button')
|
|
self.verified_label = self.widget_tree.get_widget('verified_label')
|
|
self.account_label = self.widget_tree.get_widget('account_label')
|
|
self.help_menu = self.widget_tree.get_widget('help_menu')
|
|
|
|
|
|
def init_widgets(self):
|
|
# Set the main window size
|
|
if self.resize and self.db.has_key('width') and self.db.has_key('height'):
|
|
self.window.resize(self.db['width'], self.db['height'])
|
|
|
|
self.context_id = self.status_bar.get_context_id('message')
|
|
|
|
# Add debug options to help menu
|
|
if config.debug:
|
|
menu_item = gtk.MenuItem('debug: Show Threads')
|
|
menu_item.connect('activate', self.debug_show_threads)
|
|
self.help_menu.append(menu_item)
|
|
menu_item.show()
|
|
|
|
# Add a system tray icon
|
|
if self.use_trayicon:
|
|
self.tray_icon = gtk.status_icon_new_from_file('ui/icon.svg')
|
|
self.tray_icon.connect('activate', self.on_tray_icon_clicked)
|
|
self.tray_icon.connect('popup-menu', self.on_tray_icon_popup)
|
|
self.tray_menu = gtk.Menu()
|
|
quit_item = gtk.ImageMenuItem(gtk.STOCK_QUIT)
|
|
quit_item.connect('activate', self.gtk_main_quit)
|
|
self.tray_menu.append(quit_item)
|
|
quit_item.show()
|
|
|
|
# Set the account label
|
|
self.update_account_label()
|
|
|
|
# Manual tweaks to the glade UI, to overcome its limitations
|
|
self.tweet_notebook.remove_page(0)
|
|
|
|
# Add the tabs from last session to the notebook
|
|
page_num = self.db['active_page']
|
|
for tab, single_tweet, conversation in self.db['open_tabs']:
|
|
self.add_to_notebook(tab, single_tweet, conversation)
|
|
self.tweet_notebook.set_current_page(page_num)
|
|
|
|
# Timer to update periodically
|
|
gobject.timeout_add(self.refresh_time * 1000, self.update_windows)
|
|
|
|
|
|
def update_account_label(self):
|
|
if self.username is not None:
|
|
self.account_label.set_text(self.username + ': ')
|
|
|
|
|
|
# Spawns a thread for each pane, which updates that pane.
|
|
def update_windows(self):
|
|
for i in range(0, self.tweet_notebook.get_n_pages()):
|
|
pane = self.tweet_notebook.get_nth_page(i)
|
|
self.update_single_window(pane)
|
|
|
|
# We have to return true, so the timeout_add event will keep happening
|
|
return True
|
|
|
|
|
|
def update_single_window(self, pane):
|
|
list_name = pane.get_list_name()
|
|
|
|
# Single tweets should never be updated here
|
|
if pane.get_single_tweet() is not None:
|
|
return
|
|
|
|
# Determine username and appropriate account to use
|
|
account = None
|
|
|
|
username = re.sub('/Home', '', list_name)
|
|
if self.accounts.has_key(username):
|
|
account = self.accounts[username]
|
|
|
|
if account is None:
|
|
username = re.sub('@', '', list_name)
|
|
if self.accounts.has_key(username):
|
|
account = self.accounts[username]
|
|
|
|
if account is None:
|
|
username = re.sub('/Direct Messages', '', list_name)
|
|
if self.accounts.has_key(username):
|
|
account = self.accounts[username]
|
|
|
|
if account is None:
|
|
username = re.sub(r'list: (.*)/.*', r'\1', list_name)
|
|
if self.accounts.has_key(username):
|
|
account = self.accounts[username]
|
|
|
|
if account is None:
|
|
account = self.api
|
|
username = self.username
|
|
|
|
apithreads.GetTweets(api=account,
|
|
list_name=list_name,
|
|
pane=pane,
|
|
num_entries=self.num_entries,
|
|
username=username
|
|
).start()
|
|
|
|
|
|
def update_window_callback(self, widget):
|
|
pane = self.get_current_pane()
|
|
self.update_single_window(pane)
|
|
|
|
|
|
def update_status(self):
|
|
reply_id = self.reply_id
|
|
text = self.update_entry.get_text()
|
|
|
|
thread = apithreads.PostUpdate(self.api, text, reply_id)
|
|
thread.sig_proxy.connect('update-posted', self.on_update_posted)
|
|
self.update_entry.set_sensitive(False)
|
|
self.update_status_bar('Posting...')
|
|
thread.start()
|
|
|
|
|
|
def update_status_callback(self, widget):
|
|
self.update_status()
|
|
|
|
|
|
def text_watcher(self, widget):
|
|
''' Watch text entered on the update_entry, update things '''
|
|
text_len = self.update_entry.get_text_length()
|
|
new_count = str(text_len) + "/140"
|
|
self.update_count.set_label(new_count)
|
|
|
|
# If reply_id is set, unset it if we have removed the @ symbol
|
|
if self.reply_id and not re.match('@', self.update_entry.get_text()):
|
|
self.reply_id = None
|
|
|
|
|
|
def gtk_main_quit(self, widget):
|
|
self.db.close()
|
|
gtk.main_quit()
|
|
|
|
|
|
def on_about(self, widget):
|
|
print "stub: Hrafn.on_about()"
|
|
|
|
|
|
def on_reply(self, widget, data):
|
|
self.update_entry.set_text('@' + data['screen_name'] + ' ')
|
|
self.reply_id = data['id']
|
|
self.focus_on_entry()
|
|
|
|
|
|
def on_reply_dm(self, widget, data):
|
|
self.update_entry.set_text('D ' + data + ' ')
|
|
self.focus_on_entry()
|
|
|
|
|
|
def focus_on_entry(self):
|
|
self.update_entry.grab_focus()
|
|
self.update_entry.select_region(0,0)
|
|
self.update_entry.set_position(-1)
|
|
|
|
|
|
def on_retweet(self, widget, data):
|
|
thread = apithreads.PostRetweet(self.api, data['id'])
|
|
thread.sig_proxy.connect('retweet-posted', self.on_retweet_posted)
|
|
self.update_entry.set_sensitive(False)
|
|
self.update_status_bar('Posting retweet...')
|
|
thread.start()
|
|
|
|
|
|
def on_reply_to(self, widget, data):
|
|
self.add_to_notebook(data['name'], data['id'])
|
|
|
|
|
|
def on_conversation(self, widget, data):
|
|
self.add_to_notebook(data['name'], data['id'], True)
|
|
|
|
|
|
def on_view_selected(self, event, username, name):
|
|
if name == 'Home' or name == 'Direct Messages':
|
|
full_name = username + '/' + name
|
|
elif name == '@' + username:
|
|
full_name = name
|
|
else:
|
|
full_name = 'list: ' + username + '/' + name
|
|
|
|
# Now, add a new tab with this list
|
|
self.add_to_notebook(full_name)
|
|
|
|
|
|
# Remove one of the views from the tweet notebook.
|
|
# Called when the close button is clicked on one of the views
|
|
# or Ctrl + W is pressed while the view is active
|
|
def remove_view(self, name, single_tweet, conversation):
|
|
ot = self.db['open_tabs']
|
|
ot.remove((name,single_tweet,conversation))
|
|
self.db['open_tabs'] = ot
|
|
|
|
for i in range(self.tweet_notebook.get_n_pages()):
|
|
pane = self.tweet_notebook.get_nth_page(i)
|
|
if (pane.get_list_name() == name):
|
|
self.tweet_notebook.remove_page(i)
|
|
return
|
|
|
|
|
|
def remove_view_callback(self, event, name, single_tweet, conversation):
|
|
self.remove_view(name, single_tweet, conversation)
|
|
|
|
|
|
def get_current_pane(self):
|
|
return self.tweet_notebook.get_nth_page(self.tweet_notebook.get_current_page())
|
|
|
|
|
|
def add_to_notebook(self, name, single_tweet=None, conversation=False):
|
|
# If it already exists, don't add it, just switch to it
|
|
for i in range(self.tweet_notebook.get_n_pages()):
|
|
pane = self.tweet_notebook.get_nth_page(i)
|
|
# Unless it is a single tweet... ignore those unless
|
|
# we are also a single tweet... then, special logic
|
|
if pane.get_single_tweet() is not None:
|
|
if pane.get_single_tweet() == single_tweet:
|
|
self.tweet_notebook.set_current_page(i)
|
|
return
|
|
|
|
elif pane.get_list_name() == name:
|
|
self.tweet_notebook.set_current_page(i)
|
|
return
|
|
|
|
# Add the pane to the persistent database of open panes
|
|
if (name, single_tweet, conversation) not in self.db['open_tabs']:
|
|
ot = self.db['open_tabs']
|
|
ot.append((name,single_tweet,conversation))
|
|
self.db['open_tabs'] = ot
|
|
|
|
is_user = False
|
|
if re.match('user:', name):
|
|
is_user = True
|
|
|
|
is_dm = False
|
|
if re.search('Direct Messages', name):
|
|
is_dm = True
|
|
|
|
entries=self.num_entries
|
|
if single_tweet and not conversation:
|
|
entries=1
|
|
|
|
new_pane = TweetPane(name, username=self.username, num_entries=entries, single_tweet=single_tweet, is_user=is_user, conversation=conversation, is_dm=is_dm)
|
|
new_pane.connect('new-tweets', self.on_read_tweets_changed)
|
|
new_pane.connect('tweets-read', self.on_read_tweets_changed)
|
|
|
|
if is_user:
|
|
# Find the lists this user is currently in, and pass those
|
|
# to the pane
|
|
found_lists = []
|
|
username = re.sub('user: ', '', name)
|
|
|
|
self.lists_cond.acquire()
|
|
while not self.lists.has_key(self.username):
|
|
self.lists_cond.wait()
|
|
|
|
for list_name in self.lists[self.username].keys():
|
|
try:
|
|
i = self.lists[self.username][list_name].index(username)
|
|
found_lists.append(list_name)
|
|
except ValueError:
|
|
pass
|
|
new_pane.set_lists(found_lists)
|
|
|
|
self.lists_cond.release()
|
|
|
|
new_pane.user_box.connect('at-clicked', self.on_at_button_clicked)
|
|
new_pane.user_box.connect('follow-clicked', self.on_follow_button_clicked)
|
|
apithreads.GetFollowing(api=self.api, pane=new_pane, user=name).start()
|
|
apithreads.GetUserInfo(api=self.api, pane=new_pane, user=name).start()
|
|
|
|
self.tweet_notebook.append_page_menu(new_pane, new_pane.get_tab_label(), gtk.Label(name))
|
|
self.tweet_notebook.set_tab_reorderable(new_pane, True)
|
|
new_pane.get_tab_label().connect('close-clicked', self.remove_view_callback, name, single_tweet, conversation)
|
|
new_pane.connect('tweet-reply', self.on_reply)
|
|
new_pane.connect('tweet-reply-dm', self.on_reply_dm)
|
|
new_pane.connect('tweet-retweet', self.on_retweet)
|
|
new_pane.connect('tweet-in-reply-to', self.on_reply_to)
|
|
new_pane.connect('tweet-conversation', self.on_conversation)
|
|
new_pane.connect('show-user', self.show_user_callback)
|
|
new_pane.connect('show-hashtag', self.show_hashtag)
|
|
|
|
# Special logic for single tweet pane
|
|
if single_tweet is not None:
|
|
if conversation:
|
|
apithreads.GetConversation(api=self.api,
|
|
pane=new_pane,
|
|
root_tweet_id=single_tweet).start()
|
|
else:
|
|
apithreads.GetSingleTweet(api=self.api,
|
|
pane=new_pane,
|
|
single_tweet=single_tweet).start()
|
|
else:
|
|
self.update_single_window(new_pane)
|
|
|
|
# Switch to the new pane
|
|
self.tweet_notebook.set_current_page(-1)
|
|
|
|
|
|
def on_tab_change(self, event, page, page_num):
|
|
last_page = self.db['active_page']
|
|
self.db['active_page'] = page_num
|
|
|
|
# Now get the new page, and set everything up
|
|
pane = self.tweet_notebook.get_nth_page(page_num)
|
|
pane.set_tweets_read()
|
|
|
|
|
|
def on_tabs_reordered(self, widget, child, page_num):
|
|
self.db['active_page'] = page_num
|
|
|
|
# Clear the persistent tabs list, and recreate it
|
|
# from scratch
|
|
tab_names = []
|
|
|
|
for i in range(self.tweet_notebook.get_n_pages()):
|
|
pane = self.tweet_notebook.get_nth_page(i)
|
|
tab_names.append((pane.get_list_name(), pane.get_single_tweet(), pane.get_conversation()))
|
|
|
|
self.db['open_tabs'] = tab_names
|
|
|
|
|
|
def on_search(self, event):
|
|
search_string = self.search_entry.get_text()
|
|
self.search_entry.set_text('')
|
|
self.add_to_notebook(search_string)
|
|
|
|
|
|
def update_status_bar(self, text):
|
|
self.status_bar.pop(self.context_id)
|
|
self.status_bar.push(self.context_id, text)
|
|
|
|
|
|
def on_friendship_changed(self, widget, data):
|
|
if data['success']:
|
|
if data['follow']:
|
|
self.update_status_bar('Now following ' + data['user_name'])
|
|
else:
|
|
self.update_status_bar('No longer following ' + data['user_name'])
|
|
else: # didn't succeed
|
|
if data['follow']:
|
|
self.update_status_bar('Failed to follow ' + data['user_name'])
|
|
else:
|
|
self.update_status_bar('Failed to unfollow ' + data['user_name'])
|
|
|
|
|
|
def show_user(self, name):
|
|
self.add_to_notebook('user: ' + name)
|
|
|
|
|
|
def show_user_callback(self, widget, data):
|
|
self.show_user(data)
|
|
|
|
|
|
def show_hashtag(self, widget, data):
|
|
self.add_to_notebook('#' + data)
|
|
|
|
|
|
def on_at_button_clicked(self, widget, user_name):
|
|
self.add_to_notebook('@' + user_name)
|
|
|
|
|
|
def on_follow_button_clicked(self, widget, follow):
|
|
user_name = re.sub('^user: ', '', widget.get_list_name())
|
|
|
|
thread = apithreads.ChangeFriendship(self.api, widget, user_name, follow)
|
|
thread.sig_proxy.connect('friendship-changed', self.on_friendship_changed)
|
|
thread.start()
|
|
|
|
|
|
|
|
def global_key_press_handler(self, widget, event):
|
|
keyname = gtk.gdk.keyval_name(event.keyval)
|
|
if keyname == 'w' and event.state & gtk.gdk.CONTROL_MASK:
|
|
self.close_current_tab()
|
|
|
|
# Ctrl + Shift + Tab or Ctrl + PgUp or Ctrl + Left should go to prev tab
|
|
elif event.state & gtk.gdk.CONTROL_MASK and ((keyname == 'Tab' and event.state & gtk.gdk.SHIFT_MASK) or keyname == 'ISO_Left_Tab' or keyname == 'Page_Up' or keyname == 'Left'):
|
|
if self.tweet_notebook.get_current_page() == 0:
|
|
self.tweet_notebook.set_current_page(-1)
|
|
else:
|
|
self.tweet_notebook.prev_page()
|
|
return True
|
|
|
|
# Ctrl + Tab or Ctrl + PgDown or Ctrl + Right should go to next tab
|
|
elif event.state & gtk.gdk.CONTROL_MASK and (keyname == 'Tab' or keyname == 'Page_Down' or keyname == 'Right'):
|
|
if self.tweet_notebook.get_current_page() == self.tweet_notebook.get_n_pages() - 1:
|
|
self.tweet_notebook.set_current_page(0)
|
|
else:
|
|
self.tweet_notebook.next_page()
|
|
return True
|
|
|
|
else:
|
|
scrolltype = None
|
|
if keyname == 'Page_Down':
|
|
scrolltype = gtk.SCROLL_PAGE_FORWARD
|
|
elif keyname == 'Page_Up':
|
|
scrolltype = gtk.SCROLL_PAGE_BACKWARD
|
|
elif keyname == 'Up':
|
|
scrolltype = gtk.SCROLL_STEP_BACKWARD
|
|
elif keyname == 'Down':
|
|
scrolltype = gtk.SCROLL_STEP_FORWARD
|
|
|
|
if scrolltype:
|
|
self.get_current_pane().emit('scroll-child', scrolltype, False)
|
|
return True
|
|
|
|
|
|
|
|
def close_current_tab(self):
|
|
current_pane = self.get_current_pane()
|
|
self.remove_view(current_pane.get_list_name(), current_pane.get_single_tweet(), current_pane.get_conversation())
|
|
|
|
|
|
def on_account_changed(self, widget, new_account):
|
|
if not (widget.get_active() and self.accounts.has_key(new_account)):
|
|
return
|
|
|
|
self.username = new_account
|
|
self.api = self.accounts[self.username]
|
|
self.db['active_user'] = self.username
|
|
self.update_account_label()
|
|
for i in range(0, self.tweet_notebook.get_n_pages()):
|
|
pane = self.tweet_notebook.get_nth_page(i)
|
|
if re.match(r'user: ', pane.get_list_name()):
|
|
user = re.sub(r'user: ', r'', pane.get_list_name())
|
|
apithreads.GetFollowing(api=self.api, pane=pane, user=user).start()
|
|
|
|
|
|
def add_lists(self, username, list_data):
|
|
'''
|
|
This function is called by a child thread.
|
|
It takes list info from the API, stores it for later use, and uses
|
|
the data to populate the Views menu
|
|
'''
|
|
|
|
# Setup the new sub-menu
|
|
outer_menu_item = gtk.MenuItem(username, False)
|
|
self.view_menu.append(outer_menu_item)
|
|
new_menu = gtk.Menu()
|
|
outer_menu_item.set_submenu(new_menu)
|
|
|
|
self.lists_cond.acquire()
|
|
|
|
# Save the member info in a data structure for later usage
|
|
self.lists[username] = list_data
|
|
|
|
self.lists_cond.notify()
|
|
self.lists_cond.release()
|
|
|
|
list_names = list_data.keys()
|
|
list_names.sort()
|
|
|
|
# Insert the default list items
|
|
list_names.insert(0, 'Home')
|
|
list_names.insert(1, '@' + username)
|
|
list_names.insert(2, 'Direct Messages')
|
|
|
|
for l in list_names:
|
|
# Add the item to the submenu, connect handler
|
|
menu_item = gtk.MenuItem(l, False)
|
|
new_menu.append(menu_item)
|
|
menu_item.connect('activate', self.on_view_selected, username, l)
|
|
menu_item.show()
|
|
|
|
outer_menu_item.show()
|
|
|
|
|
|
def on_resize(self, widget, event):
|
|
self.db['width'] = event.width
|
|
self.db['height'] = event.height
|
|
|
|
|
|
def on_update_posted(self, widget, success):
|
|
if success:
|
|
self.reply_id = None
|
|
self.update_entry.set_text("")
|
|
self.update_status_bar('Tweet Posted')
|
|
else:
|
|
self.update_status_bar('Failed to post tweet')
|
|
|
|
self.update_entry.set_sensitive(True)
|
|
|
|
|
|
def on_retweet_posted(self, widget, success):
|
|
if success:
|
|
self.update_status_bar('Retweet Posted')
|
|
else:
|
|
self.update_status_bar('Failed to retweet')
|
|
|
|
self.update_entry.set_sensitive(True)
|
|
|
|
|
|
def debug_show_threads(self, widget):
|
|
print 'debug_show_threads()'
|
|
for thread in enumerate():
|
|
print 'debug: thread: ' + thread.name
|
|
|
|
|
|
def on_file_add_account(self, widget):
|
|
token = apithreads.get_access_token(self.window)
|
|
if token is None:
|
|
return
|
|
|
|
api = apithreads.CustomApi(token, self)
|
|
|
|
if not self.accounts.has_key(api.username):
|
|
tokens = self.db['tokens']
|
|
tokens.append(token)
|
|
self.db['tokens'] = tokens
|
|
self.add_account(api)
|
|
|
|
|
|
def add_account(self, api):
|
|
username = api.username
|
|
self.accounts[username] = api
|
|
|
|
# Add account's menu item
|
|
menu_item = gtk.RadioMenuItem(self.first_account_item, label=username, use_underline=False)
|
|
|
|
if not self.first_account_item:
|
|
self.first_account_item = menu_item
|
|
|
|
menu_item.set_draw_as_radio(False)
|
|
|
|
if not self.db.has_key('active_user'):
|
|
self.db['active_user'] = username
|
|
elif username == self.db['active_user']:
|
|
menu_item.set_active(True)
|
|
|
|
menu_item.connect('activate', self.on_account_changed, username)
|
|
self.accounts_menu.append(menu_item)
|
|
menu_item.show()
|
|
|
|
|
|
def on_tray_icon_clicked(self, event):
|
|
if self.minimized:
|
|
self.window.deiconify()
|
|
else:
|
|
self.window.iconify()
|
|
|
|
|
|
def on_tray_icon_popup(self, icon, button, activate_time):
|
|
self.tray_menu.popup(None, None, gtk.status_icon_position_menu, button, activate_time, icon)
|
|
|
|
|
|
def on_window_state_changed(self, window, event):
|
|
if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED:
|
|
if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED:
|
|
self.minimized = True
|
|
if not self.taskbar_min:
|
|
self.window.set_property('skip-taskbar-hint', True)
|
|
else:
|
|
self.minimized = False
|
|
self.window.set_property('skip-taskbar-hint', False)
|
|
|
|
|
|
def on_read_tweets_changed(self, widget):
|
|
unread_tweets = 0
|
|
for i in range(self.tweet_notebook.get_n_pages()):
|
|
pane = self.tweet_notebook.get_nth_page(i)
|
|
unread_tweets += pane.num_new_tweets
|
|
|
|
if unread_tweets > 0:
|
|
self.tray_icon.set_property('blinking', True)
|
|
else:
|
|
self.tray_icon.set_property('blinking', False)
|
|
|
|
### end class Hrafn
|
|
|
|
|
|
|
|
# main
|
|
if __name__ == "__main__":
|
|
config.init()
|
|
|
|
base_icon = gtk.gdk.pixbuf_new_from_file('ui/icon.svg')
|
|
icon = base_icon.scale_simple(128, 128, gtk.gdk.INTERP_BILINEAR)
|
|
gtk.window_set_default_icon(icon)
|
|
my_twitter = Hrafn(config.options.resize)
|
|
|
|
gtk.gdk.threads_init()
|
|
gtk.gdk.threads_enter()
|
|
gtk.main()
|
|
gtk.gdk.threads_leave()
|