""" Application.py This is the main module that binds everything together. """ __copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc." __license__ = """ GNU General Public License This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ import time from xml.sax import saxutils import pygtk pygtk.require('2.0') import gobject import gtk import gtk.glade import gnome import gconf import utils import subscribe import Event import MessageManager import FeedList import FeedCategoryList import Config import error from ItemView import ItemView from ItemList import ItemListPresenter, ItemListView from FeedListView import FeedsView, FeedsPresenter from OfflineToggle import OfflineToggle from CategorySelector import CategoryPresenter, CategoryView from MainloopManager import MainloopManager from FeedPropertiesDialog import FeedPropertiesDialog from PreferencesDialog import PreferencesDialog from Find import FindPresenter, FindView import OPMLImport import MVP import ItemStore import ImageCache import PollManager import dialogs import constants class StatusPresenter(MVP.BasicPresenter): def _initialize(self): self._mmgr = MessageManager.get_instance() self._mmgr.signal_connect(Event.StatusDisplaySignal, self._display) return def _display(self, signal): cid = self._view.get_context_id("straw_main") self._view.pop(cid) self._view.push(cid, self._mmgr.read_message()) return class ErrorPresenter: def __init__(self, widget): self._widget = widget self._tooltips = gtk.Tooltips() self._text = '' self._curr_feed = None self._curr_category = None fclist = FeedCategoryList.get_instance() fclist.signal_connect(Event.FeedCategoryChangedSignal, self._category_changed) def display_feed_error(self, feed): if self._curr_feed is not None: self._curr_feed.signal_disconnect(Event.FeedErrorStatusChangedSignal, self._error_status_changed) self._curr_feed = feed self._curr_feed.signal_connect(Event.FeedErrorStatusChangedSignal, self._error_status_changed) self._update_view() return def display_category_error(self, category): if category: self._curr_category = category self._update_view() return def _update_view(self): text = list() if self._curr_category and self._curr_category.subscription and self._curr_category.subscription.error: text.append(_("Category error:")) text.append(self._curr_category.subscription.error) if self._curr_feed and self._curr_feed.error: text.append(_("Feed Error:")) text.append(self._curr_feed.error) if text: t = "\n".join(text) self._tooltips.set_tip(self._widget,t,t) self._tooltips.enable() self._widget.show() else: self._tooltips.disable() self._widget.hide() return def _category_changed(self, signal): if signal.sender is self._curr_category: self.display_category_error(self._curr_category) def _error_status_changed(self, signal): if signal.sender is self._curr_feed: self.display_feed_error(signal.sender) return def hide(self): self._widget.hide() class FeedInfoView(MVP.WidgetView): def _initialize(self): widget_tree = gtk.glade.get_widget_tree(self._widget) self._title = widget_tree.get_widget('feed_item_label') self._refresh = widget_tree.get_widget('feed_refresh_label') def set_title(self, title): self._title.set_label(title) return def set_refresh(self, refresh): self._refresh.set_label(refresh) self._refresh.show() return def hide(self): self._widget.hide() return def show(self): self._widget.show() return class FeedInfoPresenter(MVP.BasicPresenter): def display(self, feed): next = feed.next_refresh if feed: text = _("Next Refresh: %s") % utils.format_date(time.gmtime(next)) self._view.set_refresh(text) self._view.set_title(_("%s") % saxutils.escape(feed.title)) self._view.show() else: self._view.hide() return def hide(self): self._view.set_title("") self._view.set_refresh("") def show(self): self._view.show() class MenuFeedPropsPresenter(MVP.BasicPresenter): def set_sensitive(self, s): self._view.set_sensitive(s) return class ToolbarView(MVP.WidgetView): """ Widget: gtk.Toolbar""" GCONF_DESKTOP_INTERFACE = "/desktop/gnome/interface" def _initialize(self): client = gconf.client_get_default() if not client.dir_exists(self.GCONF_DESKTOP_INTERFACE): return client.add_dir(self.GCONF_DESKTOP_INTERFACE, gconf.CLIENT_PRELOAD_NONE) client.notify_add(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style", self._toolbar_style_changed) self._widget.set_tooltips(True) def _toolbar_style_changed(self, client, notify_id, entry, *args): value = entry.value.get_string() self._presenter.style_changed(value) def set_style(self, style): self._widget.set_style(style) def get_style(self): client = gconf.client_get_default() current_style = client.get_string(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style") return current_style class ToolbarPresenter(MVP.BasicPresenter): STYLES = {'both':gtk.TOOLBAR_BOTH, 'text':gtk.TOOLBAR_TEXT, 'icons':gtk.TOOLBAR_ICONS, 'both-horiz':gtk.TOOLBAR_BOTH_HORIZ} def _initialize(self): style = self._view.get_style() if style in self.STYLES.keys(): self._view.set_style(self.STYLES[style]) return def style_changed(self, value): if value in self.STYLES.keys(): self._view.set_style(self.STYLES[value]) return class ApplicationPresenter(MVP.BasicPresenter): def _initialize(self): self._curr_category = None self._curr_feed = None self._curr_item = None self._init_widgets() self._init_presenters() self._init_signals() self._prefs_dialog = None self._view.present() def _init_widgets(self): widget_tree = self._view.get_widget_tree() self._itemlist_view_notebook = widget_tree.get_widget('mode_view_notebook') self._feedlist_view_notebook = widget_tree.get_widget('left_mode_view_notebook') def _init_presenters(self): widget_tree = self._view.get_widget_tree() toolbar_presenter = ToolbarPresenter(view=ToolbarView(widget_tree.get_widget('toolbar_default'))) self._feed_list_presenter = FeedsPresenter(view=FeedsView(widget_tree.get_widget('feed_selection_treeview'))) self._category_selector = CategoryPresenter(view=CategoryView(widget_tree.get_widget('category_combo'))) self._error_presenter = ErrorPresenter( widget_tree.get_widget('statusbar_error_indicator')) self._offline_presenter = OfflineToggle( widget_tree.get_widget('offline_toggle')) self._itemlist_presenter = ItemListPresenter(view = ItemListView(widget_tree.get_widget('item_selection_treeview'))) self._item_view = ItemView(widget_tree.get_widget('item_view_container')) self._status_presenter = StatusPresenter(view = widget_tree.get_widget("main_statusbar")) self._menufp_presenter = MenuFeedPropsPresenter( view = widget_tree.get_widget('menu_feed_properties')) self._menufp_presenter.set_sensitive(False) self._feedinfo_presenter = FeedInfoPresenter(view = FeedInfoView(widget_tree.get_widget('article_refresh_hbox'))) self._find_presenter = FindPresenter(view=FindView(widget_tree.get_widget("find_vbox"))) return def _init_signals(self): self._feed_list_presenter.signal_connect(Event.FeedSelectionChangedSignal, self._feed_selection_changed) self._feed_list_presenter.signal_connect(Event.FeedsEmptySignal, self._feeds_empty_cb) self._itemlist_presenter.signal_connect(Event.ItemSelectionChangedSignal, self._item_view.item_selection_changed) self._category_selector.signal_connect(Event.CategorySelectionChangedSignal, self._category_selection_changed) return def _feed_selection_changed(self, signal): if signal.old and self._curr_feed: self._curr_feed.unload_contents() if signal.new: if self._curr_feed: self._curr_feed.signal_disconnect(Event.FeedPolledSignal, self._feed_polled) self._curr_feed.signal_disconnect(Event.ItemsAddedSignal, self._feed_items_added) self._curr_feed = signal.new self._curr_feed.signal_connect(Event.FeedPolledSignal, self._feed_polled) self._curr_feed.signal_connect(Event.ItemsAddedSignal, self._feed_items_added) self._display_feed(self._curr_feed) return def _feed_polled(self, signal): self._feedinfo_presenter.display(signal.sender) return def _feed_items_added(self, signal): self._itemlist_presenter.display_feed_items(self._curr_feed, False) def _category_selection_changed(self, signal): self._display_category_feeds(signal.current) return def _display_category_feeds(self, category, *args): self._curr_category = category if category: self._feed_list_presenter.display_category_feeds(category) self._error_presenter.display_category_error(category) else: self._feed_list_presenter.display_empty_category() self._menufp_presenter.set_sensitive(False) self._itemlist_presenter.display_empty_feed() self._item_view.display_empty_feed() return def _display_feed(self, feed, select_first = 1): if feed and feed.number_of_items < 1: self._item_view.display_empty_feed() self._error_presenter.display_feed_error(feed) self._feedinfo_presenter.display(feed) self._itemlist_presenter.display_feed_items(feed, select_first) self._menufp_presenter.set_sensitive(True) return def _feeds_empty_cb(self, signal): self._itemlist_presenter.display_empty_feed() self._item_view.display_empty_feed() self._feedinfo_presenter.hide() self._error_presenter.hide() self._menufp_presenter.set_sensitive(False) return def copy_itemview_text_selection(self): utils.set_clipboard_text(self._item_view.get_selected_text()) def check_allocation(self, widget, event): config = Config.get_instance() def check_size((width, height, widget)): if width == widget.allocation.width and height == widget.allocation.height: config.main_window_size = (width, height) if event.width != widget.allocation.width or event.height != widget.allocation.height: gobject.timeout_add(1000, check_size, ( (event.width, event.height, widget))) def check_main_pane_position(self, widget): config = Config.get_instance() def check_position((position, widget)): if position == widget.get_position(): config.main_pane_position = position pos = widget.get_position() if pos != config.main_pane_position: gobject.timeout_add(1000, check_position, (pos, widget)) def check_sub_pane_position(self, widget): config = Config.get_instance() def check_position((position, widget)): if position == widget.get_position(): config.sub_pane_position = position pos = widget.get_position() if pos != config.sub_pane_position: gobject.timeout_add(1000, check_position, (pos, widget)) def credits(self): return dialogs.credits() def poll_all(self): if self._warn_if_offline(): fclist = FeedCategoryList.get_instance() self._poll_categories(fclist.all_categories) return def poll_current_category(self): if self._warn_if_offline(): self._poll_categories([self._curr_category]) return def poll_current_feed(self): if self._warn_if_offline(): pm = PollManager.get_instance() pm.poll([self._curr_feed]) return def mark_feed_as_read(self): self._curr_feed.mark_all_read() def mark_all_as_read(self): mmgr = MainloopManager.get_instance() flist = FeedList.get_instance().flatten_list() mmgr.call_pending() for feed in flist: feed.mark_all_read() if feed is self._curr_feed: continue feed.unload_contents() mmgr.call_pending() def remove_selected_feed(self): self._feed_list_presenter.remove_selected_feed() def show_search(self): self._itemlist_presenter.signal_disconnect(Event.ItemSelectionChangedSignal, self._item_view.item_selection_changed) self._find_presenter.item_list.signal_connect(Event.ItemSelectionChangedSignal, self._item_view.item_selection_changed) self._item_view.display_empty_search() self._itemlist_view_notebook.set_current_page(1) self._feedlist_view_notebook.set_current_page(1) self._feedinfo_presenter.hide() def hide_search(self): self._find_presenter.clear() self._itemlist_view_notebook.set_current_page(0) self._feedlist_view_notebook.set_current_page(0) self._feedinfo_presenter.show() self._find_presenter.item_list.signal_disconnect(Event.ItemSelectionChangedSignal, self._item_view.item_selection_changed) self._itemlist_presenter.signal_connect(Event.ItemSelectionChangedSignal, self._item_view.item_selection_changed) def _poll_categories(self, fclist): pm = PollManager.get_instance() pm.poll_categories(fclist) return def _warn_if_offline(self): config = Config.get_instance() will_poll = False if config.offline: response = self._view.show_offline_dialog() if response == gtk.RESPONSE_OK: config.offline = not config.offline will_poll = True else: will_poll = True return will_poll def _get_next_category(self, category = None): if not category: category = self._curr_category fclist = FeedCategoryList.get_instance() allcats = fclist.user_categories + list(fclist.pseudo_categories) if category is None: category = allcats[0] else: index = allcats.index(category) if index < len(allcats) - 1: index += 1 else: index = 0 category = allcats[index] return category def _get_previous_category(self, category = None): if category is None: category = self._curr_category fclist = FeedCategoryList.get_instance() allcats = fclist.user_categories + list(fclist.pseudo_categories) if category is None: category = allcats[-1] else: index = allcats.index(category) if index > 0: index -= 1 else: index = len(allcats) - 1 category = allcats[index] return category def display_previous_category(self, category = None): """ Displays the category before the current selected category """ category = self._get_previous_category(category) self._category_selector.category_selected(category) self._display_category_feeds(category) def display_next_category(self, category = None): """ Display the category after the current selected category """ category = self._get_next_category(category) self._category_selector.category_selected(category) self._display_category_feeds(category) def display_previous_feed(self, item = None): """ Displays the feed before the current selected feed """ self._feed_list_presenter.select_previous_feed() def display_next_feed(self, item=None): """ Displays the feed after the current selected feed """ is_next = self._feed_list_presenter.select_next_feed() if not is_next: self._feed_list_presenter.select_first_feed() return def display_next_unread_feed(self): """ Displays the next feed with an unread item """ self._feed_list_presenter.select_next_unread_feed() def display_previous_item(self, item=None): """ Displays the item before the current selected item. If the item is the first item, scrolls to the previous feed """ is_prev = self._itemlist_presenter.select_previous_item() if not is_prev: # TODO HACK - implement select_previous_feed(select_last=True) ... # ... to select previous feed's last item self._feed_list_presenter.select_previous_feed() self._itemlist_presenter.select_last_item() return def display_next_item(self, item=None): """ Displays the item after the current selected item. If the item is the last item, selectes the next feed. If the current feed is the last feed in the list, it goes back and selects the first feed """ is_next = self._itemlist_presenter.select_next_item() if not is_next: is_next_feed = self._feed_list_presenter.select_next_feed() if not is_next_feed: self._feed_list_presenter.select_first_feed() return def scroll_or_display_next_unread_item(self, item=None): has_unread_item = False if not self._item_view.scroll_down(): has_unread_item = self._itemlist_presenter.select_next_unread_item() if not has_unread_item: self._feed_list_presenter.select_next_unread_feed() return def show_preferences_dialog(self, parent): if not self._prefs_dialog: xf = utils.find_glade_file() xml = gtk.glade.XML(xf, "preferences_dialog", gettext.textdomain()) self._prefs_dialog = PreferencesDialog(xml, parent) self._prefs_dialog.show() def show_feed_properties(self, parent): fpd = FeedPropertiesDialog.show_feed_properties(parent, self._curr_feed) return def quit(self): gtk.main_quit() ItemStore.get_instance().stop() return class ApplicationView(MVP.WidgetView): """ Widget: straw_main """ def _initialize(self): self._config = Config.get_instance() self._initialize_dnd() self._initialize_window_updater() self._create_unmodified_accelerator_group() self._attach_unmodified_accelerator_group() self._initialize_window() self._find_toggled = False def _initialize_window(self): widget_tree = gtk.glade.get_widget_tree(self._widget) if self._config.window_maximized: self._widget.maximize() else: # we use resize here since configure-event seems to # overwrite the default size if we use set_default_size. self._widget.resize(*self._config.main_window_size) mmp = widget_tree.get_widget('main_main_pane') msp = widget_tree.get_widget('main_sub_pane') mmp.set_position(self._config.main_pane_position) msp.set_position(self._config.sub_pane_position) def _initialize_dnd(self): self._widget.drag_dest_set( gtk.DEST_DEFAULT_ALL, [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1), ('x-url/http', 0, 2)], gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE) return def _initialize_window_updater(self): feedlist = FeedList.get_instance() feedlist.signal_connect(Event.AllItemsReadSignal, lambda signal: self._update_title(feedlist)) feedlist.signal_connect(Event.ItemReadSignal, lambda signal: self._update_title(feedlist)) feedlist.signal_connect(Event.ItemsAddedSignal, lambda signal: self._update_title(feedlist)) feedlist.signal_connect(Event.FeedsChangedSignal, lambda signal: self._update_title(feedlist)) return def _update_title(self, flist): uritems = urfeeds = 0 sfeeds = "feeds" listfeeds = flist.flatten_list() for ur in [f.n_items_unread for f in listfeeds]: if ur: uritems += ur urfeeds += 1 else: urfeeds = len(listfeeds) if urfeeds < 2: sfeeds = "feed" item_feed_map = {'uritems': uritems, 'urfeeds': urfeeds, 'fstring' : sfeeds} title = _('%(uritems)d unread in %(urfeeds)d %(fstring)s') % item_feed_map self._widget.set_title( title + " - %s" % constants.APPNAME) return # We have a separate accelerator group for the unmodified and # shifted accelerators, that is, stuff like space, N, P, etc. This # is so that we can have the find pane work correctly def _create_unmodified_accelerator_group(self): xml = gtk.glade.get_widget_tree(self._widget) agroup = gtk.AccelGroup() accels = (('menu_mark_feed_as_read', 'R', gtk.gdk.SHIFT_MASK), ('menu_mark_all_as_read', 'A', gtk.gdk.SHIFT_MASK), ('menu_next', 'N', gtk.gdk.SHIFT_MASK), ('menu_next_unread', ' ', 0), ('menu_previous', 'P', gtk.gdk.SHIFT_MASK)) for widget_name, key, mask in accels: widget = xml.get_widget(widget_name) widget.add_accelerator("activate", agroup, ord(key), mask, gtk.ACCEL_VISIBLE) self._unmodified_accelerator_group = agroup def _attach_unmodified_accelerator_group(self): self._widget.add_accel_group(self._unmodified_accelerator_group) def _detach_unmodified_accelerator_group(self): self._widget.remove_accel_group(self._unmodified_accelerator_group) def _on_straw_main_destroy_event(self, *args): return self._presenter.quit() def _on_straw_main_delete_event(self, *args): return self._presenter.quit() def _on_menu_quit_activate(self, *args): return self._presenter.quit() def _on_straw_main_configure_event(self, widget, event, *args): if widget.window.get_state() is not gtk.gdk.WINDOW_STATE_MAXIMIZED: self._config.window_maximized = False self._presenter.check_allocation(widget, event) else: self._config.window_maximized = True return def _on_main_main_pane_size_allocate(self, widget, *args): self._presenter.check_main_pane_position(widget) def _on_main_sub_pane_size_allocate(self, widget, *args): self._presenter.check_sub_pane_position(widget) def _on_menu_report_problem_activate(self, menuitem, *args): utils.url_show("http://bugzilla.gnome.org/simple-bug-guide.cgi?product=straw") def _on_menu_about_activate(self, menuitem, *args): widget = self._presenter.credits() widget.show() def _on_menu_refresh_all_activate(self, *args): self._presenter.poll_all() def _on_menu_refresh_category_activate(self, *args): self._presenter.poll_current_category() def _on_menu_refresh_selected_activate(self, *args): self._presenter.poll_current_feed() def _on_toolbar_refresh_all_button_clicked(self, *args): self._presenter.poll_all() def _on_menu_add_activate(self, *args): subscribe.show(self._widget) def _on_toolbar_subscribe_button_clicked(self, *args): subscribe.show(self._widget) def _on_menu_import_subscriptions_activate(self, *args): dialogs.import_subscriptions(self._widget) def _on_menu_export_subscriptions_activate(self, *args): dialogs.export_subscriptions(self._widget) def _on_menu_copy_text_activate(self, *args): self._presenter.copy_itemview_text_selection() def _on_menu_mark_feed_as_read_activate(self, *args): self._presenter.mark_feed_as_read() def _on_menu_mark_all_as_read_activate(self, *args): self._presenter.mark_all_as_read() def _on_find_activate(self, widget, *args): xml = gtk.glade.get_widget_tree(self._widget) menu_find = xml.get_widget('menu_find') accel_label = menu_find.get_child() if not self._find_toggled: self._presenter.show_search() self._detach_unmodified_accelerator_group() self._find_toggled = True # save the "Find..." stock text for later recovery self._old_label_text = accel_label.get_text() accel_label.set_text(_('Return to feed list...')) else: self._presenter.hide_search() self._attach_unmodified_accelerator_group() self._find_toggled = False accel_label.set_text(self._old_label_text) def _on_menu_next_activate(self, *args): self._presenter.display_next_item() def _on_toolbar_scroll_or_next_button_clicked(self, *args): self._presenter.scroll_or_display_next_unread_item() def _on_menu_scroll_next_activate(self, *args): self._presenter.scroll_or_display_next_unread_item() def _on_menu_previous_activate(self, *args): self._presenter.display_previous_item() def _on_menu_next_feed_unread_activate(self, *args): self._presenter.display_next_unread_feed() def _on_menu_next_feed_activate(self, *args): self._presenter.display_next_feed() def _on_menu_previous_feed_activate(self, *args): self._presenter.display_previous_feed() def _on_menu_remove_selected_feed_activate(self, *args): self._presenter.remove_selected_feed() def _on_menu_next_category_activate(self, *args): self._presenter.display_next_category() def _on_menu_previous_category_activate(self, *args): self._presenter.display_previous_category() def _on_menu_next_category_activate(self, *args): self._presenter.display_next_category() def _on_menu_previous_category_activate(self, *args): self._presenter.display_previous_category() def _on_menu_preferences_activate(self, *args): self._presenter.show_preferences_dialog(self._widget) def _on_menu_feed_properties_activate(self, *args): self._presenter.show_feed_properties(self._widget) def _on_straw_main_drag_data_received(self, w, context, x, y, data, info, time): if data and data.format == 8: url = data.data.split("\n")[0] subscribe.show(self._widget, "%s" % url) context.finish(True, False, time) else: context.finish(False, False, time) def show_offline_dialog(self): return dialogs.report_offline_status(self._widget) def get_widget_tree(self): return gtk.glade.get_widget_tree(self._widget) def present(self): self._widget.present() def should_present(self): if self._widget.window.get_state() is not gtk.gdk.WINDOW_STATE_WITHDRAWN: self._widget.hide() else: self._widget.present() import os import gettext import getopt import sys import strawdbus class Application: def __init__(self): gnome.program_init(constants.APPNAME.lower(), constants.VERSION) self._initialize_config_from_environment() gtk.window_set_auto_startup_notification(True) self._initialize_gettext() config = Config.get_instance() config.proxy_config.lookup_host() feedlist = FeedList.get_instance() feed_categories = FeedCategoryList.get_instance() # initialise GUI xmlfile = utils.find_glade_file() xml = gtk.glade.XML(xmlfile, "straw_main", gettext.textdomain()) window = xml.get_widget('straw_main') self._main_presenter = ApplicationPresenter(view = ApplicationView(window)) if config.first_time: libdir = utils.find_data_dir() filepath = os.path.join(libdir, "default_subscriptions.opml") OPMLImport.import_opml(filepath) else: feedlist.load_data() feed_categories.load_data() try: itemstore = ItemStore.get_instance(config.straw_dir) except ItemStore.ConvertException, ex: dialogs.report_error(_("There was a problem while converting the database."), _("Straw will not behave as expected. You should probably quit now. " + "The exception has been saved to the file '%s'. Please see the Straw README for further instructions." ) % ex.reason) sys.exit() ImageCache.initialize() itemstore.start() self._main_presenter.view.present() PollManager.get_instance().start_polling_loop() # set the default icon for the windows iconfile = os.path.join(utils.find_image_dir(),"straw.png") gtk.window_set_default_icon(gtk.gdk.pixbuf_new_from_file(iconfile)) strawdbus.start_services() def mainloop(self): threads = Config.get_instance().use_threads if threads: gtk.threads_enter() gtk.main() if threads: gtk.threads_leave() return def _initialize_config_from_environment(self): threads = os.getenv('STRAW_THREAD_DNS') is not None if threads: try: gtk.threads_init() except: threads = False else: threads = True config = Config.get_instance() config.use_threads = threads config.reload_css = os.getenv('STRAW_RELOAD_CSS') is not None config.no_etags = os.getenv('STRAW_NO_ETAGS') is not None return def _initialize_gettext(self): import locale lname = constants.APPNAME.lower() try: localedir = utils.find_locale_dir() gettext.bindtextdomain(lname, localedir) gettext.textdomain(lname) gettext.install(lname, localedir, unicode=1) gtk.glade.bindtextdomain(lname, localedir) except IOError: def broken_gettext_workaround(s): return s __builtins__.__dict__['_'] = broken_gettext_workaround locale.setlocale(locale.LC_ALL, '') return def load_tray(self): from Tray import Tray tray = Tray() tray.connect('button_press_event', self._tray_clicked) def _tray_clicked(self, signal, event): if event.button == 1: self._main_presenter.view.present() self._main_presenter.scroll_or_display_next_unread_item() else: self._main_presenter.view.should_present() return