Map space bar to scroll_unread_items (thanks estar).
[straw.git] / straw / Application.py
blobf58c8f40db55cc98e6a59ca76c748ce336a28aca
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 log = error.get_logger()
51 class StatusPresenter(MVP.BasicPresenter):
52 def _initialize(self):
53 self._mmgr = get_status_manager()
54 self._mmgr.connect('changed', self._display)
56 def _display(self, *args):
57 cid = self._view.get_context_id("straw_main")
58 self._view.pop(cid)
59 self._view.push(cid, self._mmgr.read_message())
60 return
62 class ErrorPresenter:
63 def __init__(self, widget):
64 self._widget = widget
65 self._tooltips = gtk.Tooltips()
66 self._text = ''
67 #self._curr_feed = None
68 #self._curr_category = None
69 #fclist = FeedCategoryList.get_instance()
70 #fclist.connect('category-changed', self._category_changed)
72 def feedlist_selection_changed(self, selection, column):
73 errortexts = None
74 return
75 (model, pathlist) = selection.get_selected_rows()
76 iters = [model.get_iter(path) for path in pathlist]
77 if not iters: return
78 errorfeeds = []
79 nodes = [model.get_value(treeiter, column) for treeiter in iters]
80 try:
81 errorfeeds = [node.feed for node in nodes if node.feed and node.feed.error]
82 errortexts = [feed.error for feed in errorfeeds]
83 except TypeError, te:
84 print te
85 ### XXX display OPML Category error too
87 text = list()
88 if errorfeeds:
89 text.append(_("Error:"))
90 text += errortexts
92 if text:
93 t = "\n".join(text)
94 self._tooltips.set_tip(self._widget, t, t)
95 self._tooltips.enable()
96 self._widget.show()
97 else:
98 self._tooltips.disable()
99 self._widget.hide()
100 return
102 def hide(self):
103 self._widget.hide()
105 class ToolbarView(MVP.WidgetView):
106 """ Widget: gtk.Toolbar"""
107 GCONF_DESKTOP_INTERFACE = "/desktop/gnome/interface"
109 def _initialize(self):
110 client = gconf.client_get_default()
111 if not client.dir_exists(self.GCONF_DESKTOP_INTERFACE):
112 return
113 client.add_dir(self.GCONF_DESKTOP_INTERFACE,
114 gconf.CLIENT_PRELOAD_NONE)
115 client.notify_add(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style",
116 self._toolbar_style_changed)
117 self._widget.set_tooltips(True)
119 def _toolbar_style_changed(self, client, notify_id, entry, *args):
120 value = entry.value.get_string()
121 self._presenter.style_changed(value)
123 def set_style(self, style):
124 self._widget.set_style(style)
126 def get_style(self):
127 client = gconf.client_get_default()
128 current_style = client.get_string(self.GCONF_DESKTOP_INTERFACE+"/toolbar_style")
129 return current_style
131 class ToolbarPresenter(MVP.BasicPresenter):
132 STYLES = {'both':gtk.TOOLBAR_BOTH,
133 'text':gtk.TOOLBAR_TEXT,
134 'icons':gtk.TOOLBAR_ICONS,
135 'both-horiz':gtk.TOOLBAR_BOTH_HORIZ}
137 def _initialize(self):
138 style = self._view.get_style()
140 if style in self.STYLES.keys():
141 self._view.set_style(self.STYLES[style])
143 widget_tree = gtk.glade.get_widget_tree(self._view._widget)
144 self.change_read_state_button = widget_tree.get_widget("toolbar_change_read_state_button")
146 def style_changed(self, value):
147 if value in self.STYLES.keys():
148 self._view.set_style(self.STYLES[value])
150 def set_change_read_button_state(self, new_state):
151 self.change_read_state_button.set_active(new_state)
153 class ApplicationPresenter(MVP.BasicPresenter):
154 def _initialize(self):
155 FeedManager._get_instance().connect("update-all-done", self._on_update_all_done)
157 self._current_item = None
159 self._init_widgets()
160 self._init_presenters()
161 self._restore_state()
162 self._view.present()
164 def _init_widgets(self):
165 widget_tree = self._view.get_widget_tree()
167 self._itemlist_view_notebook = widget_tree.get_widget('mode_view_notebook')
168 self._feedlist_view_notebook = widget_tree.get_widget('left_mode_view_notebook')
170 item_view_container = widget_tree.get_widget('item_view_container')
171 item_list_container = widget_tree.get_widget('item_list_container')
173 self.update_all_button = widget_tree.get_widget("toolbar_refresh_all_button")
175 parent_paned = item_view_container.get_parent()
176 parent_parent_widget = parent_paned.get_parent()
178 child_list = []
179 layout = Config.get(OPTION_PANE_LAYOUT)
181 if layout not in ("horizontal", "vertical"):
182 layout = "vertical"
184 if layout == "vertical":
185 new_paned = gtk.VPaned()
186 child_list.append(item_list_container)
187 child_list.append(item_view_container)
188 elif layout == "horizontal":
189 new_paned = gtk.HPaned()
190 child_list.append(item_view_container)
191 child_list.append(item_list_container)
193 for child in child_list:
194 child.reparent(new_paned)
196 parent_parent_widget.remove(parent_paned)
197 parent_parent_widget.add(new_paned)
199 new_paned.connect("size-allocate", self._view._on_main_sub_pane_size_allocate)
200 new_paned.set_position(Config.get(OPTION_SUB_PANE_POS))
201 new_paned.show()
203 def _init_presenters(self):
204 widget_tree = self._view.get_widget_tree()
205 self._toolbar_presenter = ToolbarPresenter(view=ToolbarView(widget_tree.get_widget('toolbar_default')))
206 self._error_presenter = ErrorPresenter(widget_tree.get_widget('statusbar_error_indicator'))
208 self._item_view = ItemView(widget_tree.get_widget('item_view_container'))
210 view = ItemListView(widget_tree.get_widget('item_selection_treeview'))
211 self._itemlist_presenter = ItemListPresenter(view=view)
213 view.add_selection_changed_listener(self._item_view)
214 view.add_selection_changed_listener(self)
216 view = FeedsView(widget_tree.get_widget('feed_selection_treeview'))
217 self._feed_list_presenter = FeedsPresenter(view=view)
218 view.add_selection_changed_listener(self._itemlist_presenter)
219 view.add_selection_changed_listener(self._error_presenter)
221 self._offline_presenter = OfflineToggle(widget_tree.get_widget('offline_toggle'))
223 self._status_presenter = StatusPresenter(view = widget_tree.get_widget("main_statusbar"))
224 #self._find_presenter = FindPresenter(view=FindView(widget_tree.get_widget("find_vbox")))
226 def set_current_item(self, item):
227 self._current_item = item
228 self._current_item_connect_id = item.connect("is-read-changed", self._on_current_item_is_read_changed)
230 def _restore_state(self):
231 self._feed_list_presenter.restore_state()
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 def check_size(widget, width, height, x, y):
262 if width == widget.allocation.width and height == widget.allocation.height:
263 Config.set(OPTION_WINDOW_SIZE_W, width)
264 Config.set(OPTION_WINDOW_SIZE_H, height)
266 if widget.window and (x, y) == widget.window.get_position():
267 Config.set(OPTION_WINDOW_LEFT, x)
268 Config.set(OPTION_WINDOW_TOP, y)
270 config_position = (Config.get(OPTION_WINDOW_LEFT), Config.get(OPTION_WINDOW_TOP))
272 if (event.width, event.height, event.x, event.y) != \
273 (widget.allocation.width, widget.allocation.height, config_position[0], config_position[1]):
274 gobject.timeout_add(1000, check_size, widget, event.width, event.height, event.x, event.y)
276 def check_main_pane_position(self, widget):
277 def check_position((position, widget)):
278 if position == widget.get_position():
279 Config.set(OPTION_MAIN_PANE_POS, position)
281 pos = widget.get_position()
283 if pos != Config.get(OPTION_MAIN_PANE_POS):
284 gobject.timeout_add(1000, check_position, (pos, widget))
286 def check_sub_pane_position(self, widget):
287 def check_position((position, widget)):
288 if position == widget.get_position():
289 Config.set(OPTION_SUB_PANE_POS, position)
291 pos = widget.get_position()
293 if pos != Config.get(OPTION_SUB_PANE_POS):
294 gobject.timeout_add(1000, check_position, (pos, widget))
296 def credits(self):
297 return helpers.credits()
299 def poll_all(self):
300 if not self._warn_if_offline():
301 return
303 if not FeedManager.is_update_all_running():
304 FeedManager.update_all_feeds({ "task-start": [ self._on_feed_poll_started ],
305 "task-done": [ self._on_feed_poll_done ] })
306 self.update_all_button.set_sensitive(True)
307 self.update_all_button.set_stock_id(gtk.STOCK_STOP)
308 else:
309 self.update_all_button.set_sensitive(False)
310 FeedManager.stop_update_all()
312 def _on_feed_poll_started(self, handler, feed):
313 pass
315 def _on_feed_poll_done(self, handler, data):
316 pass
318 def _on_update_all_done(self, obj):
319 self.update_all_button.set_sensitive(True)
320 self.update_all_button.set_stock_id(gtk.STOCK_REFRESH)
322 def poll_current_category(self):
323 if self._warn_if_offline():
324 self._poll_categories([self._curr_category])
326 def poll_current_feed(self):
327 if self._warn_if_offline():
328 pm = PollManager.get_instance()
329 pm.poll([self._curr_feed])
331 def mark_feed_as_read(self):
332 self._curr_feed.mark_all_read()
334 def mark_all_as_read(self):
335 print "TODO mark_all_as_read"
337 def remove_selected_feed(self):
338 # self._feed_list_presenter.remove_selected_feed()
339 pass
341 def show_search(self):
342 # self._find_presenter.item_list.signal_connect(Event.ItemSelectionChangedSignal,
343 # self._item_view.item_selection_changed)
344 self._item_view.display_empty_search()
345 self._itemlist_view_notebook.set_current_page(1)
346 self._feedlist_view_notebook.set_current_page(1)
347 self._feedinfo_presenter.hide()
349 def hide_search(self):
350 # self._find_presenter.clear()
351 self._itemlist_view_notebook.set_current_page(0)
352 self._feedlist_view_notebook.set_current_page(0)
353 self._feedinfo_presenter.show()
354 # self._find_presenter.item_list.signal_disconnect(Event.ItemSelectionChangedSignal,
355 # self._item_view.item_selection_changed)
357 def _warn_if_offline(self):
358 offline = Config.get(OPTION_OFFLINE)
359 will_poll = False
361 if offline:
362 response = self._view.show_offline_dialog()
364 if response == gtk.RESPONSE_OK:
365 Config.set(OPTION_OFFLINE, not offline)
366 will_poll = True
367 else:
368 will_poll = True
370 return will_poll
372 def display_previous_feed(self, item = None):
374 Displays the feed before the current selected feed
376 self._feed_list_presenter.select_previous_feed()
378 def display_next_feed(self, item=None):
380 Displays the feed after the current selected feed
382 self._feed_list_presenter.select_next_feed()
383 return
385 def display_next_unread_feed(self):
387 Displays the next feed with an unread item
389 self._feed_list_presenter.select_next_feed(with_unread=True)
390 pass
392 def display_previous_item(self, item=None):
394 Displays the item before the current selected item. If the item is the
395 first item, scrolls to the previous feed
397 is_prev = self._itemlist_presenter.select_previous_item()
398 if not is_prev:
399 # TODO HACK - implement select_previous_feed(select_last=True) ...
400 # ... to select previous feed's last item
401 self._feed_list_presenter.select_previous_feed()
402 self._itemlist_presenter.select_last_item()
403 pass
404 return
406 def display_next_item(self, item=None):
408 Displays the item after the current selected item. If the item is the
409 last item, selectes the next feed. If the current feed is the last
410 feed in the list, it goes back and selects the first feed
412 is_next = self._itemlist_presenter.select_next_item()
413 if not is_next:
414 is_next_feed = self._feed_list_presenter.select_next_feed()
415 if not is_next_feed:
416 self._feed_list_presenter.select_firsteed_feed()
417 return
419 def scroll_or_display_next_unread_item(self, item=None):
420 has_unread_item = False
421 if not self._item_view.scroll_down():
422 has_unread_item = self._itemlist_presenter.select_next_unread_item()
423 if not has_unread_item:
424 self._feed_list_presenter.select_next_feed(with_unread=True)
425 return
427 def show_preferences_dialog(self, parent):
428 preferences.show()
430 def show_feed_properties(self, parent):
431 feedproperties.show(self._curr_feed)
433 def quit(self):
434 self._feed_list_presenter.store_state()
435 ItemManager.shutdown()
436 gtk.main_quit()
438 def _setup_filechooser_dialog(self, title, action, extra_widget_title):
440 Setup the file chooser dialog. This includes an extra widget (a combobox)
441 to include the categories to import or export
443 dialog = gtk.FileChooserDialog(title, action=action,
444 buttons=(gtk.STOCK_CANCEL,
445 gtk.RESPONSE_CANCEL,
446 gtk.STOCK_OK, gtk.RESPONSE_OK))
447 category_list = []
449 for category in FeedManager.categories():
450 category_list.append(category)
452 model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
453 combobox = gtk.ComboBox(model)
454 celltitle = gtk.CellRendererText()
455 combobox.pack_start(celltitle, False)
456 combobox.add_attribute(celltitle, 'text', 0)
458 for category in category_list:
459 it = model.append()
460 model.set(it, 0, category.name, 1, category)
462 combobox.set_active(0)
463 label = gtk.Label(extra_widget_title)
464 label.set_alignment(1.0, 0.5)
465 hbox = gtk.HBox(spacing=6)
466 hbox.pack_start(label, True, True, 0)
467 hbox.pack_end(combobox, False, False, 0)
468 hbox.show_all()
470 dialog.set_extra_widget(hbox)
471 return (dialog, combobox)
473 def import_subscriptions(self, parent):
474 (dialog, combobox) = self._setup_filechooser_dialog(_("Import Subscriptions"),
475 gtk.FILE_CHOOSER_ACTION_OPEN,
476 _("Add new subscriptions in:"))
477 ffilter = gtk.FileFilter()
478 ffilter.set_name(_("OPML Files Only"))
479 ffilter.add_pattern("*.xml")
480 ffilter.add_pattern("*.opml")
481 dialog.add_filter(ffilter)
482 dialog.set_transient_for(parent)
483 response = dialog.run()
484 if response == gtk.RESPONSE_OK:
485 filename = dialog.get_filename()
486 model = combobox.get_model()
487 category = model[combobox.get_active()][1]
488 dialog.hide()
489 FeedManager.import_opml(filename, category)
490 dialog.destroy()
492 def export_subscriptions(self, parent):
493 (dialog, combobox) = self._setup_filechooser_dialog(_("Export Subscriptions"),
494 gtk.FILE_CHOOSER_ACTION_SAVE,
495 _("Select category to export:"))
496 def selection_changed(widget, dialog):
497 model = widget.get_model()
498 category = model[widget.get_active()][1]
499 dialog.set_current_name("Straw-%s.xml" % category.name)
501 combobox.connect('changed', selection_changed, dialog)
502 selection_changed(combobox, dialog)
503 dialog.set_transient_for(parent)
504 response = dialog.run()
506 if response == gtk.RESPONSE_OK:
507 filename = dialog.get_filename()
508 model = combobox.get_model()
509 root_category = model[combobox.get_active()][1]
510 FeedManager.export_opml(root_category.id, filename)
512 dialog.destroy()
514 def subscribe(self):
515 subscribe_show(parent=self.view._widget)
517 class ApplicationView(MVP.WidgetView):
519 Widget: straw_main
521 def _initialize(self):
522 self._initialize_dnd()
523 self._initialize_window_updater()
524 self._create_unmodified_accelerator_group()
525 self._attach_unmodified_accelerator_group()
526 self._initialize_window()
527 self._find_toggled = False
529 def _initialize_window(self):
530 widget_tree = gtk.glade.get_widget_tree(self._widget)
531 if Config.get(OPTION_WINDOW_MAX):
532 self._widget.maximize()
533 else:
534 # we use resize here since configure-event seems to
535 # overwrite the default size if we use set_default_size.
536 #self._widget.move(Config.get(OPTION_WINDOW_LEFT), Config.get(OPTION_WINDOW_TOP))
537 #self._widget.resize(Config.get(OPTION_WINDOW_SIZE_W), Config.get(OPTION_WINDOW_SIZE_H))
538 self._widget.window.move_resize(Config.get(OPTION_WINDOW_LEFT), Config.get(OPTION_WINDOW_TOP),
539 Config.get(OPTION_WINDOW_SIZE_W), Config.get(OPTION_WINDOW_SIZE_H))
540 self.mmp = widget_tree.get_widget('main_main_pane')
541 self.msp = widget_tree.get_widget('main_sub_pane')
543 self.mmp.set_position(Config.get(OPTION_MAIN_PANE_POS))
544 self.msp.set_position(Config.get(OPTION_SUB_PANE_POS))
546 def _initialize_dnd(self):
547 self._widget.drag_dest_set(
548 gtk.DEST_DEFAULT_ALL,
549 [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1),
550 ('x-url/http', 0, 2)],
551 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
552 return
554 def _initialize_window_updater(self):
555 #feedlist.signal_connect(Event.AllItemsReadSignal,
556 # lambda signal: self._update_title(feedlist))
557 #feedlist.signal_connect(Event.ItemReadSignal,
558 # lambda signal: self._update_title(feedlist))
559 #feedlist.signal_connect(Event.ItemsAddedSignal,
560 # lambda signal: self._update_title(feedlist))
561 #feedlist.signal_connect(Event.FeedsChangedSignal,
562 # lambda signal: self._update_title(feedlist))
563 pass
565 def _update_title(self, flist):
566 uritems = urfeeds = 0
567 sfeeds = "feeds"
568 listfeeds = flist.flatten_list()
569 for ur in [f.number_of_unread for f in listfeeds]:
570 if ur:
571 uritems += ur
572 urfeeds += 1
573 else:
574 urfeeds = len(listfeeds)
575 if urfeeds < 2:
576 sfeeds = "feed"
577 item_feed_map = {'uritems': uritems,
578 'urfeeds': urfeeds,
579 'fstring' : sfeeds}
580 title = _('%(uritems)d unread in %(urfeeds)d %(fstring)s') % item_feed_map
581 self._widget.set_title(title + " - %s" % straw.defs.PACKAGE)
583 # We have a separate accelerator group for the unmodified and
584 # shifted accelerators, that is, stuff like space, N, P, etc. This
585 # is so that we can have the find pane work correctly
586 def _create_unmodified_accelerator_group(self):
587 xml = gtk.glade.get_widget_tree(self._widget)
588 agroup = gtk.AccelGroup()
589 accels = (('feed_mark_as_read', 'R', gtk.gdk.SHIFT_MASK),
590 ('feed_mark_all_as_read', 'A', gtk.gdk.SHIFT_MASK),
591 ('next_item', 'N', gtk.gdk.SHIFT_MASK),
592 ('scroll_unread_items', ' ', 0),
593 ('previous_item', 'P', gtk.gdk.SHIFT_MASK))
594 for widget_name, key, mask in accels:
595 widget = xml.get_widget(widget_name)
596 widget.add_accelerator("activate", agroup, ord(key), mask,
597 gtk.ACCEL_VISIBLE)
598 self._unmodified_accelerator_group = agroup
600 def _on_category_add_activate(self, *args):
601 self._presenter.add_category()
603 def _on_toolbar_change_read_state_button_toggled(self, button):
604 self._presenter.set_current_item_is_read(button.get_active())
606 def _attach_unmodified_accelerator_group(self):
607 self._widget.add_accel_group(self._unmodified_accelerator_group)
609 def _detach_unmodified_accelerator_group(self):
610 self._widget.remove_accel_group(self._unmodified_accelerator_group)
612 def _on_straw_main_destroy_event(self, *args):
613 return self._presenter.quit()
615 def _on_straw_main_delete_event(self, *args):
616 return self._presenter.quit()
618 def _on_straw_main_window_state_event(self, widget, event):
619 is_maximized = widget.window.get_state() == gtk.gdk.WINDOW_STATE_MAXIMIZED
620 Config.set(OPTION_WINDOW_MAX, is_maximized)
622 def _on_straw_main_configure_event(self, widget, event, *args):
623 if not (widget.window.get_state() & gtk.gdk.WINDOW_STATE_MAXIMIZED):
624 self._presenter.check_allocation(widget, event)
626 def _on_main_main_pane_size_allocate(self, widget, *args):
627 self._presenter.check_main_pane_position(widget)
629 def _on_main_sub_pane_size_allocate(self, widget, *args):
630 self._presenter.check_sub_pane_position(widget)
632 # Actions menu
633 def _on_feed_subscribe_activate(self, *args):
634 self._presenter.subscribe()
636 def _on_feed_unsubscribe_activate(self, *args):
637 self._presenter.remove_selected_feed()
639 def _on_subscription_import_activate(self, *args):
640 self._presenter.import_subscriptions(self._widget)
642 def _on_subscription_export_activate(self, *args):
643 self._presenter.export_subscriptions(self._widget)
645 def _on_feed_mark_as_read_activate(self, *args):
646 self._presenter.mark_feed_as_read()
648 def _on_feed_mark_all_as_read_activate(self, *args):
649 self._presenter.mark_all_as_read()
651 def _on_feed_refresh_selected_activate(self, *args):
652 self._presenter.poll_current_feed()
654 def _on_subscription_refresh_activate(self, *args):
655 self._presenter.poll_all()
657 def _on_quit_activate(self, *args):
658 return self._presenter.quit()
660 # Edit menu
661 def _on_copy_activate(self, *args):
662 self._presenter.copy_itemview_text_selection()
664 def _on_find_activate(self, widget, *args):
665 xml = gtk.glade.get_widget_tree(self._widget)
666 menu_find = xml.get_widget('menu_find')
667 accel_label = menu_find.get_child()
669 if not self._find_toggled:
670 self._presenter.show_search()
671 self._detach_unmodified_accelerator_group()
672 self._find_toggled = True
674 # save the "Find..." stock text for later recovery
675 self._old_label_text = accel_label.get_text()
676 accel_label.set_text(_('Return to feed list...'))
678 else:
679 self._presenter.hide_search()
680 self._attach_unmodified_accelerator_group()
681 self._find_toggled = False
682 accel_label.set_text(self._old_label_text)
684 def _on_preferences_activate(self, *args):
685 self._presenter.show_preferences_dialog(self._widget)
687 def _on_feed_information_activate(self, *args):
688 self._presenter.show_feed_properties(self._widget)
690 # View menu
691 def _on_previous_item_activate(self, *args):
692 self._presenter.display_previous_item()
694 def _on_next_item_activate(self, *args):
695 self._presenter.display_next_item()
697 def _on_next_feed_activate(self, *args):
698 self._presenter.display_next_feed()
700 def _on_previous_feed_activate(self, *args):
701 self._presenter.display_previous_feed()
703 def _on_next_unread_feed_activate(self, *args):
704 self._presenter.display_next_unread_feed()
706 def _on_scroll_unread_items_activate(self, *args):
707 self._presenter.scroll_or_display_next_unread_item()
709 # Help menu
711 def _on_report_problem_activate(self, menuitem, *args):
712 helpers.url_show("http://bugzilla.gnome.org/simple-bug-guide.cgi?product=straw")
714 def _on_about_activate(self, menuitem, *args):
715 widget = self._presenter.credits()
716 widget.show()
718 def _on_straw_main_drag_data_received(self, w, context,
719 x, y, data, info, time):
720 if data and data.format == 8:
721 url = data.data.split("\n")[0]
722 subscribe_show("%s" % url, self._widget)
723 context.finish(True, False, time)
724 else:
725 context.finish(False, False, time)
727 # FIXME
728 def show_offline_dialog(self):
729 return helpers.report_offline_status(self._widget)
731 def get_widget_tree(self):
732 return gtk.glade.get_widget_tree(self._widget)
734 def present(self):
735 self._widget.present()
737 def should_present(self):
738 if self._widget.window and self._widget.window.get_state() is not gtk.gdk.WINDOW_STATE_WITHDRAWN:
739 self._widget.hide()
740 else:
741 self._widget.present()
744 class Application:
745 def __init__(self):
746 gnome.program_init(straw.defs.PACKAGE, straw.defs.VERSION)
748 # initialize threading and environment
749 gobject.threads_init()
751 #Config.set(OPTION_OFFLINEnfig.reload_css = os.getenv('STRAW_RELOAD_CSS') is not None
753 # build tray status icon
754 image = gtk.Image()
755 iconfile = os.path.normpath(straw.defs.STRAW_DATA_DIR + "/straw.png")
756 pixbuf = gtk.gdk.pixbuf_new_from_file(iconfile)
757 scaled_buf = pixbuf.scale_simple(16, 16, gtk.gdk.INTERP_BILINEAR)
758 image.set_from_pixbuf(scaled_buf)
759 try:
760 self.tray = gtk.status_icon_new_from_pixbuf(scaled_buf)
761 self.tray.connect('activate', self._tray_clicked)
762 except AttributeError:
763 import egg
764 import egg.trayicon
765 self.tray = egg.trayicon.TrayIcon(straw.defs.PACKAGE);
766 self._eventbox = gtk.EventBox()
767 self.tray.add(self._eventbox)
768 self._eventbox.connect('button_press_event', self._tray_clicked)
769 self._eventbox.connect("drag-data-received", self._on_drag_data_received)
770 self._eventbox.drag_dest_set(
771 gtk.DEST_DEFAULT_ALL,
772 [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1), ('x-url/http', 0, 2)],
773 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
774 self._eventbox.add(image)
775 self.tray.show_all()
777 self._tooltip = gtk.Tooltips()
778 self.unread_count = 0
779 # end build tray status icon
781 FeedManager.setup(storage_path = "test.db")
782 FeedManager.init()
784 ItemManager.setup(storage_path = "test.db")
785 ItemManager.init()
787 #FeedManager.import_opml(os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml"))
789 #if config.first_time:
790 # filepath = os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml")
791 # feeds.import_opml(filepath)
793 #ImageCache.initialize()
795 xml = gtk.glade.XML(os.path.join(straw.defs.STRAW_DATA_DIR, 'straw.glade'), "straw_main", gettext.textdomain())
796 window = xml.get_widget('straw_main')
797 self._main_presenter = ApplicationPresenter(view = ApplicationView(window))
798 self._main_presenter.view.present()
800 #PollManager.get_instance().start_polling_loop()
801 # set the default icon for the windows
802 iconfile = os.path.join(straw.defs.STRAW_DATA_DIR, "straw.png")
803 gtk.window_set_default_icon(gtk.gdk.pixbuf_new_from_file(iconfile))
805 start_services()
807 def mainloop(self):
808 gtk.gdk.threads_init()
809 gtk.gdk.threads_enter()
810 gtk.main()
811 gtk.gdk.threads_leave()
813 def _update(self, flist):
814 uritems = urfeeds = 0
815 for ur in [f.number_of_unread for f in flist.flatten_list()]:
816 if ur:
817 uritems += ur
818 urfeeds += 1
819 if uritems == self.unread_count:
820 return
821 self.unread_count = uritems
822 if uritems:
823 self._tooltip.set_tip(self.tray, _("%d new items")%uritems)
824 self.tray.show_all()
825 else:
826 self.tray.hide()
827 return
829 def _tray_clicked(self, widget, event=None):
830 self._main_presenter.view.should_present()
832 if event and not (event.button == 1):
833 return
834 self._main_presenter.scroll_or_display_next_unread_item()
836 def _on_drag_data_received(self, widget, context, x, y, data, info, timestamp):
837 if data and data.format == 8:
838 url = data.data.split("\n")[0]
839 subscribe_show(url="%s" % url)
840 context.finish(True, False, timestamp)
841 else:
842 context.finish(False, False, timestamp)
843 return