""" 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)
syntax highlighted by Code2HTML, v. 0.9.1