| # Copyright (c) 2013 ARM Limited | |
| # All rights reserved | |
| # | |
| # The license below extends only to copyright in the software and shall | |
| # not be construed as granting a license to any other intellectual | |
| # property including but not limited to intellectual property relating | |
| # to a hardware implementation of the functionality of the software | |
| # licensed hereunder. You may use the software subject to the license | |
| # terms below provided that you ensure that this notice is replicated | |
| # unmodified and in its entirety in all distributions of the software, | |
| # modified or unmodified, in source code or in binary form. | |
| # | |
| # Redistribution and use in source and binary forms, with or without | |
| # modification, are permitted provided that the following conditions are | |
| # met: redistributions of source code must retain the above copyright | |
| # notice, this list of conditions and the following disclaimer; | |
| # redistributions in binary form must reproduce the above copyright | |
| # notice, this list of conditions and the following disclaimer in the | |
| # documentation and/or other materials provided with the distribution; | |
| # neither the name of the copyright holders nor the names of its | |
| # contributors may be used to endorse or promote products derived from | |
| # this software without specific prior written permission. | |
| # | |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| # | |
| # Authors: Andrew Bardsley | |
| # | |
| # blobs.py: Blobs are the visual blocks, arrows and other coloured | |
| # objects on the visualiser. This file contains Blob definition and | |
| # their rendering instructions in pygtk/cairo. | |
| # | |
| import pygtk | |
| pygtk.require('2.0') | |
| import gtk | |
| import gobject | |
| import cairo | |
| import re | |
| import math | |
| from point import Point | |
| import parse | |
| import colours | |
| from colours import backgroundColour, black | |
| import model | |
| def centre_size_to_sides(centre, size): | |
| """Returns a 4-tuple of the relevant ordinates of the left, | |
| right, top and bottom sides of the described rectangle""" | |
| (x, y) = centre.to_pair() | |
| (half_width, half_height) = (size.scale(0.5)).to_pair() | |
| left = x - half_width | |
| right = x + half_width | |
| top = y - half_height | |
| bottom = y + half_height | |
| return (left, right, top, bottom) | |
| def box(cr, centre, size): | |
| """Draw a simple box""" | |
| (left, right, top, bottom) = centre_size_to_sides(centre, size) | |
| cr.move_to(left, top) | |
| cr.line_to(right, top) | |
| cr.line_to(right, bottom) | |
| cr.line_to(left, bottom) | |
| cr.close_path() | |
| def stroke_and_fill(cr, colour): | |
| """Stroke with the current colour then fill the same path with the | |
| given colour""" | |
| join = cr.get_line_join() | |
| cr.set_line_join(gtk.gdk.JOIN_ROUND) | |
| cr.close_path() | |
| cr.set_source_color(backgroundColour) | |
| cr.stroke_preserve() | |
| cr.set_source_color(colour) | |
| cr.fill() | |
| cr.set_line_join(join) | |
| def striped_box(cr, centre, size, colours): | |
| """Fill a rectangle (without outline) striped with the colours given""" | |
| num_colours = len(colours) | |
| if num_colours == 0: | |
| box(cr, centre, size) | |
| cr.set_source_color(backgroundColour) | |
| cr.fill() | |
| elif num_colours == 1: | |
| box(cr, centre, size) | |
| stroke_and_fill(cr, colours[0]) | |
| else: | |
| (left, right, top, bottom) = centre_size_to_sides(centre, size) | |
| (width, height) = size.to_pair() | |
| x_stripe_width = width / num_colours | |
| half_x_stripe_width = x_stripe_width / 2.0 | |
| # Left triangle | |
| cr.move_to(left, bottom) | |
| cr.line_to(left + half_x_stripe_width, bottom) | |
| cr.line_to(left + x_stripe_width + half_x_stripe_width, top) | |
| cr.line_to(left, top) | |
| stroke_and_fill(cr, colours[0]) | |
| # Stripes | |
| for i in xrange(1, num_colours - 1): | |
| xOffset = x_stripe_width * i | |
| cr.move_to(left + xOffset - half_x_stripe_width, bottom) | |
| cr.line_to(left + xOffset + half_x_stripe_width, bottom) | |
| cr.line_to(left + xOffset + x_stripe_width + | |
| half_x_stripe_width, top) | |
| cr.line_to(left + xOffset + x_stripe_width - | |
| half_x_stripe_width, top) | |
| stroke_and_fill(cr, colours[i]) | |
| # Right triangle | |
| cr.move_to((right - x_stripe_width) - half_x_stripe_width, bottom) | |
| cr.line_to(right, bottom) | |
| cr.line_to(right, top) | |
| cr.line_to((right - x_stripe_width) + half_x_stripe_width, top) | |
| stroke_and_fill(cr, colours[num_colours - 1]) | |
| def speech_bubble(cr, top_left, size, unit): | |
| """Draw a speech bubble with 'size'-sized internal space with its | |
| top left corner at Point(2.0 * unit, 2.0 * unit)""" | |
| def local_arc(centre, angleFrom, angleTo): | |
| cr.arc(centre.x, centre.y, unit, angleFrom * math.pi, | |
| angleTo * math.pi) | |
| cr.move_to(*top_left.to_pair()) | |
| cr.rel_line_to(unit * 2.0, unit) | |
| cr.rel_line_to(size.x, 0.0) | |
| local_arc(top_left + Point(size.x + unit * 2.0, unit * 2.0), -0.5, 0.0) | |
| cr.rel_line_to(0.0, size.y) | |
| local_arc(top_left + Point(size.x + unit * 2.0, size.y + unit * 2.0), | |
| 0, 0.5) | |
| cr.rel_line_to(-size.x, 0.0) | |
| local_arc(top_left + Point(unit * 2.0, size.y + unit * 2.0), 0.5, 1.0) | |
| cr.rel_line_to(0, -size.y) | |
| cr.close_path() | |
| def open_bottom(cr, centre, size): | |
| """Draw a box with left, top and right sides""" | |
| (left, right, top, bottom) = centre_size_to_sides(centre, size) | |
| cr.move_to(left, bottom) | |
| cr.line_to(left, top) | |
| cr.line_to(right, top) | |
| cr.line_to(right, bottom) | |
| def fifo(cr, centre, size): | |
| """Draw just the vertical sides of a box""" | |
| (left, right, top, bottom) = centre_size_to_sides(centre, size) | |
| cr.move_to(left, bottom) | |
| cr.line_to(left, top) | |
| cr.move_to(right, bottom) | |
| cr.line_to(right, top) | |
| def cross(cr, centre, size): | |
| """Draw a cross parallel with the axes""" | |
| (left, right, top, bottom) = centre_size_to_sides(centre, size) | |
| (x, y) = centre.to_pair() | |
| cr.move_to(left, y) | |
| cr.line_to(right, y) | |
| cr.move_to(x, top) | |
| cr.line_to(x, bottom) | |
| class Blob(object): | |
| """Blob super class""" | |
| def __init__(self, picChar, unit, topLeft, colour, size = Point(1,1)): | |
| self.picChar = picChar | |
| self.unit = unit | |
| self.displayName = unit | |
| self.nameLoc = 'top' | |
| self.topLeft = topLeft | |
| self.colour = colour | |
| self.size = size | |
| self.border = 1.0 | |
| self.dataSelect = model.BlobDataSelect() | |
| self.shorten = 0 | |
| def render(self, cr, view, event, select, time): | |
| """Render this blob with the given event's data. Returns either | |
| None or a pair of (centre, size) in device coordinates for the drawn | |
| blob. The return value can be used to detect if mouse clicks on | |
| the canvas are within the blob""" | |
| return None | |
| class Block(Blob): | |
| """Blocks are rectangular blogs colourable with a 2D grid of striped | |
| blocks. visualDecoder specifies how event data becomes this coloured | |
| grid""" | |
| def __init__(self, picChar, unit, topLeft=Point(0,0), | |
| colour=colours.black, | |
| size=Point(1,1)): | |
| super(Block,self).__init__(picChar, unit, topLeft, colour, | |
| size = size) | |
| # {horiz, vert} | |
| self.stripDir = 'horiz' | |
| # {LR, RL}: LR means the first strip will be on the left/top, | |
| # RL means the first strip will be on the right/bottom | |
| self.stripOrd = 'LR' | |
| # Number of blank strips if this is a frame | |
| self.blankStrips = 0 | |
| # {box, fifo, openBottom} | |
| self.shape = 'box' | |
| self.visualDecoder = None | |
| def render(self, cr, view, event, select, time): | |
| # Find the right event, visuals and sizes for things | |
| if event is None or self.displayName.startswith('_'): | |
| event = model.BlobEvent(self.unit, time) | |
| if self.picChar in event.visuals: | |
| strips = event.visuals[self.picChar].to_striped_block( | |
| select & self.dataSelect) | |
| else: | |
| strips = [[[colours.unknownColour]]] | |
| if self.stripOrd == 'RL': | |
| strips.reverse() | |
| if len(strips) == 0: | |
| strips = [[colours.errorColour]] | |
| print 'Problem with the colour of event:', event | |
| num_strips = len(strips) | |
| strip_proportion = 1.0 / num_strips | |
| first_strip_offset = (num_strips / 2.0) - 0.5 | |
| # Adjust blocks with 'shorten' attribute to the length of the data | |
| size = Point(*self.size.to_pair()) | |
| if self.shorten != 0 and self.size.x > (num_strips * self.shorten): | |
| size.x = num_strips * self.shorten | |
| box_size = size - view.blobIndentFactor.scale(2) | |
| # Now do cr sensitive things | |
| cr.save() | |
| cr.scale(*view.pitch.to_pair()) | |
| cr.translate(*self.topLeft.to_pair()) | |
| cr.translate(*(size - Point(1,1)).scale(0.5).to_pair()) | |
| translated_centre = Point(*cr.user_to_device(0.0, 0.0)) | |
| translated_size = \ | |
| Point(*cr.user_to_device_distance(*size.to_pair())) | |
| # The 2D grid is a grid of strips of blocks. Data [[1,2],[3]] | |
| # is 2 strips of 2 and 1 blocks respectively. | |
| # if stripDir == 'horiz', strips are stacked vertically | |
| # from top to bottom if stripOrd == 'LR' or bottom to top if | |
| # stripOrd == 'RL'. | |
| # if stripDir == 'vert', strips are stacked horizontally | |
| # from left to right if stripOf == 'LR' or right to left if | |
| # stripOrd == 'RL'. | |
| strip_is_horiz = self.stripDir == 'horiz' | |
| if strip_is_horiz: | |
| strip_step_base = Point(1.0,0.0) | |
| block_step_base = Point(0.0,1.0) | |
| else: | |
| strip_step_base = Point(0.0,1.0) | |
| block_step_base = Point(1.0,0.0) | |
| strip_size = (box_size * (strip_step_base.scale(strip_proportion) + | |
| block_step_base)) | |
| strip_step = strip_size * strip_step_base | |
| strip_centre = Point(0,0) - (strip_size * | |
| strip_step_base.scale(first_strip_offset)) | |
| cr.set_line_width(view.midLineWidth / view.pitch.x) | |
| # Draw the strips and their blocks | |
| for strip_index in xrange(0, num_strips): | |
| num_blocks = len(strips[strip_index]) | |
| block_proportion = 1.0 / num_blocks | |
| firstBlockOffset = (num_blocks / 2.0) - 0.5 | |
| block_size = (strip_size * | |
| (block_step_base.scale(block_proportion) + | |
| strip_step_base)) | |
| block_step = block_size * block_step_base | |
| block_centre = (strip_centre + strip_step.scale(strip_index) - | |
| (block_size * block_step_base.scale(firstBlockOffset))) | |
| for block_index in xrange(0, num_blocks): | |
| striped_box(cr, block_centre + | |
| block_step.scale(block_index), block_size, | |
| strips[strip_index][block_index]) | |
| cr.set_font_size(0.7) | |
| if self.border > 0.5: | |
| weight = cairo.FONT_WEIGHT_BOLD | |
| else: | |
| weight = cairo.FONT_WEIGHT_NORMAL | |
| cr.select_font_face('Helvetica', cairo.FONT_SLANT_NORMAL, | |
| weight) | |
| xb, yb, width, height, dx, dy = cr.text_extents(self.displayName) | |
| text_comfort_space = 0.15 | |
| if self.nameLoc == 'left': | |
| # Position text vertically along left side, top aligned | |
| cr.save() | |
| cr.rotate(- (math.pi / 2.0)) | |
| text_point = Point(size.y, size.x).scale(0.5) * Point(-1, -1) | |
| text_point += Point(max(0, size.y - width), 0) | |
| text_point += Point(-text_comfort_space, -text_comfort_space) | |
| else: # Including top | |
| # Position text above the top left hand corner | |
| text_point = size.scale(0.5) * Point(-1,-1) | |
| text_point += Point(0.00, -text_comfort_space) | |
| if (self.displayName != '' and | |
| not self.displayName.startswith('_')): | |
| cr.set_source_color(self.colour) | |
| cr.move_to(*text_point.to_pair()) | |
| cr.show_text(self.displayName) | |
| if self.nameLoc == 'left': | |
| cr.restore() | |
| # Draw the outline shape | |
| cr.save() | |
| if strip_is_horiz: | |
| cr.rotate(- (math.pi / 2.0)) | |
| box_size = Point(box_size.y, box_size.x) | |
| if self.stripOrd == "RL": | |
| cr.rotate(math.pi) | |
| if self.shape == 'box': | |
| box(cr, Point(0,0), box_size) | |
| elif self.shape == 'openBottom': | |
| open_bottom(cr, Point(0,0), box_size) | |
| elif self.shape == 'fifo': | |
| fifo(cr, Point(0,0), box_size) | |
| cr.restore() | |
| # Restore scale and stroke the outline | |
| cr.restore() | |
| cr.set_source_color(self.colour) | |
| cr.set_line_width(view.thickLineWidth * self.border) | |
| cr.stroke() | |
| # Return blob size/position | |
| if self.unit == '_': | |
| return None | |
| else: | |
| return (translated_centre, translated_size) | |
| class Key(Blob): | |
| """Draw a key to the special (and numeric colours) with swatches of the | |
| colours half as wide as the key""" | |
| def __init__(self, picChar, unit, topLeft, colour=colours.black, | |
| size=Point(1,1)): | |
| super(Key,self).__init__(picChar, unit, topLeft, colour, size = size) | |
| self.colours = 'BBBB' | |
| self.displayName = unit | |
| def render(self, cr, view, event, select, time): | |
| cr.save() | |
| cr.scale(*view.pitch.to_pair()) | |
| cr.translate(*self.topLeft.to_pair()) | |
| # cr.translate(*(self.size - Point(1,1)).scale(0.5).to_pair()) | |
| half_width = self.size.x / 2.0 | |
| cr.translate(*(self.size - Point(1.0 + half_width,1.0)).scale(0.5). | |
| to_pair()) | |
| num_colours = len(self.colours) | |
| cr.set_line_width(view.midLineWidth / view.pitch.x) | |
| blob_size = (Point(half_width,0.0) + | |
| (self.size * Point(0.0,1.0 / num_colours))) | |
| blob_step = Point(0.0,1.0) * blob_size | |
| first_blob_centre = (Point(0.0,0.0) - | |
| blob_step.scale((num_colours / 2.0) - 0.5)) | |
| cr.set_source_color(self.colour) | |
| cr.set_line_width(view.thinLineWidth / view.pitch.x) | |
| blob_proportion = 0.8 | |
| real_blob_size = blob_size.scale(blob_proportion) | |
| cr.set_font_size(0.8 * blob_size.y * blob_proportion) | |
| cr.select_font_face('Helvetica', cairo.FONT_SLANT_NORMAL, | |
| cairo.FONT_WEIGHT_BOLD) | |
| for i in xrange(0, num_colours): | |
| centre = first_blob_centre + blob_step.scale(i) | |
| box(cr, centre, real_blob_size) | |
| colour_char = self.colours[i] | |
| if colour_char.isdigit(): | |
| cr.set_source_color(colours.number_to_colour( | |
| int(colour_char))) | |
| label = '...' + colour_char | |
| else: | |
| cr.set_source_color(model.special_state_colours[colour_char]) | |
| label = model.special_state_names[colour_char] | |
| cr.fill_preserve() | |
| cr.set_source_color(self.colour) | |
| cr.stroke() | |
| xb, yb, width, height, dx, dy = cr.text_extents(label) | |
| text_left = (centre + (Point(0.5,0.0) * blob_size) + | |
| Point(0.0, height / 2.0)) | |
| cr.move_to(*text_left.to_pair()) | |
| cr.show_text(label) | |
| class Arrow(Blob): | |
| """Draw a left or right facing arrow""" | |
| def __init__(self, unit, topLeft, colour=colours.black, | |
| size=Point(1.0,1.0), direc='right'): | |
| super(Arrow,self).__init__(unit, unit, topLeft, colour, size = size) | |
| self.direc = direc | |
| def render(self, cr, view, event, select, time): | |
| cr.save() | |
| cr.scale(*view.pitch.to_pair()) | |
| cr.translate(*self.topLeft.to_pair()) | |
| cr.translate(*(self.size - Point(1,1)).scale(0.5).to_pair()) | |
| cr.scale(*self.size.to_pair()) | |
| (blob_indent_x, blob_indent_y) = \ | |
| (view.blobIndentFactor / self.size).to_pair() | |
| left = -0.5 - blob_indent_x | |
| right = 0.5 + blob_indent_x | |
| thickness = 0.2 | |
| flare = 0.2 | |
| if self.direc == 'left': | |
| cr.rotate(math.pi) | |
| cr.move_to(left, -thickness) | |
| cr.line_to(0.0, -thickness) | |
| cr.line_to(0.0, -(thickness + flare)) | |
| cr.line_to(right, 0) | |
| # Break arrow to prevent the point ruining the appearance of boxes | |
| cr.move_to(right, 0) | |
| cr.line_to(0.0, (thickness + flare)) | |
| cr.line_to(0.0, +thickness) | |
| cr.line_to(left, +thickness) | |
| cr.restore() | |
| # Draw arrow a bit more lightly than the standard line width | |
| cr.set_line_width(cr.get_line_width() * 0.75) | |
| cr.set_source_color(self.colour) | |
| cr.stroke() | |
| return None |