Browse Source

Single object; follow new guidelines for NAMESPACE and options()

master
Julio Biason 16 years ago
parent
commit
9f9b201aee
  1. 920
      mitterlib/ui/ui_pygtk.py

920
mitterlib/ui/ui_pygtk.py

@ -27,373 +27,31 @@ gtk.gdk.threads_init()
import datetime import datetime
import re import re
import timesince
import logging import logging
import mitterlib as util import mitterlib as util
from notify import Notify
from mitterlib.constants import gpl_3, version from mitterlib.constants import gpl_3, version
from mitterlib.ui.utils import str_len from mitterlib.ui.helpers.utils import str_len
from mitterlib.ui.helpers.notify import Notify
NAMESPACE = 'pygtk' from mitterlib.ui.helpers import timesince
def options(options):
"""Add the options for this interface."""
options.add_group(NAMESPACE, 'GTK+ Interface')
options.add_option('--refresh-interval',
group=NAMESPACE,
option='refresh_interval',
help='Refresh interval',
type='int',
metavar='MINUTES',
default=5,
conflict_group='interface')
# Most of the options for non-cmd-options are useless, but I'm keeping
# them as documentation.
options.add_option(
group=NAMESPACE,
option='width',
help='Window width',
type='int',
metavar='PIXELS',
default=450,
conflict_group='interface',
is_cmd_option=False)
options.add_option(
group=NAMESPACE,
option='height',
help='Window height',
type='int',
metavar='PIXELS',
default=300,
conflict_group='interface',
is_cmd_option=False)
options.add_option(
group=NAMESPACE,
option='position_x',
help='Window position on the X axis',
type='int',
metavar='PIXELS',
default=5,
conflict_group='interface',
is_cmd_option=False)
options.add_option(
group=NAMESPACE,
option='position_y',
help='Window position on the Y axis',
type='int',
metavar='PIXELS',
default=5,
conflict_group='interface',
is_cmd_option=False)
options.add_option(
group=NAMESPACE,
option='max_status_display',
help='Maximum number of elements to keep internally',
type='int',
metavar='MESSAGES',
default=60,
conflict_group='interface',
is_cmd_option=False) # TODO: Should it be config only?
# Constants # Constants
URL_RE = re.compile( URL_RE = re.compile(
r'((?:(?:https?|ftp)://|www[-\w]*\.)[^\s\n\r]+[-\w+&@#%=~])', re.I) r'((?:(?:https?|ftp)://|www[-\w]*\.)[^\s\n\r]+[-\w+&@#%=~])', re.I)
_log = logging.getLogger('ui.pygtk') _log = logging.getLogger('ui.pygtk')
class Columns:
(PIC, NAME, MESSAGE, USERNAME, ID, DATETIME, ALL_DATA) = range(7)
class _MainWindow(gtk.Window):
"""PyGTK main window."""
def __init__(self, controller):
super(_MainWindow, self).__init__()
self._controller = controller
self.connect('destroy', self._controller.destroy)
self.connect('delete-event', self._controller.delete_event)
grid = self._create_grid(None) # Where is the store?
(menu, toolbar, accelerators) = self._create_menu_and_toolbar()
update_field = self._create_update_box()
statusbar = self._create_statusbar()
update_box = gtk.VPaned()
update_box.pack1(grid, resize=True, shrink=False)
update_box.pack2(update_field, resize=False, shrink=True)
box = gtk.VBox(False, 1)
box.pack_start(menu, False, True, 0)
box.pack_start(update_box, True, True, 0)
box.pack_start(statusbar, False, False, 0)
self.add(box)
self.add_accel_group(accelerators)
return
def _create_grid(self, grid_store):
"""Add the displaying grid."""
# self.grid_store = gtk.ListStore(
# str,
# str,
# str,
# str,
# str,
# object,
# object)
# self.grid_store.set_sort_func(Columns.DATETIME, self._sort_by_time)
# self.grid_store.set_sort_column_id(Columns.DATETIME,
# gtk.SORT_DESCENDING)
self.grid = gtk.TreeView(grid_store)
self.grid.set_property('headers-visible', False)
self.grid.set_rules_hint(True) # change color for each row
user_renderer = gtk.CellRendererPixbuf()
user_column = gtk.TreeViewColumn('User', user_renderer)
user_column.set_cell_data_func(user_renderer,
self._cell_renderer_user)
self.grid.append_column(user_column)
message_renderer = gtk.CellRendererText()
message_renderer.set_property('wrap-mode', gtk.WRAP_WORD)
message_renderer.set_property('wrap-width', 200)
message_renderer.set_property('width', 10)
message_column = gtk.TreeViewColumn('Message',
message_renderer, text=1)
message_column.set_cell_data_func(message_renderer,
self._cell_renderer_message)
self.grid.append_column(message_column)
self.grid.set_resize_mode(gtk.RESIZE_IMMEDIATE)
#self.grid.connect('cursor-changed', self.check_post)
#self.grid.connect('row-activated', self.open_post)
#self.grid.connect('button-press-event', self.click_post)
#self.grid.connect('popup-menu',
# lambda view: self.show_post_popup(view, None))
scrolled_window = gtk.ScrolledWindow()
scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
scrolled_window.add(self.grid)
return scrolled_window
def _create_menu_and_toolbar(self):
"""Create the main menu and the toolbar."""
# tasks (used by the menu and toolbar)
refresh_action = gtk.Action('Refresh', '_Refresh',
'Update the listing', gtk.STOCK_REFRESH)
refresh_action.connect('activate', self.refresh)
quit_action = gtk.Action('Quit', '_Quit',
'Exit Mitter', gtk.STOCK_QUIT)
quit_action.connect('activate', self.quit)
settings_action = gtk.Action('Settings', '_Settings',
'Settings', gtk.STOCK_PREFERENCES)
settings_action.connect('activate', self.show_settings)
update_action = gtk.Action('Update', '_Update', 'Update your status',
gtk.STOCK_ADD)
update_action.set_property('sensitive', False)
update_action.connect('activate', self._update_status)
delete_action = gtk.Action('Delete', '_Delete', 'Delete a post',
gtk.STOCK_DELETE)
delete_action.set_property('sensitive', False)
delete_action.connect('activate', self.delete_tweet)
about_action = gtk.Action('About', '_About', 'About Mitter',
gtk.STOCK_ABOUT)
about_action.connect('activate', self.show_about)
shrink_url_action = gtk.Action('ShrinkURL', 'Shrink _URL',
'Shrink selected URL', gtk.STOCK_EXECUTE)
shrink_url_action.connect('activate', self.shrink_url)
mute_action = gtk.ToggleAction('MuteNotify', '_Mute Notifications',
'Mutes notifications on new tweets', gtk.STOCK_MEDIA_PAUSE)
mute_action.set_active(False)
post_action = gtk.Action('Posts', '_Posts', 'Post management', None)
file_action = gtk.Action('File', '_File', 'File', None)
edit_action = gtk.Action('Edit', '_Edit', 'Edit', None)
help_action = gtk.Action('Help', '_Help', 'Help', None)
# action group (will have all the actions, 'cause we are not actually
# grouping them, but Gtk requires them that way)
self.action_group = gtk.ActionGroup('MainMenu')
self.action_group.add_action_with_accel(refresh_action, 'F5')
# None = use the default accelerator, based on the STOCK used.
self.action_group.add_action_with_accel(quit_action, None)
self.action_group.add_action(settings_action)
self.action_group.add_action(delete_action)
self.action_group.add_action(post_action)
self.action_group.add_action(file_action)
self.action_group.add_action(edit_action)
self.action_group.add_action(help_action)
self.action_group.add_action(about_action)
self.action_group.add_action_with_accel(shrink_url_action, '<Ctrl>u')
self.action_group.add_action_with_accel(mute_action, '<Ctrl>m')
self.action_group.add_action_with_accel(update_action,
'<Ctrl>Return')
# definition of the UI
uimanager = gtk.UIManager()
uimanager.insert_action_group(self.action_group, 0)
ui = '''
<ui>
<toolbar name="MainToolbar">
<toolitem action="Refresh" />
<separator />
<toolitem action="Delete" />
<separator />
<toolitem action="Settings" />
<toolitem action="Quit" />
</toolbar>
<menubar name="MainMenu">
<menu action="File">
<menuitem action="Quit" />
</menu>
<menu action="Edit">
<menuitem action="Refresh" />
<menuitem action="Update" />
<menuitem action="Delete" />
<menuitem action="ShrinkURL" />
<menuitem action="MuteNotify" />
<separator />
<menuitem action="Settings" />
</menu>
<menu action="Help">
<menuitem action="About" />
</menu>
</menubar>
</ui>
'''
uimanager.add_ui_from_string(ui)
main_menu = uimanager.get_widget('/MainMenu')
main_toolbar = uimanager.get_widget('/MainToolbar')
return (main_menu, main_toolbar, uimanager.get_accel_group())
def _create_update_box(self):
"""Create the widgets related to the update box"""
self._update_text = gtk.TextView()
text_buffer = self._update_text.get_buffer()
text_buffer.connect('changed', self._controller._count_chars)
update_button = gtk.Button(stock=gtk.STOCK_ADD)
update_button.connect('clicked', self._controller._update_status)
update_box = gtk.HBox(False, 0)
update_box.pack_start(self._update_text, expand=True, fill=True,
padding=0)
update_box.pack_start(update_button, expand=False, fill=False,
padding=0)
info_box = gtk.HBox(False, 0)
self._char_count = gtk.Label()
self._char_count.set_text('(140)')
info_box.pack_start(gtk.Label('What are you doing?'))
info_box.pack_start(self._char_count)
update_area = gtk.VBox(True, 0)
update_area.pack_start(info_box)
update_area.pack_start(update_box)
return update_area
def _create_statusbar(self):
"""Create the statusbar."""
statusbar = gtk.Statusbar()
# TODO: Probaly set the context in the object.
return statusbar
# ------------------------------------------------------------
# Cell rendering functions
# ------------------------------------------------------------
def _cell_renderer_user(self, column, cell, store, position):
"""Callback for the user column. Used to created the pixbuf of the
userpic."""
pic = store.get_value(position, Columns.PIC)
if not pic in self._user_pics:
cell.set_property('pixbuf', self._default_pixmap)
# just make sure we download this pic too.
self.queue_pic(pic)
else:
cell.set_property('pixbuf', self._user_pics[pic])
return
def _cell_renderer_message(self, column, cell, store, position):
"""Callback for the message column. We need this to adjust the markup
property of the cell, as setting it as text won't do any markup
processing."""
user = store.get_value(position, Columns.NAME)
message = store.get_value(position, Columns.MESSAGE)
time = store.get_value(position, Columns.DATETIME)
username = store.get_value(position, Columns.USERNAME)
time = timesince.timesince(time)
# unescape escaped entities that pango is okay with
message = re.sub(r'&(?!(amp;|gt;|lt;|quot;|apos;))', r'&amp;', message)
# highlight URLs
message = url_re.sub(r'<span foreground="blue">\1</span>',
message)
# use a different highlight for the current user
message = re.sub(r'(@'+self.twitter.username+')',
r'<span foreground="#FF6633">\1</span>',
message)
markup = '<b>%s</b> <small>(%s)</small>:\n%s\n<small>%s</small>' % \
(user, username, message, time)
cell.set_property('markup', markup)
return
class _GtkController(object):
"""The interface controller."""
def __init__(self):
super(_GtkController, self).__init__()
return
def destroy(self, widget, user_data=None):
"""Called when the window is destroyed."""
_log.debug('Window destroy')
gtk.main_quit()
return True
def delete_event(self, widget, event, user_param=None): class Columns:
_log.debug('Window delete') (PIC, NAME, MESSAGE, USERNAME, ID, DATETIME, ALL_DATA) = range(7)
gtk.main_quit()
return True
class Interface(object): class Interface(object):
"""Linux/GTK interface for Mitter.""" """Linux/GTK interface for Mitter."""
NAMESPACE = 'pygtk'
def systray_cb(self, widget, user_param=None): def systray_cb(self, widget, user_param=None):
if self.window.get_property('visible') and self.window.is_active(): if self.window.get_property('visible') and self.window.is_active():
x, y = self.window.get_position() x, y = self.window.get_position()
@ -1205,154 +863,405 @@ class Interface(object):
return return
def save_interface_prefs(self): def refresh_rate_limit(self):
"""Using the save callback, save all this interface preferences.""" """Request the rate limit and check if we are doing okay."""
self.twitter.rate_limit_status(self.post_refresh_rate_limit)
return
self.prefs['refresh_interval'] = \ def post_refresh_rate_limit(self, data, error):
self.refresh_interval_field.get_value_as_int() """Callback for the refresh_rate_limit."""
if error or not data:
_log.error('Error fetching rate limit')
return
x, y = self.window.get_position() # Check if we are running low on our limit
self.prefs['position_x'] = x reset_time = datetime.datetime.fromtimestamp(
self.prefs['position_y'] = y int(data['reset_time_in_seconds']))
if reset_time < datetime.datetime.now():
# Clock differences can cause this
return
time_delta = reset_time - datetime.datetime.now()
mins_till_reset = time_delta.seconds/60 # Good enough!
needed_hits = mins_till_reset/self.prefs['refresh_interval']
remaining_hits = int(data['remaining_hits'])
_log.debug('remaining_hits: %s. reset in %s mins.'
% (remaining_hits, mins_till_reset))
if needed_hits > remaining_hits:
gtk.gdk.threads_enter()
error_dialog = gtk.MessageDialog(parent=self.window,
type=gtk.MESSAGE_WARNING,
flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
message_format='Refresh rate too high',
buttons=gtk.BUTTONS_OK)
error_dialog.format_secondary_text(
"You have only %d twitter requests left until your " \
"request count is reset in %d minutes. But at your " \
"current refresh rate (every %d minutes), you will " \
"exhaust your limit within %d minutes. You should " \
"consider increasing the refresh interval in Mitter's " \
"Settings dialog." % (remaining_hits, mins_till_reset,
self.prefs['refresh_interval'],
remaining_hits * self.prefs['refresh_interval']))
error_dialog.connect("response", lambda *a:
error_dialog.destroy())
error_dialog.run()
gtk.gdk.threads_leave()
def show_last_update(self):
"""Add the last update time in the status bar."""
last_update = self.last_update.strftime('%H:%M')
next_update = (self.last_update +
datetime.timedelta(minutes=self.prefs[
'refresh_interval'])).strftime('%H:%M')
message = 'Last update %s, next update %s' % (last_update,
next_update)
self.statusbar.push(self.statusbar_context, message)
return
def queue_pic(self, pic):
"""Check if the pic is in the queue or already downloaded. If it is
not in any of those, add it to the download queue."""
if pic in self.user_pics:
return
self.save_callback(self.username_field.get_text(), if pic in self.pic_queue:
self.password_field.get_text(), return
self.https_field.get_active(),
NAMESPACE, self.prefs)
self.pic_queue.add(pic)
self.twitter.download(pic, self.post_pic_download, id=pic)
return return
def refresh_rate_limit(self): def post_pic_download(self, data, error, id):
"""Request the rate limit and check if we are doing okay.""" """Function called once we downloaded the user pic."""
self.twitter.rate_limit_status(self.post_refresh_rate_limit)
_log.debug('Received pic %s' % (id))
if error or not data:
_log.debug('Error with the pic, not loading')
return
loader = gtk.gdk.PixbufLoader()
loader.write(data)
loader.close()
self.user_pics[id] = loader.get_pixbuf()
self.pic_queue.discard(id)
# finally, request the grid to redraw itself
gtk.gdk.threads_enter()
self.grid.queue_draw()
gtk.gdk.threads_leave()
return
# ------------------------------------------------------------
# Helper functions
# ------------------------------------------------------------
def _sort_by_time(self, model, iter1, iter2, data=None):
"""The sort function where we sort by the datetime.datetime object"""
d1 = model.get_value(iter1, Columns.DATETIME)
d2 = model.get_value(iter2, Columns.DATETIME)
# Why do we get called with None values?!
if not d1:
return 1
if not d2:
return -1
if d1 < d2:
return -1
elif d1 > d2:
return 1
return 0
# ------------------------------------------------------------
# Widget creation functions
# ------------------------------------------------------------
def _systray_setup(self):
if not (self._app_icon and self._app_icon_alert):
self._systray = None
return
self._systray = gtk.StatusIcon()
self._systray.set_from_file(self._app_icon)
self._systray.connect('activate', self.systray_cb)
self._systray.set_tooltip('Mitter: Click to toggle window visibility.')
self._systray.set_visible(True)
return return
def post_refresh_rate_limit(self, data, error): def _create_main_window(self):
"""Callback for the refresh_rate_limit.""" main_window = gtk.Window()
if error or not data:
_log.error('Error fetching rate limit') main_window.set_title('Mitter')
return main_window.set_size_request(10, 10) # very small minimal size
main_window.resize(self._options[self.NAMESPACE]['width'],
self._options[self.NAMESPACE]['height'])
main_window.move(self._options[self.NAMESPACE]['position_x'],
self._options[self.NAMESPACE]['position_y'])
if self._app_icon:
main_window.set_icon_from_file(self._app_icon)
main_window.connect('destroy', self.quit_app)
main_window.connect('delete-event', self.quit_app)
grid = self._create_grid()
(menu, toolbar, accelerators) = self._create_menu_and_toolbar()
update_field = self._create_update_box()
statusbar = self._create_statusbar()
update_box = gtk.VPaned()
update_box.pack1(grid, resize=True, shrink=False)
update_box.pack2(update_field, resize=False, shrink=True)
box = gtk.VBox(False, 1)
box.pack_start(menu, False, True, 0)
box.pack_start(update_box, True, True, 0)
box.pack_start(statusbar, False, False, 0)
main_window.add(box)
main_window.add_accel_group(accelerators)
return main_window
def _create_grid(self):
"""Add the displaying grid."""
self.grid_store = gtk.ListStore(
str,
str,
str,
str,
str,
object,
object)
self.grid_store.set_sort_func(Columns.DATETIME, self._sort_by_time)
self.grid_store.set_sort_column_id(Columns.DATETIME,
gtk.SORT_DESCENDING)
self.grid = gtk.TreeView(self.grid_store)
self.grid.set_property('headers-visible', False)
self.grid.set_rules_hint(True) # change color for each row
user_renderer = gtk.CellRendererPixbuf()
user_column = gtk.TreeViewColumn('User', user_renderer)
user_column.set_cell_data_func(user_renderer,
self._cell_renderer_user)
self.grid.append_column(user_column)
message_renderer = gtk.CellRendererText()
message_renderer.set_property('wrap-mode', gtk.WRAP_WORD)
message_renderer.set_property('wrap-width', 200)
message_renderer.set_property('width', 10)
message_column = gtk.TreeViewColumn('Message',
message_renderer, text=1)
message_column.set_cell_data_func(message_renderer,
self._cell_renderer_message)
self.grid.append_column(message_column)
self.grid.set_resize_mode(gtk.RESIZE_IMMEDIATE)
#self.grid.connect('cursor-changed', self.check_post)
#self.grid.connect('row-activated', self.open_post)
#self.grid.connect('button-press-event', self.click_post)
#self.grid.connect('popup-menu',
# lambda view: self.show_post_popup(view, None))
scrolled_window = gtk.ScrolledWindow()
scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
scrolled_window.add(self.grid)
return scrolled_window
def _create_menu_and_toolbar(self):
"""Create the main menu and the toolbar."""
# tasks (used by the menu and toolbar)
refresh_action = gtk.Action('Refresh', '_Refresh',
'Update the listing', gtk.STOCK_REFRESH)
refresh_action.connect('activate', self.refresh)
quit_action = gtk.Action('Quit', '_Quit',
'Exit Mitter', gtk.STOCK_QUIT)
quit_action.connect('activate', self.quit)
settings_action = gtk.Action('Settings', '_Settings',
'Settings', gtk.STOCK_PREFERENCES)
settings_action.connect('activate', self.show_settings)
update_action = gtk.Action('Update', '_Update', 'Update your status',
gtk.STOCK_ADD)
update_action.set_property('sensitive', False)
update_action.connect('activate', self._update_status)
delete_action = gtk.Action('Delete', '_Delete', 'Delete a post',
gtk.STOCK_DELETE)
delete_action.set_property('sensitive', False)
delete_action.connect('activate', self.delete_tweet)
about_action = gtk.Action('About', '_About', 'About Mitter',
gtk.STOCK_ABOUT)
about_action.connect('activate', self.show_about)
shrink_url_action = gtk.Action('ShrinkURL', 'Shrink _URL',
'Shrink selected URL', gtk.STOCK_EXECUTE)
shrink_url_action.connect('activate', self.shrink_url)
mute_action = gtk.ToggleAction('MuteNotify', '_Mute Notifications',
'Mutes notifications on new tweets', gtk.STOCK_MEDIA_PAUSE)
mute_action.set_active(False)
post_action = gtk.Action('Posts', '_Posts', 'Post management', None)
file_action = gtk.Action('File', '_File', 'File', None)
edit_action = gtk.Action('Edit', '_Edit', 'Edit', None)
help_action = gtk.Action('Help', '_Help', 'Help', None)
# action group (will have all the actions, 'cause we are not actually
# grouping them, but Gtk requires them that way)
self.action_group = gtk.ActionGroup('MainMenu')
self.action_group.add_action_with_accel(refresh_action, 'F5')
# None = use the default accelerator, based on the STOCK used.
self.action_group.add_action_with_accel(quit_action, None)
self.action_group.add_action(settings_action)
self.action_group.add_action(delete_action)
self.action_group.add_action(post_action)
self.action_group.add_action(file_action)
self.action_group.add_action(edit_action)
self.action_group.add_action(help_action)
self.action_group.add_action(about_action)
self.action_group.add_action_with_accel(shrink_url_action, '<Ctrl>u')
self.action_group.add_action_with_accel(mute_action, '<Ctrl>m')
self.action_group.add_action_with_accel(update_action,
'<Ctrl>Return')
# definition of the UI
# Check if we are running low on our limit uimanager = gtk.UIManager()
reset_time = datetime.datetime.fromtimestamp( uimanager.insert_action_group(self.action_group, 0)
int(data['reset_time_in_seconds'])) ui = '''
<ui>
<toolbar name="MainToolbar">
<toolitem action="Refresh" />
<separator />
<toolitem action="Delete" />
<separator />
<toolitem action="Settings" />
<toolitem action="Quit" />
</toolbar>
<menubar name="MainMenu">
<menu action="File">
<menuitem action="Quit" />
</menu>
<menu action="Edit">
<menuitem action="Refresh" />
<menuitem action="Update" />
<menuitem action="Delete" />
<menuitem action="ShrinkURL" />
<menuitem action="MuteNotify" />
<separator />
<menuitem action="Settings" />
</menu>
<menu action="Help">
<menuitem action="About" />
</menu>
</menubar>
</ui>
'''
uimanager.add_ui_from_string(ui)
if reset_time < datetime.datetime.now(): main_menu = uimanager.get_widget('/MainMenu')
# Clock differences can cause this main_toolbar = uimanager.get_widget('/MainToolbar')
return
time_delta = reset_time - datetime.datetime.now() return (main_menu, main_toolbar, uimanager.get_accel_group())
mins_till_reset = time_delta.seconds/60 # Good enough!
needed_hits = mins_till_reset/self.prefs['refresh_interval']
remaining_hits = int(data['remaining_hits'])
_log.debug('remaining_hits: %s. reset in %s mins.' def _create_update_box(self):
% (remaining_hits, mins_till_reset)) """Create the widgets related to the update box"""
self._update_text = gtk.TextView()
text_buffer = self._update_text.get_buffer()
text_buffer.connect('changed', self._count_chars)
if needed_hits > remaining_hits: update_button = gtk.Button(stock=gtk.STOCK_ADD)
gtk.gdk.threads_enter() update_button.connect('clicked', self._update_status)
error_dialog = gtk.MessageDialog(parent=self.window,
type=gtk.MESSAGE_WARNING,
flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
message_format='Refresh rate too high',
buttons=gtk.BUTTONS_OK)
error_dialog.format_secondary_text(
"You have only %d twitter requests left until your " \
"request count is reset in %d minutes. But at your " \
"current refresh rate (every %d minutes), you will " \
"exhaust your limit within %d minutes. You should " \
"consider increasing the refresh interval in Mitter's " \
"Settings dialog." % (remaining_hits, mins_till_reset,
self.prefs['refresh_interval'],
remaining_hits * self.prefs['refresh_interval']))
error_dialog.connect("response", lambda *a:
error_dialog.destroy())
error_dialog.run()
gtk.gdk.threads_leave()
def show_last_update(self): update_box = gtk.HBox(False, 0)
"""Add the last update time in the status bar.""" update_box.pack_start(self._update_text, expand=True, fill=True,
padding=0)
update_box.pack_start(update_button, expand=False, fill=False,
padding=0)
last_update = self.last_update.strftime('%H:%M') info_box = gtk.HBox(False, 0)
next_update = (self.last_update + self._char_count = gtk.Label()
datetime.timedelta(minutes=self.prefs[ self._char_count.set_text('(140)')
'refresh_interval'])).strftime('%H:%M') info_box.pack_start(gtk.Label('What are you doing?'))
info_box.pack_start(self._char_count)
message = 'Last update %s, next update %s' % (last_update, update_area = gtk.VBox(True, 0)
next_update) update_area.pack_start(info_box)
self.statusbar.push(self.statusbar_context, message) update_area.pack_start(update_box)
return
def queue_pic(self, pic): return update_area
"""Check if the pic is in the queue or already downloaded. If it is
not in any of those, add it to the download queue."""
if pic in self.user_pics:
return
if pic in self.pic_queue: def _create_statusbar(self):
return """Create the statusbar."""
statusbar = gtk.Statusbar()
# TODO: Probaly set the context in the object.
return statusbar
self.pic_queue.add(pic) # ------------------------------------------------------------
self.twitter.download(pic, self.post_pic_download, id=pic) # Cell rendering functions
return # ------------------------------------------------------------
def _cell_renderer_user(self, column, cell, store, position):
"""Callback for the user column. Used to created the pixbuf of the
userpic."""
def post_pic_download(self, data, error, id): pic = store.get_value(position, Columns.PIC)
"""Function called once we downloaded the user pic.""" if not pic in self._user_pics:
cell.set_property('pixbuf', self._default_pixmap)
_log.debug('Received pic %s' % (id)) # just make sure we download this pic too.
self.queue_pic(pic)
else:
cell.set_property('pixbuf', self._user_pics[pic])
if error or not data:
_log.debug('Error with the pic, not loading')
return return
loader = gtk.gdk.PixbufLoader() def _cell_renderer_message(self, column, cell, store, position):
loader.write(data) """Callback for the message column. We need this to adjust the markup
loader.close() property of the cell, as setting it as text won't do any markup
processing."""
self.user_pics[id] = loader.get_pixbuf()
self.pic_queue.discard(id)
# finally, request the grid to redraw itself
gtk.gdk.threads_enter()
self.grid.queue_draw()
gtk.gdk.threads_leave()
return user = store.get_value(position, Columns.NAME)
# ------------------------------------------------------------ message = store.get_value(position, Columns.MESSAGE)
# Helper functions time = store.get_value(position, Columns.DATETIME)
# ------------------------------------------------------------ username = store.get_value(position, Columns.USERNAME)
def _sort_by_time(self, model, iter1, iter2, data=None):
"""The sort function where we sort by the datetime.datetime object"""
d1 = model.get_value(iter1, Columns.DATETIME) time = timesince.timesince(time)
d2 = model.get_value(iter2, Columns.DATETIME)
# Why do we get called with None values?! # unescape escaped entities that pango is okay with
message = re.sub(r'&(?!(amp;|gt;|lt;|quot;|apos;))', r'&amp;', message)
if not d1: # highlight URLs
return 1 message = url_re.sub(r'<span foreground="blue">\1</span>',
if not d2: message)
return -1
if d1 < d2: # use a different highlight for the current user
return -1 message = re.sub(r'(@'+self.twitter.username+')',
elif d1 > d2: r'<span foreground="#FF6633">\1</span>',
return 1 message)
return 0
# ------------------------------------------------------------ markup = '<b>%s</b> <small>(%s)</small>:\n%s\n<small>%s</small>' % \
# Widget creation functions (user, username, message, time)
# ------------------------------------------------------------ cell.set_property('markup', markup)
def _systray_setup(self):
if not (self._app_icon and self._app_icon_alert):
self._systray = None
return
self._systray = gtk.StatusIcon()
self._systray.set_from_file(self._app_icon)
self._systray.connect('activate', self.systray_cb)
self._systray.set_tooltip('Mitter: Click to toggle window visibility.')
self._systray.set_visible(True)
return return
# ------------------------------------------------------------ # ------------------------------------------------------------
@ -1383,6 +1292,9 @@ class Interface(object):
self.update_text.set_sensitive(False) self.update_text.set_sensitive(False)
self.statusbar.push(self.statusbar_context, 'Updating your status...') self.statusbar.push(self.statusbar_context, 'Updating your status...')
def quit_app(self, widget=None):
gtk.main_quit()
# ------------------------------------------------------------ # ------------------------------------------------------------
# Required functions for all interfaces # Required functions for all interfaces
# ------------------------------------------------------------ # ------------------------------------------------------------
@ -1408,21 +1320,12 @@ class Interface(object):
self._default_pixmap = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, self._default_pixmap = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB,
has_alpha=False, bits_per_sample=8, width=48, height=48) has_alpha=False, bits_per_sample=8, width=48, height=48)
self._main_window = _MainWindow(_GtkController()) self._main_window = self._create_main_window()
self._main_window.set_title('Mitter')
self._main_window.set_size_request(10, 10) # very small minimal size
self._main_window.resize(self._options[NAMESPACE]['width'],
self._options[NAMESPACE]['height'])
self._main_window.move(self._options[NAMESPACE]['position_x'],
self._options[NAMESPACE]['position_y'])
if self._app_icon:
self._main_window.set_icon_from_file(self._app_icon)
#self._main_window() #self._main_window()
#self._systray_setup()
#self._systray_setup()
# self.create_settings_dialog() # self.create_settings_dialog()
# self.username_field.set_text(default_username) # self.username_field.set_text(default_username)
# self.password_field.set_text(default_password) # self.password_field.set_text(default_password)
@ -1446,3 +1349,64 @@ class Interface(object):
self._main_window.show_all() self._main_window.show_all()
gtk.main() gtk.main()
@classmethod
def options(self, options):
"""Add the options for this interface."""
options.add_group(self.NAMESPACE, 'GTK+ Interface')
options.add_option('--refresh-interval',
group=self.NAMESPACE,
option='refresh_interval',
help='Refresh interval',
type='int',
metavar='MINUTES',
default=5,
conflict_group='interface')
# Most of the options for non-cmd-options are useless, but I'm keeping
# them as documentation.
options.add_option(
group=self.NAMESPACE,
option='width',
help='Window width',
type='int',
metavar='PIXELS',
default=450,
conflict_group='interface',
is_cmd_option=False)
options.add_option(
group=self.NAMESPACE,
option='height',
help='Window height',
type='int',
metavar='PIXELS',
default=300,
conflict_group='interface',
is_cmd_option=False)
options.add_option(
group=self.NAMESPACE,
option='position_x',
help='Window position on the X axis',
type='int',
metavar='PIXELS',
default=5,
conflict_group='interface',
is_cmd_option=False)
options.add_option(
group=self.NAMESPACE,
option='position_y',
help='Window position on the Y axis',
type='int',
metavar='PIXELS',
default=5,
conflict_group='interface',
is_cmd_option=False)
options.add_option(
group=self.NAMESPACE,
option='max_status_display',
help='Maximum number of elements to keep internally',
type='int',
metavar='MESSAGES',
default=60,
conflict_group='interface',
is_cmd_option=False) # TODO: Should it be config only?

Loading…
Cancel
Save