Fixes (workarounds) in OPML parsing, more work on GUI...
[straw/fork.git] / straw / Application.py
blobb7c551ec822d18f0f817ea390b135e23bbd79571
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 from FeedListView import FeedsView, FeedsPresenter
23 from ItemList import ItemListPresenter, ItemListView
24 from ItemView import ItemView
25 from MainloopManager import MainloopManager
26 from OfflineToggle import OfflineToggle
27 from xml.sax import saxutils
28 import Config
29 import FeedManager
30 import ImageCache
31 import MVP
32 import PollManager
33 import error
34 import gnome, gconf
35 import gobject, gtk, gtk.glade
36 import helpers
37 import os, gettext, getopt, sys
38 import pygtk
39 import straw
40 import time
41 pygtk.require('2.0')
43 #from Find import FindPresenter, FindView
46 class StatusPresenter(MVP.BasicPresenter):
47 def _initialize(self):
48 self._mmgr = straw.get_status_manager()
49 self._mmgr.connect('changed', self._display)
51 def _display(self, *args):
52 cid = self._view.get_context_id("straw_main")
53 self._view.pop(cid)
54 self._view.push(cid, self._mmgr.read_message())
55 return
57 class ErrorPresenter:
58 def __init__(self, widget):
59 self._widget = widget
60 self._tooltips = gtk.Tooltips()
61 self._text = ''
62 #self._curr_feed = None
63 #self._curr_category = None
64 #fclist = FeedCategoryList.get_instance()
65 #fclist.connect('category-changed', self._category_changed)
67 def feedlist_selection_changed(self, selection, column):
68 errortexts = None
69 return
70 (model, pathlist) = selection.get_selected_rows()
71 iters = [model.get_iter(path) for path in pathlist]
72 if not iters: return
73 errorfeeds = []
74 nodes = [model.get_value(treeiter, column) for treeiter in iters]
75 try:
76 errorfeeds = [node.feed for node in nodes if node.feed and node.feed.error]
77 errortexts = [feed.error for feed in errorfeeds]
78 except TypeError, te:
79 print te
80 ### XXX display OPML Category error too
82 text = list()
83 if errorfeeds:
84 text.append(_("Error:"))
85 text += errortexts
87 if text:
88 t = "\n".join(text)
89 self._tooltips.set_tip(self._widget,t,t)
90 self._tooltips.enable()
91 self._widget.show()
92 else:
93 self._tooltips.disable()
94 self._widget.hide()
95 return
97 def hide(self):
98 self._widget.hide()
100 class MenuFeedPropsPresenter(MVP.BasicPresenter):
101 # FIXME
102 def set_sensitive(self, s):
103 self._view.set_sensitive(s)
104 return
106 class ToolbarView(MVP.WidgetView):
107 """ Widget: gtk.Toolbar"""
108 GCONF_DESKTOP_INTERFACE = "/desktop/gnome/interface"
110 def _initialize(self):
111 client = gconf.client_get_default()
112 if not client.dir_exists(self.GCONF_DESKTOP_INTERFACE):
113 return
114 client.add_dir(self.GCONF_DESKTOP_INTERFACE,
115 gconf.CLIENT_PRELOAD_NONE)
116 client.notify_add(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style",
117 self._toolbar_style_changed)
118 self._widget.set_tooltips(True)
120 def _toolbar_style_changed(self, client, notify_id, entry, *args):
121 value = entry.value.get_string()
122 self._presenter.style_changed(value)
124 def set_style(self, style):
125 self._widget.set_style(style)
127 def get_style(self):
128 client = gconf.client_get_default()
129 current_style = client.get_string(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style")
130 return current_style
132 class ToolbarPresenter(MVP.BasicPresenter):
133 STYLES = {'both':gtk.TOOLBAR_BOTH,
134 'text':gtk.TOOLBAR_TEXT,
135 'icons':gtk.TOOLBAR_ICONS,
136 'both-horiz':gtk.TOOLBAR_BOTH_HORIZ}
138 def _initialize(self):
139 style = self._view.get_style()
140 if style in self.STYLES.keys():
141 self._view.set_style(self.STYLES[style])
142 return
144 def style_changed(self, value):
145 if value in self.STYLES.keys():
146 self._view.set_style(self.STYLES[value])
147 return
149 class ApplicationPresenter(MVP.BasicPresenter):
150 def _initialize(self):
151 self._curr_category = None
152 self._curr_feed = None
153 self._curr_item = None
154 self._init_widgets()
155 self._init_presenters()
156 self._view.present()
158 def _init_widgets(self):
159 widget_tree = self._view.get_widget_tree()
160 self._itemlist_view_notebook = widget_tree.get_widget('mode_view_notebook')
161 self._feedlist_view_notebook = widget_tree.get_widget('left_mode_view_notebook')
163 item_view_container = widget_tree.get_widget('item_view_container')
164 item_list_container = widget_tree.get_widget('item_list_container')
166 parent_paned = item_view_container.get_parent()
167 parent_parent_widget = parent_paned.get_parent()
169 config = Config.get_instance()
171 child_list = []
172 layout = config.pane_layout
173 if layout == 'vertical':
174 new_paned = gtk.VPaned()
175 child_list.append(item_list_container)
176 child_list.append(item_view_container)
177 elif layout == 'horizontal':
178 new_paned = gtk.HPaned()
179 child_list.append(item_view_container)
180 child_list.append(item_list_container)
181 else: # Use vertical layout as default
182 new_paned = gtk.VPaned()
183 child_list.append(item_list_container)
184 child_list.append(item_view_container)
186 for child in child_list:
187 child.reparent(new_paned)
189 parent_parent_widget.remove(parent_paned)
190 parent_parent_widget.add(new_paned)
192 new_paned.show()
194 def _init_presenters(self):
195 widget_tree = self._view.get_widget_tree()
196 toolbar_presenter = ToolbarPresenter(view=ToolbarView(widget_tree.get_widget('toolbar_default')))
197 self._error_presenter = ErrorPresenter(widget_tree.get_widget('statusbar_error_indicator'))
199 self._item_view = ItemView(widget_tree.get_widget('item_view_container'))
201 view = ItemListView(widget_tree.get_widget('item_selection_treeview'))
202 self._itemlist_presenter = ItemListPresenter(view=view)
203 view.add_selection_changed_listener(self._item_view)
205 view = FeedsView(widget_tree.get_widget('feed_selection_treeview'))
206 self._feed_list_presenter = FeedsPresenter(view=view)
207 view.add_selection_changed_listener(self._itemlist_presenter)
208 view.add_selection_changed_listener(self._error_presenter)
210 self._offline_presenter = OfflineToggle(widget_tree.get_widget('offline_toggle'))
212 self._status_presenter = StatusPresenter(view = widget_tree.get_widget("main_statusbar"))
213 self._menufp_presenter = MenuFeedPropsPresenter( view = widget_tree.get_widget('feed_information'))
214 self._menufp_presenter.set_sensitive(False)
215 # self._find_presenter = FindPresenter(view=FindView(widget_tree.get_widget("find_vbox")))
216 return
218 def copy_itemview_text_selection(self):
219 helpers.set_clipboard_text(self._item_view.get_selected_text())
221 def check_allocation(self, widget, event):
222 config = Config.get_instance()
223 def check_size((width, height, widget)):
224 if width == widget.allocation.width and height == widget.allocation.height:
225 config.main_window_size = (width, height)
226 if event.width != widget.allocation.width or event.height != widget.allocation.height:
227 gobject.timeout_add(1000, check_size, (
228 (event.width, event.height, widget)))
230 def check_main_pane_position(self, widget):
231 config = Config.get_instance()
232 def check_position((position, widget)):
233 if position == widget.get_position():
234 config.main_pane_position = position
235 pos = widget.get_position()
236 if pos != config.main_pane_position:
237 gobject.timeout_add(1000, check_position, (pos, widget))
239 def check_sub_pane_position(self, widget):
240 config = Config.get_instance()
241 def check_position((position, widget)):
242 if position == widget.get_position():
243 config.sub_pane_position = position
244 pos = widget.get_position()
245 if pos != config.sub_pane_position:
246 gobject.timeout_add(1000, check_position, (pos, widget))
248 def credits(self):
249 return helpers.credits()
251 def poll_all(self):
252 if self._warn_if_offline():
253 FeedManager.update_all_feeds({ "task-start": [ self._on_feed_poll_started ],
254 "task-done": [ self._on_feed_poll_done ] })
256 def _on_feed_poll_started(self, handler, feed):
257 pass#print feed.id
259 def _on_feed_poll_done(self, handler, data):
260 pass
262 def poll_current_category(self):
263 if self._warn_if_offline():
264 self._poll_categories([self._curr_category])
265 return
267 def poll_current_feed(self):
268 if self._warn_if_offline():
269 pm = PollManager.get_instance()
270 pm.poll([self._curr_feed])
271 return
273 def mark_feed_as_read(self):
274 self._curr_feed.mark_all_read()
276 def mark_all_as_read(self):
277 print "TODO mark_all_as_read"
279 def remove_selected_feed(self):
280 # self._feed_list_presenter.remove_selected_feed()
281 pass
283 def show_search(self):
284 # self._find_presenter.item_list.signal_connect(Event.ItemSelectionChangedSignal,
285 # self._item_view.item_selection_changed)
286 self._item_view.display_empty_search()
287 self._itemlist_view_notebook.set_current_page(1)
288 self._feedlist_view_notebook.set_current_page(1)
289 self._feedinfo_presenter.hide()
291 def hide_search(self):
292 # self._find_presenter.clear()
293 self._itemlist_view_notebook.set_current_page(0)
294 self._feedlist_view_notebook.set_current_page(0)
295 self._feedinfo_presenter.show()
296 # self._find_presenter.item_list.signal_disconnect(Event.ItemSelectionChangedSignal,
297 # self._item_view.item_selection_changed)
300 def _poll_categories(self, fclist):
301 pm = PollManager.get_instance()
302 pm.poll_categories(fclist)
303 return
305 def _warn_if_offline(self):
306 config = Config.get_instance()
307 will_poll = False
308 if config.offline:
309 response = self._view.show_offline_dialog()
310 if response == gtk.RESPONSE_OK:
311 config.offline = not config.offline
312 will_poll = True
313 else:
314 will_poll = True
315 return will_poll
317 def display_previous_feed(self, item = None):
319 Displays the feed before the current selected feed
321 self._feed_list_presenter.select_previous_feed()
323 def display_next_feed(self, item=None):
325 Displays the feed after the current selected feed
327 self._feed_list_presenter.select_next_feed()
328 return
330 def display_next_unread_feed(self):
332 Displays the next feed with an unread item
334 self._feed_list_presenter.select_next_feed(with_unread=True)
335 pass
337 def display_previous_item(self, item=None):
339 Displays the item before the current selected item. If the item is the
340 first item, scrolls to the previous feed
342 is_prev = self._itemlist_presenter.select_previous_item()
343 if not is_prev:
344 # TODO HACK - implement select_previous_feed(select_last=True) ...
345 # ... to select previous feed's last item
346 self._feed_list_presenter.select_previous_feed()
347 self._itemlist_presenter.select_last_item()
348 pass
349 return
351 def display_next_item(self, item=None):
353 Displays the item after the current selected item. If the item is the
354 last item, selectes the next feed. If the current feed is the last
355 feed in the list, it goes back and selects the first feed
357 is_next = self._itemlist_presenter.select_next_item()
358 if not is_next:
359 is_next_feed = self._feed_list_presenter.select_next_feed()
360 if not is_next_feed:
361 self._feed_list_presenter.select_firsteed_feed()
362 return
364 def scroll_or_display_next_unread_item(self, item=None):
365 has_unread_item = False
366 if not self._item_view.scroll_down():
367 has_unread_item = self._itemlist_presenter.select_next_unread_item()
368 if not has_unread_item:
369 self._feed_list_presenter.select_next_feed(with_unread=True)
370 return
372 def show_preferences_dialog(self, parent):
373 straw.preferences_show()
375 def show_feed_properties(self, parent):
376 straw.feed_properties_show(self._curr_feed)
378 def quit(self):
379 gtk.main_quit()
381 def _setup_filechooser_dialog(self, title, action, extra_widget_title):
383 Setup the file chooser dialog. This includes an extra widget (a combobox)
384 to include the categories to import or export
386 dialog = gtk.FileChooserDialog(title, action=action,
387 buttons=(gtk.STOCK_CANCEL,
388 gtk.RESPONSE_CANCEL,
389 gtk.STOCK_OK, gtk.RESPONSE_OK))
390 category_list = []
392 for category in FeedManager.categories():
393 category_list.append(category)
395 print category_list
397 model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
398 combobox = gtk.ComboBox(model)
399 celltitle = gtk.CellRendererText()
400 combobox.pack_start(celltitle,False)
401 combobox.add_attribute(celltitle, 'text', 0)
403 for category in category_list:
404 it = model.append()
405 model.set(it, 0, category.name, 1, category)
407 combobox.set_active(0)
408 label = gtk.Label(extra_widget_title)
409 label.set_alignment(1.0,0.5)
410 hbox = gtk.HBox(spacing=6)
411 hbox.pack_start(label,True,True,0)
412 hbox.pack_end(combobox,False,False,0)
413 hbox.show_all()
415 dialog.set_extra_widget(hbox)
416 return (dialog, combobox)
418 def import_subscriptions(self, parent):
419 (dialog,combobox) = self._setup_filechooser_dialog(_("Import Subscriptions"),
420 gtk.FILE_CHOOSER_ACTION_OPEN,
421 _("Add new subscriptions in:"))
422 ffilter = gtk.FileFilter()
423 ffilter.set_name(_("OPML Files Only"))
424 ffilter.add_pattern("*.xml")
425 ffilter.add_pattern("*.opml")
426 dialog.add_filter(ffilter)
427 dialog.set_transient_for(parent)
428 response = dialog.run()
429 if response == gtk.RESPONSE_OK:
430 filename = dialog.get_filename()
431 model = combobox.get_model()
432 category = model[combobox.get_active()][1]
433 dialog.hide()
434 FeedManager.import_opml(filename, category)
435 dialog.destroy()
437 def export_subscriptions(parent):
438 (dialog,combobox) = _setup_filechooser_dialog(_("Export Subscriptions"),
439 gtk.FILE_CHOOSER_ACTION_SAVE,
440 _("Select category to export:"))
441 def selection_changed(widget, dialog):
442 model = widget.get_model()
443 category = model[widget.get_active()][1]
444 dialog.set_current_name("Straw-%s.xml" % category.title)
446 combobox.connect('changed',
447 selection_changed,
448 dialog)
449 selection_changed(combobox, dialog)
450 dialog.set_transient_for(parent)
451 response = dialog.run()
452 if response == gtk.RESPONSE_OK:
453 filename = dialog.get_filename()
454 model = combobox.get_model()
455 cat = model[combobox.get_active()][1]
456 opml.export(cat.title, cat.feeds, filename)
457 dialog.destroy()
459 def subscribe(self):
460 straw.subscribe_show(parent=self.view._widget)
463 class ApplicationView(MVP.WidgetView):
465 Widget: straw_main
467 def _initialize(self):
468 self._config = Config.get_instance()
469 self._initialize_dnd()
470 self._initialize_window_updater()
471 self._create_unmodified_accelerator_group()
472 self._attach_unmodified_accelerator_group()
473 self._initialize_window()
474 self._find_toggled = False
476 def _initialize_window(self):
477 widget_tree = gtk.glade.get_widget_tree(self._widget)
478 if self._config.window_maximized:
479 self._widget.maximize()
480 else:
481 # we use resize here since configure-event seems to
482 # overwrite the default size if we use set_default_size.
483 self._widget.resize(*self._config.main_window_size)
484 mmp = widget_tree.get_widget('main_main_pane')
485 msp = widget_tree.get_widget('main_sub_pane')
486 mmp.set_position(self._config.main_pane_position)
487 msp.set_position(self._config.sub_pane_position)
489 def _initialize_dnd(self):
490 self._widget.drag_dest_set(
491 gtk.DEST_DEFAULT_ALL,
492 [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1),
493 ('x-url/http', 0, 2)],
494 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
495 return
497 def _initialize_window_updater(self):
498 #feedlist.signal_connect(Event.AllItemsReadSignal,
499 # lambda signal: self._update_title(feedlist))
500 #feedlist.signal_connect(Event.ItemReadSignal,
501 # lambda signal: self._update_title(feedlist))
502 #feedlist.signal_connect(Event.ItemsAddedSignal,
503 # lambda signal: self._update_title(feedlist))
504 #feedlist.signal_connect(Event.FeedsChangedSignal,
505 # lambda signal: self._update_title(feedlist))
506 pass
508 def _update_title(self, flist):
509 uritems = urfeeds = 0
510 sfeeds = "feeds"
511 listfeeds = flist.flatten_list()
512 for ur in [f.number_of_unread for f in listfeeds]:
513 if ur:
514 uritems += ur
515 urfeeds += 1
516 else:
517 urfeeds = len(listfeeds)
518 if urfeeds < 2:
519 sfeeds = "feed"
520 item_feed_map = {'uritems': uritems,
521 'urfeeds': urfeeds,
522 'fstring' : sfeeds}
523 title = _('%(uritems)d unread in %(urfeeds)d %(fstring)s') % item_feed_map
524 self._widget.set_title( title + " - %s" % straw.PACKAGE)
525 return
527 # We have a separate accelerator group for the unmodified and
528 # shifted accelerators, that is, stuff like space, N, P, etc. This
529 # is so that we can have the find pane work correctly
530 def _create_unmodified_accelerator_group(self):
531 xml = gtk.glade.get_widget_tree(self._widget)
532 agroup = gtk.AccelGroup()
533 accels = (('feed_mark_as_read', 'R', gtk.gdk.SHIFT_MASK),
534 ('feed_mark_all_as_read', 'A', gtk.gdk.SHIFT_MASK),
535 ('next_item', 'N', gtk.gdk.SHIFT_MASK),
536 ('next_unread_feed', ' ', 0),
537 ('previous_item', 'P', gtk.gdk.SHIFT_MASK))
538 for widget_name, key, mask in accels:
539 widget = xml.get_widget(widget_name)
540 widget.add_accelerator("activate", agroup, ord(key), mask,
541 gtk.ACCEL_VISIBLE)
542 self._unmodified_accelerator_group = agroup
544 def _on_category_add_activate(self, *args):
545 print "add!"
547 def _attach_unmodified_accelerator_group(self):
548 self._widget.add_accel_group(self._unmodified_accelerator_group)
550 def _detach_unmodified_accelerator_group(self):
551 self._widget.remove_accel_group(self._unmodified_accelerator_group)
553 def _on_straw_main_destroy_event(self, *args):
554 return self._presenter.quit()
556 def _on_straw_main_delete_event(self, *args):
557 return self._presenter.quit()
559 def _on_straw_main_configure_event(self, widget, event, *args):
560 if widget.window.get_state() is not gtk.gdk.WINDOW_STATE_MAXIMIZED:
561 self._config.window_maximized = False
562 self._presenter.check_allocation(widget, event)
563 else:
564 self._config.window_maximized = True
565 return
567 def _on_main_main_pane_size_allocate(self, widget, *args):
568 self._presenter.check_main_pane_position(widget)
570 def _on_main_sub_pane_size_allocate(self, widget, *args):
571 self._presenter.check_sub_pane_position(widget)
573 # Actions menu
574 def _on_feed_subscribe_activate(self, *args):
575 self._presenter.subscribe()
577 def _on_feed_unsubscribe_activate(self, *args):
578 self._presenter.remove_selected_feed()
580 def _on_subscription_import_activate(self, *args):
581 self._presenter.import_subscriptions(self._widget)
583 def _on_subscription_export_activate(self, *args):
584 self._presenter.export_subscriptions(self._widget)
586 def _on_feed_mark_as_read_activate(self, *args):
587 self._presenter.mark_feed_as_read()
589 def _on_feed_mark_all_as_read_activate(self, *args):
590 self._presenter.mark_all_as_read()
592 def _on_feed_refresh_selected_activate(self, *args):
593 self._presenter.poll_current_feed()
595 def _on_subscription_refresh_activate(self, *args):
596 #print self._widget.get_children()[0].set_stock_id(gtk.STOCK_STOP)
597 self._presenter.poll_all()
599 def _on_quit_activate(self, *args):
600 return self._presenter.quit()
602 # Edit menu
603 def _on_copy_activate(self, *args):
604 self._presenter.copy_itemview_text_selection()
607 def _on_find_activate(self, widget, *args):
608 xml = gtk.glade.get_widget_tree(self._widget)
609 menu_find = xml.get_widget('menu_find')
610 accel_label = menu_find.get_child()
612 if not self._find_toggled:
613 self._presenter.show_search()
614 self._detach_unmodified_accelerator_group()
615 self._find_toggled = True
617 # save the "Find..." stock text for later recovery
618 self._old_label_text = accel_label.get_text()
619 accel_label.set_text(_('Return to feed list...'))
621 else:
622 self._presenter.hide_search()
623 self._attach_unmodified_accelerator_group()
624 self._find_toggled = False
625 accel_label.set_text(self._old_label_text)
627 def _on_preferences_activate(self, *args):
628 self._presenter.show_preferences_dialog(self._widget)
630 def _on_feed_information_activate(self, *args):
631 self._presenter.show_feed_properties(self._widget)
633 # View menu
634 def _on_previous_item_activate(self, *args):
635 self._presenter.display_previous_item()
637 def _on_next_item_activate(self, *args):
638 self._presenter.display_next_item()
640 def _on_next_feed_activate(self, *args):
641 self._presenter.display_next_feed()
643 def _on_previous_feed_activate(self, *args):
644 self._presenter.display_previous_feed()
646 def _on_next_unread_feed_activate(self, *args):
647 self._presenter.display_next_unread_feed()
649 def _on_scroll_unread_items_activate(self, *args):
650 self._presenter.scroll_or_display_next_unread_item()
652 # Help menu
654 def _on_report_problem_activate(self, menuitem, *args):
655 helpers.url_show("http://bugzilla.gnome.org/simple-bug-guide.cgi?product=straw")
657 def _on_about_activate(self, menuitem, *args):
658 widget = self._presenter.credits()
659 widget.show()
661 def _on_straw_main_drag_data_received(self, w, context,
662 x, y, data, info, time):
663 if data and data.format == 8:
664 url = data.data.split("\n")[0]
665 straw.subscribe_show("%s" % url, self._widget)
666 context.finish(True, False, time)
667 else:
668 context.finish(False, False, time)
670 # FIXME
671 def show_offline_dialog(self):
672 return helpers.report_offline_status(self._widget)
674 def get_widget_tree(self):
675 return gtk.glade.get_widget_tree(self._widget)
677 def present(self):
678 self._widget.present()
680 def should_present(self):
681 if self._widget.window.get_state() is not gtk.gdk.WINDOW_STATE_WITHDRAWN:
682 self._widget.hide()
683 else:
684 self._widget.present()
687 class Application:
688 def __init__(self):
689 gnome.program_init(straw.PACKAGE, straw.VERSION)
691 error.setup_log()
692 # initialize threading and environment
693 gobject.threads_init()
694 config = Config.get_instance()
695 config.reload_css = os.getenv('STRAW_RELOAD_CSS') is not None
697 # build tray status icon
698 image = gtk.Image()
699 iconfile = os.path.normpath(straw.STRAW_DATA_DIR + "/straw.png")
700 pixbuf = gtk.gdk.pixbuf_new_from_file(iconfile)
701 scaled_buf = pixbuf.scale_simple(16,16,gtk.gdk.INTERP_BILINEAR)
702 image.set_from_pixbuf(scaled_buf)
703 try:
704 self.tray = gtk.status_icon_new_from_pixbuf(scaled_buf)
705 self.tray.connect('activate', self._tray_clicked)
706 except AttributeError:
707 import egg
708 import egg.trayicon
709 self.tray = egg.trayicon.TrayIcon(straw.PACKAGE);
710 self._eventbox = gtk.EventBox()
711 self.tray.add(self._eventbox)
712 self._eventbox.connect('button_press_event', self._tray_clicked)
713 self._eventbox.connect("drag-data-received", self._on_drag_data_received)
714 self._eventbox.drag_dest_set(
715 gtk.DEST_DEFAULT_ALL,
716 [('_NETSCAPE_URL', 0, 0),('text/uri-list ', 0, 1),('x-url/http', 0, 2)],
717 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
718 self._eventbox.add(image)
719 self.tray.show_all()
721 self._tooltip = gtk.Tooltips()
722 self.unread_count = 0
723 # end build tray status icon
725 FeedManager.setup(storage_path = "test.db")
726 FeedManager.init()
728 #FeedManager.import_opml(os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml"))
730 #if config.first_time:
731 # filepath = os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml")
732 # feeds.import_opml(filepath)
734 #ImageCache.initialize()
736 #import feedfinder
737 #print feedfinder.feeds("http://eclipse.org", True)
739 xml = gtk.glade.XML(os.path.join(straw.STRAW_DATA_DIR,'straw.glade'), "straw_main", gettext.textdomain())
740 window = xml.get_widget('straw_main')
741 self._main_presenter = ApplicationPresenter(view = ApplicationView(window))
742 self._main_presenter.view.present()
744 #PollManager.get_instance().start_polling_loop()
745 # set the default icon for the windows
746 iconfile = os.path.join(straw.STRAW_DATA_DIR,"straw.png")
747 gtk.window_set_default_icon(gtk.gdk.pixbuf_new_from_file(iconfile))
749 straw.start_services()
751 def mainloop(self):
752 gtk.gdk.threads_init()
753 gtk.gdk.threads_enter()
754 gtk.main()
755 gtk.gdk.threads_leave()
757 def _update(self,flist):
758 uritems = urfeeds = 0
759 for ur in [f.number_of_unread for f in flist.flatten_list()]:
760 if ur:
761 uritems += ur
762 urfeeds += 1
763 if uritems == self.unread_count:
764 return
765 self.unread_count = uritems
766 if uritems:
767 self._tooltip.set_tip(self.tray, _("%d new items")%uritems)
768 self.tray.show_all()
769 else:
770 self.tray.hide()
771 return
773 def _tray_clicked(self, widget, event=None):
774 self._main_presenter.view.should_present()
775 if event and not (event.button == 1):
776 return
777 self._main_presenter.scroll_or_display_next_unread_item()
779 def _on_drag_data_received(self, widget, context, x, y, data, info, timestamp):
780 if data and data.format == 8:
781 url = data.data.split("\n")[0]
782 straw.subscribe_show(url="%s" % url)
783 context.finish(True, False, timestamp)
784 else:
785 context.finish(False, False, timestamp)
786 return