1 # -*- coding: utf-8; mode: python; -*-
3 # GStreamer Debug Viewer - View and analyze GStreamer debug log files
5 # Copyright (C) 2007 René Stadler <mail@renestadler.de>
7 # This program is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by the Free
9 # Software Foundation; either version 3 of the License, or (at your option)
12 # This program is distributed in the hope that it will be useful, but WITHOUT
13 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17 # You should have received a copy of the GNU General Public License along with
18 # this program. If not, see <http://www.gnu.org/licenses/>.
20 """GStreamer Debug Viewer GUI module."""
29 from GstDebugViewer import Common, Data
30 from GstDebugViewer.GUI.colors import LevelColorThemeTango
31 from GstDebugViewer.GUI.models import LazyLogModel, LogModelBase
33 # Sync with gst-inspector!
34 class Column (object):
36 """A single list view column, managed by a ColumnManager instance."""
41 get_modify_func = None
43 get_row_data_func = None
48 view_column = gtk.TreeViewColumn (self.label_header)
49 view_column.props.reorderable = True
51 self.view_column = view_column
53 class SizedColumn (Column):
57 def compute_default_size (self):
61 # Sync with gst-inspector?
62 class TextColumn (SizedColumn):
68 Column.__init__ (self)
70 column = self.view_column
71 cell = gtk.CellRendererText ()
72 column.pack_start (cell)
74 cell.props.yalign = 0.
78 cell.props.family = self.font_family
79 cell.props.family_set = True
81 if self.get_data_func:
82 data_func = self.get_data_func ()
86 def cell_data_func (column, cell, model, tree_iter):
87 data_func (cell.props, model.get_value (tree_iter, id_), model.get_path (tree_iter))
89 cell_data_func = data_func
90 column.set_cell_data_func (cell, cell_data_func)
91 elif self.get_row_data_func:
92 data_func = self.get_row_data_func ()
94 def cell_data_func (column, cell, model, tree_iter):
95 data_func (cell.props, model[tree_iter])
96 column.set_cell_data_func (cell, cell_data_func)
97 elif not self.get_modify_func:
98 column.add_attribute (cell, "text", self.id)
100 self.update_modify_func (column, cell)
102 column.props.resizable = True
104 def update_modify_func (self, column, cell):
106 modify_func = self.get_modify_func ()
108 def cell_data_func (column, cell, model, tree_iter):
109 cell.props.text = modify_func (model.get (tree_iter, id_)[0])
110 column.set_cell_data_func (cell, cell_data_func)
112 def compute_default_size (self):
114 values = self.get_values_for_size ()
116 return SizedColumn.compute_default_size (self)
118 cell = self.view_column.get_cell_renderers ()[0]
120 if self.get_modify_func is not None:
121 format = self.get_modify_func ()
128 cell.props.text = format (value)
129 rect, x, y, w, h = self.view_column.cell_get_size ()
130 max_width = max (max_width, w)
134 def get_values_for_size (self):
138 class TimeColumn (TextColumn):
141 label_header = _("Time")
142 id = LazyLogModel.COL_TIME
143 font_family = "monospace"
145 def __init__ (self, *a, **kw):
149 TextColumn.__init__ (self, *a, **kw)
151 def get_modify_func (self):
154 time_diff_args = Data.time_diff_args
155 base_time = self.base_time
156 def format_time (value):
157 # TODO: Hard coded to omit trailing zeroes, see below.
158 return time_diff_args (value - base_time)[:-3]
160 time_args = Data.time_args
161 def format_time (value):
162 # TODO: This is hard coded to omit hours as well as the last 3
163 # digits at the end, since current gst uses g_get_current_time,
164 # which has microsecond precision only.
165 return time_args (value)[2:-3]
169 def get_values_for_size (self):
175 def set_base_time (self, base_time):
177 self.base_time = base_time
179 column = self.view_column
180 cell = column.get_cell_renderers ()[0]
181 self.update_modify_func (column, cell)
183 class LevelColumn (TextColumn):
186 label_header = _("L")
187 id = LazyLogModel.COL_LEVEL
191 TextColumn.__init__ (self)
193 cell = self.view_column.get_cell_renderers ()[0]
194 cell.props.xalign = .5
197 def get_modify_func ():
199 def format_level (value):
205 def get_data_func ():
207 theme = LevelColorThemeTango ()
208 colors = dict ((level, tuple ((c.gdk_color ()
209 for c in theme.colors[level])),)
210 for level in Data.debug_levels
211 if level != Data.debug_level_none)
212 def level_data_func (cell_props, level, path):
213 cell_props.text = level.name[0]
215 cell_colors = colors[level]
217 cell_colors = (None, None, None,)
218 cell_props.foreground_gdk = cell_colors[0]
219 cell_props.background_gdk = cell_colors[1]
221 return level_data_func
223 def get_values_for_size (self):
225 values = [Data.debug_level_log, Data.debug_level_debug,
226 Data.debug_level_info, Data.debug_level_warning,
227 Data.debug_level_error]
231 class PidColumn (TextColumn):
234 label_header = _("PID")
235 id = LazyLogModel.COL_PID
236 font_family = "monospace"
239 def get_modify_func ():
243 def get_values_for_size (self):
247 class ThreadColumn (TextColumn):
250 label_header = _("Thread")
251 id = LazyLogModel.COL_THREAD
252 font_family = "monospace"
255 def get_modify_func ():
257 def format_thread (value):
258 return "0x%07x" % (value,)
262 def get_values_for_size (self):
264 return [int ("ffffff", 16)]
266 class CategoryColumn (TextColumn):
269 label_header = _("Category")
270 id = LazyLogModel.COL_CATEGORY
272 def get_values_for_size (self):
274 return ["GST_LONG_CATEGORY", "somelongelement"]
276 class CodeColumn (TextColumn):
279 label_header = _("Code")
283 def get_data_func ():
285 filename_id = LogModelBase.COL_FILENAME
286 line_number_id = LogModelBase.COL_LINE_NUMBER
287 def filename_data_func (column, cell, model, tree_iter):
288 args = model.get (tree_iter, filename_id, line_number_id)
289 cell.props.text = "%s:%i" % args
291 return filename_data_func
293 def get_values_for_size (self):
295 return ["gstsomefilename.c:1234"]
297 class FunctionColumn (TextColumn):
300 label_header = _("Function")
301 id = LazyLogModel.COL_FUNCTION
303 def get_values_for_size (self):
305 return ["gst_this_should_be_enough"]
307 class ObjectColumn (TextColumn):
310 label_header = _("Object")
311 id = LazyLogModel.COL_OBJECT
313 def get_values_for_size (self):
315 return ["longobjectname00"]
317 class MessageColumn (TextColumn):
320 label_header = _("Message")
321 id = LazyLogModel.COL_MESSAGE
323 def __init__ (self, *a, **kw):
325 self.highlighters = {}
327 TextColumn.__init__ (self, *a, **kw)
329 def get_row_data_func (self):
331 from pango import AttrList, AttrBackground, AttrForeground
332 highlighters = self.highlighters
335 # FIXME: This should be none; need to investigate
336 # `cellrenderertext.props.attributes = None' failure (param conversion
337 # error like `treeview.props.model = None').
338 no_attrs = AttrList ()
340 def message_data_func (props, row):
342 props.text = row[id_]
344 props.attributes = no_attrs
345 for highlighter in highlighters.values ():
346 ranges = highlighter (row)
348 props.attributes = no_attrs
350 attrlist = AttrList ()
351 for start, end in ranges:
352 attrlist.insert (AttrBackground (0, 0, 65535, start, end))
353 attrlist.insert (AttrForeground (65535, 65535, 65535, start, end))
354 props.attributes = attrlist
356 return message_data_func
358 def get_values_for_size (self):
360 values = ["Just some good minimum size"]
364 class ColumnManager (Common.GUI.Manager):
369 def iter_item_classes (cls):
371 return iter (cls.column_classes)
378 self.__columns_changed_id = None
380 self.column_order = list (self.column_classes)
382 self.action_group = gtk.ActionGroup ("ColumnActions")
384 def make_entry (col_class):
385 return ("show-%s-column" % (col_class.name,),
387 col_class.label_header,
393 entries = [make_entry (cls) for cls in self.column_classes]
394 self.action_group.add_toggle_actions (entries)
396 def iter_items (self):
398 return iter (self.columns)
402 for col_class in self.column_classes:
403 action = self.get_toggle_action (col_class)
404 if action.props.active:
405 self._add_column (col_class ())
406 action.connect ("toggled",
407 self.__handle_show_column_action_toggled,
410 self.__columns_changed_id = self.view.connect ("columns-changed",
411 self.__handle_view_columns_changed)
415 if self.__columns_changed_id is not None:
416 self.view.disconnect (self.__columns_changed_id)
417 self.__columns_changed_id = None
419 def attach_sort (self):
421 sort_model = self.view.get_model ()
423 # Inform the sorted tree model of any custom sorting functions.
424 for col_class in self.column_classes:
425 if col_class.get_sort_func:
426 sort_func = col_class.get_sort_func ()
427 sort_model.set_sort_func (col_class.id, sort_func)
429 def enable_sort (self):
431 sort_model = self.view.get_model ()
434 self.logger.debug ("activating sort")
435 sort_model.set_sort_column_id (*self.default_sort)
436 self.default_sort = None
438 self.logger.debug ("not activating sort (no model set)")
440 def disable_sort (self):
442 self.logger.debug ("deactivating sort")
444 sort_model = self.view.get_model ()
446 self.default_sort = tree_sortable_get_sort_column_id (sort_model)
448 sort_model.set_sort_column_id (TREE_SORTABLE_UNSORTED_COLUMN_ID,
451 def set_zoom (self, scale):
453 for column in self.columns:
454 cell = column.view_column.get_cell_renderers ()[0]
455 cell.props.scale = scale
456 column.view_column.queue_resize ()
460 def get_toggle_action (self, column_class):
462 action_name = "show-%s-column" % (column_class.name,)
463 return self.action_group.get_action (action_name)
465 def get_initial_column_order (self):
467 return tuple (self.column_classes)
469 def _add_column (self, column):
472 pos = self.__get_column_insert_position (column)
474 if self.view.props.fixed_height_mode:
475 column.view_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
477 cell = column.view_column.get_cell_renderers ()[0]
478 cell.props.scale = self.zoom
480 self.columns.insert (pos, column)
481 self.view.insert_column (column.view_column, pos)
483 def _remove_column (self, column):
485 self.columns.remove (column)
486 self.view.remove_column (column.view_column)
488 def __get_column_insert_position (self, column):
490 col_class = self.find_item_class (name = column.name)
491 pos = self.column_order.index (col_class)
492 before = self.column_order[:pos]
493 shown_names = [col.name for col in self.columns]
494 for col_class in before:
495 if not col_class.name in shown_names:
499 def __iter_next_hidden (self, column_class):
501 pos = self.column_order.index (column_class)
502 rest = self.column_order[pos + 1:]
503 for next_class in rest:
505 self.find_item (name = next_class.name)
507 # No instance -- the column is hidden.
512 def __handle_show_column_action_toggled (self, toggle_action, name):
514 if toggle_action.props.active:
517 column = self.find_item (name = name)
519 col_class = self.find_item_class (name = name)
520 self._add_column (col_class ())
522 # Out of sync for some reason.
526 column = self.find_item (name = name)
528 # Out of sync for some reason.
531 self._remove_column (column)
533 def __handle_view_columns_changed (self, element_view):
535 view_columns = element_view.get_columns ()
536 new_visible = [self.find_item (view_column = column)
537 for column in view_columns]
539 # We only care about reordering here.
540 if len (new_visible) != len (self.columns):
543 if new_visible != self.columns:
546 for column in new_visible:
547 col_class = self.find_item_class (name = column.name)
548 new_order.append (col_class)
549 new_order.extend (self.__iter_next_hidden (col_class))
551 names = (column.name for column in new_visible)
552 self.logger.debug ("visible columns reordered: %s",
555 self.columns[:] = new_visible
556 self.column_order[:] = new_order
558 class ViewColumnManager (ColumnManager):
560 column_classes = (TimeColumn, LevelColumn, PidColumn, ThreadColumn, CategoryColumn,
561 CodeColumn, FunctionColumn, ObjectColumn, MessageColumn,)
563 def __init__ (self, state):
565 ColumnManager.__init__ (self)
567 self.logger = logging.getLogger ("ui.columns")
571 def attach (self, view):
574 view.connect ("notify::model", self.__handle_notify_model)
576 order = self.state.column_order
577 if len (order) == len (self.column_classes):
578 self.column_order[:] = order
580 visible = self.state.columns_visible
582 visible = self.column_classes
583 for col_class in self.column_classes:
584 action = self.get_toggle_action (col_class)
585 action.props.active = (col_class in visible)
587 ColumnManager.attach (self)
589 self.columns_sized = False
593 self.state.column_order = self.column_order
594 self.state.columns_visible = self.columns
596 return ColumnManager.detach (self)
598 def set_zoom (self, scale):
600 ColumnManager.set_zoom (self, scale)
602 if self.view is None:
605 # Timestamp and log level columns are pretty much fixed size, so resize
606 # them back to default on zoom change:
607 for column in self.columns:
608 if column.name in (TimeColumn.name,
610 self.size_column (column)
612 def size_column (self, column):
614 if column.default_size is None:
615 default_size = column.compute_default_size ()
617 default_size = column.default_size
618 # FIXME: Abstract away fixed size setting in Column class!
619 if default_size is None:
621 column.view_column.props.fixed_width = 50
622 self.logger.warning ("%s column does not implement default size", column.name)
624 column.view_column.props.fixed_width = default_size
626 def _add_column (self, column):
628 result = ColumnManager._add_column (self, column)
629 self.size_column (column)
632 def _remove_column (self, column):
634 column.default_size = column.view_column.props.fixed_width
635 return ColumnManager._remove_column (self, column)
637 def __handle_notify_model (self, view, gparam):
639 if self.columns_sized:
642 model = self.view.get_model ()
645 self.logger.debug ("model changed, sizing columns")
646 for column in self.iter_items ():
647 self.size_column (column)
648 self.columns_sized = True
650 class WrappingMessageColumn (MessageColumn):
652 def wrap_to_width (self, width):
654 col = self.view_column
655 col.props.max_width = width
656 col.get_cell_renderers ()[0].props.wrap_width = width
659 class LineViewColumnManager (ColumnManager):
661 column_classes = (TimeColumn, WrappingMessageColumn,)
665 ColumnManager.__init__ (self)
667 def attach (self, window):
669 self.__size_update = None
671 self.view = window.widgets.line_view
672 self.view.set_size_request (0, 0)
673 self.view.connect_after ("size-allocate", self.__handle_size_allocate)
674 ColumnManager.attach (self)
676 def __update_sizes (self):
678 view_width = self.view.get_allocation ().width
679 if view_width == self.__size_update:
680 # Prevent endless recursion.
683 self.__size_update = view_width
685 col = self.find_item (name = "time")
686 other_width = col.view_column.props.width
689 col = self.find_item (name = "message")
693 width = view_width - other_width
694 col.wrap_to_width (width)
696 def __handle_size_allocate (self, self_, allocation):
698 self.__update_sizes ()