1802 lines
53 KiB
Python
1802 lines
53 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import re
|
|
import sys
|
|
|
|
import cairo
|
|
import gtk
|
|
|
|
|
|
## DataRange class
|
|
class DataRange:
|
|
## @var start
|
|
# start
|
|
## @var end
|
|
# end
|
|
## @var value
|
|
# value
|
|
def __init__(self, start=0, end=0, value=""):
|
|
"""! Initializer
|
|
@param self this object
|
|
@param start start
|
|
@param end end
|
|
@param value value
|
|
"""
|
|
self.start = start
|
|
self.end = end
|
|
self.value = value
|
|
|
|
|
|
## EventString class
|
|
class EventString:
|
|
## @var at
|
|
# at
|
|
## @var value
|
|
# value
|
|
def __init__(self, at=0, value=""):
|
|
"""! Initializer
|
|
@param self this object
|
|
@param at you
|
|
@param value value
|
|
"""
|
|
self.at = at
|
|
self.value = value
|
|
|
|
|
|
## EventFloat class
|
|
class EventFloat:
|
|
## @var at
|
|
# at
|
|
## @var value
|
|
# value
|
|
def __init__(self, at=0, value=0.0):
|
|
"""! Initializer
|
|
@param self this object
|
|
@param at you
|
|
@param value value
|
|
"""
|
|
self.at = at
|
|
self.value = value
|
|
|
|
|
|
## EventInt class
|
|
class EventInt:
|
|
## @var at
|
|
# at
|
|
## @var value
|
|
# value
|
|
def __init__(self, at=0, value=0.0):
|
|
"""! Initializer
|
|
@param self this object
|
|
@param at you
|
|
@param value value
|
|
"""
|
|
self.at = at
|
|
self.value = value
|
|
|
|
|
|
def ranges_cmp(a, b):
|
|
diff = a.start - b.start
|
|
if diff < 0:
|
|
return -1
|
|
elif diff > 0:
|
|
return +1
|
|
else:
|
|
return 0
|
|
|
|
|
|
def events_cmp(a, b):
|
|
diff = a.at - b.at
|
|
if diff < 0:
|
|
return -1
|
|
elif diff > 0:
|
|
return +1
|
|
else:
|
|
return 0
|
|
|
|
|
|
## TimelineDataRange
|
|
class TimelineDataRange:
|
|
## @var name
|
|
# name
|
|
## @var ranges
|
|
# ranges
|
|
def __init__(self, name=""):
|
|
"""! Initializer
|
|
@param self this object
|
|
@param name name
|
|
"""
|
|
self.name = name
|
|
self.ranges = []
|
|
return
|
|
|
|
def __search(self, key):
|
|
"""! Search
|
|
@param self this object
|
|
@param key key
|
|
@return index if found or -1 if not found
|
|
"""
|
|
l = 0
|
|
u = len(self.ranges) - 1
|
|
while l <= u:
|
|
i = int((l + u) / 2)
|
|
if key >= self.ranges[i].start and key <= self.ranges[i].end:
|
|
return i
|
|
elif key < self.ranges[i].start:
|
|
u = i - 1
|
|
else:
|
|
# key > self.ranges[i].end
|
|
l = i + 1
|
|
return -1
|
|
|
|
def add_range(self, range):
|
|
"""! Add range
|
|
@param self this object
|
|
@param range range
|
|
@return none
|
|
"""
|
|
self.ranges.append(range)
|
|
|
|
def get_all(self):
|
|
"""! Get all ranges
|
|
@param self this object
|
|
@return the ranges
|
|
"""
|
|
return self.ranges
|
|
|
|
def get_ranges(self, start, end):
|
|
"""! Get selected ranges
|
|
@param self this object
|
|
@param start range start
|
|
@param end range end
|
|
@return the range or and empty list
|
|
"""
|
|
s = self.__search(start)
|
|
e = self.__search(end)
|
|
if s == -1 and e == -1:
|
|
return []
|
|
elif s == -1:
|
|
return self.ranges[0 : e + 1]
|
|
elif e == -1:
|
|
return self.ranges[s : len(self.ranges)]
|
|
else:
|
|
return self.ranges[s : e + 1]
|
|
|
|
def get_ranges_bounds(self, start, end):
|
|
"""! Get ranges bounds
|
|
@param self this object
|
|
@param start range start
|
|
@param end range end
|
|
@return range
|
|
"""
|
|
s = self.__search(start)
|
|
e = self.__search(end)
|
|
if s == -1 and e == -1:
|
|
return (0, 0)
|
|
elif s == -1:
|
|
return (0, e + 1)
|
|
elif e == -1:
|
|
return (s, len(self.ranges))
|
|
else:
|
|
return (s, e + 1)
|
|
|
|
def sort(self):
|
|
"""! Sort ranges
|
|
@param self this object
|
|
@return none
|
|
"""
|
|
self.ranges.sort(ranges_cmp)
|
|
|
|
def get_bounds(self):
|
|
"""! Get bounds
|
|
@param self this object
|
|
@return the bounds
|
|
"""
|
|
if len(self.ranges) > 0:
|
|
lo = self.ranges[0].start
|
|
hi = self.ranges[len(self.ranges) - 1].end
|
|
return (lo, hi)
|
|
else:
|
|
return (0, 0)
|
|
|
|
|
|
## TimelineEvent class
|
|
class TimelineEvent:
|
|
## @var name
|
|
# name
|
|
## @var events
|
|
# events
|
|
def __init__(self, name=""):
|
|
"""! Get ranges bounds
|
|
@param self this object
|
|
@param name name
|
|
"""
|
|
self.name = name
|
|
self.events = []
|
|
|
|
def __search(self, key):
|
|
"""! Search function
|
|
@param self this object
|
|
@param key the key
|
|
@return event index
|
|
"""
|
|
l = 0
|
|
u = len(self.events) - 1
|
|
while l <= u:
|
|
i = int((l + u) / 2)
|
|
if key == self.events[i].at:
|
|
return i
|
|
elif key < self.events[i].at:
|
|
u = i - 1
|
|
else:
|
|
# key > self.events[i].at
|
|
l = i + 1
|
|
return l
|
|
|
|
def add_event(self, event):
|
|
"""! Add Event
|
|
@param self this object
|
|
@param event event to add
|
|
@return none
|
|
"""
|
|
self.events.append(event)
|
|
|
|
def get_events(self, start, end):
|
|
"""! Get Events
|
|
@param self this object
|
|
@param start starting event
|
|
@param end ending event
|
|
@return the events
|
|
"""
|
|
s = self.__search(start)
|
|
e = self.__search(end)
|
|
return self.events[s : e + 1]
|
|
|
|
def get_events_bounds(self, start, end):
|
|
"""! Get Events Bounds
|
|
@param self this object
|
|
@param start starting event
|
|
@param end ending event
|
|
@return event bounds
|
|
"""
|
|
s = self.__search(start)
|
|
e = self.__search(end)
|
|
return (s, e + 1)
|
|
|
|
def sort(self):
|
|
"""! Sort function
|
|
@param self this object
|
|
@return none
|
|
"""
|
|
self.events.sort(events_cmp)
|
|
|
|
def get_bounds(self):
|
|
"""! Get Bounds
|
|
@param self this object
|
|
@return the bounds
|
|
"""
|
|
if len(self.events) > 0:
|
|
lo = self.events[0].at
|
|
hi = self.events[-1].at
|
|
return (lo, hi)
|
|
else:
|
|
return (0, 0)
|
|
|
|
|
|
## Timeline class
|
|
class Timeline:
|
|
## @var name
|
|
# name
|
|
## @var ranges
|
|
# ranges
|
|
## @var event_str
|
|
# event string
|
|
## @var event_int
|
|
# event int
|
|
def __init__(self, name=""):
|
|
"""! Initializer
|
|
@param self this object
|
|
@param name name
|
|
"""
|
|
self.ranges = []
|
|
self.event_str = []
|
|
self.event_int = []
|
|
self.name = name
|
|
|
|
def get_range(self, name):
|
|
"""! Get range
|
|
@param self this object
|
|
@param name name
|
|
@return the range
|
|
"""
|
|
for range in self.ranges:
|
|
if range.name == name:
|
|
return range
|
|
timeline = TimelineDataRange(name)
|
|
self.ranges.append(timeline)
|
|
return timeline
|
|
|
|
def get_event_str(self, name):
|
|
"""! Get Event String
|
|
@param self this object
|
|
@param name name
|
|
@return the event string
|
|
"""
|
|
for event_str in self.event_str:
|
|
if event_str.name == name:
|
|
return event_str
|
|
timeline = TimelineEvent(name)
|
|
self.event_str.append(timeline)
|
|
return timeline
|
|
|
|
def get_event_int(self, name):
|
|
"""! Get Event Int
|
|
@param self this object
|
|
@param name name
|
|
@return eevent int
|
|
"""
|
|
for event_int in self.event_int:
|
|
if event_int.name == name:
|
|
return event_int
|
|
timeline = TimelineEvent(name)
|
|
self.event_int.append(timeline)
|
|
return timeline
|
|
|
|
def get_ranges(self):
|
|
"""! Get Ranges
|
|
@param self this object
|
|
@return the ranges
|
|
"""
|
|
return self.ranges
|
|
|
|
def get_events_str(self):
|
|
"""! Get Events string
|
|
@param self this object
|
|
@return event string
|
|
"""
|
|
return self.event_str
|
|
|
|
def get_events_int(self):
|
|
"""! Get Events int
|
|
@param self this object
|
|
@return evrnt int
|
|
"""
|
|
return self.event_int
|
|
|
|
def sort(self):
|
|
"""! Sort the ranges and events
|
|
@param self this object
|
|
@return none
|
|
"""
|
|
for range in self.ranges:
|
|
range.sort()
|
|
for event in self.event_int:
|
|
event.sort()
|
|
for event in self.event_str:
|
|
event.sort()
|
|
|
|
def get_bounds(self):
|
|
"""! Get Bounds
|
|
@param self this object
|
|
@return the bounds
|
|
"""
|
|
lo = 0
|
|
hi = 0
|
|
for range in self.ranges:
|
|
(range_lo, range_hi) = range.get_bounds()
|
|
if range_lo < lo:
|
|
lo = range_lo
|
|
if range_hi > hi:
|
|
hi = range_hi
|
|
for event_str in self.event_str:
|
|
(ev_lo, ev_hi) = event_str.get_bounds()
|
|
if ev_lo < lo:
|
|
lo = ev_lo
|
|
if ev_hi > hi:
|
|
hi = ev_hi
|
|
for event_int in self.event_int:
|
|
(ev_lo, ev_hi) = event_int.get_bounds()
|
|
if ev_lo < lo:
|
|
lo = ev_lo
|
|
if ev_hi > hi:
|
|
hi = ev_hi
|
|
return (lo, hi)
|
|
|
|
|
|
## Timelines class
|
|
class Timelines:
|
|
## @var timelines
|
|
# timelines
|
|
def __init__(self):
|
|
"""Initializer
|
|
@param self: this object
|
|
"""
|
|
self.timelines = []
|
|
|
|
def get(self, name):
|
|
"""! Get Timeline
|
|
@param self this object
|
|
@param name name
|
|
@return the timeline for the name
|
|
"""
|
|
for timeline in self.timelines:
|
|
if timeline.name == name:
|
|
return timeline
|
|
timeline = Timeline(name)
|
|
self.timelines.append(timeline)
|
|
return timeline
|
|
|
|
def get_all(self):
|
|
"""! Get All Timeline
|
|
@param self this object
|
|
@return all timelines
|
|
"""
|
|
return self.timelines
|
|
|
|
def sort(self):
|
|
"""! Sort the timelines
|
|
@param self this object
|
|
@return none
|
|
"""
|
|
for timeline in self.timelines:
|
|
timeline.sort()
|
|
|
|
def get_bounds(self):
|
|
"""! Get Bounds
|
|
@param self this object
|
|
@return the bounds for all timelines
|
|
"""
|
|
lo = 0
|
|
hi = 0
|
|
for timeline in self.timelines:
|
|
(t_lo, t_hi) = timeline.get_bounds()
|
|
if t_lo < lo:
|
|
lo = t_lo
|
|
if t_hi > hi:
|
|
hi = t_hi
|
|
return (lo, hi)
|
|
|
|
def get_all_range_values(self):
|
|
"""! Get All Ranges
|
|
@param self this object
|
|
@return the keys for all ranges
|
|
"""
|
|
range_values = {}
|
|
for timeline in self.timelines:
|
|
for ranges in timeline.get_ranges():
|
|
for ran in ranges.get_all():
|
|
range_values[ran.value] = 1
|
|
return range_values.keys()
|
|
|
|
|
|
## Color class
|
|
class Color:
|
|
## @var r
|
|
# red
|
|
## @var g
|
|
# green
|
|
## @var b
|
|
# blue
|
|
def __init__(self, r=0.0, g=0.0, b=0.0):
|
|
"""! Initializer
|
|
@param self: this object
|
|
@param r: red
|
|
@param g: green
|
|
@param b: blue
|
|
"""
|
|
self.r = r
|
|
self.g = g
|
|
self.b = b
|
|
|
|
def set(self, r, g, b):
|
|
"""! Set color
|
|
@param self: this object
|
|
@param r: red
|
|
@param g: green
|
|
@param b: blue
|
|
@return none
|
|
"""
|
|
self.r = r
|
|
self.g = g
|
|
self.b = b
|
|
|
|
|
|
## Colors class
|
|
class Colors:
|
|
## @var __colors
|
|
# colors
|
|
## @var default_colors
|
|
# default colors
|
|
## XXX add more
|
|
default_colors = [
|
|
Color(1, 0, 0),
|
|
Color(0, 1, 0),
|
|
Color(0, 0, 1),
|
|
Color(1, 1, 0),
|
|
Color(1, 0, 1),
|
|
Color(0, 1, 1),
|
|
]
|
|
|
|
def __init__(self):
|
|
"""! Initializer
|
|
@param self this object
|
|
"""
|
|
self.__colors = {}
|
|
|
|
def add(self, name, color):
|
|
"""! Add
|
|
@param self this object
|
|
@param name name of the color
|
|
@param color color value
|
|
@return none
|
|
"""
|
|
self.__colors[name] = color
|
|
|
|
def lookup(self, name):
|
|
"""! Lookup name
|
|
@param self this object
|
|
@param name name
|
|
@return named color
|
|
"""
|
|
if not self.__colors.has_key(name):
|
|
self.add(name, self.default_colors.pop())
|
|
return self.__colors.get(name)
|
|
|
|
|
|
## TopLegendRenderer class
|
|
class TopLegendRenderer:
|
|
## @var __padding
|
|
# padding
|
|
## @var __legends
|
|
# legends
|
|
## @var __colors
|
|
# colors
|
|
## @var __width
|
|
# width
|
|
## @var __height
|
|
# height
|
|
def __init__(self):
|
|
"""! Initializer
|
|
@param self this object
|
|
"""
|
|
self.__padding = 10
|
|
|
|
def set_padding(self, padding):
|
|
"""! Set padding
|
|
@param self this object
|
|
@param padding padding
|
|
@return none
|
|
"""
|
|
self.__padding = padding
|
|
|
|
def set_legends(self, legends, colors):
|
|
"""! Set padding
|
|
@param self this object
|
|
@param legends legends
|
|
@param colors colors
|
|
@return none
|
|
"""
|
|
self.__legends = legends
|
|
self.__colors = colors
|
|
|
|
def layout(self, width):
|
|
"""! Set padding
|
|
@param self this object
|
|
@param width width
|
|
@return none
|
|
"""
|
|
self.__width = width
|
|
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, 1)
|
|
ctx = cairo.Context(surface)
|
|
line_height = 0
|
|
total_height = self.__padding
|
|
line_used = self.__padding
|
|
for legend in self.__legends:
|
|
(t_width, t_height) = ctx.text_extents(legend)[2:4]
|
|
item_width = self.__padding + self.__padding + t_width + self.__padding
|
|
item_height = t_height + self.__padding
|
|
if item_height > line_height:
|
|
line_height = item_height
|
|
if line_used + item_width > self.__width:
|
|
line_used = self.__padding + item_width
|
|
total_height += line_height
|
|
else:
|
|
line_used += item_width
|
|
x = line_used - item_width
|
|
total_height += line_height
|
|
self.__height = total_height
|
|
|
|
def get_height(self):
|
|
"""! Set padding
|
|
@param self this object
|
|
@return height
|
|
"""
|
|
return self.__height
|
|
|
|
def draw(self, ctx):
|
|
"""! Set padding
|
|
@param self this object
|
|
@param ctx ctx
|
|
@return none
|
|
"""
|
|
i = 0
|
|
line_height = 0
|
|
total_height = self.__padding
|
|
line_used = self.__padding
|
|
for legend in self.__legends:
|
|
(t_width, t_height) = ctx.text_extents(legend)[2:4]
|
|
item_width = self.__padding + self.__padding + t_width + self.__padding
|
|
item_height = t_height + self.__padding
|
|
if item_height > line_height:
|
|
line_height = item_height
|
|
if line_used + item_width > self.__width:
|
|
line_used = self.__padding + item_width
|
|
total_height += line_height
|
|
else:
|
|
line_used += item_width
|
|
x = line_used - item_width
|
|
ctx.rectangle(x, total_height, self.__padding, self.__padding)
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.set_line_width(2)
|
|
ctx.stroke_preserve()
|
|
ctx.set_source_rgb(self.__colors[i].r, self.__colors[i].g, self.__colors[i].b)
|
|
ctx.fill()
|
|
ctx.move_to(x + self.__padding * 2, total_height + t_height)
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.show_text(legend)
|
|
i += 1
|
|
|
|
|
|
## TimelinesRenderer class
|
|
class TimelinesRenderer:
|
|
## @var padding
|
|
# padding
|
|
## @var timelines
|
|
# timelines
|
|
## @var colors
|
|
# colors
|
|
## @var start
|
|
# start
|
|
## @var end
|
|
# end
|
|
## @var left_width
|
|
# left width
|
|
## @var right_width
|
|
# right width
|
|
## @var max_text_height
|
|
# maximum text height
|
|
## @var width
|
|
# width
|
|
## @var height
|
|
# height
|
|
## @var grey_background
|
|
# grey background
|
|
def __init__(self):
|
|
"""! Initializer
|
|
@param self this object
|
|
"""
|
|
self.padding = 10
|
|
return
|
|
|
|
def get_height(self):
|
|
"""! Get Height
|
|
@param self this object
|
|
@return height
|
|
"""
|
|
return self.height
|
|
|
|
def set_timelines(self, timelines, colors):
|
|
"""! Set Timelines
|
|
@param self this object
|
|
@param timelines timelines
|
|
@param colors colors
|
|
@return none
|
|
"""
|
|
self.timelines = timelines
|
|
self.colors = colors
|
|
|
|
def set_render_range(self, start, end):
|
|
"""! Set Render Range
|
|
@param self this object
|
|
@param start start
|
|
@param end end
|
|
@return none
|
|
"""
|
|
self.start = start
|
|
self.end = end
|
|
|
|
def get_data_x_start(self):
|
|
"""! Get Data X Start
|
|
@param self: this object
|
|
@return X start
|
|
"""
|
|
return (
|
|
self.padding / 2 + self.left_width + self.padding + self.right_width + self.padding / 2
|
|
)
|
|
|
|
def layout(self, width):
|
|
"""! Get Data X Start
|
|
@param self this object
|
|
@param width width
|
|
@return none
|
|
"""
|
|
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, 1)
|
|
ctx = cairo.Context(surface)
|
|
max_text_height = ctx.text_extents(
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcedefghijklmnopqrstuvwxyz0123456789"
|
|
)[3]
|
|
|
|
left_width = 0
|
|
right_width = 0
|
|
left_n_lines = 0
|
|
range_n = 0
|
|
eventint_n = 0
|
|
eventstr_n = 0
|
|
for timeline in self.timelines.get_all():
|
|
left_n_lines += 1
|
|
t_width = ctx.text_extents(timeline.name)[2]
|
|
left_width = max(left_width, t_width)
|
|
for rang in timeline.get_ranges():
|
|
t_width = ctx.text_extents(rang.name)[2]
|
|
right_width = max(right_width, t_width)
|
|
range_n += 1
|
|
for events_int in timeline.get_events_int():
|
|
t_width = ctx.text_extents(events_int.name)[2]
|
|
right_width = max(right_width, t_width)
|
|
eventint_n += 1
|
|
for events_str in timeline.get_events_str():
|
|
t_width = ctx.text_extents(events_str.name)[2]
|
|
right_width = max(right_width, t_width)
|
|
eventstr_n += 1
|
|
|
|
left_height = left_n_lines * max_text_height + (left_n_lines - 1) * self.padding
|
|
right_n_lines = range_n + eventint_n + eventstr_n
|
|
right_height = (right_n_lines - 1) * self.padding + right_n_lines * max_text_height
|
|
right_data_height = (eventint_n + eventstr_n) * (max_text_height + 5) + range_n * 10
|
|
right_data_height += (right_n_lines - 1) * self.padding
|
|
|
|
height = max(left_height, right_height)
|
|
height = max(height, right_data_height)
|
|
|
|
self.left_width = left_width
|
|
self.right_width = right_width
|
|
self.max_text_height = max_text_height
|
|
self.width = width
|
|
self.height = height + self.padding
|
|
|
|
def draw_line(self, ctx, x, y, width, height):
|
|
"""! Draw Line
|
|
@param self this object
|
|
@param ctx ctx
|
|
@param x x
|
|
@param y y
|
|
@param width width
|
|
@param height height
|
|
@return none
|
|
"""
|
|
ctx.move_to(x, y)
|
|
ctx.rel_line_to(width, height)
|
|
ctx.close_path()
|
|
ctx.set_operator(cairo.OPERATOR_SOURCE)
|
|
ctx.set_line_width(1.0)
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.stroke()
|
|
|
|
def draw_events(self, ctx, events, x, y, width, height):
|
|
"""! Draw Event
|
|
@param self this object
|
|
@param ctx ctx
|
|
@param events events
|
|
@param x x
|
|
@param y y
|
|
@param width width
|
|
@param height height
|
|
@return none
|
|
"""
|
|
if (self.grey_background % 2) == 0:
|
|
ctx.rectangle(x, y - self.padding / 2, width, height + self.padding)
|
|
ctx.set_source_rgb(0.9, 0.9, 0.9)
|
|
ctx.fill()
|
|
last_x_drawn = int(x)
|
|
(lo, hi) = events.get_events_bounds(self.start, self.end)
|
|
for event in events.events[lo:hi]:
|
|
real_x = int(x + (event.at - self.start) * width / (self.end - self.start))
|
|
if real_x > last_x_drawn + 2:
|
|
ctx.rectangle(real_x, y, 1, 1)
|
|
ctx.set_source_rgb(1, 0, 0)
|
|
ctx.stroke()
|
|
ctx.move_to(real_x, y + self.max_text_height)
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.show_text(str(event.value))
|
|
last_x_drawn = real_x
|
|
self.grey_background += 1
|
|
|
|
def draw_ranges(self, ctx, ranges, x, y, width, height):
|
|
"""! Draw Ranges
|
|
@param self this object
|
|
@param ctx ctx
|
|
@param ranges ranges
|
|
@param x x
|
|
@param y y
|
|
@param width width
|
|
@param height height
|
|
@return none
|
|
"""
|
|
if (self.grey_background % 2) == 0:
|
|
ctx.rectangle(x, y - self.padding / 2, width, height + self.padding)
|
|
ctx.set_source_rgb(0.9, 0.9, 0.9)
|
|
ctx.fill()
|
|
last_x_drawn = int(x - 1)
|
|
(lo, hi) = ranges.get_ranges_bounds(self.start, self.end)
|
|
for data_range in ranges.ranges[lo:hi]:
|
|
s = max(data_range.start, self.start)
|
|
e = min(data_range.end, self.end)
|
|
x_start = int(x + (s - self.start) * width / (self.end - self.start))
|
|
x_end = int(x + (e - self.start) * width / (self.end - self.start))
|
|
if x_end > last_x_drawn:
|
|
ctx.rectangle(x_start, y, x_end - x_start, 10)
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.stroke_preserve()
|
|
color = self.colors.lookup(data_range.value)
|
|
ctx.set_source_rgb(color.r, color.g, color.b)
|
|
ctx.fill()
|
|
last_x_drawn = x_end
|
|
|
|
self.grey_background += 1
|
|
|
|
def draw(self, ctx):
|
|
"""! Draw
|
|
@param self this object
|
|
@param ctx ctx
|
|
@return none
|
|
"""
|
|
timeline_top = 0
|
|
top_y = self.padding / 2
|
|
left_x_start = self.padding / 2
|
|
left_x_end = left_x_start + self.left_width
|
|
right_x_start = left_x_end + self.padding
|
|
right_x_end = right_x_start + self.right_width
|
|
data_x_start = right_x_end + self.padding / 2
|
|
data_x_end = self.width
|
|
data_width = data_x_end - data_x_start
|
|
cur_y = top_y
|
|
self.draw_line(ctx, 0, 0, self.width, 0)
|
|
self.grey_background = 1
|
|
for timeline in self.timelines.get_all():
|
|
(y_bearing, t_width, t_height) = ctx.text_extents(timeline.name)[1:4]
|
|
ctx.move_to(left_x_start, cur_y + self.max_text_height - (t_height + y_bearing))
|
|
ctx.show_text(timeline.name)
|
|
for events_int in timeline.get_events_int():
|
|
(y_bearing, t_width, t_height) = ctx.text_extents(events_int.name)[1:4]
|
|
ctx.move_to(right_x_start, cur_y + self.max_text_height - (t_height + y_bearing))
|
|
ctx.show_text(events_int.name)
|
|
self.draw_events(
|
|
ctx, events_int, data_x_start, cur_y, data_width, self.max_text_height + 5
|
|
)
|
|
cur_y += self.max_text_height + 5 + self.padding
|
|
self.draw_line(
|
|
ctx,
|
|
right_x_start - self.padding / 2,
|
|
cur_y - self.padding / 2,
|
|
self.right_width + self.padding,
|
|
0,
|
|
)
|
|
|
|
for events_str in timeline.get_events_str():
|
|
(y_bearing, t_width, t_height) = ctx.text_extents(events_str.name)[1:4]
|
|
ctx.move_to(right_x_start, cur_y + self.max_text_height - (t_height + y_bearing))
|
|
ctx.show_text(events_str.name)
|
|
self.draw_events(
|
|
ctx, events_str, data_x_start, cur_y, data_width, self.max_text_height + 5
|
|
)
|
|
cur_y += self.max_text_height + 5 + self.padding
|
|
self.draw_line(
|
|
ctx,
|
|
right_x_start - self.padding / 2,
|
|
cur_y - self.padding / 2,
|
|
self.right_width + self.padding,
|
|
0,
|
|
)
|
|
for ranges in timeline.get_ranges():
|
|
(y_bearing, t_width, t_height) = ctx.text_extents(ranges.name)[1:4]
|
|
ctx.move_to(right_x_start, cur_y + self.max_text_height - (t_height + y_bearing))
|
|
ctx.show_text(ranges.name)
|
|
self.draw_ranges(ctx, ranges, data_x_start, cur_y, data_width, 10)
|
|
cur_y += self.max_text_height + self.padding
|
|
self.draw_line(
|
|
ctx,
|
|
right_x_start - self.padding / 2,
|
|
cur_y - self.padding / 2,
|
|
self.right_width + self.padding,
|
|
0,
|
|
)
|
|
self.draw_line(ctx, 0, cur_y - self.padding / 2, self.width, 0)
|
|
bot_y = cur_y - self.padding / 2
|
|
self.draw_line(ctx, left_x_end + self.padding / 2, 0, 0, bot_y)
|
|
self.draw_line(ctx, right_x_end + self.padding / 2, 0, 0, bot_y)
|
|
|
|
|
|
## ScaleRenderer class
|
|
class ScaleRenderer:
|
|
## @var __top
|
|
# top
|
|
## @var __lo
|
|
# lo
|
|
## @var __hi
|
|
# hi
|
|
## @var __delta
|
|
# delta
|
|
## @var __width
|
|
# width
|
|
## @var __height
|
|
# height
|
|
## @var max_text_height
|
|
# maximum text height
|
|
def __init__(self):
|
|
"""! Initializer
|
|
@param self this object
|
|
"""
|
|
self.__top = 0
|
|
return
|
|
|
|
def set_bounds(self, lo, hi):
|
|
"""! Set Bounds
|
|
@param self this object
|
|
@param lo lo
|
|
@param hi hi
|
|
@return none
|
|
"""
|
|
self.__lo = lo
|
|
self.__hi = hi
|
|
|
|
def get_position(self, x):
|
|
"""! Get Position
|
|
@param self this object
|
|
@param x x
|
|
@return real x
|
|
"""
|
|
real_x = (x - self.__lo) * self.__width / (self.__hi - self.__lo)
|
|
return real_x
|
|
|
|
def set_top(self):
|
|
"""! Set Top
|
|
@param self this object
|
|
@return none
|
|
"""
|
|
self.__top = 1
|
|
|
|
def set_bot(self):
|
|
"""! Set Bottom
|
|
@param self this object
|
|
@return none
|
|
"""
|
|
self.__top = 0
|
|
|
|
def layout(self, width):
|
|
"""! Layout
|
|
@param self this object
|
|
@param width width
|
|
@return none
|
|
"""
|
|
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, 1)
|
|
ctx = cairo.Context(surface)
|
|
|
|
# calculate scale delta
|
|
data_delta = self.__hi - self.__lo
|
|
closest = 1
|
|
while (closest * 10) < data_delta:
|
|
closest *= 10
|
|
if (data_delta / closest) == 0:
|
|
delta = closest
|
|
elif (data_delta / closest) == 1:
|
|
delta = closest / 10
|
|
else:
|
|
delta = closest
|
|
start = self.__lo - (self.__lo % delta) + delta
|
|
end = self.__hi - (self.__hi % delta)
|
|
|
|
self.__delta = delta
|
|
self.__width = width
|
|
|
|
# calculate text height
|
|
max_text_height = ctx.text_extents(
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcedefghijklmnopqrstuvwxyz0123456789"
|
|
)[3]
|
|
self.max_text_height = max_text_height
|
|
height = max_text_height + 10
|
|
self.__height = height
|
|
|
|
def get_height(self):
|
|
"""! Get Height
|
|
@param self: this object
|
|
@return height
|
|
"""
|
|
return self.__height
|
|
|
|
def draw(self, ctx):
|
|
"""! Draw
|
|
@param self this object
|
|
@param ctx ctx
|
|
@return none
|
|
"""
|
|
delta = self.__delta
|
|
start = self.__lo - (self.__lo % delta) + delta
|
|
end = self.__hi - (self.__hi % delta)
|
|
|
|
if self.__top == 1:
|
|
s = -1
|
|
else:
|
|
s = 1
|
|
# print scale points
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.set_line_width(1.0)
|
|
ticks = range(int(start), int(end + delta), int(delta))
|
|
for x in ticks:
|
|
real_x = (x - self.__lo) * self.__width / (self.__hi - self.__lo)
|
|
ctx.move_to(real_x, 0)
|
|
ctx.line_to(real_x, 5 * s)
|
|
ctx.close_path()
|
|
ctx.stroke()
|
|
(t_y_bearing, t_width, t_height) = ctx.text_extents(str(x))[1:4]
|
|
if self.__top:
|
|
text_delta = t_height + t_y_bearing
|
|
else:
|
|
text_delta = -t_y_bearing
|
|
ctx.move_to(real_x - t_width / 2, (5 + 5 + text_delta) * s)
|
|
ctx.show_text(str(x))
|
|
# draw subticks
|
|
delta /= 10
|
|
if delta > 0:
|
|
start = self.__lo - (self.__lo % delta) + delta
|
|
end = self.__hi - (self.__hi % delta)
|
|
for x in range(int(start), int(end + delta), int(delta)):
|
|
real_x = (x - self.__lo) * self.__width / (self.__hi - self.__lo)
|
|
ctx.move_to(real_x, 0)
|
|
ctx.line_to(real_x, 3 * s)
|
|
ctx.close_path()
|
|
ctx.stroke()
|
|
|
|
|
|
## GraphicRenderer class
|
|
class GraphicRenderer:
|
|
## @var __start
|
|
# start
|
|
## @var __end
|
|
# end
|
|
## @var __mid_scale
|
|
# mid scale
|
|
## @var __bot_scale
|
|
# bottom scale
|
|
## @var __width
|
|
# width
|
|
## @var __height
|
|
# height
|
|
## @var __r_start
|
|
# start
|
|
## @var __r_end
|
|
# end
|
|
## @var __data
|
|
# data
|
|
## @var __mid_scale
|
|
# mid scale
|
|
## @var __top_legend
|
|
# top legend
|
|
def __init__(self, start, end):
|
|
"""! Initializer
|
|
@param self this object
|
|
@param start start
|
|
@param end end
|
|
"""
|
|
self.__start = float(start)
|
|
self.__end = float(end)
|
|
self.__mid_scale = ScaleRenderer()
|
|
self.__mid_scale.set_top()
|
|
self.__bot_scale = ScaleRenderer()
|
|
self.__bot_scale.set_bounds(start, end)
|
|
self.__bot_scale.set_bot()
|
|
self.__width = 1
|
|
self.__height = 1
|
|
|
|
def get_width(self):
|
|
"""! Get Width
|
|
@param self: this object
|
|
@return width
|
|
"""
|
|
return self.__width
|
|
|
|
def get_height(self):
|
|
"""! Get Height
|
|
@param self this object
|
|
@return height
|
|
"""
|
|
return self.__height
|
|
|
|
# return x, y, width, height
|
|
def get_data_rectangle(self):
|
|
"""! Get Data Rectangle
|
|
@param self this object
|
|
@return rectangle
|
|
"""
|
|
y_start = self.__top_legend.get_height()
|
|
x_start = self.__data.get_data_x_start()
|
|
return (x_start, y_start, self.__width - x_start, self.__data.get_height())
|
|
|
|
def scale_data(self, x):
|
|
"""! Get Data Rectangle
|
|
@param self this object
|
|
@param x x
|
|
@return scaled x
|
|
"""
|
|
x_start = self.__data.get_data_x_start()
|
|
x_scaled = x / (self.__width - x_start) * (self.__r_end - self.__r_start)
|
|
return x_scaled
|
|
|
|
# return x, y, width, height
|
|
def get_selection_rectangle(self):
|
|
"""! Get Selection Rectangle
|
|
@param self this object
|
|
@return rectangle
|
|
"""
|
|
y_start = (
|
|
self.__top_legend.get_height()
|
|
+ self.__data.get_height()
|
|
+ self.__mid_scale.get_height()
|
|
+ 20
|
|
)
|
|
y_height = self.__bot_scale.get_height() + 20
|
|
x_start = self.__bot_scale.get_position(self.__r_start)
|
|
x_end = self.__bot_scale.get_position(self.__r_end)
|
|
return (x_start, y_start, x_end - x_start, y_height)
|
|
|
|
def scale_selection(self, x):
|
|
"""! Scale Selection
|
|
@param self this object
|
|
@param x the X
|
|
@return scaled X
|
|
"""
|
|
x_scaled = x / self.__width * (self.__end - self.__start)
|
|
return x_scaled
|
|
|
|
def set_range(self, start, end):
|
|
"""! Set Range
|
|
@param self this object
|
|
@param start start
|
|
@param end end
|
|
@return none
|
|
"""
|
|
s = min(start, end)
|
|
e = max(start, end)
|
|
start = max(self.__start, s)
|
|
end = min(self.__end, e)
|
|
self.__r_start = start
|
|
self.__r_end = end
|
|
self.__data.set_render_range(start, end)
|
|
self.__mid_scale.set_bounds(start, end)
|
|
self.layout(self.__width, self.__height)
|
|
|
|
def get_range(self):
|
|
"""! Get Range
|
|
@param self this object
|
|
@return range
|
|
"""
|
|
return (self.__r_start, self.__r_end)
|
|
|
|
def set_data(self, data):
|
|
"""! Set Date
|
|
@param self this object
|
|
@param data data
|
|
@return none
|
|
"""
|
|
self.__data = data
|
|
|
|
def set_top_legend(self, top_legend):
|
|
"""! Set Top Legend
|
|
@param self this object
|
|
@param top_legend The legend
|
|
@return none
|
|
"""
|
|
self.__top_legend = top_legend
|
|
|
|
def layout(self, width, height):
|
|
"""! Set Layout
|
|
@param self this object
|
|
@param width width
|
|
@param height height
|
|
@return none
|
|
"""
|
|
self.__width = width
|
|
self.__height = height
|
|
self.__top_legend.layout(width)
|
|
top_legend_height = self.__top_legend.get_height()
|
|
self.__data.layout(width)
|
|
self.__mid_scale.layout(width - self.__data.get_data_x_start())
|
|
self.__bot_scale.layout(width)
|
|
return
|
|
|
|
def __x_pixel(self, x, width):
|
|
"""! X Pixel
|
|
@param self this object
|
|
@param x x
|
|
@param width width
|
|
@return x pixel
|
|
"""
|
|
new_x = (x - self.__start) * width / (self.__end - self.__start)
|
|
return new_x
|
|
|
|
def draw(self, ctx):
|
|
"""! Draw
|
|
@param self this object
|
|
@param ctx ctx
|
|
@return none
|
|
"""
|
|
# default background is white
|
|
ctx.save()
|
|
ctx.set_source_rgb(1, 1, 1)
|
|
ctx.set_operator(cairo.OPERATOR_SOURCE)
|
|
ctx.rectangle(0, 0, self.__width, self.__height)
|
|
ctx.fill()
|
|
|
|
# top legend
|
|
ctx.save()
|
|
self.__top_legend.draw(ctx)
|
|
top_legend_height = self.__top_legend.get_height()
|
|
ctx.restore()
|
|
|
|
# separation line
|
|
ctx.move_to(0, top_legend_height)
|
|
ctx.line_to(self.__width, top_legend_height)
|
|
ctx.close_path()
|
|
ctx.set_line_width(2)
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.stroke()
|
|
|
|
# data
|
|
ctx.save()
|
|
ctx.translate(0, top_legend_height)
|
|
self.__data.draw(ctx)
|
|
ctx.restore()
|
|
|
|
# scale below data
|
|
ctx.save()
|
|
ctx.translate(
|
|
self.__data.get_data_x_start(),
|
|
top_legend_height + self.__data.get_height() + self.__mid_scale.get_height(),
|
|
)
|
|
self.__mid_scale.draw(ctx)
|
|
ctx.restore()
|
|
|
|
height_used = top_legend_height + self.__data.get_height() + self.__mid_scale.get_height()
|
|
|
|
# separation between scale and left pane
|
|
ctx.move_to(self.__data.get_data_x_start(), height_used)
|
|
ctx.rel_line_to(0, -self.__mid_scale.get_height())
|
|
ctx.close_path()
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.set_line_width(2)
|
|
ctx.stroke()
|
|
|
|
# separation below scale
|
|
ctx.move_to(0, height_used)
|
|
ctx.line_to(self.__width, height_used)
|
|
ctx.close_path()
|
|
ctx.set_line_width(2)
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.stroke()
|
|
|
|
select_start = self.__bot_scale.get_position(self.__r_start)
|
|
select_end = self.__bot_scale.get_position(self.__r_end)
|
|
|
|
# left connection between top scale and bottom scale
|
|
ctx.move_to(0, height_used)
|
|
ctx.line_to(self.__data.get_data_x_start(), height_used)
|
|
ctx.line_to(select_start, height_used + 20)
|
|
ctx.line_to(0, height_used + 20)
|
|
ctx.line_to(0, height_used)
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.set_line_width(1)
|
|
ctx.stroke_preserve()
|
|
ctx.set_source_rgb(0.9, 0.9, 0.9)
|
|
ctx.fill()
|
|
|
|
# right connection between top scale and bottom scale
|
|
ctx.move_to(self.__width, height_used)
|
|
ctx.line_to(self.__width, height_used + 20)
|
|
ctx.line_to(select_end, height_used + 20)
|
|
ctx.line_to(self.__width, height_used)
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.set_line_width(1)
|
|
ctx.stroke_preserve()
|
|
ctx.set_source_rgb(0.9, 0.9, 0.9)
|
|
ctx.fill()
|
|
|
|
height_used += 20
|
|
|
|
# unused area background
|
|
unused_start = self.__bot_scale.get_position(self.__r_start)
|
|
unused_end = self.__bot_scale.get_position(self.__r_end)
|
|
unused_height = self.__bot_scale.get_height() + 20
|
|
ctx.rectangle(0, height_used, unused_start, unused_height)
|
|
ctx.rectangle(unused_end, height_used, self.__width - unused_end, unused_height)
|
|
ctx.set_source_rgb(0.9, 0.9, 0.9)
|
|
ctx.fill()
|
|
|
|
# border line around bottom scale
|
|
ctx.move_to(unused_end, height_used)
|
|
ctx.line_to(self.__width, height_used)
|
|
ctx.line_to(self.__width, height_used + unused_height)
|
|
ctx.line_to(0, height_used + unused_height)
|
|
ctx.line_to(0, height_used)
|
|
ctx.line_to(unused_start, height_used)
|
|
ctx.close_path()
|
|
ctx.set_line_width(2)
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.stroke()
|
|
ctx.move_to(unused_start, height_used)
|
|
ctx.line_to(unused_end, height_used)
|
|
ctx.close_path()
|
|
ctx.set_line_width(1)
|
|
ctx.set_source_rgb(0.9, 0.9, 0.9)
|
|
ctx.stroke()
|
|
|
|
# unused area dot borders
|
|
ctx.save()
|
|
ctx.move_to(max(unused_start, 2), height_used)
|
|
ctx.rel_line_to(0, unused_height)
|
|
ctx.move_to(min(unused_end, self.__width - 2), height_used)
|
|
ctx.rel_line_to(0, unused_height)
|
|
ctx.set_dash([5], 0)
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.set_line_width(1)
|
|
ctx.stroke()
|
|
ctx.restore()
|
|
|
|
# bottom scale
|
|
ctx.save()
|
|
ctx.translate(0, height_used)
|
|
self.__bot_scale.draw(ctx)
|
|
ctx.restore()
|
|
|
|
|
|
## GtkGraphicRenderer class
|
|
class GtkGraphicRenderer(gtk.DrawingArea):
|
|
## @var __data
|
|
# data
|
|
## @var __moving_left
|
|
# moving left
|
|
## @var __moving_right
|
|
# moving right
|
|
## @var __moving_both
|
|
# moving both
|
|
## @var __moving_top
|
|
# moving top
|
|
## @var __force_full_redraw
|
|
# full redraw
|
|
## @var __moving_left_cur
|
|
# moving left cur
|
|
## @var __moving_right_cur
|
|
# moving right cur
|
|
## @var __moving_both_start
|
|
# moving both start
|
|
## @var __moving_both_cur
|
|
# moving both cur
|
|
## @var __moving_top_cur
|
|
# moving top cur
|
|
## @var __moving_top_start
|
|
# moving top start
|
|
## @var __width
|
|
# width
|
|
## @var __height
|
|
# height
|
|
## @var __buffer_surface
|
|
# buffer surface
|
|
def __init__(self, data):
|
|
"""! Initializer
|
|
@param self this object
|
|
@param data data
|
|
"""
|
|
super(GtkGraphicRenderer, self).__init__()
|
|
self.__data = data
|
|
self.__moving_left = False
|
|
self.__moving_right = False
|
|
self.__moving_both = False
|
|
self.__moving_top = False
|
|
self.__force_full_redraw = True
|
|
self.add_events(gtk.gdk.POINTER_MOTION_MASK)
|
|
self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
|
|
self.add_events(gtk.gdk.BUTTON_RELEASE_MASK)
|
|
self.connect("expose_event", self.expose)
|
|
self.connect("size-allocate", self.size_allocate)
|
|
self.connect("motion-notify-event", self.motion_notify)
|
|
self.connect("button-press-event", self.button_press)
|
|
self.connect("button-release-event", self.button_release)
|
|
|
|
def set_smaller_zoom(self):
|
|
"""! Set Smaller Zoom
|
|
@param self this object
|
|
@return none
|
|
"""
|
|
(start, end) = self.__data.get_range()
|
|
self.__data.set_range(start, start + (end - start) * 2)
|
|
self.__force_full_redraw = True
|
|
self.queue_draw()
|
|
|
|
def set_bigger_zoom(self):
|
|
"""! Set Bigger Zoom
|
|
@param self this object
|
|
@return none
|
|
"""
|
|
(start, end) = self.__data.get_range()
|
|
self.__data.set_range(start, start + (end - start) / 2)
|
|
self.__force_full_redraw = True
|
|
self.queue_draw()
|
|
|
|
def output_png(self, filename):
|
|
"""! Output PNG
|
|
@param self this object
|
|
@param filename file name
|
|
@return none
|
|
"""
|
|
surface = cairo.ImageSurface(
|
|
cairo.FORMAT_ARGB32, self.__data.get_width(), self.__data.get_height()
|
|
)
|
|
ctx = cairo.Context(self.__buffer_surface)
|
|
self.__data.draw(ctx)
|
|
surface.write_to_png(filename)
|
|
|
|
def button_press(self, widget, event):
|
|
"""! Button Press
|
|
@param self this object
|
|
@param widget widget
|
|
@param event event
|
|
@return true if button has been pressed otherwise false
|
|
"""
|
|
(x, y, width, height) = self.__data.get_selection_rectangle()
|
|
(d_x, d_y, d_width, d_height) = self.__data.get_data_rectangle()
|
|
if event.y > y and event.y < y + height:
|
|
if abs(event.x - x) < 5:
|
|
self.__moving_left = True
|
|
return True
|
|
if abs(event.x - (x + width)) < 5:
|
|
self.__moving_right = True
|
|
return True
|
|
if event.x > x and event.x < x + width:
|
|
self.__moving_both = True
|
|
self.__moving_both_start = event.x
|
|
self.__moving_both_cur = event.x
|
|
return True
|
|
if event.y > d_y and event.y < (d_y + d_height):
|
|
if event.x > d_x and event.x < (d_x + d_width):
|
|
self.__moving_top = True
|
|
self.__moving_top_start = event.x
|
|
self.__moving_top_cur = event.x
|
|
return True
|
|
return False
|
|
|
|
def button_release(self, widget, event):
|
|
"""! Button Release
|
|
@param self this object
|
|
@param widget widget
|
|
@param event event
|
|
@return true if button was released otherwise false
|
|
"""
|
|
if self.__moving_left:
|
|
self.__moving_left = False
|
|
left = self.__data.scale_selection(self.__moving_left_cur)
|
|
right = self.__data.get_range()[1]
|
|
self.__data.set_range(left, right)
|
|
self.__force_full_redraw = True
|
|
self.queue_draw()
|
|
return True
|
|
if self.__moving_right:
|
|
self.__moving_right = False
|
|
right = self.__data.scale_selection(self.__moving_right_cur)
|
|
left = self.__data.get_range()[0]
|
|
self.__data.set_range(left, right)
|
|
self.__force_full_redraw = True
|
|
self.queue_draw()
|
|
return True
|
|
if self.__moving_both:
|
|
self.__moving_both = False
|
|
delta = self.__data.scale_selection(self.__moving_both_cur - self.__moving_both_start)
|
|
(left, right) = self.__data.get_range()
|
|
self.__data.set_range(left + delta, right + delta)
|
|
self.__force_full_redraw = True
|
|
self.queue_draw()
|
|
return True
|
|
if self.__moving_top:
|
|
self.__moving_top = False
|
|
return False
|
|
|
|
def motion_notify(self, widget, event):
|
|
"""! Motion Notify
|
|
@param self this object
|
|
@param widget widget
|
|
@param event event
|
|
@return true if moving otherwise false
|
|
"""
|
|
(x, y, width, height) = self.__data.get_selection_rectangle()
|
|
if self.__moving_left:
|
|
if event.x <= 0:
|
|
self.__moving_left_cur = 0
|
|
elif event.x >= x + width:
|
|
self.__moving_left_cur = x + width
|
|
else:
|
|
self.__moving_left_cur = event.x
|
|
self.queue_draw_area(0, int(y), int(self.__width), int(height))
|
|
return True
|
|
if self.__moving_right:
|
|
if event.x >= self.__width:
|
|
self.__moving_right = self.__width
|
|
elif event.x < x:
|
|
self.__moving_right_cur = x
|
|
else:
|
|
self.__moving_right_cur = event.x
|
|
self.queue_draw_area(0, int(y), int(self.__width), int(height))
|
|
return True
|
|
if self.__moving_both:
|
|
cur_e = self.__width - (x + width - self.__moving_both_start)
|
|
cur_s = self.__moving_both_start - x
|
|
if event.x < cur_s:
|
|
self.__moving_both_cur = cur_s
|
|
elif event.x > cur_e:
|
|
self.__moving_both_cur = cur_e
|
|
else:
|
|
self.__moving_both_cur = event.x
|
|
self.queue_draw_area(0, int(y), int(self.__width), int(height))
|
|
return True
|
|
if self.__moving_top:
|
|
self.__moving_top_cur = event.x
|
|
delta = self.__data.scale_data(self.__moving_top_start - self.__moving_top_cur)
|
|
(left, right) = self.__data.get_range()
|
|
self.__data.set_range(left + delta, right + delta)
|
|
self.__force_full_redraw = True
|
|
self.__moving_top_start = event.x
|
|
self.queue_draw()
|
|
return True
|
|
(d_x, d_y, d_width, d_height) = self.__data.get_data_rectangle()
|
|
if event.y > y and event.y < y + height:
|
|
if abs(event.x - x) < 5 or abs(event.x - (x + width)) < 5:
|
|
widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.SB_H_DOUBLE_ARROW))
|
|
return True
|
|
if event.x > x and event.x < x + width:
|
|
widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR))
|
|
return True
|
|
if event.y > d_y and event.y < (d_y + d_height):
|
|
if event.x > d_x and event.x < (d_x + d_width):
|
|
widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR))
|
|
return True
|
|
widget.window.set_cursor(None)
|
|
return False
|
|
|
|
def size_allocate(self, widget, allocation):
|
|
"""! Size Allocate
|
|
@param self this object
|
|
@param widget widget
|
|
@param allocation allocation
|
|
@return none
|
|
"""
|
|
self.__width = allocation.width
|
|
self.__height = allocation.height
|
|
self.__data.layout(allocation.width, allocation.height)
|
|
self.__force_full_redraw = True
|
|
self.queue_draw()
|
|
|
|
def expose(self, widget, event):
|
|
"""! Expose
|
|
@param self this object
|
|
@param widget widget
|
|
@param event event
|
|
@return false
|
|
"""
|
|
if self.__force_full_redraw:
|
|
self.__buffer_surface = cairo.ImageSurface(
|
|
cairo.FORMAT_ARGB32, self.__data.get_width(), self.__data.get_height()
|
|
)
|
|
ctx = cairo.Context(self.__buffer_surface)
|
|
self.__data.draw(ctx)
|
|
self.__force_full_redraw = False
|
|
ctx = widget.window.cairo_create()
|
|
ctx.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
|
|
ctx.clip()
|
|
ctx.set_source_surface(self.__buffer_surface)
|
|
ctx.paint()
|
|
(x, y, width, height) = self.__data.get_selection_rectangle()
|
|
if self.__moving_left:
|
|
ctx.move_to(max(self.__moving_left_cur, 2), y)
|
|
ctx.rel_line_to(0, height)
|
|
ctx.close_path()
|
|
ctx.set_line_width(1)
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.stroke()
|
|
if self.__moving_right:
|
|
ctx.move_to(min(self.__moving_right_cur, self.__width - 2), y)
|
|
ctx.rel_line_to(0, height)
|
|
ctx.close_path()
|
|
ctx.set_line_width(1)
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.stroke()
|
|
if self.__moving_both:
|
|
delta_x = self.__moving_both_cur - self.__moving_both_start
|
|
left_x = x + delta_x
|
|
ctx.move_to(x + delta_x, y)
|
|
ctx.rel_line_to(0, height)
|
|
ctx.close_path()
|
|
ctx.move_to(x + width + delta_x, y)
|
|
ctx.rel_line_to(0, height)
|
|
ctx.close_path()
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
ctx.set_line_width(1)
|
|
ctx.stroke()
|
|
return False
|
|
|
|
|
|
## MainWindow class
|
|
class MainWindow:
|
|
## @var __window
|
|
# window
|
|
## @var __render
|
|
# render
|
|
## @var __dialog
|
|
# dialog
|
|
def __init__(self):
|
|
"""! Initializer
|
|
@param self this object
|
|
"""
|
|
return
|
|
|
|
def run(self, graphic):
|
|
"""! Run function
|
|
@param self this object
|
|
@param graphic graphic
|
|
@return none
|
|
"""
|
|
window = gtk.Window()
|
|
self.__window = window
|
|
window.set_default_size(200, 200)
|
|
vbox = gtk.VBox()
|
|
window.add(vbox)
|
|
render = GtkGraphicRenderer(graphic)
|
|
self.__render = render
|
|
vbox.pack_end(render, True, True, 0)
|
|
hbox = gtk.HBox()
|
|
vbox.pack_start(hbox, False, False, 0)
|
|
smaller_zoom = gtk.Button("Zoom Out")
|
|
smaller_zoom.connect("clicked", self.__set_smaller_cb)
|
|
hbox.pack_start(smaller_zoom)
|
|
bigger_zoom = gtk.Button("Zoom In")
|
|
bigger_zoom.connect("clicked", self.__set_bigger_cb)
|
|
hbox.pack_start(bigger_zoom)
|
|
output_png = gtk.Button("Output Png")
|
|
output_png.connect("clicked", self.__output_png_cb)
|
|
hbox.pack_start(output_png)
|
|
window.connect("destroy", gtk.main_quit)
|
|
window.show_all()
|
|
# gtk.bindings_activate(gtk.main_quit, 'q', 0)
|
|
gtk.main()
|
|
|
|
def __set_smaller_cb(self, widget):
|
|
"""! Set Smaller Callback
|
|
@param self this object
|
|
@param widget widget
|
|
@return none
|
|
"""
|
|
self.__render.set_smaller_zoom()
|
|
|
|
def __set_bigger_cb(self, widget):
|
|
"""! Set Bigger Callback
|
|
@param self this object
|
|
@param widget widget
|
|
@return none
|
|
"""
|
|
self.__render.set_bigger_zoom()
|
|
|
|
def __output_png_cb(self, widget):
|
|
"""! Output PNG Callback
|
|
@param self this object
|
|
@param widget widget
|
|
@return none
|
|
"""
|
|
dialog = gtk.FileChooserDialog(
|
|
"Output Png", self.__window, gtk.FILE_CHOOSER_ACTION_SAVE, ("Save", 1)
|
|
)
|
|
self.__dialog = dialog
|
|
dialog.set_default_response(1)
|
|
dialog.connect("response", self.__dialog_response_cb)
|
|
dialog.show()
|
|
|
|
def __dialog_response_cb(self, widget, response):
|
|
"""! Dialog Response Callback
|
|
@param self this object
|
|
@param widget widget
|
|
@param response response
|
|
@return none
|
|
"""
|
|
if response == 1:
|
|
filename = self.__dialog.get_filename()
|
|
self.__render.output_png(filename)
|
|
widget.hide()
|
|
|
|
|
|
## read_data function
|
|
def read_data(filename):
|
|
timelines = Timelines()
|
|
colors = Colors()
|
|
m1 = re.compile("range ([^ ]+) ([^ ]+) ([^ ]+) ([0-9]+) ([0-9]+)")
|
|
m2 = re.compile("event-str ([^ ]+) ([^ ]+) ([^ ]+) ([0-9]+)")
|
|
m3 = re.compile("event-int ([^ ]+) ([^ ]+) ([0-9]+) ([0-9]+)")
|
|
m4 = re.compile("color ([^ ]+) #([a-fA-F0-9]{2,2})([a-fA-F0-9]{2,2})([a-fA-F0-9]{2,2})")
|
|
|
|
with open(filename, encoding="utf-8") as fh:
|
|
for line in fh.readlines():
|
|
m = m1.match(line)
|
|
if m:
|
|
line_name = m.group(1)
|
|
timeline = timelines.get(m.group(1))
|
|
rang = timeline.get_range(m.group(2))
|
|
data_range = DataRange()
|
|
data_range.value = m.group(3)
|
|
data_range.start = int(m.group(4))
|
|
data_range.end = int(m.group(5))
|
|
rang.add_range(data_range)
|
|
continue
|
|
m = m2.match(line)
|
|
if m:
|
|
line_name = m.group(1)
|
|
timeline = timelines.get(m.group(1))
|
|
ev = timeline.get_event_str(m.group(2))
|
|
event = EventString()
|
|
event.value = m.group(3)
|
|
event.at = int(m.group(4))
|
|
ev.add_event(event)
|
|
continue
|
|
m = m3.match(line)
|
|
if m:
|
|
line_name = m.group(1)
|
|
timeline = timelines.get(m.group(1))
|
|
ev = timeline.get_event_int(m.group(2))
|
|
event = EventInt()
|
|
event.value = int(m.group(3))
|
|
event.at = int(m.group(4))
|
|
ev.add_event(event)
|
|
continue
|
|
|
|
m = m4.match(line)
|
|
if m:
|
|
r = int(m.group(2), 16)
|
|
g = int(m.group(3), 16)
|
|
b = int(m.group(4), 16)
|
|
color = Color(r / 255, g / 255, b / 255)
|
|
colors.add(m.group(1), color)
|
|
continue
|
|
timelines.sort()
|
|
return (colors, timelines)
|
|
|
|
|
|
def main():
|
|
(colors, timelines) = read_data(sys.argv[1])
|
|
(lower_bound, upper_bound) = timelines.get_bounds()
|
|
graphic = GraphicRenderer(lower_bound, upper_bound)
|
|
top_legend = TopLegendRenderer()
|
|
range_values = timelines.get_all_range_values()
|
|
range_colors = []
|
|
for range_value in range_values:
|
|
range_colors.append(colors.lookup(range_value))
|
|
top_legend.set_legends(range_values, range_colors)
|
|
graphic.set_top_legend(top_legend)
|
|
data = TimelinesRenderer()
|
|
data.set_timelines(timelines, colors)
|
|
graphic.set_data(data)
|
|
|
|
# default range
|
|
range_mid = (upper_bound - lower_bound) / 2
|
|
range_width = (upper_bound - lower_bound) / 10
|
|
range_lo = range_mid - range_width / 2
|
|
range_hi = range_mid + range_width / 2
|
|
graphic.set_range(range_lo, range_hi)
|
|
|
|
main_window = MainWindow()
|
|
main_window.run(graphic)
|
|
|
|
|
|
main()
|