Fixed item navigation and cleaned up feed and item changes
[straw.git] / src / lib / Application.py
blob9268c7fd02ea83c58efea84f70d6a9b205f4a325
1 """ Application.py
3 This is the main module that binds everything together.
5 """
6 __copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc."
7 __license__ = """ GNU General Public License
9 This program is free software; you can redistribute it and/or modify it under the
10 terms of the GNU General Public License as published by the Free Software
11 Foundation; either version 2 of the License, or (at your option) any later
12 version.
14 This program is distributed in the hope that it will be useful, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License along with
19 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
20 Place - Suite 330, Boston, MA 02111-1307, USA. """
22 import time
23 from xml.sax import saxutils
24 import pygtk
25 pygtk.require('2.0')
26 import gobject, gtk, gtk.glade
27 import gnome, gconf
28 import utils
29 import subscribe
30 import Event
31 import MessageManager
32 import feeds
33 import FeedCategoryList
34 import Config
35 import error
36 from ItemView import ItemView
37 from ItemList import ItemListPresenter, ItemListView
38 from FeedListView import FeedsView, FeedsPresenter
39 from OfflineToggle import OfflineToggle
40 from MainloopManager import MainloopManager
41 from FeedPropertiesDialog import FeedPropertyView, FeedPropertyPresenter, show_feed_properties as show_feed_properties_dialog
42 from PreferencesDialog import PreferencesDialog
43 #from Find import FindPresenter, FindView
44 import OPMLImport
45 import MVP
46 import ItemStore
47 import ImageCache
48 import PollManager
49 import dialogs
50 import constants
52 class StatusPresenter(MVP.BasicPresenter):
53 def _initialize(self):
54 self._mmgr = MessageManager.get_instance()
55 self._mmgr.signal_connect(Event.StatusDisplaySignal,
56 self._display)
57 return
59 def _display(self, signal):
60 cid = self._view.get_context_id("straw_main")
61 self._view.pop(cid)
62 self._view.push(cid, self._mmgr.read_message())
63 return
65 class ErrorPresenter:
66 def __init__(self, widget):
67 self._widget = widget
68 self._tooltips = gtk.Tooltips()
69 self._text = ''
70 #self._curr_feed = None
71 #self._curr_category = None
72 #fclist = FeedCategoryList.get_instance()
73 #fclist.connect('category-changed', self._category_changed)
75 def feedlist_selection_changed(self, selection, column):
76 errortexts = None
77 (model, pathlist) = selection.get_selected_rows()
78 iters = [model.get_iter(path) for path in pathlist]
79 if not iters: return
80 errorfeeds = []
81 nodes = [model.get_value(treeiter, column) for treeiter in iters]
82 try:
83 errorfeeds = [node.feed for node in nodes if node.feed and node.feed.error]
84 errortexts = [feed.error for feed in errorfeeds]
85 except TypeError, te:
86 print te
87 ### XXX display OPML Category error too
89 text = list()
90 if errorfeeds:
91 text.append(_("Error:"))
92 text += errortexts
94 if text:
95 t = "\n".join(text)
96 self._tooltips.set_tip(self._widget,t,t)
97 self._tooltips.enable()
98 self._widget.show()
99 else:
100 self._tooltips.disable()
101 self._widget.hide()
102 return
104 def hide(self):
105 self._widget.hide()
107 class MenuFeedPropsPresenter(MVP.BasicPresenter):
108 def set_sensitive(self, s):
109 self._view.set_sensitive(s)
110 return
112 class ToolbarView(MVP.WidgetView):
113 """ Widget: gtk.Toolbar"""
114 GCONF_DESKTOP_INTERFACE = "/desktop/gnome/interface"
116 def _initialize(self):
117 client = gconf.client_get_default()
118 if not client.dir_exists(self.GCONF_DESKTOP_INTERFACE):
119 return
120 client.add_dir(self.GCONF_DESKTOP_INTERFACE,
121 gconf.CLIENT_PRELOAD_NONE)
122 client.notify_add(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style",
123 self._toolbar_style_changed)
124 self._widget.set_tooltips(True)
126 def _toolbar_style_changed(self, client, notify_id, entry, *args):
127 value = entry.value.get_string()
128 self._presenter.style_changed(value)
130 def set_style(self, style):
131 self._widget.set_style(style)
133 def get_style(self):
134 client = gconf.client_get_default()
135 current_style = client.get_string(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style")
136 return current_style
138 class ToolbarPresenter(MVP.BasicPresenter):
139 STYLES = {'both':gtk.TOOLBAR_BOTH,
140 'text':gtk.TOOLBAR_TEXT,
141 'icons':gtk.TOOLBAR_ICONS,
142 'both-horiz':gtk.TOOLBAR_BOTH_HORIZ}
144 def _initialize(self):
145 style = self._view.get_style()
146 if style in self.STYLES.keys():
147 self._view.set_style(self.STYLES[style])
148 return
150 def style_changed(self, value):
151 if value in self.STYLES.keys():
152 self._view.set_style(self.STYLES[value])
153 return
155 class ApplicationPresenter(MVP.BasicPresenter):
156 def _initialize(self):
157 self._curr_category = None
158 self._curr_feed = None
159 self._curr_item = None
160 self._init_widgets()
161 self._init_presenters()
162 self._prefs_dialog = None
163 self._view.present()
165 def _init_widgets(self):
166 widget_tree = self._view.get_widget_tree()
167 self._itemlist_view_notebook = widget_tree.get_widget('mode_view_notebook')
168 self._feedlist_view_notebook = widget_tree.get_widget('left_mode_view_notebook')
170 def _init_presenters(self):
171 widget_tree = self._view.get_widget_tree()
172 toolbar_presenter = ToolbarPresenter(view=ToolbarView(widget_tree.get_widget('toolbar_default')))
173 self._error_presenter = ErrorPresenter(widget_tree.get_widget('statusbar_error_indicator'))
175 self._item_view = ItemView(widget_tree.get_widget('item_view_container'))
177 view = ItemListView(widget_tree.get_widget('item_selection_treeview'))
178 self._itemlist_presenter = ItemListPresenter(view=view)
179 view.add_selection_changed_listener(self._item_view)
181 view = FeedsView(widget_tree.get_widget('feed_selection_treeview'))
182 self._feed_list_presenter = FeedsPresenter(view=view)
183 view.add_selection_changed_listener(self._itemlist_presenter)
184 view.add_selection_changed_listener(self._error_presenter)
186 self._offline_presenter = OfflineToggle(widget_tree.get_widget('offline_toggle'))
188 self._status_presenter = StatusPresenter(view = widget_tree.get_widget("main_statusbar"))
189 self._menufp_presenter = MenuFeedPropsPresenter( view = widget_tree.get_widget('menu_feed_properties'))
190 self._menufp_presenter.set_sensitive(False)
191 # self._find_presenter = FindPresenter(view=FindView(widget_tree.get_widget("find_vbox")))
192 return
194 def copy_itemview_text_selection(self):
195 utils.set_clipboard_text(self._item_view.get_selected_text())
197 def check_allocation(self, widget, event):
198 config = Config.get_instance()
199 def check_size((width, height, widget)):
200 if width == widget.allocation.width and height == widget.allocation.height:
201 config.main_window_size = (width, height)
202 if event.width != widget.allocation.width or event.height != widget.allocation.height:
203 gobject.timeout_add(1000, check_size, (
204 (event.width, event.height, widget)))
206 def check_main_pane_position(self, widget):
207 config = Config.get_instance()
208 def check_position((position, widget)):
209 if position == widget.get_position():
210 config.main_pane_position = position
211 pos = widget.get_position()
212 if pos != config.main_pane_position:
213 gobject.timeout_add(1000, check_position, (pos, widget))
215 def check_sub_pane_position(self, widget):
216 config = Config.get_instance()
217 def check_position((position, widget)):
218 if position == widget.get_position():
219 config.sub_pane_position = position
220 pos = widget.get_position()
221 if pos != config.sub_pane_position:
222 gobject.timeout_add(1000, check_position, (pos, widget))
224 def credits(self):
225 return dialogs.credits()
227 def poll_all(self):
228 if self._warn_if_offline():
229 fclist = FeedCategoryList.get_instance()
230 self._poll_categories(fclist.all_categories)
231 return
233 def poll_current_category(self):
234 if self._warn_if_offline():
235 self._poll_categories([self._curr_category])
236 return
238 def poll_current_feed(self):
239 if self._warn_if_offline():
240 pm = PollManager.get_instance()
241 pm.poll([self._curr_feed])
242 return
244 def mark_feed_as_read(self):
245 self._curr_feed.mark_all_read()
247 def mark_all_as_read(self):
248 mmgr = MainloopManager.get_instance()
249 flist = feeds.get_instance().flatten_list()
250 mmgr.call_pending()
251 for feed in flist:
252 feed.mark_all_read()
253 if feed is self._curr_feed:
254 continue
255 feed.unload_contents()
256 mmgr.call_pending()
258 def remove_selected_feed(self):
259 # self._feed_list_presenter.remove_selected_feed()
260 pass
262 def show_search(self):
263 # self._find_presenter.item_list.signal_connect(Event.ItemSelectionChangedSignal,
264 # self._item_view.item_selection_changed)
265 self._item_view.display_empty_search()
266 self._itemlist_view_notebook.set_current_page(1)
267 self._feedlist_view_notebook.set_current_page(1)
268 self._feedinfo_presenter.hide()
270 def hide_search(self):
271 # self._find_presenter.clear()
272 self._itemlist_view_notebook.set_current_page(0)
273 self._feedlist_view_notebook.set_current_page(0)
274 self._feedinfo_presenter.show()
275 # self._find_presenter.item_list.signal_disconnect(Event.ItemSelectionChangedSignal,
276 # self._item_view.item_selection_changed)
279 def _poll_categories(self, fclist):
280 pm = PollManager.get_instance()
281 pm.poll_categories(fclist)
282 return
284 def _warn_if_offline(self):
285 config = Config.get_instance()
286 will_poll = False
287 if config.offline:
288 response = self._view.show_offline_dialog()
289 if response == gtk.RESPONSE_OK:
290 config.offline = not config.offline
291 will_poll = True
292 else:
293 will_poll = True
294 return will_poll
296 def _get_next_category(self, category = None):
297 if not category:
298 category = self._curr_category
299 fclist = FeedCategoryList.get_instance()
300 allcats = fclist.user_categories + list(fclist.pseudo_categories)
301 if category is None:
302 category = allcats[0]
303 else:
304 index = allcats.index(category)
305 if index < len(allcats) - 1:
306 index += 1
307 else:
308 index = 0
309 category = allcats[index]
310 return category
312 def _get_previous_category(self, category = None):
313 if category is None:
314 category = self._curr_category
315 fclist = FeedCategoryList.get_instance()
316 allcats = fclist.user_categories + list(fclist.pseudo_categories)
317 if category is None:
318 category = allcats[-1]
319 else:
320 index = allcats.index(category)
321 if index > 0:
322 index -= 1
323 else:
324 index = len(allcats) - 1
325 category = allcats[index]
326 return category
328 def display_previous_category(self, category = None):
330 Displays the category before the current selected category
332 category = self._get_previous_category(category)
333 # self._category_selector.category_selected(category)
334 self._display_category_feeds(category)
336 def display_next_category(self, category = None):
338 Display the category after the current selected category
340 category = self._get_next_category(category)
341 # self._category_selector.category_selected(category)
342 self._display_category_feeds(category)
344 def display_previous_feed(self, item = None):
346 Displays the feed before the current selected feed
348 self._feed_list_presenter.select_previous_feed()
350 def display_next_feed(self, item=None):
352 Displays the feed after the current selected feed
354 self._feed_list_presenter.select_next_feed()
355 return
357 def display_next_unread_feed(self):
359 Displays the next feed with an unread item
361 self._feed_list_presenter.select_next_feed(with_unread=True)
362 pass
364 def display_previous_item(self, item=None):
366 Displays the item before the current selected item. If the item is the
367 first item, scrolls to the previous feed
369 is_prev = self._itemlist_presenter.select_previous_item()
370 if not is_prev:
371 # TODO HACK - implement select_previous_feed(select_last=True) ...
372 # ... to select previous feed's last item
373 self._feed_list_presenter.select_previous_feed()
374 self._itemlist_presenter.select_last_item()
375 pass
376 return
378 def display_next_item(self, item=None):
380 Displays the item after the current selected item. If the item is the
381 last item, selectes the next feed. If the current feed is the last
382 feed in the list, it goes back and selects the first feed
384 is_next = self._itemlist_presenter.select_next_item()
385 if not is_next:
386 is_next_feed = self._feed_list_presenter.select_next_feed()
387 if not is_next_feed:
388 self._feed_list_presenter.select_firsteed_feed()
389 return
391 def scroll_or_display_next_unread_item(self, item=None):
392 has_unread_item = False
393 if not self._item_view.scroll_down():
394 has_unread_item = self._itemlist_presenter.select_next_unread_item()
395 if not has_unread_item:
396 self._feed_list_presenter.select_next_feed(with_unread=True)
397 return
399 def show_preferences_dialog(self, parent):
400 if not self._prefs_dialog:
401 xf = utils.find_glade_file()
402 xml = gtk.glade.XML(xf, "preferences_dialog", gettext.textdomain())
403 self._prefs_dialog = PreferencesDialog(xml, parent)
404 self._prefs_dialog.show()
406 def show_feed_properties(self, parent):
407 fpd = show_feed_properties_dialog(parent, self._curr_feed)
408 return
410 def quit(self):
411 gtk.main_quit()
412 ItemStore.get_instance().stop()
413 return
415 class ApplicationView(MVP.WidgetView):
417 Widget: straw_main
419 def _initialize(self):
420 self._config = Config.get_instance()
421 self._initialize_dnd()
422 self._initialize_window_updater()
423 self._create_unmodified_accelerator_group()
424 self._attach_unmodified_accelerator_group()
425 self._initialize_window()
426 self._find_toggled = False
428 def _initialize_window(self):
429 widget_tree = gtk.glade.get_widget_tree(self._widget)
430 if self._config.window_maximized:
431 self._widget.maximize()
432 else:
433 # we use resize here since configure-event seems to
434 # overwrite the default size if we use set_default_size.
435 self._widget.resize(*self._config.main_window_size)
436 mmp = widget_tree.get_widget('main_main_pane')
437 msp = widget_tree.get_widget('main_sub_pane')
438 mmp.set_position(self._config.main_pane_position)
439 msp.set_position(self._config.sub_pane_position)
441 def _initialize_dnd(self):
442 self._widget.drag_dest_set(
443 gtk.DEST_DEFAULT_ALL,
444 [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1),
445 ('x-url/http', 0, 2)],
446 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
447 return
449 def _initialize_window_updater(self):
450 feedlist = feeds.get_instance()
451 #feedlist.signal_connect(Event.AllItemsReadSignal,
452 # lambda signal: self._update_title(feedlist))
453 #feedlist.signal_connect(Event.ItemReadSignal,
454 # lambda signal: self._update_title(feedlist))
455 #feedlist.signal_connect(Event.ItemsAddedSignal,
456 # lambda signal: self._update_title(feedlist))
457 #feedlist.signal_connect(Event.FeedsChangedSignal,
458 # lambda signal: self._update_title(feedlist))
459 return
461 def _update_title(self, flist):
462 uritems = urfeeds = 0
463 sfeeds = "feeds"
464 listfeeds = flist.flatten_list()
465 for ur in [f.number_of_unread for f in listfeeds]:
466 if ur:
467 uritems += ur
468 urfeeds += 1
469 else:
470 urfeeds = len(listfeeds)
471 if urfeeds < 2:
472 sfeeds = "feed"
473 item_feed_map = {'uritems': uritems,
474 'urfeeds': urfeeds,
475 'fstring' : sfeeds}
476 title = _('%(uritems)d unread in %(urfeeds)d %(fstring)s') % item_feed_map
477 self._widget.set_title( title + " - %s" % constants.APPNAME)
478 return
480 # We have a separate accelerator group for the unmodified and
481 # shifted accelerators, that is, stuff like space, N, P, etc. This
482 # is so that we can have the find pane work correctly
483 def _create_unmodified_accelerator_group(self):
484 xml = gtk.glade.get_widget_tree(self._widget)
485 agroup = gtk.AccelGroup()
486 accels = (('menu_mark_feed_as_read', 'R', gtk.gdk.SHIFT_MASK),
487 ('menu_mark_all_as_read', 'A', gtk.gdk.SHIFT_MASK),
488 ('menu_next', 'N', gtk.gdk.SHIFT_MASK),
489 ('menu_next_unread', ' ', 0),
490 ('menu_previous', 'P', gtk.gdk.SHIFT_MASK))
491 for widget_name, key, mask in accels:
492 widget = xml.get_widget(widget_name)
493 widget.add_accelerator("activate", agroup, ord(key), mask,
494 gtk.ACCEL_VISIBLE)
495 self._unmodified_accelerator_group = agroup
497 def _attach_unmodified_accelerator_group(self):
498 self._widget.add_accel_group(self._unmodified_accelerator_group)
500 def _detach_unmodified_accelerator_group(self):
501 self._widget.remove_accel_group(self._unmodified_accelerator_group)
503 def _on_straw_main_destroy_event(self, *args):
504 return self._presenter.quit()
506 def _on_straw_main_delete_event(self, *args):
507 return self._presenter.quit()
509 def _on_menu_quit_activate(self, *args):
510 return self._presenter.quit()
512 def _on_straw_main_configure_event(self, widget, event, *args):
513 if widget.window.get_state() is not gtk.gdk.WINDOW_STATE_MAXIMIZED:
514 self._config.window_maximized = False
515 self._presenter.check_allocation(widget, event)
516 else:
517 self._config.window_maximized = True
518 return
520 def _on_main_main_pane_size_allocate(self, widget, *args):
521 self._presenter.check_main_pane_position(widget)
523 def _on_main_sub_pane_size_allocate(self, widget, *args):
524 self._presenter.check_sub_pane_position(widget)
526 def _on_menu_report_problem_activate(self, menuitem, *args):
527 utils.url_show("http://bugzilla.gnome.org/simple-bug-guide.cgi?product=straw")
529 def _on_menu_about_activate(self, menuitem, *args):
530 widget = self._presenter.credits()
531 widget.show()
533 def _on_menu_refresh_all_activate(self, *args):
534 self._presenter.poll_all()
536 def _on_menu_refresh_category_activate(self, *args):
537 self._presenter.poll_current_category()
539 def _on_menu_refresh_selected_activate(self, *args):
540 self._presenter.poll_current_feed()
542 def _on_toolbar_refresh_all_button_clicked(self, *args):
543 self._presenter.poll_all()
545 def _on_menu_add_activate(self, *args):
546 subscribe.display_window(parent=self._widget)
548 def _on_toolbar_subscribe_button_clicked(self, *args):
549 subscribe.display_window(parent=self._widget)
551 def _on_menu_import_subscriptions_activate(self, *args):
552 dialogs.import_subscriptions(self._widget)
554 def _on_menu_export_subscriptions_activate(self, *args):
555 dialogs.export_subscriptions(self._widget)
557 def _on_menu_copy_text_activate(self, *args):
558 self._presenter.copy_itemview_text_selection()
560 def _on_menu_mark_feed_as_read_activate(self, *args):
561 self._presenter.mark_feed_as_read()
563 def _on_menu_mark_all_as_read_activate(self, *args):
564 self._presenter.mark_all_as_read()
566 def _on_find_activate(self, widget, *args):
567 xml = gtk.glade.get_widget_tree(self._widget)
568 menu_find = xml.get_widget('menu_find')
569 accel_label = menu_find.get_child()
571 if not self._find_toggled:
572 self._presenter.show_search()
573 self._detach_unmodified_accelerator_group()
574 self._find_toggled = True
576 # save the "Find..." stock text for later recovery
577 self._old_label_text = accel_label.get_text()
578 accel_label.set_text(_('Return to feed list...'))
580 else:
581 self._presenter.hide_search()
582 self._attach_unmodified_accelerator_group()
583 self._find_toggled = False
584 accel_label.set_text(self._old_label_text)
586 def _on_menu_next_activate(self, *args):
587 self._presenter.display_next_item()
589 def _on_toolbar_scroll_or_next_button_clicked(self, *args):
590 self._presenter.scroll_or_display_next_unread_item()
592 def _on_menu_scroll_next_activate(self, *args):
593 self._presenter.scroll_or_display_next_unread_item()
595 def _on_menu_previous_activate(self, *args):
596 self._presenter.display_previous_item()
598 def _on_menu_next_feed_unread_activate(self, *args):
599 self._presenter.display_next_unread_feed()
601 def _on_menu_next_feed_activate(self, *args):
602 self._presenter.display_next_feed()
604 def _on_menu_previous_feed_activate(self, *args):
605 self._presenter.display_previous_feed()
607 def _on_menu_remove_selected_feed_activate(self, *args):
608 self._presenter.remove_selected_feed()
610 def _on_menu_next_category_activate(self, *args):
611 self._presenter.display_next_category()
613 def _on_menu_previous_category_activate(self, *args):
614 self._presenter.display_previous_category()
616 def _on_menu_next_category_activate(self, *args):
617 self._presenter.display_next_category()
619 def _on_menu_previous_category_activate(self, *args):
620 self._presenter.display_previous_category()
622 def _on_menu_preferences_activate(self, *args):
623 self._presenter.show_preferences_dialog(self._widget)
625 def _on_menu_feed_properties_activate(self, *args):
626 self._presenter.show_feed_properties(self._widget)
628 def _on_straw_main_drag_data_received(self, w, context,
629 x, y, data, info, time):
630 if data and data.format == 8:
631 url = data.data.split("\n")[0]
632 subscribe.display_window("%s" % url, self._widget)
633 context.finish(True, False, time)
634 else:
635 context.finish(False, False, time)
637 def show_offline_dialog(self):
638 return dialogs.report_offline_status(self._widget)
640 def get_widget_tree(self):
641 return gtk.glade.get_widget_tree(self._widget)
643 def present(self):
644 self._widget.present()
646 def should_present(self):
647 if self._widget.window.get_state() is not gtk.gdk.WINDOW_STATE_WITHDRAWN:
648 self._widget.hide()
649 else:
650 self._widget.present()
652 import os
653 import gettext
654 import getopt
655 import sys
656 import strawdbus
658 class Application:
659 def __init__(self):
660 gnome.program_init(constants.APPNAME.lower(), constants.VERSION)
661 # initialize threading and environment
662 gobject.threads_init()
663 config = Config.get_instance()
664 config.reload_css = os.getenv('STRAW_RELOAD_CSS') is not None
666 gtk.window_set_auto_startup_notification(True)
667 self._initialize_gettext()
669 config = Config.get_instance()
671 feedlist = feeds.get_instance()
672 feed_categories = FeedCategoryList.get_instance()
674 # initialise GUI
675 #xmlfile = utils.find_glade_file()
676 #xml = gtk.glade.XML(xmlfile, "straw_main", gettext.textdomain())
677 #window = xml.get_widget('straw_main')
678 #self._main_presenter = ApplicationPresenter(view =
679 # ApplicationView(window))
681 if config.first_time:
682 libdir = utils.find_data_dir()
683 filepath = os.path.join(libdir, "default_subscriptions.opml")
684 OPMLImport.import_opml(filepath)
685 else:
686 feedlist.load_data()
687 feed_categories.load_data()
689 try:
690 itemstore = ItemStore.get_instance()
691 except ItemStore.ConvertException, ex:
692 dialogs.report_error(_("There was a problem while converting the database."),
693 _("Straw will not behave as expected. You should probably quit now. " +
694 "The exception has been saved to the file '%s'. Please see the Straw README for further instructions."
695 ) % ex.reason)
696 sys.exit()
698 ImageCache.initialize()
699 itemstore.start()
702 xmlfile = utils.find_glade_file()
703 xml = gtk.glade.XML(xmlfile, "straw_main", gettext.textdomain())
704 window = xml.get_widget('straw_main')
705 self._main_presenter = ApplicationPresenter(view =
706 ApplicationView(window))
707 self._main_presenter.view.present()
709 PollManager.get_instance().start_polling_loop()
710 # set the default icon for the windows
711 iconfile = os.path.join(utils.find_image_dir(),"straw.png")
712 gtk.window_set_default_icon(gtk.gdk.pixbuf_new_from_file(iconfile))
714 strawdbus.start_services()
716 def mainloop(self):
717 gtk.main()
718 return
720 def _initialize_gettext(self):
721 import locale
722 lname = constants.APPNAME.lower()
723 try:
724 localedir = utils.find_locale_dir()
725 gettext.bindtextdomain(lname, localedir)
726 gettext.textdomain(lname)
727 gettext.install(lname, localedir, unicode=1)
728 gtk.glade.bindtextdomain(lname, localedir)
729 except IOError:
730 def broken_gettext_workaround(s):
731 return s
732 __builtins__.__dict__['_'] = broken_gettext_workaround
733 locale.setlocale(locale.LC_ALL, '')
734 return
736 def load_tray(self):
737 from Tray import Tray
738 tray = Tray()
739 tray.connect('button_press_event', self._tray_clicked)
741 def _tray_clicked(self, signal, event):
742 if event.button == 1:
743 self._main_presenter.view.present()
744 self._main_presenter.scroll_or_display_next_unread_item()
745 else:
746 self._main_presenter.view.should_present()
747 return