gstreamer/debug-viewer/GstDebugViewer/Plugins/FindBar.py

473 lines
16 KiB
Python
Raw Normal View History

2007-11-26 07:47:53 +00:00
# -*- coding: utf-8; mode: python; -*-
#
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
#
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
#
# This program 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 3 of the License, or (at your option)
# any later version.
#
# This program 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, see <http://www.gnu.org/licenses/>.
"""GStreamer Debug Viewer timeline widget plugin."""
import logging
from GstDebugViewer import Common, Data, GUI
from GstDebugViewer.Plugins import *
2007-12-10 09:49:39 +00:00
import pango
2007-11-26 07:47:53 +00:00
import gtk
class SearchOperation (object):
2007-12-10 15:06:23 +00:00
def __init__ (self, model, search_text, search_forward = True, start_position = None):
2007-11-26 07:47:53 +00:00
self.model = model
2007-12-10 15:06:23 +00:00
self.search_text = search_text
2007-11-26 07:47:53 +00:00
self.search_forward = search_forward
self.start_position = start_position
col_id = GUI.LogModelBase.COL_MESSAGE
2007-12-10 15:06:23 +00:00
len_search_text = len (search_text)
def match_func (model_row):
message = model_row[col_id]
2007-12-10 15:06:23 +00:00
if search_text in message:
# TODO: Return all match ranges here.
2007-12-10 15:06:23 +00:00
pos = message.find (search_text)
return ((pos, pos + len_search_text,),)
else:
return ()
self.match_func = match_func
2007-11-26 07:47:53 +00:00
class SearchSentinel (object):
def __init__ (self):
self.dispatcher = Common.Data.GSourceDispatcher ()
2007-12-10 12:22:51 +00:00
self.cancelled = False
2007-11-26 07:47:53 +00:00
def run_for (self, operation):
self.dispatcher.cancel ()
self.dispatcher (self.__process (operation))
2007-12-10 12:22:51 +00:00
self.cancelled = False
2007-11-26 07:47:53 +00:00
def abort (self):
self.dispatcher.cancel ()
2007-12-10 12:22:51 +00:00
self.cancelled = True
2007-11-26 07:47:53 +00:00
def __process (self, operation):
model = operation.model
if operation.start_position is not None:
start_pos = operation.start_position
elif operation.search_forward:
start_pos = 0
2007-11-26 07:47:53 +00:00
else:
start_pos = len (model) - 1
2007-11-26 07:47:53 +00:00
start_iter = model.iter_nth_child (None, start_pos)
2007-11-26 07:47:53 +00:00
match_func = operation.match_func
if operation.search_forward:
iter_next = model.iter_next
else:
# FIXME: This is really ugly.
nth_child = model.iter_nth_child
def iter_next_ ():
for i in xrange (start_pos, -1, -1):
yield nth_child (None, i)
yield None
it_ = iter_next_ ()
def iter_next (it):
return it_.next ()
2007-11-26 07:47:53 +00:00
YIELD_LIMIT = 1000
i = YIELD_LIMIT
tree_iter = start_iter
2007-12-10 12:22:51 +00:00
while tree_iter and not self.cancelled:
2007-11-26 07:47:53 +00:00
i -= 1
if i == 0:
yield True
i = YIELD_LIMIT
row = model[tree_iter]
if match_func (row):
2007-11-26 07:47:53 +00:00
self.handle_match_found (model, tree_iter)
tree_iter = iter_next (tree_iter)
2007-12-10 12:22:51 +00:00
if not self.cancelled:
self.handle_search_complete ()
2007-11-26 07:47:53 +00:00
yield False
def handle_match_found (self, model, tree_iter):
pass
def handle_search_complete (self):
pass
class FindBarWidget (gtk.HBox):
2007-12-10 09:49:39 +00:00
__status = {"no-match-found" : _N("No match found"),
"searching" : _N("Searching...")}
2007-12-03 09:35:31 +00:00
def __init__ (self, action_group):
2007-11-26 07:47:53 +00:00
gtk.HBox.__init__ (self)
label = gtk.Label (_("Find:"))
2007-12-03 09:35:31 +00:00
self.pack_start (label, False, False, 2)
2007-11-26 07:47:53 +00:00
self.entry = gtk.Entry ()
self.pack_start (self.entry)
2007-12-03 09:35:31 +00:00
prev_action = action_group.get_action ("goto-previous-search-result")
prev_button = gtk.Button ()
prev_action.connect_proxy (prev_button)
self.pack_start (prev_button, False, False, 0)
next_action = action_group.get_action ("goto-next-search-result")
next_button = gtk.Button ()
next_action.connect_proxy (next_button)
self.pack_start (next_button, False, False, 0)
2007-12-10 09:49:39 +00:00
self.status_label = gtk.Label ("")
self.status_label.props.xalign = 0.
attrs = pango.AttrList ()
attrs.insert (pango.AttrWeight (pango.WEIGHT_BOLD, 0, -1))
self.status_label.props.attributes = attrs
self.pack_start (self.status_label, False, False, 6)
self.__compute_status_size ()
self.status_label.connect ("notify::style", self.__handle_notify_style)
2007-11-26 07:47:53 +00:00
self.show_all ()
2007-12-10 09:49:39 +00:00
def __compute_status_size (self):
label = self.status_label
old_text = label.props.label
label.set_size_request (-1, -1)
max_width = 0
try:
for status in self.__status.values ():
self.__set_status (_(status))
width, height = label.size_request ()
max_width = max (max_width, width)
label.set_size_request (max_width, -1)
finally:
label.props.label = old_text
def __handle_notify_style (self, *a, **kw):
self.__compute_status_size ()
def __set_status (self, text):
self.status_label.props.label = text
def status_no_match_found (self):
self.__set_status (_(self.__status["no-match-found"]))
def status_searching (self):
self.__set_status (_(self.__status["searching"]))
def clear_status (self):
self.__set_status ("")
2007-11-26 07:47:53 +00:00
class FindBarFeature (FeatureBase):
2007-11-26 12:42:46 +00:00
def __init__ (self, app):
2007-11-26 07:47:53 +00:00
2007-11-26 12:42:46 +00:00
FeatureBase.__init__ (self, app)
2007-11-26 07:47:53 +00:00
self.logger = logging.getLogger ("ui.findbar")
self.action_group = gtk.ActionGroup ("FindBarActions")
self.action_group.add_toggle_actions ([("show-find-bar",
None,
_("Find Bar"),
"<Ctrl>F")])
2007-12-03 09:35:31 +00:00
self.action_group.add_actions ([("goto-next-search-result",
None, _("Goto Next Match"),
"<Ctrl>G"),
2007-12-03 09:35:31 +00:00
("goto-previous-search-result",
None, _("Goto Previous Match"),
"<Ctrl><Shift>G")])
2007-11-26 07:47:53 +00:00
self.bar = None
self.operation = None
2007-12-10 15:04:47 +00:00
self.search_state = None
self.next_match = None
self.prev_match = None
self.scroll_match = False
2007-11-26 07:47:53 +00:00
self.sentinel = SearchSentinel ()
self.sentinel.handle_match_found = self.handle_match_found
self.sentinel.handle_search_complete = self.handle_search_complete
def scroll_view_to_line (self, line_index):
view = self.log_view
path = (line_index,)
start_path, end_path = view.get_visible_range ()
if path >= start_path and path <= end_path:
self.logger.debug ("line index %i already visible, not scrolling", line_index)
return
self.logger.debug ("scrolling to line_index %i", line_index)
view.scroll_to_cell (path, use_align = True, row_align = .5)
def handle_attach_window (self, window):
2007-11-27 10:03:32 +00:00
self.window = window
2007-11-26 07:47:53 +00:00
ui = window.ui_manager
ui.insert_action_group (self.action_group, 0)
self.log_view = window.log_view
self.merge_id = ui.new_merge_id ()
for name, action_name in [("ViewFindBar", "show-find-bar",),
("ViewNextResult", "goto-next-search-result",),
("ViewPrevResult", "goto-previous-search-result",)]:
ui.add_ui (self.merge_id, "/menubar/ViewMenu/ViewMenuAdditions",
name, action_name, gtk.UI_MANAGER_MENUITEM, False)
2007-11-26 07:47:53 +00:00
box = window.widgets.vbox_view
2007-12-03 09:35:31 +00:00
self.bar = FindBarWidget (self.action_group)
2007-11-26 07:47:53 +00:00
box.pack_end (self.bar, False, False, 0)
self.bar.hide ()
action = self.action_group.get_action ("show-find-bar")
handler = self.handle_show_find_bar_action_toggled
2007-11-26 07:47:53 +00:00
action.connect ("toggled", handler)
2007-12-03 09:35:31 +00:00
action = self.action_group.get_action ("goto-previous-search-result")
handler = self.handle_goto_previous_search_result_action_activate
action.props.sensitive = False
2007-12-03 09:35:31 +00:00
action.connect ("activate", handler)
action = self.action_group.get_action ("goto-next-search-result")
handler = self.handle_goto_next_search_result_action_activate
action.props.sensitive = False
2007-12-03 09:35:31 +00:00
action.connect ("activate", handler)
2007-11-26 07:47:53 +00:00
self.bar.entry.connect ("changed", self.handle_entry_changed)
def handle_detach_window (self, window):
2007-11-27 10:03:32 +00:00
self.window = None
2007-11-26 07:47:53 +00:00
window.ui_manager.remove_ui (self.merge_id)
self.merge_id = None
def handle_show_find_bar_action_toggled (self, action):
2007-11-26 07:47:53 +00:00
if action.props.active:
self.bar.show ()
self.bar.entry.grab_focus ()
self.update_search ()
2007-11-26 07:47:53 +00:00
else:
try:
column = self.window.column_manager.find_item (name = "message")
del column.highlighters[self]
except KeyError:
pass
2007-12-10 09:49:39 +00:00
self.bar.clear_status ()
2007-11-26 07:47:53 +00:00
self.bar.hide ()
for action_name in ["goto-next-search-result",
"goto-previous-search-result"]:
self.action_group.get_action (action_name).props.sensitive = False
2007-11-26 07:47:53 +00:00
2007-12-03 09:35:31 +00:00
def handle_goto_previous_search_result_action_activate (self, action):
if self.prev_match is None:
self.logger.warning ("inconsistent action sensitivity")
return
2007-12-03 09:35:31 +00:00
self.scroll_view_to_line (self.prev_match)
self.prev_match = None
2007-12-03 09:35:31 +00:00
start_path = self.log_view.get_visible_range ()[0]
new_position = start_path[0] - 1
self.start_search_operation (start_position = new_position,
forward = False)
2007-12-03 09:35:31 +00:00
# FIXME
2007-12-03 09:35:31 +00:00
def handle_goto_next_search_result_action_activate (self, action):
2007-12-10 15:04:47 +00:00
if self.next_match is None:
self.logger.warning ("inconsistent action sensitivity")
return
2007-12-03 09:35:31 +00:00
2007-12-10 15:04:47 +00:00
self.scroll_view_to_line (self.next_match)
self.next_match = None
2007-12-03 09:35:31 +00:00
2007-12-10 15:04:47 +00:00
end_path = self.log_view.get_visible_range ()[1]
new_position = end_path[0] + 1
self.start_search_operation (start_position = new_position,
forward = True)
2007-12-10 15:04:47 +00:00
# FIXME: Finish.
2007-12-03 09:35:31 +00:00
2007-12-10 15:04:47 +00:00
## model = self.log_view.props.model
## start_path, end_path = self.log_view.get_visible_range ()
## start_index, end_index = start_path[0], end_path[0]
## for line_index in self.matches:
## if line_index > end_index:
## break
## else:
## return
## self.scroll_view_to_line (line_index)
2007-12-03 09:35:31 +00:00
2007-11-26 07:47:53 +00:00
def handle_entry_changed (self, entry):
self.update_search ()
def update_search (self):
2007-11-26 07:47:53 +00:00
model = self.log_view.props.model
search_text = self.bar.entry.props.text
2007-12-10 12:22:51 +00:00
column = self.window.column_manager.find_item (name = "message")
2007-12-10 15:06:23 +00:00
if search_text == "":
2007-11-26 07:47:53 +00:00
self.logger.debug ("search string set to '', aborting search")
2007-12-10 15:04:47 +00:00
self.search_state = None
self.next_match = None
self.prev_match = None
self.update_sensitivity ()
2007-11-26 07:47:53 +00:00
self.sentinel.abort ()
2007-12-10 12:22:51 +00:00
try:
del column.highlighters[self]
except KeyError:
pass
else:
2007-12-10 15:06:23 +00:00
self.logger.debug ("starting search for %r", search_text)
2007-12-10 15:04:47 +00:00
self.next_match = None
self.prev_match = None
self.update_sensitivity ()
self.scroll_match = True
2007-12-10 12:22:51 +00:00
start_path = self.log_view.get_visible_range ()[0]
2007-12-10 15:06:23 +00:00
self.start_search_operation (search_text, start_position = start_path[0])
2007-12-10 12:22:51 +00:00
self.bar.status_searching ()
column.highlighters[self] = self.operation.match_func
2007-11-26 07:47:53 +00:00
self.window.update_view ()
2007-12-10 15:04:47 +00:00
def update_sensitivity (self):
for name, value in (("goto-next-search-result", self.next_match,),
("goto-previous-search-result", self.prev_match,),):
action = self.action_group.get_action (name)
action.props.sensitive = (value is not None)
2007-12-10 15:06:23 +00:00
def start_search_operation (self, search_text = None, forward = True, start_position = None):
2007-12-10 15:04:47 +00:00
model = self.log_view.props.model
if forward:
self.search_state = "search-forward"
if start_position is None:
start_position = 0
else:
self.search_state = "search-backward"
if start_position is None:
start_position = len (model) - 1
2007-12-10 15:06:23 +00:00
if search_text is None:
2007-12-10 15:04:47 +00:00
operation = self.operation
if operation is None:
2007-12-10 15:06:23 +00:00
raise ValueError ("search_text not given but have no previous search operation")
search_text = operation.search_text
2007-12-10 15:04:47 +00:00
self.operation = SearchOperation (model, search_text,
start_position = start_position,
search_forward = forward)
2007-12-10 15:04:47 +00:00
self.sentinel.run_for (self.operation)
2007-11-26 07:47:53 +00:00
def handle_match_found (self, model, tree_iter):
2007-12-10 15:04:47 +00:00
if not self.search_state in ("search-forward", "search-backward",):
self.logger.warning ("inconsistent search state %r", self.search_state)
return
2007-11-26 07:47:53 +00:00
line_index = model.get_path (tree_iter)[0]
2007-12-10 15:04:47 +00:00
forward_search = (self.search_state == "search-forward")
if forward_search:
self.logger.debug ("forward search for %r matches line %i",
2007-12-10 15:06:23 +00:00
self.operation.search_text, line_index)
2007-12-10 15:04:47 +00:00
else:
self.logger.debug ("backward search for %r matches line %i",
2007-12-10 15:06:23 +00:00
self.operation.search_text, line_index)
2007-11-26 07:47:53 +00:00
2007-12-10 15:04:47 +00:00
self.sentinel.abort ()
2007-11-27 10:03:32 +00:00
2007-12-10 15:04:47 +00:00
if self.scroll_match:
self.logger.debug ("scrolling to matching line")
2007-11-26 07:47:53 +00:00
self.scroll_view_to_line (line_index)
2007-12-10 15:04:47 +00:00
# Now search for the next one:
self.scroll_match = False
# FIXME: Start with first line that is outside of the visible range.
self.start_search_operation (start_position = line_index + 1,
forward = forward_search)
2007-12-10 15:04:47 +00:00
else:
if forward_search:
self.next_match = line_index
self.search_state = "search-backward"
self.start_search_operation (forward = False,
start_position = line_index - 1)
2007-12-10 15:04:47 +00:00
else:
self.prev_match = line_index
self.update_sensitivity ()
self.bar.clear_status ()
2007-11-26 07:47:53 +00:00
def handle_search_complete (self):
2007-12-10 15:04:47 +00:00
if self.search_state == "search-forward":
self.logger.debug ("forward search for %r reached last line",
2007-12-10 15:06:23 +00:00
self.operation.search_text)
2007-12-10 15:04:47 +00:00
self.next_match = None
elif self.search_state == "search-backward":
self.logger.debug ("backward search for %r reached first line",
2007-12-10 15:06:23 +00:00
self.operation.search_text)
2007-12-10 15:04:47 +00:00
self.prev_match = None
else:
self.logger.warning ("inconsistent search state %r",
self.search_state)
return
self.update_sensitivity ()
if self.prev_match is None and self.next_match is None:
self.bar.status_no_match_found ()
2007-11-26 07:47:53 +00:00
class Plugin (PluginBase):
features = [FindBarFeature]