2009-08-06 23:54:10 +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 GUI module."""
|
|
|
|
|
2011-11-05 22:52:40 +00:00
|
|
|
ZOOM_FACTOR = 1.15
|
|
|
|
|
2009-08-06 23:54:10 +00:00
|
|
|
def _ (s):
|
|
|
|
return s
|
|
|
|
|
|
|
|
import os.path
|
|
|
|
from bisect import bisect_right, bisect_left
|
|
|
|
import logging
|
|
|
|
|
2012-08-23 22:24:55 +00:00
|
|
|
import pango
|
2009-08-06 23:54:10 +00:00
|
|
|
import gobject
|
|
|
|
import gtk
|
|
|
|
|
|
|
|
from GstDebugViewer import Common, Data, Main
|
|
|
|
from GstDebugViewer.GUI.columns import LineViewColumnManager, ViewColumnManager
|
|
|
|
from GstDebugViewer.GUI.filters import (CategoryFilter,
|
|
|
|
DebugLevelFilter,
|
|
|
|
FilenameFilter,
|
|
|
|
ObjectFilter)
|
|
|
|
from GstDebugViewer.GUI.models import (FilteredLogModel,
|
|
|
|
LazyLogModel,
|
|
|
|
LineViewLogModel,
|
|
|
|
LogModelBase,
|
|
|
|
RangeFilteredLogModel)
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
def action (func):
|
|
|
|
|
|
|
|
func.is_action_handler = True
|
|
|
|
|
|
|
|
return func
|
|
|
|
|
|
|
|
def iter_actions (manager):
|
|
|
|
|
|
|
|
cls = type (manager)
|
|
|
|
it = cls.__dict__.iteritems ()
|
|
|
|
for name, member in it:
|
|
|
|
try:
|
|
|
|
member.is_action_handler
|
|
|
|
except AttributeError:
|
|
|
|
continue
|
|
|
|
|
|
|
|
bound_method = getattr (manager, name)
|
|
|
|
|
|
|
|
assert name.startswith ("handle_")
|
|
|
|
assert name.endswith ("_action_activate")
|
|
|
|
action_name = name[len ("handle_"):-len ("_action_activate")]
|
|
|
|
action_name = action_name.replace ("_", "-")
|
|
|
|
|
|
|
|
yield (action_name, bound_method,)
|
|
|
|
|
2009-08-06 23:54:10 +00:00
|
|
|
class LineView (object):
|
|
|
|
|
|
|
|
def __init__ (self):
|
|
|
|
|
|
|
|
self.column_manager = LineViewColumnManager ()
|
|
|
|
|
|
|
|
def attach (self, window):
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
for action_name, handler in iter_actions (self):
|
|
|
|
action = getattr (window.actions, action_name)
|
|
|
|
action.connect ("activate", handler)
|
|
|
|
|
2009-08-06 23:54:10 +00:00
|
|
|
self.clear_action = window.actions.clear_line_view
|
|
|
|
|
|
|
|
self.line_view = window.widgets.line_view
|
|
|
|
self.line_view.connect ("row-activated", self.handle_line_view_row_activated)
|
|
|
|
|
|
|
|
ui = window.ui_manager
|
|
|
|
self.popup = ui.get_widget ("/ui/context/LineViewContextMenu").get_submenu ()
|
|
|
|
Common.GUI.widget_add_popup_menu (self.line_view, self.popup)
|
|
|
|
|
|
|
|
self.log_view = log_view = window.log_view
|
|
|
|
log_view.connect ("row-activated", self.handle_log_view_row_activated)
|
|
|
|
sel = log_view.get_selection ()
|
|
|
|
sel.connect ("changed", self.handle_log_view_selection_changed)
|
|
|
|
|
|
|
|
self.clear_action.props.sensitive = False
|
|
|
|
self.column_manager.attach (window)
|
|
|
|
|
|
|
|
def clear (self):
|
|
|
|
|
2011-09-07 14:11:58 +00:00
|
|
|
model = self.line_view.get_model ()
|
2009-08-06 23:54:10 +00:00
|
|
|
|
|
|
|
if len (model) == 0:
|
|
|
|
return
|
|
|
|
|
|
|
|
for i in range (1, len (model)):
|
|
|
|
model.remove_line (1)
|
|
|
|
|
|
|
|
self.clear_action.props.sensitive = False
|
|
|
|
|
|
|
|
def handle_attach_log_file (self, window):
|
|
|
|
|
2011-09-07 14:11:58 +00:00
|
|
|
self.line_view.set_model (LineViewLogModel (window.log_model))
|
2009-08-06 23:54:10 +00:00
|
|
|
|
|
|
|
def handle_line_view_row_activated (self, view, path, column):
|
|
|
|
|
|
|
|
line_index = path[0]
|
2011-09-07 14:11:58 +00:00
|
|
|
line_model = view.get_model ()
|
|
|
|
log_model = self.log_view.get_model ()
|
2009-08-06 23:54:10 +00:00
|
|
|
top_index = line_model.line_index_to_top (line_index)
|
|
|
|
log_index = log_model.line_index_from_top (top_index)
|
|
|
|
path = (log_index,)
|
|
|
|
self.log_view.scroll_to_cell (path, use_align = True, row_align = .5)
|
|
|
|
sel = self.log_view.get_selection ()
|
|
|
|
sel.select_path (path)
|
|
|
|
|
|
|
|
def handle_log_view_row_activated (self, view, path, column):
|
|
|
|
|
2011-09-07 14:11:58 +00:00
|
|
|
log_model = view.get_model ()
|
2009-08-06 23:54:10 +00:00
|
|
|
line_index = path[0]
|
|
|
|
|
|
|
|
top_line_index = log_model.line_index_to_top (line_index)
|
2011-09-07 14:11:58 +00:00
|
|
|
line_model = self.line_view.get_model ()
|
2009-08-06 23:54:10 +00:00
|
|
|
if line_model is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
if len (line_model):
|
|
|
|
timestamps = [row[line_model.COL_TIME] for row in line_model]
|
|
|
|
row = log_model[(line_index,)]
|
|
|
|
position = bisect_right (timestamps, row[line_model.COL_TIME])
|
|
|
|
else:
|
|
|
|
position = 0
|
|
|
|
if len (line_model) > 1:
|
|
|
|
other_index = line_model.line_index_to_top (position - 1)
|
|
|
|
else:
|
|
|
|
other_index = -1
|
|
|
|
if other_index == top_line_index and position != 1:
|
|
|
|
# Already have the line.
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
line_model.insert_line (position, top_line_index)
|
|
|
|
self.clear_action.props.sensitive = True
|
|
|
|
|
|
|
|
def handle_log_view_selection_changed (self, selection):
|
|
|
|
|
2011-09-07 14:11:58 +00:00
|
|
|
line_model = self.line_view.get_model ()
|
2009-08-06 23:54:10 +00:00
|
|
|
if line_model is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
model, tree_iter = selection.get_selected ()
|
|
|
|
|
|
|
|
if tree_iter is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
path = model.get_path (tree_iter)
|
|
|
|
line_index = model.line_index_to_top (path[0])
|
|
|
|
|
|
|
|
if len (line_model) == 0:
|
|
|
|
line_model.insert_line (0, line_index)
|
|
|
|
else:
|
|
|
|
line_model.replace_line (0, line_index)
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_clear_line_view_action_activate (self, action):
|
|
|
|
|
|
|
|
self.clear ()
|
|
|
|
|
|
|
|
class ProgressDialog (object):
|
|
|
|
|
|
|
|
def __init__ (self, window, title = ""):
|
|
|
|
|
2012-08-23 22:24:55 +00:00
|
|
|
bar = gtk.InfoBar ()
|
|
|
|
bar.props.message_type = gtk.MESSAGE_INFO
|
|
|
|
bar.connect ("response", self.__handle_info_bar_response)
|
|
|
|
bar.add_button (gtk.STOCK_CANCEL, 1)
|
|
|
|
area_box = bar.get_content_area ()
|
|
|
|
box = gtk.HBox (spacing = 8)
|
2009-08-06 23:54:10 +00:00
|
|
|
|
2012-08-23 22:24:55 +00:00
|
|
|
box.pack_start (gtk.Label (title), False, False, 0)
|
2009-08-06 23:54:10 +00:00
|
|
|
|
2012-08-23 22:24:55 +00:00
|
|
|
progress = gtk.ProgressBar ()
|
|
|
|
box.pack_start (progress, False, False, 0)
|
2009-08-06 23:54:10 +00:00
|
|
|
|
2012-08-23 22:24:55 +00:00
|
|
|
area_box.pack_start (box, False, False, 0)
|
|
|
|
|
|
|
|
self.widget = bar
|
|
|
|
self.__progress_bar = progress
|
|
|
|
|
|
|
|
def __handle_info_bar_response (self, info_bar, response):
|
2009-08-06 23:54:10 +00:00
|
|
|
|
|
|
|
self.handle_cancel ()
|
|
|
|
|
|
|
|
def handle_cancel (self):
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
def update (self, progress):
|
|
|
|
|
|
|
|
if self.__progress_bar is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
self.__progress_bar.props.fraction = progress
|
|
|
|
|
|
|
|
class Window (object):
|
|
|
|
|
|
|
|
def __init__ (self, app):
|
|
|
|
|
|
|
|
self.logger = logging.getLogger ("ui.window")
|
|
|
|
self.app = app
|
|
|
|
|
|
|
|
self.dispatcher = None
|
2012-08-23 22:24:55 +00:00
|
|
|
self.info_widget = None
|
2009-08-06 23:54:10 +00:00
|
|
|
self.progress_dialog = None
|
|
|
|
self.update_progress_id = None
|
|
|
|
|
|
|
|
self.window_state = Common.GUI.WindowState ()
|
|
|
|
self.column_manager = ViewColumnManager (app.state_section)
|
|
|
|
|
|
|
|
self.actions = Common.GUI.Actions ()
|
|
|
|
|
|
|
|
group = gtk.ActionGroup ("MenuActions")
|
|
|
|
group.add_actions ([("FileMenuAction", None, _("_File")),
|
|
|
|
("ViewMenuAction", None, _("_View")),
|
|
|
|
("ViewColumnsMenuAction", None, _("_Columns")),
|
|
|
|
("HelpMenuAction", None, _("_Help")),
|
|
|
|
("LineViewContextMenuAction", None, "")])
|
|
|
|
self.actions.add_group (group)
|
|
|
|
|
|
|
|
group = gtk.ActionGroup ("WindowActions")
|
|
|
|
group.add_actions ([("new-window", gtk.STOCK_NEW, _("_New Window"), "<Ctrl>N"),
|
|
|
|
("open-file", gtk.STOCK_OPEN, _("_Open File"), "<Ctrl>O"),
|
|
|
|
("reload-file", gtk.STOCK_REFRESH, _("_Reload File"), "<Ctrl>R"),
|
|
|
|
("close-window", gtk.STOCK_CLOSE, _("Close _Window"), "<Ctrl>W"),
|
|
|
|
("cancel-load", gtk.STOCK_CANCEL, None,),
|
|
|
|
("clear-line-view", gtk.STOCK_CLEAR, None),
|
2010-04-16 15:26:26 +00:00
|
|
|
("show-about", gtk.STOCK_ABOUT, None),
|
|
|
|
("enlarge-text", gtk.STOCK_ZOOM_IN, _("Enlarge Text"), "<Ctrl>plus"),
|
2011-11-05 22:05:00 +00:00
|
|
|
("shrink-text", gtk.STOCK_ZOOM_OUT, _("Shrink Text"), "<Ctrl>minus"),
|
|
|
|
("reset-text", gtk.STOCK_ZOOM_100, _("Normal Text Size"), "<Ctrl>0")])
|
2009-08-06 23:54:10 +00:00
|
|
|
self.actions.add_group (group)
|
|
|
|
self.actions.reload_file.props.sensitive = False
|
|
|
|
|
|
|
|
group = gtk.ActionGroup ("RowActions")
|
2011-09-09 20:02:28 +00:00
|
|
|
group.add_actions ([("hide-before-line", None, _("Hide lines before this point")),
|
|
|
|
("hide-after-line", None, _("Hide lines after this point")),
|
2009-08-06 23:54:10 +00:00
|
|
|
("show-hidden-lines", None, _("Show hidden lines")),
|
|
|
|
("edit-copy-line", gtk.STOCK_COPY, _("Copy line"), "<Ctrl>C"),
|
|
|
|
("edit-copy-message", gtk.STOCK_COPY, _("Copy message"), ""),
|
2009-10-16 18:45:29 +00:00
|
|
|
("set-base-time", None, _("Set base time")),
|
2009-08-06 23:54:10 +00:00
|
|
|
("hide-log-level", None, _("Hide log level")),
|
|
|
|
("hide-log-category", None, _("Hide log category")),
|
|
|
|
("hide-log-object", None, _("Hide object")),
|
|
|
|
("hide-filename", None, _("Hide filename"))])
|
|
|
|
group.props.sensitive = False
|
|
|
|
self.actions.add_group (group)
|
|
|
|
|
|
|
|
self.actions.add_group (self.column_manager.action_group)
|
|
|
|
|
|
|
|
self.log_file = None
|
|
|
|
self.setup_model (LazyLogModel ())
|
|
|
|
|
|
|
|
self.widget_factory = Common.GUI.WidgetFactory (Main.Paths.data_dir)
|
|
|
|
self.widgets = self.widget_factory.make ("main-window.ui", "main_window")
|
|
|
|
|
|
|
|
ui_filename = os.path.join (Main.Paths.data_dir, "menus.ui")
|
|
|
|
self.ui_factory = Common.GUI.UIFactory (ui_filename, self.actions)
|
|
|
|
|
|
|
|
self.ui_manager = ui = self.ui_factory.make ()
|
|
|
|
menubar = ui.get_widget ("/ui/menubar")
|
|
|
|
self.widgets.vbox_main.pack_start (menubar, False, False, 0)
|
|
|
|
|
|
|
|
self.gtk_window = self.widgets.main_window
|
|
|
|
self.gtk_window.add_accel_group (ui.get_accel_group ())
|
|
|
|
self.log_view = self.widgets.log_view
|
|
|
|
self.log_view.drag_dest_unset ()
|
|
|
|
self.log_view.set_search_column (-1)
|
|
|
|
sel = self.log_view.get_selection ()
|
|
|
|
sel.connect ("changed", self.handle_log_view_selection_changed)
|
|
|
|
|
|
|
|
self.view_popup = ui.get_widget ("/ui/context/LogViewContextMenu").get_submenu ()
|
|
|
|
Common.GUI.widget_add_popup_menu (self.log_view, self.view_popup)
|
|
|
|
|
|
|
|
self.line_view = LineView ()
|
|
|
|
|
|
|
|
self.attach ()
|
|
|
|
self.column_manager.attach (self.log_view)
|
|
|
|
|
|
|
|
def setup_model (self, model, filter = False):
|
|
|
|
|
|
|
|
self.log_model = model
|
|
|
|
self.log_range = RangeFilteredLogModel (self.log_model)
|
|
|
|
if filter:
|
|
|
|
self.log_filter = FilteredLogModel (self.log_range)
|
|
|
|
self.log_filter.handle_process_finished = self.handle_log_filter_process_finished
|
|
|
|
else:
|
|
|
|
self.log_filter = None
|
|
|
|
|
|
|
|
def get_top_attach_point (self):
|
|
|
|
|
|
|
|
return self.widgets.vbox_main
|
|
|
|
|
|
|
|
def get_side_attach_point (self):
|
|
|
|
|
|
|
|
return self.widgets.hbox_view
|
|
|
|
|
|
|
|
def attach (self):
|
|
|
|
|
2011-11-05 22:52:40 +00:00
|
|
|
self.zoom_level = 0
|
2011-11-05 22:47:47 +00:00
|
|
|
zoom_percent = self.app.state_section.zoom_level
|
|
|
|
if zoom_percent:
|
|
|
|
self.restore_zoom (float (zoom_percent) / 100.)
|
|
|
|
|
2009-08-06 23:54:10 +00:00
|
|
|
self.window_state.attach (window = self.gtk_window,
|
|
|
|
state = self.app.state_section)
|
|
|
|
|
|
|
|
self.clipboard = gtk.Clipboard (self.gtk_window.get_display (),
|
|
|
|
gtk.gdk.SELECTION_CLIPBOARD)
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
for action_name, handler in iter_actions (self):
|
|
|
|
action = getattr (self.actions, action_name)
|
2009-08-06 23:54:10 +00:00
|
|
|
action.connect ("activate", handler)
|
|
|
|
|
|
|
|
self.gtk_window.connect ("delete-event", self.handle_window_delete_event)
|
|
|
|
|
|
|
|
self.features = []
|
|
|
|
|
|
|
|
for plugin_feature in self.app.iter_plugin_features ():
|
|
|
|
feature = plugin_feature (self.app)
|
|
|
|
self.features.append (feature)
|
|
|
|
|
|
|
|
for feature in self.features:
|
|
|
|
feature.handle_attach_window (self)
|
|
|
|
|
|
|
|
# FIXME: With multiple selection mode, browsing the list with key
|
|
|
|
# up/down slows to a crawl! WTF is wrong with this stupid widget???
|
|
|
|
sel = self.log_view.get_selection ()
|
|
|
|
sel.set_mode (gtk.SELECTION_BROWSE)
|
|
|
|
|
|
|
|
self.line_view.attach (self)
|
|
|
|
|
2012-08-23 22:10:05 +00:00
|
|
|
# Do not translate; fallback application name for e.g. gnome-shell if
|
|
|
|
# the desktop file is not installed:
|
|
|
|
self.gtk_window.set_wmclass ("gst-debug-viewer", "GStreamer Debug Viewer")
|
|
|
|
|
2009-08-06 23:54:10 +00:00
|
|
|
self.gtk_window.show ()
|
|
|
|
|
|
|
|
def detach (self):
|
|
|
|
|
|
|
|
self.set_log_file (None)
|
|
|
|
for feature in self.features:
|
|
|
|
feature.handle_detach_window (self)
|
|
|
|
|
|
|
|
self.window_state.detach ()
|
|
|
|
self.column_manager.detach ()
|
|
|
|
|
|
|
|
def get_active_line_index (self):
|
|
|
|
|
|
|
|
selection = self.log_view.get_selection ()
|
|
|
|
model, tree_iter = selection.get_selected ()
|
|
|
|
if tree_iter is None:
|
|
|
|
raise ValueError ("no line selected")
|
|
|
|
path = model.get_path (tree_iter)
|
|
|
|
return path[0]
|
|
|
|
|
|
|
|
def get_active_line (self):
|
|
|
|
|
|
|
|
selection = self.log_view.get_selection ()
|
|
|
|
model, tree_iter = selection.get_selected ()
|
|
|
|
if tree_iter is None:
|
|
|
|
raise ValueError ("no line selected")
|
2011-09-07 14:11:58 +00:00
|
|
|
model = self.log_view.get_model ()
|
2009-08-06 23:54:10 +00:00
|
|
|
return model.get (tree_iter, *LogModelBase.column_ids)
|
|
|
|
|
|
|
|
def close (self, *a, **kw):
|
|
|
|
|
|
|
|
self.logger.debug ("closing window, detaching")
|
|
|
|
self.detach ()
|
|
|
|
self.gtk_window.hide ()
|
|
|
|
self.logger.debug ("requesting close from app")
|
|
|
|
self.app.close_window (self)
|
|
|
|
|
|
|
|
def push_view_state (self):
|
|
|
|
|
|
|
|
self.default_index = None
|
|
|
|
self.default_start_index = None
|
|
|
|
|
2011-09-07 14:11:58 +00:00
|
|
|
model = self.log_view.get_model ()
|
2009-08-06 23:54:10 +00:00
|
|
|
if model is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
line_index = self.get_active_line_index ()
|
|
|
|
except ValueError:
|
|
|
|
super_index = None
|
|
|
|
self.logger.debug ("no line selected")
|
|
|
|
else:
|
|
|
|
super_index = model.line_index_to_top (line_index)
|
|
|
|
self.logger.debug ("pushing selected line %i (abs %i)",
|
|
|
|
line_index, super_index)
|
|
|
|
|
|
|
|
self.default_index = super_index
|
|
|
|
|
|
|
|
vis_range = self.log_view.get_visible_range ()
|
|
|
|
if vis_range is not None:
|
|
|
|
start_path, end_path = vis_range
|
|
|
|
start_index = start_path[0]
|
|
|
|
self.default_start_index = model.line_index_to_top (start_index)
|
|
|
|
|
|
|
|
def update_model (self, model = None):
|
|
|
|
|
|
|
|
if model is None:
|
2011-09-07 14:11:58 +00:00
|
|
|
model = self.log_view.get_model ()
|
2009-08-06 23:54:10 +00:00
|
|
|
|
2011-09-07 14:11:58 +00:00
|
|
|
previous_model = self.log_view.get_model ()
|
2009-08-06 23:54:10 +00:00
|
|
|
|
|
|
|
if previous_model == model:
|
|
|
|
# Force update.
|
|
|
|
self.log_view.set_model (None)
|
2011-09-07 14:11:58 +00:00
|
|
|
self.log_view.set_model (model)
|
2009-08-06 23:54:10 +00:00
|
|
|
|
|
|
|
def pop_view_state (self, scroll_to_selection = False):
|
|
|
|
|
2011-09-07 14:11:58 +00:00
|
|
|
model = self.log_view.get_model ()
|
2009-08-06 23:54:10 +00:00
|
|
|
if model is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
selected_index = self.default_index
|
|
|
|
start_index = self.default_start_index
|
|
|
|
|
|
|
|
if selected_index is not None:
|
|
|
|
|
|
|
|
try:
|
|
|
|
select_index = model.line_index_from_top (selected_index)
|
2011-11-16 18:45:16 +00:00
|
|
|
except IndexError as exc:
|
2009-08-06 23:54:10 +00:00
|
|
|
self.logger.debug ("abs line index %i filtered out, not reselecting",
|
|
|
|
selected_index)
|
|
|
|
else:
|
|
|
|
assert select_index >= 0
|
|
|
|
sel = self.log_view.get_selection ()
|
|
|
|
path = (select_index,)
|
|
|
|
sel.select_path (path)
|
|
|
|
|
|
|
|
if start_index is None or scroll_to_selection:
|
|
|
|
self.log_view.scroll_to_cell (path, use_align = True, row_align = .5)
|
|
|
|
|
|
|
|
if start_index is not None and not scroll_to_selection:
|
|
|
|
|
|
|
|
def traverse ():
|
|
|
|
for i in xrange (start_index, len (model)):
|
|
|
|
yield i
|
|
|
|
for i in xrange (start_index - 1, 0, -1):
|
|
|
|
yield i
|
|
|
|
for current_index in traverse ():
|
|
|
|
try:
|
|
|
|
target_index = model.line_index_from_top (current_index)
|
|
|
|
except IndexError:
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
path = (target_index,)
|
|
|
|
self.log_view.scroll_to_cell (path, use_align = True, row_align = 0.)
|
|
|
|
break
|
|
|
|
|
|
|
|
def update_view (self):
|
|
|
|
|
|
|
|
view = self.log_view
|
2011-09-07 14:11:58 +00:00
|
|
|
model = view.get_model ()
|
2009-08-06 23:54:10 +00:00
|
|
|
|
|
|
|
start_path, end_path = view.get_visible_range ()
|
|
|
|
start_index, end_index = start_path[0], end_path[0]
|
|
|
|
|
|
|
|
for line_index in range (start_index, end_index + 1):
|
|
|
|
path = (line_index,)
|
|
|
|
tree_iter = model.get_iter (path)
|
|
|
|
model.row_changed (path, tree_iter)
|
|
|
|
|
|
|
|
def handle_log_view_selection_changed (self, selection):
|
|
|
|
|
|
|
|
try:
|
|
|
|
line_index = self.get_active_line_index ()
|
|
|
|
except ValueError:
|
|
|
|
first_selected = True
|
|
|
|
last_selected = True
|
|
|
|
else:
|
|
|
|
first_selected = (line_index == 0)
|
2011-09-07 14:11:58 +00:00
|
|
|
last_selected = (line_index == len (self.log_view.get_model ()) - 1)
|
2009-08-06 23:54:10 +00:00
|
|
|
|
|
|
|
self.actions.hide_before_line.props.sensitive = not first_selected
|
|
|
|
self.actions.hide_after_line.props.sensitive = not last_selected
|
|
|
|
|
|
|
|
def handle_window_delete_event (self, window, event):
|
|
|
|
|
|
|
|
self.actions.close_window.activate ()
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_new_window_action_activate (self, action):
|
|
|
|
|
|
|
|
self.app.open_window ()
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_open_file_action_activate (self, action):
|
|
|
|
|
|
|
|
dialog = gtk.FileChooserDialog (None, self.gtk_window,
|
|
|
|
gtk.FILE_CHOOSER_ACTION_OPEN,
|
|
|
|
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
|
|
|
|
gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT,))
|
|
|
|
response = dialog.run ()
|
|
|
|
dialog.hide ()
|
|
|
|
if response == gtk.RESPONSE_ACCEPT:
|
|
|
|
self.set_log_file (dialog.get_filename ())
|
|
|
|
dialog.destroy ()
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_reload_file_action_activate (self, action):
|
|
|
|
|
|
|
|
if self.log_file is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
self.set_log_file (self.log_file.path)
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_cancel_load_action_activate (self, action):
|
|
|
|
|
|
|
|
self.logger.debug ("cancelling data load")
|
|
|
|
|
|
|
|
self.set_log_file (None)
|
|
|
|
|
2012-08-23 22:24:55 +00:00
|
|
|
if self.progress_dialog is not None:
|
|
|
|
self.hide_info ()
|
2009-08-06 23:54:10 +00:00
|
|
|
self.progress_dialog = None
|
|
|
|
if self.update_progress_id is not None:
|
|
|
|
gobject.source_remove (self.update_progress_id)
|
|
|
|
self.update_progress_id = None
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_close_window_action_activate (self, action):
|
|
|
|
|
|
|
|
self.close ()
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_hide_after_line_action_activate (self, action):
|
|
|
|
|
|
|
|
self.hide_range (after = True)
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_hide_before_line_action_activate (self, action):
|
|
|
|
|
|
|
|
self.hide_range (after = False)
|
|
|
|
|
|
|
|
def hide_range (self, after):
|
|
|
|
|
2011-09-07 14:11:58 +00:00
|
|
|
model = self.log_view.get_model ()
|
2009-08-06 23:54:10 +00:00
|
|
|
try:
|
|
|
|
filtered_line_index = self.get_active_line_index ()
|
|
|
|
except ValueError:
|
|
|
|
return
|
|
|
|
|
|
|
|
if after:
|
|
|
|
first_index = model.line_index_to_top (0)
|
|
|
|
last_index = model.line_index_to_top (filtered_line_index)
|
|
|
|
|
|
|
|
self.logger.info ("hiding lines after %i (abs %i), first line is abs %i",
|
|
|
|
filtered_line_index,
|
|
|
|
last_index,
|
|
|
|
first_index)
|
|
|
|
else:
|
|
|
|
first_index = model.line_index_to_top (filtered_line_index)
|
|
|
|
last_index = model.line_index_to_top (len (model) - 1)
|
|
|
|
|
|
|
|
self.logger.info ("hiding lines before %i (abs %i), last line is abs %i",
|
|
|
|
filtered_line_index,
|
|
|
|
first_index,
|
|
|
|
last_index)
|
|
|
|
|
|
|
|
self.push_view_state ()
|
|
|
|
start_index = first_index
|
|
|
|
stop_index = last_index + 1
|
|
|
|
self.log_range.set_range (start_index, stop_index)
|
|
|
|
if self.log_filter:
|
|
|
|
self.log_filter.super_model_changed_range ()
|
|
|
|
self.update_model ()
|
|
|
|
self.pop_view_state ()
|
|
|
|
self.actions.show_hidden_lines.props.sensitive = True
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_show_hidden_lines_action_activate (self, action):
|
|
|
|
|
|
|
|
self.logger.info ("restoring model filter to show all lines")
|
|
|
|
self.push_view_state ()
|
|
|
|
self.log_range.reset ()
|
|
|
|
self.log_filter = None
|
|
|
|
self.update_model (self.log_range)
|
|
|
|
self.pop_view_state (scroll_to_selection = True)
|
|
|
|
self.actions.show_hidden_lines.props.sensitive = False
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_edit_copy_line_action_activate (self, action):
|
|
|
|
|
|
|
|
# TODO: Should probably copy the _exact_ line as taken from the file.
|
|
|
|
|
|
|
|
line = self.get_active_line ()
|
|
|
|
log_line = Data.LogLine (line)
|
|
|
|
self.clipboard.set_text (log_line.line_string ())
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_edit_copy_message_action_activate (self, action):
|
|
|
|
|
|
|
|
col_id = LogModelBase.COL_MESSAGE
|
|
|
|
self.clipboard.set_text (self.get_active_line ()[col_id])
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2010-04-16 15:26:26 +00:00
|
|
|
def handle_enlarge_text_action_activate (self, action):
|
2011-11-05 22:52:40 +00:00
|
|
|
|
|
|
|
self.update_zoom_level (1)
|
2010-04-16 15:26:26 +00:00
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2010-04-16 15:26:26 +00:00
|
|
|
def handle_shrink_text_action_activate (self, action):
|
2011-11-05 22:52:40 +00:00
|
|
|
|
|
|
|
self.update_zoom_level (-1)
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2011-11-05 22:05:00 +00:00
|
|
|
def handle_reset_text_action_activate (self, action):
|
|
|
|
|
|
|
|
self.update_zoom_level (-self.zoom_level)
|
|
|
|
|
2011-11-05 22:47:47 +00:00
|
|
|
def restore_zoom (self, scale):
|
|
|
|
|
|
|
|
from math import log
|
|
|
|
|
|
|
|
self.zoom_level = int (round (log (scale) / log (ZOOM_FACTOR)))
|
|
|
|
|
|
|
|
self.column_manager.set_zoom (scale)
|
|
|
|
|
2011-11-05 22:52:40 +00:00
|
|
|
def update_zoom_level (self, delta_step):
|
|
|
|
|
|
|
|
if not delta_step:
|
|
|
|
return
|
|
|
|
|
|
|
|
self.zoom_level += delta_step
|
|
|
|
scale = ZOOM_FACTOR ** self.zoom_level
|
|
|
|
|
|
|
|
self.column_manager.set_zoom (scale)
|
2010-04-16 15:26:26 +00:00
|
|
|
|
2011-11-05 22:47:47 +00:00
|
|
|
self.app.state_section.zoom_level = int (round (scale * 100.))
|
|
|
|
|
2012-08-23 22:24:55 +00:00
|
|
|
def show_info (self, widget):
|
|
|
|
|
|
|
|
self.hide_info ()
|
|
|
|
|
|
|
|
box = self.widgets.vbox_main
|
|
|
|
box.pack_start (widget, False, False, 0)
|
|
|
|
box.reorder_child (widget, 2)
|
|
|
|
widget.show_all ()
|
|
|
|
self.info_widget = widget
|
|
|
|
|
|
|
|
def hide_info (self):
|
|
|
|
|
|
|
|
if self.info_widget is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
self.info_widget.destroy ()
|
|
|
|
self.info_widget = None
|
|
|
|
|
2009-08-06 23:54:10 +00:00
|
|
|
def add_model_filter (self, filter):
|
|
|
|
|
|
|
|
self.progress_dialog = ProgressDialog (self, _("Filtering"))
|
2012-08-23 22:24:55 +00:00
|
|
|
self.show_info (self.progress_dialog.widget)
|
2009-08-06 23:54:10 +00:00
|
|
|
self.progress_dialog.handle_cancel = self.handle_filter_progress_dialog_cancel
|
|
|
|
dispatcher = Common.Data.GSourceDispatcher ()
|
|
|
|
self.filter_dispatcher = dispatcher
|
|
|
|
|
|
|
|
# FIXME: Unsetting the model to keep e.g. the dispatched timeline
|
|
|
|
# sentinel from collecting data while we filter idly, which slows
|
|
|
|
# things down for nothing.
|
|
|
|
self.push_view_state ()
|
|
|
|
self.log_view.set_model (None)
|
|
|
|
if self.log_filter is None:
|
|
|
|
self.log_filter = FilteredLogModel (self.log_range)
|
|
|
|
self.log_filter.handle_process_finished = self.handle_log_filter_process_finished
|
|
|
|
self.log_filter.add_filter (filter, dispatcher = dispatcher)
|
|
|
|
|
|
|
|
gobject.timeout_add (250, self.update_filter_progress)
|
|
|
|
|
|
|
|
def update_filter_progress (self):
|
|
|
|
|
|
|
|
if self.progress_dialog is None:
|
|
|
|
return False
|
|
|
|
|
|
|
|
try:
|
|
|
|
progress = self.log_filter.get_filter_progress ()
|
|
|
|
except ValueError:
|
|
|
|
self.logger.warning ("no filter process running")
|
|
|
|
return False
|
|
|
|
|
|
|
|
self.progress_dialog.update (progress)
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def handle_filter_progress_dialog_cancel (self):
|
|
|
|
|
2012-08-23 22:24:55 +00:00
|
|
|
self.hide_info ()
|
2009-08-06 23:54:10 +00:00
|
|
|
self.progress_dialog = None
|
|
|
|
|
|
|
|
self.log_filter.abort_process ()
|
2011-09-07 14:11:58 +00:00
|
|
|
self.log_view.set_model (self.log_filter)
|
2009-08-06 23:54:10 +00:00
|
|
|
self.pop_view_state ()
|
|
|
|
|
|
|
|
def handle_log_filter_process_finished (self):
|
|
|
|
|
2012-08-23 22:24:55 +00:00
|
|
|
self.hide_info ()
|
2009-08-06 23:54:10 +00:00
|
|
|
self.progress_dialog = None
|
|
|
|
|
|
|
|
# No push_view_state here, did this in add_model_filter.
|
|
|
|
self.update_model (self.log_filter)
|
|
|
|
self.pop_view_state ()
|
|
|
|
|
|
|
|
self.actions.show_hidden_lines.props.sensitive = True
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-10-16 18:45:29 +00:00
|
|
|
def handle_set_base_time_action_activate (self, action):
|
|
|
|
|
|
|
|
row = self.get_active_line ()
|
2011-11-06 12:35:26 +00:00
|
|
|
self.column_manager.set_base_time (row[LogModelBase.COL_TIME])
|
2009-10-16 18:45:29 +00:00
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_hide_log_level_action_activate (self, action):
|
|
|
|
|
|
|
|
row = self.get_active_line ()
|
|
|
|
debug_level = row[LogModelBase.COL_LEVEL]
|
|
|
|
self.add_model_filter (DebugLevelFilter (debug_level))
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_hide_log_category_action_activate (self, action):
|
|
|
|
|
|
|
|
row = self.get_active_line ()
|
|
|
|
category = row[LogModelBase.COL_CATEGORY]
|
|
|
|
self.add_model_filter (CategoryFilter (category))
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_hide_log_object_action_activate (self, action):
|
|
|
|
|
|
|
|
row = self.get_active_line ()
|
|
|
|
object_ = row[LogModelBase.COL_OBJECT]
|
|
|
|
self.add_model_filter (ObjectFilter (object_))
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_hide_filename_action_activate (self, action):
|
|
|
|
|
|
|
|
row = self.get_active_line ()
|
|
|
|
filename = row[LogModelBase.COL_FILENAME]
|
|
|
|
self.add_model_filter (FilenameFilter (filename))
|
|
|
|
|
2011-11-16 19:23:31 +00:00
|
|
|
@action
|
2009-08-06 23:54:10 +00:00
|
|
|
def handle_show_about_action_activate (self, action):
|
|
|
|
|
|
|
|
from GstDebugViewer import version
|
|
|
|
|
|
|
|
dialog = self.widget_factory.make_one ("about-dialog.ui", "about_dialog")
|
|
|
|
dialog.props.version = version
|
|
|
|
dialog.run ()
|
|
|
|
dialog.destroy ()
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _timestamp_cell_data_func (column, renderer, model, tree_iter):
|
|
|
|
|
|
|
|
ts = model.get_value (tree_iter, LogModel.COL_TIME)
|
|
|
|
renderer.props.text = Data.time_args (ts)
|
|
|
|
|
|
|
|
def _message_cell_data_func (self, column, renderer, model, tree_iter):
|
|
|
|
|
|
|
|
offset = model.get_value (tree_iter, LogModel.COL_MESSAGE_OFFSET)
|
|
|
|
self.log_file.seek (offset)
|
|
|
|
renderer.props.text = strip_escape (self.log_file.readline ().strip ())
|
|
|
|
|
|
|
|
def set_log_file (self, filename):
|
|
|
|
|
|
|
|
if self.log_file is not None:
|
|
|
|
for feature in self.features:
|
|
|
|
feature.handle_detach_log_file (self, self.log_file)
|
|
|
|
|
|
|
|
if filename is None:
|
|
|
|
if self.dispatcher is not None:
|
|
|
|
self.dispatcher.cancel ()
|
|
|
|
self.dispatcher = None
|
|
|
|
self.log_file = None
|
|
|
|
self.actions.groups["RowActions"].props.sensitive = False
|
|
|
|
else:
|
|
|
|
self.logger.debug ("setting log file %r", filename)
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.setup_model (LazyLogModel ())
|
|
|
|
|
|
|
|
self.dispatcher = Common.Data.GSourceDispatcher ()
|
|
|
|
self.log_file = Data.LogFile (filename, self.dispatcher)
|
2011-11-16 18:45:16 +00:00
|
|
|
except EnvironmentError as exc:
|
2009-08-06 23:54:10 +00:00
|
|
|
try:
|
|
|
|
file_size = os.path.getsize (filename)
|
|
|
|
except EnvironmentError:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
if file_size == 0:
|
|
|
|
# Trying to mmap an empty file results in an invalid
|
|
|
|
# argument error.
|
|
|
|
self.show_error (_("Could not open file"),
|
|
|
|
_("The selected file is empty"))
|
|
|
|
return
|
|
|
|
self.handle_environment_error (exc, filename)
|
|
|
|
return
|
|
|
|
|
|
|
|
basename = os.path.basename (filename)
|
|
|
|
self.gtk_window.props.title = _("%s - GStreamer Debug Viewer") % (basename,)
|
|
|
|
|
|
|
|
self.log_file.consumers.append (self)
|
|
|
|
self.log_file.start_loading ()
|
|
|
|
|
|
|
|
def handle_environment_error (self, exc, filename):
|
|
|
|
|
|
|
|
self.show_error (_("Could not open file"), str (exc))
|
|
|
|
|
|
|
|
def show_error (self, message1, message2):
|
|
|
|
|
2012-08-23 22:24:55 +00:00
|
|
|
bar = gtk.InfoBar ()
|
|
|
|
bar.props.message_type = gtk.MESSAGE_ERROR
|
|
|
|
box = bar.get_content_area ()
|
|
|
|
|
|
|
|
attrs = pango.AttrList ()
|
|
|
|
attrs.insert (pango.AttrWeight (pango.WEIGHT_BOLD, 0, len (message1)))
|
|
|
|
label = gtk.Label ()
|
|
|
|
label.props.label = "%s %s" % (message1, message2)
|
|
|
|
label.props.attributes = attrs
|
|
|
|
label.props.selectable = True
|
|
|
|
box.pack_start (label, False, False, 0)
|
|
|
|
|
|
|
|
self.show_info (bar)
|
2009-08-06 23:54:10 +00:00
|
|
|
|
|
|
|
def handle_load_started (self):
|
|
|
|
|
|
|
|
self.logger.debug ("load has started")
|
|
|
|
|
|
|
|
self.progress_dialog = ProgressDialog (self, _("Loading log file"))
|
2012-08-23 22:24:55 +00:00
|
|
|
self.show_info (self.progress_dialog.widget)
|
2009-08-06 23:54:10 +00:00
|
|
|
self.progress_dialog.handle_cancel = self.handle_load_progress_dialog_cancel
|
|
|
|
self.update_progress_id = gobject.timeout_add (250, self.update_load_progress)
|
|
|
|
|
|
|
|
def handle_load_progress_dialog_cancel (self):
|
|
|
|
|
|
|
|
self.actions.cancel_load.activate ()
|
|
|
|
|
|
|
|
def update_load_progress (self):
|
|
|
|
|
|
|
|
if self.progress_dialog is None:
|
|
|
|
self.logger.debug ("progress dialog is gone, removing progress update timeout")
|
|
|
|
self.update_progress_id = None
|
|
|
|
return False
|
|
|
|
|
|
|
|
progress = self.log_file.get_load_progress ()
|
|
|
|
self.progress_dialog.update (progress)
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def handle_load_finished (self):
|
|
|
|
|
|
|
|
self.logger.debug ("load has finshed")
|
|
|
|
|
2012-08-23 22:24:55 +00:00
|
|
|
self.hide_info ()
|
2009-08-06 23:54:10 +00:00
|
|
|
self.progress_dialog = None
|
|
|
|
|
|
|
|
self.log_model.set_log (self.log_file)
|
|
|
|
self.log_range.reset ()
|
|
|
|
self.log_filter = None
|
|
|
|
|
|
|
|
self.actions.reload_file.props.sensitive = True
|
|
|
|
self.actions.groups["RowActions"].props.sensitive = True
|
|
|
|
self.actions.show_hidden_lines.props.sensitive = False
|
|
|
|
|
2012-08-23 23:20:05 +00:00
|
|
|
if len (self.log_model) == 0:
|
|
|
|
self.show_error (_("The file does not contain any parsable lines."),
|
|
|
|
_("It is not a GStreamer log file."))
|
|
|
|
|
2009-08-06 23:54:10 +00:00
|
|
|
def idle_set ():
|
2011-09-07 14:11:58 +00:00
|
|
|
self.log_view.set_model (self.log_range)
|
|
|
|
|
2009-08-06 23:54:10 +00:00
|
|
|
self.line_view.handle_attach_log_file (self)
|
|
|
|
for feature in self.features:
|
|
|
|
feature.handle_attach_log_file (self, self.log_file)
|
|
|
|
if len (self.log_range):
|
|
|
|
sel = self.log_view.get_selection ()
|
|
|
|
sel.select_path ((0,))
|
|
|
|
return False
|
|
|
|
|
|
|
|
gobject.idle_add (idle_set)
|