Take functionality related to items out of FeedManager to ItemManager.
[straw.git] / straw / Application.py
blobdaa3203963547cbbb1c5a3bb5883c540f14ec6e1
1 """ Application.py
3 This is the main module that binds everything together.
5 """
7 __copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc."
8 __license__ = """ GNU General Public License
10 This program is free software; you can redistribute it and/or modify it under the
11 terms of the GNU General Public License as published by the Free Software
12 Foundation; either version 2 of the License, or (at your option) any later
13 version.
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License along with
20 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
21 Place - Suite 330, Boston, MA 02111-1307, USA. """
23 from FeedListView import FeedsView, FeedsPresenter
24 from ItemList import ItemListPresenter, ItemListView
25 from ItemView import ItemView
26 from MainloopManager import MainloopManager
27 from OfflineToggle import OfflineToggle
28 from xml.sax import saxutils
29 import Config
30 import FeedManager
31 import ImageCache
32 import ItemManager
33 import JobManager
34 import MVP
35 import error
36 import gnome, gconf
37 import gobject, gtk, gtk.glade
38 import helpers
39 import os, gettext, getopt, sys
40 import pygtk
41 import straw
42 import time
43 pygtk.require('2.0')
45 #from Find import FindPresenter, FindView
48 class StatusPresenter(MVP.BasicPresenter):
49 def _initialize(self):
50 self._mmgr = straw.get_status_manager()
51 self._mmgr.connect('changed', self._display)
53 def _display(self, *args):
54 cid = self._view.get_context_id("straw_main")
55 self._view.pop(cid)
56 self._view.push(cid, self._mmgr.read_message())
57 return
59 class ErrorPresenter:
60 def __init__(self, widget):
61 self._widget = widget
62 self._tooltips = gtk.Tooltips()
63 self._text = ''
64 #self._curr_feed = None
65 #self._curr_category = None
66 #fclist = FeedCategoryList.get_instance()
67 #fclist.connect('category-changed', self._category_changed)
69 def feedlist_selection_changed(self, selection, column):
70 errortexts = None
71 return
72 (model, pathlist) = selection.get_selected_rows()
73 iters = [model.get_iter(path) for path in pathlist]
74 if not iters: return
75 errorfeeds = []
76 nodes = [model.get_value(treeiter, column) for treeiter in iters]
77 try:
78 errorfeeds = [node.feed for node in nodes if node.feed and node.feed.error]
79 errortexts = [feed.error for feed in errorfeeds]
80 except TypeError, te:
81 print te
82 ### XXX display OPML Category error too
84 text = list()
85 if errorfeeds:
86 text.append(_("Error:"))
87 text += errortexts
89 if text:
90 t = "\n".join(text)
91 self._tooltips.set_tip(self._widget,t,t)
92 self._tooltips.enable()
93 self._widget.show()
94 else:
95 self._tooltips.disable()
96 self._widget.hide()
97 return
99 def hide(self):
100 self._widget.hide()
102 class MenuFeedPropsPresenter(MVP.BasicPresenter):
103 # FIXME
104 def set_sensitive(self, s):
105 self._view.set_sensitive(s)
106 return
108 class ToolbarView(MVP.WidgetView):
109 """ Widget: gtk.Toolbar"""
110 GCONF_DESKTOP_INTERFACE = "/desktop/gnome/interface"
112 def _initialize(self):
113 client = gconf.client_get_default()
114 if not client.dir_exists(self.GCONF_DESKTOP_INTERFACE):
115 return
116 client.add_dir(self.GCONF_DESKTOP_INTERFACE,
117 gconf.CLIENT_PRELOAD_NONE)
118 client.notify_add(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style",
119 self._toolbar_style_changed)
120 self._widget.set_tooltips(True)
122 def _toolbar_style_changed(self, client, notify_id, entry, *args):
123 value = entry.value.get_string()
124 self._presenter.style_changed(value)
126 def set_style(self, style):
127 self._widget.set_style(style)
129 def get_style(self):
130 client = gconf.client_get_default()
131 current_style = client.get_string(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style")
132 return current_style
134 class ToolbarPresenter(MVP.BasicPresenter):
135 STYLES = {'both':gtk.TOOLBAR_BOTH,
136 'text':gtk.TOOLBAR_TEXT,
137 'icons':gtk.TOOLBAR_ICONS,
138 'both-horiz':gtk.TOOLBAR_BOTH_HORIZ}
140 def _initialize(self):
141 style = self._view.get_style()
143 if style in self.STYLES.keys():
144 self._view.set_style(self.STYLES[style])
146 widget_tree = gtk.glade.get_widget_tree(self._view._widget)
147 self.change_read_state_button = widget_tree.get_widget("toolbar_change_read_state_button")
149 def style_changed(self, value):
150 if value in self.STYLES.keys():
151 self._view.set_style(self.STYLES[value])
153 def set_change_read_button_state(self, new_state):
154 self.change_read_state_button.set_active(new_state)
156 class ApplicationPresenter(MVP.BasicPresenter):
157 def _initialize(self):
158 FeedManager._get_instance().connect("update-all-done", self._on_update_all_done)
160 self._current_item = None
162 self._init_widgets()
163 self._init_presenters()
164 self._view.present()
166 def _init_widgets(self):
167 widget_tree = self._view.get_widget_tree()
169 self._itemlist_view_notebook = widget_tree.get_widget('mode_view_notebook')
170 self._feedlist_view_notebook = widget_tree.get_widget('left_mode_view_notebook')
172 item_view_container = widget_tree.get_widget('item_view_container')
173 item_list_container = widget_tree.get_widget('item_list_container')
175 self.update_all_button = widget_tree.get_widget("toolbar_refresh_all_button")
177 parent_paned = item_view_container.get_parent()
178 parent_parent_widget = parent_paned.get_parent()
180 config = Config.get_instance()
182 child_list = []
183 layout = config.pane_layout
184 if layout == 'vertical':
185 new_paned = gtk.VPaned()
186 child_list.append(item_list_container)
187 child_list.append(item_view_container)
188 elif layout == 'horizontal':
189 new_paned = gtk.HPaned()
190 child_list.append(item_view_container)
191 child_list.append(item_list_container)
192 else: # Use vertical layout as default
193 new_paned = gtk.VPaned()
194 child_list.append(item_list_container)
195 child_list.append(item_view_container)
197 for child in child_list:
198 child.reparent(new_paned)
200 parent_parent_widget.remove(parent_paned)
201 parent_parent_widget.add(new_paned)
203 new_paned.show()
205 def _init_presenters(self):
206 widget_tree = self._view.get_widget_tree()
207 self._toolbar_presenter = ToolbarPresenter(view=ToolbarView(widget_tree.get_widget('toolbar_default')))
208 self._error_presenter = ErrorPresenter(widget_tree.get_widget('statusbar_error_indicator'))
210 self._item_view = ItemView(widget_tree.get_widget('item_view_container'))
212 view = ItemListView(widget_tree.get_widget('item_selection_treeview'))
213 self._itemlist_presenter = ItemListPresenter(view=view)
215 view.add_selection_changed_listener(self._item_view)
216 view.add_selection_changed_listener(self)
218 view = FeedsView(widget_tree.get_widget('feed_selection_treeview'))
219 self._feed_list_presenter = FeedsPresenter(view=view)
220 view.add_selection_changed_listener(self._itemlist_presenter)
221 view.add_selection_changed_listener(self._error_presenter)
223 self._offline_presenter = OfflineToggle(widget_tree.get_widget('offline_toggle'))
225 self._status_presenter = StatusPresenter(view = widget_tree.get_widget("main_statusbar"))
226 self._menufp_presenter = MenuFeedPropsPresenter( view = widget_tree.get_widget('feed_information'))
227 self._menufp_presenter.set_sensitive(False)
228 # self._find_presenter = FindPresenter(view=FindView(widget_tree.get_widget("find_vbox")))
230 def set_current_item(self, item):
231 self._current_item = item
232 self._current_item_connect_id = item.connect("is-read-changed", self._on_current_item_is_read_changed)
234 def clear_current_item(self):
235 if self._current_item:
236 self._current_item.disconnect(self._current_item_connect_id)
237 self._current_item = None
239 def set_current_item_is_read(self, is_read):
240 if self._current_item:
241 self._current_item.props.is_read = is_read
243 def _on_current_item_is_read_changed(self, obj, is_read):
244 self._toolbar_presenter.set_change_read_button_state(is_read)
246 def itemlist_selection_changed(self, selection, column):
247 self.clear_current_item()
249 (model, treeiter) = selection.get_selected()
250 if not treeiter: return
251 item = model.get_value(treeiter, column)
252 self._toolbar_presenter.set_change_read_button_state(item.is_read)
253 self.set_current_item(item)
255 def add_category(self):
256 self._feed_list_presenter.add_category()
258 def copy_itemview_text_selection(self):
259 helpers.set_clipboard_text(self._item_view.get_selected_text())
261 def check_allocation(self, widget, event):
262 config = Config.get_instance()
263 def check_size((width, height, widget)):
264 if width == widget.allocation.width and height == widget.allocation.height:
265 config.main_window_size = (width, height)
266 if event.width != widget.allocation.width or event.height != widget.allocation.height:
267 gobject.timeout_add(1000, check_size, (
268 (event.width, event.height, widget)))
270 def check_main_pane_position(self, widget):
271 config = Config.get_instance()
272 def check_position((position, widget)):
273 if position == widget.get_position():
274 config.main_pane_position = position
275 pos = widget.get_position()
276 if pos != config.main_pane_position:
277 gobject.timeout_add(1000, check_position, (pos, widget))
279 def check_sub_pane_position(self, widget):
280 config = Config.get_instance()
281 def check_position((position, widget)):
282 if position == widget.get_position():
283 config.sub_pane_position = position
284 pos = widget.get_position()
285 if pos != config.sub_pane_position:
286 gobject.timeout_add(1000, check_position, (pos, widget))
288 def credits(self):
289 return helpers.credits()
291 def poll_all(self):
292 if not self._warn_if_offline():
293 return
295 if not FeedManager.is_update_all_running():
296 FeedManager.update_all_feeds({ "task-start": [ self._on_feed_poll_started ],
297 "task-done": [ self._on_feed_poll_done ] })
298 self.update_all_button.set_sensitive(True)
299 self.update_all_button.set_stock_id(gtk.STOCK_STOP)
300 else:
301 self.update_all_button.set_sensitive(False)
302 JobManager.stop_jobs()
304 def _on_feed_poll_started(self, handler, feed):
305 pass
307 def _on_feed_poll_done(self, handler, data):
308 pass
310 def _on_update_all_done(self, obj):
311 self.update_all_button.set_sensitive(True)
312 self.update_all_button.set_stock_id(gtk.STOCK_REFRESH)
314 def poll_current_category(self):
315 if self._warn_if_offline():
316 self._poll_categories([self._curr_category])
318 def poll_current_feed(self):
319 if self._warn_if_offline():
320 print self._curr_feed
321 pm = PollManager.get_instance()
322 pm.poll([self._curr_feed])
324 def mark_feed_as_read(self):
325 self._curr_feed.mark_all_read()
327 def mark_all_as_read(self):
328 print "TODO mark_all_as_read"
330 def remove_selected_feed(self):
331 # self._feed_list_presenter.remove_selected_feed()
332 pass
334 def show_search(self):
335 # self._find_presenter.item_list.signal_connect(Event.ItemSelectionChangedSignal,
336 # self._item_view.item_selection_changed)
337 self._item_view.display_empty_search()
338 self._itemlist_view_notebook.set_current_page(1)
339 self._feedlist_view_notebook.set_current_page(1)
340 self._feedinfo_presenter.hide()
342 def hide_search(self):
343 # self._find_presenter.clear()
344 self._itemlist_view_notebook.set_current_page(0)
345 self._feedlist_view_notebook.set_current_page(0)
346 self._feedinfo_presenter.show()
347 # self._find_presenter.item_list.signal_disconnect(Event.ItemSelectionChangedSignal,
348 # self._item_view.item_selection_changed)
350 def _poll_categories(self, fclist):
351 pm = PollManager.get_instance()
352 pm.poll_categories(fclist)
354 def _warn_if_offline(self):
355 config = Config.get_instance()
356 will_poll = False
357 if config.offline:
358 response = self._view.show_offline_dialog()
359 if response == gtk.RESPONSE_OK:
360 config.offline = not config.offline
361 will_poll = True
362 else:
363 will_poll = True
364 return will_poll
366 def display_previous_feed(self, item = None):
368 Displays the feed before the current selected feed
370 self._feed_list_presenter.select_previous_feed()
372 def display_next_feed(self, item=None):
374 Displays the feed after the current selected feed
376 self._feed_list_presenter.select_next_feed()
377 return
379 def display_next_unread_feed(self):
381 Displays the next feed with an unread item
383 self._feed_list_presenter.select_next_feed(with_unread=True)
384 pass
386 def display_previous_item(self, item=None):
388 Displays the item before the current selected item. If the item is the
389 first item, scrolls to the previous feed
391 is_prev = self._itemlist_presenter.select_previous_item()
392 if not is_prev:
393 # TODO HACK - implement select_previous_feed(select_last=True) ...
394 # ... to select previous feed's last item
395 self._feed_list_presenter.select_previous_feed()
396 self._itemlist_presenter.select_last_item()
397 pass
398 return
400 def display_next_item(self, item=None):
402 Displays the item after the current selected item. If the item is the
403 last item, selectes the next feed. If the current feed is the last
404 feed in the list, it goes back and selects the first feed
406 is_next = self._itemlist_presenter.select_next_item()
407 if not is_next:
408 is_next_feed = self._feed_list_presenter.select_next_feed()
409 if not is_next_feed:
410 self._feed_list_presenter.select_firsteed_feed()
411 return
413 def scroll_or_display_next_unread_item(self, item=None):
414 has_unread_item = False
415 if not self._item_view.scroll_down():
416 has_unread_item = self._itemlist_presenter.select_next_unread_item()
417 if not has_unread_item:
418 self._feed_list_presenter.select_next_feed(with_unread=True)
419 return
421 def show_preferences_dialog(self, parent):
422 straw.preferences_show()
424 def show_feed_properties(self, parent):
425 straw.feed_properties_show(self._curr_feed)
427 def quit(self):
428 gtk.main_quit()
430 def _setup_filechooser_dialog(self, title, action, extra_widget_title):
432 Setup the file chooser dialog. This includes an extra widget (a combobox)
433 to include the categories to import or export
435 dialog = gtk.FileChooserDialog(title, action=action,
436 buttons=(gtk.STOCK_CANCEL,
437 gtk.RESPONSE_CANCEL,
438 gtk.STOCK_OK, gtk.RESPONSE_OK))
439 category_list = []
441 for category in FeedManager.categories():
442 category_list.append(category)
444 model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
445 combobox = gtk.ComboBox(model)
446 celltitle = gtk.CellRendererText()
447 combobox.pack_start(celltitle,False)
448 combobox.add_attribute(celltitle, 'text', 0)
450 for category in category_list:
451 it = model.append()
452 model.set(it, 0, category.name, 1, category)
454 combobox.set_active(0)
455 label = gtk.Label(extra_widget_title)
456 label.set_alignment(1.0,0.5)
457 hbox = gtk.HBox(spacing=6)
458 hbox.pack_start(label,True,True,0)
459 hbox.pack_end(combobox,False,False,0)
460 hbox.show_all()
462 dialog.set_extra_widget(hbox)
463 return (dialog, combobox)
465 def import_subscriptions(self, parent):
466 (dialog,combobox) = self._setup_filechooser_dialog(_("Import Subscriptions"),
467 gtk.FILE_CHOOSER_ACTION_OPEN,
468 _("Add new subscriptions in:"))
469 ffilter = gtk.FileFilter()
470 ffilter.set_name(_("OPML Files Only"))
471 ffilter.add_pattern("*.xml")
472 ffilter.add_pattern("*.opml")
473 dialog.add_filter(ffilter)
474 dialog.set_transient_for(parent)
475 response = dialog.run()
476 if response == gtk.RESPONSE_OK:
477 filename = dialog.get_filename()
478 model = combobox.get_model()
479 category = model[combobox.get_active()][1]
480 dialog.hide()
481 FeedManager.import_opml(filename, category)
482 dialog.destroy()
484 def export_subscriptions(self, parent):
485 (dialog,combobox) = self._setup_filechooser_dialog(_("Export Subscriptions"),
486 gtk.FILE_CHOOSER_ACTION_SAVE,
487 _("Select category to export:"))
488 def selection_changed(widget, dialog):
489 model = widget.get_model()
490 category = model[widget.get_active()][1]
491 dialog.set_current_name("Straw-%s.xml" % category.name)
493 combobox.connect('changed', selection_changed, dialog)
494 selection_changed(combobox, dialog)
495 dialog.set_transient_for(parent)
496 response = dialog.run()
498 if response == gtk.RESPONSE_OK:
499 filename = dialog.get_filename()
500 model = combobox.get_model()
501 root_category = model[combobox.get_active()][1]
502 FeedManager.export_opml(root_category.id, filename)
504 dialog.destroy()
506 def subscribe(self):
507 straw.subscribe_show(parent=self.view._widget)
509 class ApplicationView(MVP.WidgetView):
511 Widget: straw_main
513 def _initialize(self):
514 self._config = Config.get_instance()
515 self._initialize_dnd()
516 self._initialize_window_updater()
517 self._create_unmodified_accelerator_group()
518 self._attach_unmodified_accelerator_group()
519 self._initialize_window()
520 self._find_toggled = False
522 def _initialize_window(self):
523 widget_tree = gtk.glade.get_widget_tree(self._widget)
524 if self._config.window_maximized:
525 self._widget.maximize()
526 else:
527 # we use resize here since configure-event seems to
528 # overwrite the default size if we use set_default_size.
529 self._widget.resize(*self._config.main_window_size)
530 mmp = widget_tree.get_widget('main_main_pane')
531 msp = widget_tree.get_widget('main_sub_pane')
532 mmp.set_position(self._config.main_pane_position)
533 msp.set_position(self._config.sub_pane_position)
535 def _initialize_dnd(self):
536 self._widget.drag_dest_set(
537 gtk.DEST_DEFAULT_ALL,
538 [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1),
539 ('x-url/http', 0, 2)],
540 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
541 return
543 def _initialize_window_updater(self):
544 #feedlist.signal_connect(Event.AllItemsReadSignal,
545 # lambda signal: self._update_title(feedlist))
546 #feedlist.signal_connect(Event.ItemReadSignal,
547 # lambda signal: self._update_title(feedlist))
548 #feedlist.signal_connect(Event.ItemsAddedSignal,
549 # lambda signal: self._update_title(feedlist))
550 #feedlist.signal_connect(Event.FeedsChangedSignal,
551 # lambda signal: self._update_title(feedlist))
552 pass
554 def _update_title(self, flist):
555 uritems = urfeeds = 0
556 sfeeds = "feeds"
557 listfeeds = flist.flatten_list()
558 for ur in [f.number_of_unread for f in listfeeds]:
559 if ur:
560 uritems += ur
561 urfeeds += 1
562 else:
563 urfeeds = len(listfeeds)
564 if urfeeds < 2:
565 sfeeds = "feed"
566 item_feed_map = {'uritems': uritems,
567 'urfeeds': urfeeds,
568 'fstring' : sfeeds}
569 title = _('%(uritems)d unread in %(urfeeds)d %(fstring)s') % item_feed_map
570 self._widget.set_title( title + " - %s" % straw.PACKAGE)
572 # We have a separate accelerator group for the unmodified and
573 # shifted accelerators, that is, stuff like space, N, P, etc. This
574 # is so that we can have the find pane work correctly
575 def _create_unmodified_accelerator_group(self):
576 xml = gtk.glade.get_widget_tree(self._widget)
577 agroup = gtk.AccelGroup()
578 accels = (('feed_mark_as_read', 'R', gtk.gdk.SHIFT_MASK),
579 ('feed_mark_all_as_read', 'A', gtk.gdk.SHIFT_MASK),
580 ('next_item', 'N', gtk.gdk.SHIFT_MASK),
581 ('next_unread_feed', ' ', 0),
582 ('previous_item', 'P', gtk.gdk.SHIFT_MASK))
583 for widget_name, key, mask in accels:
584 widget = xml.get_widget(widget_name)
585 widget.add_accelerator("activate", agroup, ord(key), mask,
586 gtk.ACCEL_VISIBLE)
587 self._unmodified_accelerator_group = agroup
589 def _on_category_add_activate(self, *args):
590 self._presenter.add_category()
592 def _on_toolbar_change_read_state_button_toggled(self, button):
593 self._presenter.set_current_item_is_read(button.get_active())
595 def _attach_unmodified_accelerator_group(self):
596 self._widget.add_accel_group(self._unmodified_accelerator_group)
598 def _detach_unmodified_accelerator_group(self):
599 self._widget.remove_accel_group(self._unmodified_accelerator_group)
601 def _on_straw_main_destroy_event(self, *args):
602 return self._presenter.quit()
604 def _on_straw_main_delete_event(self, *args):
605 return self._presenter.quit()
607 def _on_straw_main_configure_event(self, widget, event, *args):
608 if widget.window.get_state() is not gtk.gdk.WINDOW_STATE_MAXIMIZED:
609 self._config.window_maximized = False
610 self._presenter.check_allocation(widget, event)
611 else:
612 self._config.window_maximized = True
613 return
615 def _on_main_main_pane_size_allocate(self, widget, *args):
616 self._presenter.check_main_pane_position(widget)
618 def _on_main_sub_pane_size_allocate(self, widget, *args):
619 self._presenter.check_sub_pane_position(widget)
621 # Actions menu
622 def _on_feed_subscribe_activate(self, *args):
623 self._presenter.subscribe()
625 def _on_feed_unsubscribe_activate(self, *args):
626 self._presenter.remove_selected_feed()
628 def _on_subscription_import_activate(self, *args):
629 self._presenter.import_subscriptions(self._widget)
631 def _on_subscription_export_activate(self, *args):
632 self._presenter.export_subscriptions(self._widget)
634 def _on_feed_mark_as_read_activate(self, *args):
635 self._presenter.mark_feed_as_read()
637 def _on_feed_mark_all_as_read_activate(self, *args):
638 self._presenter.mark_all_as_read()
640 def _on_feed_refresh_selected_activate(self, *args):
641 self._presenter.poll_current_feed()
643 def _on_subscription_refresh_activate(self, *args):
644 #print self._widget.get_children()[0].set_stock_id(gtk.STOCK_STOP)
645 self._presenter.poll_all()
647 def _on_quit_activate(self, *args):
648 return self._presenter.quit()
650 # Edit menu
651 def _on_copy_activate(self, *args):
652 self._presenter.copy_itemview_text_selection()
655 def _on_find_activate(self, widget, *args):
656 xml = gtk.glade.get_widget_tree(self._widget)
657 menu_find = xml.get_widget('menu_find')
658 accel_label = menu_find.get_child()
660 if not self._find_toggled:
661 self._presenter.show_search()
662 self._detach_unmodified_accelerator_group()
663 self._find_toggled = True
665 # save the "Find..." stock text for later recovery
666 self._old_label_text = accel_label.get_text()
667 accel_label.set_text(_('Return to feed list...'))
669 else:
670 self._presenter.hide_search()
671 self._attach_unmodified_accelerator_group()
672 self._find_toggled = False
673 accel_label.set_text(self._old_label_text)
675 def _on_preferences_activate(self, *args):
676 self._presenter.show_preferences_dialog(self._widget)
678 def _on_feed_information_activate(self, *args):
679 self._presenter.show_feed_properties(self._widget)
681 # View menu
682 def _on_previous_item_activate(self, *args):
683 self._presenter.display_previous_item()
685 def _on_next_item_activate(self, *args):
686 self._presenter.display_next_item()
688 def _on_next_feed_activate(self, *args):
689 self._presenter.display_next_feed()
691 def _on_previous_feed_activate(self, *args):
692 self._presenter.display_previous_feed()
694 def _on_next_unread_feed_activate(self, *args):
695 self._presenter.display_next_unread_feed()
697 def _on_scroll_unread_items_activate(self, *args):
698 self._presenter.scroll_or_display_next_unread_item()
700 # Help menu
702 def _on_report_problem_activate(self, menuitem, *args):
703 helpers.url_show("http://bugzilla.gnome.org/simple-bug-guide.cgi?product=straw")
705 def _on_about_activate(self, menuitem, *args):
706 widget = self._presenter.credits()
707 widget.show()
709 def _on_straw_main_drag_data_received(self, w, context,
710 x, y, data, info, time):
711 if data and data.format == 8:
712 url = data.data.split("\n")[0]
713 straw.subscribe_show("%s" % url, self._widget)
714 context.finish(True, False, time)
715 else:
716 context.finish(False, False, time)
718 # FIXME
719 def show_offline_dialog(self):
720 return helpers.report_offline_status(self._widget)
722 def get_widget_tree(self):
723 return gtk.glade.get_widget_tree(self._widget)
725 def present(self):
726 self._widget.present()
728 def should_present(self):
729 if self._widget.window.get_state() is not gtk.gdk.WINDOW_STATE_WITHDRAWN:
730 self._widget.hide()
731 else:
732 self._widget.present()
735 class Application:
736 def __init__(self):
737 gnome.program_init(straw.PACKAGE, straw.VERSION)
739 error.setup_log()
740 # initialize threading and environment
741 gobject.threads_init()
742 config = Config.get_instance()
743 config.reload_css = os.getenv('STRAW_RELOAD_CSS') is not None
745 # build tray status icon
746 image = gtk.Image()
747 iconfile = os.path.normpath(straw.STRAW_DATA_DIR + "/straw.png")
748 pixbuf = gtk.gdk.pixbuf_new_from_file(iconfile)
749 scaled_buf = pixbuf.scale_simple(16,16,gtk.gdk.INTERP_BILINEAR)
750 image.set_from_pixbuf(scaled_buf)
751 try:
752 self.tray = gtk.status_icon_new_from_pixbuf(scaled_buf)
753 self.tray.connect('activate', self._tray_clicked)
754 except AttributeError:
755 import egg
756 import egg.trayicon
757 self.tray = egg.trayicon.TrayIcon(straw.PACKAGE);
758 self._eventbox = gtk.EventBox()
759 self.tray.add(self._eventbox)
760 self._eventbox.connect('button_press_event', self._tray_clicked)
761 self._eventbox.connect("drag-data-received", self._on_drag_data_received)
762 self._eventbox.drag_dest_set(
763 gtk.DEST_DEFAULT_ALL,
764 [('_NETSCAPE_URL', 0, 0),('text/uri-list ', 0, 1),('x-url/http', 0, 2)],
765 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
766 self._eventbox.add(image)
767 self.tray.show_all()
769 self._tooltip = gtk.Tooltips()
770 self.unread_count = 0
771 # end build tray status icon
773 FeedManager.setup(storage_path = "test.db")
774 FeedManager.init()
776 ItemManager.setup(storage_path = "test.db")
777 ItemManager.init()
779 #FeedManager.import_opml(os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml"))
781 #if config.first_time:
782 # filepath = os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml")
783 # feeds.import_opml(filepath)
785 #ImageCache.initialize()
787 #import feedfinder
788 #print feedfinder.feeds("http://eclipse.org", True)
790 xml = gtk.glade.XML(os.path.join(straw.STRAW_DATA_DIR,'straw.glade'), "straw_main", gettext.textdomain())
791 window = xml.get_widget('straw_main')
792 self._main_presenter = ApplicationPresenter(view = ApplicationView(window))
793 self._main_presenter.view.present()
795 #PollManager.get_instance().start_polling_loop()
796 # set the default icon for the windows
797 iconfile = os.path.join(straw.STRAW_DATA_DIR,"straw.png")
798 gtk.window_set_default_icon(gtk.gdk.pixbuf_new_from_file(iconfile))
800 straw.start_services()
802 def mainloop(self):
803 gtk.gdk.threads_init()
804 gtk.gdk.threads_enter()
805 gtk.main()
806 gtk.gdk.threads_leave()
808 def _update(self,flist):
809 uritems = urfeeds = 0
810 for ur in [f.number_of_unread for f in flist.flatten_list()]:
811 if ur:
812 uritems += ur
813 urfeeds += 1
814 if uritems == self.unread_count:
815 return
816 self.unread_count = uritems
817 if uritems:
818 self._tooltip.set_tip(self.tray, _("%d new items")%uritems)
819 self.tray.show_all()
820 else:
821 self.tray.hide()
822 return
824 def _tray_clicked(self, widget, event=None):
825 self._main_presenter.view.should_present()
826 if event and not (event.button == 1):
827 return
828 self._main_presenter.scroll_or_display_next_unread_item()
830 def _on_drag_data_received(self, widget, context, x, y, data, info, timestamp):
831 if data and data.format == 8:
832 url = data.data.split("\n")[0]
833 straw.subscribe_show(url="%s" % url)
834 context.finish(True, False, timestamp)
835 else:
836 context.finish(False, False, timestamp)
837 return