110259SAndrew.Bardsley@arm.com# Copyright (c) 2013 ARM Limited
210259SAndrew.Bardsley@arm.com# All rights reserved
310259SAndrew.Bardsley@arm.com#
410259SAndrew.Bardsley@arm.com# The license below extends only to copyright in the software and shall
510259SAndrew.Bardsley@arm.com# not be construed as granting a license to any other intellectual
610259SAndrew.Bardsley@arm.com# property including but not limited to intellectual property relating
710259SAndrew.Bardsley@arm.com# to a hardware implementation of the functionality of the software
810259SAndrew.Bardsley@arm.com# licensed hereunder.  You may use the software subject to the license
910259SAndrew.Bardsley@arm.com# terms below provided that you ensure that this notice is replicated
1010259SAndrew.Bardsley@arm.com# unmodified and in its entirety in all distributions of the software,
1110259SAndrew.Bardsley@arm.com# modified or unmodified, in source code or in binary form.
1210259SAndrew.Bardsley@arm.com#
1310259SAndrew.Bardsley@arm.com# Redistribution and use in source and binary forms, with or without
1410259SAndrew.Bardsley@arm.com# modification, are permitted provided that the following conditions are
1510259SAndrew.Bardsley@arm.com# met: redistributions of source code must retain the above copyright
1610259SAndrew.Bardsley@arm.com# notice, this list of conditions and the following disclaimer;
1710259SAndrew.Bardsley@arm.com# redistributions in binary form must reproduce the above copyright
1810259SAndrew.Bardsley@arm.com# notice, this list of conditions and the following disclaimer in the
1910259SAndrew.Bardsley@arm.com# documentation and/or other materials provided with the distribution;
2010259SAndrew.Bardsley@arm.com# neither the name of the copyright holders nor the names of its
2110259SAndrew.Bardsley@arm.com# contributors may be used to endorse or promote products derived from
2210259SAndrew.Bardsley@arm.com# this software without specific prior written permission.
2310259SAndrew.Bardsley@arm.com#
2410259SAndrew.Bardsley@arm.com# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
2510259SAndrew.Bardsley@arm.com# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2610259SAndrew.Bardsley@arm.com# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2710259SAndrew.Bardsley@arm.com# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2810259SAndrew.Bardsley@arm.com# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2910259SAndrew.Bardsley@arm.com# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
3010259SAndrew.Bardsley@arm.com# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
3110259SAndrew.Bardsley@arm.com# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
3210259SAndrew.Bardsley@arm.com# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3310259SAndrew.Bardsley@arm.com# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3410259SAndrew.Bardsley@arm.com# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3510259SAndrew.Bardsley@arm.com#
3610259SAndrew.Bardsley@arm.com# Authors: Andrew Bardsley
3710259SAndrew.Bardsley@arm.com
3810259SAndrew.Bardsley@arm.comimport pygtk
3910259SAndrew.Bardsley@arm.compygtk.require('2.0')
4010259SAndrew.Bardsley@arm.comimport gtk
4110259SAndrew.Bardsley@arm.comimport gobject
4210259SAndrew.Bardsley@arm.comimport cairo
4310259SAndrew.Bardsley@arm.comimport re
4410259SAndrew.Bardsley@arm.com
4510259SAndrew.Bardsley@arm.comfrom point import Point
4610259SAndrew.Bardsley@arm.comimport parse
4710259SAndrew.Bardsley@arm.comimport colours
4810259SAndrew.Bardsley@arm.comimport model
4910259SAndrew.Bardsley@arm.comfrom model import Id, BlobModel, BlobDataSelect, special_state_chars
5010259SAndrew.Bardsley@arm.comimport blobs
5110259SAndrew.Bardsley@arm.com
5210259SAndrew.Bardsley@arm.comclass BlobView(object):
5310259SAndrew.Bardsley@arm.com    """The canvas view of the pipeline"""
5410259SAndrew.Bardsley@arm.com    def __init__(self, model):
5510259SAndrew.Bardsley@arm.com        # A unit blob will appear at size blobSize inside a space of
5610259SAndrew.Bardsley@arm.com        #   size pitch.
5710259SAndrew.Bardsley@arm.com        self.blobSize = Point(45.0, 45.0)
5810259SAndrew.Bardsley@arm.com        self.pitch = Point(60.0, 60.0)
5910259SAndrew.Bardsley@arm.com        self.origin = Point(50.0, 50.0)
6010259SAndrew.Bardsley@arm.com        # Some common line definitions to cut down on arbitrary
6110259SAndrew.Bardsley@arm.com        #   set_line_widths
6210259SAndrew.Bardsley@arm.com        self.thickLineWidth = 10.0
6310259SAndrew.Bardsley@arm.com        self.thinLineWidth = 4.0
6410259SAndrew.Bardsley@arm.com        self.midLineWidth = 6.0
6510259SAndrew.Bardsley@arm.com        # The scale from the units of pitch to device units (nominally
6610259SAndrew.Bardsley@arm.com        #   pixels for 1.0 to 1.0
6710259SAndrew.Bardsley@arm.com        self.masterScale = Point(1.0,1.0)
6810259SAndrew.Bardsley@arm.com        self.model = model
6910259SAndrew.Bardsley@arm.com        self.fillColour = colours.emptySlotColour
7010259SAndrew.Bardsley@arm.com        self.timeIndex = 0
7110259SAndrew.Bardsley@arm.com        self.time = 0
7210259SAndrew.Bardsley@arm.com        self.positions = []
7310259SAndrew.Bardsley@arm.com        self.controlbar = None
7410259SAndrew.Bardsley@arm.com        # The sequence number selector state
7510259SAndrew.Bardsley@arm.com        self.dataSelect = BlobDataSelect()
7610259SAndrew.Bardsley@arm.com        # Offset of this view's time from self.time used for miniviews
7710259SAndrew.Bardsley@arm.com        #   This is actually an offset of the index into the array of times
7810259SAndrew.Bardsley@arm.com        #   seen in the event file)
7910259SAndrew.Bardsley@arm.com        self.timeOffset = 0
8010259SAndrew.Bardsley@arm.com        # Maximum view size for initial window mapping
8110259SAndrew.Bardsley@arm.com        self.initialHeight = 600.0
8210259SAndrew.Bardsley@arm.com
8310259SAndrew.Bardsley@arm.com        # Overlays are speech bubbles explaining blob data
8410259SAndrew.Bardsley@arm.com        self.overlays = []
8510259SAndrew.Bardsley@arm.com
8610259SAndrew.Bardsley@arm.com        self.da = gtk.DrawingArea()
8710259SAndrew.Bardsley@arm.com        def draw(arg1, arg2):
8810259SAndrew.Bardsley@arm.com            self.redraw()
8910259SAndrew.Bardsley@arm.com        self.da.connect('expose_event', draw)
9010259SAndrew.Bardsley@arm.com
9110259SAndrew.Bardsley@arm.com        # Handy offsets from the blob size
9210259SAndrew.Bardsley@arm.com        self.blobIndent = (self.pitch - self.blobSize).scale(0.5)
9310259SAndrew.Bardsley@arm.com        self.blobIndentFactor = self.blobIndent / self.pitch
9410259SAndrew.Bardsley@arm.com
9510259SAndrew.Bardsley@arm.com    def add_control_bar(self, controlbar):
9610259SAndrew.Bardsley@arm.com        """Add a BlobController to this view"""
9710259SAndrew.Bardsley@arm.com        self.controlbar = controlbar
9810259SAndrew.Bardsley@arm.com
9910259SAndrew.Bardsley@arm.com    def draw_to_png(self, filename):
10010259SAndrew.Bardsley@arm.com        """Draw the view to a PNG file"""
10110259SAndrew.Bardsley@arm.com        surface = cairo.ImageSurface(
10210259SAndrew.Bardsley@arm.com            cairo.FORMAT_ARGB32,
10310259SAndrew.Bardsley@arm.com            self.da.get_allocation().width,
10410259SAndrew.Bardsley@arm.com            self.da.get_allocation().height)
10510259SAndrew.Bardsley@arm.com        cr = gtk.gdk.CairoContext(cairo.Context(surface))
10610259SAndrew.Bardsley@arm.com        self.draw_to_cr(cr)
10710259SAndrew.Bardsley@arm.com        surface.write_to_png(filename)
10810259SAndrew.Bardsley@arm.com
10910259SAndrew.Bardsley@arm.com    def draw_to_cr(self, cr):
11010259SAndrew.Bardsley@arm.com        """Draw to a given CairoContext"""
11110259SAndrew.Bardsley@arm.com        cr.set_source_color(colours.backgroundColour)
11210259SAndrew.Bardsley@arm.com        cr.set_line_width(self.thickLineWidth)
11310259SAndrew.Bardsley@arm.com        cr.paint()
11410259SAndrew.Bardsley@arm.com        cr.save()
11510259SAndrew.Bardsley@arm.com        cr.scale(*self.masterScale.to_pair())
11610259SAndrew.Bardsley@arm.com        cr.translate(*self.origin.to_pair())
11710259SAndrew.Bardsley@arm.com
11810259SAndrew.Bardsley@arm.com        positions = [] # {}
11910259SAndrew.Bardsley@arm.com
12010259SAndrew.Bardsley@arm.com        # Draw each blob
12110259SAndrew.Bardsley@arm.com        for blob in self.model.blobs:
12210259SAndrew.Bardsley@arm.com            blob_event = self.model.find_unit_event_by_time(
12310259SAndrew.Bardsley@arm.com                blob.unit, self.time)
12410259SAndrew.Bardsley@arm.com
12510259SAndrew.Bardsley@arm.com            cr.save()
12610259SAndrew.Bardsley@arm.com            pos = blob.render(cr, self, blob_event, self.dataSelect,
12710259SAndrew.Bardsley@arm.com                self.time)
12810259SAndrew.Bardsley@arm.com            cr.restore()
12910259SAndrew.Bardsley@arm.com            if pos is not None:
13010259SAndrew.Bardsley@arm.com                (centre, size) = pos
13110259SAndrew.Bardsley@arm.com                positions.append((blob, centre, size))
13210259SAndrew.Bardsley@arm.com
13310259SAndrew.Bardsley@arm.com        # Draw all the overlays over the top
13410259SAndrew.Bardsley@arm.com        for overlay in self.overlays:
13510259SAndrew.Bardsley@arm.com            overlay.show(cr)
13610259SAndrew.Bardsley@arm.com
13710259SAndrew.Bardsley@arm.com        cr.restore()
13810259SAndrew.Bardsley@arm.com
13910259SAndrew.Bardsley@arm.com        return positions
14010259SAndrew.Bardsley@arm.com
14110259SAndrew.Bardsley@arm.com    def redraw(self):
14210259SAndrew.Bardsley@arm.com        """Redraw the whole view"""
14310259SAndrew.Bardsley@arm.com        buffer = cairo.ImageSurface(
14410259SAndrew.Bardsley@arm.com            cairo.FORMAT_ARGB32,
14510259SAndrew.Bardsley@arm.com            self.da.get_allocation().width,
14610259SAndrew.Bardsley@arm.com            self.da.get_allocation().height)
14710259SAndrew.Bardsley@arm.com
14810259SAndrew.Bardsley@arm.com        cr = gtk.gdk.CairoContext(cairo.Context(buffer))
14910259SAndrew.Bardsley@arm.com        positions = self.draw_to_cr(cr)
15010259SAndrew.Bardsley@arm.com
15110259SAndrew.Bardsley@arm.com        # Assume that blobs are in order for depth so we want to
15210259SAndrew.Bardsley@arm.com        #   hit the frontmost blob first if we search by position
15310259SAndrew.Bardsley@arm.com        positions.reverse()
15410259SAndrew.Bardsley@arm.com        self.positions = positions
15510259SAndrew.Bardsley@arm.com
15610259SAndrew.Bardsley@arm.com        # Paint the drawn buffer onto the DrawingArea
15710259SAndrew.Bardsley@arm.com        dacr = self.da.window.cairo_create()
15810259SAndrew.Bardsley@arm.com        dacr.set_source_surface(buffer, 0.0, 0.0)
15910259SAndrew.Bardsley@arm.com        dacr.paint()
16010259SAndrew.Bardsley@arm.com
16110259SAndrew.Bardsley@arm.com        buffer.finish()
16210259SAndrew.Bardsley@arm.com
16310259SAndrew.Bardsley@arm.com    def set_time_index(self, time):
16410259SAndrew.Bardsley@arm.com        """Set the time index for the view.  A time index is an index into
16510259SAndrew.Bardsley@arm.com        the model's times array of seen event times"""
16610259SAndrew.Bardsley@arm.com        self.timeIndex = time + self.timeOffset
16710259SAndrew.Bardsley@arm.com        if len(self.model.times) != 0:
16810259SAndrew.Bardsley@arm.com            if self.timeIndex >= len(self.model.times):
16910259SAndrew.Bardsley@arm.com                self.time = self.model.times[len(self.model.times) - 1]
17010259SAndrew.Bardsley@arm.com            else:
17110259SAndrew.Bardsley@arm.com                self.time = self.model.times[self.timeIndex]
17210259SAndrew.Bardsley@arm.com        else:
17310259SAndrew.Bardsley@arm.com            self.time = 0
17410259SAndrew.Bardsley@arm.com
17510259SAndrew.Bardsley@arm.com    def get_pic_size(self):
17610259SAndrew.Bardsley@arm.com        """Return the size of ASCII-art picture of the pipeline scaled by
17710259SAndrew.Bardsley@arm.com        the blob pitch"""
17810259SAndrew.Bardsley@arm.com        return (self.origin + self.pitch *
17910259SAndrew.Bardsley@arm.com            (self.model.picSize + Point(1.0,1.0)))
18010259SAndrew.Bardsley@arm.com
18110259SAndrew.Bardsley@arm.com    def set_da_size(self):
18210259SAndrew.Bardsley@arm.com        """Set the DrawingArea size after scaling"""
18310259SAndrew.Bardsley@arm.com        self.da.set_size_request(10 , int(self.initialHeight))
18410259SAndrew.Bardsley@arm.com
18510259SAndrew.Bardsley@arm.comclass BlobController(object):
18610259SAndrew.Bardsley@arm.com    """The controller bar for the viewer"""
18710259SAndrew.Bardsley@arm.com    def __init__(self, model, view,
18810259SAndrew.Bardsley@arm.com        defaultEventFile="", defaultPictureFile=""):
18910259SAndrew.Bardsley@arm.com        self.model = model
19010259SAndrew.Bardsley@arm.com        self.view = view
19110259SAndrew.Bardsley@arm.com        self.playTimer = None
19210259SAndrew.Bardsley@arm.com        self.filenameEntry = gtk.Entry()
19310259SAndrew.Bardsley@arm.com        self.filenameEntry.set_text(defaultEventFile)
19410259SAndrew.Bardsley@arm.com        self.pictureEntry = gtk.Entry()
19510259SAndrew.Bardsley@arm.com        self.pictureEntry.set_text(defaultPictureFile)
19610259SAndrew.Bardsley@arm.com        self.timeEntry = None
19710259SAndrew.Bardsley@arm.com        self.defaultEventFile = defaultEventFile
19810259SAndrew.Bardsley@arm.com        self.startTime = None
19910259SAndrew.Bardsley@arm.com        self.endTime = None
20010259SAndrew.Bardsley@arm.com
20110259SAndrew.Bardsley@arm.com        self.otherViews = []
20210259SAndrew.Bardsley@arm.com
20310259SAndrew.Bardsley@arm.com        def make_bar(elems):
20410259SAndrew.Bardsley@arm.com            box = gtk.HBox(homogeneous=False, spacing=2)
20510259SAndrew.Bardsley@arm.com            box.set_border_width(2)
20610259SAndrew.Bardsley@arm.com            for widget, signal, handler in elems:
20710259SAndrew.Bardsley@arm.com                if signal is not None:
20810259SAndrew.Bardsley@arm.com                    widget.connect(signal, handler)
20910259SAndrew.Bardsley@arm.com                box.pack_start(widget, False, True, 0)
21010259SAndrew.Bardsley@arm.com            return box
21110259SAndrew.Bardsley@arm.com
21210259SAndrew.Bardsley@arm.com        self.timeEntry = gtk.Entry()
21310259SAndrew.Bardsley@arm.com
21410259SAndrew.Bardsley@arm.com        t = gtk.ToggleButton('T')
21510259SAndrew.Bardsley@arm.com        t.set_active(False)
21610259SAndrew.Bardsley@arm.com        s = gtk.ToggleButton('S')
21710259SAndrew.Bardsley@arm.com        s.set_active(True)
21810259SAndrew.Bardsley@arm.com        p = gtk.ToggleButton('P')
21910259SAndrew.Bardsley@arm.com        p.set_active(True)
22010259SAndrew.Bardsley@arm.com        l = gtk.ToggleButton('L')
22110259SAndrew.Bardsley@arm.com        l.set_active(True)
22210259SAndrew.Bardsley@arm.com        f = gtk.ToggleButton('F')
22310259SAndrew.Bardsley@arm.com        f.set_active(True)
22410259SAndrew.Bardsley@arm.com        e = gtk.ToggleButton('E')
22510259SAndrew.Bardsley@arm.com        e.set_active(True)
22610259SAndrew.Bardsley@arm.com
22710259SAndrew.Bardsley@arm.com        # Should really generate this from above
22810259SAndrew.Bardsley@arm.com        self.view.dataSelect.ids = set("SPLFE")
22910259SAndrew.Bardsley@arm.com
23010259SAndrew.Bardsley@arm.com        self.bar = gtk.VBox()
23110259SAndrew.Bardsley@arm.com        self.bar.set_homogeneous(False)
23210259SAndrew.Bardsley@arm.com
23310259SAndrew.Bardsley@arm.com        row1 = make_bar([
23410259SAndrew.Bardsley@arm.com            (gtk.Button('Start'), 'clicked', self.time_start),
23510259SAndrew.Bardsley@arm.com            (gtk.Button('End'), 'clicked', self.time_end),
23610259SAndrew.Bardsley@arm.com            (gtk.Button('Back'), 'clicked', self.time_back),
23710259SAndrew.Bardsley@arm.com            (gtk.Button('Forward'), 'clicked', self.time_forward),
23810259SAndrew.Bardsley@arm.com            (gtk.Button('Play'), 'clicked', self.time_play),
23910259SAndrew.Bardsley@arm.com            (gtk.Button('Stop'), 'clicked', self.time_stop),
24010259SAndrew.Bardsley@arm.com            (self.timeEntry, 'activate', self.time_set),
24110259SAndrew.Bardsley@arm.com            (gtk.Label('Visible ids:'), None, None),
24210259SAndrew.Bardsley@arm.com            (t, 'clicked', self.toggle_id('T')),
24310259SAndrew.Bardsley@arm.com            (gtk.Label('/'), None, None),
24410259SAndrew.Bardsley@arm.com            (s, 'clicked', self.toggle_id('S')),
24510259SAndrew.Bardsley@arm.com            (gtk.Label('.'), None, None),
24610259SAndrew.Bardsley@arm.com            (p, 'clicked', self.toggle_id('P')),
24710259SAndrew.Bardsley@arm.com            (gtk.Label('/'), None, None),
24810259SAndrew.Bardsley@arm.com            (l, 'clicked', self.toggle_id('L')),
24910259SAndrew.Bardsley@arm.com            (gtk.Label('/'), None, None),
25010259SAndrew.Bardsley@arm.com            (f, 'clicked', self.toggle_id('F')),
25110259SAndrew.Bardsley@arm.com            (gtk.Label('.'), None, None),
25210259SAndrew.Bardsley@arm.com            (e, 'clicked', self.toggle_id('E')),
25310259SAndrew.Bardsley@arm.com            (self.filenameEntry, 'activate', self.load_events),
25410259SAndrew.Bardsley@arm.com            (gtk.Button('Reload'), 'clicked', self.load_events)
25510259SAndrew.Bardsley@arm.com            ])
25610259SAndrew.Bardsley@arm.com
25710259SAndrew.Bardsley@arm.com        self.bar.pack_start(row1, False, True, 0)
25810259SAndrew.Bardsley@arm.com        self.set_time_index(0)
25910259SAndrew.Bardsley@arm.com
26010259SAndrew.Bardsley@arm.com    def toggle_id(self, id):
26110259SAndrew.Bardsley@arm.com        """One of the sequence number selector buttons has been toggled"""
26210259SAndrew.Bardsley@arm.com        def toggle(button):
26310259SAndrew.Bardsley@arm.com            if button.get_active():
26410259SAndrew.Bardsley@arm.com                self.view.dataSelect.ids.add(id)
26510259SAndrew.Bardsley@arm.com            else:
26610259SAndrew.Bardsley@arm.com                self.view.dataSelect.ids.discard(id)
26710259SAndrew.Bardsley@arm.com
26810259SAndrew.Bardsley@arm.com            # Always leave one thing visible
26910259SAndrew.Bardsley@arm.com            if len(self.view.dataSelect.ids) == 0:
27010259SAndrew.Bardsley@arm.com                self.view.dataSelect.ids.add(id)
27110259SAndrew.Bardsley@arm.com                button.set_active(True)
27210259SAndrew.Bardsley@arm.com            self.view.redraw()
27310259SAndrew.Bardsley@arm.com        return toggle
27410259SAndrew.Bardsley@arm.com
27510259SAndrew.Bardsley@arm.com    def set_time_index(self, time):
27610259SAndrew.Bardsley@arm.com        """Set the time index in the view"""
27710259SAndrew.Bardsley@arm.com        self.view.set_time_index(time)
27810259SAndrew.Bardsley@arm.com
27910259SAndrew.Bardsley@arm.com        for view in self.otherViews:
28010259SAndrew.Bardsley@arm.com            view.set_time_index(time)
28110259SAndrew.Bardsley@arm.com            view.redraw()
28210259SAndrew.Bardsley@arm.com
28310259SAndrew.Bardsley@arm.com        self.timeEntry.set_text(str(self.view.time))
28410259SAndrew.Bardsley@arm.com
28510259SAndrew.Bardsley@arm.com    def time_start(self, button):
28610259SAndrew.Bardsley@arm.com        """Start pressed"""
28710259SAndrew.Bardsley@arm.com        self.set_time_index(0)
28810259SAndrew.Bardsley@arm.com        self.view.redraw()
28910259SAndrew.Bardsley@arm.com
29010259SAndrew.Bardsley@arm.com    def time_end(self, button):
29110259SAndrew.Bardsley@arm.com        """End pressed"""
29210259SAndrew.Bardsley@arm.com        self.set_time_index(len(self.model.times) - 1)
29310259SAndrew.Bardsley@arm.com        self.view.redraw()
29410259SAndrew.Bardsley@arm.com
29510259SAndrew.Bardsley@arm.com    def time_forward(self, button):
29610259SAndrew.Bardsley@arm.com        """Step forward pressed"""
29710259SAndrew.Bardsley@arm.com        self.set_time_index(min(self.view.timeIndex + 1,
29810259SAndrew.Bardsley@arm.com            len(self.model.times) - 1))
29910259SAndrew.Bardsley@arm.com        self.view.redraw()
30010259SAndrew.Bardsley@arm.com        gtk.gdk.flush()
30110259SAndrew.Bardsley@arm.com
30210259SAndrew.Bardsley@arm.com    def time_back(self, button):
30310259SAndrew.Bardsley@arm.com        """Step back pressed"""
30410259SAndrew.Bardsley@arm.com        self.set_time_index(max(self.view.timeIndex - 1, 0))
30510259SAndrew.Bardsley@arm.com        self.view.redraw()
30610259SAndrew.Bardsley@arm.com
30710259SAndrew.Bardsley@arm.com    def time_set(self, entry):
30810259SAndrew.Bardsley@arm.com        """Time dialogue changed.  Need to find a suitable time
30910259SAndrew.Bardsley@arm.com        <= the entry's time"""
31010259SAndrew.Bardsley@arm.com        newTime = self.model.find_time_index(int(entry.get_text()))
31110259SAndrew.Bardsley@arm.com        self.set_time_index(newTime)
31210259SAndrew.Bardsley@arm.com        self.view.redraw()
31310259SAndrew.Bardsley@arm.com
31410259SAndrew.Bardsley@arm.com    def time_step(self):
31510259SAndrew.Bardsley@arm.com        """Time step while playing"""
31610259SAndrew.Bardsley@arm.com        if not self.playTimer \
31710259SAndrew.Bardsley@arm.com            or self.view.timeIndex == len(self.model.times) - 1:
31810259SAndrew.Bardsley@arm.com            self.time_stop(None)
31910259SAndrew.Bardsley@arm.com            return False
32010259SAndrew.Bardsley@arm.com        else:
32110259SAndrew.Bardsley@arm.com            self.time_forward(None)
32210259SAndrew.Bardsley@arm.com            return True
32310259SAndrew.Bardsley@arm.com
32410259SAndrew.Bardsley@arm.com    def time_play(self, play):
32510259SAndrew.Bardsley@arm.com        """Automatically advance time every 100 ms"""
32610259SAndrew.Bardsley@arm.com        if not self.playTimer:
32710259SAndrew.Bardsley@arm.com            self.playTimer = gobject.timeout_add(100, self.time_step)
32810259SAndrew.Bardsley@arm.com
32910259SAndrew.Bardsley@arm.com    def time_stop(self, play):
33010259SAndrew.Bardsley@arm.com        """Stop play pressed"""
33110259SAndrew.Bardsley@arm.com        if self.playTimer:
33210259SAndrew.Bardsley@arm.com            gobject.source_remove(self.playTimer)
33310259SAndrew.Bardsley@arm.com            self.playTimer = None
33410259SAndrew.Bardsley@arm.com
33510259SAndrew.Bardsley@arm.com    def load_events(self, button):
33610259SAndrew.Bardsley@arm.com        """Reload events file"""
33710259SAndrew.Bardsley@arm.com        self.model.load_events(self.filenameEntry.get_text(),
33810259SAndrew.Bardsley@arm.com            startTime=self.startTime, endTime=self.endTime)
33910259SAndrew.Bardsley@arm.com        self.set_time_index(min(len(self.model.times) - 1,
34010259SAndrew.Bardsley@arm.com            self.view.timeIndex))
34110259SAndrew.Bardsley@arm.com        self.view.redraw()
34210259SAndrew.Bardsley@arm.com
34310259SAndrew.Bardsley@arm.comclass Overlay(object):
34410259SAndrew.Bardsley@arm.com    """An Overlay is a speech bubble explaining the data in a blob"""
34510259SAndrew.Bardsley@arm.com    def __init__(self, model, view, point, blob):
34610259SAndrew.Bardsley@arm.com        self.model = model
34710259SAndrew.Bardsley@arm.com        self.view = view
34810259SAndrew.Bardsley@arm.com        self.point = point
34910259SAndrew.Bardsley@arm.com        self.blob = blob
35010259SAndrew.Bardsley@arm.com
35110259SAndrew.Bardsley@arm.com    def find_event(self):
35210259SAndrew.Bardsley@arm.com        """Find the event for a changing time and a fixed blob"""
35310259SAndrew.Bardsley@arm.com        return self.model.find_unit_event_by_time(self.blob.unit,
35410259SAndrew.Bardsley@arm.com            self.view.time)
35510259SAndrew.Bardsley@arm.com
35610259SAndrew.Bardsley@arm.com    def show(self, cr):
35710259SAndrew.Bardsley@arm.com        """Draw the overlay"""
35810259SAndrew.Bardsley@arm.com        event = self.find_event()
35910259SAndrew.Bardsley@arm.com
36010259SAndrew.Bardsley@arm.com        if event is None:
36110259SAndrew.Bardsley@arm.com            return
36210259SAndrew.Bardsley@arm.com
36310259SAndrew.Bardsley@arm.com        insts = event.find_ided_objects(self.model, self.blob.picChar,
36410259SAndrew.Bardsley@arm.com            False)
36510259SAndrew.Bardsley@arm.com
36610259SAndrew.Bardsley@arm.com        cr.set_line_width(self.view.thinLineWidth)
36710259SAndrew.Bardsley@arm.com        cr.translate(*(Point(0.0,0.0) - self.view.origin).to_pair())
36810259SAndrew.Bardsley@arm.com        cr.scale(*(Point(1.0,1.0) / self.view.masterScale).to_pair())
36910259SAndrew.Bardsley@arm.com
37010259SAndrew.Bardsley@arm.com        # Get formatted data from the insts to format into a table
37110259SAndrew.Bardsley@arm.com        lines = list(inst.table_line() for inst in insts)
37210259SAndrew.Bardsley@arm.com
37310259SAndrew.Bardsley@arm.com        text_size = 10.0
37410259SAndrew.Bardsley@arm.com        cr.set_font_size(text_size)
37510259SAndrew.Bardsley@arm.com
37610259SAndrew.Bardsley@arm.com        def text_width(str):
37710259SAndrew.Bardsley@arm.com            xb, yb, width, height, dx, dy = cr.text_extents(str)
37810259SAndrew.Bardsley@arm.com            return width
37910259SAndrew.Bardsley@arm.com
38010259SAndrew.Bardsley@arm.com        # Find the maximum number of columns and the widths of each column
38110259SAndrew.Bardsley@arm.com        num_columns = 0
38210259SAndrew.Bardsley@arm.com        for line in lines:
38310259SAndrew.Bardsley@arm.com            num_columns = max(num_columns, len(line))
38410259SAndrew.Bardsley@arm.com
38510259SAndrew.Bardsley@arm.com        widths = [0] * num_columns
38610259SAndrew.Bardsley@arm.com        for line in lines:
38710259SAndrew.Bardsley@arm.com            for i in xrange(0, len(line)):
38810259SAndrew.Bardsley@arm.com                widths[i] = max(widths[i], text_width(line[i]))
38910259SAndrew.Bardsley@arm.com
39010259SAndrew.Bardsley@arm.com        # Calculate the size of the speech bubble
39110259SAndrew.Bardsley@arm.com        column_gap = 1 * text_size
39210259SAndrew.Bardsley@arm.com        id_width = 6 * text_size
39310259SAndrew.Bardsley@arm.com        total_width = sum(widths) + id_width + column_gap * (num_columns + 1)
39410259SAndrew.Bardsley@arm.com        gap_step = Point(1.0, 0.0).scale(column_gap)
39510259SAndrew.Bardsley@arm.com
39610259SAndrew.Bardsley@arm.com        text_point = self.point
39710259SAndrew.Bardsley@arm.com        text_step = Point(0.0, text_size)
39810259SAndrew.Bardsley@arm.com
39910259SAndrew.Bardsley@arm.com        size = Point(total_width, text_size * len(insts))
40010259SAndrew.Bardsley@arm.com
40110259SAndrew.Bardsley@arm.com        # Draw the speech bubble
40210259SAndrew.Bardsley@arm.com        blobs.speech_bubble(cr, self.point, size, text_size)
40310259SAndrew.Bardsley@arm.com        cr.set_source_color(colours.backgroundColour)
40410259SAndrew.Bardsley@arm.com        cr.fill_preserve()
40510259SAndrew.Bardsley@arm.com        cr.set_source_color(colours.black)
40610259SAndrew.Bardsley@arm.com        cr.stroke()
40710259SAndrew.Bardsley@arm.com
40810259SAndrew.Bardsley@arm.com        text_point += Point(1.0,1.0).scale(2.0 * text_size)
40910259SAndrew.Bardsley@arm.com
41010259SAndrew.Bardsley@arm.com        id_size = Point(id_width, text_size)
41110259SAndrew.Bardsley@arm.com
41210259SAndrew.Bardsley@arm.com        # Draw the rows in the table
41310259SAndrew.Bardsley@arm.com        for i in xrange(0, len(insts)):
41410259SAndrew.Bardsley@arm.com            row_point = text_point
41510259SAndrew.Bardsley@arm.com            inst = insts[i]
41610259SAndrew.Bardsley@arm.com            line = lines[i]
41710259SAndrew.Bardsley@arm.com            blobs.striped_box(cr, row_point + id_size.scale(0.5),
41810259SAndrew.Bardsley@arm.com                id_size, inst.id.to_striped_block(self.view.dataSelect))
41910259SAndrew.Bardsley@arm.com            cr.set_source_color(colours.black)
42010259SAndrew.Bardsley@arm.com
42110259SAndrew.Bardsley@arm.com            row_point += Point(1.0, 0.0).scale(id_width)
42210259SAndrew.Bardsley@arm.com            row_point += text_step
42310259SAndrew.Bardsley@arm.com            # Draw the columns of each row
42410259SAndrew.Bardsley@arm.com            for j in xrange(0, len(line)):
42510259SAndrew.Bardsley@arm.com                row_point += gap_step
42610259SAndrew.Bardsley@arm.com                cr.move_to(*row_point.to_pair())
42710259SAndrew.Bardsley@arm.com                cr.show_text(line[j])
42810259SAndrew.Bardsley@arm.com                row_point += Point(1.0, 0.0).scale(widths[j])
42910259SAndrew.Bardsley@arm.com
43010259SAndrew.Bardsley@arm.com            text_point += text_step
43110259SAndrew.Bardsley@arm.com
43210259SAndrew.Bardsley@arm.comclass BlobWindow(object):
43310259SAndrew.Bardsley@arm.com    """The top-level window and its mouse control"""
43410259SAndrew.Bardsley@arm.com    def __init__(self, model, view, controller):
43510259SAndrew.Bardsley@arm.com        self.model = model
43610259SAndrew.Bardsley@arm.com        self.view = view
43710259SAndrew.Bardsley@arm.com        self.controller = controller
43810259SAndrew.Bardsley@arm.com        self.controlbar = None
43910259SAndrew.Bardsley@arm.com        self.window = None
44010259SAndrew.Bardsley@arm.com        self.miniViewCount = 0
44110259SAndrew.Bardsley@arm.com
44210259SAndrew.Bardsley@arm.com    def add_control_bar(self, controlbar):
44310259SAndrew.Bardsley@arm.com        self.controlbar = controlbar
44410259SAndrew.Bardsley@arm.com
44510259SAndrew.Bardsley@arm.com    def show_window(self):
44610259SAndrew.Bardsley@arm.com        self.window = gtk.Window()
44710259SAndrew.Bardsley@arm.com
44810259SAndrew.Bardsley@arm.com        self.vbox = gtk.VBox()
44910259SAndrew.Bardsley@arm.com        self.vbox.set_homogeneous(False)
45010259SAndrew.Bardsley@arm.com        if self.controlbar:
45110259SAndrew.Bardsley@arm.com            self.vbox.pack_start(self.controlbar, False, True, 0)
45210259SAndrew.Bardsley@arm.com        self.vbox.add(self.view.da)
45310259SAndrew.Bardsley@arm.com
45410259SAndrew.Bardsley@arm.com        if self.miniViewCount > 0:
45510259SAndrew.Bardsley@arm.com            self.miniViews = []
45610259SAndrew.Bardsley@arm.com            self.miniViewHBox = gtk.HBox(homogeneous=True, spacing=2)
45710259SAndrew.Bardsley@arm.com
45810259SAndrew.Bardsley@arm.com            # Draw mini views
45910259SAndrew.Bardsley@arm.com            for i in xrange(1, self.miniViewCount + 1):
46010259SAndrew.Bardsley@arm.com                miniView = BlobView(self.model)
46110259SAndrew.Bardsley@arm.com                miniView.set_time_index(0)
46210259SAndrew.Bardsley@arm.com                miniView.masterScale = Point(0.1, 0.1)
46310259SAndrew.Bardsley@arm.com                miniView.set_da_size()
46410259SAndrew.Bardsley@arm.com                miniView.timeOffset = i + 1
46510259SAndrew.Bardsley@arm.com                self.miniViews.append(miniView)
46610259SAndrew.Bardsley@arm.com                self.miniViewHBox.pack_start(miniView.da, False, True, 0)
46710259SAndrew.Bardsley@arm.com
46810259SAndrew.Bardsley@arm.com            self.controller.otherViews = self.miniViews
46910259SAndrew.Bardsley@arm.com            self.vbox.add(self.miniViewHBox)
47010259SAndrew.Bardsley@arm.com
47110259SAndrew.Bardsley@arm.com        self.window.add(self.vbox)
47210259SAndrew.Bardsley@arm.com
47310259SAndrew.Bardsley@arm.com        def show_event(picChar, event):
47410259SAndrew.Bardsley@arm.com            print '**** Comments for', event.unit, \
47510259SAndrew.Bardsley@arm.com                'at time', self.view.time
47610259SAndrew.Bardsley@arm.com            for name, value in event.pairs.iteritems():
47710259SAndrew.Bardsley@arm.com                print name, '=', value
47810259SAndrew.Bardsley@arm.com            for comment in event.comments:
47910259SAndrew.Bardsley@arm.com                print comment
48010259SAndrew.Bardsley@arm.com            if picChar in event.visuals:
48110259SAndrew.Bardsley@arm.com                # blocks = event.visuals[picChar].elems()
48210259SAndrew.Bardsley@arm.com                print '**** Colour data'
48310259SAndrew.Bardsley@arm.com                objs = event.find_ided_objects(self.model, picChar, True)
48410259SAndrew.Bardsley@arm.com                for obj in objs:
48510259SAndrew.Bardsley@arm.com                    print ' '.join(obj.table_line())
48610259SAndrew.Bardsley@arm.com
48710259SAndrew.Bardsley@arm.com        def clicked_da(da, b):
48810259SAndrew.Bardsley@arm.com            point = Point(b.x, b.y)
48910259SAndrew.Bardsley@arm.com
49010259SAndrew.Bardsley@arm.com            overlay = None
49110259SAndrew.Bardsley@arm.com            for blob, centre, size in self.view.positions:
49210259SAndrew.Bardsley@arm.com                if point.is_within_box((centre, size)):
49310259SAndrew.Bardsley@arm.com                    event = self.model.find_unit_event_by_time(blob.unit,
49410259SAndrew.Bardsley@arm.com                        self.view.time)
49510259SAndrew.Bardsley@arm.com                    if event is not None:
49610259SAndrew.Bardsley@arm.com                        if overlay is None:
49710259SAndrew.Bardsley@arm.com                            overlay = Overlay(self.model, self.view, point,
49810259SAndrew.Bardsley@arm.com                                blob)
49910259SAndrew.Bardsley@arm.com                        show_event(blob.picChar, event)
50010259SAndrew.Bardsley@arm.com            if overlay is not None:
50110259SAndrew.Bardsley@arm.com                self.view.overlays = [overlay]
50210259SAndrew.Bardsley@arm.com            else:
50310259SAndrew.Bardsley@arm.com                self.view.overlays = []
50410259SAndrew.Bardsley@arm.com
50510259SAndrew.Bardsley@arm.com            self.view.redraw()
50610259SAndrew.Bardsley@arm.com
50710259SAndrew.Bardsley@arm.com        # Set initial size and event callbacks
50810259SAndrew.Bardsley@arm.com        self.view.set_da_size()
50910259SAndrew.Bardsley@arm.com        self.view.da.add_events(gtk.gdk.BUTTON_PRESS_MASK)
51010259SAndrew.Bardsley@arm.com        self.view.da.connect('button-press-event', clicked_da)
51110259SAndrew.Bardsley@arm.com        self.window.connect('destroy', lambda(widget): gtk.main_quit())
51210259SAndrew.Bardsley@arm.com
51310259SAndrew.Bardsley@arm.com        def resize(window, event):
51410259SAndrew.Bardsley@arm.com            """Resize DrawingArea to match new window size"""
51510259SAndrew.Bardsley@arm.com            size = Point(float(event.width), float(event.height))
51610259SAndrew.Bardsley@arm.com            proportion = size / self.view.get_pic_size()
51710259SAndrew.Bardsley@arm.com            # Preserve aspect ratio
51810259SAndrew.Bardsley@arm.com            daScale = min(proportion.x, proportion.y)
51910259SAndrew.Bardsley@arm.com            self.view.masterScale = Point(daScale, daScale)
52010259SAndrew.Bardsley@arm.com            self.view.overlays = []
52110259SAndrew.Bardsley@arm.com
52210259SAndrew.Bardsley@arm.com        self.view.da.connect('configure-event', resize)
52310259SAndrew.Bardsley@arm.com
52410259SAndrew.Bardsley@arm.com        self.window.show_all()
525