mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-25 03:01:03 +00:00
Don't make timeline data processing block the GUI
This commit is contained in:
parent
5a0fff722b
commit
7cda31f6c7
2 changed files with 144 additions and 60 deletions
|
@ -30,6 +30,10 @@ class Dispatcher (object):
|
||||||
|
|
||||||
raise NotImplementedError ("derived classes must override this method")
|
raise NotImplementedError ("derived classes must override this method")
|
||||||
|
|
||||||
|
def cancel (self):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
class DefaultDispatcher (Dispatcher):
|
class DefaultDispatcher (Dispatcher):
|
||||||
|
|
||||||
def __call__ (self, iterator):
|
def __call__ (self, iterator):
|
||||||
|
@ -51,3 +55,11 @@ class GSourceDispatcher (Dispatcher):
|
||||||
gobject.source_remove (self.source_id)
|
gobject.source_remove (self.source_id)
|
||||||
|
|
||||||
self.source_id = gobject.idle_add (iterator.next)
|
self.source_id = gobject.idle_add (iterator.next)
|
||||||
|
|
||||||
|
def cancel (self):
|
||||||
|
|
||||||
|
if self.source_id is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
gobject.source_remove (self.source_id)
|
||||||
|
self.source_id = None
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from GstDebugViewer import Data, GUI
|
from GstDebugViewer import Common, Data, GUI
|
||||||
from GstDebugViewer.Plugins import *
|
from GstDebugViewer.Plugins import *
|
||||||
|
|
||||||
import cairo
|
import cairo
|
||||||
|
@ -18,7 +18,12 @@ class LineFrequencySentinel (object):
|
||||||
def __init__ (self, model):
|
def __init__ (self, model):
|
||||||
|
|
||||||
self.model = model
|
self.model = model
|
||||||
|
self.clear ()
|
||||||
|
|
||||||
|
def clear (self):
|
||||||
|
|
||||||
self.data = None
|
self.data = None
|
||||||
|
self.n_partitions = None
|
||||||
self.partitions = None
|
self.partitions = None
|
||||||
self.step = None
|
self.step = None
|
||||||
self.ts_range = (None, None,)
|
self.ts_range = (None, None,)
|
||||||
|
@ -46,6 +51,10 @@ class LineFrequencySentinel (object):
|
||||||
if n == 0:
|
if n == 0:
|
||||||
raise ValueError ("illegal value for n")
|
raise ValueError ("illegal value for n")
|
||||||
|
|
||||||
|
self.n_partitions = n
|
||||||
|
|
||||||
|
def process (self):
|
||||||
|
|
||||||
model = self.model
|
model = self.model
|
||||||
result = []
|
result = []
|
||||||
partitions = []
|
partitions = []
|
||||||
|
@ -71,12 +80,19 @@ class LineFrequencySentinel (object):
|
||||||
if last_ts is None:
|
if last_ts is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
step = int (float (last_ts - first_ts) / float (n))
|
step = int (float (last_ts - first_ts) / float (self.n_partitions))
|
||||||
|
|
||||||
|
YIELD_LIMIT = 100
|
||||||
|
limit = YIELD_LIMIT
|
||||||
|
|
||||||
first_index = 0
|
first_index = 0
|
||||||
target_ts = first_ts + step
|
target_ts = first_ts + step
|
||||||
old_found = 0
|
old_found = 0
|
||||||
while target_ts < last_ts:
|
while target_ts < last_ts:
|
||||||
|
limit -= 1
|
||||||
|
if limit == 0:
|
||||||
|
limit = YIELD_LIMIT
|
||||||
|
yield True
|
||||||
found = self._search_ts (target_ts, first_index, last_index)
|
found = self._search_ts (target_ts, first_index, last_index)
|
||||||
result.append (found - old_found)
|
result.append (found - old_found)
|
||||||
partitions.append (found)
|
partitions.append (found)
|
||||||
|
@ -91,12 +107,20 @@ class LineFrequencySentinel (object):
|
||||||
|
|
||||||
class LevelDistributionSentinel (object):
|
class LevelDistributionSentinel (object):
|
||||||
|
|
||||||
def __init__ (self, model):
|
def __init__ (self, freq_sentinel, model):
|
||||||
|
|
||||||
|
self.freq_sentinel = freq_sentinel
|
||||||
self.model = model
|
self.model = model
|
||||||
self.data = []
|
self.data = []
|
||||||
|
|
||||||
def run_for (self, freq_sentinel):
|
def clear (self):
|
||||||
|
|
||||||
|
del self.data[:]
|
||||||
|
|
||||||
|
def process (self):
|
||||||
|
|
||||||
|
YIELD_LIMIT = 10000
|
||||||
|
y = YIELD_LIMIT
|
||||||
|
|
||||||
model_get = self.model.get_value
|
model_get = self.model.get_value
|
||||||
model_next = self.model.iter_next
|
model_next = self.model.iter_next
|
||||||
|
@ -105,10 +129,14 @@ class LevelDistributionSentinel (object):
|
||||||
result = []
|
result = []
|
||||||
i = 0
|
i = 0
|
||||||
partitions_i = 0
|
partitions_i = 0
|
||||||
partitions = freq_sentinel.partitions
|
partitions = self.freq_sentinel.partitions
|
||||||
counts = [0] * 6
|
counts = [0] * 6
|
||||||
tree_iter = self.model.get_iter_first ()
|
tree_iter = self.model.get_iter_first ()
|
||||||
while tree_iter:
|
while tree_iter:
|
||||||
|
y -= 1
|
||||||
|
if y == 0:
|
||||||
|
y = YIELD_LIMIT
|
||||||
|
yield True
|
||||||
level = model_get (tree_iter, id_level)
|
level = model_get (tree_iter, id_level)
|
||||||
if i > partitions[partitions_i]:
|
if i > partitions[partitions_i]:
|
||||||
result.append (tuple (counts))
|
result.append (tuple (counts))
|
||||||
|
@ -125,26 +153,87 @@ class LevelDistributionSentinel (object):
|
||||||
|
|
||||||
self.data = result
|
self.data = result
|
||||||
|
|
||||||
|
yield False
|
||||||
|
|
||||||
|
class UpdateProcess (object):
|
||||||
|
|
||||||
|
def __init__ (self, freq_sentinel, dist_sentinel):
|
||||||
|
|
||||||
|
self.freq_sentinel = freq_sentinel
|
||||||
|
self.dist_sentinel = dist_sentinel
|
||||||
|
self.is_running = False
|
||||||
|
self.dispatcher = Common.Data.GSourceDispatcher ()
|
||||||
|
|
||||||
|
def __process (self):
|
||||||
|
|
||||||
|
self.is_running = True
|
||||||
|
|
||||||
|
for x in self.freq_sentinel.process ():
|
||||||
|
yield True
|
||||||
|
|
||||||
|
self.handle_sentinel_finished (self.freq_sentinel)
|
||||||
|
|
||||||
|
for x in self.dist_sentinel.process ():
|
||||||
|
yield True
|
||||||
|
|
||||||
|
self.is_running = False
|
||||||
|
|
||||||
|
self.handle_sentinel_finished (self.dist_sentinel)
|
||||||
|
self.handle_process_finished ()
|
||||||
|
|
||||||
|
yield False
|
||||||
|
|
||||||
|
def run (self):
|
||||||
|
|
||||||
|
if self.is_running:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.dispatcher (self.__process ())
|
||||||
|
|
||||||
|
def abort (self):
|
||||||
|
|
||||||
|
if not self.is_running:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.dispatcher.cancel ()
|
||||||
|
self.is_running = False
|
||||||
|
|
||||||
|
def handle_sentinel_finished (self, sentinel):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle_process_finished (self):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
class TimelineWidget (gtk.DrawingArea):
|
class TimelineWidget (gtk.DrawingArea):
|
||||||
|
|
||||||
__gtype_name__ = "GstDebugViewerTimelineWidget"
|
__gtype_name__ = "GstDebugViewerTimelineWidget"
|
||||||
|
|
||||||
def __init__ (self, sentinel = None):
|
def __init__ (self, log_model):
|
||||||
|
|
||||||
gtk.DrawingArea.__init__ (self)
|
gtk.DrawingArea.__init__ (self)
|
||||||
|
|
||||||
self.logger = logging.getLogger ("ui.timeline")
|
self.logger = logging.getLogger ("ui.timeline")
|
||||||
|
|
||||||
self.sentinel = sentinel
|
self.freq_sentinel = LineFrequencySentinel (log_model)
|
||||||
self.level_dist_sentinel = None
|
self.dist_sentinel = LevelDistributionSentinel (self.freq_sentinel, log_model)
|
||||||
|
self.process = UpdateProcess (self.freq_sentinel, self.dist_sentinel)
|
||||||
self.connect ("expose-event", self.__handle_expose_event)
|
self.connect ("expose-event", self.__handle_expose_event)
|
||||||
self.connect ("configure-event", self.__handle_configure_event)
|
self.connect ("configure-event", self.__handle_configure_event)
|
||||||
self.connect ("size-request", self.__handle_size_request)
|
self.connect ("size-request", self.__handle_size_request)
|
||||||
|
self.process.handle_sentinel_finished = self.handle_sentinel_finished
|
||||||
|
self.process.handle_process_finished = self.handle_process_finished
|
||||||
|
|
||||||
def set_sentinel (self, sentinel):
|
self.__offscreen = None
|
||||||
|
|
||||||
|
def handle_sentinel_finished (self, sentinel):
|
||||||
|
|
||||||
|
if sentinel == self.freq_sentinel:
|
||||||
|
self.__redraw ()
|
||||||
|
|
||||||
|
def handle_process_finished (self):
|
||||||
|
|
||||||
self.sentinel = sentinel
|
|
||||||
self.level_dist_sentinel = LevelDistributionSentinel (sentinel.model)
|
|
||||||
self.__redraw ()
|
self.__redraw ()
|
||||||
|
|
||||||
def __redraw (self):
|
def __redraw (self):
|
||||||
|
@ -153,9 +242,9 @@ class TimelineWidget (gtk.DrawingArea):
|
||||||
return
|
return
|
||||||
|
|
||||||
x, y, w, h = self.get_allocation ()
|
x, y, w, h = self.get_allocation ()
|
||||||
self.offscreen = gtk.gdk.Pixmap (self.window, w, h, -1)
|
self.__offscreen = gtk.gdk.Pixmap (self.window, w, h, -1)
|
||||||
|
|
||||||
self.__draw (self.offscreen)
|
self.__draw (self.__offscreen)
|
||||||
|
|
||||||
self.__update ()
|
self.__update ()
|
||||||
|
|
||||||
|
@ -164,17 +253,24 @@ class TimelineWidget (gtk.DrawingArea):
|
||||||
if not self.props.visible:
|
if not self.props.visible:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.__offscreen is None:
|
||||||
|
self.__redraw ()
|
||||||
|
|
||||||
gc = gtk.gdk.GC (self.window)
|
gc = gtk.gdk.GC (self.window)
|
||||||
self.window.draw_drawable (gc, self.offscreen, 0, 0, 0, 0, -1, -1)
|
self.window.draw_drawable (gc, self.__offscreen, 0, 0, 0, 0, -1, -1)
|
||||||
|
|
||||||
def update_position (self, start_ts, end_ts):
|
def update_position (self, start_ts, end_ts):
|
||||||
|
|
||||||
|
if not self.freq_sentinel.data:
|
||||||
|
return
|
||||||
|
|
||||||
self.__update ()
|
self.__update ()
|
||||||
|
|
||||||
first_ts, last_ts = self.sentinel.ts_range
|
first_ts, last_ts = self.freq_sentinel.ts_range
|
||||||
|
step = self.freq_sentinel.step
|
||||||
|
|
||||||
position1 = int (float (start_ts - first_ts) / self.sentinel.step)
|
position1 = int (float (start_ts - first_ts) / step)
|
||||||
position2 = int (float (end_ts - first_ts) / self.sentinel.step)
|
position2 = int (float (end_ts - first_ts) / step)
|
||||||
|
|
||||||
ctx = self.window.cairo_create ()
|
ctx = self.window.cairo_create ()
|
||||||
x, y, w, h = self.get_allocation ()
|
x, y, w, h = self.get_allocation ()
|
||||||
|
@ -194,8 +290,8 @@ class TimelineWidget (gtk.DrawingArea):
|
||||||
def find_indicative_time_step (self):
|
def find_indicative_time_step (self):
|
||||||
|
|
||||||
MINIMUM_PIXEL_STEP = 32
|
MINIMUM_PIXEL_STEP = 32
|
||||||
time_per_pixel = self.sentinel.step
|
time_per_pixel = self.freq_sentinel.step
|
||||||
return 32 # FIXME use self.sentinel.step and len (self.sentinel.data)
|
return 32 # FIXME use self.freq_sentinel.step and len (self.freq_sentinel.data)
|
||||||
|
|
||||||
def __draw (self, drawable):
|
def __draw (self, drawable):
|
||||||
|
|
||||||
|
@ -223,25 +319,21 @@ class TimelineWidget (gtk.DrawingArea):
|
||||||
ctx.line_to (x, h)
|
ctx.line_to (x, h)
|
||||||
ctx.stroke ()
|
ctx.stroke ()
|
||||||
|
|
||||||
if self.sentinel and not self.sentinel.data:
|
if not self.freq_sentinel.data:
|
||||||
if w > 15:
|
self.logger.debug ("frequency sentinel has no data yet")
|
||||||
self.logger.debug ("running sentinel for width %i", w)
|
|
||||||
self.__update_sentinel_data (w)
|
|
||||||
else:
|
|
||||||
self.logger.debug ("not running sentinel: widget width too small (%i)", w)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.sentinel is None:
|
maximum = max (self.freq_sentinel.data)
|
||||||
self.logger.debug ("not redrawing: no sentinel set")
|
|
||||||
return
|
|
||||||
|
|
||||||
maximum = max (self.sentinel.data)
|
|
||||||
|
|
||||||
ctx.set_source_rgb (0., 0., 0.)
|
ctx.set_source_rgb (0., 0., 0.)
|
||||||
self.__draw_graph (ctx, w, h, maximum, self.sentinel.data)
|
self.__draw_graph (ctx, w, h, maximum, self.freq_sentinel.data)
|
||||||
|
|
||||||
|
if not self.dist_sentinel.data:
|
||||||
|
self.logger.debug ("level distribution sentinel has no data yet")
|
||||||
|
return
|
||||||
|
|
||||||
theme = GUI.LevelColorThemeTango ()
|
theme = GUI.LevelColorThemeTango ()
|
||||||
dist_data = self.level_dist_sentinel.data
|
dist_data = self.dist_sentinel.data
|
||||||
|
|
||||||
def cumulative_level_counts (*levels):
|
def cumulative_level_counts (*levels):
|
||||||
for level_counts in dist_data:
|
for level_counts in dist_data:
|
||||||
|
@ -298,16 +390,6 @@ class TimelineWidget (gtk.DrawingArea):
|
||||||
self.__redraw ()
|
self.__redraw ()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __update_sentinel_data (self, width):
|
|
||||||
|
|
||||||
self.logger.debug ("updating sentinel data for width %i", width)
|
|
||||||
self.sentinel.run_for (width)
|
|
||||||
self.logger.debug ("updating level distribution data")
|
|
||||||
self.level_dist_sentinel.run_for (self.sentinel)
|
|
||||||
if not self.level_dist_sentinel.data:
|
|
||||||
self.logger.warning ("no level distribution data sensed")
|
|
||||||
self.logger.debug ("sentinel update complete")
|
|
||||||
|
|
||||||
def __handle_configure_event (self, self_, event):
|
def __handle_configure_event (self, self_, event):
|
||||||
|
|
||||||
self.logger.debug ("widget size configured to %ix%i",
|
self.logger.debug ("widget size configured to %ix%i",
|
||||||
|
@ -316,8 +398,11 @@ class TimelineWidget (gtk.DrawingArea):
|
||||||
if event.width < 16:
|
if event.width < 16:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.sentinel:
|
self.process.abort ()
|
||||||
self.__update_sentinel_data (event.width)
|
self.freq_sentinel.clear ()
|
||||||
|
self.dist_sentinel.clear ()
|
||||||
|
self.freq_sentinel.run_for (event.width)
|
||||||
|
self.process.run ()
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -353,7 +438,7 @@ class TimelineFeature (FeatureBase):
|
||||||
|
|
||||||
box = window.get_top_attach_point ()
|
box = window.get_top_attach_point ()
|
||||||
|
|
||||||
self.timeline = TimelineWidget ()
|
self.timeline = TimelineWidget (self.log_model)
|
||||||
self.timeline.add_events (gtk.gdk.ALL_EVENTS_MASK) # FIXME
|
self.timeline.add_events (gtk.gdk.ALL_EVENTS_MASK) # FIXME
|
||||||
self.timeline.connect ("button-press-event", self.handle_timeline_button_press_event)
|
self.timeline.connect ("button-press-event", self.handle_timeline_button_press_event)
|
||||||
self.timeline.connect ("motion-notify-event", self.handle_timeline_motion_notify_event)
|
self.timeline.connect ("motion-notify-event", self.handle_timeline_motion_notify_event)
|
||||||
|
@ -366,12 +451,8 @@ class TimelineFeature (FeatureBase):
|
||||||
handler = self.handle_show_action_toggled
|
handler = self.handle_show_action_toggled
|
||||||
self.action_group.get_action ("show-timeline").connect ("toggled", handler)
|
self.action_group.get_action ("show-timeline").connect ("toggled", handler)
|
||||||
|
|
||||||
window.sentinels.append (self.sentinel_process)
|
|
||||||
|
|
||||||
def detach (self, window):
|
def detach (self, window):
|
||||||
|
|
||||||
window.sentinels.remove (self.sentinel_process)
|
|
||||||
|
|
||||||
window.ui_manager.remove_ui (self.merge_id)
|
window.ui_manager.remove_ui (self.merge_id)
|
||||||
self.merge_id = None
|
self.merge_id = None
|
||||||
|
|
||||||
|
@ -380,12 +461,6 @@ class TimelineFeature (FeatureBase):
|
||||||
self.timeline.destroy ()
|
self.timeline.destroy ()
|
||||||
self.timeline = None
|
self.timeline = None
|
||||||
|
|
||||||
def sentinel_process (self):
|
|
||||||
|
|
||||||
if self.action_group.get_action ("show-timeline").props.active:
|
|
||||||
sentinel = LineDensitySentinel (self.log_model)
|
|
||||||
self.timeline.set_sentinel (sentinel)
|
|
||||||
|
|
||||||
def handle_log_view_adjustment_value_changed (self, adj):
|
def handle_log_view_adjustment_value_changed (self, adj):
|
||||||
|
|
||||||
# FIXME: If not visible, disconnect this handler!
|
# FIXME: If not visible, disconnect this handler!
|
||||||
|
@ -405,9 +480,6 @@ class TimelineFeature (FeatureBase):
|
||||||
|
|
||||||
if show:
|
if show:
|
||||||
self.timeline.show ()
|
self.timeline.show ()
|
||||||
if self.timeline.sentinel is None:
|
|
||||||
sentinel = LineFrequencySentinel (self.log_model)
|
|
||||||
self.timeline.set_sentinel (sentinel)
|
|
||||||
else:
|
else:
|
||||||
self.timeline.hide ()
|
self.timeline.hide ()
|
||||||
|
|
||||||
|
@ -431,7 +503,7 @@ class TimelineFeature (FeatureBase):
|
||||||
|
|
||||||
def goto_time_position (self, pos):
|
def goto_time_position (self, pos):
|
||||||
|
|
||||||
data = self.timeline.sentinel.data
|
data = self.timeline.freq_sentinel.data
|
||||||
if not data:
|
if not data:
|
||||||
return True
|
return True
|
||||||
count = sum (data[:pos + 1])
|
count = sum (data[:pos + 1])
|
||||||
|
|
Loading…
Reference in a new issue