iteration 1 - Use gobject for events in feed, summaryitem and feedlist
[straw.git] / src / lib / Application.py
blob43f5e44a6494155ce0b86fa2f3dbe2688bb34d81
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
27 import gtk
28 import gtk.glade
29 import gnome
30 import gconf
31 import utils
32 import subscribe
33 import Event
34 import MessageManager
35 import feeds
36 import FeedCategoryList
37 import Config
38 import error
39 from ItemView import ItemView
40 from ItemList import ItemListPresenter, ItemListView
41 from FeedListView import FeedsView, FeedsPresenter
42 from OfflineToggle import OfflineToggle
43 #from CategorySelector import CategoryPresenter, CategoryView
44 from MainloopManager import MainloopManager
45 from FeedPropertiesDialog import FeedPropertyView, FeedPropertyPresenter, show_feed_properties as show_feed_properties_dialog
46 from PreferencesDialog import PreferencesDialog
47 from Find import FindPresenter, FindView
48 import OPMLImport
49 import MVP
50 import ItemStore
51 import ImageCache
52 import PollManager
53 import dialogs
54 import constants
56 class StatusPresenter(MVP.BasicPresenter):
57 def _initialize(self):
58 self._mmgr = MessageManager.get_instance()
59 self._mmgr.signal_connect(Event.StatusDisplaySignal,
60 self._display)
61 return
63 def _display(self, signal):
64 cid = self._view.get_context_id("straw_main")
65 self._view.pop(cid)
66 self._view.push(cid, self._mmgr.read_message())
67 return
69 class ErrorPresenter:
70 def __init__(self, widget):
71 self._widget = widget
72 self._tooltips = gtk.Tooltips()
73 self._text = ''
74 self._curr_feed = None
75 self._curr_category = None
76 fclist = FeedCategoryList.get_instance()
77 fclist.signal_connect(Event.FeedCategoryChangedSignal,
78 self._category_changed)
80 def display_feed_error(self, feed):
81 if self._curr_feed is not None:
82 self._curr_feed.signal_disconnect(Event.FeedErrorStatusChangedSignal,
83 self._error_status_changed)
84 self._curr_feed = feed
85 self._curr_feed.signal_connect(Event.FeedErrorStatusChangedSignal,
86 self._error_status_changed)
87 self._update_view()
88 return
90 def display_category_error(self, category):
91 if category:
92 self._curr_category = category
93 self._update_view()
94 return
96 def _update_view(self):
97 text = list()
98 if self._curr_category and self._curr_category.subscription and self._curr_category.subscription.error:
99 text.append(_("Category error:"))
100 text.append(self._curr_category.subscription.error)
101 if self._curr_feed and self._curr_feed.error:
102 text.append(_("Feed Error:"))
103 text.append(self._curr_feed.error)
104 if text:
105 t = "\n".join(text)
106 self._tooltips.set_tip(self._widget,t,t)
107 self._tooltips.enable()
108 self._widget.show()
109 else:
110 self._tooltips.disable()
111 self._widget.hide()
112 return
114 def _category_changed(self, signal):
115 if signal.sender is self._curr_category:
116 self.display_category_error(self._curr_category)
118 def _error_status_changed(self, signal):
119 if signal.sender is self._curr_feed:
120 self.display_feed_error(signal.sender)
121 return
123 def hide(self):
124 self._widget.hide()
126 class MenuFeedPropsPresenter(MVP.BasicPresenter):
127 def set_sensitive(self, s):
128 self._view.set_sensitive(s)
129 return
131 class ToolbarView(MVP.WidgetView):
132 """ Widget: gtk.Toolbar"""
133 GCONF_DESKTOP_INTERFACE = "/desktop/gnome/interface"
135 def _initialize(self):
136 client = gconf.client_get_default()
137 if not client.dir_exists(self.GCONF_DESKTOP_INTERFACE):
138 return
139 client.add_dir(self.GCONF_DESKTOP_INTERFACE,
140 gconf.CLIENT_PRELOAD_NONE)
141 client.notify_add(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style",
142 self._toolbar_style_changed)
143 self._widget.set_tooltips(True)
145 def _toolbar_style_changed(self, client, notify_id, entry, *args):
146 value = entry.value.get_string()
147 self._presenter.style_changed(value)
149 def set_style(self, style):
150 self._widget.set_style(style)
152 def get_style(self):
153 client = gconf.client_get_default()
154 current_style = client.get_string(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style")
155 return current_style
157 class ToolbarPresenter(MVP.BasicPresenter):
158 STYLES = {'both':gtk.TOOLBAR_BOTH,
159 'text':gtk.TOOLBAR_TEXT,
160 'icons':gtk.TOOLBAR_ICONS,
161 'both-horiz':gtk.TOOLBAR_BOTH_HORIZ}
163 def _initialize(self):
164 style = self._view.get_style()
165 if style in self.STYLES.keys():
166 self._view.set_style(self.STYLES[style])
167 return
169 def style_changed(self, value):
170 if value in self.STYLES.keys():
171 self._view.set_style(self.STYLES[value])
172 return
174 class ApplicationPresenter(MVP.BasicPresenter):
175 def _initialize(self):
176 self._curr_category = None
177 self._curr_feed = None
178 self._curr_item = None
179 self._init_widgets()
180 self._init_presenters()
181 self._init_signals()
182 self._prefs_dialog = None
183 self._view.present()
185 def _init_widgets(self):
186 widget_tree = self._view.get_widget_tree()
187 self._itemlist_view_notebook = widget_tree.get_widget('mode_view_notebook')
188 self._feedlist_view_notebook = widget_tree.get_widget('left_mode_view_notebook')
190 def _init_presenters(self):
191 widget_tree = self._view.get_widget_tree()
192 toolbar_presenter = ToolbarPresenter(view=ToolbarView(widget_tree.get_widget('toolbar_default')))
194 self._itemlist_presenter = ItemListPresenter(view = ItemListView(widget_tree.get_widget('item_selection_treeview')))
196 view=FeedsView(widget_tree.get_widget('feed_selection_treeview'))
197 self._feed_list_presenter = FeedsPresenter(view=view)
198 view.add_selection_changed_listener(self._itemlist_presenter)
200 # self._category_selector = CategoryPresenter(view=CategoryView(widget_tree.get_widget('category_combo')))
202 self._error_presenter = ErrorPresenter(
203 widget_tree.get_widget('statusbar_error_indicator'))
204 self._offline_presenter = OfflineToggle(
205 widget_tree.get_widget('offline_toggle'))
207 self._item_view = ItemView(widget_tree.get_widget('item_view_container'))
208 self._status_presenter = StatusPresenter(view = widget_tree.get_widget("main_statusbar"))
209 self._menufp_presenter = MenuFeedPropsPresenter( view = widget_tree.get_widget('menu_feed_properties'))
210 self._menufp_presenter.set_sensitive(False)
211 # self._find_presenter = FindPresenter(view=FindView(widget_tree.get_widget("find_vbox")))
212 return
214 def _init_signals(self):
215 # self._feed_list_presenter.signal_connect(Event.FeedSelectionChangedSignal,
216 # self._feed_selection_changed)
217 # self._feed_list_presenter.signal_connect(Event.FeedsEmptySignal,
218 # self._feeds_empty_cb)
219 self._itemlist_presenter.signal_connect(Event.ItemSelectionChangedSignal,
220 self._item_view.item_selection_changed)
221 # self._category_selector.signal_connect(Event.CategorySelectionChangedSignal,
222 # self._category_selection_changed)
223 return
225 def _feed_selection_changed(self, signal):
226 if signal.old and self._curr_feed:
227 self._curr_feed.unload_contents()
228 if signal.new:
229 if self._curr_feed:
230 self._curr_feed.signal_disconnect(Event.FeedPolledSignal,
231 self._feed_polled)
232 self._curr_feed.signal_disconnect(Event.ItemsAddedSignal,
233 self._feed_items_added)
234 self._curr_feed = signal.new
235 self._curr_feed.signal_connect(Event.FeedPolledSignal,
236 self._feed_polled)
237 self._curr_feed.signal_connect(Event.ItemsAddedSignal,
238 self._feed_items_added)
239 self._display_feed(self._curr_feed)
240 return
242 def _feed_polled(self, signal):
243 self._feedinfo_presenter.display(signal.sender)
244 return
246 def _feed_items_added(self, signal):
247 self._itemlist_presenter.display_feed_items(self._curr_feed, False)
249 def _category_selection_changed(self, signal):
250 self._display_category_feeds(signal.current)
251 return
253 def _display_category_feeds(self, category, *args):
254 self._curr_category = category
255 if category:
256 # self._feed_list_presenter.display_category_feeds(category)
257 self._error_presenter.display_category_error(category)
258 else:
259 # self._feed_list_presenter.display_empty_category()
260 self._menufp_presenter.set_sensitive(False)
261 #self._itemlist_presenter.display_empty_feed()
262 self._item_view.display_empty_feed()
263 return
265 def _display_feed(self, feed, select_first = 1):
266 if feed and feed.number_of_items < 1:
267 self._item_view.display_empty_feed()
268 self._error_presenter.display_feed_error(feed)
269 self._feedinfo_presenter.display(feed)
270 #self._itemlist_presenter.display_feed_items(feed, select_first)
271 self._menufp_presenter.set_sensitive(True)
272 return
274 def _feeds_empty_cb(self, signal):
275 #self._itemlist_presenter.display_empty_feed()
276 self._item_view.display_empty_feed()
277 self._feedinfo_presenter.hide()
278 self._error_presenter.hide()
279 self._menufp_presenter.set_sensitive(False)
280 return
282 def copy_itemview_text_selection(self):
283 utils.set_clipboard_text(self._item_view.get_selected_text())
285 def check_allocation(self, widget, event):
286 config = Config.get_instance()
287 def check_size((width, height, widget)):
288 if width == widget.allocation.width and height == widget.allocation.height:
289 config.main_window_size = (width, height)
290 if event.width != widget.allocation.width or event.height != widget.allocation.height:
291 gobject.timeout_add(1000, check_size, (
292 (event.width, event.height, widget)))
294 def check_main_pane_position(self, widget):
295 config = Config.get_instance()
296 def check_position((position, widget)):
297 if position == widget.get_position():
298 config.main_pane_position = position
299 pos = widget.get_position()
300 if pos != config.main_pane_position:
301 gobject.timeout_add(1000, check_position, (pos, widget))
303 def check_sub_pane_position(self, widget):
304 config = Config.get_instance()
305 def check_position((position, widget)):
306 if position == widget.get_position():
307 config.sub_pane_position = position
308 pos = widget.get_position()
309 if pos != config.sub_pane_position:
310 gobject.timeout_add(1000, check_position, (pos, widget))
312 def credits(self):
313 return dialogs.credits()
315 def poll_all(self):
316 if self._warn_if_offline():
317 fclist = FeedCategoryList.get_instance()
318 self._poll_categories(fclist.all_categories)
319 return
321 def poll_current_category(self):
322 if self._warn_if_offline():
323 self._poll_categories([self._curr_category])
324 return
326 def poll_current_feed(self):
327 if self._warn_if_offline():
328 pm = PollManager.get_instance()
329 pm.poll([self._curr_feed])
330 return
332 def mark_feed_as_read(self):
333 self._curr_feed.mark_all_read()
335 def mark_all_as_read(self):
336 mmgr = MainloopManager.get_instance()
337 flist = feeds.get_instance().flatten_list()
338 mmgr.call_pending()
339 for feed in flist:
340 feed.mark_all_read()
341 if feed is self._curr_feed:
342 continue
343 feed.unload_contents()
344 mmgr.call_pending()
346 def remove_selected_feed(self):
347 # self._feed_list_presenter.remove_selected_feed()
348 pass
350 def show_search(self):
351 self._itemlist_presenter.signal_disconnect(Event.ItemSelectionChangedSignal,
352 self._item_view.item_selection_changed)
353 # self._find_presenter.item_list.signal_connect(Event.ItemSelectionChangedSignal,
354 # self._item_view.item_selection_changed)
355 self._item_view.display_empty_search()
356 self._itemlist_view_notebook.set_current_page(1)
357 self._feedlist_view_notebook.set_current_page(1)
358 self._feedinfo_presenter.hide()
360 def hide_search(self):
361 # self._find_presenter.clear()
362 self._itemlist_view_notebook.set_current_page(0)
363 self._feedlist_view_notebook.set_current_page(0)
364 self._feedinfo_presenter.show()
365 # self._find_presenter.item_list.signal_disconnect(Event.ItemSelectionChangedSignal,
366 # self._item_view.item_selection_changed)
367 self._itemlist_presenter.signal_connect(Event.ItemSelectionChangedSignal,
368 self._item_view.item_selection_changed)
370 def _poll_categories(self, fclist):
371 pm = PollManager.get_instance()
372 pm.poll_categories(fclist)
373 return
375 def _warn_if_offline(self):
376 config = Config.get_instance()
377 will_poll = False
378 if config.offline:
379 response = self._view.show_offline_dialog()
380 if response == gtk.RESPONSE_OK:
381 config.offline = not config.offline
382 will_poll = True
383 else:
384 will_poll = True
385 return will_poll
387 def _get_next_category(self, category = None):
388 if not category:
389 category = self._curr_category
390 fclist = FeedCategoryList.get_instance()
391 allcats = fclist.user_categories + list(fclist.pseudo_categories)
392 if category is None:
393 category = allcats[0]
394 else:
395 index = allcats.index(category)
396 if index < len(allcats) - 1:
397 index += 1
398 else:
399 index = 0
400 category = allcats[index]
401 return category
403 def _get_previous_category(self, category = None):
404 if category is None:
405 category = self._curr_category
406 fclist = FeedCategoryList.get_instance()
407 allcats = fclist.user_categories + list(fclist.pseudo_categories)
408 if category is None:
409 category = allcats[-1]
410 else:
411 index = allcats.index(category)
412 if index > 0:
413 index -= 1
414 else:
415 index = len(allcats) - 1
416 category = allcats[index]
417 return category
419 def display_previous_category(self, category = None):
421 Displays the category before the current selected category
423 category = self._get_previous_category(category)
424 # self._category_selector.category_selected(category)
425 self._display_category_feeds(category)
427 def display_next_category(self, category = None):
429 Display the category after the current selected category
431 category = self._get_next_category(category)
432 # self._category_selector.category_selected(category)
433 self._display_category_feeds(category)
435 def display_previous_feed(self, item = None):
437 Displays the feed before the current selected feed
439 self._feed_list_presenter.select_previous_feed()
441 def display_next_feed(self, item=None):
443 Displays the feed after the current selected feed
445 # XXX feed_list_presenter usage
446 self._feed_list_presenter.select_next_feed()
447 return
449 def display_next_unread_feed(self):
451 Displays the next feed with an unread item
453 # self._feed_list_presenter.select_next_unread_feed()
454 pass
456 def display_previous_item(self, item=None):
458 Displays the item before the current selected item. If the item is the
459 first item, scrolls to the previous feed
461 is_prev = self._itemlist_presenter.select_previous_item()
462 if not is_prev:
463 # TODO HACK - implement select_previous_feed(select_last=True) ...
464 # ... to select previous feed's last item
465 # self._feed_list_presenter.select_previous_feed()
466 self._itemlist_presenter.select_last_item()
467 return
469 def display_next_item(self, item=None):
471 Displays the item after the current selected item. If the item is the
472 last item, selectes the next feed. If the current feed is the last
473 feed in the list, it goes back and selects the first feed
475 is_next = self._itemlist_presenter.select_next_item()
476 # if not is_next:
477 # is_next_feed = self._feed_list_presenter.select_next_feed()
478 # if not is_next_feed:
479 # self._feed_list_presenter.select_first_feed()
480 return
482 def scroll_or_display_next_unread_item(self, item=None):
483 has_unread_item = False
484 if not self._item_view.scroll_down():
485 has_unread_item = self._itemlist_presenter.select_next_unread_item()
486 # if not has_unread_item:
487 # self._feed_list_presenter.select_next_unread_feed()
488 return
490 def show_preferences_dialog(self, parent):
491 if not self._prefs_dialog:
492 xf = utils.find_glade_file()
493 xml = gtk.glade.XML(xf, "preferences_dialog", gettext.textdomain())
494 self._prefs_dialog = PreferencesDialog(xml, parent)
495 self._prefs_dialog.show()
497 def show_feed_properties(self, parent):
498 fpd = show_feed_properties_dialog(parent, self._curr_feed)
499 return
501 def quit(self):
502 gtk.main_quit()
503 ItemStore.get_instance().stop()
504 return
506 class ApplicationView(MVP.WidgetView):
508 Widget: straw_main
510 def _initialize(self):
511 self._config = Config.get_instance()
512 self._initialize_dnd()
513 self._initialize_window_updater()
514 self._create_unmodified_accelerator_group()
515 self._attach_unmodified_accelerator_group()
516 self._initialize_window()
517 self._find_toggled = False
519 def _initialize_window(self):
520 widget_tree = gtk.glade.get_widget_tree(self._widget)
521 if self._config.window_maximized:
522 self._widget.maximize()
523 else:
524 # we use resize here since configure-event seems to
525 # overwrite the default size if we use set_default_size.
526 self._widget.resize(*self._config.main_window_size)
527 mmp = widget_tree.get_widget('main_main_pane')
528 msp = widget_tree.get_widget('main_sub_pane')
529 mmp.set_position(self._config.main_pane_position)
530 msp.set_position(self._config.sub_pane_position)
532 def _initialize_dnd(self):
533 self._widget.drag_dest_set(
534 gtk.DEST_DEFAULT_ALL,
535 [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1),
536 ('x-url/http', 0, 2)],
537 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
538 return
540 def _initialize_window_updater(self):
541 feedlist = feeds.get_instance()
542 #feedlist.signal_connect(Event.AllItemsReadSignal,
543 # lambda signal: self._update_title(feedlist))
544 #feedlist.signal_connect(Event.ItemReadSignal,
545 # lambda signal: self._update_title(feedlist))
546 #feedlist.signal_connect(Event.ItemsAddedSignal,
547 # lambda signal: self._update_title(feedlist))
548 #feedlist.signal_connect(Event.FeedsChangedSignal,
549 # lambda signal: self._update_title(feedlist))
550 return
552 def _update_title(self, flist):
553 uritems = urfeeds = 0
554 sfeeds = "feeds"
555 listfeeds = flist.flatten_list()
556 for ur in [f.n_items_unread for f in listfeeds]:
557 if ur:
558 uritems += ur
559 urfeeds += 1
560 else:
561 urfeeds = len(listfeeds)
562 if urfeeds < 2:
563 sfeeds = "feed"
564 item_feed_map = {'uritems': uritems,
565 'urfeeds': urfeeds,
566 'fstring' : sfeeds}
567 title = _('%(uritems)d unread in %(urfeeds)d %(fstring)s') % item_feed_map
568 self._widget.set_title( title + " - %s" % constants.APPNAME)
569 return
571 # We have a separate accelerator group for the unmodified and
572 # shifted accelerators, that is, stuff like space, N, P, etc. This
573 # is so that we can have the find pane work correctly
574 def _create_unmodified_accelerator_group(self):
575 xml = gtk.glade.get_widget_tree(self._widget)
576 agroup = gtk.AccelGroup()
577 accels = (('menu_mark_feed_as_read', 'R', gtk.gdk.SHIFT_MASK),
578 ('menu_mark_all_as_read', 'A', gtk.gdk.SHIFT_MASK),
579 ('menu_next', 'N', gtk.gdk.SHIFT_MASK),
580 ('menu_next_unread', ' ', 0),
581 ('menu_previous', 'P', gtk.gdk.SHIFT_MASK))
582 for widget_name, key, mask in accels:
583 widget = xml.get_widget(widget_name)
584 widget.add_accelerator("activate", agroup, ord(key), mask,
585 gtk.ACCEL_VISIBLE)
586 self._unmodified_accelerator_group = agroup
588 def _attach_unmodified_accelerator_group(self):
589 self._widget.add_accel_group(self._unmodified_accelerator_group)
591 def _detach_unmodified_accelerator_group(self):
592 self._widget.remove_accel_group(self._unmodified_accelerator_group)
594 def _on_straw_main_destroy_event(self, *args):
595 return self._presenter.quit()
597 def _on_straw_main_delete_event(self, *args):
598 return self._presenter.quit()
600 def _on_menu_quit_activate(self, *args):
601 return self._presenter.quit()
603 def _on_straw_main_configure_event(self, widget, event, *args):
604 if widget.window.get_state() is not gtk.gdk.WINDOW_STATE_MAXIMIZED:
605 self._config.window_maximized = False
606 self._presenter.check_allocation(widget, event)
607 else:
608 self._config.window_maximized = True
609 return
611 def _on_main_main_pane_size_allocate(self, widget, *args):
612 self._presenter.check_main_pane_position(widget)
614 def _on_main_sub_pane_size_allocate(self, widget, *args):
615 self._presenter.check_sub_pane_position(widget)
617 def _on_menu_report_problem_activate(self, menuitem, *args):
618 utils.url_show("http://bugzilla.gnome.org/simple-bug-guide.cgi?product=straw")
620 def _on_menu_about_activate(self, menuitem, *args):
621 widget = self._presenter.credits()
622 widget.show()
624 def _on_menu_refresh_all_activate(self, *args):
625 self._presenter.poll_all()
627 def _on_menu_refresh_category_activate(self, *args):
628 self._presenter.poll_current_category()
630 def _on_menu_refresh_selected_activate(self, *args):
631 self._presenter.poll_current_feed()
633 def _on_toolbar_refresh_all_button_clicked(self, *args):
634 self._presenter.poll_all()
636 def _on_menu_add_activate(self, *args):
637 subscribe.display_window(parent=self._widget)
639 def _on_toolbar_subscribe_button_clicked(self, *args):
640 subscribe.display_window(parent=self._widget)
642 def _on_menu_import_subscriptions_activate(self, *args):
643 dialogs.import_subscriptions(self._widget)
645 def _on_menu_export_subscriptions_activate(self, *args):
646 dialogs.export_subscriptions(self._widget)
648 def _on_menu_copy_text_activate(self, *args):
649 self._presenter.copy_itemview_text_selection()
651 def _on_menu_mark_feed_as_read_activate(self, *args):
652 self._presenter.mark_feed_as_read()
654 def _on_menu_mark_all_as_read_activate(self, *args):
655 self._presenter.mark_all_as_read()
657 def _on_find_activate(self, widget, *args):
658 xml = gtk.glade.get_widget_tree(self._widget)
659 menu_find = xml.get_widget('menu_find')
660 accel_label = menu_find.get_child()
662 if not self._find_toggled:
663 self._presenter.show_search()
664 self._detach_unmodified_accelerator_group()
665 self._find_toggled = True
667 # save the "Find..." stock text for later recovery
668 self._old_label_text = accel_label.get_text()
669 accel_label.set_text(_('Return to feed list...'))
671 else:
672 self._presenter.hide_search()
673 self._attach_unmodified_accelerator_group()
674 self._find_toggled = False
675 accel_label.set_text(self._old_label_text)
677 def _on_menu_next_activate(self, *args):
678 self._presenter.display_next_item()
680 def _on_toolbar_scroll_or_next_button_clicked(self, *args):
681 self._presenter.scroll_or_display_next_unread_item()
683 def _on_menu_scroll_next_activate(self, *args):
684 self._presenter.scroll_or_display_next_unread_item()
686 def _on_menu_previous_activate(self, *args):
687 self._presenter.display_previous_item()
689 def _on_menu_next_feed_unread_activate(self, *args):
690 self._presenter.display_next_unread_feed()
692 def _on_menu_next_feed_activate(self, *args):
693 self._presenter.display_next_feed()
695 def _on_menu_previous_feed_activate(self, *args):
696 self._presenter.display_previous_feed()
698 def _on_menu_remove_selected_feed_activate(self, *args):
699 self._presenter.remove_selected_feed()
701 def _on_menu_next_category_activate(self, *args):
702 self._presenter.display_next_category()
704 def _on_menu_previous_category_activate(self, *args):
705 self._presenter.display_previous_category()
707 def _on_menu_next_category_activate(self, *args):
708 self._presenter.display_next_category()
710 def _on_menu_previous_category_activate(self, *args):
711 self._presenter.display_previous_category()
713 def _on_menu_preferences_activate(self, *args):
714 self._presenter.show_preferences_dialog(self._widget)
716 def _on_menu_feed_properties_activate(self, *args):
717 self._presenter.show_feed_properties(self._widget)
719 def _on_straw_main_drag_data_received(self, w, context,
720 x, y, data, info, time):
721 if data and data.format == 8:
722 url = data.data.split("\n")[0]
723 subscribe.display_window("%s" % url, self._widget)
724 context.finish(True, False, time)
725 else:
726 context.finish(False, False, time)
728 def show_offline_dialog(self):
729 return dialogs.report_offline_status(self._widget)
731 def get_widget_tree(self):
732 return gtk.glade.get_widget_tree(self._widget)
734 def present(self):
735 self._widget.present()
737 def should_present(self):
738 if self._widget.window.get_state() is not gtk.gdk.WINDOW_STATE_WITHDRAWN:
739 self._widget.hide()
740 else:
741 self._widget.present()
743 import os
744 import gettext
745 import getopt
746 import sys
747 import strawdbus
749 class Application:
750 def __init__(self):
751 gnome.program_init(constants.APPNAME.lower(), constants.VERSION)
752 # initialize threading and environment
753 gobject.threads_init()
754 config = Config.get_instance()
755 config.reload_css = os.getenv('STRAW_RELOAD_CSS') is not None
757 gtk.window_set_auto_startup_notification(True)
758 self._initialize_gettext()
760 config = Config.get_instance()
762 feedlist = feeds.get_instance()
763 feed_categories = FeedCategoryList.get_instance()
765 # initialise GUI
766 #xmlfile = utils.find_glade_file()
767 #xml = gtk.glade.XML(xmlfile, "straw_main", gettext.textdomain())
768 #window = xml.get_widget('straw_main')
769 #self._main_presenter = ApplicationPresenter(view =
770 # ApplicationView(window))
772 if config.first_time:
773 libdir = utils.find_data_dir()
774 filepath = os.path.join(libdir, "default_subscriptions.opml")
775 OPMLImport.import_opml(filepath)
776 else:
777 feedlist.load_data()
778 feed_categories.load_data()
780 try:
781 itemstore = ItemStore.get_instance()
782 except ItemStore.ConvertException, ex:
783 dialogs.report_error(_("There was a problem while converting the database."),
784 _("Straw will not behave as expected. You should probably quit now. " +
785 "The exception has been saved to the file '%s'. Please see the Straw README for further instructions."
786 ) % ex.reason)
787 sys.exit()
789 ImageCache.initialize()
790 itemstore.start()
793 xmlfile = utils.find_glade_file()
794 xml = gtk.glade.XML(xmlfile, "straw_main", gettext.textdomain())
795 window = xml.get_widget('straw_main')
796 self._main_presenter = ApplicationPresenter(view =
797 ApplicationView(window))
798 self._main_presenter.view.present()
800 PollManager.get_instance().start_polling_loop()
801 # set the default icon for the windows
802 iconfile = os.path.join(utils.find_image_dir(),"straw.png")
803 gtk.window_set_default_icon(gtk.gdk.pixbuf_new_from_file(iconfile))
805 strawdbus.start_services()
807 def mainloop(self):
808 gtk.main()
809 return
811 def _initialize_gettext(self):
812 import locale
813 lname = constants.APPNAME.lower()
814 try:
815 localedir = utils.find_locale_dir()
816 gettext.bindtextdomain(lname, localedir)
817 gettext.textdomain(lname)
818 gettext.install(lname, localedir, unicode=1)
819 gtk.glade.bindtextdomain(lname, localedir)
820 except IOError:
821 def broken_gettext_workaround(s):
822 return s
823 __builtins__.__dict__['_'] = broken_gettext_workaround
824 locale.setlocale(locale.LC_ALL, '')
825 return
827 def load_tray(self):
828 from Tray import Tray
829 tray = Tray()
830 tray.connect('button_press_event', self._tray_clicked)
832 def _tray_clicked(self, signal, event):
833 if event.button == 1:
834 self._main_presenter.view.present()
835 self._main_presenter.scroll_or_display_next_unread_item()
836 else:
837 self._main_presenter.view.should_present()
838 return