Remove gtkmozembed view widget from the frame - workaround for shutdown issues with...
[straw.git] / straw / Application.py
blob20038f240dc38d1a6bd49f9330e0d8e67dca55e0
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 view.add_mark_all_as_read_listener(self._itemlist_presenter)
223 self._offline_presenter = OfflineToggle(widget_tree.get_widget('offline_toggle'))
225 self._status_presenter = StatusPresenter(view = widget_tree.get_widget("main_statusbar"))
226 #self._find_presenter = FindPresenter(view=FindView(widget_tree.get_widget("find_vbox")))
228 def set_current_item(self, item):
229 self._current_item = item
230 self._current_item_connect_id = item.connect("is-read-changed", self._on_current_item_is_read_changed)
232 def _restore_state(self):
233 self._feed_list_presenter.restore_state()
235 def clear_current_item(self):
236 if self._current_item:
237 self._current_item.disconnect(self._current_item_connect_id)
238 self._current_item = None
240 def set_current_item_is_read(self, is_read):
241 if self._current_item:
242 self._current_item.props.is_read = is_read
244 def _on_current_item_is_read_changed(self, obj, is_read):
245 self._toolbar_presenter.set_change_read_button_state(is_read)
247 def itemlist_selection_changed(self, selection, column):
248 self.clear_current_item()
250 (model, treeiter) = selection.get_selected()
251 if not treeiter: return
252 item = model.get_value(treeiter, column)
253 self._toolbar_presenter.set_change_read_button_state(item.is_read)
254 self.set_current_item(item)
256 def add_category(self):
257 self._feed_list_presenter.add_category()
259 def copy_itemview_text_selection(self):
260 helpers.set_clipboard_text(self._item_view.get_selected_text())
262 def check_allocation(self, widget, event):
263 def check_size(widget, width, height, x, y):
264 if width == widget.allocation.width and height == widget.allocation.height:
265 Config.set(OPTION_WINDOW_SIZE_W, width)
266 Config.set(OPTION_WINDOW_SIZE_H, height)
268 if widget.window and (x, y) == widget.window.get_position():
269 Config.set(OPTION_WINDOW_LEFT, x)
270 Config.set(OPTION_WINDOW_TOP, y)
272 config_position = (Config.get(OPTION_WINDOW_LEFT), Config.get(OPTION_WINDOW_TOP))
274 if (event.width, event.height, event.x, event.y) != \
275 (widget.allocation.width, widget.allocation.height, config_position[0], config_position[1]):
276 gobject.timeout_add(1000, check_size, widget, event.width, event.height, event.x, event.y)
278 def check_main_pane_position(self, widget):
279 def check_position((position, widget)):
280 if position == widget.get_position():
281 Config.set(OPTION_MAIN_PANE_POS, position)
283 pos = widget.get_position()
285 if pos != Config.get(OPTION_MAIN_PANE_POS):
286 gobject.timeout_add(1000, check_position, (pos, widget))
288 def check_sub_pane_position(self, widget):
289 def check_position((position, widget)):
290 if position == widget.get_position():
291 Config.set(OPTION_SUB_PANE_POS, position)
293 pos = widget.get_position()
295 if pos != Config.get(OPTION_SUB_PANE_POS):
296 gobject.timeout_add(1000, check_position, (pos, widget))
298 def credits(self):
299 return helpers.credits()
301 def poll_all(self):
302 if not self._warn_if_offline():
303 return
305 if not FeedManager.is_update_all_running():
306 FeedManager.update_all_feeds({ "task-start": [ self._on_feed_poll_started ],
307 "task-done": [ self._on_feed_poll_done ] })
308 self.update_all_button.set_sensitive(True)
309 self.update_all_button.set_stock_id(gtk.STOCK_STOP)
310 else:
311 self.update_all_button.set_sensitive(False)
312 FeedManager.stop_update_all()
314 def _on_feed_poll_started(self, handler, feed):
315 pass
317 def _on_feed_poll_done(self, handler, data):
318 pass
320 def _on_update_all_done(self, obj):
321 self.update_all_button.set_sensitive(True)
322 self.update_all_button.set_stock_id(gtk.STOCK_REFRESH)
324 def poll_current_category(self):
325 if self._warn_if_offline():
326 self._poll_categories([self._curr_category])
328 def poll_current_feed(self):
329 if self._warn_if_offline():
330 pm = PollManager.get_instance()
331 pm.poll([self._curr_feed])
333 def mark_feed_as_read(self):
334 self._curr_feed.mark_all_read()
336 def mark_all_as_read(self):
337 print "TODO mark_all_as_read"
339 def remove_selected_feed(self):
340 # self._feed_list_presenter.remove_selected_feed()
341 pass
343 def show_search(self):
344 # self._find_presenter.item_list.signal_connect(Event.ItemSelectionChangedSignal,
345 # self._item_view.item_selection_changed)
346 self._item_view.display_empty_search()
347 self._itemlist_view_notebook.set_current_page(1)
348 self._feedlist_view_notebook.set_current_page(1)
349 self._feedinfo_presenter.hide()
351 def hide_search(self):
352 # self._find_presenter.clear()
353 self._itemlist_view_notebook.set_current_page(0)
354 self._feedlist_view_notebook.set_current_page(0)
355 self._feedinfo_presenter.show()
356 # self._find_presenter.item_list.signal_disconnect(Event.ItemSelectionChangedSignal,
357 # self._item_view.item_selection_changed)
359 def _warn_if_offline(self):
360 offline = Config.get(OPTION_OFFLINE)
361 will_poll = False
363 if offline:
364 response = self._view.show_offline_dialog()
366 if response == gtk.RESPONSE_OK:
367 Config.set(OPTION_OFFLINE, not offline)
368 will_poll = True
369 else:
370 will_poll = True
372 return will_poll
374 def display_previous_feed(self, item = None):
376 Displays the feed before the current selected feed
378 self._feed_list_presenter.select_previous_feed()
380 def display_next_feed(self, item=None):
382 Displays the feed after the current selected feed
384 self._feed_list_presenter.select_next_feed()
385 return
387 def display_next_unread_feed(self):
389 Displays the next feed with an unread item
391 self._feed_list_presenter.select_next_feed(with_unread=True)
392 pass
394 def display_previous_item(self, item=None):
396 Displays the item before the current selected item. If the item is the
397 first item, scrolls to the previous feed
399 is_prev = self._itemlist_presenter.select_previous_item()
400 if not is_prev:
401 # TODO HACK - implement select_previous_feed(select_last=True) ...
402 # ... to select previous feed's last item
403 self._feed_list_presenter.select_previous_feed()
404 self._itemlist_presenter.select_last_item()
405 pass
406 return
408 def display_next_item(self, item=None):
410 Displays the item after the current selected item. If the item is the
411 last item, selectes the next feed. If the current feed is the last
412 feed in the list, it goes back and selects the first feed
414 is_next = self._itemlist_presenter.select_next_item()
415 if not is_next:
416 is_next_feed = self._feed_list_presenter.select_next_feed()
417 if not is_next_feed:
418 self._feed_list_presenter.select_firsteed_feed()
419 return
421 def scroll_or_display_next_unread_item(self, item=None):
422 has_unread_item = False
423 if not self._item_view.scroll_down():
424 has_unread_item = self._itemlist_presenter.select_next_unread_item()
425 if not has_unread_item:
426 self._feed_list_presenter.select_next_feed(with_unread=True)
427 return
429 def show_preferences_dialog(self, parent):
430 preferences.show()
432 def show_feed_properties(self, parent):
433 feedproperties.show(self._curr_feed)
435 def quit(self):
436 self._item_view.destroy()
437 self._feed_list_presenter.store_state()
438 ItemManager.shutdown()
439 gtk.main_quit()
441 def _setup_filechooser_dialog(self, title, action, extra_widget_title):
443 Setup the file chooser dialog. This includes an extra widget (a combobox)
444 to include the categories to import or export
446 dialog = gtk.FileChooserDialog(title, action=action,
447 buttons=(gtk.STOCK_CANCEL,
448 gtk.RESPONSE_CANCEL,
449 gtk.STOCK_OK, gtk.RESPONSE_OK))
450 category_list = []
452 for category in FeedManager.categories():
453 category_list.append(category)
455 model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
456 combobox = gtk.ComboBox(model)
457 celltitle = gtk.CellRendererText()
458 combobox.pack_start(celltitle, False)
459 combobox.add_attribute(celltitle, 'text', 0)
461 for category in category_list:
462 it = model.append()
463 model.set(it, 0, category.name, 1, category)
465 combobox.set_active(0)
466 label = gtk.Label(extra_widget_title)
467 label.set_alignment(1.0, 0.5)
468 hbox = gtk.HBox(spacing=6)
469 hbox.pack_start(label, True, True, 0)
470 hbox.pack_end(combobox, False, False, 0)
471 hbox.show_all()
473 dialog.set_extra_widget(hbox)
474 return (dialog, combobox)
476 def import_subscriptions(self, parent):
477 (dialog, combobox) = self._setup_filechooser_dialog(_("Import Subscriptions"),
478 gtk.FILE_CHOOSER_ACTION_OPEN,
479 _("Add new subscriptions in:"))
480 ffilter = gtk.FileFilter()
481 ffilter.set_name(_("OPML Files Only"))
482 ffilter.add_pattern("*.xml")
483 ffilter.add_pattern("*.opml")
484 dialog.add_filter(ffilter)
485 dialog.set_transient_for(parent)
486 response = dialog.run()
487 if response == gtk.RESPONSE_OK:
488 filename = dialog.get_filename()
489 model = combobox.get_model()
490 category = model[combobox.get_active()][1]
491 dialog.hide()
492 FeedManager.import_opml(filename, category)
493 dialog.destroy()
495 def export_subscriptions(self, parent):
496 (dialog, combobox) = self._setup_filechooser_dialog(_("Export Subscriptions"),
497 gtk.FILE_CHOOSER_ACTION_SAVE,
498 _("Select category to export:"))
499 def selection_changed(widget, dialog):
500 model = widget.get_model()
501 category = model[widget.get_active()][1]
502 dialog.set_current_name("Straw-%s.xml" % category.name)
504 combobox.connect('changed', selection_changed, dialog)
505 selection_changed(combobox, dialog)
506 dialog.set_transient_for(parent)
507 response = dialog.run()
509 if response == gtk.RESPONSE_OK:
510 filename = dialog.get_filename()
511 model = combobox.get_model()
512 root_category = model[combobox.get_active()][1]
513 FeedManager.export_opml(root_category.id, filename)
515 dialog.destroy()
517 def subscribe(self):
518 subscribe_show(parent=self.view._widget)
520 class ApplicationView(MVP.WidgetView):
522 Widget: straw_main
524 def _initialize(self):
525 self._initialize_dnd()
526 self._initialize_window_updater()
527 self._create_unmodified_accelerator_group()
528 self._attach_unmodified_accelerator_group()
529 self._initialize_window()
530 self._find_toggled = False
532 def _initialize_window(self):
533 widget_tree = gtk.glade.get_widget_tree(self._widget)
534 if Config.get(OPTION_WINDOW_MAX):
535 self._widget.maximize()
536 else:
537 # we use resize here since configure-event seems to
538 # overwrite the default size if we use set_default_size.
539 #self._widget.move(Config.get(OPTION_WINDOW_LEFT), Config.get(OPTION_WINDOW_TOP))
540 #self._widget.resize(Config.get(OPTION_WINDOW_SIZE_W), Config.get(OPTION_WINDOW_SIZE_H))
541 self._widget.window.move_resize(Config.get(OPTION_WINDOW_LEFT), Config.get(OPTION_WINDOW_TOP),
542 Config.get(OPTION_WINDOW_SIZE_W), Config.get(OPTION_WINDOW_SIZE_H))
543 self.mmp = widget_tree.get_widget('main_main_pane')
544 self.msp = widget_tree.get_widget('main_sub_pane')
546 self.mmp.set_position(Config.get(OPTION_MAIN_PANE_POS))
547 self.msp.set_position(Config.get(OPTION_SUB_PANE_POS))
549 def _initialize_dnd(self):
550 self._widget.drag_dest_set(
551 gtk.DEST_DEFAULT_ALL,
552 [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1),
553 ('x-url/http', 0, 2)],
554 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
555 return
557 def _initialize_window_updater(self):
558 #feedlist.signal_connect(Event.AllItemsReadSignal,
559 # lambda signal: self._update_title(feedlist))
560 #feedlist.signal_connect(Event.ItemReadSignal,
561 # lambda signal: self._update_title(feedlist))
562 #feedlist.signal_connect(Event.ItemsAddedSignal,
563 # lambda signal: self._update_title(feedlist))
564 #feedlist.signal_connect(Event.FeedsChangedSignal,
565 # lambda signal: self._update_title(feedlist))
566 pass
568 def _update_title(self, flist):
569 uritems = urfeeds = 0
570 sfeeds = "feeds"
571 listfeeds = flist.flatten_list()
572 for ur in [f.number_of_unread for f in listfeeds]:
573 if ur:
574 uritems += ur
575 urfeeds += 1
576 else:
577 urfeeds = len(listfeeds)
578 if urfeeds < 2:
579 sfeeds = "feed"
580 item_feed_map = {'uritems': uritems,
581 'urfeeds': urfeeds,
582 'fstring' : sfeeds}
583 title = _('%(uritems)d unread in %(urfeeds)d %(fstring)s') % item_feed_map
584 self._widget.set_title(title + " - %s" % straw.defs.PACKAGE)
586 # We have a separate accelerator group for the unmodified and
587 # shifted accelerators, that is, stuff like space, N, P, etc. This
588 # is so that we can have the find pane work correctly
589 def _create_unmodified_accelerator_group(self):
590 xml = gtk.glade.get_widget_tree(self._widget)
591 agroup = gtk.AccelGroup()
592 accels = (('feed_mark_as_read', 'R', gtk.gdk.SHIFT_MASK),
593 ('feed_mark_all_as_read', 'A', gtk.gdk.SHIFT_MASK),
594 ('next_item', 'N', gtk.gdk.SHIFT_MASK),
595 ('scroll_unread_items', ' ', 0),
596 ('previous_item', 'P', gtk.gdk.SHIFT_MASK))
597 for widget_name, key, mask in accels:
598 widget = xml.get_widget(widget_name)
599 widget.add_accelerator("activate", agroup, ord(key), mask,
600 gtk.ACCEL_VISIBLE)
601 self._unmodified_accelerator_group = agroup
603 def _on_category_add_activate(self, *args):
604 self._presenter.add_category()
606 def _on_toolbar_change_read_state_button_toggled(self, button):
607 self._presenter.set_current_item_is_read(button.get_active())
609 def _attach_unmodified_accelerator_group(self):
610 self._widget.add_accel_group(self._unmodified_accelerator_group)
612 def _detach_unmodified_accelerator_group(self):
613 self._widget.remove_accel_group(self._unmodified_accelerator_group)
615 def _on_straw_main_destroy_event(self, *args):
616 return self._presenter.quit()
618 def _on_straw_main_delete_event(self, *args):
619 return self._presenter.quit()
621 def _on_straw_main_window_state_event(self, widget, event):
622 is_maximized = widget.window.get_state() == gtk.gdk.WINDOW_STATE_MAXIMIZED
623 Config.set(OPTION_WINDOW_MAX, is_maximized)
625 def _on_straw_main_configure_event(self, widget, event, *args):
626 if not (widget.window.get_state() & gtk.gdk.WINDOW_STATE_MAXIMIZED):
627 self._presenter.check_allocation(widget, event)
629 def _on_main_main_pane_size_allocate(self, widget, *args):
630 self._presenter.check_main_pane_position(widget)
632 def _on_main_sub_pane_size_allocate(self, widget, *args):
633 self._presenter.check_sub_pane_position(widget)
635 # Actions menu
636 def _on_feed_subscribe_activate(self, *args):
637 self._presenter.subscribe()
639 def _on_feed_unsubscribe_activate(self, *args):
640 self._presenter.remove_selected_feed()
642 def _on_subscription_import_activate(self, *args):
643 self._presenter.import_subscriptions(self._widget)
645 def _on_subscription_export_activate(self, *args):
646 self._presenter.export_subscriptions(self._widget)
648 def _on_feed_mark_as_read_activate(self, *args):
649 self._presenter.mark_feed_as_read()
651 def _on_feed_mark_all_as_read_activate(self, *args):
652 self._presenter.mark_all_as_read()
654 def _on_feed_refresh_selected_activate(self, *args):
655 self._presenter.poll_current_feed()
657 def _on_subscription_refresh_activate(self, *args):
658 self._presenter.poll_all()
660 def _on_quit_activate(self, *args):
661 return self._presenter.quit()
663 # Edit menu
664 def _on_copy_activate(self, *args):
665 self._presenter.copy_itemview_text_selection()
667 def _on_find_activate(self, widget, *args):
668 xml = gtk.glade.get_widget_tree(self._widget)
669 menu_find = xml.get_widget('menu_find')
670 accel_label = menu_find.get_child()
672 if not self._find_toggled:
673 self._presenter.show_search()
674 self._detach_unmodified_accelerator_group()
675 self._find_toggled = True
677 # save the "Find..." stock text for later recovery
678 self._old_label_text = accel_label.get_text()
679 accel_label.set_text(_('Return to feed list...'))
681 else:
682 self._presenter.hide_search()
683 self._attach_unmodified_accelerator_group()
684 self._find_toggled = False
685 accel_label.set_text(self._old_label_text)
687 def _on_preferences_activate(self, *args):
688 self._presenter.show_preferences_dialog(self._widget)
690 def _on_feed_information_activate(self, *args):
691 self._presenter.show_feed_properties(self._widget)
693 # View menu
694 def _on_previous_item_activate(self, *args):
695 self._presenter.display_previous_item()
697 def _on_next_item_activate(self, *args):
698 self._presenter.display_next_item()
700 def _on_next_feed_activate(self, *args):
701 self._presenter.display_next_feed()
703 def _on_previous_feed_activate(self, *args):
704 self._presenter.display_previous_feed()
706 def _on_next_unread_feed_activate(self, *args):
707 self._presenter.display_next_unread_feed()
709 def _on_scroll_unread_items_activate(self, *args):
710 self._presenter.scroll_or_display_next_unread_item()
712 # Help menu
714 def _on_report_problem_activate(self, menuitem, *args):
715 helpers.url_show("http://bugzilla.gnome.org/simple-bug-guide.cgi?product=straw")
717 def _on_about_activate(self, menuitem, *args):
718 widget = self._presenter.credits()
719 widget.show()
721 def _on_straw_main_drag_data_received(self, w, context,
722 x, y, data, info, time):
723 if data and data.format == 8:
724 url = data.data.split("\n")[0]
725 subscribe_show("%s" % url, self._widget)
726 context.finish(True, False, time)
727 else:
728 context.finish(False, False, time)
730 # FIXME
731 def show_offline_dialog(self):
732 return helpers.report_offline_status(self._widget)
734 def get_widget_tree(self):
735 return gtk.glade.get_widget_tree(self._widget)
737 def present(self):
738 self._widget.present()
740 def should_present(self):
741 if self._widget.window and self._widget.window.get_state() is not gtk.gdk.WINDOW_STATE_WITHDRAWN:
742 self._widget.hide()
743 else:
744 self._widget.present()
747 class Application:
748 def __init__(self):
749 gnome.program_init(straw.defs.PACKAGE, straw.defs.VERSION)
751 # initialize threading and environment
752 gobject.threads_init()
754 #Config.set(OPTION_OFFLINEnfig.reload_css = os.getenv('STRAW_RELOAD_CSS') is not None
756 # build tray status icon
757 image = gtk.Image()
758 iconfile = os.path.normpath(straw.defs.STRAW_DATA_DIR + "/straw.svg")
759 pixbuf = gtk.gdk.pixbuf_new_from_file(iconfile)
760 scaled_buf = pixbuf.scale_simple(16, 16, gtk.gdk.INTERP_BILINEAR)
761 image.set_from_pixbuf(scaled_buf)
762 try:
763 self.tray = gtk.status_icon_new_from_pixbuf(scaled_buf)
764 self.tray.connect('activate', self._tray_clicked)
765 except AttributeError:
766 import egg
767 import egg.trayicon
768 self.tray = egg.trayicon.TrayIcon(straw.defs.PACKAGE);
769 self._eventbox = gtk.EventBox()
770 self.tray.add(self._eventbox)
771 self._eventbox.connect('button_press_event', self._tray_clicked)
772 self._eventbox.connect("drag-data-received", self._on_drag_data_received)
773 self._eventbox.drag_dest_set(
774 gtk.DEST_DEFAULT_ALL,
775 [('_NETSCAPE_URL', 0, 0), ('text/uri-list ', 0, 1), ('x-url/http', 0, 2)],
776 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
777 self._eventbox.add(image)
778 self.tray.show_all()
780 self._tooltip = gtk.Tooltips()
781 self.unread_count = 0
782 # end build tray status icon
784 FeedManager.setup(storage_path = "test.db")
785 FeedManager.init()
787 ItemManager.setup(storage_path = "test.db")
788 ItemManager.init()
790 #FeedManager.import_opml(os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml"))
792 #if config.first_time:
793 # filepath = os.path.join(straw.STRAW_DATA_DIR, "default_subscriptions.opml")
794 # feeds.import_opml(filepath)
796 #ImageCache.initialize()
798 xml = gtk.glade.XML(os.path.join(straw.defs.STRAW_DATA_DIR, 'straw.glade'), "straw_main", gettext.textdomain())
799 window = xml.get_widget('straw_main')
800 self._main_presenter = ApplicationPresenter(view = ApplicationView(window))
801 self._main_presenter.view.present()
803 #PollManager.get_instance().start_polling_loop()
804 # set the default icon for the windows
805 iconfile = os.path.join(straw.defs.STRAW_DATA_DIR, "straw.svg")
806 gtk.window_set_default_icon(gtk.gdk.pixbuf_new_from_file(iconfile))
808 start_services()
810 def mainloop(self):
811 gtk.gdk.threads_init()
812 gtk.gdk.threads_enter()
813 gtk.main()
814 gtk.gdk.threads_leave()
816 def _update(self, flist):
817 uritems = urfeeds = 0
818 for ur in [f.number_of_unread for f in flist.flatten_list()]:
819 if ur:
820 uritems += ur
821 urfeeds += 1
822 if uritems == self.unread_count:
823 return
824 self.unread_count = uritems
825 if uritems:
826 self._tooltip.set_tip(self.tray, _("%d new items")%uritems)
827 self.tray.show_all()
828 else:
829 self.tray.hide()
830 return
832 def _tray_clicked(self, widget, event=None):
833 self._main_presenter.view.should_present()
835 if event and not (event.button == 1):
836 return
837 self._main_presenter.scroll_or_display_next_unread_item()
839 def _on_drag_data_received(self, widget, context, x, y, data, info, timestamp):
840 if data and data.format == 8:
841 url = data.data.split("\n")[0]
842 subscribe_show(url="%s" % url)
843 context.finish(True, False, timestamp)
844 else:
845 context.finish(False, False, timestamp)
846 return