Fixed OPML export.
[straw.git] / straw / Application.py
blob73901d164a2572d0d23dd2fb8320296b2cade670
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 error
33 import gnome, gconf
34 import gobject, gtk, gtk.glade
35 import helpers
36 import os, gettext, getopt, sys
37 import pygtk
38 import straw
39 import time
40 pygtk.require('2.0')
42 #from Find import FindPresenter, FindView
45 class StatusPresenter(MVP.BasicPresenter):
46 def _initialize(self):
47 self._mmgr = straw.get_status_manager()
48 self._mmgr.connect('changed', self._display)
50 def _display(self, *args):
51 cid = self._view.get_context_id("straw_main")
52 self._view.pop(cid)
53 self._view.push(cid, self._mmgr.read_message())
54 return
56 class ErrorPresenter:
57 def __init__(self, widget):
58 self._widget = widget
59 self._tooltips = gtk.Tooltips()
60 self._text = ''
61 #self._curr_feed = None
62 #self._curr_category = None
63 #fclist = FeedCategoryList.get_instance()
64 #fclist.connect('category-changed', self._category_changed)
66 def feedlist_selection_changed(self, selection, column):
67 errortexts = None
68 return
69 (model, pathlist) = selection.get_selected_rows()
70 iters = [model.get_iter(path) for path in pathlist]
71 if not iters: return
72 errorfeeds = []
73 nodes = [model.get_value(treeiter, column) for treeiter in iters]
74 try:
75 errorfeeds = [node.feed for node in nodes if node.feed and node.feed.error]
76 errortexts = [feed.error for feed in errorfeeds]
77 except TypeError, te:
78 print te
79 ### XXX display OPML Category error too
81 text = list()
82 if errorfeeds:
83 text.append(_("Error:"))
84 text += errortexts
86 if text:
87 t = "\n".join(text)
88 self._tooltips.set_tip(self._widget,t,t)
89 self._tooltips.enable()
90 self._widget.show()
91 else:
92 self._tooltips.disable()
93 self._widget.hide()
94 return
96 def hide(self):
97 self._widget.hide()
99 class MenuFeedPropsPresenter(MVP.BasicPresenter):
100 # FIXME
101 def set_sensitive(self, s):
102 self._view.set_sensitive(s)
103 return
105 class ToolbarView(MVP.WidgetView):
106 """ Widget: gtk.Toolbar"""
107 GCONF_DESKTOP_INTERFACE = "/desktop/gnome/interface"
109 def _initialize(self):
110 client = gconf.client_get_default()
111 if not client.dir_exists(self.GCONF_DESKTOP_INTERFACE):
112 return
113 client.add_dir(self.GCONF_DESKTOP_INTERFACE,
114 gconf.CLIENT_PRELOAD_NONE)
115 client.notify_add(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style",
116 self._toolbar_style_changed)
117 self._widget.set_tooltips(True)
119 def _toolbar_style_changed(self, client, notify_id, entry, *args):
120 value = entry.value.get_string()
121 self._presenter.style_changed(value)
123 def set_style(self, style):
124 self._widget.set_style(style)
126 def get_style(self):
127 client = gconf.client_get_default()
128 current_style = client.get_string(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style")
129 return current_style
131 class ToolbarPresenter(MVP.BasicPresenter):
132 STYLES = {'both':gtk.TOOLBAR_BOTH,
133 'text':gtk.TOOLBAR_TEXT,
134 'icons':gtk.TOOLBAR_ICONS,
135 'both-horiz':gtk.TOOLBAR_BOTH_HORIZ}
137 def _initialize(self):
138 style = self._view.get_style()
139 if style in self.STYLES.keys():
140 self._view.set_style(self.STYLES[style])
141 return
143 def style_changed(self, value):
144 if value in self.STYLES.keys():
145 self._view.set_style(self.STYLES[value])
146 return
148 class ApplicationPresenter(MVP.BasicPresenter):
149 def _initialize(self):
150 self._curr_category = None
151 self._curr_feed = None
152 self._curr_item = None
153 self._init_widgets()
154 self._init_presenters()
155 self._view.present()
157 def _init_widgets(self):
158 widget_tree = self._view.get_widget_tree()
159 self._itemlist_view_notebook = widget_tree.get_widget('mode_view_notebook')
160 self._feedlist_view_notebook = widget_tree.get_widget('left_mode_view_notebook')
162 item_view_container = widget_tree.get_widget('item_view_container')
163 item_list_container = widget_tree.get_widget('item_list_container')
165 parent_paned = item_view_container.get_parent()
166 parent_parent_widget = parent_paned.get_parent()
168 config = Config.get_instance()
170 child_list = []
171 layout = config.pane_layout
172 if layout == 'vertical':
173 new_paned = gtk.VPaned()
174 child_list.append(item_list_container)
175 child_list.append(item_view_container)
176 elif layout == 'horizontal':
177 new_paned = gtk.HPaned()
178 child_list.append(item_view_container)
179 child_list.append(item_list_container)
180 else: # Use vertical layout as default
181 new_paned = gtk.VPaned()
182 child_list.append(item_list_container)
183 child_list.append(item_view_container)
185 for child in child_list:
186 child.reparent(new_paned)
188 parent_parent_widget.remove(parent_paned)
189 parent_parent_widget.add(new_paned)
191 new_paned.show()
193 def _init_presenters(self):
194 widget_tree = self._view.get_widget_tree()
195 toolbar_presenter = ToolbarPresenter(view=ToolbarView(widget_tree.get_widget('toolbar_default')))
196 self._error_presenter = ErrorPresenter(widget_tree.get_widget('statusbar_error_indicator'))
198 self._item_view = ItemView(widget_tree.get_widget('item_view_container'))
200 view = ItemListView(widget_tree.get_widget('item_selection_treeview'))
201 self._itemlist_presenter = ItemListPresenter(view=view)
202 view.add_selection_changed_listener(self._item_view)
204 view = FeedsView(widget_tree.get_widget('feed_selection_treeview'))
205 self._feed_list_presenter = FeedsPresenter(view=view)
206 view.add_selection_changed_listener(self._itemlist_presenter)
207 view.add_selection_changed_listener(self._error_presenter)
209 self._offline_presenter = OfflineToggle(widget_tree.get_widget('offline_toggle'))
211 self._status_presenter = StatusPresenter(view = widget_tree.get_widget("main_statusbar"))
212 self._menufp_presenter = MenuFeedPropsPresenter( view = widget_tree.get_widget('feed_information'))
213 self._menufp_presenter.set_sensitive(False)
214 # self._find_presenter = FindPresenter(view=FindView(widget_tree.get_widget("find_vbox")))
215 return
217 def add_category(self):
218 self._feed_list_presenter.add_category()
220 def copy_itemview_text_selection(self):
221 helpers.set_clipboard_text(self._item_view.get_selected_text())
223 def check_allocation(self, widget, event):
224 config = Config.get_instance()
225 def check_size((width, height, widget)):
226 if width == widget.allocation.width and height == widget.allocation.height:
227 config.main_window_size = (width, height)
228 if event.width != widget.allocation.width or event.height != widget.allocation.height:
229 gobject.timeout_add(1000, check_size, (
230 (event.width, event.height, widget)))
232 def check_main_pane_position(self, widget):
233 config = Config.get_instance()
234 def check_position((position, widget)):
235 if position == widget.get_position():
236 config.main_pane_position = position
237 pos = widget.get_position()
238 if pos != config.main_pane_position:
239 gobject.timeout_add(1000, check_position, (pos, widget))
241 def check_sub_pane_position(self, widget):
242 config = Config.get_instance()
243 def check_position((position, widget)):
244 if position == widget.get_position():
245 config.sub_pane_position = position
246 pos = widget.get_position()
247 if pos != config.sub_pane_position:
248 gobject.timeout_add(1000, check_position, (pos, widget))
250 def credits(self):
251 return helpers.credits()
253 def poll_all(self):
254 if self._warn_if_offline():
255 FeedManager.update_all_feeds({ "task-start": [ self._on_feed_poll_started ],
256 "task-done": [ self._on_feed_poll_done ] })
258 def _on_feed_poll_started(self, handler, feed):
259 pass#print feed.id
261 def _on_feed_poll_done(self, handler, data):
262 pass
264 def poll_current_category(self):
265 if self._warn_if_offline():
266 self._poll_categories([self._curr_category])
268 def poll_current_feed(self):
269 if self._warn_if_offline():
270 print self._curr_feed
271 pm = PollManager.get_instance()
272 pm.poll([self._curr_feed])
274 def mark_feed_as_read(self):
275 self._curr_feed.mark_all_read()
277 def mark_all_as_read(self):
278 print "TODO mark_all_as_read"
280 def remove_selected_feed(self):
281 # self._feed_list_presenter.remove_selected_feed()
282 pass
284 def show_search(self):
285 # self._find_presenter.item_list.signal_connect(Event.ItemSelectionChangedSignal,
286 # self._item_view.item_selection_changed)
287 self._item_view.display_empty_search()
288 self._itemlist_view_notebook.set_current_page(1)
289 self._feedlist_view_notebook.set_current_page(1)
290 self._feedinfo_presenter.hide()
292 def hide_search(self):
293 # self._find_presenter.clear()
294 self._itemlist_view_notebook.set_current_page(0)
295 self._feedlist_view_notebook.set_current_page(0)
296 self._feedinfo_presenter.show()
297 # self._find_presenter.item_list.signal_disconnect(Event.ItemSelectionChangedSignal,
298 # self._item_view.item_selection_changed)
301 def _poll_categories(self, fclist):
302 pm = PollManager.get_instance()
303 pm.poll_categories(fclist)
304 return
306 def _warn_if_offline(self):
307 config = Config.get_instance()
308 will_poll = False
309 if config.offline:
310 response = self._view.show_offline_dialog()
311 if response == gtk.RESPONSE_OK:
312 config.offline = not config.offline
313 will_poll = True
314 else:
315 will_poll = True
316 return will_poll
318 def display_previous_feed(self, item = None):
320 Displays the feed before the current selected feed
322 self._feed_list_presenter.select_previous_feed()
324 def display_next_feed(self, item=None):
326 Displays the feed after the current selected feed
328 self._feed_list_presenter.select_next_feed()
329 return
331 def display_next_unread_feed(self):
333 Displays the next feed with an unread item
335 self._feed_list_presenter.select_next_feed(with_unread=True)
336 pass
338 def display_previous_item(self, item=None):
340 Displays the item before the current selected item. If the item is the
341 first item, scrolls to the previous feed
343 is_prev = self._itemlist_presenter.select_previous_item()
344 if not is_prev:
345 # TODO HACK - implement select_previous_feed(select_last=True) ...
346 # ... to select previous feed's last item
347 self._feed_list_presenter.select_previous_feed()
348 self._itemlist_presenter.select_last_item()
349 pass
350 return
352 def display_next_item(self, item=None):
354 Displays the item after the current selected item. If the item is the
355 last item, selectes the next feed. If the current feed is the last
356 feed in the list, it goes back and selects the first feed
358 is_next = self._itemlist_presenter.select_next_item()
359 if not is_next:
360 is_next_feed = self._feed_list_presenter.select_next_feed()
361 if not is_next_feed:
362 self._feed_list_presenter.select_firsteed_feed()
363 return
365 def scroll_or_display_next_unread_item(self, item=None):
366 has_unread_item = False
367 if not self._item_view.scroll_down():
368 has_unread_item = self._itemlist_presenter.select_next_unread_item()
369 if not has_unread_item:
370 self._feed_list_presenter.select_next_feed(with_unread=True)
371 return
373 def show_preferences_dialog(self, parent):
374 straw.preferences_show()
376 def show_feed_properties(self, parent):
377 straw.feed_properties_show(self._curr_feed)
379 def quit(self):
380 gtk.main_quit()
382 def _setup_filechooser_dialog(self, title, action, extra_widget_title):
384 Setup the file chooser dialog. This includes an extra widget (a combobox)
385 to include the categories to import or export
387 dialog = gtk.FileChooserDialog(title, action=action,
388 buttons=(gtk.STOCK_CANCEL,
389 gtk.RESPONSE_CANCEL,
390 gtk.STOCK_OK, gtk.RESPONSE_OK))
391 category_list = []
393 for category in FeedManager.categories():
394 category_list.append(category)
396 model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
397 combobox = gtk.ComboBox(model)
398 celltitle = gtk.CellRendererText()
399 combobox.pack_start(celltitle,False)
400 combobox.add_attribute(celltitle, 'text', 0)
402 for category in category_list:
403 it = model.append()
404 model.set(it, 0, category.name, 1, category)
406 combobox.set_active(0)
407 label = gtk.Label(extra_widget_title)
408 label.set_alignment(1.0,0.5)
409 hbox = gtk.HBox(spacing=6)
410 hbox.pack_start(label,True,True,0)
411 hbox.pack_end(combobox,False,False,0)
412 hbox.show_all()
414 dialog.set_extra_widget(hbox)
415 return (dialog, combobox)
417 def import_subscriptions(self, parent):
418 (dialog,combobox) = self._setup_filechooser_dialog(_("Import Subscriptions"),
419 gtk.FILE_CHOOSER_ACTION_OPEN,
420 _("Add new subscriptions in:"))
421 ffilter = gtk.FileFilter()
422 ffilter.set_name(_("OPML Files Only"))
423 ffilter.add_pattern("*.xml")
424 ffilter.add_pattern("*.opml")
425 dialog.add_filter(ffilter)
426 dialog.set_transient_for(parent)
427 response = dialog.run()
428 if response == gtk.RESPONSE_OK:
429 filename = dialog.get_filename()
430 model = combobox.get_model()
431 category = model[combobox.get_active()][1]
432 dialog.hide()
433 FeedManager.import_opml(filename, category)
434 dialog.destroy()
436 def export_subscriptions(self, parent):
437 (dialog,combobox) = self._setup_filechooser_dialog(_("Export Subscriptions"),
438 gtk.FILE_CHOOSER_ACTION_SAVE,
439 _("Select category to export:"))
440 def selection_changed(widget, dialog):
441 model = widget.get_model()
442 category = model[widget.get_active()][1]
443 dialog.set_current_name("Straw-%s.xml" % category.name)
445 combobox.connect('changed', selection_changed, dialog)
446 selection_changed(combobox, dialog)
447 dialog.set_transient_for(parent)
448 response = dialog.run()
450 if response == gtk.RESPONSE_OK:
451 filename = dialog.get_filename()
452 model = combobox.get_model()
453 root_category = model[combobox.get_active()][1]
454 FeedManager.export_opml(root_category.id, filename)
456 dialog.destroy()
458 def subscribe(self):
459 straw.subscribe_show(parent=self.view._widget)
461 class ApplicationView(MVP.WidgetView):
463 Widget: straw_main
465 def _initialize(self):
466 self._config = Config.get_instance()
467 self._initialize_dnd()
468 self._initialize_window_updater()
469 self._create_unmodified_accelerator_group()
470 self._attach_unmodified_accelerator_group()
471 self._initialize_window()
472 self._find_toggled = False
474 def _initialize_window(self):
475 widget_tree = gtk.glade.get_widget_tree(self._widget)
476 if self._config.window_maximized:
477 self._widget.maximize()
478 else:
479 # we use resize here since configure-event seems to
480 # overwrite the default size if we use set_default_size.
481 self._widget.resize(*self._config.main_window_size)
482 mmp = widget_tree.get_widget('main_main_pane')
483 msp = widget_tree.get_widget('main_sub_pane')
484 mmp.set_position(self._config.main_pane_position)
485 msp.set_position(self._config.sub_pane_position)
487 def _initialize_dnd(self):
488 self._widget.drag_dest_set(
489 gtk.DEST_DEFAULT_ALL,
490 [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1),
491 ('x-url/http', 0, 2)],
492 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
493 return
495 def _initialize_window_updater(self):
496 #feedlist.signal_connect(Event.AllItemsReadSignal,
497 # lambda signal: self._update_title(feedlist))
498 #feedlist.signal_connect(Event.ItemReadSignal,
499 # lambda signal: self._update_title(feedlist))
500 #feedlist.signal_connect(Event.ItemsAddedSignal,
501 # lambda signal: self._update_title(feedlist))
502 #feedlist.signal_connect(Event.FeedsChangedSignal,
503 # lambda signal: self._update_title(feedlist))
504 pass
506 def _update_title(self, flist):
507 uritems = urfeeds = 0
508 sfeeds = "feeds"
509 listfeeds = flist.flatten_list()
510 for ur in [f.number_of_unread for f in listfeeds]:
511 if ur:
512 uritems += ur
513 urfeeds += 1
514 else:
515 urfeeds = len(listfeeds)
516 if urfeeds < 2:
517 sfeeds = "feed"
518 item_feed_map = {'uritems': uritems,
519 'urfeeds': urfeeds,
520 'fstring' : sfeeds}
521 title = _('%(uritems)d unread in %(urfeeds)d %(fstring)s') % item_feed_map
522 self._widget.set_title( title + " - %s" % straw.PACKAGE)
523 return
525 # We have a separate accelerator group for the unmodified and
526 # shifted accelerators, that is, stuff like space, N, P, etc. This
527 # is so that we can have the find pane work correctly
528 def _create_unmodified_accelerator_group(self):
529 xml = gtk.glade.get_widget_tree(self._widget)
530 agroup = gtk.AccelGroup()
531 accels = (('feed_mark_as_read', 'R', gtk.gdk.SHIFT_MASK),
532 ('feed_mark_all_as_read', 'A', gtk.gdk.SHIFT_MASK),
533 ('next_item', 'N', gtk.gdk.SHIFT_MASK),
534 ('next_unread_feed', ' ', 0),
535 ('previous_item', 'P', gtk.gdk.SHIFT_MASK))
536 for widget_name, key, mask in accels:
537 widget = xml.get_widget(widget_name)
538 widget.add_accelerator("activate", agroup, ord(key), mask,
539 gtk.ACCEL_VISIBLE)
540 self._unmodified_accelerator_group = agroup
542 def _on_category_add_activate(self, *args):
543 self._presenter.add_category()
545 def _attach_unmodified_accelerator_group(self):
546 self._widget.add_accel_group(self._unmodified_accelerator_group)
548 def _detach_unmodified_accelerator_group(self):
549 self._widget.remove_accel_group(self._unmodified_accelerator_group)
551 def _on_straw_main_destroy_event(self, *args):
552 return self._presenter.quit()
554 def _on_straw_main_delete_event(self, *args):
555 return self._presenter.quit()
557 def _on_straw_main_configure_event(self, widget, event, *args):
558 if widget.window.get_state() is not gtk.gdk.WINDOW_STATE_MAXIMIZED:
559 self._config.window_maximized = False
560 self._presenter.check_allocation(widget, event)
561 else:
562 self._config.window_maximized = True
563 return
565 def _on_main_main_pane_size_allocate(self, widget, *args):
566 self._presenter.check_main_pane_position(widget)
568 def _on_main_sub_pane_size_allocate(self, widget, *args):
569 self._presenter.check_sub_pane_position(widget)
571 # Actions menu
572 def _on_feed_subscribe_activate(self, *args):
573 self._presenter.subscribe()
575 def _on_feed_unsubscribe_activate(self, *args):
576 self._presenter.remove_selected_feed()
578 def _on_subscription_import_activate(self, *args):
579 self._presenter.import_subscriptions(self._widget)
581 def _on_subscription_export_activate(self, *args):
582 self._presenter.export_subscriptions(self._widget)
584 def _on_feed_mark_as_read_activate(self, *args):
585 self._presenter.mark_feed_as_read()
587 def _on_feed_mark_all_as_read_activate(self, *args):
588 self._presenter.mark_all_as_read()
590 def _on_feed_refresh_selected_activate(self, *args):
591 self._presenter.poll_current_feed()
593 def _on_subscription_refresh_activate(self, *args):
594 #print self._widget.get_children()[0].set_stock_id(gtk.STOCK_STOP)
595 self._presenter.poll_all()
597 def _on_quit_activate(self, *args):
598 return self._presenter.quit()
600 # Edit menu
601 def _on_copy_activate(self, *args):
602 self._presenter.copy_itemview_text_selection()
605 def _on_find_activate(self, widget, *args):
606 xml = gtk.glade.get_widget_tree(self._widget)
607 menu_find = xml.get_widget('menu_find')
608 accel_label = menu_find.get_child()
610 if not self._find_toggled:
611 self._presenter.show_search()
612 self._detach_unmodified_accelerator_group()
613 self._find_toggled = True
615 # save the "Find..." stock text for later recovery
616 self._old_label_text = accel_label.get_text()
617 accel_label.set_text(_('Return to feed list...'))
619 else:
620 self._presenter.hide_search()
621 self._attach_unmodified_accelerator_group()
622 self._find_toggled = False
623 accel_label.set_text(self._old_label_text)
625 def _on_preferences_activate(self, *args):
626 self._presenter.show_preferences_dialog(self._widget)
628 def _on_feed_information_activate(self, *args):
629 self._presenter.show_feed_properties(self._widget)
631 # View menu
632 def _on_previous_item_activate(self, *args):
633 self._presenter.display_previous_item()
635 def _on_next_item_activate(self, *args):
636 self._presenter.display_next_item()
638 def _on_next_feed_activate(self, *args):
639 self._presenter.display_next_feed()
641 def _on_previous_feed_activate(self, *args):
642 self._presenter.display_previous_feed()
644 def _on_next_unread_feed_activate(self, *args):
645 self._presenter.display_next_unread_feed()
647 def _on_scroll_unread_items_activate(self, *args):
648 self._presenter.scroll_or_display_next_unread_item()
650 # Help menu
652 def _on_report_problem_activate(self, menuitem, *args):
653 helpers.url_show("http://bugzilla.gnome.org/simple-bug-guide.cgi?product=straw")
655 def _on_about_activate(self, menuitem, *args):
656 widget = self._presenter.credits()
657 widget.show()
659 def _on_straw_main_drag_data_received(self, w, context,
660 x, y, data, info, time):
661 if data and data.format == 8:
662 url = data.data.split("\n")[0]
663 straw.subscribe_show("%s" % url, self._widget)
664 context.finish(True, False, time)
665 else:
666 context.finish(False, False, time)
668 # FIXME
669 def show_offline_dialog(self):
670 return helpers.report_offline_status(self._widget)
672 def get_widget_tree(self):
673 return gtk.glade.get_widget_tree(self._widget)
675 def present(self):
676 self._widget.present()
678 def should_present(self):
679 if self._widget.window.get_state() is not gtk.gdk.WINDOW_STATE_WITHDRAWN:
680 self._widget.hide()
681 else:
682 self._widget.present()
685 class Application:
686 def __init__(self):
687 gnome.program_init(straw.PACKAGE, straw.VERSION)
689 error.setup_log()
690 # initialize threading and environment
691 gobject.threads_init()
692 config = Config.get_instance()
693 config.reload_css = os.getenv('STRAW_RELOAD_CSS') is not None
695 # build tray status icon
696 image = gtk.Image()
697 iconfile = os.path.normpath(straw.STRAW_DATA_DIR + "/straw.png")
698 pixbuf = gtk.gdk.pixbuf_new_from_file(iconfile)
699 scaled_buf = pixbuf.scale_simple(16,16,gtk.gdk.INTERP_BILINEAR)
700 image.set_from_pixbuf(scaled_buf)
701 try:
702 self.tray = gtk.status_icon_new_from_pixbuf(scaled_buf)
703 self.tray.connect('activate', self._tray_clicked)
704 except AttributeError:
705 import egg
706 import egg.trayicon
707 self.tray = egg.trayicon.TrayIcon(straw.PACKAGE);
708 self._eventbox = gtk.EventBox()
709 self.tray.add(self._eventbox)
710 self._eventbox.connect('button_press_event', self._tray_clicked)
711 self._eventbox.connect("drag-data-received", self._on_drag_data_received)
712 self._eventbox.drag_dest_set(
713 gtk.DEST_DEFAULT_ALL,
714 [('_NETSCAPE_URL', 0, 0),('text/uri-list ', 0, 1),('x-url/http', 0, 2)],
715 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
716 self._eventbox.add(image)
717 self.tray.show_all()
719 self._tooltip = gtk.Tooltips()
720 self.unread_count = 0
721 # end build tray status icon
723 FeedManager.setup(storage_path = "test.db")
724 FeedManager.init()
726 #FeedManager.import_opml(os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml"))
728 #if config.first_time:
729 # filepath = os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml")
730 # feeds.import_opml(filepath)
732 #ImageCache.initialize()
734 #import feedfinder
735 #print feedfinder.feeds("http://eclipse.org", True)
737 xml = gtk.glade.XML(os.path.join(straw.STRAW_DATA_DIR,'straw.glade'), "straw_main", gettext.textdomain())
738 window = xml.get_widget('straw_main')
739 self._main_presenter = ApplicationPresenter(view = ApplicationView(window))
740 self._main_presenter.view.present()
742 #PollManager.get_instance().start_polling_loop()
743 # set the default icon for the windows
744 iconfile = os.path.join(straw.STRAW_DATA_DIR,"straw.png")
745 gtk.window_set_default_icon(gtk.gdk.pixbuf_new_from_file(iconfile))
747 straw.start_services()
749 def mainloop(self):
750 gtk.gdk.threads_init()
751 gtk.gdk.threads_enter()
752 gtk.main()
753 gtk.gdk.threads_leave()
755 def _update(self,flist):
756 uritems = urfeeds = 0
757 for ur in [f.number_of_unread for f in flist.flatten_list()]:
758 if ur:
759 uritems += ur
760 urfeeds += 1
761 if uritems == self.unread_count:
762 return
763 self.unread_count = uritems
764 if uritems:
765 self._tooltip.set_tip(self.tray, _("%d new items")%uritems)
766 self.tray.show_all()
767 else:
768 self.tray.hide()
769 return
771 def _tray_clicked(self, widget, event=None):
772 self._main_presenter.view.should_present()
773 if event and not (event.button == 1):
774 return
775 self._main_presenter.scroll_or_display_next_unread_item()
777 def _on_drag_data_received(self, widget, context, x, y, data, info, timestamp):
778 if data and data.format == 8:
779 url = data.data.split("\n")[0]
780 straw.subscribe_show(url="%s" % url)
781 context.finish(True, False, timestamp)
782 else:
783 context.finish(False, False, timestamp)
784 return