6da19a649bc8d6341705944817449c1bd05738d4
[straw.git] / straw / Application.py
blob6da19a649bc8d6341705944817449c1bd05738d4
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._restore_state()
160 self._view.present()
162 def _init_widgets(self):
163 widget_tree = self._view.get_widget_tree()
165 self._itemlist_view_notebook = widget_tree.get_widget('mode_view_notebook')
166 self._feedlist_view_notebook = widget_tree.get_widget('left_mode_view_notebook')
168 item_view_container = widget_tree.get_widget('item_view_container')
169 item_list_container = widget_tree.get_widget('item_list_container')
171 self.update_all_button = widget_tree.get_widget("toolbar_refresh_all_button")
173 parent_paned = item_view_container.get_parent()
174 parent_parent_widget = parent_paned.get_parent()
176 child_list = []
177 layout = Config.get(OPTION_PANE_LAYOUT)
179 if layout not in ("horizontal", "vertical"):
180 layout = "vertical"
182 if layout == "vertical":
183 new_paned = gtk.VPaned()
184 child_list.append(item_list_container)
185 child_list.append(item_view_container)
186 elif layout == "horizontal":
187 new_paned = gtk.HPaned()
188 child_list.append(item_view_container)
189 child_list.append(item_list_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.connect("size-allocate", self._view._on_main_sub_pane_size_allocate)
198 new_paned.set_position(Config.get(OPTION_SUB_PANE_POS))
199 new_paned.show()
201 def _init_presenters(self):
202 widget_tree = self._view.get_widget_tree()
203 self._toolbar_presenter = ToolbarPresenter(view=ToolbarView(widget_tree.get_widget('toolbar_default')))
204 self._error_presenter = ErrorPresenter(widget_tree.get_widget('statusbar_error_indicator'))
206 self._item_view = ItemView(widget_tree.get_widget('item_view_container'))
208 view = ItemListView(widget_tree.get_widget('item_selection_treeview'))
209 self._itemlist_presenter = ItemListPresenter(view=view)
211 view.add_selection_changed_listener(self._item_view)
212 view.add_selection_changed_listener(self)
214 view = FeedsView(widget_tree.get_widget('feed_selection_treeview'))
215 self._feed_list_presenter = FeedsPresenter(view=view)
216 view.add_selection_changed_listener(self._itemlist_presenter)
217 view.add_selection_changed_listener(self._error_presenter)
219 self._offline_presenter = OfflineToggle(widget_tree.get_widget('offline_toggle'))
221 self._status_presenter = StatusPresenter(view = widget_tree.get_widget("main_statusbar"))
222 # self._find_presenter = FindPresenter(view=FindView(widget_tree.get_widget("find_vbox")))
224 def set_current_item(self, item):
225 self._current_item = item
226 self._current_item_connect_id = item.connect("is-read-changed", self._on_current_item_is_read_changed)
228 def _restore_state(self):
229 self._feed_list_presenter.restore_state()
231 def clear_current_item(self):
232 if self._current_item:
233 self._current_item.disconnect(self._current_item_connect_id)
234 self._current_item = None
236 def set_current_item_is_read(self, is_read):
237 if self._current_item:
238 self._current_item.props.is_read = is_read
240 def _on_current_item_is_read_changed(self, obj, is_read):
241 self._toolbar_presenter.set_change_read_button_state(is_read)
243 def itemlist_selection_changed(self, selection, column):
244 self.clear_current_item()
246 (model, treeiter) = selection.get_selected()
247 if not treeiter: return
248 item = model.get_value(treeiter, column)
249 self._toolbar_presenter.set_change_read_button_state(item.is_read)
250 self.set_current_item(item)
252 def add_category(self):
253 self._feed_list_presenter.add_category()
255 def copy_itemview_text_selection(self):
256 helpers.set_clipboard_text(self._item_view.get_selected_text())
258 def check_allocation(self, widget, event):
259 def check_size(widget, width, height, x, y):
260 if width == widget.allocation.width and height == widget.allocation.height:
261 Config.set(OPTION_WINDOW_SIZE_W, width)
262 Config.set(OPTION_WINDOW_SIZE_H, height)
264 if widget.window and (x, y) == widget.window.get_position():
265 Config.set(OPTION_WINDOW_LEFT, x)
266 Config.set(OPTION_WINDOW_TOP, y)
268 config_position = (Config.get(OPTION_WINDOW_LEFT), Config.get(OPTION_WINDOW_TOP))
270 if (event.width, event.height, event.x, event.y) != \
271 (widget.allocation.width, widget.allocation.height, config_position[0], config_position[1]):
272 gobject.timeout_add(1000, check_size, widget, event.width, event.height, event.x, event.y)
274 def check_main_pane_position(self, widget):
275 def check_position((position, widget)):
276 if position == widget.get_position():
277 Config.set(OPTION_MAIN_PANE_POS, position)
279 pos = widget.get_position()
281 if pos != Config.get(OPTION_MAIN_PANE_POS):
282 gobject.timeout_add(1000, check_position, (pos, widget))
284 def check_sub_pane_position(self, widget):
285 def check_position((position, widget)):
286 if position == widget.get_position():
287 Config.set(OPTION_SUB_PANE_POS, position)
289 pos = widget.get_position()
291 if pos != Config.get(OPTION_SUB_PANE_POS):
292 gobject.timeout_add(1000, check_position, (pos, widget))
294 def credits(self):
295 return helpers.credits()
297 def poll_all(self):
298 if not self._warn_if_offline():
299 return
301 if not FeedManager.is_update_all_running():
302 FeedManager.update_all_feeds({ "task-start": [ self._on_feed_poll_started ],
303 "task-done": [ self._on_feed_poll_done ] })
304 self.update_all_button.set_sensitive(True)
305 self.update_all_button.set_stock_id(gtk.STOCK_STOP)
306 else:
307 self.update_all_button.set_sensitive(False)
308 FeedManager.stop_update_all()
310 def _on_feed_poll_started(self, handler, feed):
311 pass
313 def _on_feed_poll_done(self, handler, data):
314 pass
316 def _on_update_all_done(self, obj):
317 self.update_all_button.set_sensitive(True)
318 self.update_all_button.set_stock_id(gtk.STOCK_REFRESH)
320 def poll_current_category(self):
321 if self._warn_if_offline():
322 self._poll_categories([self._curr_category])
324 def poll_current_feed(self):
325 if self._warn_if_offline():
326 pm = PollManager.get_instance()
327 pm.poll([self._curr_feed])
329 def mark_feed_as_read(self):
330 self._curr_feed.mark_all_read()
332 def mark_all_as_read(self):
333 print "TODO mark_all_as_read"
335 def remove_selected_feed(self):
336 # self._feed_list_presenter.remove_selected_feed()
337 pass
339 def show_search(self):
340 # self._find_presenter.item_list.signal_connect(Event.ItemSelectionChangedSignal,
341 # self._item_view.item_selection_changed)
342 self._item_view.display_empty_search()
343 self._itemlist_view_notebook.set_current_page(1)
344 self._feedlist_view_notebook.set_current_page(1)
345 self._feedinfo_presenter.hide()
347 def hide_search(self):
348 # self._find_presenter.clear()
349 self._itemlist_view_notebook.set_current_page(0)
350 self._feedlist_view_notebook.set_current_page(0)
351 self._feedinfo_presenter.show()
352 # self._find_presenter.item_list.signal_disconnect(Event.ItemSelectionChangedSignal,
353 # self._item_view.item_selection_changed)
355 def _warn_if_offline(self):
356 offline = Config.get(OPTION_OFFLINE)
357 will_poll = False
359 if offline:
360 response = self._view.show_offline_dialog()
362 if response == gtk.RESPONSE_OK:
363 Config.set(OPTION_OFFLINE, not offline)
364 will_poll = True
365 else:
366 will_poll = True
368 return will_poll
370 def display_previous_feed(self, item = None):
372 Displays the feed before the current selected feed
374 self._feed_list_presenter.select_previous_feed()
376 def display_next_feed(self, item=None):
378 Displays the feed after the current selected feed
380 self._feed_list_presenter.select_next_feed()
381 return
383 def display_next_unread_feed(self):
385 Displays the next feed with an unread item
387 self._feed_list_presenter.select_next_feed(with_unread=True)
388 pass
390 def display_previous_item(self, item=None):
392 Displays the item before the current selected item. If the item is the
393 first item, scrolls to the previous feed
395 is_prev = self._itemlist_presenter.select_previous_item()
396 if not is_prev:
397 # TODO HACK - implement select_previous_feed(select_last=True) ...
398 # ... to select previous feed's last item
399 self._feed_list_presenter.select_previous_feed()
400 self._itemlist_presenter.select_last_item()
401 pass
402 return
404 def display_next_item(self, item=None):
406 Displays the item after the current selected item. If the item is the
407 last item, selectes the next feed. If the current feed is the last
408 feed in the list, it goes back and selects the first feed
410 is_next = self._itemlist_presenter.select_next_item()
411 if not is_next:
412 is_next_feed = self._feed_list_presenter.select_next_feed()
413 if not is_next_feed:
414 self._feed_list_presenter.select_firsteed_feed()
415 return
417 def scroll_or_display_next_unread_item(self, item=None):
418 has_unread_item = False
419 if not self._item_view.scroll_down():
420 has_unread_item = self._itemlist_presenter.select_next_unread_item()
421 if not has_unread_item:
422 self._feed_list_presenter.select_next_feed(with_unread=True)
423 return
425 def show_preferences_dialog(self, parent):
426 preferences.show()
428 def show_feed_properties(self, parent):
429 feedproperties.show(self._curr_feed)
431 def quit(self):
432 self._feed_list_presenter.store_state()
433 gtk.main_quit()
435 def _setup_filechooser_dialog(self, title, action, extra_widget_title):
437 Setup the file chooser dialog. This includes an extra widget (a combobox)
438 to include the categories to import or export
440 dialog = gtk.FileChooserDialog(title, action=action,
441 buttons=(gtk.STOCK_CANCEL,
442 gtk.RESPONSE_CANCEL,
443 gtk.STOCK_OK, gtk.RESPONSE_OK))
444 category_list = []
446 for category in FeedManager.categories():
447 category_list.append(category)
449 model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
450 combobox = gtk.ComboBox(model)
451 celltitle = gtk.CellRendererText()
452 combobox.pack_start(celltitle,False)
453 combobox.add_attribute(celltitle, 'text', 0)
455 for category in category_list:
456 it = model.append()
457 model.set(it, 0, category.name, 1, category)
459 combobox.set_active(0)
460 label = gtk.Label(extra_widget_title)
461 label.set_alignment(1.0,0.5)
462 hbox = gtk.HBox(spacing=6)
463 hbox.pack_start(label,True,True,0)
464 hbox.pack_end(combobox,False,False,0)
465 hbox.show_all()
467 dialog.set_extra_widget(hbox)
468 return (dialog, combobox)
470 def import_subscriptions(self, parent):
471 (dialog,combobox) = self._setup_filechooser_dialog(_("Import Subscriptions"),
472 gtk.FILE_CHOOSER_ACTION_OPEN,
473 _("Add new subscriptions in:"))
474 ffilter = gtk.FileFilter()
475 ffilter.set_name(_("OPML Files Only"))
476 ffilter.add_pattern("*.xml")
477 ffilter.add_pattern("*.opml")
478 dialog.add_filter(ffilter)
479 dialog.set_transient_for(parent)
480 response = dialog.run()
481 if response == gtk.RESPONSE_OK:
482 filename = dialog.get_filename()
483 model = combobox.get_model()
484 category = model[combobox.get_active()][1]
485 dialog.hide()
486 FeedManager.import_opml(filename, category)
487 dialog.destroy()
489 def export_subscriptions(self, parent):
490 (dialog,combobox) = self._setup_filechooser_dialog(_("Export Subscriptions"),
491 gtk.FILE_CHOOSER_ACTION_SAVE,
492 _("Select category to export:"))
493 def selection_changed(widget, dialog):
494 model = widget.get_model()
495 category = model[widget.get_active()][1]
496 dialog.set_current_name("Straw-%s.xml" % category.name)
498 combobox.connect('changed', selection_changed, dialog)
499 selection_changed(combobox, dialog)
500 dialog.set_transient_for(parent)
501 response = dialog.run()
503 if response == gtk.RESPONSE_OK:
504 filename = dialog.get_filename()
505 model = combobox.get_model()
506 root_category = model[combobox.get_active()][1]
507 FeedManager.export_opml(root_category.id, filename)
509 dialog.destroy()
511 def subscribe(self):
512 subscribe_show(parent=self.view._widget)
514 class ApplicationView(MVP.WidgetView):
516 Widget: straw_main
518 def _initialize(self):
519 self._initialize_dnd()
520 self._initialize_window_updater()
521 self._create_unmodified_accelerator_group()
522 self._attach_unmodified_accelerator_group()
523 self._initialize_window()
524 self._find_toggled = False
526 def _initialize_window(self):
527 widget_tree = gtk.glade.get_widget_tree(self._widget)
528 if Config.get(OPTION_WINDOW_MAX):
529 self._widget.maximize()
530 else:
531 # we use resize here since configure-event seems to
532 # overwrite the default size if we use set_default_size.
533 #self._widget.move(Config.get(OPTION_WINDOW_LEFT), Config.get(OPTION_WINDOW_TOP))
534 #self._widget.resize(Config.get(OPTION_WINDOW_SIZE_W), Config.get(OPTION_WINDOW_SIZE_H))
535 self._widget.window.move_resize(Config.get(OPTION_WINDOW_LEFT), Config.get(OPTION_WINDOW_TOP),
536 Config.get(OPTION_WINDOW_SIZE_W), Config.get(OPTION_WINDOW_SIZE_H))
537 self.mmp = widget_tree.get_widget('main_main_pane')
538 self.msp = widget_tree.get_widget('main_sub_pane')
540 self.mmp.set_position(Config.get(OPTION_MAIN_PANE_POS))
541 self.msp.set_position(Config.get(OPTION_SUB_PANE_POS))
543 def _initialize_dnd(self):
544 self._widget.drag_dest_set(
545 gtk.DEST_DEFAULT_ALL,
546 [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1),
547 ('x-url/http', 0, 2)],
548 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
549 return
551 def _initialize_window_updater(self):
552 #feedlist.signal_connect(Event.AllItemsReadSignal,
553 # lambda signal: self._update_title(feedlist))
554 #feedlist.signal_connect(Event.ItemReadSignal,
555 # lambda signal: self._update_title(feedlist))
556 #feedlist.signal_connect(Event.ItemsAddedSignal,
557 # lambda signal: self._update_title(feedlist))
558 #feedlist.signal_connect(Event.FeedsChangedSignal,
559 # lambda signal: self._update_title(feedlist))
560 pass
562 def _update_title(self, flist):
563 uritems = urfeeds = 0
564 sfeeds = "feeds"
565 listfeeds = flist.flatten_list()
566 for ur in [f.number_of_unread for f in listfeeds]:
567 if ur:
568 uritems += ur
569 urfeeds += 1
570 else:
571 urfeeds = len(listfeeds)
572 if urfeeds < 2:
573 sfeeds = "feed"
574 item_feed_map = {'uritems': uritems,
575 'urfeeds': urfeeds,
576 'fstring' : sfeeds}
577 title = _('%(uritems)d unread in %(urfeeds)d %(fstring)s') % item_feed_map
578 self._widget.set_title( title + " - %s" % straw.defs.PACKAGE)
580 # We have a separate accelerator group for the unmodified and
581 # shifted accelerators, that is, stuff like space, N, P, etc. This
582 # is so that we can have the find pane work correctly
583 def _create_unmodified_accelerator_group(self):
584 xml = gtk.glade.get_widget_tree(self._widget)
585 agroup = gtk.AccelGroup()
586 accels = (('feed_mark_as_read', 'R', gtk.gdk.SHIFT_MASK),
587 ('feed_mark_all_as_read', 'A', gtk.gdk.SHIFT_MASK),
588 ('next_item', 'N', gtk.gdk.SHIFT_MASK),
589 ('next_unread_feed', ' ', 0),
590 ('previous_item', 'P', gtk.gdk.SHIFT_MASK))
591 for widget_name, key, mask in accels:
592 widget = xml.get_widget(widget_name)
593 widget.add_accelerator("activate", agroup, ord(key), mask,
594 gtk.ACCEL_VISIBLE)
595 self._unmodified_accelerator_group = agroup
597 def _on_category_add_activate(self, *args):
598 self._presenter.add_category()
600 def _on_toolbar_change_read_state_button_toggled(self, button):
601 self._presenter.set_current_item_is_read(button.get_active())
603 def _attach_unmodified_accelerator_group(self):
604 self._widget.add_accel_group(self._unmodified_accelerator_group)
606 def _detach_unmodified_accelerator_group(self):
607 self._widget.remove_accel_group(self._unmodified_accelerator_group)
609 def _on_straw_main_destroy_event(self, *args):
610 return self._presenter.quit()
612 def _on_straw_main_delete_event(self, *args):
613 return self._presenter.quit()
615 def _on_straw_main_window_state_event(self, widget, event):
616 is_maximized = widget.window.get_state() == gtk.gdk.WINDOW_STATE_MAXIMIZED
617 Config.set(OPTION_WINDOW_MAX, is_maximized)
619 def _on_straw_main_configure_event(self, widget, event, *args):
620 if not (widget.window.get_state() & gtk.gdk.WINDOW_STATE_MAXIMIZED):
621 self._presenter.check_allocation(widget, event)
623 def _on_main_main_pane_size_allocate(self, widget, *args):
624 self._presenter.check_main_pane_position(widget)
626 def _on_main_sub_pane_size_allocate(self, widget, *args):
627 self._presenter.check_sub_pane_position(widget)
629 # Actions menu
630 def _on_feed_subscribe_activate(self, *args):
631 self._presenter.subscribe()
633 def _on_feed_unsubscribe_activate(self, *args):
634 self._presenter.remove_selected_feed()
636 def _on_subscription_import_activate(self, *args):
637 self._presenter.import_subscriptions(self._widget)
639 def _on_subscription_export_activate(self, *args):
640 self._presenter.export_subscriptions(self._widget)
642 def _on_feed_mark_as_read_activate(self, *args):
643 self._presenter.mark_feed_as_read()
645 def _on_feed_mark_all_as_read_activate(self, *args):
646 self._presenter.mark_all_as_read()
648 def _on_feed_refresh_selected_activate(self, *args):
649 self._presenter.poll_current_feed()
651 def _on_subscription_refresh_activate(self, *args):
652 self._presenter.poll_all()
654 def _on_quit_activate(self, *args):
655 return self._presenter.quit()
657 # Edit menu
658 def _on_copy_activate(self, *args):
659 self._presenter.copy_itemview_text_selection()
661 def _on_find_activate(self, widget, *args):
662 xml = gtk.glade.get_widget_tree(self._widget)
663 menu_find = xml.get_widget('menu_find')
664 accel_label = menu_find.get_child()
666 if not self._find_toggled:
667 self._presenter.show_search()
668 self._detach_unmodified_accelerator_group()
669 self._find_toggled = True
671 # save the "Find..." stock text for later recovery
672 self._old_label_text = accel_label.get_text()
673 accel_label.set_text(_('Return to feed list...'))
675 else:
676 self._presenter.hide_search()
677 self._attach_unmodified_accelerator_group()
678 self._find_toggled = False
679 accel_label.set_text(self._old_label_text)
681 def _on_preferences_activate(self, *args):
682 self._presenter.show_preferences_dialog(self._widget)
684 def _on_feed_information_activate(self, *args):
685 self._presenter.show_feed_properties(self._widget)
687 # View menu
688 def _on_previous_item_activate(self, *args):
689 self._presenter.display_previous_item()
691 def _on_next_item_activate(self, *args):
692 self._presenter.display_next_item()
694 def _on_next_feed_activate(self, *args):
695 self._presenter.display_next_feed()
697 def _on_previous_feed_activate(self, *args):
698 self._presenter.display_previous_feed()
700 def _on_next_unread_feed_activate(self, *args):
701 self._presenter.display_next_unread_feed()
703 def _on_scroll_unread_items_activate(self, *args):
704 self._presenter.scroll_or_display_next_unread_item()
706 # Help menu
708 def _on_report_problem_activate(self, menuitem, *args):
709 helpers.url_show("http://bugzilla.gnome.org/simple-bug-guide.cgi?product=straw")
711 def _on_about_activate(self, menuitem, *args):
712 widget = self._presenter.credits()
713 widget.show()
715 def _on_straw_main_drag_data_received(self, w, context,
716 x, y, data, info, time):
717 if data and data.format == 8:
718 url = data.data.split("\n")[0]
719 subscribe_show("%s" % url, self._widget)
720 context.finish(True, False, time)
721 else:
722 context.finish(False, False, time)
724 # FIXME
725 def show_offline_dialog(self):
726 return helpers.report_offline_status(self._widget)
728 def get_widget_tree(self):
729 return gtk.glade.get_widget_tree(self._widget)
731 def present(self):
732 self._widget.present()
734 def should_present(self):
735 if self._widget.window and self._widget.window.get_state() is not gtk.gdk.WINDOW_STATE_WITHDRAWN:
736 self._widget.hide()
737 else:
738 self._widget.present()
741 class Application:
742 def __init__(self):
743 gnome.program_init(straw.defs.PACKAGE, straw.defs.VERSION)
745 error.setup_log()
746 # initialize threading and environment
747 gobject.threads_init()
749 #Config.set(OPTION_OFFLINEnfig.reload_css = os.getenv('STRAW_RELOAD_CSS') is not None
751 # build tray status icon
752 image = gtk.Image()
753 iconfile = os.path.normpath(straw.defs.STRAW_DATA_DIR + "/straw.png")
754 pixbuf = gtk.gdk.pixbuf_new_from_file(iconfile)
755 scaled_buf = pixbuf.scale_simple(16,16,gtk.gdk.INTERP_BILINEAR)
756 image.set_from_pixbuf(scaled_buf)
757 try:
758 self.tray = gtk.status_icon_new_from_pixbuf(scaled_buf)
759 self.tray.connect('activate', self._tray_clicked)
760 except AttributeError:
761 import egg
762 import egg.trayicon
763 self.tray = egg.trayicon.TrayIcon(straw.defs.PACKAGE);
764 self._eventbox = gtk.EventBox()
765 self.tray.add(self._eventbox)
766 self._eventbox.connect('button_press_event', self._tray_clicked)
767 self._eventbox.connect("drag-data-received", self._on_drag_data_received)
768 self._eventbox.drag_dest_set(
769 gtk.DEST_DEFAULT_ALL,
770 [('_NETSCAPE_URL', 0, 0),('text/uri-list ', 0, 1),('x-url/http', 0, 2)],
771 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
772 self._eventbox.add(image)
773 self.tray.show_all()
775 self._tooltip = gtk.Tooltips()
776 self.unread_count = 0
777 # end build tray status icon
779 FeedManager.setup(storage_path = "test.db")
780 FeedManager.init()
782 ItemManager.setup(storage_path = "test.db")
783 ItemManager.init()
785 #FeedManager.import_opml(os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml"))
787 #if config.first_time:
788 # filepath = os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml")
789 # feeds.import_opml(filepath)
791 #ImageCache.initialize()
793 xml = gtk.glade.XML(os.path.join(straw.defs.STRAW_DATA_DIR,'straw.glade'), "straw_main", gettext.textdomain())
794 window = xml.get_widget('straw_main')
795 self._main_presenter = ApplicationPresenter(view = ApplicationView(window))
796 self._main_presenter.view.present()
798 self._main_presenter.view._widget.window.move_resize(Config.get(OPTION_WINDOW_LEFT), Config.get(OPTION_WINDOW_TOP),
799 Config.get(OPTION_WINDOW_SIZE_W), Config.get(OPTION_WINDOW_SIZE_H))
801 #PollManager.get_instance().start_polling_loop()
802 # set the default icon for the windows
803 iconfile = os.path.join(straw.defs.STRAW_DATA_DIR,"straw.png")
804 gtk.window_set_default_icon(gtk.gdk.pixbuf_new_from_file(iconfile))
806 start_services()
808 def mainloop(self):
809 gtk.gdk.threads_init()
810 gtk.gdk.threads_enter()
811 gtk.main()
812 gtk.gdk.threads_leave()
814 def _update(self,flist):
815 uritems = urfeeds = 0
816 for ur in [f.number_of_unread for f in flist.flatten_list()]:
817 if ur:
818 uritems += ur
819 urfeeds += 1
820 if uritems == self.unread_count:
821 return
822 self.unread_count = uritems
823 if uritems:
824 self._tooltip.set_tip(self.tray, _("%d new items")%uritems)
825 self.tray.show_all()
826 else:
827 self.tray.hide()
828 return
830 def _tray_clicked(self, widget, event=None):
831 self._main_presenter.view.should_present()
833 if event and not (event.button == 1):
834 return
835 self._main_presenter.scroll_or_display_next_unread_item()
837 def _on_drag_data_received(self, widget, context, x, y, data, info, timestamp):
838 if data and data.format == 8:
839 url = data.data.split("\n")[0]
840 subscribe_show(url="%s" % url)
841 context.finish(True, False, timestamp)
842 else:
843 context.finish(False, False, timestamp)
844 return