gstreamer/debug-viewer/GstDebugViewer/GUI/columns.py

745 lines
20 KiB
Python
Raw Normal View History

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."""
import logging
from gi.repository import Gtk, GLib
2009-08-06 23:54:10 +00:00
from GstDebugViewer import Common, Data
from GstDebugViewer.GUI.colors import LevelColorThemeTango
from GstDebugViewer.GUI.models import LazyLogModel, LogModelBase
2018-04-14 15:04:22 +00:00
def _(s):
return s
2009-08-06 23:54:10 +00:00
# Sync with gst-inspector!
2009-08-06 23:54:10 +00:00
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_data_func = None
get_sort_func = None
def __init__(self):
2009-08-06 23:54:10 +00:00
view_column = Gtk.TreeViewColumn(self.label_header)
2009-08-06 23:54:10 +00:00
view_column.props.reorderable = True
self.view_column = view_column
2009-08-06 23:54:10 +00:00
class SizedColumn (Column):
default_size = None
def compute_default_size(self):
2009-08-06 23:54:10 +00:00
return None
# Sync with gst-inspector?
2009-08-06 23:54:10 +00:00
class TextColumn (SizedColumn):
font_family = None
def __init__(self):
2009-08-06 23:54:10 +00:00
Column.__init__(self)
2009-08-06 23:54:10 +00:00
column = self.view_column
cell = Gtk.CellRendererText()
column.pack_start(cell, True)
2009-08-06 23:54:10 +00:00
cell.props.yalign = 0.
cell.props.ypad = 0
2009-08-06 23:54:10 +00:00
if self.font_family:
cell.props.family = self.font_family
cell.props.family_set = True
if self.get_data_func:
data_func = self.get_data_func()
2009-08-06 23:54:10 +00:00
assert data_func
id_ = self.id
if id_ is not None:
def cell_data_func(column, cell, model, tree_iter, user_data):
data_func(cell.props, model.get_value(tree_iter, id_))
2009-08-06 23:54:10 +00:00
else:
cell_data_func = data_func
column.set_cell_data_func(cell, cell_data_func)
2009-08-06 23:54:10 +00:00
elif not self.get_modify_func:
column.add_attribute(cell, "text", self.id)
2009-08-06 23:54:10 +00:00
else:
self.update_modify_func(column, cell)
2009-08-06 23:54:10 +00:00
column.props.resizable = True
def update_modify_func(self, column, cell):
modify_func = self.get_modify_func()
id_ = self.id
def cell_data_func(column, cell, model, tree_iter, user_data):
cell.props.text = modify_func(model.get_value(tree_iter, id_))
column.set_cell_data_func(cell, cell_data_func)
def compute_default_size(self):
2009-08-06 23:54:10 +00:00
values = self.get_values_for_size()
2009-08-06 23:54:10 +00:00
if not values:
return SizedColumn.compute_default_size(self)
2009-08-06 23:54:10 +00:00
cell = self.view_column.get_cells()[0]
2009-08-06 23:54:10 +00:00
if self.get_modify_func is not None:
format = self.get_modify_func()
2009-08-06 23:54:10 +00:00
else:
def identity(x):
2009-08-06 23:54:10 +00:00
return x
format = identity
max_width = 0
for value in values:
cell.props.text = format(value)
x, y, w, h = self.view_column.cell_get_size()
max_width = max(max_width, w)
2009-08-06 23:54:10 +00:00
return max_width
def get_values_for_size(self):
2009-08-06 23:54:10 +00:00
return ()
2009-08-06 23:54:10 +00:00
class TimeColumn (TextColumn):
name = "time"
label_header = _("Time")
id = LazyLogModel.COL_TIME
font_family = "monospace"
def __init__(self, *a, **kw):
self.base_time = 0
2009-08-06 23:54:10 +00:00
TextColumn.__init__(self, *a, **kw)
def get_modify_func(self):
if self.base_time:
time_diff_args = Data.time_diff_args
base_time = self.base_time
def format_time(value):
# TODO: Hard coded to omit trailing zeroes, see below.
return time_diff_args(value - base_time)[:-3]
else:
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]
2009-08-06 23:54:10 +00:00
return format_time
def get_values_for_size(self):
2009-08-06 23:54:10 +00:00
values = [0]
return values
def set_base_time(self, base_time):
self.base_time = base_time
column = self.view_column
cell = column.get_cells()[0]
self.update_modify_func(column, cell)
2009-08-06 23:54:10 +00:00
class LevelColumn (TextColumn):
name = "level"
label_header = _("L")
id = LazyLogModel.COL_LEVEL
def __init__(self):
2009-08-06 23:54:10 +00:00
TextColumn.__init__(self)
2009-08-06 23:54:10 +00:00
cell = self.view_column.get_cells()[0]
2009-08-06 23:54:10 +00:00
cell.props.xalign = .5
@staticmethod
def get_modify_func():
2009-08-06 23:54:10 +00:00
def format_level(value):
2009-08-06 23:54:10 +00:00
return value.name[0]
return format_level
@staticmethod
def get_data_func():
theme = LevelColorThemeTango()
colors = dict((level, tuple((c.gdk_color()
for c in theme.colors[level])),)
for level in Data.debug_levels
if level != Data.debug_level_none)
def level_data_func(cell_props, level):
2009-08-06 23:54:10 +00:00
cell_props.text = level.name[0]
if level in colors:
cell_colors = colors[level]
else:
cell_colors = (None, None, None,)
cell_props.foreground_gdk = cell_colors[0]
cell_props.background_gdk = cell_colors[1]
2009-08-06 23:54:10 +00:00
return level_data_func
def get_values_for_size(self):
2009-08-06 23:54:10 +00:00
values = [Data.debug_level_log, Data.debug_level_debug,
Data.debug_level_info, Data.debug_level_warning,
Data.debug_level_error]
return values
2009-08-06 23:54:10 +00:00
class PidColumn (TextColumn):
name = "pid"
label_header = _("PID")
id = LazyLogModel.COL_PID
font_family = "monospace"
@staticmethod
def get_modify_func():
2009-08-06 23:54:10 +00:00
return str
def get_values_for_size(self):
2009-08-06 23:54:10 +00:00
return ["999999"]
2009-08-06 23:54:10 +00:00
class ThreadColumn (TextColumn):
name = "thread"
label_header = _("Thread")
id = LazyLogModel.COL_THREAD
font_family = "monospace"
@staticmethod
def get_modify_func():
2009-08-06 23:54:10 +00:00
def format_thread(value):
2009-08-06 23:54:10 +00:00
return "0x%07x" % (value,)
return format_thread
def get_values_for_size(self):
return [int("ffffff", 16)]
2009-08-06 23:54:10 +00:00
class CategoryColumn (TextColumn):
name = "category"
label_header = _("Category")
id = LazyLogModel.COL_CATEGORY
def get_values_for_size(self):
2009-08-06 23:54:10 +00:00
return ["GST_LONG_CATEGORY", "somelongelement"]
2009-08-06 23:54:10 +00:00
class CodeColumn (TextColumn):
name = "code"
label_header = _("Code")
id = None
@staticmethod
def get_data_func():
2009-08-06 23:54:10 +00:00
filename_id = LogModelBase.COL_FILENAME
line_number_id = LogModelBase.COL_LINE_NUMBER
def filename_data_func(column, cell, model, tree_iter, user_data):
args = model.get(tree_iter, filename_id, line_number_id)
2009-08-06 23:54:10 +00:00
cell.props.text = "%s:%i" % args
return filename_data_func
def get_values_for_size(self):
2009-08-06 23:54:10 +00:00
return ["gstsomefilename.c:1234"]
2009-08-06 23:54:10 +00:00
class FunctionColumn (TextColumn):
name = "function"
label_header = _("Function")
id = LazyLogModel.COL_FUNCTION
def get_values_for_size(self):
2009-08-06 23:54:10 +00:00
return ["gst_this_should_be_enough"]
2009-08-06 23:54:10 +00:00
class ObjectColumn (TextColumn):
name = "object"
label_header = _("Object")
id = LazyLogModel.COL_OBJECT
def get_values_for_size(self):
2009-08-06 23:54:10 +00:00
return ["longobjectname00"]
2009-08-06 23:54:10 +00:00
class MessageColumn (TextColumn):
name = "message"
label_header = _("Message")
id = None
2009-08-06 23:54:10 +00:00
def __init__(self, *a, **kw):
2009-08-06 23:54:10 +00:00
self.highlighters = {}
TextColumn.__init__(self, *a, **kw)
2009-08-06 23:54:10 +00:00
def get_data_func(self):
2009-08-06 23:54:10 +00:00
highlighters = self.highlighters
id_ = LazyLogModel.COL_MESSAGE
2009-08-06 23:54:10 +00:00
def message_data_func(column, cell, model, tree_iter, user_data):
2009-08-06 23:54:10 +00:00
msg = model.get_value(tree_iter, id_).decode("utf8")
2009-08-06 23:54:10 +00:00
if not highlighters:
cell.props.text = msg
return
if len(highlighters) > 1:
raise NotImplementedError("FIXME: Support more than one...")
highlighter = list(highlighters.values())[0]
row = model[tree_iter]
ranges = highlighter(row)
if not ranges:
cell.props.text = msg
else:
tags = []
prev_end = 0
end = None
for start, end in ranges:
if prev_end < start:
tags.append(
GLib.markup_escape_text(msg[prev_end:start]))
msg_escape = GLib.markup_escape_text(msg[start:end])
tags.append("<span foreground=\'#FFFFFF\'"
" background=\'#0000FF\'>%s</span>" % (msg_escape,))
prev_end = end
if end is not None:
tags.append(GLib.markup_escape_text(msg[end:]))
cell.props.markup = "".join(tags)
2009-08-06 23:54:10 +00:00
return message_data_func
def get_values_for_size(self):
2009-08-06 23:54:10 +00:00
values = ["Just some good minimum size"]
return values
2009-08-06 23:54:10 +00:00
class ColumnManager (Common.GUI.Manager):
column_classes = ()
@classmethod
def iter_item_classes(cls):
2009-08-06 23:54:10 +00:00
return iter(cls.column_classes)
2009-08-06 23:54:10 +00:00
def __init__(self):
2009-08-06 23:54:10 +00:00
self.view = None
self.actions = None
self.zoom = 1.0
2009-08-06 23:54:10 +00:00
self.__columns_changed_id = None
self.columns = []
self.column_order = list(self.column_classes)
2009-08-06 23:54:10 +00:00
self.action_group = Gtk.ActionGroup("ColumnActions")
2009-08-06 23:54:10 +00:00
def make_entry(col_class):
2009-08-06 23:54:10 +00:00
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)
2009-08-06 23:54:10 +00:00
def iter_items(self):
2009-08-06 23:54:10 +00:00
return iter(self.columns)
2009-08-06 23:54:10 +00:00
def attach(self):
2009-08-06 23:54:10 +00:00
for col_class in self.column_classes:
action = self.get_toggle_action(col_class)
2009-08-06 23:54:10 +00:00
if action.props.active:
self._add_column(col_class())
action.connect("toggled",
self.__handle_show_column_action_toggled,
col_class.name)
2009-08-06 23:54:10 +00:00
self.__columns_changed_id = self.view.connect("columns-changed",
self.__handle_view_columns_changed)
2009-08-06 23:54:10 +00:00
def detach(self):
2009-08-06 23:54:10 +00:00
if self.__columns_changed_id is not None:
self.view.disconnect(self.__columns_changed_id)
2009-08-06 23:54:10 +00:00
self.__columns_changed_id = None
def attach_sort(self):
2009-08-06 23:54:10 +00:00
sort_model = self.view.get_model()
2009-08-06 23:54:10 +00:00
# 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)
2009-08-06 23:54:10 +00:00
def enable_sort(self):
2009-08-06 23:54:10 +00:00
sort_model = self.view.get_model()
2009-08-06 23:54:10 +00:00
if sort_model:
self.logger.debug("activating sort")
sort_model.set_sort_column_id(*self.default_sort)
2009-08-06 23:54:10 +00:00
self.default_sort = None
else:
self.logger.debug("not activating sort (no model set)")
2009-08-06 23:54:10 +00:00
def disable_sort(self):
2009-08-06 23:54:10 +00:00
self.logger.debug("deactivating sort")
2009-08-06 23:54:10 +00:00
sort_model = self.view.get_model()
2009-08-06 23:54:10 +00:00
self.default_sort = tree_sortable_get_sort_column_id(sort_model)
2009-08-06 23:54:10 +00:00
sort_model.set_sort_column_id(TREE_SORTABLE_UNSORTED_COLUMN_ID,
Gtk.SortType.ASCENDING)
2009-08-06 23:54:10 +00:00
def set_zoom(self, scale):
for column in self.columns:
cell = column.view_column.get_cells()[0]
cell.props.scale = scale
column.view_column.queue_resize()
self.zoom = scale
def set_base_time(self, base_time):
try:
time_column = self.find_item(name=TimeColumn.name)
except KeyError:
return
time_column.set_base_time(base_time)
self.size_column(time_column)
def get_toggle_action(self, column_class):
2009-08-06 23:54:10 +00:00
action_name = "show-%s-column" % (column_class.name,)
return self.action_group.get_action(action_name)
2009-08-06 23:54:10 +00:00
def get_initial_column_order(self):
2009-08-06 23:54:10 +00:00
return tuple(self.column_classes)
2009-08-06 23:54:10 +00:00
def _add_column(self, column):
2009-08-06 23:54:10 +00:00
name = column.name
pos = self.__get_column_insert_position(column)
2009-08-06 23:54:10 +00:00
if self.view.props.fixed_height_mode:
column.view_column.props.sizing = Gtk.TreeViewColumnSizing.FIXED
cell = column.view_column.get_cells()[0]
cell.props.scale = self.zoom
self.columns.insert(pos, column)
self.view.insert_column(column.view_column, pos)
2009-08-06 23:54:10 +00:00
def _remove_column(self, column):
2009-08-06 23:54:10 +00:00
self.columns.remove(column)
self.view.remove_column(column.view_column)
2009-08-06 23:54:10 +00:00
def __get_column_insert_position(self, column):
2009-08-06 23:54:10 +00:00
col_class = self.find_item_class(name=column.name)
pos = self.column_order.index(col_class)
2009-08-06 23:54:10 +00:00
before = self.column_order[:pos]
shown_names = [col.name for col in self.columns]
for col_class in before:
2018-04-14 15:04:22 +00:00
if col_class.name not in shown_names:
2009-08-06 23:54:10 +00:00
pos -= 1
return pos
def __iter_next_hidden(self, column_class):
2009-08-06 23:54:10 +00:00
pos = self.column_order.index(column_class)
2009-08-06 23:54:10 +00:00
rest = self.column_order[pos + 1:]
for next_class in rest:
try:
self.find_item(name=next_class.name)
2009-08-06 23:54:10 +00:00
except KeyError:
# No instance -- the column is hidden.
yield next_class
else:
break
def __handle_show_column_action_toggled(self, toggle_action, name):
2009-08-06 23:54:10 +00:00
if toggle_action.props.active:
try:
# This should fail.
column = self.find_item(name=name)
2009-08-06 23:54:10 +00:00
except KeyError:
col_class = self.find_item_class(name=name)
self._add_column(col_class())
2009-08-06 23:54:10 +00:00
else:
# Out of sync for some reason.
return
else:
try:
column = self.find_item(name=name)
2009-08-06 23:54:10 +00:00
except KeyError:
# Out of sync for some reason.
return
else:
self._remove_column(column)
2009-08-06 23:54:10 +00:00
def __handle_view_columns_changed(self, element_view):
2009-08-06 23:54:10 +00:00
view_columns = element_view.get_columns()
new_visible = [self.find_item(view_column=column)
2009-08-06 23:54:10 +00:00
for column in view_columns]
# We only care about reordering here.
if len(new_visible) != len(self.columns):
2009-08-06 23:54:10 +00:00
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))
2009-08-06 23:54:10 +00:00
names = (column.name for column in new_visible)
self.logger.debug("visible columns reordered: %s",
", ".join(names))
2009-08-06 23:54:10 +00:00
self.columns[:] = new_visible
self.column_order[:] = new_order
2009-08-06 23:54:10 +00:00
class ViewColumnManager (ColumnManager):
column_classes = (
TimeColumn, LevelColumn, PidColumn, ThreadColumn, CategoryColumn,
CodeColumn, FunctionColumn, ObjectColumn, MessageColumn,)
2009-08-06 23:54:10 +00:00
default_column_classes = (
TimeColumn, LevelColumn, CategoryColumn, CodeColumn,
FunctionColumn, ObjectColumn, MessageColumn,)
2012-09-26 00:00:10 +00:00
def __init__(self, state):
2009-08-06 23:54:10 +00:00
ColumnManager.__init__(self)
2009-08-06 23:54:10 +00:00
self.logger = logging.getLogger("ui.columns")
2009-08-06 23:54:10 +00:00
self.state = state
def attach(self, view):
2009-08-06 23:54:10 +00:00
self.view = view
view.connect("notify::model", self.__handle_notify_model)
2009-08-06 23:54:10 +00:00
order = self.state.column_order
if len(order) == len(self.column_classes):
2009-08-06 23:54:10 +00:00
self.column_order[:] = order
visible = self.state.columns_visible
if not visible:
2012-09-26 00:00:10 +00:00
visible = self.default_column_classes
2009-08-06 23:54:10 +00:00
for col_class in self.column_classes:
action = self.get_toggle_action(col_class)
2009-08-06 23:54:10 +00:00
action.props.active = (col_class in visible)
ColumnManager.attach(self)
2009-08-06 23:54:10 +00:00
self.columns_sized = False
def detach(self):
2009-08-06 23:54:10 +00:00
self.state.column_order = self.column_order
self.state.columns_visible = self.columns
return ColumnManager.detach(self)
2009-08-06 23:54:10 +00:00
def set_zoom(self, scale):
ColumnManager.set_zoom(self, scale)
if self.view is None:
return
# Timestamp and log level columns are pretty much fixed size, so resize
# them back to default on zoom change:
names = (TimeColumn.name,
LevelColumn.name,
PidColumn.name,
ThreadColumn.name)
for column in self.columns:
if column.name in names:
self.size_column(column)
def size_column(self, column):
2009-08-06 23:54:10 +00:00
if column.default_size is None:
default_size = column.compute_default_size()
2009-08-06 23:54:10 +00:00
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)
2009-08-06 23:54:10 +00:00
else:
column.view_column.props.fixed_width = default_size
def _add_column(self, column):
2009-08-06 23:54:10 +00:00
result = ColumnManager._add_column(self, column)
self.size_column(column)
2009-08-06 23:54:10 +00:00
return result
def _remove_column(self, column):
2009-08-06 23:54:10 +00:00
column.default_size = column.view_column.props.fixed_width
return ColumnManager._remove_column(self, column)
2009-08-06 23:54:10 +00:00
def __handle_notify_model(self, view, gparam):
2009-08-06 23:54:10 +00:00
if self.columns_sized:
# Already sized.
return
model = self.view.get_model()
2009-08-06 23:54:10 +00:00
if model is None:
return
self.logger.debug("model changed, sizing columns")
for column in self.iter_items():
self.size_column(column)
2009-08-06 23:54:10 +00:00
self.columns_sized = True
2009-08-06 23:54:10 +00:00
class WrappingMessageColumn (MessageColumn):
def wrap_to_width(self, width):
2009-08-06 23:54:10 +00:00
col = self.view_column
col.props.max_width = width
col.get_cells()[0].props.wrap_width = width
col.queue_resize()
2009-08-06 23:54:10 +00:00
class LineViewColumnManager (ColumnManager):
column_classes = (TimeColumn, WrappingMessageColumn,)
def __init__(self):
2009-08-06 23:54:10 +00:00
ColumnManager.__init__(self)
2009-08-06 23:54:10 +00:00
def attach(self, window):
2009-08-06 23:54:10 +00:00
self.__size_update = None
self.view = window.widgets.line_view
self.view.set_size_request(0, 0)
self.view.connect_after("size-allocate", self.__handle_size_allocate)
ColumnManager.attach(self)
2009-08-06 23:54:10 +00:00
def __update_sizes(self):
2009-08-06 23:54:10 +00:00
view_width = self.view.get_allocation().width
2009-08-06 23:54:10 +00:00
if view_width == self.__size_update:
# Prevent endless recursion.
return
self.__size_update = view_width
col = self.find_item(name="time")
2009-08-06 23:54:10 +00:00
other_width = col.view_column.props.width
try:
col = self.find_item(name="message")
2009-08-06 23:54:10 +00:00
except KeyError:
return
width = view_width - other_width
col.wrap_to_width(width)
2009-08-06 23:54:10 +00:00
def __handle_size_allocate(self, self_, allocation):
2009-08-06 23:54:10 +00:00
self.__update_sizes()