""" Find.py Module for searching items. """ __copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc." __license__ = """ GNU General Public License Straw is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Straw is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ import pygtk pygtk.require('2.0') import gtk import gtk.glade import gobject import pango import time import Event import utils import FeedList import MessageManager from ItemList import ItemsView, ItemsPresenter import MVP import error class Column: TITLE = 0 FEED_TITLE = 1 ITEM = 2 BOLD = 3 class FindLimit(object): __slots__ = ('text', 'start', 'end') def __init__(self, text, start = None, end = None): self.text = text self.start = start self.end = end def is_subset(self, fl): if self.text.find(fl.text) == -1: return 0 if fl.end and (self.end or self.end > fl.end): return 0 if fl.start and (self.start or self.start < fl.start): return 0 return 1 def equals(self, fl): return (self.text == fl.text and self.end == fl.end and self.start == fl.start) def time_contains(self, item): rval = 1 if item.pub_date: # pub_date hasn't been initialized in older versions of straw if self.start and self.start > item.pub_date: rval = 0 if self.end and self.end < item.pub_date: rval = 0 return rval class Finder: def __init__(self): self._stack = list() self._feedlist = FeedList.get_instance() def find_matching(self, limit, items): if not len(items): for feed in self._feedlist: match = False for item in feed.items: if limit.time_contains(item) and item.match(limit.text): match = True yield item if not match: feed.unload_contents() while gtk.events_pending(): gtk.main_iteration(False) else: return else: error.log("ITEMS IS NOT NONE.!",items) # we expect this to be quicker than the above since the items are # already loaded in the memory. for item in items: if limit.time_contains(item) and item.match(limit.text): yield item return def find(self, limit): if len(limit.text) < 1: del self._stack[:] return [] items = [] if len(self._stack): if limit.is_subset(self._stack[-1][0]): # the new search string is longer than the previous if len(self._stack[-1][1]): # return existing items items = self._stack[-1][1] else: # text was deleted length = len(self._stack) foundprev = 0 while length > 0: length -= 1 if limit.equals(self._stack[length][0]): foundprev = 1 break if limit.is_subset(self._stack[length][0]): break self._stack = self._stack[:length+1] if foundprev or len(self._stack): items = self._stack[-1][1] return (limit, items) def append_matches(self, limit, matches): lm = (limit,matches) self._stack.append(lm) return class FindResultListView(ItemsView): """ Widget: gtk.TreeView Model: Result Liststore Presenter: ItemListPresenter """ def _initialize(self): ItemsView._initialize(self) self._widget.set_rules_hint(True) self._create_columns() selection = self._widget.get_selection() selection.connect("changed", self._item_selection_changed) self._create_popup() def _model_set(self): self._widget.set_model(self._model) def _create_columns(self): renderer = gtk.CellRendererText() column = gtk.TreeViewColumn('Title', renderer, text=Column.TITLE, weight=Column.BOLD) self._widget.append_column(column) renderer = gtk.CellRendererText() column = gtk.TreeViewColumn('Feed', renderer, text=Column.FEED_TITLE, weight=Column.BOLD) self._widget.append_column(column) return def clear(self): self._widget.get_model().clear() class FindResultPresenter(ItemsPresenter): def _initialize(self): self._connect() model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_PYOBJECT, gobject.TYPE_INT) self.model = model self._finder = Finder() self._interrupted = False self._matches=list() def _mark_item(self, item, index=None): if not index: index = self._matches.index(item) self._set_column_weight(item, index) def find(self, limit): self._clear_matches() limit_items = self._finder.find(limit) if not len(limit_items): return self._matches limit, items = limit_items for item in self._finder.find_matching(limit, items): if self._interrupted: self._interrupted = True break treeiter = self._model.append() weight = pango.WEIGHT_NORMAL if not item.seen: weight = pango.WEIGHT_BOLD self._matches.append(item) self._model.set(treeiter, Column.TITLE, item.title, Column.FEED_TITLE, item.feed.title, Column.ITEM, item, Column.BOLD, weight) error.log("MATCHES: %d %s %s" %(len(self._matches),limit.text,items)) self._finder.append_matches(limit, self._matches) return self._matches def stop(self, signal): error.log("stopping...") self._interrupted = True return def clear(self): self._interrupted = False self._model.clear() self._clear_matches() return def _clear_matches(self): for feed in dict.fromkeys((item.feed for item in self._matches)).keys(): feed.unload_contents() del self._matches[:] class FindView(MVP.WidgetView): SECS_DAY = 86400 def _initialize(self): self._widget_tree = gtk.glade.get_widget_tree(self._widget) self._text_entry = self._widget_tree.get_widget("find_text_entry") def _on_find_text_entry_insert_text(self, *args): gobject.timeout_add(700, self._after_change) return def _on_find_text_entry_delete_text(self, *args): gobject.timeout_add(700, self._after_change) return def _on_find_start_time_limit_check_toggled(self, button, *args): state = button.get_active() start_date_widget = self._widget_tree.get_widget('find_start_date_edit') start_date_widget.set_sensitive(state) stime = self._determine_time(state, start_date_widget.get_time()) self._presenter.start_date(stime) return def _on_find_end_time_limit_check_toggled(self, button, *args): state = button.get_active() end_date_widget = self._widget_tree.get_widget('find_end_date_edit') end_date_widget.set_sensitive(state) etime = self._determine_time(state, end_date_widget.get_time() + self.SECS_DAY) self._presenter.end_date(etime) return def _on_find_start_date_edit_date_changed(self, widget): """ Precondition: start_date_toggle is toggled (ON) or else this signal won't be triggered in the first place """ stime = self._determine_time(True, widget.get_time()) self._presenter.start_date(stime) return def _on_find_end_date_edit_date_changed(self, widget): """ Precondition: end_date_toggle is toggled (ON) or else this signal won't be triggered in the first place """ etime = self._determine_time(True, widget.get_time() + self.SECS_DAY) self._presenter.end_date(etime) return def _after_change(self): newtext = self._text_entry.get_text() self._presenter.text_changed(newtext) return def _determine_time(self, togglestate, dtime): xtime = None if togglestate: xtime = time.gmtime(dtime) return xtime def _get_text(self): return self._text text = property(_get_text) def get_widget(self): return self._widget def clear(self): self._text_entry.delete_text(0,-1) return class FindPresenter(MVP.BasicPresenter): def _initialize(self): self.initialize_slots(Event.FindInterruptSignal) widget_tree = gtk.glade.get_widget_tree(self._view.get_widget()) self._find_result_pres = FindResultPresenter(view = FindResultListView(widget_tree.get_widget("find_results_treeview"))) self.signal_connect(Event.FindInterruptSignal, self._find_result_pres.stop) self._resultscount = widget_tree.get_widget("find_results_count_display") self._rendering = False self._text = '' self._start_date = None self._end_date = None def start_date(self, stime): self._start_date = stime if stime and stime is not self._start_date: limit = FindLimit(self._text, stime, self._end_date) self._find_items(limit) return def end_date(self, etime): self._end_date = etime if etime and etime is not self._end_date: limit = FindLimit(self._text, self._start_date, etime) self._find_items(limit) return def text_changed(self, newtext): # terminate an existing result rendering in progress if self._rendering: self.emit_signal(Event.FindInterruptSignal(self)) changed = newtext is not self._text if changed: self._text = newtext self._rendering = False if changed and not self._rendering: self._rendering = True limit = FindLimit(self._text, self._start_date, self._end_date) self._find_items(limit) self._rendering = False return def _find_items(self, limit): matches = self._find_result_pres.find(limit) nmatches = len(matches) if nmatches <= 0: self._find_result_pres.clear() self._resultscount.set_text(_("%d items found") % nmatches or 0) return def clear(self): self._find_result_pres.clear() self._view.clear() def _find_result_presenter(self): return self._find_result_pres item_list = property(_find_result_presenter)