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