370de2d1c9fc4d148f134d0565caa44454f6f25d
[straw.git] / straw / Application.py
blob370de2d1c9fc4d148f134d0565caa44454f6f25d
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 Constants 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 not in ("horizontal", "vertical"):
179 layout = "vertical"
181 if layout == "vertical":
182 new_paned = gtk.VPaned()
183 child_list.append(item_list_container)
184 child_list.append(item_view_container)
185 elif layout == "horizontal":
186 new_paned = gtk.HPaned()
187 child_list.append(item_view_container)
188 child_list.append(item_list_container)
190 for child in child_list:
191 child.reparent(new_paned)
193 parent_parent_widget.remove(parent_paned)
194 parent_parent_widget.add(new_paned)
196 new_paned.connect("size-allocate", self._view._on_main_sub_pane_size_allocate)
197 new_paned.set_position(Config.get(OPTION_SUB_PANE_POS))
198 new_paned.show()
200 def _init_presenters(self):
201 widget_tree = self._view.get_widget_tree()
202 self._toolbar_presenter = ToolbarPresenter(view=ToolbarView(widget_tree.get_widget('toolbar_default')))
203 self._error_presenter = ErrorPresenter(widget_tree.get_widget('statusbar_error_indicator'))
205 self._item_view = ItemView(widget_tree.get_widget('item_view_container'))
207 view = ItemListView(widget_tree.get_widget('item_selection_treeview'))
208 self._itemlist_presenter = ItemListPresenter(view=view)
210 view.add_selection_changed_listener(self._item_view)
211 view.add_selection_changed_listener(self)
213 view = FeedsView(widget_tree.get_widget('feed_selection_treeview'))
214 self._feed_list_presenter = FeedsPresenter(view=view)
215 view.add_selection_changed_listener(self._itemlist_presenter)
216 view.add_selection_changed_listener(self._error_presenter)
218 self._offline_presenter = OfflineToggle(widget_tree.get_widget('offline_toggle'))
220 self._status_presenter = StatusPresenter(view = widget_tree.get_widget("main_statusbar"))
221 # self._find_presenter = FindPresenter(view=FindView(widget_tree.get_widget("find_vbox")))
223 def set_current_item(self, item):
224 self._current_item = item
225 self._current_item_connect_id = item.connect("is-read-changed", self._on_current_item_is_read_changed)
227 def clear_current_item(self):
228 if self._current_item:
229 self._current_item.disconnect(self._current_item_connect_id)
230 self._current_item = None
232 def set_current_item_is_read(self, is_read):
233 if self._current_item:
234 self._current_item.props.is_read = is_read
236 def _on_current_item_is_read_changed(self, obj, is_read):
237 self._toolbar_presenter.set_change_read_button_state(is_read)
239 def itemlist_selection_changed(self, selection, column):
240 self.clear_current_item()
242 (model, treeiter) = selection.get_selected()
243 if not treeiter: return
244 item = model.get_value(treeiter, column)
245 self._toolbar_presenter.set_change_read_button_state(item.is_read)
246 self.set_current_item(item)
248 def add_category(self):
249 self._feed_list_presenter.add_category()
251 def copy_itemview_text_selection(self):
252 helpers.set_clipboard_text(self._item_view.get_selected_text())
254 def check_allocation(self, widget, event):
255 def check_size(widget, width, height, x, y):
256 if width == widget.allocation.width and height == widget.allocation.height:
257 Config.set(OPTION_WINDOW_SIZE_W, width)
258 Config.set(OPTION_WINDOW_SIZE_H, height)
260 if (x, y) == widget.window.get_position():
261 Config.set(OPTION_WINDOW_LEFT, x)
262 Config.set(OPTION_WINDOW_TOP, y)
264 config_position = (Config.get(OPTION_WINDOW_LEFT), Config.get(OPTION_WINDOW_TOP))
266 if (event.width, event.height, event.x, event.y) != \
267 (widget.allocation.width, widget.allocation.height, config_position[0], config_position[1]):
268 gobject.timeout_add(1000, check_size, widget, event.width, event.height, event.x, event.y)
270 def check_main_pane_position(self, widget):
271 def check_position((position, widget)):
272 if position == widget.get_position():
273 Config.set(OPTION_MAIN_PANE_POS, position)
275 pos = widget.get_position()
277 if pos != Config.get(OPTION_MAIN_PANE_POS):
278 gobject.timeout_add(1000, check_position, (pos, widget))
280 def check_sub_pane_position(self, widget):
281 def check_position((position, widget)):
282 if position == widget.get_position():
283 Config.set(OPTION_SUB_PANE_POS, position)
285 pos = widget.get_position()
287 if pos != Config.get(OPTION_SUB_PANE_POS):
288 gobject.timeout_add(1000, check_position, (pos, widget))
290 def credits(self):
291 return helpers.credits()
293 def poll_all(self):
294 if not self._warn_if_offline():
295 return
297 if not FeedManager.is_update_all_running():
298 FeedManager.update_all_feeds({ "task-start": [ self._on_feed_poll_started ],
299 "task-done": [ self._on_feed_poll_done ] })
300 self.update_all_button.set_sensitive(True)
301 self.update_all_button.set_stock_id(gtk.STOCK_STOP)
302 else:
303 self.update_all_button.set_sensitive(False)
304 FeedManager.stop_update_all()
306 def _on_feed_poll_started(self, handler, feed):
307 pass
309 def _on_feed_poll_done(self, handler, data):
310 pass
312 def _on_update_all_done(self, obj):
313 self.update_all_button.set_sensitive(True)
314 self.update_all_button.set_stock_id(gtk.STOCK_REFRESH)
316 def poll_current_category(self):
317 if self._warn_if_offline():
318 self._poll_categories([self._curr_category])
320 def poll_current_feed(self):
321 if self._warn_if_offline():
322 pm = PollManager.get_instance()
323 pm.poll([self._curr_feed])
325 def mark_feed_as_read(self):
326 self._curr_feed.mark_all_read()
328 def mark_all_as_read(self):
329 print "TODO mark_all_as_read"
331 def remove_selected_feed(self):
332 # self._feed_list_presenter.remove_selected_feed()
333 pass
335 def show_search(self):
336 # self._find_presenter.item_list.signal_connect(Event.ItemSelectionChangedSignal,
337 # self._item_view.item_selection_changed)
338 self._item_view.display_empty_search()
339 self._itemlist_view_notebook.set_current_page(1)
340 self._feedlist_view_notebook.set_current_page(1)
341 self._feedinfo_presenter.hide()
343 def hide_search(self):
344 # self._find_presenter.clear()
345 self._itemlist_view_notebook.set_current_page(0)
346 self._feedlist_view_notebook.set_current_page(0)
347 self._feedinfo_presenter.show()
348 # self._find_presenter.item_list.signal_disconnect(Event.ItemSelectionChangedSignal,
349 # self._item_view.item_selection_changed)
351 def _warn_if_offline(self):
352 offline = Config.get(OPTION_OFFLINE)
353 will_poll = False
355 if offline:
356 response = self._view.show_offline_dialog()
358 if response == gtk.RESPONSE_OK:
359 Config.set(OPTION_OFFLINE, not offline)
360 will_poll = True
361 else:
362 will_poll = True
364 return will_poll
366 def display_previous_feed(self, item = None):
368 Displays the feed before the current selected feed
370 self._feed_list_presenter.select_previous_feed()
372 def display_next_feed(self, item=None):
374 Displays the feed after the current selected feed
376 self._feed_list_presenter.select_next_feed()
377 return
379 def display_next_unread_feed(self):
381 Displays the next feed with an unread item
383 self._feed_list_presenter.select_next_feed(with_unread=True)
384 pass
386 def display_previous_item(self, item=None):
388 Displays the item before the current selected item. If the item is the
389 first item, scrolls to the previous feed
391 is_prev = self._itemlist_presenter.select_previous_item()
392 if not is_prev:
393 # TODO HACK - implement select_previous_feed(select_last=True) ...
394 # ... to select previous feed's last item
395 self._feed_list_presenter.select_previous_feed()
396 self._itemlist_presenter.select_last_item()
397 pass
398 return
400 def display_next_item(self, item=None):
402 Displays the item after the current selected item. If the item is the
403 last item, selectes the next feed. If the current feed is the last
404 feed in the list, it goes back and selects the first feed
406 is_next = self._itemlist_presenter.select_next_item()
407 if not is_next:
408 is_next_feed = self._feed_list_presenter.select_next_feed()
409 if not is_next_feed:
410 self._feed_list_presenter.select_firsteed_feed()
411 return
413 def scroll_or_display_next_unread_item(self, item=None):
414 has_unread_item = False
415 if not self._item_view.scroll_down():
416 has_unread_item = self._itemlist_presenter.select_next_unread_item()
417 if not has_unread_item:
418 self._feed_list_presenter.select_next_feed(with_unread=True)
419 return
421 def show_preferences_dialog(self, parent):
422 preferences.show()
424 def show_feed_properties(self, parent):
425 feedproperties.show(self._curr_feed)
427 def quit(self):
428 gtk.main_quit()
430 def _setup_filechooser_dialog(self, title, action, extra_widget_title):
432 Setup the file chooser dialog. This includes an extra widget (a combobox)
433 to include the categories to import or export
435 dialog = gtk.FileChooserDialog(title, action=action,
436 buttons=(gtk.STOCK_CANCEL,
437 gtk.RESPONSE_CANCEL,
438 gtk.STOCK_OK, gtk.RESPONSE_OK))
439 category_list = []
441 for category in FeedManager.categories():
442 category_list.append(category)
444 model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
445 combobox = gtk.ComboBox(model)
446 celltitle = gtk.CellRendererText()
447 combobox.pack_start(celltitle,False)
448 combobox.add_attribute(celltitle, 'text', 0)
450 for category in category_list:
451 it = model.append()
452 model.set(it, 0, category.name, 1, category)
454 combobox.set_active(0)
455 label = gtk.Label(extra_widget_title)
456 label.set_alignment(1.0,0.5)
457 hbox = gtk.HBox(spacing=6)
458 hbox.pack_start(label,True,True,0)
459 hbox.pack_end(combobox,False,False,0)
460 hbox.show_all()
462 dialog.set_extra_widget(hbox)
463 return (dialog, combobox)
465 def import_subscriptions(self, parent):
466 (dialog,combobox) = self._setup_filechooser_dialog(_("Import Subscriptions"),
467 gtk.FILE_CHOOSER_ACTION_OPEN,
468 _("Add new subscriptions in:"))
469 ffilter = gtk.FileFilter()
470 ffilter.set_name(_("OPML Files Only"))
471 ffilter.add_pattern("*.xml")
472 ffilter.add_pattern("*.opml")
473 dialog.add_filter(ffilter)
474 dialog.set_transient_for(parent)
475 response = dialog.run()
476 if response == gtk.RESPONSE_OK:
477 filename = dialog.get_filename()
478 model = combobox.get_model()
479 category = model[combobox.get_active()][1]
480 dialog.hide()
481 FeedManager.import_opml(filename, category)
482 dialog.destroy()
484 def export_subscriptions(self, parent):
485 (dialog,combobox) = self._setup_filechooser_dialog(_("Export Subscriptions"),
486 gtk.FILE_CHOOSER_ACTION_SAVE,
487 _("Select category to export:"))
488 def selection_changed(widget, dialog):
489 model = widget.get_model()
490 category = model[widget.get_active()][1]
491 dialog.set_current_name("Straw-%s.xml" % category.name)
493 combobox.connect('changed', selection_changed, dialog)
494 selection_changed(combobox, dialog)
495 dialog.set_transient_for(parent)
496 response = dialog.run()
498 if response == gtk.RESPONSE_OK:
499 filename = dialog.get_filename()
500 model = combobox.get_model()
501 root_category = model[combobox.get_active()][1]
502 FeedManager.export_opml(root_category.id, filename)
504 dialog.destroy()
506 def subscribe(self):
507 subscribe_show(parent=self.view._widget)
509 class ApplicationView(MVP.WidgetView):
511 Widget: straw_main
513 def _initialize(self):
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 Config.get(OPTION_WINDOW_MAX):
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.move(Config.get(OPTION_WINDOW_LEFT), Config.get(OPTION_WINDOW_TOP))
529 #self._widget.resize(Config.get(OPTION_WINDOW_SIZE_W), Config.get(OPTION_WINDOW_SIZE_H))
530 self._widget.window.move_resize(Config.get(OPTION_WINDOW_LEFT), Config.get(OPTION_WINDOW_TOP),
531 Config.get(OPTION_WINDOW_SIZE_W), Config.get(OPTION_WINDOW_SIZE_H))
532 self.mmp = widget_tree.get_widget('main_main_pane')
533 self.msp = widget_tree.get_widget('main_sub_pane')
535 self.mmp.set_position(Config.get(OPTION_MAIN_PANE_POS))
536 self.msp.set_position(Config.get(OPTION_SUB_PANE_POS))
538 def _initialize_dnd(self):
539 self._widget.drag_dest_set(
540 gtk.DEST_DEFAULT_ALL,
541 [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1),
542 ('x-url/http', 0, 2)],
543 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
544 return
546 def _initialize_window_updater(self):
547 #feedlist.signal_connect(Event.AllItemsReadSignal,
548 # lambda signal: self._update_title(feedlist))
549 #feedlist.signal_connect(Event.ItemReadSignal,
550 # lambda signal: self._update_title(feedlist))
551 #feedlist.signal_connect(Event.ItemsAddedSignal,
552 # lambda signal: self._update_title(feedlist))
553 #feedlist.signal_connect(Event.FeedsChangedSignal,
554 # lambda signal: self._update_title(feedlist))
555 pass
557 def _update_title(self, flist):
558 uritems = urfeeds = 0
559 sfeeds = "feeds"
560 listfeeds = flist.flatten_list()
561 for ur in [f.number_of_unread for f in listfeeds]:
562 if ur:
563 uritems += ur
564 urfeeds += 1
565 else:
566 urfeeds = len(listfeeds)
567 if urfeeds < 2:
568 sfeeds = "feed"
569 item_feed_map = {'uritems': uritems,
570 'urfeeds': urfeeds,
571 'fstring' : sfeeds}
572 title = _('%(uritems)d unread in %(urfeeds)d %(fstring)s') % item_feed_map
573 self._widget.set_title( title + " - %s" % straw.defs.PACKAGE)
575 # We have a separate accelerator group for the unmodified and
576 # shifted accelerators, that is, stuff like space, N, P, etc. This
577 # is so that we can have the find pane work correctly
578 def _create_unmodified_accelerator_group(self):
579 xml = gtk.glade.get_widget_tree(self._widget)
580 agroup = gtk.AccelGroup()
581 accels = (('feed_mark_as_read', 'R', gtk.gdk.SHIFT_MASK),
582 ('feed_mark_all_as_read', 'A', gtk.gdk.SHIFT_MASK),
583 ('next_item', 'N', gtk.gdk.SHIFT_MASK),
584 ('next_unread_feed', ' ', 0),
585 ('previous_item', 'P', gtk.gdk.SHIFT_MASK))
586 for widget_name, key, mask in accels:
587 widget = xml.get_widget(widget_name)
588 widget.add_accelerator("activate", agroup, ord(key), mask,
589 gtk.ACCEL_VISIBLE)
590 self._unmodified_accelerator_group = agroup
592 def _on_category_add_activate(self, *args):
593 self._presenter.add_category()
595 def _on_toolbar_change_read_state_button_toggled(self, button):
596 self._presenter.set_current_item_is_read(button.get_active())
598 def _attach_unmodified_accelerator_group(self):
599 self._widget.add_accel_group(self._unmodified_accelerator_group)
601 def _detach_unmodified_accelerator_group(self):
602 self._widget.remove_accel_group(self._unmodified_accelerator_group)
604 def _on_straw_main_destroy_event(self, *args):
605 return self._presenter.quit()
607 def _on_straw_main_delete_event(self, *args):
608 return self._presenter.quit()
610 def _on_straw_main_window_state_event(self, widget, event):
611 is_maximized = widget.window.get_state() == gtk.gdk.WINDOW_STATE_MAXIMIZED
612 Config.set(OPTION_WINDOW_MAX, is_maximized)
614 def _on_straw_main_configure_event(self, widget, event, *args):
615 if not (widget.window.get_state() & gtk.gdk.WINDOW_STATE_MAXIMIZED):
616 self._presenter.check_allocation(widget, event)
618 def _on_main_main_pane_size_allocate(self, widget, *args):
619 self._presenter.check_main_pane_position(widget)
621 def _on_main_sub_pane_size_allocate(self, widget, *args):
622 self._presenter.check_sub_pane_position(widget)
624 # Actions menu
625 def _on_feed_subscribe_activate(self, *args):
626 self._presenter.subscribe()
628 def _on_feed_unsubscribe_activate(self, *args):
629 self._presenter.remove_selected_feed()
631 def _on_subscription_import_activate(self, *args):
632 self._presenter.import_subscriptions(self._widget)
634 def _on_subscription_export_activate(self, *args):
635 self._presenter.export_subscriptions(self._widget)
637 def _on_feed_mark_as_read_activate(self, *args):
638 self._presenter.mark_feed_as_read()
640 def _on_feed_mark_all_as_read_activate(self, *args):
641 self._presenter.mark_all_as_read()
643 def _on_feed_refresh_selected_activate(self, *args):
644 self._presenter.poll_current_feed()
646 def _on_subscription_refresh_activate(self, *args):
647 self._presenter.poll_all()
649 def _on_quit_activate(self, *args):
650 return self._presenter.quit()
652 # Edit menu
653 def _on_copy_activate(self, *args):
654 self._presenter.copy_itemview_text_selection()
656 def _on_find_activate(self, widget, *args):
657 xml = gtk.glade.get_widget_tree(self._widget)
658 menu_find = xml.get_widget('menu_find')
659 accel_label = menu_find.get_child()
661 if not self._find_toggled:
662 self._presenter.show_search()
663 self._detach_unmodified_accelerator_group()
664 self._find_toggled = True
666 # save the "Find..." stock text for later recovery
667 self._old_label_text = accel_label.get_text()
668 accel_label.set_text(_('Return to feed list...'))
670 else:
671 self._presenter.hide_search()
672 self._attach_unmodified_accelerator_group()
673 self._find_toggled = False
674 accel_label.set_text(self._old_label_text)
676 def _on_preferences_activate(self, *args):
677 self._presenter.show_preferences_dialog(self._widget)
679 def _on_feed_information_activate(self, *args):
680 self._presenter.show_feed_properties(self._widget)
682 # View menu
683 def _on_previous_item_activate(self, *args):
684 self._presenter.display_previous_item()
686 def _on_next_item_activate(self, *args):
687 self._presenter.display_next_item()
689 def _on_next_feed_activate(self, *args):
690 self._presenter.display_next_feed()
692 def _on_previous_feed_activate(self, *args):
693 self._presenter.display_previous_feed()
695 def _on_next_unread_feed_activate(self, *args):
696 self._presenter.display_next_unread_feed()
698 def _on_scroll_unread_items_activate(self, *args):
699 self._presenter.scroll_or_display_next_unread_item()
701 # Help menu
703 def _on_report_problem_activate(self, menuitem, *args):
704 helpers.url_show("http://bugzilla.gnome.org/simple-bug-guide.cgi?product=straw")
706 def _on_about_activate(self, menuitem, *args):
707 widget = self._presenter.credits()
708 widget.show()
710 def _on_straw_main_drag_data_received(self, w, context,
711 x, y, data, info, time):
712 if data and data.format == 8:
713 url = data.data.split("\n")[0]
714 subscribe_show("%s" % url, self._widget)
715 context.finish(True, False, time)
716 else:
717 context.finish(False, False, time)
719 # FIXME
720 def show_offline_dialog(self):
721 return helpers.report_offline_status(self._widget)
723 def get_widget_tree(self):
724 return gtk.glade.get_widget_tree(self._widget)
726 def present(self):
727 self._widget.present()
729 def should_present(self):
730 if self._widget.window.get_state() is not gtk.gdk.WINDOW_STATE_WITHDRAWN:
731 self._widget.hide()
732 else:
733 self._widget.present()
736 class Application:
737 def __init__(self):
738 gnome.program_init(straw.defs.PACKAGE, straw.defs.VERSION)
740 error.setup_log()
741 # initialize threading and environment
742 gobject.threads_init()
744 #Config.set(OPTION_OFFLINEnfig.reload_css = os.getenv('STRAW_RELOAD_CSS') is not None
746 # build tray status icon
747 image = gtk.Image()
748 iconfile = os.path.normpath(straw.defs.STRAW_DATA_DIR + "/straw.png")
749 pixbuf = gtk.gdk.pixbuf_new_from_file(iconfile)
750 scaled_buf = pixbuf.scale_simple(16,16,gtk.gdk.INTERP_BILINEAR)
751 image.set_from_pixbuf(scaled_buf)
752 try:
753 self.tray = gtk.status_icon_new_from_pixbuf(scaled_buf)
754 self.tray.connect('activate', self._tray_clicked)
755 except AttributeError:
756 import egg
757 import egg.trayicon
758 self.tray = egg.trayicon.TrayIcon(straw.defs.PACKAGE);
759 self._eventbox = gtk.EventBox()
760 self.tray.add(self._eventbox)
761 self._eventbox.connect('button_press_event', self._tray_clicked)
762 self._eventbox.connect("drag-data-received", self._on_drag_data_received)
763 self._eventbox.drag_dest_set(
764 gtk.DEST_DEFAULT_ALL,
765 [('_NETSCAPE_URL', 0, 0),('text/uri-list ', 0, 1),('x-url/http', 0, 2)],
766 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
767 self._eventbox.add(image)
768 self.tray.show_all()
770 self._tooltip = gtk.Tooltips()
771 self.unread_count = 0
772 # end build tray status icon
774 FeedManager.setup(storage_path = "test.db")
775 FeedManager.init()
777 ItemManager.setup(storage_path = "test.db")
778 ItemManager.init()
780 #FeedManager.import_opml(os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml"))
782 #if config.first_time:
783 # filepath = os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml")
784 # feeds.import_opml(filepath)
786 #ImageCache.initialize()
788 xml = gtk.glade.XML(os.path.join(straw.defs.STRAW_DATA_DIR,'straw.glade'), "straw_main", gettext.textdomain())
789 window = xml.get_widget('straw_main')
790 self._main_presenter = ApplicationPresenter(view = ApplicationView(window))
791 self._main_presenter.view.present()
793 self._main_presenter.view._widget.window.move_resize(Config.get(OPTION_WINDOW_LEFT), Config.get(OPTION_WINDOW_TOP),
794 Config.get(OPTION_WINDOW_SIZE_W), Config.get(OPTION_WINDOW_SIZE_H))
796 #PollManager.get_instance().start_polling_loop()
797 # set the default icon for the windows
798 iconfile = os.path.join(straw.defs.STRAW_DATA_DIR,"straw.png")
799 gtk.window_set_default_icon(gtk.gdk.pixbuf_new_from_file(iconfile))
801 start_services()
803 def mainloop(self):
804 gtk.gdk.threads_init()
805 gtk.gdk.threads_enter()
806 gtk.main()
807 gtk.gdk.threads_leave()
809 def _update(self,flist):
810 uritems = urfeeds = 0
811 for ur in [f.number_of_unread for f in flist.flatten_list()]:
812 if ur:
813 uritems += ur
814 urfeeds += 1
815 if uritems == self.unread_count:
816 return
817 self.unread_count = uritems
818 if uritems:
819 self._tooltip.set_tip(self.tray, _("%d new items")%uritems)
820 self.tray.show_all()
821 else:
822 self.tray.hide()
823 return
825 def _tray_clicked(self, widget, event=None):
826 self._main_presenter.view.should_present()
828 if event and not (event.button == 1):
829 return
830 self._main_presenter.scroll_or_display_next_unread_item()
832 def _on_drag_data_received(self, widget, context, x, y, data, info, timestamp):
833 if data and data.format == 8:
834 url = data.data.split("\n")[0]
835 subscribe_show(url="%s" % url)
836 context.finish(True, False, timestamp)
837 else:
838 context.finish(False, False, timestamp)
839 return