A Discrete-Event Network Simulator
API
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
grid.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 ## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
3 
4 import cairo
5 import sys
6 import re
7 import gtk
8 
9 
10 
11 class DataRange:
12  def __init__(self, start = 0, end = 0, value = ''):
13  self.start = start
14  self.end = end
15  self.value = value
17  def __init__(self, at = 0, value = ''):
18  self.at = at
19  self.value = value
20 class EventFloat:
21  def __init__(self, at = 0, value = 0.0):
22  self.at = at
23  self.value = value
24 class EventInt:
25  def __init__(self, at = 0, value = 0.0):
26  self.at = at
27  self.value = value
28 def ranges_cmp(a, b):
29  diff = a.start - b.start
30  if diff < 0:
31  return -1
32  elif diff > 0:
33  return +1
34  else:
35  return 0
36 def events_cmp(a, b):
37  diff = a.at - b.at
38  if diff < 0:
39  return -1
40  elif diff > 0:
41  return +1
42  else:
43  return 0
45  def __init__(self, name = ''):
46  self.name = name
47  self.ranges = []
48  return
49  def __search(self, key):
50  l = 0
51  u = len(self.ranges)-1
52  while l <= u:
53  i = int((l + u) / 2)
54  if key >= self.ranges[i].start and key <= self.ranges[i].end:
55  return i
56  elif key < self.ranges[i].start:
57  u = i - 1
58  else:
59  # key > self.ranges[i].end
60  l = i + 1
61  return - 1
62  def add_range(self, range):
63  self.ranges.append(range)
64  def get_all(self):
65  return self.ranges
66  def get_ranges(self, start, end):
67  s = self.__search(start)
68  e = self.__search(end)
69  if s == -1 and e == -1:
70  return []
71  elif s == -1:
72  return self.ranges[0:e + 1]
73  elif e == -1:
74  return self.ranges[s:len(self.ranges)]
75  else:
76  return self.ranges[s:e + 1]
77  def get_ranges_bounds(self, start, end):
78  s = self.__search(start)
79  e = self.__search(end)
80  if s == -1 and e == -1:
81  return(0, 0)
82  elif s == -1:
83  return(0, e + 1)
84  elif e == -1:
85  return(s, len(self.ranges))
86  else:
87  return(s, e + 1)
88  def sort(self):
89  self.ranges.sort(ranges_cmp)
90  def get_bounds(self):
91  if len(self.ranges) > 0:
92  lo = self.ranges[0].start
93  hi = self.ranges[len(self.ranges)-1].end
94  return(lo, hi)
95  else:
96  return(0, 0)
98  def __init__(self, name = ''):
99  self.name = name
100  self.events = []
101  def __search(self, key):
102  l = 0
103  u = len(self.events)-1
104  while l <= u:
105  i = int((l + u) / 2)
106  if key == self.events[i].at:
107  return i
108  elif key < self.events[i].at:
109  u = i - 1
110  else:
111  # key > self.events[i].at
112  l = i + 1
113  return l
114  def add_event(self, event):
115  self.events.append(event)
116  def get_events(self, start, end):
117  s = self.__search(start)
118  e = self.__search(end)
119  return self.events[s:e + 1]
120  def get_events_bounds(self, start, end):
121  s = self.__search(start)
122  e = self.__search(end)
123  return(s, e + 1)
124  def sort(self):
125  self.events.sort(events_cmp)
126  def get_bounds(self):
127  if len(self.events) > 0:
128  lo = self.events[0].at
129  hi = self.events[-1].at
130  return(lo, hi)
131  else:
132  return(0, 0)
133 
134 class Timeline:
135  def __init__(self, name = ''):
136  self.ranges = []
137  self.event_str = []
138  self.event_int = []
139  self.name = name
140  def get_range(self, name):
141  for range in self.ranges:
142  if range.name == name:
143  return range
144  timeline = TimelineDataRange(name)
145  self.ranges.append(timeline)
146  return timeline
147  def get_event_str(self, name):
148  for event_str in self.event_str:
149  if event_str.name == name:
150  return event_str
151  timeline = TimelineEvent(name)
152  self.event_str.append(timeline)
153  return timeline
154  def get_event_int(self, name):
155  for event_int in self.event_int:
156  if event_int.name == name:
157  return event_int
158  timeline = TimelineEvent(name)
159  self.event_int.append(timeline)
160  return timeline
161  def get_ranges(self):
162  return self.ranges
163  def get_events_str(self):
164  return self.event_str
165  def get_events_int(self):
166  return self.event_int
167  def sort(self):
168  for range in self.ranges:
169  range.sort()
170  for event in self.event_int:
171  event.sort()
172  for event in self.event_str:
173  event.sort()
174  def get_bounds(self):
175  lo = 0
176  hi = 0
177  for range in self.ranges:
178  (range_lo, range_hi) = range.get_bounds()
179  if range_lo < lo:
180  lo = range_lo
181  if range_hi > hi:
182  hi = range_hi
183  for event_str in self.event_str:
184  (ev_lo, ev_hi) = event_str.get_bounds()
185  if ev_lo < lo:
186  lo = ev_lo
187  if ev_hi > hi:
188  hi = ev_hi
189  for event_int in self.event_int:
190  (ev_lo, ev_hi) = event_int.get_bounds()
191  if ev_lo < lo:
192  lo = ev_lo
193  if ev_hi > hi:
194  hi = ev_hi
195  return(lo, hi)
196 class Timelines:
197  def __init__(self):
198  self.timelines = []
199  def get(self, name):
200  for timeline in self.timelines:
201  if timeline.name == name:
202  return timeline
203  timeline = Timeline(name)
204  self.timelines.append(timeline)
205  return timeline
206  def get_all(self):
207  return self.timelines
208  def sort(self):
209  for timeline in self.timelines:
210  timeline.sort()
211  def get_bounds(self):
212  lo = 0
213  hi = 0
214  for timeline in self.timelines:
215  (t_lo, t_hi) = timeline.get_bounds()
216  if t_lo < lo:
217  lo = t_lo
218  if t_hi > hi:
219  hi = t_hi
220  return(lo, hi)
222  range_values = {}
223  for timeline in self.timelines:
224  for ranges in timeline.get_ranges():
225  for ran in ranges.get_all():
226  range_values[ran.value] = 1
227  return range_values.keys()
228 class Color:
229  def __init__(self, r = 0.0, g = 0.0, b = 0.0):
230  self.r = r
231  self.g = g
232  self.b = b
233  def set(self, r, g, b):
234  self.r = r
235  self.g = g
236  self.b = b
237 class Colors:
238  # XXX add more
239  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)]
240  def __init__(self):
241  self.__colors = {}
242  def add(self, name, color):
243  self.__colors[name] = color
244  def lookup(self, name):
245  if not self.__colors.has_key(name):
246  self.add(name, self.default_colors.pop())
247  return self.__colors.get(name)
248 
249 
251  def __init__(self):
252  self.__padding = 10
253  def set_padding(self, padding):
254  self.__padding = padding
255  def set_legends(self, legends, colors):
256  self.__legends = legends
257  self.__colors = colors
258  def layout(self, width):
259  self.__width = width
260  surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, 1)
261  ctx = cairo.Context(surface)
262  line_height = 0
263  total_height = self.__padding
264  line_used = self.__padding
265  for legend in self.__legends:
266  (t_width, t_height) = ctx.text_extents(legend)[2:4]
267  item_width = self.__padding + self.__padding + t_width + self.__padding
268  item_height = t_height + self.__padding
269  if item_height > line_height:
270  line_height = item_height
271  if line_used + item_width > self.__width:
272  line_used = self.__padding + item_width
273  total_height += line_height
274  else:
275  line_used += item_width
276  x = line_used - item_width
277  total_height += line_height
278  self.__height = total_height
279 
280  def get_height(self):
281  return self.__height
282  def draw(self, ctx):
283  i = 0
284  line_height = 0
285  total_height = self.__padding
286  line_used = self.__padding
287  for legend in self.__legends:
288  (t_width, t_height) = ctx.text_extents(legend)[2:4]
289  item_width = self.__padding + self.__padding + t_width + self.__padding
290  item_height = t_height + self.__padding
291  if item_height > line_height:
292  line_height = item_height
293  if line_used + item_width > self.__width:
294  line_used = self.__padding + item_width
295  total_height += line_height
296  else:
297  line_used += item_width
298  x = line_used - item_width
299  ctx.rectangle(x, total_height, self.__padding, self.__padding)
300  ctx.set_source_rgb(0, 0, 0)
301  ctx.set_line_width(2)
302  ctx.stroke_preserve()
303  ctx.set_source_rgb(self.__colors[i].r,
304  self.__colors[i].g,
305  self.__colors[i].b)
306  ctx.fill()
307  ctx.move_to(x + self.__padding*2, total_height + t_height)
308  ctx.set_source_rgb(0, 0, 0)
309  ctx.show_text(legend)
310  i += 1
311 
312  return
313 
315  def __init__(self):
316  self.padding = 10
317  return
318  def get_height(self):
319  return self.height
320  def set_timelines(self, timelines, colors):
321  self.timelines = timelines
322  self.colors = colors
323  def set_render_range(self, start, end):
324  self.start = start
325  self.end = end
326  def get_data_x_start(self):
327  return self.padding / 2 + self.left_width + self.padding + self.right_width + self.padding / 2
328  def layout(self, width):
329  surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, 1)
330  ctx = cairo.Context(surface)
331  max_text_height = ctx.text_extents("ABCDEFGHIJKLMNOPQRSTUVWXYZabcedefghijklmnopqrstuvwxyz0123456789")[3]
332 
333  left_width = 0
334  right_width = 0
335  left_n_lines = 0
336  range_n = 0
337  eventint_n = 0
338  eventstr_n = 0
339  for timeline in self.timelines.get_all():
340  left_n_lines += 1
341  t_width = ctx.text_extents(timeline.name)[2]
342  left_width = max(left_width, t_width)
343  for rang in timeline.get_ranges():
344  t_width = ctx.text_extents(rang.name)[2]
345  right_width = max(right_width, t_width)
346  range_n += 1
347  for events_int in timeline.get_events_int():
348  t_width = ctx.text_extents(events_int.name)[2]
349  right_width = max(right_width, t_width)
350  eventint_n += 1
351  for events_str in timeline.get_events_str():
352  t_width = ctx.text_extents(events_str.name)[2]
353  right_width = max(right_width, t_width)
354  eventstr_n += 1
355 
356  left_height = left_n_lines * max_text_height + (left_n_lines - 1) * self.padding
357  right_n_lines = range_n + eventint_n + eventstr_n
358  right_height = (right_n_lines - 1) * self.padding + right_n_lines * max_text_height
359  right_data_height = (eventint_n + eventstr_n) * (max_text_height + 5) + range_n * 10
360  right_data_height += (right_n_lines - 1) * self.padding
361 
362  height = max(left_height, right_height)
363  height = max(height, right_data_height)
364 
365  self.left_width = left_width
366  self.right_width = right_width
367  self.max_text_height = max_text_height
368  self.width = width
369  self.height = height + self.padding
370  def draw_line(self, ctx, x, y, width, height):
371  ctx.move_to(x, y)
372  ctx.rel_line_to(width, height)
373  ctx.close_path()
374  ctx.set_operator(cairo.OPERATOR_SOURCE)
375  ctx.set_line_width(1.0)
376  ctx.set_source_rgb(0, 0, 0)
377  ctx.stroke()
378  def draw_events(self, ctx, events, x, y, width, height):
379  if (self.grey_background % 2) == 0:
380  ctx.rectangle(x, y - self.padding / 2,
381  width, height + self.padding)
382  ctx.set_source_rgb(0.9, 0.9, 0.9)
383  ctx.fill()
384  last_x_drawn = int(x)
385  (lo, hi) = events.get_events_bounds(self.start, self.end)
386  for event in events.events[lo:hi]:
387  real_x = int(x + (event.at - self.start) * width / (self.end - self.start))
388  if real_x > last_x_drawn + 2:
389  ctx.rectangle(real_x, y, 1, 1)
390  ctx.set_source_rgb(1, 0, 0)
391  ctx.stroke()
392  ctx.move_to(real_x, y + self.max_text_height)
393  ctx.set_source_rgb(0, 0, 0)
394  ctx.show_text(str(event.value))
395  last_x_drawn = real_x
396  self.grey_background += 1
397  def draw_ranges(self, ctx, ranges, x, y, width, height):
398  if (self.grey_background % 2) == 0:
399  ctx.rectangle(x, y - self.padding / 2,
400  width, height + self.padding)
401  ctx.set_source_rgb(0.9, 0.9, 0.9)
402  ctx.fill()
403  last_x_drawn = int(x - 1)
404  (lo, hi) = ranges.get_ranges_bounds(self.start, self.end)
405  for data_range in ranges.ranges[lo:hi]:
406  s = max(data_range.start, self.start)
407  e = min(data_range.end, self.end)
408  x_start = int(x + (s - self.start) * width / (self.end - self.start))
409  x_end = int(x + (e - self.start) * width / (self.end - self.start))
410  if x_end > last_x_drawn:
411  ctx.rectangle(x_start, y, x_end - x_start, 10)
412  ctx.set_source_rgb(0, 0, 0)
413  ctx.stroke_preserve()
414  color = self.colors.lookup(data_range.value)
415  ctx.set_source_rgb(color.r, color.g, color.b)
416  ctx.fill()
417  last_x_drawn = x_end
418 
419  self.grey_background += 1
420 
421  def draw(self, ctx):
422  timeline_top = 0
423  top_y = self.padding / 2
424  left_x_start = self.padding / 2
425  left_x_end = left_x_start + self.left_width
426  right_x_start = left_x_end + self.padding
427  right_x_end = right_x_start + self.right_width
428  data_x_start = right_x_end + self.padding / 2
429  data_x_end = self.width
430  data_width = data_x_end - data_x_start
431  cur_y = top_y
432  self.draw_line(ctx, 0, 0, self.width, 0)
434  for timeline in self.timelines.get_all():
435  (y_bearing, t_width, t_height) = ctx.text_extents(timeline.name)[1:4]
436  ctx.move_to(left_x_start, cur_y + self.max_text_height - (t_height + y_bearing))
437  ctx.show_text(timeline.name);
438  for events_int in timeline.get_events_int():
439  (y_bearing, t_width, t_height) = ctx.text_extents(events_int.name)[1:4]
440  ctx.move_to(right_x_start, cur_y + self.max_text_height - (t_height + y_bearing))
441  ctx.show_text(events_int.name)
442  self.draw_events(ctx, events_int, data_x_start, cur_y, data_width, self.max_text_height + 5)
443  cur_y += self.max_text_height + 5 + self.padding
444  self.draw_line(ctx, right_x_start - self.padding / 2, cur_y - self.padding / 2,
445  self.right_width + self.padding, 0)
446 
447  for events_str in timeline.get_events_str():
448  (y_bearing, t_width, t_height) = ctx.text_extents(events_str.name)[1:4]
449  ctx.move_to(right_x_start, cur_y + self.max_text_height - (t_height + y_bearing))
450  ctx.show_text(events_str.name)
451  self.draw_events(ctx, events_str, data_x_start, cur_y, data_width, self.max_text_height + 5)
452  cur_y += self.max_text_height + 5 + self.padding
453  self.draw_line(ctx, right_x_start - self.padding / 2, cur_y - self.padding / 2,
454  self.right_width + self.padding, 0)
455  for ranges in timeline.get_ranges():
456  (y_bearing, t_width, t_height) = ctx.text_extents(ranges.name)[1:4]
457  ctx.move_to(right_x_start, cur_y + self.max_text_height - (t_height + y_bearing))
458  ctx.show_text(ranges.name)
459  self.draw_ranges(ctx, ranges, data_x_start, cur_y, data_width, 10)
460  cur_y += self.max_text_height + self.padding
461  self.draw_line(ctx, right_x_start - self.padding / 2, cur_y - self.padding / 2,
462  self.right_width + self.padding, 0)
463  self.draw_line(ctx, 0, cur_y - self.padding / 2,
464  self.width, 0)
465  bot_y = cur_y - self.padding / 2
466  self.draw_line(ctx, left_x_end + self.padding / 2, 0,
467  0, bot_y)
468  self.draw_line(ctx, right_x_end + self.padding / 2, 0,
469  0, bot_y)
470  return
471 
473  def __init__(self):
474  self.__top = 0
475  return
476  def set_bounds(self, lo, hi):
477  self.__lo = lo
478  self.__hi = hi
479  def get_position(self, x):
480  real_x = (x - self.__lo ) * self.__width / (self.__hi - self.__lo)
481  return real_x
482  def set_top(self):
483  self.__top = 1
484  def set_bot(self):
485  self.__top = 0
486  def layout(self, width):
487  surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, 1)
488  ctx = cairo.Context(surface)
489 
490  # calculate scale delta
491  data_delta = self.__hi - self.__lo
492  closest = 1
493  while (closest*10) < data_delta:
494  closest *= 10
495  if (data_delta / closest) == 0:
496  delta = closest
497  elif(data_delta / closest) == 1:
498  delta = closest / 10
499  else:
500  delta = closest
501  start = self.__lo - (self.__lo % delta) + delta
502  end = self.__hi - (self.__hi % delta)
503 
504  self.__delta = delta
505  self.__width = width
506 
507  # calculate text height
508  max_text_height = ctx.text_extents("ABCDEFGHIJKLMNOPQRSTUVWXYZabcedefghijklmnopqrstuvwxyz0123456789")[3]
509  self.max_text_height = max_text_height
510  height = max_text_height + 10
511  self.__height = height
512 
513  def get_height(self):
514  return self.__height
515  def draw(self, ctx):
516  delta = self.__delta
517  start = self.__lo - (self.__lo % delta) + delta
518  end = self.__hi - (self.__hi % delta)
519 
520  if self.__top == 1:
521  s = -1
522  else:
523  s = 1
524  # print scale points
525  ctx.set_source_rgb(0, 0, 0)
526  ctx.set_line_width(1.0)
527  ticks = range(int(start), int(end + delta), int(delta))
528  for x in ticks:
529  real_x = (x - self.__lo ) * self.__width / (self.__hi - self.__lo)
530  ctx.move_to(real_x, 0)
531  ctx.line_to(real_x, 5*s)
532  ctx.close_path()
533  ctx.stroke()
534  (t_y_bearing, t_width, t_height) = ctx.text_extents(str(x))[1:4]
535  if self.__top:
536  text_delta = t_height + t_y_bearing
537  else:
538  text_delta = -t_y_bearing
539  ctx.move_to(real_x - t_width / 2, (5 + 5 + text_delta)*s)
540  ctx.show_text(str(x))
541  # draw subticks
542  delta /= 10
543  if delta > 0:
544  start = self.__lo - (self.__lo % delta) + delta
545  end = self.__hi - (self.__hi % delta)
546  for x in range(int(start), int(end + delta), int(delta)):
547  real_x = (x - self.__lo ) * self.__width / (self.__hi - self.__lo)
548  ctx.move_to(real_x, 0)
549  ctx.line_to(real_x, 3*s)
550  ctx.close_path()
551  ctx.stroke()
552 
553 
554 
556  def __init__(self, start, end):
557  self.__start = float(start)
558  self.__end = float(end)
560  self.__mid_scale.set_top()
562  self.__bot_scale.set_bounds(start, end)
563  self.__bot_scale.set_bot()
564  self.__width = 1
565  self.__height = 1
566  def get_width(self):
567  return self.__width
568  def get_height(self):
569  return self.__height
570  # return x, y, width, height
572  y_start = self.__top_legend.get_height()
573  x_start = self.__data.get_data_x_start()
574  return(x_start, y_start, self.__width - x_start, self.__data.get_height())
575  def scale_data(self, x):
576  x_start = self.__data.get_data_x_start()
577  x_scaled = x / (self.__width - x_start) * (self.__r_end - self.__r_start)
578  return x_scaled
579  # return x, y, width, height
581  y_start = self.__top_legend.get_height() + self.__data.get_height() + self.__mid_scale.get_height() + 20
582  y_height = self.__bot_scale.get_height() + 20
583  x_start = self.__bot_scale.get_position(self.__r_start)
584  x_end = self.__bot_scale.get_position(self.__r_end)
585  return(x_start, y_start, x_end - x_start, y_height)
586  def scale_selection(self, x):
587  x_scaled = x / self.__width * (self.__end - self.__start)
588  return x_scaled
589  def set_range(self, start, end):
590  s = min(start, end)
591  e = max(start, end)
592  start = max(self.__start, s)
593  end = min(self.__end, e)
594  self.__r_start = start
595  self.__r_end = end
596  self.__data.set_render_range(start, end)
597  self.__mid_scale.set_bounds(start, end)
598  self.layout(self.__width, self.__height)
599  def get_range(self):
600  return(self.__r_start, self.__r_end)
601  def set_data(self, data):
602  self.__data = data
603  def set_top_legend(self, top_legend):
604  self.__top_legend = top_legend
605  def layout(self, width, height):
606  self.__width = width
607  self.__height = height
608  self.__top_legend.layout(width)
609  top_legend_height = self.__top_legend.get_height()
610  self.__data.layout(width)
611  self.__mid_scale.layout(width - self.__data.get_data_x_start())
612  self.__bot_scale.layout(width)
613  return
614  def __x_pixel(self, x, width):
615  new_x = (x - self.__start) * width / (self.__end - self.__start)
616  return new_x
617 
618  def draw(self, ctx):
619  # default background is white
620  ctx.save()
621  ctx.set_source_rgb(1, 1, 1)
622  ctx.set_operator(cairo.OPERATOR_SOURCE)
623  ctx.rectangle(0, 0, self.__width, self.__height)
624  ctx.fill()
625 
626  # top legend
627  ctx.save()
628  self.__top_legend.draw(ctx)
629  top_legend_height = self.__top_legend.get_height()
630  ctx.restore()
631 
632  # separation line
633  ctx.move_to(0, top_legend_height)
634  ctx.line_to(self.__width, top_legend_height)
635  ctx.close_path()
636  ctx.set_line_width(2)
637  ctx.set_source_rgb(0, 0, 0)
638  ctx.stroke()
639 
640  # data
641  ctx.save()
642  ctx.translate(0,
643  top_legend_height)
644  self.__data.draw(ctx)
645  ctx.restore()
646 
647  # scale below data
648  ctx.save()
649  ctx.translate(self.__data.get_data_x_start(),
650  top_legend_height + self.__data.get_height() + self.__mid_scale.get_height())
651  self.__mid_scale.draw(ctx)
652  ctx.restore()
653 
654  height_used = top_legend_height + self.__data.get_height() + self.__mid_scale.get_height()
655 
656  # separation between scale and left pane
657  ctx.move_to(self.__data.get_data_x_start(), height_used)
658  ctx.rel_line_to(0, -self.__mid_scale.get_height())
659  ctx.close_path()
660  ctx.set_source_rgb(0, 0, 0)
661  ctx.set_line_width(2)
662  ctx.stroke()
663 
664  # separation below scale
665  ctx.move_to(0, height_used)
666  ctx.line_to(self.__width, height_used)
667  ctx.close_path()
668  ctx.set_line_width(2)
669  ctx.set_source_rgb(0, 0, 0)
670  ctx.stroke()
671 
672  select_start = self.__bot_scale.get_position(self.__r_start)
673  select_end = self.__bot_scale.get_position(self.__r_end)
674 
675  # left connection between top scale and bottom scale
676  ctx.move_to(0, height_used);
677  ctx.line_to(self.__data.get_data_x_start(), height_used)
678  ctx.line_to(select_start, height_used + 20)
679  ctx.line_to(0, height_used + 20)
680  ctx.line_to(0, height_used)
681  ctx.set_source_rgb(0, 0, 0)
682  ctx.set_line_width(1)
683  ctx.stroke_preserve()
684  ctx.set_source_rgb(0.9, 0.9, 0.9)
685  ctx.fill()
686 
687  # right connection between top scale and bottom scale
688  ctx.move_to(self.__width, height_used)
689  ctx.line_to(self.__width, height_used + 20)
690  ctx.line_to(select_end, height_used + 20)
691  ctx.line_to(self.__width, height_used)
692  ctx.set_source_rgb(0, 0, 0)
693  ctx.set_line_width(1)
694  ctx.stroke_preserve()
695  ctx.set_source_rgb(0.9, 0.9, 0.9)
696  ctx.fill()
697 
698  height_used += 20
699 
700  # unused area background
701  unused_start = self.__bot_scale.get_position(self.__r_start)
702  unused_end = self.__bot_scale.get_position(self.__r_end)
703  unused_height = self.__bot_scale.get_height() + 20
704  ctx.rectangle(0, height_used,
705  unused_start,
706  unused_height)
707  ctx.rectangle(unused_end,
708  height_used,
709  self.__width - unused_end,
710  unused_height)
711  ctx.set_source_rgb(0.9, 0.9, 0.9)
712  ctx.fill()
713 
714  # border line around bottom scale
715  ctx.move_to(unused_end, height_used)
716  ctx.line_to(self.__width, height_used)
717  ctx.line_to(self.__width, height_used + unused_height)
718  ctx.line_to(0, height_used + unused_height)
719  ctx.line_to(0, height_used)
720  ctx.line_to(unused_start, height_used)
721  ctx.close_path()
722  ctx.set_line_width(2)
723  ctx.set_source_rgb(0, 0, 0)
724  ctx.stroke()
725  ctx.move_to(unused_start, height_used)
726  ctx.line_to(unused_end, height_used)
727  ctx.close_path()
728  ctx.set_line_width(1)
729  ctx.set_source_rgb(0.9, 0.9, 0.9)
730  ctx.stroke()
731 
732  # unused area dot borders
733  ctx.save()
734  ctx.move_to(max(unused_start, 2), height_used)
735  ctx.rel_line_to(0, unused_height)
736  ctx.move_to(min(unused_end, self.__width - 2), height_used)
737  ctx.rel_line_to(0, unused_height)
738  ctx.set_dash([5], 0)
739  ctx.set_source_rgb(0, 0, 0)
740  ctx.set_line_width(1)
741  ctx.stroke()
742  ctx.restore()
743 
744  # bottom scale
745  ctx.save()
746  ctx.translate(0, height_used)
747  self.__bot_scale.draw(ctx)
748  ctx.restore()
749 
750 class GtkGraphicRenderer(gtk.DrawingArea):
751  def __init__(self, data):
752  super(GtkGraphicRenderer, self).__init__()
753  self.__data = data
754  self.__moving_left = False
755  self.__moving_right = False
756  self.__moving_both = False
757  self.__moving_top = False
759  self.add_events(gtk.gdk.POINTER_MOTION_MASK)
760  self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
761  self.add_events(gtk.gdk.BUTTON_RELEASE_MASK)
762  self.connect("expose_event", self.expose)
763  self.connect('size-allocate', self.size_allocate)
764  self.connect('motion-notify-event', self.motion_notify)
765  self.connect('button-press-event', self.button_press)
766  self.connect('button-release-event', self.button_release)
767  def set_smaller_zoom(self):
768  (start, end) = self.__data.get_range()
769  self.__data.set_range(start, start + (end - start)*2)
770  self.__force_full_redraw = True
771  self.queue_draw()
772  def set_bigger_zoom(self):
773  (start, end) = self.__data.get_range()
774  self.__data.set_range(start, start + (end - start) / 2)
775  self.__force_full_redraw = True
776  self.queue_draw()
777  def output_png(self, filename):
778  surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
779  self.__data.get_width(),
780  self.__data.get_height())
781  ctx = cairo.Context(self.__buffer_surface)
782  self.__data.draw(ctx)
783  surface.write_to_png(filename)
784  def button_press(self, widget, event):
785  (x, y, width, height) = self.__data.get_selection_rectangle()
786  (d_x, d_y, d_width, d_height) = self.__data.get_data_rectangle()
787  if event.y > y and event.y < y + height:
788  if abs(event.x - x) < 5:
789  self.__moving_left = True
790  return True
791  if abs(event.x - (x + width)) < 5:
792  self.__moving_right = True
793  return True
794  if event.x > x and event.x < x + width:
795  self.__moving_both = True
796  self.__moving_both_start = event.x
797  self.__moving_both_cur = event.x
798  return True
799  if event.y > d_y and event.y < (d_y + d_height):
800  if event.x > d_x and event.x < (d_x + d_width):
801  self.__moving_top = True
802  self.__moving_top_start = event.x
803  self.__moving_top_cur = event.x
804  return True
805  return False
806  def button_release(self, widget, event):
807  if self.__moving_left:
808  self.__moving_left = False
809  left = self.__data.scale_selection(self.__moving_left_cur)
810  right = self.__data.get_range()[1]
811  self.__data.set_range(left, right)
812  self.__force_full_redraw = True
813  self.queue_draw()
814  return True
815  if self.__moving_right:
816  self.__moving_right = False
817  right = self.__data.scale_selection(self.__moving_right_cur)
818  left = self.__data.get_range()[0]
819  self.__data.set_range(left, right)
820  self.__force_full_redraw = True
821  self.queue_draw()
822  return True
823  if self.__moving_both:
824  self.__moving_both = False
825  delta = self.__data.scale_selection(self.__moving_both_cur - self.__moving_both_start)
826  (left, right) = self.__data.get_range()
827  self.__data.set_range(left + delta, right + delta)
828  self.__force_full_redraw = True
829  self.queue_draw()
830  return True
831  if self.__moving_top:
832  self.__moving_top = False
833  return False
834  def motion_notify(self, widget, event):
835  (x, y, width, height) = self.__data.get_selection_rectangle()
836  if self.__moving_left:
837  if event.x <= 0:
839  elif event.x >= x + width:
840  self.__moving_left_cur = x + width
841  else:
842  self.__moving_left_cur = event.x
843  self.queue_draw_area(0, int(y), int(self.__width), int(height))
844  return True
845  if self.__moving_right:
846  if event.x >= self.__width:
847  self.__moving_right = self.__width
848  elif event.x < x:
850  else:
851  self.__moving_right_cur = event.x
852  self.queue_draw_area(0, int(y), int(self.__width), int(height))
853  return True
854  if self.__moving_both:
855  cur_e = self.__width - (x + width - self.__moving_both_start)
856  cur_s = (self.__moving_both_start - x)
857  if event.x < cur_s:
858  self.__moving_both_cur = cur_s
859  elif event.x > cur_e:
860  self.__moving_both_cur = cur_e
861  else:
862  self.__moving_both_cur = event.x
863  self.queue_draw_area(0, int(y), int(self.__width), int(height))
864  return True
865  if self.__moving_top:
866  self.__moving_top_cur = event.x
867  delta = self.__data.scale_data(self.__moving_top_start - self.__moving_top_cur)
868  (left, right) = self.__data.get_range()
869  self.__data.set_range(left + delta, right + delta)
870  self.__force_full_redraw = True
871  self.__moving_top_start = event.x
872  self.queue_draw()
873  return True
874  (d_x, d_y, d_width, d_height) = self.__data.get_data_rectangle()
875  if event.y > y and event.y < y + height:
876  if abs(event.x - x) < 5 or abs(event.x - (x + width)) < 5:
877  widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.SB_H_DOUBLE_ARROW))
878  return True
879  if event.x > x and event.x < x + width:
880  widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR))
881  return True
882  if event.y > d_y and event.y < (d_y + d_height):
883  if event.x > d_x and event.x < (d_x + d_width):
884  widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR))
885  return True
886  widget.window.set_cursor(None)
887  return False
888  def size_allocate(self, widget, allocation):
889  self.__width = allocation.width
890  self.__height = allocation.height
891  self.__data.layout(allocation.width, allocation.height)
892  self.__force_full_redraw = True
893  self.queue_draw()
894  def expose(self, widget, event):
895  if self.__force_full_redraw:
896  self.__buffer_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
897  self.__data.get_width(),
898  self.__data.get_height())
899  ctx = cairo.Context(self.__buffer_surface)
900  self.__data.draw(ctx)
901  self.__force_full_redraw = False
902  ctx = widget.window.cairo_create()
903  ctx.rectangle(event.area.x, event.area.y,
904  event.area.width, event.area.height)
905  ctx.clip()
906  ctx.set_source_surface(self.__buffer_surface)
907  ctx.paint()
908  (x, y, width, height) = self.__data.get_selection_rectangle()
909  if self.__moving_left:
910  ctx.move_to(max(self.__moving_left_cur, 2), y)
911  ctx.rel_line_to(0, height)
912  ctx.close_path()
913  ctx.set_line_width(1)
914  ctx.set_source_rgb(0, 0, 0)
915  ctx.stroke()
916  if self.__moving_right:
917  ctx.move_to(min(self.__moving_right_cur, self.__width - 2), y)
918  ctx.rel_line_to(0, height)
919  ctx.close_path()
920  ctx.set_line_width(1)
921  ctx.set_source_rgb(0, 0, 0)
922  ctx.stroke()
923  if self.__moving_both:
924  delta_x = self.__moving_both_cur - self.__moving_both_start
925  left_x = x + delta_x
926  ctx.move_to(x + delta_x, y)
927  ctx.rel_line_to(0, height)
928  ctx.close_path()
929  ctx.move_to(x + width + delta_x, y)
930  ctx.rel_line_to(0, height)
931  ctx.close_path()
932  ctx.set_source_rgb(0, 0, 0)
933  ctx.set_line_width(1)
934  ctx.stroke()
935  return False
936 
938  def __init__(self):
939  return
940  def run(self, graphic):
941  window = gtk.Window()
942  self.__window = window
943  window.set_default_size(200, 200)
944  vbox = gtk.VBox()
945  window.add(vbox)
946  render = GtkGraphicRenderer(graphic)
947  self.__render = render
948  vbox.pack_end(render, True, True, 0)
949  hbox = gtk.HBox()
950  vbox.pack_start(hbox, False, False, 0)
951  smaller_zoom = gtk.Button("Zoom Out")
952  smaller_zoom.connect("clicked", self.__set_smaller_cb)
953  hbox.pack_start(smaller_zoom)
954  bigger_zoom = gtk.Button("Zoom In")
955  bigger_zoom.connect("clicked", self.__set_bigger_cb)
956  hbox.pack_start(bigger_zoom)
957  output_png = gtk.Button("Output Png")
958  output_png.connect("clicked", self.__output_png_cb)
959  hbox.pack_start(output_png)
960  window.connect('destroy', gtk.main_quit)
961  window.show_all()
962  #gtk.bindings_activate(gtk.main_quit, 'q', 0)
963  gtk.main()
964  def __set_smaller_cb(self, widget):
965  self.__render.set_smaller_zoom()
966  def __set_bigger_cb(self, widget):
967  self.__render.set_bigger_zoom()
968  def __output_png_cb(self, widget):
969  dialog = gtk.FileChooserDialog("Output Png", self.__window,
970  gtk.FILE_CHOOSER_ACTION_SAVE, ("Save", 1))
971  self.__dialog = dialog
972  dialog.set_default_response(1)
973  dialog.connect("response", self.__dialog_response_cb)
974  dialog.show()
975  return
976  def __dialog_response_cb(self, widget, response):
977  if response == 1:
978  filename = self.__dialog.get_filename()
979  self.__render.output_png(filename)
980  widget.hide()
981  return
982 
983 
984 
985 def read_data(filename):
986  timelines = Timelines()
987  colors = Colors()
988  fh = open(filename)
989  m1 = re.compile('range ([^ ]+) ([^ ]+) ([^ ]+) ([0-9]+) ([0-9]+)')
990  m2 = re.compile('event-str ([^ ]+) ([^ ]+) ([^ ]+) ([0-9]+)')
991  m3 = re.compile('event-int ([^ ]+) ([^ ]+) ([0-9]+) ([0-9]+)')
992  m4 = re.compile('color ([^ ]+) #([a-fA-F0-9]{2,2})([a-fA-F0-9]{2,2})([a-fA-F0-9]{2,2})')
993  for line in fh.readlines():
994  m = m1.match(line)
995  if m:
996  line_name = m.group(1)
997  timeline = timelines.get(m.group(1))
998  rang = timeline.get_range(m.group(2))
999  data_range = DataRange()
1000  data_range.value = m.group(3)
1001  data_range.start = int(m.group(4))
1002  data_range.end = int(m.group(5))
1003  rang.add_range(data_range)
1004  continue
1005  m = m2.match(line)
1006  if m:
1007  line_name = m.group(1)
1008  timeline = timelines.get(m.group(1))
1009  ev = timeline.get_event_str(m.group(2))
1010  event = EventString()
1011  event.value = m.group(3)
1012  event.at = int(m.group(4))
1013  ev.add_event(event)
1014  continue
1015  m = m3.match(line)
1016  if m:
1017  line_name = m.group(1)
1018  timeline = timelines.get(m.group(1))
1019  ev = timeline.get_event_int(m.group(2))
1020  event = EventInt()
1021  event.value = int(m.group(3))
1022  event.at = int(m.group(4))
1023  ev.add_event(event)
1024  continue
1025 
1026  m = m4.match(line)
1027  if m:
1028  r = int(m.group(2), 16)
1029  g = int(m.group(3), 16)
1030  b = int(m.group(4), 16)
1031  color = Color(r / 255, g / 255, b / 255)
1032  colors.add(m.group(1), color)
1033  continue
1034  timelines.sort()
1035  return (colors, timelines)
1036 
1037 
1038 
1039 def main():
1040  (colors, timelines) = read_data(sys.argv[1])
1041  (lower_bound, upper_bound) = timelines.get_bounds()
1042  graphic = GraphicRenderer(lower_bound, upper_bound)
1043  top_legend = TopLegendRenderer()
1044  range_values = timelines.get_all_range_values()
1045  range_colors = []
1046  for range_value in range_values:
1047  range_colors.append(colors.lookup(range_value))
1048  top_legend.set_legends(range_values,
1049  range_colors)
1050  graphic.set_top_legend(top_legend)
1051  data = TimelinesRenderer()
1052  data.set_timelines(timelines, colors)
1053  graphic.set_data(data)
1054 
1055  # default range
1056  range_mid = (upper_bound - lower_bound) / 2
1057  range_width = (upper_bound - lower_bound) / 10
1058  range_lo = range_mid - range_width / 2
1059  range_hi = range_mid + range_width / 2
1060  graphic.set_range(range_lo, range_hi)
1061 
1062  main_window = MainWindow()
1063  main_window.run(graphic)
1064 
1065 
1066 main()
def __init__
Definition: grid.py:240
def __dialog_response_cb
Definition: grid.py:976
def __init__
Definition: grid.py:12
def events_cmp
Definition: grid.py:36
def get_data_rectangle
Definition: grid.py:571
def __init__
Definition: grid.py:938
def get_ranges
Definition: grid.py:161
def __init__
Definition: grid.py:135
def sort
Definition: grid.py:167
def get_events_bounds
Definition: grid.py:120
def set
Definition: grid.py:233
def __init__
Definition: grid.py:197
def get_event_int
Definition: grid.py:154
def get_events_str
Definition: grid.py:163
def get_position
Definition: grid.py:479
def get_all
Definition: grid.py:206
def read_data
Definition: grid.py:985
def lookup
Definition: grid.py:244
def ranges_cmp
Definition: grid.py:28
def get_range
Definition: grid.py:140
def get_events_int
Definition: grid.py:165
def __init__
Definition: grid.py:17
def get_all_range_values
Definition: grid.py:221
def get_selection_rectangle
Definition: grid.py:580
def __init__
Definition: grid.py:229
def __set_smaller_cb
Definition: grid.py:964
def main
Definition: grid.py:1039
def add
Definition: grid.py:242
def __set_bigger_cb
Definition: grid.py:966
def get_bounds
Definition: grid.py:174
def get_bounds
Definition: grid.py:211
def __init__
Definition: grid.py:21
def sort
Definition: grid.py:208
def __init__
Definition: grid.py:25
def __output_png_cb
Definition: grid.py:968
def get_event_str
Definition: grid.py:147