redesigned subscribe dialog
[straw.git] / src / lib / Application.py
blob98e6abf12ade5dd050943ac68b345d0c5c7f2afb
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 FeedList
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 FeedInfoView(MVP.WidgetView):
127 def _initialize(self):
128 widget_tree = gtk.glade.get_widget_tree(self._widget)
129 self._title = widget_tree.get_widget('feed_item_label')
130 self._refresh = widget_tree.get_widget('feed_refresh_label')
132 def set_title(self, title):
133 self._title.set_label(title)
134 return
136 def set_refresh(self, refresh):
137 self._refresh.set_label(refresh)
138 self._refresh.show()
139 return
141 def hide(self):
142 self._widget.hide()
143 return
145 def show(self):
146 self._widget.show()
147 return
149 class FeedInfoPresenter(MVP.BasicPresenter):
150 def display(self, feed):
151 next = feed.next_refresh
152 if feed:
153 text = _("Next Refresh: %s") % utils.format_date(time.gmtime(next))
154 self._view.set_refresh(text)
155 self._view.set_title(_("%s") % saxutils.escape(feed.title))
156 self._view.show()
157 else:
158 self._view.hide()
159 return
161 def hide(self):
162 self._view.set_title("")
163 self._view.set_refresh("")
165 def show(self): self._view.show()
167 class MenuFeedPropsPresenter(MVP.BasicPresenter):
168 def set_sensitive(self, s):
169 self._view.set_sensitive(s)
170 return
172 class ToolbarView(MVP.WidgetView):
173 """ Widget: gtk.Toolbar"""
174 GCONF_DESKTOP_INTERFACE = "/desktop/gnome/interface"
176 def _initialize(self):
177 client = gconf.client_get_default()
178 if not client.dir_exists(self.GCONF_DESKTOP_INTERFACE):
179 return
180 client.add_dir(self.GCONF_DESKTOP_INTERFACE,
181 gconf.CLIENT_PRELOAD_NONE)
182 client.notify_add(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style",
183 self._toolbar_style_changed)
184 self._widget.set_tooltips(True)
186 def _toolbar_style_changed(self, client, notify_id, entry, *args):
187 value = entry.value.get_string()
188 self._presenter.style_changed(value)
190 def set_style(self, style):
191 self._widget.set_style(style)
193 def get_style(self):
194 client = gconf.client_get_default()
195 current_style = client.get_string(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style")
196 return current_style
198 class ToolbarPresenter(MVP.BasicPresenter):
199 STYLES = {'both':gtk.TOOLBAR_BOTH,
200 'text':gtk.TOOLBAR_TEXT,
201 'icons':gtk.TOOLBAR_ICONS,
202 'both-horiz':gtk.TOOLBAR_BOTH_HORIZ}
204 def _initialize(self):
205 style = self._view.get_style()
206 if style in self.STYLES.keys():
207 self._view.set_style(self.STYLES[style])
208 return
210 def style_changed(self, value):
211 if value in self.STYLES.keys():
212 self._view.set_style(self.STYLES[value])
213 return
215 class ApplicationPresenter(MVP.BasicPresenter):
216 def _initialize(self):
217 self._curr_category = None
218 self._curr_feed = None
219 self._curr_item = None
220 self._init_widgets()
221 self._init_presenters()
222 self._init_signals()
223 self._prefs_dialog = None
224 self._view.present()
226 def _init_widgets(self):
227 widget_tree = self._view.get_widget_tree()
228 self._itemlist_view_notebook = widget_tree.get_widget('mode_view_notebook')
229 self._feedlist_view_notebook = widget_tree.get_widget('left_mode_view_notebook')
231 def _init_presenters(self):
232 widget_tree = self._view.get_widget_tree()
233 toolbar_presenter = ToolbarPresenter(view=ToolbarView(widget_tree.get_widget('toolbar_default')))
234 self._feed_list_presenter = FeedsPresenter(view=FeedsView(widget_tree.get_widget('feed_selection_treeview')))
235 self._category_selector = CategoryPresenter(view=CategoryView(widget_tree.get_widget('category_combo')))
236 self._error_presenter = ErrorPresenter(
237 widget_tree.get_widget('statusbar_error_indicator'))
238 self._offline_presenter = OfflineToggle(
239 widget_tree.get_widget('offline_toggle'))
240 self._itemlist_presenter = ItemListPresenter(view = ItemListView(widget_tree.get_widget('item_selection_treeview')))
241 self._item_view = ItemView(widget_tree.get_widget('item_view_container'))
242 self._status_presenter = StatusPresenter(view = widget_tree.get_widget("main_statusbar"))
243 self._menufp_presenter = MenuFeedPropsPresenter( view = widget_tree.get_widget('menu_feed_properties'))
244 self._menufp_presenter.set_sensitive(False)
245 self._feedinfo_presenter = FeedInfoPresenter(view = FeedInfoView(widget_tree.get_widget('article_refresh_hbox')))
246 self._find_presenter = FindPresenter(view=FindView(widget_tree.get_widget("find_vbox")))
247 return
249 def _init_signals(self):
250 self._feed_list_presenter.signal_connect(Event.FeedSelectionChangedSignal,
251 self._feed_selection_changed)
252 self._feed_list_presenter.signal_connect(Event.FeedsEmptySignal,
253 self._feeds_empty_cb)
254 self._itemlist_presenter.signal_connect(Event.ItemSelectionChangedSignal,
255 self._item_view.item_selection_changed)
256 self._category_selector.signal_connect(Event.CategorySelectionChangedSignal,
257 self._category_selection_changed)
258 return
260 def _feed_selection_changed(self, signal):
261 if signal.old and self._curr_feed:
262 self._curr_feed.unload_contents()
263 if signal.new:
264 if self._curr_feed:
265 self._curr_feed.signal_disconnect(Event.FeedPolledSignal,
266 self._feed_polled)
267 self._curr_feed.signal_disconnect(Event.ItemsAddedSignal,
268 self._feed_items_added)
269 self._curr_feed = signal.new
270 self._curr_feed.signal_connect(Event.FeedPolledSignal,
271 self._feed_polled)
272 self._curr_feed.signal_connect(Event.ItemsAddedSignal,
273 self._feed_items_added)
274 self._display_feed(self._curr_feed)
275 return
277 def _feed_polled(self, signal):
278 self._feedinfo_presenter.display(signal.sender)
279 return
281 def _feed_items_added(self, signal):
282 self._itemlist_presenter.display_feed_items(self._curr_feed, False)
284 def _category_selection_changed(self, signal):
285 self._display_category_feeds(signal.current)
286 return
288 def _display_category_feeds(self, category, *args):
289 self._curr_category = category
290 if category:
291 self._feed_list_presenter.display_category_feeds(category)
292 self._error_presenter.display_category_error(category)
293 else:
294 self._feed_list_presenter.display_empty_category()
295 self._menufp_presenter.set_sensitive(False)
296 self._itemlist_presenter.display_empty_feed()
297 self._item_view.display_empty_feed()
298 return
300 def _display_feed(self, feed, select_first = 1):
301 if feed and feed.number_of_items < 1:
302 self._item_view.display_empty_feed()
303 self._error_presenter.display_feed_error(feed)
304 self._feedinfo_presenter.display(feed)
305 self._itemlist_presenter.display_feed_items(feed, select_first)
306 self._menufp_presenter.set_sensitive(True)
307 return
309 def _feeds_empty_cb(self, signal):
310 self._itemlist_presenter.display_empty_feed()
311 self._item_view.display_empty_feed()
312 self._feedinfo_presenter.hide()
313 self._error_presenter.hide()
314 self._menufp_presenter.set_sensitive(False)
315 return
317 def copy_itemview_text_selection(self):
318 utils.set_clipboard_text(self._item_view.get_selected_text())
320 def check_allocation(self, widget, event):
321 config = Config.get_instance()
322 def check_size((width, height, widget)):
323 if width == widget.allocation.width and height == widget.allocation.height:
324 config.main_window_size = (width, height)
325 if event.width != widget.allocation.width or event.height != widget.allocation.height:
326 gobject.timeout_add(1000, check_size, (
327 (event.width, event.height, widget)))
329 def check_main_pane_position(self, widget):
330 config = Config.get_instance()
331 def check_position((position, widget)):
332 if position == widget.get_position():
333 config.main_pane_position = position
334 pos = widget.get_position()
335 if pos != config.main_pane_position:
336 gobject.timeout_add(1000, check_position, (pos, widget))
338 def check_sub_pane_position(self, widget):
339 config = Config.get_instance()
340 def check_position((position, widget)):
341 if position == widget.get_position():
342 config.sub_pane_position = position
343 pos = widget.get_position()
344 if pos != config.sub_pane_position:
345 gobject.timeout_add(1000, check_position, (pos, widget))
347 def credits(self):
348 return dialogs.credits()
350 def poll_all(self):
351 if self._warn_if_offline():
352 fclist = FeedCategoryList.get_instance()
353 self._poll_categories(fclist.all_categories)
354 return
356 def poll_current_category(self):
357 if self._warn_if_offline():
358 self._poll_categories([self._curr_category])
359 return
361 def poll_current_feed(self):
362 if self._warn_if_offline():
363 pm = PollManager.get_instance()
364 pm.poll([self._curr_feed])
365 return
367 def mark_feed_as_read(self):
368 self._curr_feed.mark_all_read()
370 def mark_all_as_read(self):
371 mmgr = MainloopManager.get_instance()
372 flist = FeedList.get_instance().flatten_list()
373 mmgr.call_pending()
374 for feed in flist:
375 feed.mark_all_read()
376 if feed is self._curr_feed:
377 continue
378 feed.unload_contents()
379 mmgr.call_pending()
381 def remove_selected_feed(self):
382 self._feed_list_presenter.remove_selected_feed()
384 def show_search(self):
385 self._itemlist_presenter.signal_disconnect(Event.ItemSelectionChangedSignal,
386 self._item_view.item_selection_changed)
387 self._find_presenter.item_list.signal_connect(Event.ItemSelectionChangedSignal,
388 self._item_view.item_selection_changed)
389 self._item_view.display_empty_search()
390 self._itemlist_view_notebook.set_current_page(1)
391 self._feedlist_view_notebook.set_current_page(1)
392 self._feedinfo_presenter.hide()
394 def hide_search(self):
395 self._find_presenter.clear()
396 self._itemlist_view_notebook.set_current_page(0)
397 self._feedlist_view_notebook.set_current_page(0)
398 self._feedinfo_presenter.show()
399 self._find_presenter.item_list.signal_disconnect(Event.ItemSelectionChangedSignal,
400 self._item_view.item_selection_changed)
401 self._itemlist_presenter.signal_connect(Event.ItemSelectionChangedSignal,
402 self._item_view.item_selection_changed)
404 def _poll_categories(self, fclist):
405 pm = PollManager.get_instance()
406 pm.poll_categories(fclist)
407 return
409 def _warn_if_offline(self):
410 config = Config.get_instance()
411 will_poll = False
412 if config.offline:
413 response = self._view.show_offline_dialog()
414 if response == gtk.RESPONSE_OK:
415 config.offline = not config.offline
416 will_poll = True
417 else:
418 will_poll = True
419 return will_poll
421 def _get_next_category(self, category = None):
422 if not category:
423 category = self._curr_category
424 fclist = FeedCategoryList.get_instance()
425 allcats = fclist.user_categories + list(fclist.pseudo_categories)
426 if category is None:
427 category = allcats[0]
428 else:
429 index = allcats.index(category)
430 if index < len(allcats) - 1:
431 index += 1
432 else:
433 index = 0
434 category = allcats[index]
435 return category
437 def _get_previous_category(self, category = None):
438 if category is None:
439 category = self._curr_category
440 fclist = FeedCategoryList.get_instance()
441 allcats = fclist.user_categories + list(fclist.pseudo_categories)
442 if category is None:
443 category = allcats[-1]
444 else:
445 index = allcats.index(category)
446 if index > 0:
447 index -= 1
448 else:
449 index = len(allcats) - 1
450 category = allcats[index]
451 return category
453 def display_previous_category(self, category = None):
455 Displays the category before the current selected category
457 category = self._get_previous_category(category)
458 self._category_selector.category_selected(category)
459 self._display_category_feeds(category)
461 def display_next_category(self, category = None):
463 Display the category after the current selected category
465 category = self._get_next_category(category)
466 self._category_selector.category_selected(category)
467 self._display_category_feeds(category)
469 def display_previous_feed(self, item = None):
471 Displays the feed before the current selected feed
473 self._feed_list_presenter.select_previous_feed()
475 def display_next_feed(self, item=None):
477 Displays the feed after the current selected feed
479 is_next = self._feed_list_presenter.select_next_feed()
480 if not is_next:
481 self._feed_list_presenter.select_first_feed()
482 return
484 def display_next_unread_feed(self):
486 Displays the next feed with an unread item
488 self._feed_list_presenter.select_next_unread_feed()
490 def display_previous_item(self, item=None):
492 Displays the item before the current selected item. If the item is the
493 first item, scrolls to the previous feed
495 is_prev = self._itemlist_presenter.select_previous_item()
496 if not is_prev:
497 # TODO HACK - implement select_previous_feed(select_last=True) ...
498 # ... to select previous feed's last item
499 self._feed_list_presenter.select_previous_feed()
500 self._itemlist_presenter.select_last_item()
501 return
503 def display_next_item(self, item=None):
505 Displays the item after the current selected item. If the item is the
506 last item, selectes the next feed. If the current feed is the last
507 feed in the list, it goes back and selects the first feed
509 is_next = self._itemlist_presenter.select_next_item()
510 if not is_next:
511 is_next_feed = self._feed_list_presenter.select_next_feed()
512 if not is_next_feed:
513 self._feed_list_presenter.select_first_feed()
514 return
516 def scroll_or_display_next_unread_item(self, item=None):
517 has_unread_item = False
518 if not self._item_view.scroll_down():
519 has_unread_item = self._itemlist_presenter.select_next_unread_item()
520 if not has_unread_item:
521 self._feed_list_presenter.select_next_unread_feed()
522 return
524 def show_preferences_dialog(self, parent):
525 if not self._prefs_dialog:
526 xf = utils.find_glade_file()
527 xml = gtk.glade.XML(xf, "preferences_dialog", gettext.textdomain())
528 self._prefs_dialog = PreferencesDialog(xml, parent)
529 self._prefs_dialog.show()
531 def show_feed_properties(self, parent):
532 fpd = show_feed_properties_dialog(parent, self._curr_feed)
533 return
535 def quit(self):
536 gtk.main_quit()
537 ItemStore.get_instance().stop()
538 return
540 class ApplicationView(MVP.WidgetView):
542 Widget: straw_main
544 def _initialize(self):
545 self._config = Config.get_instance()
546 self._initialize_dnd()
547 self._initialize_window_updater()
548 self._create_unmodified_accelerator_group()
549 self._attach_unmodified_accelerator_group()
550 self._initialize_window()
551 self._find_toggled = False
553 def _initialize_window(self):
554 widget_tree = gtk.glade.get_widget_tree(self._widget)
555 if self._config.window_maximized:
556 self._widget.maximize()
557 else:
558 # we use resize here since configure-event seems to
559 # overwrite the default size if we use set_default_size.
560 self._widget.resize(*self._config.main_window_size)
561 mmp = widget_tree.get_widget('main_main_pane')
562 msp = widget_tree.get_widget('main_sub_pane')
563 mmp.set_position(self._config.main_pane_position)
564 msp.set_position(self._config.sub_pane_position)
566 def _initialize_dnd(self):
567 self._widget.drag_dest_set(
568 gtk.DEST_DEFAULT_ALL,
569 [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1),
570 ('x-url/http', 0, 2)],
571 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
572 return
574 def _initialize_window_updater(self):
575 feedlist = FeedList.get_instance()
576 feedlist.signal_connect(Event.AllItemsReadSignal,
577 lambda signal: self._update_title(feedlist))
578 feedlist.signal_connect(Event.ItemReadSignal,
579 lambda signal: self._update_title(feedlist))
580 feedlist.signal_connect(Event.ItemsAddedSignal,
581 lambda signal: self._update_title(feedlist))
582 feedlist.signal_connect(Event.FeedsChangedSignal,
583 lambda signal: self._update_title(feedlist))
584 return
586 def _update_title(self, flist):
587 uritems = urfeeds = 0
588 sfeeds = "feeds"
589 listfeeds = flist.flatten_list()
590 for ur in [f.n_items_unread for f in listfeeds]:
591 if ur:
592 uritems += ur
593 urfeeds += 1
594 else:
595 urfeeds = len(listfeeds)
596 if urfeeds < 2:
597 sfeeds = "feed"
598 item_feed_map = {'uritems': uritems,
599 'urfeeds': urfeeds,
600 'fstring' : sfeeds}
601 title = _('%(uritems)d unread in %(urfeeds)d %(fstring)s') % item_feed_map
602 self._widget.set_title( title + " - %s" % constants.APPNAME)
603 return
605 # We have a separate accelerator group for the unmodified and
606 # shifted accelerators, that is, stuff like space, N, P, etc. This
607 # is so that we can have the find pane work correctly
608 def _create_unmodified_accelerator_group(self):
609 xml = gtk.glade.get_widget_tree(self._widget)
610 agroup = gtk.AccelGroup()
611 accels = (('menu_mark_feed_as_read', 'R', gtk.gdk.SHIFT_MASK),
612 ('menu_mark_all_as_read', 'A', gtk.gdk.SHIFT_MASK),
613 ('menu_next', 'N', gtk.gdk.SHIFT_MASK),
614 ('menu_next_unread', ' ', 0),
615 ('menu_previous', 'P', gtk.gdk.SHIFT_MASK))
616 for widget_name, key, mask in accels:
617 widget = xml.get_widget(widget_name)
618 widget.add_accelerator("activate", agroup, ord(key), mask,
619 gtk.ACCEL_VISIBLE)
620 self._unmodified_accelerator_group = agroup
622 def _attach_unmodified_accelerator_group(self):
623 self._widget.add_accel_group(self._unmodified_accelerator_group)
625 def _detach_unmodified_accelerator_group(self):
626 self._widget.remove_accel_group(self._unmodified_accelerator_group)
628 def _on_straw_main_destroy_event(self, *args):
629 return self._presenter.quit()
631 def _on_straw_main_delete_event(self, *args):
632 return self._presenter.quit()
634 def _on_menu_quit_activate(self, *args):
635 return self._presenter.quit()
637 def _on_straw_main_configure_event(self, widget, event, *args):
638 if widget.window.get_state() is not gtk.gdk.WINDOW_STATE_MAXIMIZED:
639 self._config.window_maximized = False
640 self._presenter.check_allocation(widget, event)
641 else:
642 self._config.window_maximized = True
643 return
645 def _on_main_main_pane_size_allocate(self, widget, *args):
646 self._presenter.check_main_pane_position(widget)
648 def _on_main_sub_pane_size_allocate(self, widget, *args):
649 self._presenter.check_sub_pane_position(widget)
651 def _on_menu_report_problem_activate(self, menuitem, *args):
652 utils.url_show("http://bugzilla.gnome.org/simple-bug-guide.cgi?product=straw")
654 def _on_menu_about_activate(self, menuitem, *args):
655 widget = self._presenter.credits()
656 widget.show()
658 def _on_menu_refresh_all_activate(self, *args):
659 self._presenter.poll_all()
661 def _on_menu_refresh_category_activate(self, *args):
662 self._presenter.poll_current_category()
664 def _on_menu_refresh_selected_activate(self, *args):
665 self._presenter.poll_current_feed()
667 def _on_toolbar_refresh_all_button_clicked(self, *args):
668 self._presenter.poll_all()
670 def _on_menu_add_activate(self, *args):
671 subscribe.display_window(parent=self._widget)
673 def _on_toolbar_subscribe_button_clicked(self, *args):
674 subscribe.display_window(parent=self._widget)
676 def _on_menu_import_subscriptions_activate(self, *args):
677 dialogs.import_subscriptions(self._widget)
679 def _on_menu_export_subscriptions_activate(self, *args):
680 dialogs.export_subscriptions(self._widget)
682 def _on_menu_copy_text_activate(self, *args):
683 self._presenter.copy_itemview_text_selection()
685 def _on_menu_mark_feed_as_read_activate(self, *args):
686 self._presenter.mark_feed_as_read()
688 def _on_menu_mark_all_as_read_activate(self, *args):
689 self._presenter.mark_all_as_read()
691 def _on_find_activate(self, widget, *args):
692 xml = gtk.glade.get_widget_tree(self._widget)
693 menu_find = xml.get_widget('menu_find')
694 accel_label = menu_find.get_child()
696 if not self._find_toggled:
697 self._presenter.show_search()
698 self._detach_unmodified_accelerator_group()
699 self._find_toggled = True
701 # save the "Find..." stock text for later recovery
702 self._old_label_text = accel_label.get_text()
703 accel_label.set_text(_('Return to feed list...'))
705 else:
706 self._presenter.hide_search()
707 self._attach_unmodified_accelerator_group()
708 self._find_toggled = False
709 accel_label.set_text(self._old_label_text)
711 def _on_menu_next_activate(self, *args):
712 self._presenter.display_next_item()
714 def _on_toolbar_scroll_or_next_button_clicked(self, *args):
715 self._presenter.scroll_or_display_next_unread_item()
717 def _on_menu_scroll_next_activate(self, *args):
718 self._presenter.scroll_or_display_next_unread_item()
720 def _on_menu_previous_activate(self, *args):
721 self._presenter.display_previous_item()
723 def _on_menu_next_feed_unread_activate(self, *args):
724 self._presenter.display_next_unread_feed()
726 def _on_menu_next_feed_activate(self, *args):
727 self._presenter.display_next_feed()
729 def _on_menu_previous_feed_activate(self, *args):
730 self._presenter.display_previous_feed()
732 def _on_menu_remove_selected_feed_activate(self, *args):
733 self._presenter.remove_selected_feed()
735 def _on_menu_next_category_activate(self, *args):
736 self._presenter.display_next_category()
738 def _on_menu_previous_category_activate(self, *args):
739 self._presenter.display_previous_category()
741 def _on_menu_next_category_activate(self, *args):
742 self._presenter.display_next_category()
744 def _on_menu_previous_category_activate(self, *args):
745 self._presenter.display_previous_category()
747 def _on_menu_preferences_activate(self, *args):
748 self._presenter.show_preferences_dialog(self._widget)
750 def _on_menu_feed_properties_activate(self, *args):
751 self._presenter.show_feed_properties(self._widget)
753 def _on_straw_main_drag_data_received(self, w, context,
754 x, y, data, info, time):
755 if data and data.format == 8:
756 url = data.data.split("\n")[0]
757 subscribe.display_window("%s" % url, self._widget)
758 context.finish(True, False, time)
759 else:
760 context.finish(False, False, time)
762 def show_offline_dialog(self):
763 return dialogs.report_offline_status(self._widget)
765 def get_widget_tree(self):
766 return gtk.glade.get_widget_tree(self._widget)
768 def present(self):
769 self._widget.present()
771 def should_present(self):
772 if self._widget.window.get_state() is not gtk.gdk.WINDOW_STATE_WITHDRAWN:
773 self._widget.hide()
774 else:
775 self._widget.present()
777 import os
778 import gettext
779 import getopt
780 import sys
781 import strawdbus
783 class Application:
784 def __init__(self):
785 gnome.program_init(constants.APPNAME.lower(), constants.VERSION)
786 # initialize threading and environment
787 gobject.threads_init()
788 config = Config.get_instance()
789 config.use_threads = True
790 config.reload_css = os.getenv('STRAW_RELOAD_CSS') is not None
791 config.no_etags = os.getenv('STRAW_NO_ETAGS') is not None
793 gtk.window_set_auto_startup_notification(True)
794 self._initialize_gettext()
796 config = Config.get_instance()
798 feedlist = FeedList.get_instance()
799 feed_categories = FeedCategoryList.get_instance()
801 # initialise GUI
802 xmlfile = utils.find_glade_file()
803 xml = gtk.glade.XML(xmlfile, "straw_main", gettext.textdomain())
804 window = xml.get_widget('straw_main')
805 self._main_presenter = ApplicationPresenter(view =
806 ApplicationView(window))
808 if config.first_time:
809 libdir = utils.find_data_dir()
810 filepath = os.path.join(libdir, "default_subscriptions.opml")
811 OPMLImport.import_opml(filepath)
812 else:
813 feedlist.load_data()
814 feed_categories.load_data()
816 try:
817 itemstore = ItemStore.get_instance(config.straw_dir)
818 except ItemStore.ConvertException, ex:
819 dialogs.report_error(_("There was a problem while converting the database."),
820 _("Straw will not behave as expected. You should probably quit now. " +
821 "The exception has been saved to the file '%s'. Please see the Straw README for further instructions."
822 ) % ex.reason)
823 sys.exit()
825 ImageCache.initialize()
826 itemstore.start()
828 self._main_presenter.view.present()
829 PollManager.get_instance().start_polling_loop()
831 # set the default icon for the windows
832 iconfile = os.path.join(utils.find_image_dir(),"straw.png")
833 gtk.window_set_default_icon(gtk.gdk.pixbuf_new_from_file(iconfile))
835 strawdbus.start_services()
837 def mainloop(self):
838 gtk.main()
839 return
841 def _initialize_gettext(self):
842 import locale
843 lname = constants.APPNAME.lower()
844 try:
845 localedir = utils.find_locale_dir()
846 gettext.bindtextdomain(lname, localedir)
847 gettext.textdomain(lname)
848 gettext.install(lname, localedir, unicode=1)
849 gtk.glade.bindtextdomain(lname, localedir)
850 except IOError:
851 def broken_gettext_workaround(s):
852 return s
853 __builtins__.__dict__['_'] = broken_gettext_workaround
854 locale.setlocale(locale.LC_ALL, '')
855 return
857 def load_tray(self):
858 from Tray import Tray
859 tray = Tray()
860 tray.connect('button_press_event', self._tray_clicked)
862 def _tray_clicked(self, signal, event):
863 if event.button == 1:
864 self._main_presenter.view.present()
865 self._main_presenter.scroll_or_display_next_unread_item()
866 else:
867 self._main_presenter.view.should_present()
868 return