Fixed exception that occured in some cases when removing nodes.
[straw.git] / straw / Application.py
blob32ca35a242238915c2d264ff8cf2890a8c1d594c
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 JobManager
33 import MVP
34 import error
35 import gnome, gconf
36 import gobject, gtk, gtk.glade
37 import helpers
38 import os, gettext, getopt, sys
39 import pygtk
40 import straw
41 import time
42 pygtk.require('2.0')
44 #from Find import FindPresenter, FindView
47 class StatusPresenter(MVP.BasicPresenter):
48 def _initialize(self):
49 self._mmgr = straw.get_status_manager()
50 self._mmgr.connect('changed', self._display)
52 def _display(self, *args):
53 cid = self._view.get_context_id("straw_main")
54 self._view.pop(cid)
55 self._view.push(cid, self._mmgr.read_message())
56 return
58 class ErrorPresenter:
59 def __init__(self, widget):
60 self._widget = widget
61 self._tooltips = gtk.Tooltips()
62 self._text = ''
63 #self._curr_feed = None
64 #self._curr_category = None
65 #fclist = FeedCategoryList.get_instance()
66 #fclist.connect('category-changed', self._category_changed)
68 def feedlist_selection_changed(self, selection, column):
69 errortexts = None
70 return
71 (model, pathlist) = selection.get_selected_rows()
72 iters = [model.get_iter(path) for path in pathlist]
73 if not iters: return
74 errorfeeds = []
75 nodes = [model.get_value(treeiter, column) for treeiter in iters]
76 try:
77 errorfeeds = [node.feed for node in nodes if node.feed and node.feed.error]
78 errortexts = [feed.error for feed in errorfeeds]
79 except TypeError, te:
80 print te
81 ### XXX display OPML Category error too
83 text = list()
84 if errorfeeds:
85 text.append(_("Error:"))
86 text += errortexts
88 if text:
89 t = "\n".join(text)
90 self._tooltips.set_tip(self._widget,t,t)
91 self._tooltips.enable()
92 self._widget.show()
93 else:
94 self._tooltips.disable()
95 self._widget.hide()
96 return
98 def hide(self):
99 self._widget.hide()
101 class MenuFeedPropsPresenter(MVP.BasicPresenter):
102 # FIXME
103 def set_sensitive(self, s):
104 self._view.set_sensitive(s)
105 return
107 class ToolbarView(MVP.WidgetView):
108 """ Widget: gtk.Toolbar"""
109 GCONF_DESKTOP_INTERFACE = "/desktop/gnome/interface"
111 def _initialize(self):
112 client = gconf.client_get_default()
113 if not client.dir_exists(self.GCONF_DESKTOP_INTERFACE):
114 return
115 client.add_dir(self.GCONF_DESKTOP_INTERFACE,
116 gconf.CLIENT_PRELOAD_NONE)
117 client.notify_add(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style",
118 self._toolbar_style_changed)
119 self._widget.set_tooltips(True)
121 def _toolbar_style_changed(self, client, notify_id, entry, *args):
122 value = entry.value.get_string()
123 self._presenter.style_changed(value)
125 def set_style(self, style):
126 self._widget.set_style(style)
128 def get_style(self):
129 client = gconf.client_get_default()
130 current_style = client.get_string(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style")
131 return current_style
133 class ToolbarPresenter(MVP.BasicPresenter):
134 STYLES = {'both':gtk.TOOLBAR_BOTH,
135 'text':gtk.TOOLBAR_TEXT,
136 'icons':gtk.TOOLBAR_ICONS,
137 'both-horiz':gtk.TOOLBAR_BOTH_HORIZ}
139 def _initialize(self):
140 style = self._view.get_style()
142 if style in self.STYLES.keys():
143 self._view.set_style(self.STYLES[style])
145 widget_tree = gtk.glade.get_widget_tree(self._view._widget)
146 self.change_read_state_button = widget_tree.get_widget("toolbar_change_read_state_button")
148 def style_changed(self, value):
149 if value in self.STYLES.keys():
150 self._view.set_style(self.STYLES[value])
152 def set_change_read_button_state(self, new_state):
153 self.change_read_state_button.set_active(new_state)
155 class ApplicationPresenter(MVP.BasicPresenter):
156 def _initialize(self):
157 FeedManager._get_instance().connect("update-all-done", self._on_update_all_done)
159 self._current_item = None
161 self._init_widgets()
162 self._init_presenters()
163 self._view.present()
165 def _init_widgets(self):
166 widget_tree = self._view.get_widget_tree()
168 self._itemlist_view_notebook = widget_tree.get_widget('mode_view_notebook')
169 self._feedlist_view_notebook = widget_tree.get_widget('left_mode_view_notebook')
171 item_view_container = widget_tree.get_widget('item_view_container')
172 item_list_container = widget_tree.get_widget('item_list_container')
174 self.update_all_button = widget_tree.get_widget("toolbar_refresh_all_button")
176 parent_paned = item_view_container.get_parent()
177 parent_parent_widget = parent_paned.get_parent()
179 config = Config.get_instance()
181 child_list = []
182 layout = config.pane_layout
183 if layout == 'vertical':
184 new_paned = gtk.VPaned()
185 child_list.append(item_list_container)
186 child_list.append(item_view_container)
187 elif layout == 'horizontal':
188 new_paned = gtk.HPaned()
189 child_list.append(item_view_container)
190 child_list.append(item_list_container)
191 else: # Use vertical layout as default
192 new_paned = gtk.VPaned()
193 child_list.append(item_list_container)
194 child_list.append(item_view_container)
196 for child in child_list:
197 child.reparent(new_paned)
199 parent_parent_widget.remove(parent_paned)
200 parent_parent_widget.add(new_paned)
202 new_paned.show()
204 def _init_presenters(self):
205 widget_tree = self._view.get_widget_tree()
206 self._toolbar_presenter = ToolbarPresenter(view=ToolbarView(widget_tree.get_widget('toolbar_default')))
207 self._error_presenter = ErrorPresenter(widget_tree.get_widget('statusbar_error_indicator'))
209 self._item_view = ItemView(widget_tree.get_widget('item_view_container'))
211 view = ItemListView(widget_tree.get_widget('item_selection_treeview'))
212 self._itemlist_presenter = ItemListPresenter(view=view)
214 view.add_selection_changed_listener(self._item_view)
215 view.add_selection_changed_listener(self)
217 view = FeedsView(widget_tree.get_widget('feed_selection_treeview'))
218 self._feed_list_presenter = FeedsPresenter(view=view)
219 view.add_selection_changed_listener(self._itemlist_presenter)
220 view.add_selection_changed_listener(self._error_presenter)
222 self._offline_presenter = OfflineToggle(widget_tree.get_widget('offline_toggle'))
224 self._status_presenter = StatusPresenter(view = widget_tree.get_widget("main_statusbar"))
225 self._menufp_presenter = MenuFeedPropsPresenter( view = widget_tree.get_widget('feed_information'))
226 self._menufp_presenter.set_sensitive(False)
227 # self._find_presenter = FindPresenter(view=FindView(widget_tree.get_widget("find_vbox")))
229 def set_current_item(self, item):
230 self._current_item = item
231 self._current_item_connect_id = item.connect("is-read-changed", self._on_current_item_is_read_changed)
233 def clear_current_item(self):
234 if self._current_item:
235 self._current_item.disconnect(self._current_item_connect_id)
236 self._current_item = None
238 def set_current_item_is_read(self, is_read):
239 if self._current_item:
240 self._current_item.props.is_read = is_read
242 def _on_current_item_is_read_changed(self, obj, is_read):
243 self._toolbar_presenter.set_change_read_button_state(is_read)
245 def itemlist_selection_changed(self, selection, column):
246 self.clear_current_item()
248 (model, treeiter) = selection.get_selected()
249 if not treeiter: return
250 item = model.get_value(treeiter, column)
251 self._toolbar_presenter.set_change_read_button_state(item.is_read)
252 self.set_current_item(item)
254 def add_category(self):
255 self._feed_list_presenter.add_category()
257 def copy_itemview_text_selection(self):
258 helpers.set_clipboard_text(self._item_view.get_selected_text())
260 def check_allocation(self, widget, event):
261 config = Config.get_instance()
262 def check_size((width, height, widget)):
263 if width == widget.allocation.width and height == widget.allocation.height:
264 config.main_window_size = (width, height)
265 if event.width != widget.allocation.width or event.height != widget.allocation.height:
266 gobject.timeout_add(1000, check_size, (
267 (event.width, event.height, widget)))
269 def check_main_pane_position(self, widget):
270 config = Config.get_instance()
271 def check_position((position, widget)):
272 if position == widget.get_position():
273 config.main_pane_position = position
274 pos = widget.get_position()
275 if pos != config.main_pane_position:
276 gobject.timeout_add(1000, check_position, (pos, widget))
278 def check_sub_pane_position(self, widget):
279 config = Config.get_instance()
280 def check_position((position, widget)):
281 if position == widget.get_position():
282 config.sub_pane_position = position
283 pos = widget.get_position()
284 if pos != config.sub_pane_position:
285 gobject.timeout_add(1000, check_position, (pos, widget))
287 def credits(self):
288 return helpers.credits()
290 def poll_all(self):
291 if not self._warn_if_offline():
292 return
294 if not FeedManager.is_update_all_running():
295 FeedManager.update_all_feeds({ "task-start": [ self._on_feed_poll_started ],
296 "task-done": [ self._on_feed_poll_done ] })
297 self.update_all_button.set_sensitive(True)
298 self.update_all_button.set_stock_id(gtk.STOCK_STOP)
299 else:
300 self.update_all_button.set_sensitive(False)
301 JobManager.stop_jobs()
303 def _on_feed_poll_started(self, handler, feed):
304 pass
306 def _on_feed_poll_done(self, handler, data):
307 pass
309 def _on_update_all_done(self, obj):
310 self.update_all_button.set_sensitive(True)
311 self.update_all_button.set_stock_id(gtk.STOCK_REFRESH)
313 def poll_current_category(self):
314 if self._warn_if_offline():
315 self._poll_categories([self._curr_category])
317 def poll_current_feed(self):
318 if self._warn_if_offline():
319 print self._curr_feed
320 pm = PollManager.get_instance()
321 pm.poll([self._curr_feed])
323 def mark_feed_as_read(self):
324 self._curr_feed.mark_all_read()
326 def mark_all_as_read(self):
327 print "TODO mark_all_as_read"
329 def remove_selected_feed(self):
330 # self._feed_list_presenter.remove_selected_feed()
331 pass
333 def show_search(self):
334 # self._find_presenter.item_list.signal_connect(Event.ItemSelectionChangedSignal,
335 # self._item_view.item_selection_changed)
336 self._item_view.display_empty_search()
337 self._itemlist_view_notebook.set_current_page(1)
338 self._feedlist_view_notebook.set_current_page(1)
339 self._feedinfo_presenter.hide()
341 def hide_search(self):
342 # self._find_presenter.clear()
343 self._itemlist_view_notebook.set_current_page(0)
344 self._feedlist_view_notebook.set_current_page(0)
345 self._feedinfo_presenter.show()
346 # self._find_presenter.item_list.signal_disconnect(Event.ItemSelectionChangedSignal,
347 # self._item_view.item_selection_changed)
349 def _poll_categories(self, fclist):
350 pm = PollManager.get_instance()
351 pm.poll_categories(fclist)
353 def _warn_if_offline(self):
354 config = Config.get_instance()
355 will_poll = False
356 if config.offline:
357 response = self._view.show_offline_dialog()
358 if response == gtk.RESPONSE_OK:
359 config.offline = not config.offline
360 will_poll = True
361 else:
362 will_poll = True
363 return will_poll
365 def display_previous_feed(self, item = None):
367 Displays the feed before the current selected feed
369 self._feed_list_presenter.select_previous_feed()
371 def display_next_feed(self, item=None):
373 Displays the feed after the current selected feed
375 self._feed_list_presenter.select_next_feed()
376 return
378 def display_next_unread_feed(self):
380 Displays the next feed with an unread item
382 self._feed_list_presenter.select_next_feed(with_unread=True)
383 pass
385 def display_previous_item(self, item=None):
387 Displays the item before the current selected item. If the item is the
388 first item, scrolls to the previous feed
390 is_prev = self._itemlist_presenter.select_previous_item()
391 if not is_prev:
392 # TODO HACK - implement select_previous_feed(select_last=True) ...
393 # ... to select previous feed's last item
394 self._feed_list_presenter.select_previous_feed()
395 self._itemlist_presenter.select_last_item()
396 pass
397 return
399 def display_next_item(self, item=None):
401 Displays the item after the current selected item. If the item is the
402 last item, selectes the next feed. If the current feed is the last
403 feed in the list, it goes back and selects the first feed
405 is_next = self._itemlist_presenter.select_next_item()
406 if not is_next:
407 is_next_feed = self._feed_list_presenter.select_next_feed()
408 if not is_next_feed:
409 self._feed_list_presenter.select_firsteed_feed()
410 return
412 def scroll_or_display_next_unread_item(self, item=None):
413 has_unread_item = False
414 if not self._item_view.scroll_down():
415 has_unread_item = self._itemlist_presenter.select_next_unread_item()
416 if not has_unread_item:
417 self._feed_list_presenter.select_next_feed(with_unread=True)
418 return
420 def show_preferences_dialog(self, parent):
421 straw.preferences_show()
423 def show_feed_properties(self, parent):
424 straw.feed_properties_show(self._curr_feed)
426 def quit(self):
427 gtk.main_quit()
429 def _setup_filechooser_dialog(self, title, action, extra_widget_title):
431 Setup the file chooser dialog. This includes an extra widget (a combobox)
432 to include the categories to import or export
434 dialog = gtk.FileChooserDialog(title, action=action,
435 buttons=(gtk.STOCK_CANCEL,
436 gtk.RESPONSE_CANCEL,
437 gtk.STOCK_OK, gtk.RESPONSE_OK))
438 category_list = []
440 for category in FeedManager.categories():
441 category_list.append(category)
443 model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
444 combobox = gtk.ComboBox(model)
445 celltitle = gtk.CellRendererText()
446 combobox.pack_start(celltitle,False)
447 combobox.add_attribute(celltitle, 'text', 0)
449 for category in category_list:
450 it = model.append()
451 model.set(it, 0, category.name, 1, category)
453 combobox.set_active(0)
454 label = gtk.Label(extra_widget_title)
455 label.set_alignment(1.0,0.5)
456 hbox = gtk.HBox(spacing=6)
457 hbox.pack_start(label,True,True,0)
458 hbox.pack_end(combobox,False,False,0)
459 hbox.show_all()
461 dialog.set_extra_widget(hbox)
462 return (dialog, combobox)
464 def import_subscriptions(self, parent):
465 (dialog,combobox) = self._setup_filechooser_dialog(_("Import Subscriptions"),
466 gtk.FILE_CHOOSER_ACTION_OPEN,
467 _("Add new subscriptions in:"))
468 ffilter = gtk.FileFilter()
469 ffilter.set_name(_("OPML Files Only"))
470 ffilter.add_pattern("*.xml")
471 ffilter.add_pattern("*.opml")
472 dialog.add_filter(ffilter)
473 dialog.set_transient_for(parent)
474 response = dialog.run()
475 if response == gtk.RESPONSE_OK:
476 filename = dialog.get_filename()
477 model = combobox.get_model()
478 category = model[combobox.get_active()][1]
479 dialog.hide()
480 FeedManager.import_opml(filename, category)
481 dialog.destroy()
483 def export_subscriptions(self, parent):
484 (dialog,combobox) = self._setup_filechooser_dialog(_("Export Subscriptions"),
485 gtk.FILE_CHOOSER_ACTION_SAVE,
486 _("Select category to export:"))
487 def selection_changed(widget, dialog):
488 model = widget.get_model()
489 category = model[widget.get_active()][1]
490 dialog.set_current_name("Straw-%s.xml" % category.name)
492 combobox.connect('changed', selection_changed, dialog)
493 selection_changed(combobox, dialog)
494 dialog.set_transient_for(parent)
495 response = dialog.run()
497 if response == gtk.RESPONSE_OK:
498 filename = dialog.get_filename()
499 model = combobox.get_model()
500 root_category = model[combobox.get_active()][1]
501 FeedManager.export_opml(root_category.id, filename)
503 dialog.destroy()
505 def subscribe(self):
506 straw.subscribe_show(parent=self.view._widget)
508 class ApplicationView(MVP.WidgetView):
510 Widget: straw_main
512 def _initialize(self):
513 self._config = Config.get_instance()
514 self._initialize_dnd()
515 self._initialize_window_updater()
516 self._create_unmodified_accelerator_group()
517 self._attach_unmodified_accelerator_group()
518 self._initialize_window()
519 self._find_toggled = False
521 def _initialize_window(self):
522 widget_tree = gtk.glade.get_widget_tree(self._widget)
523 if self._config.window_maximized:
524 self._widget.maximize()
525 else:
526 # we use resize here since configure-event seems to
527 # overwrite the default size if we use set_default_size.
528 self._widget.resize(*self._config.main_window_size)
529 mmp = widget_tree.get_widget('main_main_pane')
530 msp = widget_tree.get_widget('main_sub_pane')
531 mmp.set_position(self._config.main_pane_position)
532 msp.set_position(self._config.sub_pane_position)
534 def _initialize_dnd(self):
535 self._widget.drag_dest_set(
536 gtk.DEST_DEFAULT_ALL,
537 [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1),
538 ('x-url/http', 0, 2)],
539 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
540 return
542 def _initialize_window_updater(self):
543 #feedlist.signal_connect(Event.AllItemsReadSignal,
544 # lambda signal: self._update_title(feedlist))
545 #feedlist.signal_connect(Event.ItemReadSignal,
546 # lambda signal: self._update_title(feedlist))
547 #feedlist.signal_connect(Event.ItemsAddedSignal,
548 # lambda signal: self._update_title(feedlist))
549 #feedlist.signal_connect(Event.FeedsChangedSignal,
550 # lambda signal: self._update_title(feedlist))
551 pass
553 def _update_title(self, flist):
554 uritems = urfeeds = 0
555 sfeeds = "feeds"
556 listfeeds = flist.flatten_list()
557 for ur in [f.number_of_unread for f in listfeeds]:
558 if ur:
559 uritems += ur
560 urfeeds += 1
561 else:
562 urfeeds = len(listfeeds)
563 if urfeeds < 2:
564 sfeeds = "feed"
565 item_feed_map = {'uritems': uritems,
566 'urfeeds': urfeeds,
567 'fstring' : sfeeds}
568 title = _('%(uritems)d unread in %(urfeeds)d %(fstring)s') % item_feed_map
569 self._widget.set_title( title + " - %s" % straw.PACKAGE)
571 # We have a separate accelerator group for the unmodified and
572 # shifted accelerators, that is, stuff like space, N, P, etc. This
573 # is so that we can have the find pane work correctly
574 def _create_unmodified_accelerator_group(self):
575 xml = gtk.glade.get_widget_tree(self._widget)
576 agroup = gtk.AccelGroup()
577 accels = (('feed_mark_as_read', 'R', gtk.gdk.SHIFT_MASK),
578 ('feed_mark_all_as_read', 'A', gtk.gdk.SHIFT_MASK),
579 ('next_item', 'N', gtk.gdk.SHIFT_MASK),
580 ('next_unread_feed', ' ', 0),
581 ('previous_item', 'P', gtk.gdk.SHIFT_MASK))
582 for widget_name, key, mask in accels:
583 widget = xml.get_widget(widget_name)
584 widget.add_accelerator("activate", agroup, ord(key), mask,
585 gtk.ACCEL_VISIBLE)
586 self._unmodified_accelerator_group = agroup
588 def _on_category_add_activate(self, *args):
589 self._presenter.add_category()
591 def _on_toolbar_change_read_state_button_toggled(self, button):
592 self._presenter.set_current_item_is_read(button.get_active())
594 def _attach_unmodified_accelerator_group(self):
595 self._widget.add_accel_group(self._unmodified_accelerator_group)
597 def _detach_unmodified_accelerator_group(self):
598 self._widget.remove_accel_group(self._unmodified_accelerator_group)
600 def _on_straw_main_destroy_event(self, *args):
601 return self._presenter.quit()
603 def _on_straw_main_delete_event(self, *args):
604 return self._presenter.quit()
606 def _on_straw_main_configure_event(self, widget, event, *args):
607 if widget.window.get_state() is not gtk.gdk.WINDOW_STATE_MAXIMIZED:
608 self._config.window_maximized = False
609 self._presenter.check_allocation(widget, event)
610 else:
611 self._config.window_maximized = True
612 return
614 def _on_main_main_pane_size_allocate(self, widget, *args):
615 self._presenter.check_main_pane_position(widget)
617 def _on_main_sub_pane_size_allocate(self, widget, *args):
618 self._presenter.check_sub_pane_position(widget)
620 # Actions menu
621 def _on_feed_subscribe_activate(self, *args):
622 self._presenter.subscribe()
624 def _on_feed_unsubscribe_activate(self, *args):
625 self._presenter.remove_selected_feed()
627 def _on_subscription_import_activate(self, *args):
628 self._presenter.import_subscriptions(self._widget)
630 def _on_subscription_export_activate(self, *args):
631 self._presenter.export_subscriptions(self._widget)
633 def _on_feed_mark_as_read_activate(self, *args):
634 self._presenter.mark_feed_as_read()
636 def _on_feed_mark_all_as_read_activate(self, *args):
637 self._presenter.mark_all_as_read()
639 def _on_feed_refresh_selected_activate(self, *args):
640 self._presenter.poll_current_feed()
642 def _on_subscription_refresh_activate(self, *args):
643 #print self._widget.get_children()[0].set_stock_id(gtk.STOCK_STOP)
644 self._presenter.poll_all()
646 def _on_quit_activate(self, *args):
647 return self._presenter.quit()
649 # Edit menu
650 def _on_copy_activate(self, *args):
651 self._presenter.copy_itemview_text_selection()
654 def _on_find_activate(self, widget, *args):
655 xml = gtk.glade.get_widget_tree(self._widget)
656 menu_find = xml.get_widget('menu_find')
657 accel_label = menu_find.get_child()
659 if not self._find_toggled:
660 self._presenter.show_search()
661 self._detach_unmodified_accelerator_group()
662 self._find_toggled = True
664 # save the "Find..." stock text for later recovery
665 self._old_label_text = accel_label.get_text()
666 accel_label.set_text(_('Return to feed list...'))
668 else:
669 self._presenter.hide_search()
670 self._attach_unmodified_accelerator_group()
671 self._find_toggled = False
672 accel_label.set_text(self._old_label_text)
674 def _on_preferences_activate(self, *args):
675 self._presenter.show_preferences_dialog(self._widget)
677 def _on_feed_information_activate(self, *args):
678 self._presenter.show_feed_properties(self._widget)
680 # View menu
681 def _on_previous_item_activate(self, *args):
682 self._presenter.display_previous_item()
684 def _on_next_item_activate(self, *args):
685 self._presenter.display_next_item()
687 def _on_next_feed_activate(self, *args):
688 self._presenter.display_next_feed()
690 def _on_previous_feed_activate(self, *args):
691 self._presenter.display_previous_feed()
693 def _on_next_unread_feed_activate(self, *args):
694 self._presenter.display_next_unread_feed()
696 def _on_scroll_unread_items_activate(self, *args):
697 self._presenter.scroll_or_display_next_unread_item()
699 # Help menu
701 def _on_report_problem_activate(self, menuitem, *args):
702 helpers.url_show("http://bugzilla.gnome.org/simple-bug-guide.cgi?product=straw")
704 def _on_about_activate(self, menuitem, *args):
705 widget = self._presenter.credits()
706 widget.show()
708 def _on_straw_main_drag_data_received(self, w, context,
709 x, y, data, info, time):
710 if data and data.format == 8:
711 url = data.data.split("\n")[0]
712 straw.subscribe_show("%s" % url, self._widget)
713 context.finish(True, False, time)
714 else:
715 context.finish(False, False, time)
717 # FIXME
718 def show_offline_dialog(self):
719 return helpers.report_offline_status(self._widget)
721 def get_widget_tree(self):
722 return gtk.glade.get_widget_tree(self._widget)
724 def present(self):
725 self._widget.present()
727 def should_present(self):
728 if self._widget.window.get_state() is not gtk.gdk.WINDOW_STATE_WITHDRAWN:
729 self._widget.hide()
730 else:
731 self._widget.present()
734 class Application:
735 def __init__(self):
736 gnome.program_init(straw.PACKAGE, straw.VERSION)
738 error.setup_log()
739 # initialize threading and environment
740 gobject.threads_init()
741 config = Config.get_instance()
742 config.reload_css = os.getenv('STRAW_RELOAD_CSS') is not None
744 # build tray status icon
745 image = gtk.Image()
746 iconfile = os.path.normpath(straw.STRAW_DATA_DIR + "/straw.png")
747 pixbuf = gtk.gdk.pixbuf_new_from_file(iconfile)
748 scaled_buf = pixbuf.scale_simple(16,16,gtk.gdk.INTERP_BILINEAR)
749 image.set_from_pixbuf(scaled_buf)
750 try:
751 self.tray = gtk.status_icon_new_from_pixbuf(scaled_buf)
752 self.tray.connect('activate', self._tray_clicked)
753 except AttributeError:
754 import egg
755 import egg.trayicon
756 self.tray = egg.trayicon.TrayIcon(straw.PACKAGE);
757 self._eventbox = gtk.EventBox()
758 self.tray.add(self._eventbox)
759 self._eventbox.connect('button_press_event', self._tray_clicked)
760 self._eventbox.connect("drag-data-received", self._on_drag_data_received)
761 self._eventbox.drag_dest_set(
762 gtk.DEST_DEFAULT_ALL,
763 [('_NETSCAPE_URL', 0, 0),('text/uri-list ', 0, 1),('x-url/http', 0, 2)],
764 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
765 self._eventbox.add(image)
766 self.tray.show_all()
768 self._tooltip = gtk.Tooltips()
769 self.unread_count = 0
770 # end build tray status icon
772 FeedManager.setup(storage_path = "test.db")
773 FeedManager.init()
775 #FeedManager.import_opml(os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml"))
777 #if config.first_time:
778 # filepath = os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml")
779 # feeds.import_opml(filepath)
781 #ImageCache.initialize()
783 #import feedfinder
784 #print feedfinder.feeds("http://eclipse.org", True)
786 xml = gtk.glade.XML(os.path.join(straw.STRAW_DATA_DIR,'straw.glade'), "straw_main", gettext.textdomain())
787 window = xml.get_widget('straw_main')
788 self._main_presenter = ApplicationPresenter(view = ApplicationView(window))
789 self._main_presenter.view.present()
791 #PollManager.get_instance().start_polling_loop()
792 # set the default icon for the windows
793 iconfile = os.path.join(straw.STRAW_DATA_DIR,"straw.png")
794 gtk.window_set_default_icon(gtk.gdk.pixbuf_new_from_file(iconfile))
796 straw.start_services()
798 def mainloop(self):
799 gtk.gdk.threads_init()
800 gtk.gdk.threads_enter()
801 gtk.main()
802 gtk.gdk.threads_leave()
804 def _update(self,flist):
805 uritems = urfeeds = 0
806 for ur in [f.number_of_unread for f in flist.flatten_list()]:
807 if ur:
808 uritems += ur
809 urfeeds += 1
810 if uritems == self.unread_count:
811 return
812 self.unread_count = uritems
813 if uritems:
814 self._tooltip.set_tip(self.tray, _("%d new items")%uritems)
815 self.tray.show_all()
816 else:
817 self.tray.hide()
818 return
820 def _tray_clicked(self, widget, event=None):
821 self._main_presenter.view.should_present()
822 if event and not (event.button == 1):
823 return
824 self._main_presenter.scroll_or_display_next_unread_item()
826 def _on_drag_data_received(self, widget, context, x, y, data, info, timestamp):
827 if data and data.format == 8:
828 url = data.data.split("\n")[0]
829 straw.subscribe_show(url="%s" % url)
830 context.finish(True, False, timestamp)
831 else:
832 context.finish(False, False, timestamp)
833 return