Removed URLFetch module (its functionality is now provided by Fetcher module).
[straw.git] / straw / Application.py
blob470e56724f6238cacb25624eb387e5c62db637f6
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 ConfigOptions import *
24 from FeedListView import FeedsView, FeedsPresenter
25 from ItemList import ItemListPresenter, ItemListView
26 from ItemView import ItemView
27 from MainloopManager import MainloopManager
28 from MessageManager import post_status_message, get_status_manager, start_services
29 from OfflineToggle import OfflineToggle
30 from subscribe import subscribe_show
31 from xml.sax import saxutils
32 import Config
33 import FeedManager
34 import ItemManager
35 import JobManager
36 import MVP
37 import error
38 import feedproperties
39 import gnome, gconf
40 import gobject, gtk, gtk.glade
41 import helpers
42 import os, gettext, getopt, sys
43 import preferences
44 import pygtk
45 import straw.defs
46 import time
47 pygtk.require('2.0')
49 class StatusPresenter(MVP.BasicPresenter):
50 def _initialize(self):
51 self._mmgr = get_status_manager()
52 self._mmgr.connect('changed', self._display)
54 def _display(self, *args):
55 cid = self._view.get_context_id("straw_main")
56 self._view.pop(cid)
57 self._view.push(cid, self._mmgr.read_message())
58 return
60 class ErrorPresenter:
61 def __init__(self, widget):
62 self._widget = widget
63 self._tooltips = gtk.Tooltips()
64 self._text = ''
65 #self._curr_feed = None
66 #self._curr_category = None
67 #fclist = FeedCategoryList.get_instance()
68 #fclist.connect('category-changed', self._category_changed)
70 def feedlist_selection_changed(self, selection, column):
71 errortexts = None
72 return
73 (model, pathlist) = selection.get_selected_rows()
74 iters = [model.get_iter(path) for path in pathlist]
75 if not iters: return
76 errorfeeds = []
77 nodes = [model.get_value(treeiter, column) for treeiter in iters]
78 try:
79 errorfeeds = [node.feed for node in nodes if node.feed and node.feed.error]
80 errortexts = [feed.error for feed in errorfeeds]
81 except TypeError, te:
82 print te
83 ### XXX display OPML Category error too
85 text = list()
86 if errorfeeds:
87 text.append(_("Error:"))
88 text += errortexts
90 if text:
91 t = "\n".join(text)
92 self._tooltips.set_tip(self._widget,t,t)
93 self._tooltips.enable()
94 self._widget.show()
95 else:
96 self._tooltips.disable()
97 self._widget.hide()
98 return
100 def hide(self):
101 self._widget.hide()
103 class ToolbarView(MVP.WidgetView):
104 """ Widget: gtk.Toolbar"""
105 GCONF_DESKTOP_INTERFACE = "/desktop/gnome/interface"
107 def _initialize(self):
108 client = gconf.client_get_default()
109 if not client.dir_exists(self.GCONF_DESKTOP_INTERFACE):
110 return
111 client.add_dir(self.GCONF_DESKTOP_INTERFACE,
112 gconf.CLIENT_PRELOAD_NONE)
113 client.notify_add(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style",
114 self._toolbar_style_changed)
115 self._widget.set_tooltips(True)
117 def _toolbar_style_changed(self, client, notify_id, entry, *args):
118 value = entry.value.get_string()
119 self._presenter.style_changed(value)
121 def set_style(self, style):
122 self._widget.set_style(style)
124 def get_style(self):
125 client = gconf.client_get_default()
126 current_style = client.get_string(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style")
127 return current_style
129 class ToolbarPresenter(MVP.BasicPresenter):
130 STYLES = {'both':gtk.TOOLBAR_BOTH,
131 'text':gtk.TOOLBAR_TEXT,
132 'icons':gtk.TOOLBAR_ICONS,
133 'both-horiz':gtk.TOOLBAR_BOTH_HORIZ}
135 def _initialize(self):
136 style = self._view.get_style()
138 if style in self.STYLES.keys():
139 self._view.set_style(self.STYLES[style])
141 widget_tree = gtk.glade.get_widget_tree(self._view._widget)
142 self.change_read_state_button = widget_tree.get_widget("toolbar_change_read_state_button")
144 def style_changed(self, value):
145 if value in self.STYLES.keys():
146 self._view.set_style(self.STYLES[value])
148 def set_change_read_button_state(self, new_state):
149 self.change_read_state_button.set_active(new_state)
151 class ApplicationPresenter(MVP.BasicPresenter):
152 def _initialize(self):
153 FeedManager._get_instance().connect("update-all-done", self._on_update_all_done)
155 self._current_item = None
157 self._init_widgets()
158 self._init_presenters()
159 self._view.present()
161 def _init_widgets(self):
162 widget_tree = self._view.get_widget_tree()
164 self._itemlist_view_notebook = widget_tree.get_widget('mode_view_notebook')
165 self._feedlist_view_notebook = widget_tree.get_widget('left_mode_view_notebook')
167 item_view_container = widget_tree.get_widget('item_view_container')
168 item_list_container = widget_tree.get_widget('item_list_container')
170 self.update_all_button = widget_tree.get_widget("toolbar_refresh_all_button")
172 parent_paned = item_view_container.get_parent()
173 parent_parent_widget = parent_paned.get_parent()
175 child_list = []
176 layout = Config.get(OPTION_PANE_LAYOUT)
178 if layout == 'vertical':
179 new_paned = gtk.VPaned()
180 child_list.append(item_list_container)
181 child_list.append(item_view_container)
182 elif layout == 'horizontal':
183 new_paned = gtk.HPaned()
184 child_list.append(item_view_container)
185 child_list.append(item_list_container)
186 else: # Use vertical layout as default
187 new_paned = gtk.VPaned()
188 child_list.append(item_list_container)
189 child_list.append(item_view_container)
191 for child in child_list:
192 child.reparent(new_paned)
194 parent_parent_widget.remove(parent_paned)
195 parent_parent_widget.add(new_paned)
197 new_paned.show()
199 def _init_presenters(self):
200 widget_tree = self._view.get_widget_tree()
201 self._toolbar_presenter = ToolbarPresenter(view=ToolbarView(widget_tree.get_widget('toolbar_default')))
202 self._error_presenter = ErrorPresenter(widget_tree.get_widget('statusbar_error_indicator'))
204 self._item_view = ItemView(widget_tree.get_widget('item_view_container'))
206 view = ItemListView(widget_tree.get_widget('item_selection_treeview'))
207 self._itemlist_presenter = ItemListPresenter(view=view)
209 view.add_selection_changed_listener(self._item_view)
210 view.add_selection_changed_listener(self)
212 view = FeedsView(widget_tree.get_widget('feed_selection_treeview'))
213 self._feed_list_presenter = FeedsPresenter(view=view)
214 view.add_selection_changed_listener(self._itemlist_presenter)
215 view.add_selection_changed_listener(self._error_presenter)
217 self._offline_presenter = OfflineToggle(widget_tree.get_widget('offline_toggle'))
219 self._status_presenter = StatusPresenter(view = widget_tree.get_widget("main_statusbar"))
220 # self._find_presenter = FindPresenter(view=FindView(widget_tree.get_widget("find_vbox")))
222 def set_current_item(self, item):
223 self._current_item = item
224 self._current_item_connect_id = item.connect("is-read-changed", self._on_current_item_is_read_changed)
226 def clear_current_item(self):
227 if self._current_item:
228 self._current_item.disconnect(self._current_item_connect_id)
229 self._current_item = None
231 def set_current_item_is_read(self, is_read):
232 if self._current_item:
233 self._current_item.props.is_read = is_read
235 def _on_current_item_is_read_changed(self, obj, is_read):
236 self._toolbar_presenter.set_change_read_button_state(is_read)
238 def itemlist_selection_changed(self, selection, column):
239 self.clear_current_item()
241 (model, treeiter) = selection.get_selected()
242 if not treeiter: return
243 item = model.get_value(treeiter, column)
244 self._toolbar_presenter.set_change_read_button_state(item.is_read)
245 self.set_current_item(item)
247 def add_category(self):
248 self._feed_list_presenter.add_category()
250 def copy_itemview_text_selection(self):
251 helpers.set_clipboard_text(self._item_view.get_selected_text())
253 def check_allocation(self, widget, event):
254 def check_size((width, height, widget)):
255 if width == widget.allocation.width and height == widget.allocation.height:
256 Config.set(OPTION_WINDOW_SIZE_W, width)
257 Config.set(OPTION_WINDOW_SIZE_H, height)
259 if event.width != widget.allocation.width or event.height != widget.allocation.height:
260 gobject.timeout_add(1000, check_size, (
261 (event.width, event.height, widget)))
263 def check_main_pane_position(self, widget):
264 pass
265 """def check_position((position, widget)):
266 if position == widget.get_position():
267 Config.set(OPTION_PANE_LAYOUT, position)
269 pos = widget.get_position()
271 if pos != config.main_pane_position:
272 gobject.timeout_add(1000, check_position, (pos, widget))"""
274 def check_sub_pane_position(self, widget):
275 pass
276 """config = Config.get_instance()
277 def check_position((position, widget)):
278 if position == widget.get_position():
279 config.sub_pane_position = position
280 pos = widget.get_position()
281 if pos != config.sub_pane_position:
282 gobject.timeout_add(1000, check_position, (pos, widget))"""
284 def credits(self):
285 return helpers.credits()
287 def poll_all(self):
288 if not self._warn_if_offline():
289 return
291 if not FeedManager.is_update_all_running():
292 FeedManager.update_all_feeds({ "task-start": [ self._on_feed_poll_started ],
293 "task-done": [ self._on_feed_poll_done ] })
294 self.update_all_button.set_sensitive(True)
295 self.update_all_button.set_stock_id(gtk.STOCK_STOP)
296 else:
297 self.update_all_button.set_sensitive(False)
298 FeedManager.stop_update_all()
300 def _on_feed_poll_started(self, handler, feed):
301 pass
303 def _on_feed_poll_done(self, handler, data):
304 pass
306 def _on_update_all_done(self, obj):
307 self.update_all_button.set_sensitive(True)
308 self.update_all_button.set_stock_id(gtk.STOCK_REFRESH)
310 def poll_current_category(self):
311 if self._warn_if_offline():
312 self._poll_categories([self._curr_category])
314 def poll_current_feed(self):
315 if self._warn_if_offline():
316 print self._curr_feed
317 pm = PollManager.get_instance()
318 pm.poll([self._curr_feed])
320 def mark_feed_as_read(self):
321 self._curr_feed.mark_all_read()
323 def mark_all_as_read(self):
324 print "TODO mark_all_as_read"
326 def remove_selected_feed(self):
327 # self._feed_list_presenter.remove_selected_feed()
328 pass
330 def show_search(self):
331 # self._find_presenter.item_list.signal_connect(Event.ItemSelectionChangedSignal,
332 # self._item_view.item_selection_changed)
333 self._item_view.display_empty_search()
334 self._itemlist_view_notebook.set_current_page(1)
335 self._feedlist_view_notebook.set_current_page(1)
336 self._feedinfo_presenter.hide()
338 def hide_search(self):
339 # self._find_presenter.clear()
340 self._itemlist_view_notebook.set_current_page(0)
341 self._feedlist_view_notebook.set_current_page(0)
342 self._feedinfo_presenter.show()
343 # self._find_presenter.item_list.signal_disconnect(Event.ItemSelectionChangedSignal,
344 # self._item_view.item_selection_changed)
346 def _warn_if_offline(self):
347 offline = Config.get(OPTION_OFFLINE)
348 will_poll = False
350 if offline:
351 response = self._view.show_offline_dialog()
353 if response == gtk.RESPONSE_OK:
354 Config.set(OPTION_OFFLINE, not offline)
355 will_poll = True
356 else:
357 will_poll = True
359 return will_poll
361 def display_previous_feed(self, item = None):
363 Displays the feed before the current selected feed
365 self._feed_list_presenter.select_previous_feed()
367 def display_next_feed(self, item=None):
369 Displays the feed after the current selected feed
371 self._feed_list_presenter.select_next_feed()
372 return
374 def display_next_unread_feed(self):
376 Displays the next feed with an unread item
378 self._feed_list_presenter.select_next_feed(with_unread=True)
379 pass
381 def display_previous_item(self, item=None):
383 Displays the item before the current selected item. If the item is the
384 first item, scrolls to the previous feed
386 is_prev = self._itemlist_presenter.select_previous_item()
387 if not is_prev:
388 # TODO HACK - implement select_previous_feed(select_last=True) ...
389 # ... to select previous feed's last item
390 self._feed_list_presenter.select_previous_feed()
391 self._itemlist_presenter.select_last_item()
392 pass
393 return
395 def display_next_item(self, item=None):
397 Displays the item after the current selected item. If the item is the
398 last item, selectes the next feed. If the current feed is the last
399 feed in the list, it goes back and selects the first feed
401 is_next = self._itemlist_presenter.select_next_item()
402 if not is_next:
403 is_next_feed = self._feed_list_presenter.select_next_feed()
404 if not is_next_feed:
405 self._feed_list_presenter.select_firsteed_feed()
406 return
408 def scroll_or_display_next_unread_item(self, item=None):
409 has_unread_item = False
410 if not self._item_view.scroll_down():
411 has_unread_item = self._itemlist_presenter.select_next_unread_item()
412 if not has_unread_item:
413 self._feed_list_presenter.select_next_feed(with_unread=True)
414 return
416 def show_preferences_dialog(self, parent):
417 preferences.show()
419 def show_feed_properties(self, parent):
420 feedproperties.show(self._curr_feed)
422 def quit(self):
423 gtk.main_quit()
425 def _setup_filechooser_dialog(self, title, action, extra_widget_title):
427 Setup the file chooser dialog. This includes an extra widget (a combobox)
428 to include the categories to import or export
430 dialog = gtk.FileChooserDialog(title, action=action,
431 buttons=(gtk.STOCK_CANCEL,
432 gtk.RESPONSE_CANCEL,
433 gtk.STOCK_OK, gtk.RESPONSE_OK))
434 category_list = []
436 for category in FeedManager.categories():
437 category_list.append(category)
439 model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
440 combobox = gtk.ComboBox(model)
441 celltitle = gtk.CellRendererText()
442 combobox.pack_start(celltitle,False)
443 combobox.add_attribute(celltitle, 'text', 0)
445 for category in category_list:
446 it = model.append()
447 model.set(it, 0, category.name, 1, category)
449 combobox.set_active(0)
450 label = gtk.Label(extra_widget_title)
451 label.set_alignment(1.0,0.5)
452 hbox = gtk.HBox(spacing=6)
453 hbox.pack_start(label,True,True,0)
454 hbox.pack_end(combobox,False,False,0)
455 hbox.show_all()
457 dialog.set_extra_widget(hbox)
458 return (dialog, combobox)
460 def import_subscriptions(self, parent):
461 (dialog,combobox) = self._setup_filechooser_dialog(_("Import Subscriptions"),
462 gtk.FILE_CHOOSER_ACTION_OPEN,
463 _("Add new subscriptions in:"))
464 ffilter = gtk.FileFilter()
465 ffilter.set_name(_("OPML Files Only"))
466 ffilter.add_pattern("*.xml")
467 ffilter.add_pattern("*.opml")
468 dialog.add_filter(ffilter)
469 dialog.set_transient_for(parent)
470 response = dialog.run()
471 if response == gtk.RESPONSE_OK:
472 filename = dialog.get_filename()
473 model = combobox.get_model()
474 category = model[combobox.get_active()][1]
475 dialog.hide()
476 FeedManager.import_opml(filename, category)
477 dialog.destroy()
479 def export_subscriptions(self, parent):
480 (dialog,combobox) = self._setup_filechooser_dialog(_("Export Subscriptions"),
481 gtk.FILE_CHOOSER_ACTION_SAVE,
482 _("Select category to export:"))
483 def selection_changed(widget, dialog):
484 model = widget.get_model()
485 category = model[widget.get_active()][1]
486 dialog.set_current_name("Straw-%s.xml" % category.name)
488 combobox.connect('changed', selection_changed, dialog)
489 selection_changed(combobox, dialog)
490 dialog.set_transient_for(parent)
491 response = dialog.run()
493 if response == gtk.RESPONSE_OK:
494 filename = dialog.get_filename()
495 model = combobox.get_model()
496 root_category = model[combobox.get_active()][1]
497 FeedManager.export_opml(root_category.id, filename)
499 dialog.destroy()
501 def subscribe(self):
502 subscribe_show(parent=self.view._widget)
504 class ApplicationView(MVP.WidgetView):
506 Widget: straw_main
508 def _initialize(self):
509 self._initialize_dnd()
510 self._initialize_window_updater()
511 self._create_unmodified_accelerator_group()
512 self._attach_unmodified_accelerator_group()
513 self._initialize_window()
514 self._find_toggled = False
516 def _initialize_window(self):
517 widget_tree = gtk.glade.get_widget_tree(self._widget)
518 if Config.get(OPTION_WINDOW_MAX):
519 self._widget.maximize()
520 else:
521 # we use resize here since configure-event seems to
522 # overwrite the default size if we use set_default_size.
523 self._widget.resize(Config.get(OPTION_WINDOW_SIZE_W), Config.get(OPTION_WINDOW_SIZE_H))
524 mmp = widget_tree.get_widget('main_main_pane')
525 msp = widget_tree.get_widget('main_sub_pane')
526 #mmp.set_position(Config.main_pane_position)
527 #msp.set_position(Config.sub_pane_position)
529 def _initialize_dnd(self):
530 self._widget.drag_dest_set(
531 gtk.DEST_DEFAULT_ALL,
532 [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1),
533 ('x-url/http', 0, 2)],
534 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
535 return
537 def _initialize_window_updater(self):
538 #feedlist.signal_connect(Event.AllItemsReadSignal,
539 # lambda signal: self._update_title(feedlist))
540 #feedlist.signal_connect(Event.ItemReadSignal,
541 # lambda signal: self._update_title(feedlist))
542 #feedlist.signal_connect(Event.ItemsAddedSignal,
543 # lambda signal: self._update_title(feedlist))
544 #feedlist.signal_connect(Event.FeedsChangedSignal,
545 # lambda signal: self._update_title(feedlist))
546 pass
548 def _update_title(self, flist):
549 uritems = urfeeds = 0
550 sfeeds = "feeds"
551 listfeeds = flist.flatten_list()
552 for ur in [f.number_of_unread for f in listfeeds]:
553 if ur:
554 uritems += ur
555 urfeeds += 1
556 else:
557 urfeeds = len(listfeeds)
558 if urfeeds < 2:
559 sfeeds = "feed"
560 item_feed_map = {'uritems': uritems,
561 'urfeeds': urfeeds,
562 'fstring' : sfeeds}
563 title = _('%(uritems)d unread in %(urfeeds)d %(fstring)s') % item_feed_map
564 self._widget.set_title( title + " - %s" % straw.defs.PACKAGE)
566 # We have a separate accelerator group for the unmodified and
567 # shifted accelerators, that is, stuff like space, N, P, etc. This
568 # is so that we can have the find pane work correctly
569 def _create_unmodified_accelerator_group(self):
570 xml = gtk.glade.get_widget_tree(self._widget)
571 agroup = gtk.AccelGroup()
572 accels = (('feed_mark_as_read', 'R', gtk.gdk.SHIFT_MASK),
573 ('feed_mark_all_as_read', 'A', gtk.gdk.SHIFT_MASK),
574 ('next_item', 'N', gtk.gdk.SHIFT_MASK),
575 ('next_unread_feed', ' ', 0),
576 ('previous_item', 'P', gtk.gdk.SHIFT_MASK))
577 for widget_name, key, mask in accels:
578 widget = xml.get_widget(widget_name)
579 widget.add_accelerator("activate", agroup, ord(key), mask,
580 gtk.ACCEL_VISIBLE)
581 self._unmodified_accelerator_group = agroup
583 def _on_category_add_activate(self, *args):
584 self._presenter.add_category()
586 def _on_toolbar_change_read_state_button_toggled(self, button):
587 self._presenter.set_current_item_is_read(button.get_active())
589 def _attach_unmodified_accelerator_group(self):
590 self._widget.add_accel_group(self._unmodified_accelerator_group)
592 def _detach_unmodified_accelerator_group(self):
593 self._widget.remove_accel_group(self._unmodified_accelerator_group)
595 def _on_straw_main_destroy_event(self, *args):
596 return self._presenter.quit()
598 def _on_straw_main_delete_event(self, *args):
599 return self._presenter.quit()
601 def _on_straw_main_window_state_event(self, widget, event):
602 is_maximized = widget.window.get_state() == gtk.gdk.WINDOW_STATE_MAXIMIZED
603 Config.set(OPTION_WINDOW_MAX, is_maximized)
605 def _on_straw_main_configure_event(self, widget, event, *args):
606 #if not (widget.window.get_state() & gtk.gdk.WINDOW_STATE_MAXIMIZED):
607 # self._presenter.check_allocation(widget, event)
608 pass
610 def _on_main_main_pane_size_allocate(self, widget, *args):
611 self._presenter.check_main_pane_position(widget)
613 def _on_main_sub_pane_size_allocate(self, widget, *args):
614 self._presenter.check_sub_pane_position(widget)
616 # Actions menu
617 def _on_feed_subscribe_activate(self, *args):
618 self._presenter.subscribe()
620 def _on_feed_unsubscribe_activate(self, *args):
621 self._presenter.remove_selected_feed()
623 def _on_subscription_import_activate(self, *args):
624 self._presenter.import_subscriptions(self._widget)
626 def _on_subscription_export_activate(self, *args):
627 self._presenter.export_subscriptions(self._widget)
629 def _on_feed_mark_as_read_activate(self, *args):
630 self._presenter.mark_feed_as_read()
632 def _on_feed_mark_all_as_read_activate(self, *args):
633 self._presenter.mark_all_as_read()
635 def _on_feed_refresh_selected_activate(self, *args):
636 self._presenter.poll_current_feed()
638 def _on_subscription_refresh_activate(self, *args):
639 #print self._widget.get_children()[0].set_stock_id(gtk.STOCK_STOP)
640 self._presenter.poll_all()
642 def _on_quit_activate(self, *args):
643 return self._presenter.quit()
645 # Edit menu
646 def _on_copy_activate(self, *args):
647 self._presenter.copy_itemview_text_selection()
650 def _on_find_activate(self, widget, *args):
651 xml = gtk.glade.get_widget_tree(self._widget)
652 menu_find = xml.get_widget('menu_find')
653 accel_label = menu_find.get_child()
655 if not self._find_toggled:
656 self._presenter.show_search()
657 self._detach_unmodified_accelerator_group()
658 self._find_toggled = True
660 # save the "Find..." stock text for later recovery
661 self._old_label_text = accel_label.get_text()
662 accel_label.set_text(_('Return to feed list...'))
664 else:
665 self._presenter.hide_search()
666 self._attach_unmodified_accelerator_group()
667 self._find_toggled = False
668 accel_label.set_text(self._old_label_text)
670 def _on_preferences_activate(self, *args):
671 self._presenter.show_preferences_dialog(self._widget)
673 def _on_feed_information_activate(self, *args):
674 self._presenter.show_feed_properties(self._widget)
676 # View menu
677 def _on_previous_item_activate(self, *args):
678 self._presenter.display_previous_item()
680 def _on_next_item_activate(self, *args):
681 self._presenter.display_next_item()
683 def _on_next_feed_activate(self, *args):
684 self._presenter.display_next_feed()
686 def _on_previous_feed_activate(self, *args):
687 self._presenter.display_previous_feed()
689 def _on_next_unread_feed_activate(self, *args):
690 self._presenter.display_next_unread_feed()
692 def _on_scroll_unread_items_activate(self, *args):
693 self._presenter.scroll_or_display_next_unread_item()
695 # Help menu
697 def _on_report_problem_activate(self, menuitem, *args):
698 helpers.url_show("http://bugzilla.gnome.org/simple-bug-guide.cgi?product=straw")
700 def _on_about_activate(self, menuitem, *args):
701 widget = self._presenter.credits()
702 widget.show()
704 def _on_straw_main_drag_data_received(self, w, context,
705 x, y, data, info, time):
706 if data and data.format == 8:
707 url = data.data.split("\n")[0]
708 subscribe_show("%s" % url, self._widget)
709 context.finish(True, False, time)
710 else:
711 context.finish(False, False, time)
713 # FIXME
714 def show_offline_dialog(self):
715 return helpers.report_offline_status(self._widget)
717 def get_widget_tree(self):
718 return gtk.glade.get_widget_tree(self._widget)
720 def present(self):
721 self._widget.present()
723 def should_present(self):
724 if self._widget.window.get_state() is not gtk.gdk.WINDOW_STATE_WITHDRAWN:
725 self._widget.hide()
726 else:
727 self._widget.present()
730 class Application:
731 def __init__(self):
732 gnome.program_init(straw.defs.PACKAGE, straw.defs.VERSION)
734 error.setup_log()
735 # initialize threading and environment
736 gobject.threads_init()
738 #Config.set(OPTION_OFFLINEnfig.reload_css = os.getenv('STRAW_RELOAD_CSS') is not None
740 # build tray status icon
741 image = gtk.Image()
742 iconfile = os.path.normpath(straw.defs.STRAW_DATA_DIR + "/straw.png")
743 pixbuf = gtk.gdk.pixbuf_new_from_file(iconfile)
744 scaled_buf = pixbuf.scale_simple(16,16,gtk.gdk.INTERP_BILINEAR)
745 image.set_from_pixbuf(scaled_buf)
746 try:
747 self.tray = gtk.status_icon_new_from_pixbuf(scaled_buf)
748 self.tray.connect('activate', self._tray_clicked)
749 except AttributeError:
750 import egg
751 import egg.trayicon
752 self.tray = egg.trayicon.TrayIcon(straw.defs.PACKAGE);
753 self._eventbox = gtk.EventBox()
754 self.tray.add(self._eventbox)
755 self._eventbox.connect('button_press_event', self._tray_clicked)
756 self._eventbox.connect("drag-data-received", self._on_drag_data_received)
757 self._eventbox.drag_dest_set(
758 gtk.DEST_DEFAULT_ALL,
759 [('_NETSCAPE_URL', 0, 0),('text/uri-list ', 0, 1),('x-url/http', 0, 2)],
760 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
761 self._eventbox.add(image)
762 self.tray.show_all()
764 self._tooltip = gtk.Tooltips()
765 self.unread_count = 0
766 # end build tray status icon
768 FeedManager.setup(storage_path = "test.db")
769 FeedManager.init()
771 ItemManager.setup(storage_path = "test.db")
772 ItemManager.init()
774 #FeedManager.import_opml(os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml"))
776 #if config.first_time:
777 # filepath = os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml")
778 # feeds.import_opml(filepath)
780 #ImageCache.initialize()
782 #import feedfinder
783 #print feedfinder.feeds("http://eclipse.org", True)
785 xml = gtk.glade.XML(os.path.join(straw.defs.STRAW_DATA_DIR,'straw.glade'), "straw_main", gettext.textdomain())
786 window = xml.get_widget('straw_main')
787 self._main_presenter = ApplicationPresenter(view = ApplicationView(window))
788 self._main_presenter.view.present()
790 #PollManager.get_instance().start_polling_loop()
791 # set the default icon for the windows
792 iconfile = os.path.join(straw.defs.STRAW_DATA_DIR,"straw.png")
793 gtk.window_set_default_icon(gtk.gdk.pixbuf_new_from_file(iconfile))
795 start_services()
797 def mainloop(self):
798 gtk.gdk.threads_init()
799 gtk.gdk.threads_enter()
800 gtk.main()
801 gtk.gdk.threads_leave()
803 def _update(self,flist):
804 uritems = urfeeds = 0
805 for ur in [f.number_of_unread for f in flist.flatten_list()]:
806 if ur:
807 uritems += ur
808 urfeeds += 1
809 if uritems == self.unread_count:
810 return
811 self.unread_count = uritems
812 if uritems:
813 self._tooltip.set_tip(self.tray, _("%d new items")%uritems)
814 self.tray.show_all()
815 else:
816 self.tray.hide()
817 return
819 def _tray_clicked(self, widget, event=None):
820 self._main_presenter.view.should_present()
821 if event and not (event.button == 1):
822 return
823 self._main_presenter.scroll_or_display_next_unread_item()
825 def _on_drag_data_received(self, widget, context, x, y, data, info, timestamp):
826 if data and data.format == 8:
827 url = data.data.split("\n")[0]
828 subscribe_show(url="%s" % url)
829 context.finish(True, False, timestamp)
830 else:
831 context.finish(False, False, timestamp)
832 return