Yet another fixes to tree view manipulation...
[straw.git] / straw / Application.py
blob07df6a9d3a918a4f00fbe964a215eb9a0a834d07
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 copy_itemview_text_selection(self):
218 helpers.set_clipboard_text(self._item_view.get_selected_text())
220 def check_allocation(self, widget, event):
221 config = Config.get_instance()
222 def check_size((width, height, widget)):
223 if width == widget.allocation.width and height == widget.allocation.height:
224 config.main_window_size = (width, height)
225 if event.width != widget.allocation.width or event.height != widget.allocation.height:
226 gobject.timeout_add(1000, check_size, (
227 (event.width, event.height, widget)))
229 def check_main_pane_position(self, widget):
230 config = Config.get_instance()
231 def check_position((position, widget)):
232 if position == widget.get_position():
233 config.main_pane_position = position
234 pos = widget.get_position()
235 if pos != config.main_pane_position:
236 gobject.timeout_add(1000, check_position, (pos, widget))
238 def check_sub_pane_position(self, widget):
239 config = Config.get_instance()
240 def check_position((position, widget)):
241 if position == widget.get_position():
242 config.sub_pane_position = position
243 pos = widget.get_position()
244 if pos != config.sub_pane_position:
245 gobject.timeout_add(1000, check_position, (pos, widget))
247 def credits(self):
248 return helpers.credits()
250 def poll_all(self):
251 if self._warn_if_offline():
252 FeedManager.update_all_feeds({ "task-start": [ self._on_feed_poll_started ],
253 "task-done": [ self._on_feed_poll_done ] })
255 def _on_feed_poll_started(self, handler, feed):
256 pass#print feed.id
258 def _on_feed_poll_done(self, handler, data):
259 pass
261 def poll_current_category(self):
262 if self._warn_if_offline():
263 self._poll_categories([self._curr_category])
265 def poll_current_feed(self):
266 if self._warn_if_offline():
267 print self._curr_feed
268 pm = PollManager.get_instance()
269 pm.poll([self._curr_feed])
271 def mark_feed_as_read(self):
272 self._curr_feed.mark_all_read()
274 def mark_all_as_read(self):
275 print "TODO mark_all_as_read"
277 def remove_selected_feed(self):
278 # self._feed_list_presenter.remove_selected_feed()
279 pass
281 def show_search(self):
282 # self._find_presenter.item_list.signal_connect(Event.ItemSelectionChangedSignal,
283 # self._item_view.item_selection_changed)
284 self._item_view.display_empty_search()
285 self._itemlist_view_notebook.set_current_page(1)
286 self._feedlist_view_notebook.set_current_page(1)
287 self._feedinfo_presenter.hide()
289 def hide_search(self):
290 # self._find_presenter.clear()
291 self._itemlist_view_notebook.set_current_page(0)
292 self._feedlist_view_notebook.set_current_page(0)
293 self._feedinfo_presenter.show()
294 # self._find_presenter.item_list.signal_disconnect(Event.ItemSelectionChangedSignal,
295 # self._item_view.item_selection_changed)
298 def _poll_categories(self, fclist):
299 pm = PollManager.get_instance()
300 pm.poll_categories(fclist)
301 return
303 def _warn_if_offline(self):
304 config = Config.get_instance()
305 will_poll = False
306 if config.offline:
307 response = self._view.show_offline_dialog()
308 if response == gtk.RESPONSE_OK:
309 config.offline = not config.offline
310 will_poll = True
311 else:
312 will_poll = True
313 return will_poll
315 def display_previous_feed(self, item = None):
317 Displays the feed before the current selected feed
319 self._feed_list_presenter.select_previous_feed()
321 def display_next_feed(self, item=None):
323 Displays the feed after the current selected feed
325 self._feed_list_presenter.select_next_feed()
326 return
328 def display_next_unread_feed(self):
330 Displays the next feed with an unread item
332 self._feed_list_presenter.select_next_feed(with_unread=True)
333 pass
335 def display_previous_item(self, item=None):
337 Displays the item before the current selected item. If the item is the
338 first item, scrolls to the previous feed
340 is_prev = self._itemlist_presenter.select_previous_item()
341 if not is_prev:
342 # TODO HACK - implement select_previous_feed(select_last=True) ...
343 # ... to select previous feed's last item
344 self._feed_list_presenter.select_previous_feed()
345 self._itemlist_presenter.select_last_item()
346 pass
347 return
349 def display_next_item(self, item=None):
351 Displays the item after the current selected item. If the item is the
352 last item, selectes the next feed. If the current feed is the last
353 feed in the list, it goes back and selects the first feed
355 is_next = self._itemlist_presenter.select_next_item()
356 if not is_next:
357 is_next_feed = self._feed_list_presenter.select_next_feed()
358 if not is_next_feed:
359 self._feed_list_presenter.select_firsteed_feed()
360 return
362 def scroll_or_display_next_unread_item(self, item=None):
363 has_unread_item = False
364 if not self._item_view.scroll_down():
365 has_unread_item = self._itemlist_presenter.select_next_unread_item()
366 if not has_unread_item:
367 self._feed_list_presenter.select_next_feed(with_unread=True)
368 return
370 def show_preferences_dialog(self, parent):
371 straw.preferences_show()
373 def show_feed_properties(self, parent):
374 straw.feed_properties_show(self._curr_feed)
376 def quit(self):
377 gtk.main_quit()
379 def _setup_filechooser_dialog(self, title, action, extra_widget_title):
381 Setup the file chooser dialog. This includes an extra widget (a combobox)
382 to include the categories to import or export
384 dialog = gtk.FileChooserDialog(title, action=action,
385 buttons=(gtk.STOCK_CANCEL,
386 gtk.RESPONSE_CANCEL,
387 gtk.STOCK_OK, gtk.RESPONSE_OK))
388 category_list = []
390 for category in FeedManager.categories():
391 category_list.append(category)
393 print category_list
395 model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
396 combobox = gtk.ComboBox(model)
397 celltitle = gtk.CellRendererText()
398 combobox.pack_start(celltitle,False)
399 combobox.add_attribute(celltitle, 'text', 0)
401 for category in category_list:
402 it = model.append()
403 model.set(it, 0, category.name, 1, category)
405 combobox.set_active(0)
406 label = gtk.Label(extra_widget_title)
407 label.set_alignment(1.0,0.5)
408 hbox = gtk.HBox(spacing=6)
409 hbox.pack_start(label,True,True,0)
410 hbox.pack_end(combobox,False,False,0)
411 hbox.show_all()
413 dialog.set_extra_widget(hbox)
414 return (dialog, combobox)
416 def import_subscriptions(self, parent):
417 (dialog,combobox) = self._setup_filechooser_dialog(_("Import Subscriptions"),
418 gtk.FILE_CHOOSER_ACTION_OPEN,
419 _("Add new subscriptions in:"))
420 ffilter = gtk.FileFilter()
421 ffilter.set_name(_("OPML Files Only"))
422 ffilter.add_pattern("*.xml")
423 ffilter.add_pattern("*.opml")
424 dialog.add_filter(ffilter)
425 dialog.set_transient_for(parent)
426 response = dialog.run()
427 if response == gtk.RESPONSE_OK:
428 filename = dialog.get_filename()
429 model = combobox.get_model()
430 category = model[combobox.get_active()][1]
431 dialog.hide()
432 FeedManager.import_opml(filename, category)
433 dialog.destroy()
435 def export_subscriptions(parent):
436 (dialog,combobox) = _setup_filechooser_dialog(_("Export Subscriptions"),
437 gtk.FILE_CHOOSER_ACTION_SAVE,
438 _("Select category to export:"))
439 def selection_changed(widget, dialog):
440 model = widget.get_model()
441 category = model[widget.get_active()][1]
442 dialog.set_current_name("Straw-%s.xml" % category.title)
444 combobox.connect('changed',
445 selection_changed,
446 dialog)
447 selection_changed(combobox, dialog)
448 dialog.set_transient_for(parent)
449 response = dialog.run()
450 if response == gtk.RESPONSE_OK:
451 filename = dialog.get_filename()
452 model = combobox.get_model()
453 cat = model[combobox.get_active()][1]
454 opml.export(cat.title, cat.feeds, filename)
455 dialog.destroy()
457 def subscribe(self):
458 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 print "add!"
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