Work on feed properties dialog, added some properties to Feed model class.
[straw.git] / straw / Application.py
blob9e38a84e22782212547182fee01966f40af83cd1
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.defs
42 from feedproperties import feed_properties_show
43 from preferences import preferences_show
44 from subscribe import subscribe_show
45 from MessageManager import post_status_message, get_status_manager, start_services
46 import time
47 pygtk.require('2.0')
49 #from Find import FindPresenter, FindView
52 class StatusPresenter(MVP.BasicPresenter):
53 def _initialize(self):
54 self._mmgr = get_status_manager()
55 self._mmgr.connect('changed', self._display)
57 def _display(self, *args):
58 cid = self._view.get_context_id("straw_main")
59 self._view.pop(cid)
60 self._view.push(cid, self._mmgr.read_message())
61 return
63 class ErrorPresenter:
64 def __init__(self, widget):
65 self._widget = widget
66 self._tooltips = gtk.Tooltips()
67 self._text = ''
68 #self._curr_feed = None
69 #self._curr_category = None
70 #fclist = FeedCategoryList.get_instance()
71 #fclist.connect('category-changed', self._category_changed)
73 def feedlist_selection_changed(self, selection, column):
74 errortexts = None
75 return
76 (model, pathlist) = selection.get_selected_rows()
77 iters = [model.get_iter(path) for path in pathlist]
78 if not iters: return
79 errorfeeds = []
80 nodes = [model.get_value(treeiter, column) for treeiter in iters]
81 try:
82 errorfeeds = [node.feed for node in nodes if node.feed and node.feed.error]
83 errortexts = [feed.error for feed in errorfeeds]
84 except TypeError, te:
85 print te
86 ### XXX display OPML Category error too
88 text = list()
89 if errorfeeds:
90 text.append(_("Error:"))
91 text += errortexts
93 if text:
94 t = "\n".join(text)
95 self._tooltips.set_tip(self._widget,t,t)
96 self._tooltips.enable()
97 self._widget.show()
98 else:
99 self._tooltips.disable()
100 self._widget.hide()
101 return
103 def hide(self):
104 self._widget.hide()
106 class MenuFeedPropsPresenter(MVP.BasicPresenter):
107 # FIXME
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()
147 if style in self.STYLES.keys():
148 self._view.set_style(self.STYLES[style])
150 widget_tree = gtk.glade.get_widget_tree(self._view._widget)
151 self.change_read_state_button = widget_tree.get_widget("toolbar_change_read_state_button")
153 def style_changed(self, value):
154 if value in self.STYLES.keys():
155 self._view.set_style(self.STYLES[value])
157 def set_change_read_button_state(self, new_state):
158 self.change_read_state_button.set_active(new_state)
160 class ApplicationPresenter(MVP.BasicPresenter):
161 def _initialize(self):
162 FeedManager._get_instance().connect("update-all-done", self._on_update_all_done)
164 self._current_item = None
166 self._init_widgets()
167 self._init_presenters()
168 self._view.present()
170 def _init_widgets(self):
171 widget_tree = self._view.get_widget_tree()
173 self._itemlist_view_notebook = widget_tree.get_widget('mode_view_notebook')
174 self._feedlist_view_notebook = widget_tree.get_widget('left_mode_view_notebook')
176 item_view_container = widget_tree.get_widget('item_view_container')
177 item_list_container = widget_tree.get_widget('item_list_container')
179 self.update_all_button = widget_tree.get_widget("toolbar_refresh_all_button")
181 parent_paned = item_view_container.get_parent()
182 parent_parent_widget = parent_paned.get_parent()
184 config = Config.get_instance()
186 child_list = []
187 layout = config.pane_layout
188 if layout == 'vertical':
189 new_paned = gtk.VPaned()
190 child_list.append(item_list_container)
191 child_list.append(item_view_container)
192 elif layout == 'horizontal':
193 new_paned = gtk.HPaned()
194 child_list.append(item_view_container)
195 child_list.append(item_list_container)
196 else: # Use vertical layout as default
197 new_paned = gtk.VPaned()
198 child_list.append(item_list_container)
199 child_list.append(item_view_container)
201 for child in child_list:
202 child.reparent(new_paned)
204 parent_parent_widget.remove(parent_paned)
205 parent_parent_widget.add(new_paned)
207 new_paned.show()
209 def _init_presenters(self):
210 widget_tree = self._view.get_widget_tree()
211 self._toolbar_presenter = ToolbarPresenter(view=ToolbarView(widget_tree.get_widget('toolbar_default')))
212 self._error_presenter = ErrorPresenter(widget_tree.get_widget('statusbar_error_indicator'))
214 self._item_view = ItemView(widget_tree.get_widget('item_view_container'))
216 view = ItemListView(widget_tree.get_widget('item_selection_treeview'))
217 self._itemlist_presenter = ItemListPresenter(view=view)
219 view.add_selection_changed_listener(self._item_view)
220 view.add_selection_changed_listener(self)
222 view = FeedsView(widget_tree.get_widget('feed_selection_treeview'))
223 self._feed_list_presenter = FeedsPresenter(view=view)
224 view.add_selection_changed_listener(self._itemlist_presenter)
225 view.add_selection_changed_listener(self._error_presenter)
227 self._offline_presenter = OfflineToggle(widget_tree.get_widget('offline_toggle'))
229 self._status_presenter = StatusPresenter(view = widget_tree.get_widget("main_statusbar"))
230 self._menufp_presenter = MenuFeedPropsPresenter( view = widget_tree.get_widget('feed_information'))
231 self._menufp_presenter.set_sensitive(False)
232 # self._find_presenter = FindPresenter(view=FindView(widget_tree.get_widget("find_vbox")))
234 def set_current_item(self, item):
235 self._current_item = item
236 self._current_item_connect_id = item.connect("is-read-changed", self._on_current_item_is_read_changed)
238 def clear_current_item(self):
239 if self._current_item:
240 self._current_item.disconnect(self._current_item_connect_id)
241 self._current_item = None
243 def set_current_item_is_read(self, is_read):
244 if self._current_item:
245 self._current_item.props.is_read = is_read
247 def _on_current_item_is_read_changed(self, obj, is_read):
248 self._toolbar_presenter.set_change_read_button_state(is_read)
250 def itemlist_selection_changed(self, selection, column):
251 self.clear_current_item()
253 (model, treeiter) = selection.get_selected()
254 if not treeiter: return
255 item = model.get_value(treeiter, column)
256 self._toolbar_presenter.set_change_read_button_state(item.is_read)
257 self.set_current_item(item)
259 def add_category(self):
260 self._feed_list_presenter.add_category()
262 def copy_itemview_text_selection(self):
263 helpers.set_clipboard_text(self._item_view.get_selected_text())
265 def check_allocation(self, widget, event):
266 config = Config.get_instance()
267 def check_size((width, height, widget)):
268 if width == widget.allocation.width and height == widget.allocation.height:
269 config.main_window_size = (width, height)
270 if event.width != widget.allocation.width or event.height != widget.allocation.height:
271 gobject.timeout_add(1000, check_size, (
272 (event.width, event.height, widget)))
274 def check_main_pane_position(self, widget):
275 config = Config.get_instance()
276 def check_position((position, widget)):
277 if position == widget.get_position():
278 config.main_pane_position = position
279 pos = widget.get_position()
280 if pos != config.main_pane_position:
281 gobject.timeout_add(1000, check_position, (pos, widget))
283 def check_sub_pane_position(self, widget):
284 config = Config.get_instance()
285 def check_position((position, widget)):
286 if position == widget.get_position():
287 config.sub_pane_position = position
288 pos = widget.get_position()
289 if pos != config.sub_pane_position:
290 gobject.timeout_add(1000, check_position, (pos, widget))
292 def credits(self):
293 return helpers.credits()
295 def poll_all(self):
296 if not self._warn_if_offline():
297 return
299 if not FeedManager.is_update_all_running():
300 FeedManager.update_all_feeds({ "task-start": [ self._on_feed_poll_started ],
301 "task-done": [ self._on_feed_poll_done ] })
302 self.update_all_button.set_sensitive(True)
303 self.update_all_button.set_stock_id(gtk.STOCK_STOP)
304 else:
305 self.update_all_button.set_sensitive(False)
306 JobManager.stop_jobs()
308 def _on_feed_poll_started(self, handler, feed):
309 pass
311 def _on_feed_poll_done(self, handler, data):
312 pass
314 def _on_update_all_done(self, obj):
315 self.update_all_button.set_sensitive(True)
316 self.update_all_button.set_stock_id(gtk.STOCK_REFRESH)
318 def poll_current_category(self):
319 if self._warn_if_offline():
320 self._poll_categories([self._curr_category])
322 def poll_current_feed(self):
323 if self._warn_if_offline():
324 print self._curr_feed
325 pm = PollManager.get_instance()
326 pm.poll([self._curr_feed])
328 def mark_feed_as_read(self):
329 self._curr_feed.mark_all_read()
331 def mark_all_as_read(self):
332 print "TODO mark_all_as_read"
334 def remove_selected_feed(self):
335 # self._feed_list_presenter.remove_selected_feed()
336 pass
338 def show_search(self):
339 # self._find_presenter.item_list.signal_connect(Event.ItemSelectionChangedSignal,
340 # self._item_view.item_selection_changed)
341 self._item_view.display_empty_search()
342 self._itemlist_view_notebook.set_current_page(1)
343 self._feedlist_view_notebook.set_current_page(1)
344 self._feedinfo_presenter.hide()
346 def hide_search(self):
347 # self._find_presenter.clear()
348 self._itemlist_view_notebook.set_current_page(0)
349 self._feedlist_view_notebook.set_current_page(0)
350 self._feedinfo_presenter.show()
351 # self._find_presenter.item_list.signal_disconnect(Event.ItemSelectionChangedSignal,
352 # self._item_view.item_selection_changed)
354 def _poll_categories(self, fclist):
355 pm = PollManager.get_instance()
356 pm.poll_categories(fclist)
358 def _warn_if_offline(self):
359 config = Config.get_instance()
360 will_poll = False
361 if config.offline:
362 response = self._view.show_offline_dialog()
363 if response == gtk.RESPONSE_OK:
364 config.offline = not config.offline
365 will_poll = True
366 else:
367 will_poll = True
368 return will_poll
370 def display_previous_feed(self, item = None):
372 Displays the feed before the current selected feed
374 self._feed_list_presenter.select_previous_feed()
376 def display_next_feed(self, item=None):
378 Displays the feed after the current selected feed
380 self._feed_list_presenter.select_next_feed()
381 return
383 def display_next_unread_feed(self):
385 Displays the next feed with an unread item
387 self._feed_list_presenter.select_next_feed(with_unread=True)
388 pass
390 def display_previous_item(self, item=None):
392 Displays the item before the current selected item. If the item is the
393 first item, scrolls to the previous feed
395 is_prev = self._itemlist_presenter.select_previous_item()
396 if not is_prev:
397 # TODO HACK - implement select_previous_feed(select_last=True) ...
398 # ... to select previous feed's last item
399 self._feed_list_presenter.select_previous_feed()
400 self._itemlist_presenter.select_last_item()
401 pass
402 return
404 def display_next_item(self, item=None):
406 Displays the item after the current selected item. If the item is the
407 last item, selectes the next feed. If the current feed is the last
408 feed in the list, it goes back and selects the first feed
410 is_next = self._itemlist_presenter.select_next_item()
411 if not is_next:
412 is_next_feed = self._feed_list_presenter.select_next_feed()
413 if not is_next_feed:
414 self._feed_list_presenter.select_firsteed_feed()
415 return
417 def scroll_or_display_next_unread_item(self, item=None):
418 has_unread_item = False
419 if not self._item_view.scroll_down():
420 has_unread_item = self._itemlist_presenter.select_next_unread_item()
421 if not has_unread_item:
422 self._feed_list_presenter.select_next_feed(with_unread=True)
423 return
425 def show_preferences_dialog(self, parent):
426 preferences_show()
428 def show_feed_properties(self, parent):
429 feed_properties_show(self._curr_feed)
431 def quit(self):
432 gtk.main_quit()
434 def _setup_filechooser_dialog(self, title, action, extra_widget_title):
436 Setup the file chooser dialog. This includes an extra widget (a combobox)
437 to include the categories to import or export
439 dialog = gtk.FileChooserDialog(title, action=action,
440 buttons=(gtk.STOCK_CANCEL,
441 gtk.RESPONSE_CANCEL,
442 gtk.STOCK_OK, gtk.RESPONSE_OK))
443 category_list = []
445 for category in FeedManager.categories():
446 category_list.append(category)
448 model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
449 combobox = gtk.ComboBox(model)
450 celltitle = gtk.CellRendererText()
451 combobox.pack_start(celltitle,False)
452 combobox.add_attribute(celltitle, 'text', 0)
454 for category in category_list:
455 it = model.append()
456 model.set(it, 0, category.name, 1, category)
458 combobox.set_active(0)
459 label = gtk.Label(extra_widget_title)
460 label.set_alignment(1.0,0.5)
461 hbox = gtk.HBox(spacing=6)
462 hbox.pack_start(label,True,True,0)
463 hbox.pack_end(combobox,False,False,0)
464 hbox.show_all()
466 dialog.set_extra_widget(hbox)
467 return (dialog, combobox)
469 def import_subscriptions(self, parent):
470 (dialog,combobox) = self._setup_filechooser_dialog(_("Import Subscriptions"),
471 gtk.FILE_CHOOSER_ACTION_OPEN,
472 _("Add new subscriptions in:"))
473 ffilter = gtk.FileFilter()
474 ffilter.set_name(_("OPML Files Only"))
475 ffilter.add_pattern("*.xml")
476 ffilter.add_pattern("*.opml")
477 dialog.add_filter(ffilter)
478 dialog.set_transient_for(parent)
479 response = dialog.run()
480 if response == gtk.RESPONSE_OK:
481 filename = dialog.get_filename()
482 model = combobox.get_model()
483 category = model[combobox.get_active()][1]
484 dialog.hide()
485 FeedManager.import_opml(filename, category)
486 dialog.destroy()
488 def export_subscriptions(self, parent):
489 (dialog,combobox) = self._setup_filechooser_dialog(_("Export Subscriptions"),
490 gtk.FILE_CHOOSER_ACTION_SAVE,
491 _("Select category to export:"))
492 def selection_changed(widget, dialog):
493 model = widget.get_model()
494 category = model[widget.get_active()][1]
495 dialog.set_current_name("Straw-%s.xml" % category.name)
497 combobox.connect('changed', selection_changed, dialog)
498 selection_changed(combobox, dialog)
499 dialog.set_transient_for(parent)
500 response = dialog.run()
502 if response == gtk.RESPONSE_OK:
503 filename = dialog.get_filename()
504 model = combobox.get_model()
505 root_category = model[combobox.get_active()][1]
506 FeedManager.export_opml(root_category.id, filename)
508 dialog.destroy()
510 def subscribe(self):
511 subscribe_show(parent=self.view._widget)
513 class ApplicationView(MVP.WidgetView):
515 Widget: straw_main
517 def _initialize(self):
518 self._config = Config.get_instance()
519 self._initialize_dnd()
520 self._initialize_window_updater()
521 self._create_unmodified_accelerator_group()
522 self._attach_unmodified_accelerator_group()
523 self._initialize_window()
524 self._find_toggled = False
526 def _initialize_window(self):
527 widget_tree = gtk.glade.get_widget_tree(self._widget)
528 if self._config.window_maximized:
529 self._widget.maximize()
530 else:
531 # we use resize here since configure-event seems to
532 # overwrite the default size if we use set_default_size.
533 self._widget.resize(*self._config.main_window_size)
534 mmp = widget_tree.get_widget('main_main_pane')
535 msp = widget_tree.get_widget('main_sub_pane')
536 mmp.set_position(self._config.main_pane_position)
537 msp.set_position(self._config.sub_pane_position)
539 def _initialize_dnd(self):
540 self._widget.drag_dest_set(
541 gtk.DEST_DEFAULT_ALL,
542 [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1),
543 ('x-url/http', 0, 2)],
544 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
545 return
547 def _initialize_window_updater(self):
548 #feedlist.signal_connect(Event.AllItemsReadSignal,
549 # lambda signal: self._update_title(feedlist))
550 #feedlist.signal_connect(Event.ItemReadSignal,
551 # lambda signal: self._update_title(feedlist))
552 #feedlist.signal_connect(Event.ItemsAddedSignal,
553 # lambda signal: self._update_title(feedlist))
554 #feedlist.signal_connect(Event.FeedsChangedSignal,
555 # lambda signal: self._update_title(feedlist))
556 pass
558 def _update_title(self, flist):
559 uritems = urfeeds = 0
560 sfeeds = "feeds"
561 listfeeds = flist.flatten_list()
562 for ur in [f.number_of_unread for f in listfeeds]:
563 if ur:
564 uritems += ur
565 urfeeds += 1
566 else:
567 urfeeds = len(listfeeds)
568 if urfeeds < 2:
569 sfeeds = "feed"
570 item_feed_map = {'uritems': uritems,
571 'urfeeds': urfeeds,
572 'fstring' : sfeeds}
573 title = _('%(uritems)d unread in %(urfeeds)d %(fstring)s') % item_feed_map
574 self._widget.set_title( title + " - %s" % straw.defs.PACKAGE)
576 # We have a separate accelerator group for the unmodified and
577 # shifted accelerators, that is, stuff like space, N, P, etc. This
578 # is so that we can have the find pane work correctly
579 def _create_unmodified_accelerator_group(self):
580 xml = gtk.glade.get_widget_tree(self._widget)
581 agroup = gtk.AccelGroup()
582 accels = (('feed_mark_as_read', 'R', gtk.gdk.SHIFT_MASK),
583 ('feed_mark_all_as_read', 'A', gtk.gdk.SHIFT_MASK),
584 ('next_item', 'N', gtk.gdk.SHIFT_MASK),
585 ('next_unread_feed', ' ', 0),
586 ('previous_item', 'P', gtk.gdk.SHIFT_MASK))
587 for widget_name, key, mask in accels:
588 widget = xml.get_widget(widget_name)
589 widget.add_accelerator("activate", agroup, ord(key), mask,
590 gtk.ACCEL_VISIBLE)
591 self._unmodified_accelerator_group = agroup
593 def _on_category_add_activate(self, *args):
594 self._presenter.add_category()
596 def _on_toolbar_change_read_state_button_toggled(self, button):
597 self._presenter.set_current_item_is_read(button.get_active())
599 def _attach_unmodified_accelerator_group(self):
600 self._widget.add_accel_group(self._unmodified_accelerator_group)
602 def _detach_unmodified_accelerator_group(self):
603 self._widget.remove_accel_group(self._unmodified_accelerator_group)
605 def _on_straw_main_destroy_event(self, *args):
606 return self._presenter.quit()
608 def _on_straw_main_delete_event(self, *args):
609 return self._presenter.quit()
611 def _on_straw_main_configure_event(self, widget, event, *args):
612 if widget.window.get_state() is not gtk.gdk.WINDOW_STATE_MAXIMIZED:
613 self._config.window_maximized = False
614 self._presenter.check_allocation(widget, event)
615 else:
616 self._config.window_maximized = True
617 return
619 def _on_main_main_pane_size_allocate(self, widget, *args):
620 self._presenter.check_main_pane_position(widget)
622 def _on_main_sub_pane_size_allocate(self, widget, *args):
623 self._presenter.check_sub_pane_position(widget)
625 # Actions menu
626 def _on_feed_subscribe_activate(self, *args):
627 self._presenter.subscribe()
629 def _on_feed_unsubscribe_activate(self, *args):
630 self._presenter.remove_selected_feed()
632 def _on_subscription_import_activate(self, *args):
633 self._presenter.import_subscriptions(self._widget)
635 def _on_subscription_export_activate(self, *args):
636 self._presenter.export_subscriptions(self._widget)
638 def _on_feed_mark_as_read_activate(self, *args):
639 self._presenter.mark_feed_as_read()
641 def _on_feed_mark_all_as_read_activate(self, *args):
642 self._presenter.mark_all_as_read()
644 def _on_feed_refresh_selected_activate(self, *args):
645 self._presenter.poll_current_feed()
647 def _on_subscription_refresh_activate(self, *args):
648 #print self._widget.get_children()[0].set_stock_id(gtk.STOCK_STOP)
649 self._presenter.poll_all()
651 def _on_quit_activate(self, *args):
652 return self._presenter.quit()
654 # Edit menu
655 def _on_copy_activate(self, *args):
656 self._presenter.copy_itemview_text_selection()
659 def _on_find_activate(self, widget, *args):
660 xml = gtk.glade.get_widget_tree(self._widget)
661 menu_find = xml.get_widget('menu_find')
662 accel_label = menu_find.get_child()
664 if not self._find_toggled:
665 self._presenter.show_search()
666 self._detach_unmodified_accelerator_group()
667 self._find_toggled = True
669 # save the "Find..." stock text for later recovery
670 self._old_label_text = accel_label.get_text()
671 accel_label.set_text(_('Return to feed list...'))
673 else:
674 self._presenter.hide_search()
675 self._attach_unmodified_accelerator_group()
676 self._find_toggled = False
677 accel_label.set_text(self._old_label_text)
679 def _on_preferences_activate(self, *args):
680 self._presenter.show_preferences_dialog(self._widget)
682 def _on_feed_information_activate(self, *args):
683 self._presenter.show_feed_properties(self._widget)
685 # View menu
686 def _on_previous_item_activate(self, *args):
687 self._presenter.display_previous_item()
689 def _on_next_item_activate(self, *args):
690 self._presenter.display_next_item()
692 def _on_next_feed_activate(self, *args):
693 self._presenter.display_next_feed()
695 def _on_previous_feed_activate(self, *args):
696 self._presenter.display_previous_feed()
698 def _on_next_unread_feed_activate(self, *args):
699 self._presenter.display_next_unread_feed()
701 def _on_scroll_unread_items_activate(self, *args):
702 self._presenter.scroll_or_display_next_unread_item()
704 # Help menu
706 def _on_report_problem_activate(self, menuitem, *args):
707 helpers.url_show("http://bugzilla.gnome.org/simple-bug-guide.cgi?product=straw")
709 def _on_about_activate(self, menuitem, *args):
710 widget = self._presenter.credits()
711 widget.show()
713 def _on_straw_main_drag_data_received(self, w, context,
714 x, y, data, info, time):
715 if data and data.format == 8:
716 url = data.data.split("\n")[0]
717 subscribe_show("%s" % url, self._widget)
718 context.finish(True, False, time)
719 else:
720 context.finish(False, False, time)
722 # FIXME
723 def show_offline_dialog(self):
724 return helpers.report_offline_status(self._widget)
726 def get_widget_tree(self):
727 return gtk.glade.get_widget_tree(self._widget)
729 def present(self):
730 self._widget.present()
732 def should_present(self):
733 if self._widget.window.get_state() is not gtk.gdk.WINDOW_STATE_WITHDRAWN:
734 self._widget.hide()
735 else:
736 self._widget.present()
739 class Application:
740 def __init__(self):
741 gnome.program_init(straw.defs.PACKAGE, straw.defs.VERSION)
743 error.setup_log()
744 # initialize threading and environment
745 gobject.threads_init()
746 config = Config.get_instance()
747 config.reload_css = os.getenv('STRAW_RELOAD_CSS') is not None
749 # build tray status icon
750 image = gtk.Image()
751 iconfile = os.path.normpath(straw.defs.STRAW_DATA_DIR + "/straw.png")
752 pixbuf = gtk.gdk.pixbuf_new_from_file(iconfile)
753 scaled_buf = pixbuf.scale_simple(16,16,gtk.gdk.INTERP_BILINEAR)
754 image.set_from_pixbuf(scaled_buf)
755 try:
756 self.tray = gtk.status_icon_new_from_pixbuf(scaled_buf)
757 self.tray.connect('activate', self._tray_clicked)
758 except AttributeError:
759 import egg
760 import egg.trayicon
761 self.tray = egg.trayicon.TrayIcon(straw.defs.PACKAGE);
762 self._eventbox = gtk.EventBox()
763 self.tray.add(self._eventbox)
764 self._eventbox.connect('button_press_event', self._tray_clicked)
765 self._eventbox.connect("drag-data-received", self._on_drag_data_received)
766 self._eventbox.drag_dest_set(
767 gtk.DEST_DEFAULT_ALL,
768 [('_NETSCAPE_URL', 0, 0),('text/uri-list ', 0, 1),('x-url/http', 0, 2)],
769 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
770 self._eventbox.add(image)
771 self.tray.show_all()
773 self._tooltip = gtk.Tooltips()
774 self.unread_count = 0
775 # end build tray status icon
777 FeedManager.setup(storage_path = "test.db")
778 FeedManager.init()
780 ItemManager.setup(storage_path = "test.db")
781 ItemManager.init()
783 #FeedManager.import_opml(os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml"))
785 #if config.first_time:
786 # filepath = os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml")
787 # feeds.import_opml(filepath)
789 #ImageCache.initialize()
791 #import feedfinder
792 #print feedfinder.feeds("http://eclipse.org", True)
794 xml = gtk.glade.XML(os.path.join(straw.defs.STRAW_DATA_DIR,'straw.glade'), "straw_main", gettext.textdomain())
795 window = xml.get_widget('straw_main')
796 self._main_presenter = ApplicationPresenter(view = ApplicationView(window))
797 self._main_presenter.view.present()
799 #PollManager.get_instance().start_polling_loop()
800 # set the default icon for the windows
801 iconfile = os.path.join(straw.defs.STRAW_DATA_DIR,"straw.png")
802 gtk.window_set_default_icon(gtk.gdk.pixbuf_new_from_file(iconfile))
804 start_services()
806 def mainloop(self):
807 gtk.gdk.threads_init()
808 gtk.gdk.threads_enter()
809 gtk.main()
810 gtk.gdk.threads_leave()
812 def _update(self,flist):
813 uritems = urfeeds = 0
814 for ur in [f.number_of_unread for f in flist.flatten_list()]:
815 if ur:
816 uritems += ur
817 urfeeds += 1
818 if uritems == self.unread_count:
819 return
820 self.unread_count = uritems
821 if uritems:
822 self._tooltip.set_tip(self.tray, _("%d new items")%uritems)
823 self.tray.show_all()
824 else:
825 self.tray.hide()
826 return
828 def _tray_clicked(self, widget, event=None):
829 self._main_presenter.view.should_present()
830 if event and not (event.button == 1):
831 return
832 self._main_presenter.scroll_or_display_next_unread_item()
834 def _on_drag_data_received(self, widget, context, x, y, data, info, timestamp):
835 if data and data.format == 8:
836 url = data.data.split("\n")[0]
837 subscribe_show(url="%s" % url)
838 context.finish(True, False, timestamp)
839 else:
840 context.finish(False, False, timestamp)
841 return