diff --git a/debug-viewer/GstDebugViewer/GUI.py b/debug-viewer/GstDebugViewer/GUI.py
index 35ea0234e9..e0f0d37023 100644
--- a/debug-viewer/GstDebugViewer/GUI.py
+++ b/debug-viewer/GstDebugViewer/GUI.py
@@ -17,2262 +17,3 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see .
-"""GStreamer Debug Viewer GUI module."""
-
-__author__ = u"René Stadler "
-__version__ = "0.1"
-
-def _ (s):
- return s
-
-import sys
-import os.path
-from bisect import bisect_right, bisect_left
-import logging
-
-import pygtk
-pygtk.require ("2.0")
-
-import gobject
-import gtk
-
-from GstDebugViewer import Common, Data, Main
-
-class Color (object):
-
- def __init__ (self, hex_24):
-
- if hex_24.startswith ("#"):
- s = hex_24[1:]
- else:
- s = hex_24
-
- self._fields = tuple ((int (hs, 16) for hs in (s[:2], s[2:4], s[4:],)))
-
- def gdk_color (self):
-
- return gtk.gdk.color_parse (self.hex_string ())
-
- def hex_string (self):
-
- return "#%02x%02x%02x" % self._fields
-
- def float_tuple (self):
-
- return tuple ((float (x) / 255 for x in self._fields))
-
- def byte_tuple (self):
-
- return self._fields
-
- def short_tuple (self):
-
- return tuple ((x << 8 for x in self._fields))
-
-class ColorPalette (object):
-
- @classmethod
- def get (cls):
-
- try:
- return cls._instance
- except AttributeError:
- cls._instance = cls ()
- return cls._instance
-
-class TangoPalette (ColorPalette):
-
- def __init__ (self):
-
- for name, r, g, b in [("black", 0, 0, 0,),
- ("white", 255, 255, 255,),
- ("butter1", 252, 233, 79),
- ("butter2", 237, 212, 0),
- ("butter3", 196, 160, 0),
- ("chameleon1", 138, 226, 52),
- ("chameleon2", 115, 210, 22),
- ("chameleon3", 78, 154, 6),
- ("orange1", 252, 175, 62),
- ("orange2", 245, 121, 0),
- ("orange3", 206, 92, 0),
- ("skyblue1", 114, 159, 207),
- ("skyblue2", 52, 101, 164),
- ("skyblue3", 32, 74, 135),
- ("plum1", 173, 127, 168),
- ("plum2", 117, 80, 123),
- ("plum3", 92, 53, 102),
- ("chocolate1", 233, 185, 110),
- ("chocolate2", 193, 125, 17),
- ("chocolate3", 143, 89, 2),
- ("scarletred1", 239, 41, 41),
- ("scarletred2", 204, 0, 0),
- ("scarletred3", 164, 0, 0),
- ("aluminium1", 238, 238, 236),
- ("aluminium2", 211, 215, 207),
- ("aluminium3", 186, 189, 182),
- ("aluminium4", 136, 138, 133),
- ("aluminium5", 85, 87, 83),
- ("aluminium6", 46, 52, 54)]:
- setattr (self, name, Color ("%02x%02x%02x" % (r, g, b,)))
-
-class ColorTheme (object):
-
- def __init__ (self):
-
- self.colors = {}
-
- def add_color (self, key, *colors):
-
- self.colors[key] = colors
-
-class LevelColorTheme (ColorTheme):
-
- pass
-
-class LevelColorThemeTango (LevelColorTheme):
-
- def __init__ (self):
-
- LevelColorTheme.__init__ (self)
-
- p = TangoPalette.get ()
- self.add_color (Data.debug_level_none,
- None, None, None)
- self.add_color (Data.debug_level_log,
- p.black, p.plum1, Color ("#e0a4d9"))
- self.add_color (Data.debug_level_debug,
- p.black, p.skyblue1, Color ("#8cc4ff"))
- self.add_color (Data.debug_level_info,
- p.black, p.chameleon1, Color ("#9dff3b"))
- self.add_color (Data.debug_level_warning,
- p.black, p.orange1, Color ("#ffc266"))
- self.add_color (Data.debug_level_error,
- p.white, p.scarletred1, Color ("#ff4545"))
-
-class ThreadColorTheme (ColorTheme):
-
- pass
-
-class ThreadColorThemeTango (ThreadColorTheme):
-
- def __init__ (self):
-
- ThreadColorTheme.__init__ (self)
-
- t = TangoPalette.get ()
- for i, color in enumerate ([t.butter2,
- t.orange2,
- t.chocolate3,
- t.chameleon2,
- t.skyblue1,
- t.plum1,
- t.scarletred1,
- t.aluminium6]):
- self.add_color (i, color)
-
-class LogModelBase (gtk.GenericTreeModel):
-
- __metaclass__ = Common.GUI.MetaModel
-
- columns = ("COL_TIME", gobject.TYPE_UINT64,
- "COL_PID", int,
- "COL_THREAD", gobject.TYPE_UINT64,
- "COL_LEVEL", object,
- "COL_CATEGORY", str,
- "COL_FILENAME", str,
- "COL_LINE_NUMBER", int,
- "COL_FUNCTION", str,
- "COL_OBJECT", str,
- "COL_MESSAGE", str,)
-
- def __init__ (self):
-
- gtk.GenericTreeModel.__init__ (self)
-
- ##self.props.leak_references = False
-
- self.line_offsets = []
- self.line_levels = [] # FIXME: Not so nice!
- self.line_cache = {}
-
- def ensure_cached (self, line_offset):
-
- raise NotImplementedError ("derived classes must override this method")
-
- def access_offset (self, offset):
-
- raise NotImplementedError ("derived classes must override this method")
-
- def iter_rows_offset (self):
-
- ensure_cached = self.ensure_cached
- line_cache = self.line_cache
- line_levels = self.line_levels
- COL_LEVEL = self.COL_LEVEL
-
- for i, offset in enumerate (self.line_offsets):
- ensure_cached (offset)
- row = line_cache[offset]
- row[COL_LEVEL] = line_levels[i] # FIXME
- yield (row, offset,)
-
- 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, col_id):
-
- return self.column_types[col_id]
-
- def on_get_iter (self, path):
-
- if not path:
- return
-
- if len (path) > 1:
- # Flat model.
- return None
-
- line_index = path[0]
-
- if line_index > len (self.line_offsets) - 1:
- return None
-
- return line_index
-
- def on_get_path (self, rowref):
-
- line_index = rowref
-
- return (line_index,)
-
- def on_get_value (self, line_index, col_id):
-
- last_index = len (self.line_offsets) - 1
-
- if line_index > last_index:
- return None
-
- if col_id == self.COL_LEVEL:
- return self.line_levels[line_index]
-
- line_offset = self.line_offsets[line_index]
- self.ensure_cached (line_offset)
-
- value = self.line_cache[line_offset][col_id]
- if col_id == self.COL_MESSAGE:
- message_offset = value
- value = self.access_offset (line_offset + message_offset).strip ()
-
- return value
-
- def on_iter_next (self, line_index):
-
- last_index = len (self.line_offsets) - 1
-
- if line_index >= last_index:
- return None
- else:
- return line_index + 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):
-
- if rowref is not None:
- return 0
-
- return len (self.line_offsets)
-
- def on_iter_nth_child (self, parent, n):
-
- last_index = len (self.line_offsets) - 1
-
- if parent or n > last_index:
- return None
-
- return n
-
- def on_iter_parent (self, child):
-
- return None
-
- ## def on_ref_node (self, rowref):
-
- ## pass
-
- ## def on_unref_node (self, rowref):
-
- ## pass
-
-class LazyLogModel (LogModelBase):
-
- def __init__ (self, log_obj = None):
-
- LogModelBase.__init__ (self)
-
- self.__log_obj = log_obj
-
- if log_obj:
- self.set_log (log_obj)
-
- def set_log (self, log_obj):
-
- self.__fileobj = log_obj.fileobj
-
- self.line_cache.clear ()
- self.line_offsets = log_obj.line_cache.offsets
- self.line_levels = log_obj.line_cache.levels
-
- def access_offset (self, offset):
-
- # TODO: Implement using one slice access instead of seek+readline.
- self.__fileobj.seek (offset)
- return self.__fileobj.readline ()
-
- def ensure_cached (self, line_offset):
-
- if line_offset in self.line_cache:
- return
-
- if len (self.line_cache) > 10000:
- self.line_cache.clear ()
-
- self.__fileobj.seek (line_offset)
- line = self.__fileobj.readline ()
-
- self.line_cache[line_offset] = Data.LogLine.parse_full (line)
-
-class FilteredLogModelBase (LogModelBase):
-
- def __init__ (self, super_model):
-
- LogModelBase.__init__ (self)
-
- self.logger = logging.getLogger ("filter-model-base")
-
- self.super_model = super_model
- self.access_offset = super_model.access_offset
- self.ensure_cached = super_model.ensure_cached
- self.line_cache = super_model.line_cache
-
- def line_index_to_super (self, line_index):
-
- raise NotImplementedError ("index conversion not supported")
-
- def line_index_from_super (self, super_line_index):
-
- raise NotImplementedError ("index conversion not supported")
-
- def line_index_to_top (self, line_index):
-
- _log_indices = [line_index]
-
- super_index = line_index
- for model in self._iter_hierarchy ():
- super_index = model.line_index_to_super (super_index)
- _log_indices.append (super_index)
-
- _log_trans = " -> ".join ([str (x) for x in _log_indices])
- self.logger.debug ("translated index to top: %s", _log_trans)
-
- return super_index
-
- def line_index_from_top (self, super_index):
-
- _log_indices = [super_index]
-
- line_index = super_index
- for model in reversed (list (self._iter_hierarchy ())):
- line_index = model.line_index_from_super (line_index)
- _log_indices.append (line_index)
-
- _log_trans = " -> ".join ([str (x) for x in _log_indices])
- self.logger.debug ("translated index from top: %s", _log_trans)
-
- return line_index
-
- def super_model_changed (self):
-
- pass
-
- def _iter_hierarchy (self):
-
- model = self
- while hasattr (model, "super_model") and model.super_model:
- yield model
- model = model.super_model
-
-class FilteredLogModelIdentity (FilteredLogModelBase):
-
- def __init__ (self, super_model):
-
- FilteredLogModelBase.__init__ (self, super_model)
-
- self.line_offsets = self.super_model.line_offsets
- self.line_levels = self.super_model.line_levels
-
- def line_index_from_super (self, super_line_index):
-
- return super_line_index
-
- def line_index_to_super (self, line_index):
-
- return line_index
-
-class FilteredLogModel (FilteredLogModelBase):
-
- def __init__ (self, super_model):
-
- FilteredLogModelBase.__init__ (self, super_model)
-
- self.logger = logging.getLogger ("filtered-log-model")
-
- self.filters = []
- self.super_index = []
- self.from_super_index = {}
- self.reset ()
- self.__active_process = None
- self.__filter_progress = 0.
- self.__old_super_model_range = super_model.line_index_range
-
- def reset (self):
-
- del self.line_offsets[:]
- self.line_offsets += self.super_model.line_offsets
- del self.line_levels[:]
- self.line_levels += self.super_model.line_levels
-
- del self.super_index[:]
- self.from_super_index.clear ()
-
- del self.filters[:]
-
- def __filter_process (self, filter):
-
- YIELD_LIMIT = 10000
-
- self.logger.debug ("preparing new filter")
- ## del self.line_offsets[:]
- ## del self.line_levels[:]
- new_line_offsets = []
- new_line_levels = []
- new_super_index = []
- new_from_super_index = {}
- level_id = self.COL_LEVEL
- func = filter.filter_func
- if len (self.filters) == 1:
- # This is the first filter that gets applied.
- def enum ():
- i = 0
- for row, offset in self.iter_rows_offset ():
- yield (i, row, offset,)
- i += 1
- else:
- def enum ():
- i = 0
- for row, offset in self.iter_rows_offset ():
- line_index = self.super_index[i]
- yield (line_index, row, offset,)
- i += 1
- self.logger.debug ("running filter")
- progress = 0.
- progress_full = float (len (self))
- y = YIELD_LIMIT
- for i, row, offset in enum ():
- if func (row):
- new_line_offsets.append (offset)
- new_line_levels.append (row[level_id])
- new_super_index.append (i)
- new_from_super_index[i] = len (new_super_index) - 1
- y -= 1
- if y == 0:
- progress += float (YIELD_LIMIT)
- self.__filter_progress = progress / progress_full
- y = YIELD_LIMIT
- yield True
- self.line_offsets = new_line_offsets
- self.line_levels = new_line_levels
- self.super_index = new_super_index
- self.from_super_index = new_from_super_index
- self.logger.debug ("filtering finished")
-
- self.__filter_progress = 1.
- self.__handle_filter_process_finished ()
- yield False
-
- def add_filter (self, filter, dispatcher):
-
- if self.__active_process is not None:
- raise ValueError ("dispatched a filter process already")
-
- self.filters.append (filter)
-
- self.__dispatcher = dispatcher
- self.__active_process = self.__filter_process (filter)
- dispatcher (self.__active_process)
-
- def abort_process (self):
-
- if self.__active_process is None:
- raise ValueError ("no filter process running")
-
- self.__dispatcher.cancel ()
- self.__active_process = None
- self.__dispatcher = None
-
- del self.filters[-1]
-
- def get_filter_progress (self):
-
- if self.__active_process is None:
- raise ValueError ("no filter process running")
-
- return self.__filter_progress
-
- def __handle_filter_process_finished (self):
-
- self.__active_process = None
- self.handle_process_finished ()
-
- def handle_process_finished (self):
-
- pass
-
- def line_index_from_super (self, super_line_index):
-
- if len (self.filters) == 0:
- # Identity.
- return super_line_index
-
- try:
- return self.from_super_index[super_line_index]
- except KeyError:
- raise IndexError ("super index %i not handled" % (super_line_index,))
-
- def line_index_to_super (self, line_index):
-
- if len (self.filters) == 0:
- # Identity.
- return line_index
-
- return self.super_index[line_index]
-
- def __filtered_indices_in_range (self, start, stop):
-
- if start < 0:
- raise ValueError ("start cannot be negative (got %r)" % (start,))
-
- super_start = bisect_left (self.super_index, start)
- super_stop = bisect_left (self.super_index, stop)
-
- return super_stop - super_start
-
- def super_model_changed_range (self):
-
- range_model = self.super_model
- old_start, old_stop = self.__old_super_model_range
- super_start, super_stop = range_model.line_index_range
-
- super_start_offset = super_start - old_start
- if super_start_offset < 0:
- # TODO:
- raise NotImplementedError ("Only handling further restriction of the range"
- " (start offset = %i)" % (super_start_offset,))
-
- super_end_offset = super_stop - old_stop
- if super_end_offset > 0:
- # TODO:
- raise NotImplementedError ("Only handling further restriction of the range"
- " (end offset = %i)" % (super_end_offset,))
-
- if super_end_offset < 0:
- if not self.super_index:
- # Identity; there are no filters.
- end_offset = len (self.line_offsets) + super_end_offset
- else:
- n_filtered = self.__filtered_indices_in_range (super_stop - super_start,
- old_stop - super_start)
- end_offset = len (self.line_offsets) - n_filtered
- stop = len (self.line_offsets) # FIXME?
- assert end_offset < stop
-
- self.__remove_range (end_offset, stop)
-
- if super_start_offset > 0:
- if not self.super_index:
- # Identity; there are no filters.
- n_filtered = super_start_offset
- start_offset = n_filtered
- else:
- n_filtered = self.__filtered_indices_in_range (0, super_start_offset)
- start_offset = n_filtered
-
- if n_filtered > 0:
- self.__remove_range (0, start_offset)
-
- from_super = self.from_super_index
- for i in self.super_index:
- old_index = from_super[i]
- del from_super[i]
- from_super[i - super_start_offset] = old_index - start_offset
-
- for i in range (len (self.super_index)):
- self.super_index[i] -= super_start_offset
-
- self.__old_super_model_range = (super_start, super_stop,)
-
- def __remove_range (self, start, stop):
-
- if start < 0:
- raise ValueError ("start cannot be negative (got %r)" % (start,))
- if start == stop:
- return
- if stop > len (self.line_offsets):
- raise ValueError ("stop value out of range (got %r)" % (stop,))
- if start > stop:
- raise ValueError ("start cannot be greater than stop (got %r, %r)" % (start, stop,))
-
- self.logger.debug ("removing line range (%i, %i)",
- start, stop)
-
- del self.line_offsets[start:stop]
- del self.line_levels[start:stop]
- for super_index in self.super_index[start:stop]:
- del self.from_super_index[super_index]
- del self.super_index[start:stop]
-
-class Filter (object):
-
- pass
-
-class DebugLevelFilter (Filter):
-
- def __init__ (self, debug_level):
-
- col_id = LogModelBase.COL_LEVEL
- def filter_func (row):
- return row[col_id] != debug_level
- self.filter_func = filter_func
-
-class CategoryFilter (Filter):
-
- def __init__ (self, category):
-
- col_id = LogModelBase.COL_CATEGORY
- def category_filter_func (row):
- return row[col_id] != category
- self.filter_func = category_filter_func
-
-class ObjectFilter (Filter):
-
- def __init__ (self, object_):
-
- col_id = LogModelBase.COL_OBJECT
- def object_filter_func (row):
- return row[col_id] != object_
- self.filter_func = object_filter_func
-
-class FilenameFilter (Filter):
-
- def __init__ (self, filename):
-
- col_id = LogModelBase.COL_FILENAME
- def filename_filter_func (row):
- return row[col_id] != filename
- self.filter_func = filename_filter_func
-
-class SubRange (object):
-
- __slots__ = ("l", "start", "stop",)
-
- def __init__ (self, l, start, stop):
-
- if start > stop:
- raise ValueError ("need start <= stop (got %r, %r)" % (start, stop,))
-
- self.l = l
- self.start = start
- self.stop = stop
-
- def __getitem__ (self, i):
-
- return self.l[i + self.start]
-
- def __len__ (self):
-
- return self.stop - self.start
-
- def __iter__ (self):
-
- l = self.l
- for i in xrange (self.start, self.stop):
- yield l[i]
-
-class RangeFilteredLogModel (FilteredLogModelBase):
-
- def __init__ (self, super_model):
-
- FilteredLogModelBase.__init__ (self, super_model)
-
- self.logger = logging.getLogger ("range-filtered-model")
-
- self.line_index_range = None
-
- def set_range (self, start_index, stop_index):
-
- self.logger.debug ("setting range to start = %i, stop = %i",
- start_index, stop_index)
-
- self.line_index_range = (start_index, stop_index,)
- self.line_offsets = SubRange (self.super_model.line_offsets,
- start_index, stop_index)
- self.line_levels = SubRange (self.super_model.line_levels,
- start_index, stop_index)
-
- def reset (self):
-
- self.logger.debug ("reset")
-
- start_index = 0
- stop_index = len (self.super_model)
-
- self.set_range (start_index, stop_index,)
-
- def line_index_to_super (self, line_index):
-
- start_index = self.line_index_range[0]
-
- return line_index + start_index
-
- def line_index_from_super (self, li):
-
- start, stop = self.line_index_range
-
- if li < start or li >= stop:
- raise IndexError ("not in range")
-
- return li - start
-
-# 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_data_func = None
- get_row_data_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
-
-class SizedColumn (Column):
-
- default_size = None
-
- def compute_default_size (self, view, model):
-
- return None
-
-# Sync with gst-inspector?
-class TextColumn (SizedColumn):
-
- font_family = None
-
- def __init__ (self):
-
- Column.__init__ (self)
-
- column = self.view_column
- cell = gtk.CellRendererText ()
- column.pack_start (cell)
-
- cell.props.yalign = 0.
-
- 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 ()
- assert data_func
- id_ = self.id
- if id_ is not None:
- def cell_data_func (column, cell, model, tree_iter):
- data_func (cell.props, model.get_value (tree_iter, id_), model.get_path (tree_iter))
- else:
- cell_data_func = data_func
- column.set_cell_data_func (cell, cell_data_func)
- elif self.get_row_data_func:
- data_func = self.get_row_data_func ()
- assert data_func
- def cell_data_func (column, cell, model, tree_iter):
- data_func (cell.props, model[tree_iter])
- column.set_cell_data_func (cell, cell_data_func)
- elif 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
-
- 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_cell_renderers ()[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)
- rect, x, y, w, h = self.view_column.cell_get_size ()
- max_width = max (max_width, w)
-
- return max_width
-
- def get_values_for_size (self):
-
- return ()
-
-class TimeColumn (TextColumn):
-
- name = "time"
- label_header = _("Time")
- id = LazyLogModel.COL_TIME
- font_family = "monospace"
-
- @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):
-
- values = [0]
-
- return values
-
-class LevelColumn (TextColumn):
-
- name = "level"
- label_header = _("L")
- id = LazyLogModel.COL_LEVEL
-
- def __init__ (self):
-
- TextColumn.__init__ (self)
-
- cell = self.view_column.get_cell_renderers ()[0]
- cell.props.xalign = .5
-
- @staticmethod
- def get_modify_func ():
-
- def format_level (value):
- 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, path):
- 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]
- if path[0] % 2:
- cell_props.background_gdk = cell_colors[1]
- else:
- cell_props.background_gdk = cell_colors[2]
-
- return level_data_func
-
- def get_values_for_size (self):
-
- values = [Data.debug_level_log, Data.debug_level_debug,
- Data.debug_level_info, Data.debug_level_warning,
- Data.debug_level_error]
-
- return values
-
-class PidColumn (TextColumn):
-
- name = "pid"
- label_header = _("PID")
- id = LazyLogModel.COL_PID
- font_family = "monospace"
-
- @staticmethod
- def get_modify_func ():
-
- return str
-
- def get_values_for_size (self):
-
- return ["999999"]
-
-class ThreadColumn (TextColumn):
-
- name = "thread"
- label_header = _("Thread")
- id = LazyLogModel.COL_THREAD
- font_family = "monospace"
-
- @staticmethod
- def get_modify_func ():
-
- def format_thread (value):
- return "0x%07x" % (value,)
-
- return format_thread
-
- def get_values_for_size (self):
-
- return [int ("ffffff", 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 CodeColumn (TextColumn):
-
- name = "code"
- label_header = _("Code")
- id = None
-
- @staticmethod
- def get_data_func ():
-
- filename_id = LogModelBase.COL_FILENAME
- line_number_id = LogModelBase.COL_LINE_NUMBER
- def filename_data_func (column, cell, model, tree_iter):
- args = model.get (tree_iter, filename_id, line_number_id)
- cell.props.text = "%s:%i" % args
-
- return filename_data_func
-
- def get_values_for_size (self):
-
- return ["gstsomefilename.c:1234"]
-
-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 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
-
- def __init__ (self, *a, **kw):
-
- self.highlighters = {}
-
- TextColumn.__init__ (self, *a, **kw)
-
- def get_row_data_func (self):
-
- from pango import AttrList, AttrBackground, AttrForeground
- highlighters = self.highlighters
- id_ = self.id
-
- # FIXME: This should be none; need to investigate
- # `cellrenderertext.props.attributes = None' failure (param conversion
- # error like `treeview.props.model = None').
- no_attrs = AttrList ()
-
- def message_data_func (props, row):
-
- props.text = row[id_]
- if not highlighters:
- props.attributes = no_attrs
- for highlighter in highlighters.values ():
- ranges = highlighter (row)
- if not ranges:
- props.attributes = no_attrs
- else:
- attrlist = AttrList ()
- for start, end in ranges:
- attrlist.insert (AttrBackground (0, 0, 65535, start, end))
- attrlist.insert (AttrForeground (65535, 65535, 65535, start, end))
- props.attributes = attrlist
-
- return message_data_func
-
- def get_values_for_size (self):
-
- values = ["Just some good minimum size"]
-
- return values
-
-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, PidColumn, ThreadColumn, CategoryColumn,
- CodeColumn, FunctionColumn, ObjectColumn, MessageColumn,)
-
- def __init__ (self, state):
-
- ColumnManager.__init__ (self)
-
- self.logger = logging.getLogger ("ui.columns")
-
- self.state = state
-
- def attach (self, view):
-
- self.view = view
- view.connect ("notify::model", self.__handle_notify_model)
-
- order = self.state.column_order
- if len (order) == len (self.column_classes):
- self.column_order[:] = order
-
- visible = self.state.columns_visible
- if not visible:
- visible = self.column_classes
- for col_class in self.column_classes:
- action = self.get_toggle_action (col_class)
- action.props.active = (col_class in visible)
-
- ColumnManager.attach (self)
-
- self.columns_sized = False
-
- def detach (self):
-
- self.state.column_order = self.column_order
- self.state.columns_visible = self.columns
-
- return ColumnManager.detach (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):
-
- if self.columns_sized:
- # Already sized.
- return
- model = self.view.props.model
- if model is None:
- return
- self.logger.debug ("model changed, sizing columns")
- for column in self.iter_items ():
- self.size_column (column, view, model)
- self.columns_sized = True
-
-class LineViewLogModel (FilteredLogModelBase):
-
- def __init__ (self, super_model):
-
- FilteredLogModelBase.__init__ (self, super_model)
-
- self.line_offsets = []
- self.line_levels = []
-
- self.parent_indices = []
-
- def reset (self):
-
- del self.line_offsets[:]
- del self.line_levels[:]
-
- def line_index_to_super (self, line_index):
-
- return self.parent_indices[line_index]
-
- def insert_line (self, position, super_line_index):
-
- if position == -1:
- position = len (self.line_offsets)
- li = super_line_index
- self.line_offsets.insert (position, self.super_model.line_offsets[li])
- self.line_levels.insert (position, self.super_model.line_levels[li])
- self.parent_indices.insert (position, super_line_index)
-
- path = (position,)
- tree_iter = self.get_iter (path)
- self.row_inserted (path, tree_iter)
-
- def replace_line (self, line_index, super_line_index):
-
- li = line_index
- self.line_offsets[li] = self.super_model.line_offsets[super_line_index]
- self.line_levels[li] = self.super_model.line_levels[super_line_index]
- self.parent_indices[li] = super_line_index
-
- path = (line_index,)
- tree_iter = self.get_iter (path)
- self.row_changed (path, tree_iter)
-
- def remove_line (self, line_index):
-
- for l in (self.line_offsets,
- self.line_levels,
- self.parent_indices,):
- del l[line_index]
-
- path = (line_index,)
- self.row_deleted (path)
-
-class WrappingMessageColumn (MessageColumn):
-
- def wrap_to_width (self, width):
-
- col = self.view_column
- col.props.max_width = width
- col.get_cell_renderers ()[0].props.wrap_width = width
- col.queue_resize ()
-
-class LineViewColumnManager (ColumnManager):
-
- column_classes = (TimeColumn, WrappingMessageColumn,)
-
- def __init__ (self):
-
- ColumnManager.__init__ (self)
-
- def attach (self, window):
-
- 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)
-
- def __update_sizes (self):
-
- view_width = self.view.get_allocation ().width
- if view_width == self.__size_update:
- # Prevent endless recursion.
- return
-
- self.__size_update = view_width
-
- col = self.find_item (name = "time")
- other_width = col.view_column.props.width
-
- try:
- col = self.find_item (name = "message")
- except KeyError:
- return
-
- width = view_width - other_width
- col.wrap_to_width (width)
-
- def __handle_size_allocate (self, self_, allocation):
-
- self.__update_sizes ()
-
-class LineView (object):
-
- def __init__ (self):
-
- self.column_manager = LineViewColumnManager ()
-
- def attach (self, window):
-
- self.clear_action = window.actions.clear_line_view
- handler = self.handle_clear_line_view_action_activate
- self.clear_action.connect ("activate", handler)
-
- 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):
-
- model = self.line_view.props.model
-
- 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):
-
- self.line_view.props.model = LineViewLogModel (window.log_model)
-
- def handle_line_view_row_activated (self, view, path, column):
-
- line_index = path[0]
- line_model = view.props.model
- log_model = self.log_view.props.model
- 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):
-
- log_model = view.props.model
- line_index = path[0]
-
- top_line_index = log_model.line_index_to_top (line_index)
- line_model = self.line_view.props.model
- 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):
-
- line_model = self.line_view.props.model
- 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)
-
- def handle_clear_line_view_action_activate (self, action):
-
- self.clear ()
-
-class ProgressDialog (object):
-
- def __init__ (self, window, title = ""):
-
- widgets = window.widget_factory.make ("progress-dialog.ui", "progress_dialog")
- dialog = widgets.progress_dialog
- dialog.connect ("response", self.__handle_dialog_response)
-
- self.__dialog = dialog
- self.__progress_bar = widgets.progress_bar
- self.__progress_bar.props.text = title
-
- dialog.set_transient_for (window.gtk_window)
- dialog.show ()
-
- def __handle_dialog_response (self, dialog, resp):
-
- 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
-
- def destroy (self):
-
- if self.__dialog is None:
- return
- self.__dialog.destroy ()
- self.__dialog = None
- self.__progress_bar = None
-
-class Window (object):
-
- def __init__ (self, app):
-
- self.logger = logging.getLogger ("ui.window")
- self.app = app
-
- self.dispatcher = None
- 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"), "N"),
- ("open-file", gtk.STOCK_OPEN, _("_Open File"), "O"),
- ("reload-file", gtk.STOCK_REFRESH, _("_Reload File"), "R"),
- ("close-window", gtk.STOCK_CLOSE, _("Close _Window"), "W"),
- ("cancel-load", gtk.STOCK_CANCEL, None,),
- ("clear-line-view", gtk.STOCK_CLEAR, None),
- ("show-about", gtk.STOCK_ABOUT, None)])
- self.actions.add_group (group)
- self.actions.reload_file.props.sensitive = False
-
- group = gtk.ActionGroup ("RowActions")
- group.add_actions ([("hide-before-line", None, _("Hide lines before this one")),
- ("hide-after-line", None, _("Hide lines after this one")),
- ("show-hidden-lines", None, _("Show hidden lines")),
- ("edit-copy-line", gtk.STOCK_COPY, _("Copy line"), "C"),
- ("edit-copy-message", gtk.STOCK_COPY, _("Copy message"), ""),
- ("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):
-
- 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)
-
- for action_name in ("new-window", "open-file", "reload-file",
- "close-window", "cancel-load",
- "hide-before-line", "hide-after-line", "show-hidden-lines",
- "edit-copy-line", "edit-copy-message",
- "hide-log-level", "hide-log-category", "hide-log-object",
- "hide-filename", "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 (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)
-
- 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")
- model = self.log_view.props.model
- 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
-
- model = self.log_view.props.model
- 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:
- model = self.log_view.props.model
-
- previous_model = self.log_view.props.model
-
- if previous_model == model:
- # Force update.
- self.log_view.set_model (None)
- self.log_view.props.model = model
-
- def pop_view_state (self, scroll_to_selection = False):
-
- model = self.log_view.props.model
- 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)
- except IndexError, exc:
- 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
- model = view.props.model
-
- 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)
- last_selected = (line_index == len (self.log_view.props.model) - 1)
-
- 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 ()
-
- def handle_new_window_action_activate (self, action):
-
- self.app.open_window ()
-
- 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 ()
-
- def handle_reload_file_action_activate (self, action):
-
- if self.log_file is None:
- return
-
- self.set_log_file (self.log_file.path)
-
- def handle_cancel_load_action_activate (self, action):
-
- self.logger.debug ("cancelling data load")
-
- self.set_log_file (None)
-
- if self.progress_dialog:
- self.progress_dialog.destroy ()
- self.progress_dialog = None
- if self.update_progress_id is not None:
- gobject.source_remove (self.update_progress_id)
- self.update_progress_id = None
-
- def handle_close_window_action_activate (self, action):
-
- self.close ()
-
- def handle_hide_after_line_action_activate (self, action):
-
- self.hide_range (after = True)
-
- def handle_hide_before_line_action_activate (self, action):
-
- self.hide_range (after = False)
-
- def hide_range (self, after):
-
- model = self.log_view.props.model
- 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
-
- 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
-
- 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 ())
-
- def handle_edit_copy_message_action_activate (self, action):
-
- col_id = LogModelBase.COL_MESSAGE
- self.clipboard.set_text (self.get_active_line ()[col_id])
-
- def add_model_filter (self, filter):
-
- self.progress_dialog = ProgressDialog (self, _("Filtering"))
- 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):
-
- self.progress_dialog.destroy ()
- self.progress_dialog = None
-
- self.log_filter.abort_process ()
- self.log_view.props.model = self.log_filter
- self.pop_view_state ()
-
- def handle_log_filter_process_finished (self):
-
- self.progress_dialog.destroy ()
- 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
-
- 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))
-
- 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))
-
- 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_))
-
- def handle_hide_filename_action_activate (self, action):
-
- row = self.get_active_line ()
- filename = row[LogModelBase.COL_FILENAME]
- self.add_model_filter (FilenameFilter (filename))
-
- 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)
- except EnvironmentError, exc:
- 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):
-
- dialog = gtk.MessageDialog (self.gtk_window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR,
- gtk.BUTTONS_OK, message1)
- # The property for secondary text is new in 2.10, so we use this clunky
- # method instead.
- dialog.format_secondary_text (message2)
- dialog.set_default_response (0)
- dialog.run ()
- dialog.destroy ()
-
- def handle_load_started (self):
-
- self.logger.debug ("load has started")
-
- self.progress_dialog = ProgressDialog (self, _("Loading log file"))
- 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")
-
- self.progress_dialog.destroy ()
- 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
-
- def idle_set ():
- self.log_view.props.model = self.log_range
- 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)
-
-class AppStateSection (Common.GUI.StateSection):
-
- _name = "state"
-
- geometry = Common.GUI.StateInt4 ("window-geometry")
- maximized = Common.GUI.StateBool ("window-maximized")
-
- column_order = Common.GUI.StateItemList ("column-order", ViewColumnManager)
- columns_visible = Common.GUI.StateItemList ("columns-visible", ViewColumnManager)
-
-class AppState (Common.GUI.State):
-
- def __init__ (self, *a, **kw):
-
- Common.GUI.State.__init__ (self, *a, **kw)
-
- self.add_section_class (AppStateSection)
-
-class App (object):
-
- def __init__ (self):
-
- self.attach ()
-
- def load_plugins (self):
-
- from GstDebugViewer import Plugins
-
- plugin_classes = list (Plugins.load ([os.path.dirname (Plugins.__file__)]))
- self.plugins = []
- for plugin_class in plugin_classes:
- plugin = plugin_class (self)
- self.plugins.append (plugin)
-
- def iter_plugin_features (self):
-
- for plugin in self.plugins:
- for feature in plugin.features:
- yield feature
-
- def attach (self):
-
- config_home = Common.utils.XDG.CONFIG_HOME
-
- state_filename = os.path.join (config_home, "gst-debug-viewer", "state")
-
- self.state = AppState (state_filename)
- self.state_section = self.state.sections["state"]
-
- self.load_plugins ()
-
- self.windows = []
-
- self.open_window ()
-
- 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 open_window (self):
-
- self.windows.append (Window (self))
-
- def close_window (self, window):
-
- self.windows.remove (window)
- if not self.windows:
- # GtkTreeView takes some time to go down for large files. Let's block
- # until the window is hidden:
- gobject.idle_add (gtk.main_quit)
- gtk.main ()
-
- gtk.main_quit ()
-
-def main (options):
-
- args = options["args"]
-
- app = App ()
-
- # TODO: Once we support more than one window, open one window for each
- # supplied filename.
- window = app.windows[0]
- if len (args) > 0:
- window.set_log_file (args[0])
-
- app.run ()
-
-if __name__ == "__main__":
- main ()
diff --git a/debug-viewer/GstDebugViewer/GUI/__init__.py b/debug-viewer/GstDebugViewer/GUI/__init__.py
new file mode 100644
index 0000000000..a3e3dd3e39
--- /dev/null
+++ b/debug-viewer/GstDebugViewer/GUI/__init__.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8; mode: python; -*-
+#
+# GStreamer Debug Viewer - View and analyze GStreamer debug log files
+#
+# Copyright (C) 2007 René Stadler
+#
+# 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 .
+
+"""GStreamer Debug Viewer GUI module."""
+
+__author__ = u"René Stadler "
+__version__ = "0.1"
+
+import pygtk
+pygtk.require ("2.0")
+del pygtk
+
+from GstDebugViewer.GUI.app import App
+
+def main (options):
+
+ args = options["args"]
+
+ app = App ()
+
+ # TODO: Once we support more than one window, open one window for each
+ # supplied filename.
+ window = app.windows[0]
+ if len (args) > 0:
+ window.set_log_file (args[0])
+
+ app.run ()
+
+if __name__ == "__main__":
+ main ()
diff --git a/debug-viewer/GstDebugViewer/GUI/app.py b/debug-viewer/GstDebugViewer/GUI/app.py
new file mode 100644
index 0000000000..bd53a359a5
--- /dev/null
+++ b/debug-viewer/GstDebugViewer/GUI/app.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8; mode: python; -*-
+#
+# GStreamer Debug Viewer - View and analyze GStreamer debug log files
+#
+# Copyright (C) 2007 René Stadler
+#
+# 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 .
+
+"""GStreamer Debug Viewer GUI module."""
+
+import os.path
+
+import gobject
+import gtk
+
+from GstDebugViewer import Common
+from GstDebugViewer.GUI.columns import ViewColumnManager
+from GstDebugViewer.GUI.window import Window
+
+class AppStateSection (Common.GUI.StateSection):
+
+ _name = "state"
+
+ geometry = Common.GUI.StateInt4 ("window-geometry")
+ maximized = Common.GUI.StateBool ("window-maximized")
+
+ column_order = Common.GUI.StateItemList ("column-order", ViewColumnManager)
+ columns_visible = Common.GUI.StateItemList ("columns-visible", ViewColumnManager)
+
+class AppState (Common.GUI.State):
+
+ def __init__ (self, *a, **kw):
+
+ Common.GUI.State.__init__ (self, *a, **kw)
+
+ self.add_section_class (AppStateSection)
+
+class App (object):
+
+ def __init__ (self):
+
+ self.attach ()
+
+ def load_plugins (self):
+
+ from GstDebugViewer import Plugins
+
+ plugin_classes = list (Plugins.load ([os.path.dirname (Plugins.__file__)]))
+ self.plugins = []
+ for plugin_class in plugin_classes:
+ plugin = plugin_class (self)
+ self.plugins.append (plugin)
+
+ def iter_plugin_features (self):
+
+ for plugin in self.plugins:
+ for feature in plugin.features:
+ yield feature
+
+ def attach (self):
+
+ config_home = Common.utils.XDG.CONFIG_HOME
+
+ state_filename = os.path.join (config_home, "gst-debug-viewer", "state")
+
+ self.state = AppState (state_filename)
+ self.state_section = self.state.sections["state"]
+
+ self.load_plugins ()
+
+ self.windows = []
+
+ self.open_window ()
+
+ 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 open_window (self):
+
+ self.windows.append (Window (self))
+
+ def close_window (self, window):
+
+ self.windows.remove (window)
+ if not self.windows:
+ # GtkTreeView takes some time to go down for large files. Let's block
+ # until the window is hidden:
+ gobject.idle_add (gtk.main_quit)
+ gtk.main ()
+
+ gtk.main_quit ()
diff --git a/debug-viewer/GstDebugViewer/GUI/colors.py b/debug-viewer/GstDebugViewer/GUI/colors.py
new file mode 100644
index 0000000000..6e84093677
--- /dev/null
+++ b/debug-viewer/GstDebugViewer/GUI/colors.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8; mode: python; -*-
+#
+# GStreamer Debug Viewer - View and analyze GStreamer debug log files
+#
+# Copyright (C) 2007 René Stadler
+#
+# 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 .
+
+"""GStreamer Debug Viewer GUI module."""
+
+import gtk
+
+from GstDebugViewer import Data
+
+class Color (object):
+
+ def __init__ (self, hex_24):
+
+ if hex_24.startswith ("#"):
+ s = hex_24[1:]
+ else:
+ s = hex_24
+
+ self._fields = tuple ((int (hs, 16) for hs in (s[:2], s[2:4], s[4:],)))
+
+ def gdk_color (self):
+
+ return gtk.gdk.color_parse (self.hex_string ())
+
+ def hex_string (self):
+
+ return "#%02x%02x%02x" % self._fields
+
+ def float_tuple (self):
+
+ return tuple ((float (x) / 255 for x in self._fields))
+
+ def byte_tuple (self):
+
+ return self._fields
+
+ def short_tuple (self):
+
+ return tuple ((x << 8 for x in self._fields))
+
+class ColorPalette (object):
+
+ @classmethod
+ def get (cls):
+
+ try:
+ return cls._instance
+ except AttributeError:
+ cls._instance = cls ()
+ return cls._instance
+
+class TangoPalette (ColorPalette):
+
+ def __init__ (self):
+
+ for name, r, g, b in [("black", 0, 0, 0,),
+ ("white", 255, 255, 255,),
+ ("butter1", 252, 233, 79),
+ ("butter2", 237, 212, 0),
+ ("butter3", 196, 160, 0),
+ ("chameleon1", 138, 226, 52),
+ ("chameleon2", 115, 210, 22),
+ ("chameleon3", 78, 154, 6),
+ ("orange1", 252, 175, 62),
+ ("orange2", 245, 121, 0),
+ ("orange3", 206, 92, 0),
+ ("skyblue1", 114, 159, 207),
+ ("skyblue2", 52, 101, 164),
+ ("skyblue3", 32, 74, 135),
+ ("plum1", 173, 127, 168),
+ ("plum2", 117, 80, 123),
+ ("plum3", 92, 53, 102),
+ ("chocolate1", 233, 185, 110),
+ ("chocolate2", 193, 125, 17),
+ ("chocolate3", 143, 89, 2),
+ ("scarletred1", 239, 41, 41),
+ ("scarletred2", 204, 0, 0),
+ ("scarletred3", 164, 0, 0),
+ ("aluminium1", 238, 238, 236),
+ ("aluminium2", 211, 215, 207),
+ ("aluminium3", 186, 189, 182),
+ ("aluminium4", 136, 138, 133),
+ ("aluminium5", 85, 87, 83),
+ ("aluminium6", 46, 52, 54)]:
+ setattr (self, name, Color ("%02x%02x%02x" % (r, g, b,)))
+
+class ColorTheme (object):
+
+ def __init__ (self):
+
+ self.colors = {}
+
+ def add_color (self, key, *colors):
+
+ self.colors[key] = colors
+
+class LevelColorTheme (ColorTheme):
+
+ pass
+
+class LevelColorThemeTango (LevelColorTheme):
+
+ def __init__ (self):
+
+ LevelColorTheme.__init__ (self)
+
+ p = TangoPalette.get ()
+ self.add_color (Data.debug_level_none,
+ None, None, None)
+ self.add_color (Data.debug_level_log,
+ p.black, p.plum1, Color ("#e0a4d9"))
+ self.add_color (Data.debug_level_debug,
+ p.black, p.skyblue1, Color ("#8cc4ff"))
+ self.add_color (Data.debug_level_info,
+ p.black, p.chameleon1, Color ("#9dff3b"))
+ self.add_color (Data.debug_level_warning,
+ p.black, p.orange1, Color ("#ffc266"))
+ self.add_color (Data.debug_level_error,
+ p.white, p.scarletred1, Color ("#ff4545"))
+
+class ThreadColorTheme (ColorTheme):
+
+ pass
+
+class ThreadColorThemeTango (ThreadColorTheme):
+
+ def __init__ (self):
+
+ ThreadColorTheme.__init__ (self)
+
+ t = TangoPalette.get ()
+ for i, color in enumerate ([t.butter2,
+ t.orange2,
+ t.chocolate3,
+ t.chameleon2,
+ t.skyblue1,
+ t.plum1,
+ t.scarletred1,
+ t.aluminium6]):
+ self.add_color (i, color)
diff --git a/debug-viewer/GstDebugViewer/GUI/columns.py b/debug-viewer/GstDebugViewer/GUI/columns.py
new file mode 100644
index 0000000000..522285d160
--- /dev/null
+++ b/debug-viewer/GstDebugViewer/GUI/columns.py
@@ -0,0 +1,648 @@
+# -*- coding: utf-8; mode: python; -*-
+#
+# GStreamer Debug Viewer - View and analyze GStreamer debug log files
+#
+# Copyright (C) 2007 René Stadler
+#
+# 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 .
+
+"""GStreamer Debug Viewer GUI module."""
+
+def _ (s):
+ return s
+
+import logging
+
+import gtk
+
+from GstDebugViewer import Common, Data
+from GstDebugViewer.GUI.colors import LevelColorThemeTango
+from GstDebugViewer.GUI.models import LazyLogModel, LogModelBase
+
+# 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_data_func = None
+ get_row_data_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
+
+class SizedColumn (Column):
+
+ default_size = None
+
+ def compute_default_size (self, view, model):
+
+ return None
+
+# Sync with gst-inspector?
+class TextColumn (SizedColumn):
+
+ font_family = None
+
+ def __init__ (self):
+
+ Column.__init__ (self)
+
+ column = self.view_column
+ cell = gtk.CellRendererText ()
+ column.pack_start (cell)
+
+ cell.props.yalign = 0.
+
+ 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 ()
+ assert data_func
+ id_ = self.id
+ if id_ is not None:
+ def cell_data_func (column, cell, model, tree_iter):
+ data_func (cell.props, model.get_value (tree_iter, id_), model.get_path (tree_iter))
+ else:
+ cell_data_func = data_func
+ column.set_cell_data_func (cell, cell_data_func)
+ elif self.get_row_data_func:
+ data_func = self.get_row_data_func ()
+ assert data_func
+ def cell_data_func (column, cell, model, tree_iter):
+ data_func (cell.props, model[tree_iter])
+ column.set_cell_data_func (cell, cell_data_func)
+ elif 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
+
+ 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_cell_renderers ()[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)
+ rect, x, y, w, h = self.view_column.cell_get_size ()
+ max_width = max (max_width, w)
+
+ return max_width
+
+ def get_values_for_size (self):
+
+ return ()
+
+class TimeColumn (TextColumn):
+
+ name = "time"
+ label_header = _("Time")
+ id = LazyLogModel.COL_TIME
+ font_family = "monospace"
+
+ @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):
+
+ values = [0]
+
+ return values
+
+class LevelColumn (TextColumn):
+
+ name = "level"
+ label_header = _("L")
+ id = LazyLogModel.COL_LEVEL
+
+ def __init__ (self):
+
+ TextColumn.__init__ (self)
+
+ cell = self.view_column.get_cell_renderers ()[0]
+ cell.props.xalign = .5
+
+ @staticmethod
+ def get_modify_func ():
+
+ def format_level (value):
+ 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, path):
+ 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]
+ if path[0] % 2:
+ cell_props.background_gdk = cell_colors[1]
+ else:
+ cell_props.background_gdk = cell_colors[2]
+
+ return level_data_func
+
+ def get_values_for_size (self):
+
+ values = [Data.debug_level_log, Data.debug_level_debug,
+ Data.debug_level_info, Data.debug_level_warning,
+ Data.debug_level_error]
+
+ return values
+
+class PidColumn (TextColumn):
+
+ name = "pid"
+ label_header = _("PID")
+ id = LazyLogModel.COL_PID
+ font_family = "monospace"
+
+ @staticmethod
+ def get_modify_func ():
+
+ return str
+
+ def get_values_for_size (self):
+
+ return ["999999"]
+
+class ThreadColumn (TextColumn):
+
+ name = "thread"
+ label_header = _("Thread")
+ id = LazyLogModel.COL_THREAD
+ font_family = "monospace"
+
+ @staticmethod
+ def get_modify_func ():
+
+ def format_thread (value):
+ return "0x%07x" % (value,)
+
+ return format_thread
+
+ def get_values_for_size (self):
+
+ return [int ("ffffff", 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 CodeColumn (TextColumn):
+
+ name = "code"
+ label_header = _("Code")
+ id = None
+
+ @staticmethod
+ def get_data_func ():
+
+ filename_id = LogModelBase.COL_FILENAME
+ line_number_id = LogModelBase.COL_LINE_NUMBER
+ def filename_data_func (column, cell, model, tree_iter):
+ args = model.get (tree_iter, filename_id, line_number_id)
+ cell.props.text = "%s:%i" % args
+
+ return filename_data_func
+
+ def get_values_for_size (self):
+
+ return ["gstsomefilename.c:1234"]
+
+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 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
+
+ def __init__ (self, *a, **kw):
+
+ self.highlighters = {}
+
+ TextColumn.__init__ (self, *a, **kw)
+
+ def get_row_data_func (self):
+
+ from pango import AttrList, AttrBackground, AttrForeground
+ highlighters = self.highlighters
+ id_ = self.id
+
+ # FIXME: This should be none; need to investigate
+ # `cellrenderertext.props.attributes = None' failure (param conversion
+ # error like `treeview.props.model = None').
+ no_attrs = AttrList ()
+
+ def message_data_func (props, row):
+
+ props.text = row[id_]
+ if not highlighters:
+ props.attributes = no_attrs
+ for highlighter in highlighters.values ():
+ ranges = highlighter (row)
+ if not ranges:
+ props.attributes = no_attrs
+ else:
+ attrlist = AttrList ()
+ for start, end in ranges:
+ attrlist.insert (AttrBackground (0, 0, 65535, start, end))
+ attrlist.insert (AttrForeground (65535, 65535, 65535, start, end))
+ props.attributes = attrlist
+
+ return message_data_func
+
+ def get_values_for_size (self):
+
+ values = ["Just some good minimum size"]
+
+ return values
+
+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, PidColumn, ThreadColumn, CategoryColumn,
+ CodeColumn, FunctionColumn, ObjectColumn, MessageColumn,)
+
+ def __init__ (self, state):
+
+ ColumnManager.__init__ (self)
+
+ self.logger = logging.getLogger ("ui.columns")
+
+ self.state = state
+
+ def attach (self, view):
+
+ self.view = view
+ view.connect ("notify::model", self.__handle_notify_model)
+
+ order = self.state.column_order
+ if len (order) == len (self.column_classes):
+ self.column_order[:] = order
+
+ visible = self.state.columns_visible
+ if not visible:
+ visible = self.column_classes
+ for col_class in self.column_classes:
+ action = self.get_toggle_action (col_class)
+ action.props.active = (col_class in visible)
+
+ ColumnManager.attach (self)
+
+ self.columns_sized = False
+
+ def detach (self):
+
+ self.state.column_order = self.column_order
+ self.state.columns_visible = self.columns
+
+ return ColumnManager.detach (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):
+
+ if self.columns_sized:
+ # Already sized.
+ return
+ model = self.view.props.model
+ if model is None:
+ return
+ self.logger.debug ("model changed, sizing columns")
+ for column in self.iter_items ():
+ self.size_column (column, view, model)
+ self.columns_sized = True
+
+class WrappingMessageColumn (MessageColumn):
+
+ def wrap_to_width (self, width):
+
+ col = self.view_column
+ col.props.max_width = width
+ col.get_cell_renderers ()[0].props.wrap_width = width
+ col.queue_resize ()
+
+class LineViewColumnManager (ColumnManager):
+
+ column_classes = (TimeColumn, WrappingMessageColumn,)
+
+ def __init__ (self):
+
+ ColumnManager.__init__ (self)
+
+ def attach (self, window):
+
+ 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)
+
+ def __update_sizes (self):
+
+ view_width = self.view.get_allocation ().width
+ if view_width == self.__size_update:
+ # Prevent endless recursion.
+ return
+
+ self.__size_update = view_width
+
+ col = self.find_item (name = "time")
+ other_width = col.view_column.props.width
+
+ try:
+ col = self.find_item (name = "message")
+ except KeyError:
+ return
+
+ width = view_width - other_width
+ col.wrap_to_width (width)
+
+ def __handle_size_allocate (self, self_, allocation):
+
+ self.__update_sizes ()
diff --git a/debug-viewer/GstDebugViewer/GUI/filters.py b/debug-viewer/GstDebugViewer/GUI/filters.py
new file mode 100644
index 0000000000..8367e0e3fe
--- /dev/null
+++ b/debug-viewer/GstDebugViewer/GUI/filters.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8; mode: python; -*-
+#
+# GStreamer Debug Viewer - View and analyze GStreamer debug log files
+#
+# Copyright (C) 2007 René Stadler
+#
+# 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 .
+
+"""GStreamer Debug Viewer GUI module."""
+
+from GstDebugViewer.GUI.models import LogModelBase
+
+class Filter (object):
+
+ pass
+
+class DebugLevelFilter (Filter):
+
+ def __init__ (self, debug_level):
+
+ col_id = LogModelBase.COL_LEVEL
+ def filter_func (row):
+ return row[col_id] != debug_level
+ self.filter_func = filter_func
+
+class CategoryFilter (Filter):
+
+ def __init__ (self, category):
+
+ col_id = LogModelBase.COL_CATEGORY
+ def category_filter_func (row):
+ return row[col_id] != category
+ self.filter_func = category_filter_func
+
+class ObjectFilter (Filter):
+
+ def __init__ (self, object_):
+
+ col_id = LogModelBase.COL_OBJECT
+ def object_filter_func (row):
+ return row[col_id] != object_
+ self.filter_func = object_filter_func
+
+class FilenameFilter (Filter):
+
+ def __init__ (self, filename):
+
+ col_id = LogModelBase.COL_FILENAME
+ def filename_filter_func (row):
+ return row[col_id] != filename
+ self.filter_func = filename_filter_func
+
diff --git a/debug-viewer/GstDebugViewer/GUI/models.py b/debug-viewer/GstDebugViewer/GUI/models.py
new file mode 100644
index 0000000000..f3139de2d5
--- /dev/null
+++ b/debug-viewer/GstDebugViewer/GUI/models.py
@@ -0,0 +1,639 @@
+# -*- coding: utf-8; mode: python; -*-
+#
+# GStreamer Debug Viewer - View and analyze GStreamer debug log files
+#
+# Copyright (C) 2007 René Stadler
+#
+# 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 .
+
+"""GStreamer Debug Viewer GUI module."""
+
+from bisect import bisect_left
+import logging
+
+import gobject
+import gtk
+
+from GstDebugViewer import Common, Data
+
+class LogModelBase (gtk.GenericTreeModel):
+
+ __metaclass__ = Common.GUI.MetaModel
+
+ columns = ("COL_TIME", gobject.TYPE_UINT64,
+ "COL_PID", int,
+ "COL_THREAD", gobject.TYPE_UINT64,
+ "COL_LEVEL", object,
+ "COL_CATEGORY", str,
+ "COL_FILENAME", str,
+ "COL_LINE_NUMBER", int,
+ "COL_FUNCTION", str,
+ "COL_OBJECT", str,
+ "COL_MESSAGE", str,)
+
+ def __init__ (self):
+
+ gtk.GenericTreeModel.__init__ (self)
+
+ ##self.props.leak_references = False
+
+ self.line_offsets = []
+ self.line_levels = [] # FIXME: Not so nice!
+ self.line_cache = {}
+
+ def ensure_cached (self, line_offset):
+
+ raise NotImplementedError ("derived classes must override this method")
+
+ def access_offset (self, offset):
+
+ raise NotImplementedError ("derived classes must override this method")
+
+ def iter_rows_offset (self):
+
+ ensure_cached = self.ensure_cached
+ line_cache = self.line_cache
+ line_levels = self.line_levels
+ COL_LEVEL = self.COL_LEVEL
+
+ for i, offset in enumerate (self.line_offsets):
+ ensure_cached (offset)
+ row = line_cache[offset]
+ row[COL_LEVEL] = line_levels[i] # FIXME
+ yield (row, offset,)
+
+ 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, col_id):
+
+ return self.column_types[col_id]
+
+ def on_get_iter (self, path):
+
+ if not path:
+ return
+
+ if len (path) > 1:
+ # Flat model.
+ return None
+
+ line_index = path[0]
+
+ if line_index > len (self.line_offsets) - 1:
+ return None
+
+ return line_index
+
+ def on_get_path (self, rowref):
+
+ line_index = rowref
+
+ return (line_index,)
+
+ def on_get_value (self, line_index, col_id):
+
+ last_index = len (self.line_offsets) - 1
+
+ if line_index > last_index:
+ return None
+
+ if col_id == self.COL_LEVEL:
+ return self.line_levels[line_index]
+
+ line_offset = self.line_offsets[line_index]
+ self.ensure_cached (line_offset)
+
+ value = self.line_cache[line_offset][col_id]
+ if col_id == self.COL_MESSAGE:
+ message_offset = value
+ value = self.access_offset (line_offset + message_offset).strip ()
+
+ return value
+
+ def on_iter_next (self, line_index):
+
+ last_index = len (self.line_offsets) - 1
+
+ if line_index >= last_index:
+ return None
+ else:
+ return line_index + 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):
+
+ if rowref is not None:
+ return 0
+
+ return len (self.line_offsets)
+
+ def on_iter_nth_child (self, parent, n):
+
+ last_index = len (self.line_offsets) - 1
+
+ if parent or n > last_index:
+ return None
+
+ return n
+
+ def on_iter_parent (self, child):
+
+ return None
+
+ ## def on_ref_node (self, rowref):
+
+ ## pass
+
+ ## def on_unref_node (self, rowref):
+
+ ## pass
+
+class LazyLogModel (LogModelBase):
+
+ def __init__ (self, log_obj = None):
+
+ LogModelBase.__init__ (self)
+
+ self.__log_obj = log_obj
+
+ if log_obj:
+ self.set_log (log_obj)
+
+ def set_log (self, log_obj):
+
+ self.__fileobj = log_obj.fileobj
+
+ self.line_cache.clear ()
+ self.line_offsets = log_obj.line_cache.offsets
+ self.line_levels = log_obj.line_cache.levels
+
+ def access_offset (self, offset):
+
+ # TODO: Implement using one slice access instead of seek+readline.
+ self.__fileobj.seek (offset)
+ return self.__fileobj.readline ()
+
+ def ensure_cached (self, line_offset):
+
+ if line_offset in self.line_cache:
+ return
+
+ if len (self.line_cache) > 10000:
+ self.line_cache.clear ()
+
+ self.__fileobj.seek (line_offset)
+ line = self.__fileobj.readline ()
+
+ self.line_cache[line_offset] = Data.LogLine.parse_full (line)
+
+class FilteredLogModelBase (LogModelBase):
+
+ def __init__ (self, super_model):
+
+ LogModelBase.__init__ (self)
+
+ self.logger = logging.getLogger ("filter-model-base")
+
+ self.super_model = super_model
+ self.access_offset = super_model.access_offset
+ self.ensure_cached = super_model.ensure_cached
+ self.line_cache = super_model.line_cache
+
+ def line_index_to_super (self, line_index):
+
+ raise NotImplementedError ("index conversion not supported")
+
+ def line_index_from_super (self, super_line_index):
+
+ raise NotImplementedError ("index conversion not supported")
+
+ def line_index_to_top (self, line_index):
+
+ _log_indices = [line_index]
+
+ super_index = line_index
+ for model in self._iter_hierarchy ():
+ super_index = model.line_index_to_super (super_index)
+ _log_indices.append (super_index)
+
+ _log_trans = " -> ".join ([str (x) for x in _log_indices])
+ self.logger.debug ("translated index to top: %s", _log_trans)
+
+ return super_index
+
+ def line_index_from_top (self, super_index):
+
+ _log_indices = [super_index]
+
+ line_index = super_index
+ for model in reversed (list (self._iter_hierarchy ())):
+ line_index = model.line_index_from_super (line_index)
+ _log_indices.append (line_index)
+
+ _log_trans = " -> ".join ([str (x) for x in _log_indices])
+ self.logger.debug ("translated index from top: %s", _log_trans)
+
+ return line_index
+
+ def super_model_changed (self):
+
+ pass
+
+ def _iter_hierarchy (self):
+
+ model = self
+ while hasattr (model, "super_model") and model.super_model:
+ yield model
+ model = model.super_model
+
+class FilteredLogModelIdentity (FilteredLogModelBase):
+
+ def __init__ (self, super_model):
+
+ FilteredLogModelBase.__init__ (self, super_model)
+
+ self.line_offsets = self.super_model.line_offsets
+ self.line_levels = self.super_model.line_levels
+
+ def line_index_from_super (self, super_line_index):
+
+ return super_line_index
+
+ def line_index_to_super (self, line_index):
+
+ return line_index
+
+class FilteredLogModel (FilteredLogModelBase):
+
+ def __init__ (self, super_model):
+
+ FilteredLogModelBase.__init__ (self, super_model)
+
+ self.logger = logging.getLogger ("filtered-log-model")
+
+ self.filters = []
+ self.super_index = []
+ self.from_super_index = {}
+ self.reset ()
+ self.__active_process = None
+ self.__filter_progress = 0.
+ self.__old_super_model_range = super_model.line_index_range
+
+ def reset (self):
+
+ del self.line_offsets[:]
+ self.line_offsets += self.super_model.line_offsets
+ del self.line_levels[:]
+ self.line_levels += self.super_model.line_levels
+
+ del self.super_index[:]
+ self.from_super_index.clear ()
+
+ del self.filters[:]
+
+ def __filter_process (self, filter):
+
+ YIELD_LIMIT = 10000
+
+ self.logger.debug ("preparing new filter")
+ ## del self.line_offsets[:]
+ ## del self.line_levels[:]
+ new_line_offsets = []
+ new_line_levels = []
+ new_super_index = []
+ new_from_super_index = {}
+ level_id = self.COL_LEVEL
+ func = filter.filter_func
+ if len (self.filters) == 1:
+ # This is the first filter that gets applied.
+ def enum ():
+ i = 0
+ for row, offset in self.iter_rows_offset ():
+ yield (i, row, offset,)
+ i += 1
+ else:
+ def enum ():
+ i = 0
+ for row, offset in self.iter_rows_offset ():
+ line_index = self.super_index[i]
+ yield (line_index, row, offset,)
+ i += 1
+ self.logger.debug ("running filter")
+ progress = 0.
+ progress_full = float (len (self))
+ y = YIELD_LIMIT
+ for i, row, offset in enum ():
+ if func (row):
+ new_line_offsets.append (offset)
+ new_line_levels.append (row[level_id])
+ new_super_index.append (i)
+ new_from_super_index[i] = len (new_super_index) - 1
+ y -= 1
+ if y == 0:
+ progress += float (YIELD_LIMIT)
+ self.__filter_progress = progress / progress_full
+ y = YIELD_LIMIT
+ yield True
+ self.line_offsets = new_line_offsets
+ self.line_levels = new_line_levels
+ self.super_index = new_super_index
+ self.from_super_index = new_from_super_index
+ self.logger.debug ("filtering finished")
+
+ self.__filter_progress = 1.
+ self.__handle_filter_process_finished ()
+ yield False
+
+ def add_filter (self, filter, dispatcher):
+
+ if self.__active_process is not None:
+ raise ValueError ("dispatched a filter process already")
+
+ self.filters.append (filter)
+
+ self.__dispatcher = dispatcher
+ self.__active_process = self.__filter_process (filter)
+ dispatcher (self.__active_process)
+
+ def abort_process (self):
+
+ if self.__active_process is None:
+ raise ValueError ("no filter process running")
+
+ self.__dispatcher.cancel ()
+ self.__active_process = None
+ self.__dispatcher = None
+
+ del self.filters[-1]
+
+ def get_filter_progress (self):
+
+ if self.__active_process is None:
+ raise ValueError ("no filter process running")
+
+ return self.__filter_progress
+
+ def __handle_filter_process_finished (self):
+
+ self.__active_process = None
+ self.handle_process_finished ()
+
+ def handle_process_finished (self):
+
+ pass
+
+ def line_index_from_super (self, super_line_index):
+
+ if len (self.filters) == 0:
+ # Identity.
+ return super_line_index
+
+ try:
+ return self.from_super_index[super_line_index]
+ except KeyError:
+ raise IndexError ("super index %i not handled" % (super_line_index,))
+
+ def line_index_to_super (self, line_index):
+
+ if len (self.filters) == 0:
+ # Identity.
+ return line_index
+
+ return self.super_index[line_index]
+
+ def __filtered_indices_in_range (self, start, stop):
+
+ if start < 0:
+ raise ValueError ("start cannot be negative (got %r)" % (start,))
+
+ super_start = bisect_left (self.super_index, start)
+ super_stop = bisect_left (self.super_index, stop)
+
+ return super_stop - super_start
+
+ def super_model_changed_range (self):
+
+ range_model = self.super_model
+ old_start, old_stop = self.__old_super_model_range
+ super_start, super_stop = range_model.line_index_range
+
+ super_start_offset = super_start - old_start
+ if super_start_offset < 0:
+ # TODO:
+ raise NotImplementedError ("Only handling further restriction of the range"
+ " (start offset = %i)" % (super_start_offset,))
+
+ super_end_offset = super_stop - old_stop
+ if super_end_offset > 0:
+ # TODO:
+ raise NotImplementedError ("Only handling further restriction of the range"
+ " (end offset = %i)" % (super_end_offset,))
+
+ if super_end_offset < 0:
+ if not self.super_index:
+ # Identity; there are no filters.
+ end_offset = len (self.line_offsets) + super_end_offset
+ else:
+ n_filtered = self.__filtered_indices_in_range (super_stop - super_start,
+ old_stop - super_start)
+ end_offset = len (self.line_offsets) - n_filtered
+ stop = len (self.line_offsets) # FIXME?
+ assert end_offset < stop
+
+ self.__remove_range (end_offset, stop)
+
+ if super_start_offset > 0:
+ if not self.super_index:
+ # Identity; there are no filters.
+ n_filtered = super_start_offset
+ start_offset = n_filtered
+ else:
+ n_filtered = self.__filtered_indices_in_range (0, super_start_offset)
+ start_offset = n_filtered
+
+ if n_filtered > 0:
+ self.__remove_range (0, start_offset)
+
+ from_super = self.from_super_index
+ for i in self.super_index:
+ old_index = from_super[i]
+ del from_super[i]
+ from_super[i - super_start_offset] = old_index - start_offset
+
+ for i in range (len (self.super_index)):
+ self.super_index[i] -= super_start_offset
+
+ self.__old_super_model_range = (super_start, super_stop,)
+
+ def __remove_range (self, start, stop):
+
+ if start < 0:
+ raise ValueError ("start cannot be negative (got %r)" % (start,))
+ if start == stop:
+ return
+ if stop > len (self.line_offsets):
+ raise ValueError ("stop value out of range (got %r)" % (stop,))
+ if start > stop:
+ raise ValueError ("start cannot be greater than stop (got %r, %r)" % (start, stop,))
+
+ self.logger.debug ("removing line range (%i, %i)",
+ start, stop)
+
+ del self.line_offsets[start:stop]
+ del self.line_levels[start:stop]
+ for super_index in self.super_index[start:stop]:
+ del self.from_super_index[super_index]
+ del self.super_index[start:stop]
+
+class SubRange (object):
+
+ __slots__ = ("l", "start", "stop",)
+
+ def __init__ (self, l, start, stop):
+
+ if start > stop:
+ raise ValueError ("need start <= stop (got %r, %r)" % (start, stop,))
+
+ self.l = l
+ self.start = start
+ self.stop = stop
+
+ def __getitem__ (self, i):
+
+ return self.l[i + self.start]
+
+ def __len__ (self):
+
+ return self.stop - self.start
+
+ def __iter__ (self):
+
+ l = self.l
+ for i in xrange (self.start, self.stop):
+ yield l[i]
+
+class RangeFilteredLogModel (FilteredLogModelBase):
+
+ def __init__ (self, super_model):
+
+ FilteredLogModelBase.__init__ (self, super_model)
+
+ self.logger = logging.getLogger ("range-filtered-model")
+
+ self.line_index_range = None
+
+ def set_range (self, start_index, stop_index):
+
+ self.logger.debug ("setting range to start = %i, stop = %i",
+ start_index, stop_index)
+
+ self.line_index_range = (start_index, stop_index,)
+ self.line_offsets = SubRange (self.super_model.line_offsets,
+ start_index, stop_index)
+ self.line_levels = SubRange (self.super_model.line_levels,
+ start_index, stop_index)
+
+ def reset (self):
+
+ self.logger.debug ("reset")
+
+ start_index = 0
+ stop_index = len (self.super_model)
+
+ self.set_range (start_index, stop_index,)
+
+ def line_index_to_super (self, line_index):
+
+ start_index = self.line_index_range[0]
+
+ return line_index + start_index
+
+ def line_index_from_super (self, li):
+
+ start, stop = self.line_index_range
+
+ if li < start or li >= stop:
+ raise IndexError ("not in range")
+
+ return li - start
+
+class LineViewLogModel (FilteredLogModelBase):
+
+ def __init__ (self, super_model):
+
+ FilteredLogModelBase.__init__ (self, super_model)
+
+ self.line_offsets = []
+ self.line_levels = []
+
+ self.parent_indices = []
+
+ def reset (self):
+
+ del self.line_offsets[:]
+ del self.line_levels[:]
+
+ def line_index_to_super (self, line_index):
+
+ return self.parent_indices[line_index]
+
+ def insert_line (self, position, super_line_index):
+
+ if position == -1:
+ position = len (self.line_offsets)
+ li = super_line_index
+ self.line_offsets.insert (position, self.super_model.line_offsets[li])
+ self.line_levels.insert (position, self.super_model.line_levels[li])
+ self.parent_indices.insert (position, super_line_index)
+
+ path = (position,)
+ tree_iter = self.get_iter (path)
+ self.row_inserted (path, tree_iter)
+
+ def replace_line (self, line_index, super_line_index):
+
+ li = line_index
+ self.line_offsets[li] = self.super_model.line_offsets[super_line_index]
+ self.line_levels[li] = self.super_model.line_levels[super_line_index]
+ self.parent_indices[li] = super_line_index
+
+ path = (line_index,)
+ tree_iter = self.get_iter (path)
+ self.row_changed (path, tree_iter)
+
+ def remove_line (self, line_index):
+
+ for l in (self.line_offsets,
+ self.line_levels,
+ self.parent_indices,):
+ del l[line_index]
+
+ path = (line_index,)
+ self.row_deleted (path)
+
diff --git a/debug-viewer/GstDebugViewer/GUI/window.py b/debug-viewer/GstDebugViewer/GUI/window.py
new file mode 100644
index 0000000000..b8320d935a
--- /dev/null
+++ b/debug-viewer/GstDebugViewer/GUI/window.py
@@ -0,0 +1,780 @@
+# -*- coding: utf-8; mode: python; -*-
+#
+# GStreamer Debug Viewer - View and analyze GStreamer debug log files
+#
+# Copyright (C) 2007 René Stadler
+#
+# 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 .
+
+"""GStreamer Debug Viewer GUI module."""
+
+def _ (s):
+ return s
+
+import os.path
+from bisect import bisect_right, bisect_left
+import logging
+
+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)
+
+class LineView (object):
+
+ def __init__ (self):
+
+ self.column_manager = LineViewColumnManager ()
+
+ def attach (self, window):
+
+ self.clear_action = window.actions.clear_line_view
+ handler = self.handle_clear_line_view_action_activate
+ self.clear_action.connect ("activate", handler)
+
+ 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):
+
+ model = self.line_view.props.model
+
+ 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):
+
+ self.line_view.props.model = LineViewLogModel (window.log_model)
+
+ def handle_line_view_row_activated (self, view, path, column):
+
+ line_index = path[0]
+ line_model = view.props.model
+ log_model = self.log_view.props.model
+ 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):
+
+ log_model = view.props.model
+ line_index = path[0]
+
+ top_line_index = log_model.line_index_to_top (line_index)
+ line_model = self.line_view.props.model
+ 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):
+
+ line_model = self.line_view.props.model
+ 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)
+
+ def handle_clear_line_view_action_activate (self, action):
+
+ self.clear ()
+
+class ProgressDialog (object):
+
+ def __init__ (self, window, title = ""):
+
+ widgets = window.widget_factory.make ("progress-dialog.ui", "progress_dialog")
+ dialog = widgets.progress_dialog
+ dialog.connect ("response", self.__handle_dialog_response)
+
+ self.__dialog = dialog
+ self.__progress_bar = widgets.progress_bar
+ self.__progress_bar.props.text = title
+
+ dialog.set_transient_for (window.gtk_window)
+ dialog.show ()
+
+ def __handle_dialog_response (self, dialog, resp):
+
+ 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
+
+ def destroy (self):
+
+ if self.__dialog is None:
+ return
+ self.__dialog.destroy ()
+ self.__dialog = None
+ self.__progress_bar = None
+
+class Window (object):
+
+ def __init__ (self, app):
+
+ self.logger = logging.getLogger ("ui.window")
+ self.app = app
+
+ self.dispatcher = None
+ 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"), "N"),
+ ("open-file", gtk.STOCK_OPEN, _("_Open File"), "O"),
+ ("reload-file", gtk.STOCK_REFRESH, _("_Reload File"), "R"),
+ ("close-window", gtk.STOCK_CLOSE, _("Close _Window"), "W"),
+ ("cancel-load", gtk.STOCK_CANCEL, None,),
+ ("clear-line-view", gtk.STOCK_CLEAR, None),
+ ("show-about", gtk.STOCK_ABOUT, None)])
+ self.actions.add_group (group)
+ self.actions.reload_file.props.sensitive = False
+
+ group = gtk.ActionGroup ("RowActions")
+ group.add_actions ([("hide-before-line", None, _("Hide lines before this one")),
+ ("hide-after-line", None, _("Hide lines after this one")),
+ ("show-hidden-lines", None, _("Show hidden lines")),
+ ("edit-copy-line", gtk.STOCK_COPY, _("Copy line"), "C"),
+ ("edit-copy-message", gtk.STOCK_COPY, _("Copy message"), ""),
+ ("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):
+
+ 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)
+
+ for action_name in ("new-window", "open-file", "reload-file",
+ "close-window", "cancel-load",
+ "hide-before-line", "hide-after-line", "show-hidden-lines",
+ "edit-copy-line", "edit-copy-message",
+ "hide-log-level", "hide-log-category", "hide-log-object",
+ "hide-filename", "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 (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)
+
+ 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")
+ model = self.log_view.props.model
+ 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
+
+ model = self.log_view.props.model
+ 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:
+ model = self.log_view.props.model
+
+ previous_model = self.log_view.props.model
+
+ if previous_model == model:
+ # Force update.
+ self.log_view.set_model (None)
+ self.log_view.props.model = model
+
+ def pop_view_state (self, scroll_to_selection = False):
+
+ model = self.log_view.props.model
+ 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)
+ except IndexError, exc:
+ 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
+ model = view.props.model
+
+ 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)
+ last_selected = (line_index == len (self.log_view.props.model) - 1)
+
+ 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 ()
+
+ def handle_new_window_action_activate (self, action):
+
+ self.app.open_window ()
+
+ 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 ()
+
+ def handle_reload_file_action_activate (self, action):
+
+ if self.log_file is None:
+ return
+
+ self.set_log_file (self.log_file.path)
+
+ def handle_cancel_load_action_activate (self, action):
+
+ self.logger.debug ("cancelling data load")
+
+ self.set_log_file (None)
+
+ if self.progress_dialog:
+ self.progress_dialog.destroy ()
+ self.progress_dialog = None
+ if self.update_progress_id is not None:
+ gobject.source_remove (self.update_progress_id)
+ self.update_progress_id = None
+
+ def handle_close_window_action_activate (self, action):
+
+ self.close ()
+
+ def handle_hide_after_line_action_activate (self, action):
+
+ self.hide_range (after = True)
+
+ def handle_hide_before_line_action_activate (self, action):
+
+ self.hide_range (after = False)
+
+ def hide_range (self, after):
+
+ model = self.log_view.props.model
+ 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
+
+ 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
+
+ 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 ())
+
+ def handle_edit_copy_message_action_activate (self, action):
+
+ col_id = LogModelBase.COL_MESSAGE
+ self.clipboard.set_text (self.get_active_line ()[col_id])
+
+ def add_model_filter (self, filter):
+
+ self.progress_dialog = ProgressDialog (self, _("Filtering"))
+ 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):
+
+ self.progress_dialog.destroy ()
+ self.progress_dialog = None
+
+ self.log_filter.abort_process ()
+ self.log_view.props.model = self.log_filter
+ self.pop_view_state ()
+
+ def handle_log_filter_process_finished (self):
+
+ self.progress_dialog.destroy ()
+ 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
+
+ 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))
+
+ 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))
+
+ 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_))
+
+ def handle_hide_filename_action_activate (self, action):
+
+ row = self.get_active_line ()
+ filename = row[LogModelBase.COL_FILENAME]
+ self.add_model_filter (FilenameFilter (filename))
+
+ 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)
+ except EnvironmentError, exc:
+ 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):
+
+ dialog = gtk.MessageDialog (self.gtk_window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR,
+ gtk.BUTTONS_OK, message1)
+ # The property for secondary text is new in 2.10, so we use this clunky
+ # method instead.
+ dialog.format_secondary_text (message2)
+ dialog.set_default_response (0)
+ dialog.run ()
+ dialog.destroy ()
+
+ def handle_load_started (self):
+
+ self.logger.debug ("load has started")
+
+ self.progress_dialog = ProgressDialog (self, _("Loading log file"))
+ 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")
+
+ self.progress_dialog.destroy ()
+ 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
+
+ def idle_set ():
+ self.log_view.props.model = self.log_range
+ 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)
diff --git a/debug-viewer/GstDebugViewer/Plugins/FindBar.py b/debug-viewer/GstDebugViewer/Plugins/FindBar.py
index 69ec9455db..fe913c4acd 100644
--- a/debug-viewer/GstDebugViewer/Plugins/FindBar.py
+++ b/debug-viewer/GstDebugViewer/Plugins/FindBar.py
@@ -36,7 +36,7 @@ class SearchOperation (object):
self.search_forward = search_forward
self.start_position = start_position
- col_id = GUI.LogModelBase.COL_MESSAGE
+ col_id = GUI.models.LogModelBase.COL_MESSAGE
len_search_text = len (search_text)
def match_func (model_row):
diff --git a/debug-viewer/GstDebugViewer/Plugins/Timeline.py b/debug-viewer/GstDebugViewer/Plugins/Timeline.py
index c4e5c377b4..f95a8d9f81 100644
--- a/debug-viewer/GstDebugViewer/Plugins/Timeline.py
+++ b/debug-viewer/GstDebugViewer/Plugins/Timeline.py
@@ -21,7 +21,8 @@
import logging
-from GstDebugViewer import Common, Data, GUI
+from GstDebugViewer import Common, Data
+from GstDebugViewer.GUI.colors import LevelColorThemeTango, ThreadColorThemeTango
from GstDebugViewer.Plugins import *
import gobject
@@ -264,7 +265,7 @@ class VerticalTimelineWidget (gtk.DrawingArea):
self.logger = logging.getLogger ("ui.vtimeline")
self.log_view = log_view
- self.theme = GUI.ThreadColorThemeTango ()
+ self.theme = ThreadColorThemeTango ()
self.params = None
self.thread_colors = {}
self.next_thread_color = 0
@@ -558,7 +559,7 @@ class TimelineWidget (gtk.DrawingArea):
self.logger.debug ("level distribution sentinel has no data yet")
return
- colors = GUI.LevelColorThemeTango ().colors
+ colors = LevelColorThemeTango ().colors
dist_data = self.process.dist_sentinel.data
def cumulative_level_counts (*levels):