gstreamer/debug-viewer/GstDebugViewer/GUI.py
2014-09-11 20:51:36 +02:00

1196 lines
36 KiB
Python
Executable file

#!/usr/bin/python
# -*- coding: utf-8; mode: python; -*-
##
## gst-debug-viewer.py: GStreamer debug log viewer
##
## Copyright (C) 2006 Rene 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 2 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 Lesser General Public
## License along with this library; if not, write to the Free
## Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
## Boston, MA 02110-1301 USA
##
__author__ = u"René Stadler <mail@renestadler.de>"
__version__ = "0.1"
def _ (s):
return s
import sys
import os
import os.path
from operator import add
from sets import Set
import logging
import pygtk
pygtk.require ("2.0")
import gobject
import gtk
import gtk.glade
## import gnome # FIXME
import GstDebugViewer.Common.Data
import GstDebugViewer.Common.GUI
import GstDebugViewer.Common.Main
Common = GstDebugViewer.Common
from GstDebugViewer.Common import utils
from GstDebugViewer import Data, Main
## # Keep in sync with gst-inspector.py!
## class MetaModel (gobject.GObjectMeta):
## def __init__ (cls, name, bases, dict):
## super (MetaModel, cls).__init__ (name, bases, dict)
## columns = cls.columns
## column_types = []
## def gen ():
## i = 0
## it = iter (columns)
## while True:
## yield (i, it.next (), it.next (),)
## i += 1
## for col_index, col_name, col_type in gen ():
## setattr (cls, col_name, col_index)
## column_types.append (col_type)
## cls.column_types = tuple (column_types)
class LazyLogModel (gtk.GenericTreeModel):
__metaclass__ = Common.GUI.MetaModel
columns = ("COL_LEVEL", str,
"COL_PID", int,
"COL_THREAD", gobject.TYPE_UINT64,
"COL_TIME", gobject.TYPE_UINT64,
"COL_CATEGORY", str,
"COL_FILENAME", str,
"COL_LINE", int,
"COL_FUNCTION", str,
"COL_OBJECT", str,
"COL_MESSAGE", str,)
def __init__ (self, log_obj = None):
gtk.GenericTreeModel.__init__ (self)
##self.props.leak_references = False
self.__log_obj = log_obj
self.__line_regex = Data.default_log_line_regex ()
self.__line_match_order = (self.COL_TIME,
self.COL_PID,
self.COL_THREAD,
self.COL_LEVEL,
self.COL_CATEGORY,
self.COL_FILENAME,
self.COL_LINE,
self.COL_FUNCTION,
self.COL_OBJECT,
self.COL_MESSAGE,)
self.__line_offsets = []
self.__line_cache = {}
if log_obj:
self.set_log (log_obj)
def set_log (self, log_obj):
self.__line_cache.clear ()
self.__line_offsets = log_obj.line_cache.offsets
self.__fileobj = log_obj.fileobj
def __ensure_cached (self, line_index):
if line_index in self.__line_cache:
return
line_offset = self.__line_offsets[line_index]
if line_offset == 0:
self.__fileobj.seek (0)
line = self.__fileobj.readline ()
else:
# Seek a bit further backwards to verify that offset (still) points
# to the beginning of a line:
self.__fileobj.seek (line_offset - len (os.linesep))
line_start = (self.__fileobj.readline () == os.linesep)
if not line_start:
# FIXME: We should re-read the file instead!
raise ValueError ("file changed!")
line = self.__fileobj.readline ()
ts_len = 17
ts = Data.parse_time (line[:ts_len])
match = self.__line_regex.match (line[ts_len:-len (os.linesep)])
if match is None:
# FIXME?
groups = [ts, 0, 0, "?", "", "", 0, "", "", line[ts_len:-len (os.linesep)]]
else:
groups = [ts] + list (match.groups ())
# TODO: Figure out how much string interning can save here and how
# much run time speed it costs!
groups[1] = int (groups[1]) # pid
groups[2] = int (groups[2], 16) # thread pointer
groups[6] = int (groups[6]) # line
groups[8] = groups[8] or "" # object (optional)
groups = [x[1] for x in sorted (zip (self.__line_match_order,
groups))]
self.__line_cache[line_index] = groups
def on_get_flags (self):
flags = gtk.TREE_MODEL_LIST_ONLY | gtk.TREE_MODEL_ITERS_PERSIST
return flags
def on_get_n_columns (self):
return len (self.column_types)
def on_get_column_type (self, index):
return self.column_types[index]
def on_get_iter (self, path):
if len (path) > 1:
raise ValueError ("flat model")
line_index = path[0]
return line_index
def on_get_path (self, rowref):
return (rowref,)
def on_get_value (self, rowref, column):
if rowref >= len (self.__line_offsets):
return None
self.__ensure_cached (rowref)
return self.__line_cache[rowref][column]
def on_iter_next (self, rowref):
if rowref >= len (self.__line_offsets):
return None
else:
return rowref + 1
def on_iter_children (self, parent):
return self.on_iter_nth_child (parent, 0)
def on_iter_has_child (self, rowref):
return False
def on_iter_n_children (self, rowref):
return len (self.__line_offsets)
def on_iter_nth_child (self, parent, n):
if parent or n >= len (self.__line_offsets):
return None
else:
return n ## self.__line_offsets[n]
def on_iter_parent (self, child):
return None
def on_ref_node (self, rowref):
pass
def on_unref_node (self, rowref):
pass
class LogModel (gtk.ListStore):
__metaclass__ = Common.GUI.MetaModel
__gsignals__ = {"loading-progress" : (0, None, (float,),),
"loading-finished" : (0, None, (),),}
columns = ("COL_LEVEL", str,
"COL_PID", int,
"COL_THREAD", gobject.TYPE_UINT64,
"COL_TIME", gobject.TYPE_UINT64,
"COL_CATEGORY", str,
"COL_FILENAME", str,
"COL_LINE", int,
"COL_FUNCTION", str,
"COL_OBJECT", str,
#"COL_MESSAGE_OFFSET", object,)
"COL_MESSAGE", str,)
def __init__ (self):
gtk.ListStore.__init__ (self, *self.column_types)
self.match_line = default_log_line_regex ()
self.match_column_order = (self.COL_TIME,
self.COL_PID,
self.COL_THREAD,
self.COL_LEVEL,
self.COL_CATEGORY,
self.COL_FILENAME,
self.COL_LINE,
self.COL_FUNCTION,
self.COL_OBJECT,
#self.COL_MESSAGE_OFFSET,)
self.COL_MESSAGE,)
self.categories = Set ()
self.objects = {}
@staticmethod
def _filter_func (model, tree_iter):
return True
def filtered (self):
filtered_model = self.filter_new ()
filtered_model.set_visible_func (self._filter_func)
return filtered_model
def add_parsed (self, args):
category = args[4]
self.categories.add (category)
object_name = args[8]
if object_name:
self.objects[object_name] = None
#args[-1] = args[-1][:60] # FIXME: Message truncated
list_iter = self.insert (-1)
try:
self.set (list_iter, *(reduce (add,
zip (self.match_column_order,
args))))
except TypeError, exc:
for column_id, arg in zip (self.match_column_order, args):
try:
self.set (list_iter, column_id, arg)
except TypeError, exc:
# FIXME
print >> sys.stderr, str (exc)
print >> sys.stderr, "column: %i, arg: %r" % (column_id, arg)
def parse_line (self, line, offset):
if not line.endswith (os.linesep):
raise ValueError, "line does not end in a line separator"
match = self.match_line.match (line[:-1])
if match is None:
raise ValueError ("line does not match format")
groups = list (match.groups ())
groups[0] = parse_time (groups[0]) # time
groups[1] = int (groups[1]) # pid
groups[2] = int (groups[2], 16) # thread pointer
groups[6] = int (groups[6]) # line
groups[8] = groups[8] or "" # object (optional)
##groups[9] = offset + match.start (10)
return groups
def load_file (self, filename):
file_class = file
if filename.endswith (".gz") or filename.endswith (".gzip"):
import gzip
file_class = gzip.GzipFile
elif filename.endswith (".bz") or filename.endswith (".bz2"):
import bz2
file_class = bz2.BZ2File
self.fp = file_class (filename, "r")
self.size = os.path.getsize (filename)
gobject.idle_add (self.load_deferred ().next)
def load_deferred (self):
yield True
UPDATE = 1000
i = 0
while True:
offset = self.fp.tell ()
line = self.fp.readline ()
if not line:
break
if not line.strip ():
continue
line = strip_escape (line)
try:
self.add_parsed (self.parse_line (line, offset))
except ValueError, exc:
print >> sys.stderr, "Cannot parse %s (%s)" % (repr (line), exc)
i += 1
if i % UPDATE == 0:
self.emit ("loading-progress", min (1.0, float (self.fp.tell ()) / self.size))
yield True
if i % UPDATE != 0:
self.emit ("loading-progress", 1.0)
self.emit ("loading-finished")
yield False
# Sync with gst-inspector!
class Column (object):
"""A single list view column, managed by a ColumnManager instance."""
name = None
id = None
label_header = None
get_modify_func = None
get_sort_func = None
def __init__ (self):
view_column = gtk.TreeViewColumn (self.label_header)
view_column.props.reorderable = True
self.view_column = view_column
# FIXME: Merge with gst-inspector?
class SizedColumn (Column):
default_size = None
def compute_default_size (self, view, model):
return None
# Sync with gst-inspector?
class TextColumn (SizedColumn):
def __init__ (self):
Column.__init__ (self)
column = self.view_column
cell = gtk.CellRendererText ()
column.pack_start (cell)
if not self.get_modify_func:
column.add_attribute (cell, "text", self.id)
else:
modify_func = self.get_modify_func ()
id_ = self.id
def cell_data_func (column, cell, model, tree_iter):
cell.props.text = modify_func (model.get (tree_iter, id_)[0])
column.set_cell_data_func (cell, cell_data_func)
column.props.resizable = True
column.set_sort_column_id (self.id)
def compute_default_size (self, view, model):
values = self.get_values_for_size ()
if not values:
return SizedColumn.compute_default_size (self, view, model)
cell = self.view_column.get_cells ()[0]
if self.get_modify_func is not None:
format = self.get_modify_func ()
else:
def identity (x):
return x
format = identity
max_width = 0
for value in values:
cell.props.text = format (value)
max_width = max (max_width, cell.get_size (view, None)[2])
return max_width
def get_values_for_size (self):
return ()
class TimeColumn (TextColumn):
name = "time"
label_header = _("Time")
id = LazyLogModel.COL_TIME
@staticmethod
def get_modify_func ():
time_args = Data.time_args
def format_time (value):
# TODO: This is hard coded to omit hours as well as the last 3
# digits at the end, since current gst uses g_get_current_time,
# which has microsecond precision only.
return time_args (value)[2:-3]
return format_time
def get_values_for_size (self):
# TODO: Use more than just 0:00:00.000000000 to account for funny fonts
# maybe? Well, or use monospaced...
values = [0]
return values
class LevelColumn (TextColumn):
name = "level"
label_header = _("L")
id = LazyLogModel.COL_LEVEL
@staticmethod
def get_modify_func ():
def format_level (value):
if value is None:
# FIXME: Should never be None!
return ""
return value[0]
return format_level
def get_values_for_size (self):
values = ["LOG", "DEBUG", "INFO", "WARN", "ERROR"]
return values
class ThreadColumn (TextColumn):
name = "thread"
label_header = _("Thread")
id = LazyLogModel.COL_THREAD
@staticmethod
def get_modify_func ():
def format_thread (value):
return "0x%07x" % (value,)
return format_thread
def get_values_for_size (self):
# TODO: Same as for TimeColumn. There is no guarantee that aaaaaaaa is
# the widest string; use fixed font or come up with something better.
return [int ("aaaaaaaaa", 16)]
class CategoryColumn (TextColumn):
name = "category"
label_header = _("Category")
id = LazyLogModel.COL_CATEGORY
def get_values_for_size (self):
return ["GST_LONG_CATEGORY", "somelongelement"]
class FunctionColumn (TextColumn):
name = "function"
label_header = _("Function")
id = LazyLogModel.COL_FUNCTION
def get_values_for_size (self):
return ["gst_this_should_be_enough"]
## class FullCodeLocation (TextColumn):
## name = "code-location"
## label_header = _("Code Location")
## id = LazyLogModel.COL_FILENAME
## def get_values_for_size (self):
## return ["gstwhateverfile.c:1234"]
class ObjectColumn (TextColumn):
name = "object"
label_header = _("Object")
id = LazyLogModel.COL_OBJECT
def get_values_for_size (self):
return ["longobjectname00"]
class MessageColumn (TextColumn):
name = "message"
label_header = _("Message")
id = LazyLogModel.COL_MESSAGE
# Sync with gst-inspector!
class ColumnManager (Common.GUI.Manager):
column_classes = ()
@classmethod
def iter_item_classes (cls):
return iter (cls.column_classes)
def __init__ (self):
self.view = None
self.actions = None
self.__columns_changed_id = None
self.columns = []
self.column_order = list (self.column_classes)
self.action_group = gtk.ActionGroup ("ColumnActions")
def make_entry (col_class):
return ("show-%s-column" % (col_class.name,),
None,
col_class.label_header,
None,
None,
None,
True,)
entries = [make_entry (cls) for cls in self.column_classes]
self.action_group.add_toggle_actions (entries)
def iter_items (self):
return iter (self.columns)
def attach (self):
for col_class in self.column_classes:
action = self.get_toggle_action (col_class)
if action.props.active:
self._add_column (col_class ())
action.connect ("toggled",
self.__handle_show_column_action_toggled,
col_class.name)
self.__columns_changed_id = self.view.connect ("columns-changed",
self.__handle_view_columns_changed)
def detach (self):
if self.__columns_changed_id is not None:
self.view.disconnect (self.__columns_changed_id)
self.__columns_changed_id = None
def attach_sort (self):
sort_model = self.view.props.model
# Inform the sorted tree model of any custom sorting functions.
for col_class in self.column_classes:
if col_class.get_sort_func:
sort_func = col_class.get_sort_func ()
sort_model.set_sort_func (col_class.id, sort_func)
def enable_sort (self):
sort_model = self.view.props.model
if sort_model:
self.logger.debug ("activating sort")
sort_model.set_sort_column_id (*self.default_sort)
self.default_sort = None
else:
self.logger.debug ("not activating sort (no model set)")
def disable_sort (self):
self.logger.debug ("deactivating sort")
sort_model = self.view.props.model
self.default_sort = tree_sortable_get_sort_column_id (sort_model)
sort_model.set_sort_column_id (TREE_SORTABLE_UNSORTED_COLUMN_ID,
gtk.SORT_ASCENDING)
def get_toggle_action (self, column_class):
action_name = "show-%s-column" % (column_class.name,)
return self.action_group.get_action (action_name)
def get_initial_column_order (self):
return tuple (self.column_classes)
def _add_column (self, column):
name = column.name
pos = self.__get_column_insert_position (column)
if self.view.props.fixed_height_mode:
column.view_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
self.columns.insert (pos, column)
self.view.insert_column (column.view_column, pos)
def _remove_column (self, column):
self.columns.remove (column)
self.view.remove_column (column.view_column)
def __get_column_insert_position (self, column):
col_class = self.find_item_class (name = column.name)
pos = self.column_order.index (col_class)
before = self.column_order[:pos]
shown_names = [col.name for col in self.columns]
for col_class in before:
if not col_class.name in shown_names:
pos -= 1
return pos
def __iter_next_hidden (self, column_class):
pos = self.column_order.index (column_class)
rest = self.column_order[pos + 1:]
for next_class in rest:
try:
self.find_item (name = next_class.name)
except KeyError:
# No instance -- the column is hidden.
yield next_class
else:
break
def __handle_show_column_action_toggled (self, toggle_action, name):
if toggle_action.props.active:
try:
# This should fail.
column = self.find_item (name = name)
except KeyError:
col_class = self.find_item_class (name = name)
self._add_column (col_class ())
else:
# Out of sync for some reason.
return
else:
try:
column = self.find_item (name = name)
except KeyError:
# Out of sync for some reason.
return
else:
self._remove_column (column)
def __handle_view_columns_changed (self, element_view):
view_columns = element_view.get_columns ()
new_visible = [self.find_item (view_column = column)
for column in view_columns]
# We only care about reordering here.
if len (new_visible) != len (self.columns):
return
if new_visible != self.columns:
new_order = []
for column in new_visible:
col_class = self.find_item_class (name = column.name)
new_order.append (col_class)
new_order.extend (self.__iter_next_hidden (col_class))
names = (column.name for column in new_visible)
self.logger.debug ("visible columns reordered: %s",
", ".join (names))
self.columns[:] = new_visible
self.column_order[:] = new_order
class ViewColumnManager (ColumnManager):
column_classes = (TimeColumn, LevelColumn, ThreadColumn, CategoryColumn,
FunctionColumn, ObjectColumn, MessageColumn,)
def __init__ (self, *a, **kw):
ColumnManager.__init__ (self, *a, **kw)
self.logger = logging.getLogger ("ui.columns")
def attach (self, view):
self.view = view
view.connect ("notify::model", self.__handle_notify_model)
ColumnManager.attach (self)
def size_column (self, column, view, model):
if column.default_size is None:
default_size = column.compute_default_size (view, model)
else:
default_size = column.default_size
# FIXME: Abstract away fixed size setting in Column class!
if default_size is None:
# Dummy fallback:
column.view_column.props.fixed_width = 50
self.logger.warning ("%s column does not implement default size", column.name)
else:
column.view_column.props.fixed_width = default_size
def _add_column (self, column):
result = ColumnManager._add_column (self, column)
model = self.view.props.model
self.size_column (column, self.view, model)
return result
def _remove_column (self, column):
column.default_size = column.view_column.props.fixed_width
return ColumnManager._remove_column (self, column)
def __handle_notify_model (self, view, gparam):
model = self.view.props.model
for column in self.iter_items ():
self.size_column (column, view, model)
class Window (object):
def __init__ (self, app):
self.logger = logging.getLogger ("ui.window")
self.app = app
self.sentinels = []
self.progress_bar = None
self.update_progress_id = None
self.window_state = Common.GUI.WindowState ()
self.column_manager = ViewColumnManager ()
self.actions = Common.GUI.Actions ()
group = gtk.ActionGroup ("MenuActions")
group.add_actions ([("FileMenuAction", None, _("_File")),
("ViewMenuAction", None, _("_View")),
("ViewColumnsMenuAction", None, _("View columns")),
("HelpMenuAction", None, _("_Help"))])
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"),
("close-window", gtk.STOCK_CLOSE, _("Close _Window"), "<Ctrl>W"),
("show-about", gtk.STOCK_ABOUT, None)])
## group.add_toggle_actions ([("show-line-density", None, _("Line _Density"), "<Ctrl>D")])
self.actions.add_group (group)
group = gtk.ActionGroup ("RowActions")
group.add_actions ([("edit-copy-line", gtk.STOCK_COPY, _("Copy line"), "<Ctrl>C"),
("edit-copy-message", gtk.STOCK_COPY, _("Copy message"))])
self.actions.add_group (group)
self.actions.add_group (self.column_manager.action_group)
self.file = None
self.log_model = LazyLogModel ()
## self.log_model.connect ("loading-progress", self.handle_load_progress)
## self.log_model.connect ("loading-finished", self.handle_load_finished)
glade_filename = os.path.join (Main.Paths.data_dir, "gst-debug-viewer.glade")
self.widget_factory = Common.GUI.WidgetFactory (glade_filename)
self.widgets = self.widget_factory.make ("main_window")
ui_filename = os.path.join (Main.Paths.data_dir,
"gst-debug-viewer.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.view_popup = ui.get_widget ("/ui/menubar/ViewMenu").get_submenu ()
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.props.fixed_height_mode = True
#self.log_view.props.model = self.log_model.filtered ()
self.log_view.connect ("button-press-event", self.handle_log_view_button_press_event)
self.attach ()
self.column_manager.attach (self.log_view)
## cell = gtk.CellRendererText ()
## column = gtk.TreeViewColumn ("Level", cell,
## text = self.log_model.COL_LEVEL)
## column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
## column.props.fixed_width = 80 # FIXME
## self.log_view.append_column (column)
## cell = gtk.CellRendererText ()
## cell.props.family = "monospace"
## cell.props.family_set = True
## column = gtk.TreeViewColumn ("Time", cell)
## #text = self.log_model.COL_TIME)
## column.set_cell_data_func (cell, self._timestamp_cell_data_func)
## column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
## column.props.fixed_width = 180 # FIXME
## self.log_view.append_column (column)
## cell = gtk.CellRendererText ()
## column = gtk.TreeViewColumn ("Category", cell,
## text = self.log_model.COL_CATEGORY)
## column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
## column.props.fixed_width = 150 # FIXME
## self.log_view.append_column (column)
## cell = gtk.CellRendererText ()
## column = gtk.TreeViewColumn ("Function", cell,
## text = self.log_model.COL_FUNCTION)
## column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
## column.props.fixed_width = 180 # FIXME
## self.log_view.append_column (column)
## cell = gtk.CellRendererText ()
## column = gtk.TreeViewColumn ("Object", cell,
## text = self.log_model.COL_OBJECT)
## column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
## column.props.fixed_width = 150 # FIXME
## self.log_view.append_column (column)
## cell = gtk.CellRendererText ()
## column = gtk.TreeViewColumn ("Message", cell, text = self.log_model.COL_MESSAGE)
## ##column.set_cell_data_func (cell, self._message_cell_data_func)
## column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
## self.log_view.append_column (column)
def get_top_attach_point (self):
return self.widgets.vbox_main
def attach (self):
self.window_state.attach (window = self.gtk_window, state = self.app.state)
self.clipboard = gtk.Clipboard (self.gtk_window.get_display (),
gtk.gdk.SELECTION_CLIPBOARD)
for action_name in ("new-window", "open-file", "close-window",
"edit-copy-line", "edit-copy-message",
"show-about",):
name = action_name.replace ("-", "_")
action = getattr (self.actions, name)
handler = getattr (self, "handle_%s_action_activate" % (name,))
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 ()
feature.attach (self)
self.features.append (feature)
def detach (self):
self.window_state.detach ()
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")
return self.log_model.get (tree_iter, *LazyLogModel.column_ids)
def close (self, *a, **kw):
self.detach ()
self.gtk_window.hide ()
self.app.close_window (self)
def handle_window_delete_event (self, window, event):
self.actions.close_window.activate ()
def handle_new_window_action_activate (self, action):
pass
def handle_open_file_action_activate (self, action):
dialog = gtk.FileChooserDialog (None, self.gtk_window,
gtk.FILE_CHOOSER_ACTION_OPEN,
(gtk.STOCK_CANCEL, 1,
gtk.STOCK_OPEN, 0,))
response = dialog.run ()
dialog.hide ()
if response == 0:
self.set_log_file (dialog.get_filename ())
dialog.destroy ()
def handle_close_window_action_activate (self, action):
self.close ()
def handle_edit_copy_line_action_activate (self, action):
self.logger.warning ("FIXME")
return
col_id = self.log_model.COL_
self.clipboard.set_text (self.get_active_line ()[col_id])
def handle_edit_copy_message_action_activate (self, action):
col_id = self.log_model.COL_MESSAGE
self.clipboard.set_text (self.get_active_line ()[col_id])
def handle_show_about_action_activate (self, action):
from GstDebugViewer import version
dialog = self.widget_factory.make_one ("about_dialog")
dialog.props.version = version
dialog.run ()
dialog.destroy ()
@staticmethod
def _timestamp_cell_data_func (column, renderer, model, tree_iter):
ts = model.get (tree_iter, LogModel.COL_TIME)[0]
renderer.props.text = Data.time_args (ts)
def _message_cell_data_func (self, column, renderer, model, tree_iter):
offset = model.get (tree_iter, LogModel.COL_MESSAGE_OFFSET)[0]
self.log_file.seek (offset)
renderer.props.text = strip_escape (self.log_file.readline ().strip ())
def set_log_file (self, filename):
self.logger.debug ("setting log file %r", filename)
dispatcher = Common.Data.GSourceDispatcher ()
self.log_file = Data.LogFile (filename, dispatcher)
self.log_file.consumers.append (self)
self.log_file.start_loading ()
def handle_log_view_button_press_event (self, view, event):
if event.button != 3:
return False
self.view_popup.popup (None, None, None, event.button, event.get_time ())
return True
def handle_load_started (self):
self.logger.debug ("load has started")
widgets = self.widget_factory.make ("progress_dialog")
dialog = widgets.progress_dialog
self.progress_dialog = dialog
self.progress_bar = widgets.progress_bar
dialog.set_transient_for (self.gtk_window)
dialog.show ()
self.update_progress_id = gobject.timeout_add (250, self.update_load_progress)
def update_load_progress (self):
if not self.progress_bar:
self.logger.debug ("progress window is gone, removing progress update timeout")
self.update_progress_id = None
return False
progress = self.log_file.get_load_progress ()
self.logger.debug ("update progress to %i%%", progress * 100)
self.progress_bar.props.fraction = progress
return True
def handle_load_finished (self):
self.logger.debug ("load has finshed")
if self.update_progress_id is not None:
gobject.source_remove (self.update_progress_id)
self.update_progress_id = None
self.progress_dialog.hide ()
self.progress_dialog.destroy ()
self.progress_dialog = None
self.progress_bar = None
## parent_menu = self.glade_tree.get_widget ("view_categories")
## sub_menu = gtk.Menu ()
## sub_menu.show ()
## parent_menu.set_submenu (sub_menu)
## for category in sorted (model.categories):
## item = gtk.CheckMenuItem (category, use_underline = False)
## item.props.active = True
## item.connect ("toggled", self.handle_view_category_toggled)
## item.show ()
## sub_menu.append (item)
self.log_model.set_log (self.log_file)
for sentinel in self.sentinels:
sentinel ()
def idle_set ():
self.log_view.props.model = self.log_model #model.filtered ()
return False
gobject.idle_add (idle_set)
class AppState (Common.GUI.AppState):
geometry = Common.GUI.StateInt4 ("window-geometry")
maximized = Common.GUI.StateBool ("window-maximized")
class App (object):
def __init__ (self):
self.load_plugins ()
self.attach ()
def load_plugins (self):
from GstDebugViewer import Plugins
self.plugins = list (Plugins.load ([os.path.dirname (Plugins.__file__)]))
def iter_plugin_features (self):
for plugin in self.plugins:
for feature in plugin.features:
yield feature
def attach (self):
state_filename = os.path.join (utils.XDG.CONFIG_HOME, "gst-debug-viewer", "state")
self.state = AppState (state_filename)
self.windows = [Window (self)]
def detach (self):
# TODO: If we take over deferred saving from the inspector, specify now
# = True here!
self.state.save ()
def run (self):
try:
Common.Main.MainLoopWrapper (gtk.main, gtk.main_quit).run ()
except:
raise
else:
self.detach ()
def close_window (self, window):
# For some reason, going down takes some time for large files. Let's
# block until the window is hidden:
gobject.idle_add (gtk.main_quit)
gtk.main ()
gtk.main_quit ()
import time
class TestParsingPerformance (object):
def __init__ (self, filename):
self.main_loop = gobject.MainLoop ()
self.log_file = Data.LogFile (filename, DefaultDispatcher ())
self.log_file.consumers.append (self)
def start (self):
self.log_file.start_loading ()
def handle_load_started (self):
self.start_time = time.time ()
def handle_load_finished (self):
diff = time.time () - self.start_time
print "line cache built in %0.1f ms" % (diff * 1000.,)
self.start_time = time.time ()
model = LazyLogModel (self.log_file)
for row in model:
pass
diff = time.time () - self.start_time
print "data parsed in %0.1f ms" % (diff * 1000.,)
def main ():
if len (sys.argv) > 1 and sys.argv[1] == "--benchmark":
test = TestParsingPerformance (sys.argv[2])
test.start ()
return
app = App ()
window = app.windows[0]
if len (sys.argv) > 1:
window.set_log_file (sys.argv[-1])
app.run ()
if __name__ == "__main__":
main ()