1# Copyright (c) 2013 ARM Limited 2# All rights reserved 3# 4# The license below extends only to copyright in the software and shall 5# not be construed as granting a license to any other intellectual 6# property including but not limited to intellectual property relating 7# to a hardware implementation of the functionality of the software 8# licensed hereunder. You may use the software subject to the license 9# terms below provided that you ensure that this notice is replicated 10# unmodified and in its entirety in all distributions of the software, 11# modified or unmodified, in source code or in binary form. 12# 13# Redistribution and use in source and binary forms, with or without 14# modification, are permitted provided that the following conditions are 15# met: redistributions of source code must retain the above copyright 16# notice, this list of conditions and the following disclaimer; 17# redistributions in binary form must reproduce the above copyright 18# notice, this list of conditions and the following disclaimer in the 19# documentation and/or other materials provided with the distribution; 20# neither the name of the copyright holders nor the names of its 21# contributors may be used to endorse or promote products derived from 22# this software without specific prior written permission. 23# 24# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 27# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 28# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 29# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 30# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 32# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 34# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35# 36# Authors: Andrew Bardsley 37# 38# blobs.py: Blobs are the visual blocks, arrows and other coloured 39# objects on the visualiser. This file contains Blob definition and 40# their rendering instructions in pygtk/cairo. 41# 42 43import pygtk 44pygtk.require('2.0') 45import gtk 46import gobject 47import cairo 48import re 49import math 50 51from point import Point 52import parse 53import colours 54from colours import backgroundColour, black 55import model 56 57def centre_size_to_sides(centre, size): 58 """Returns a 4-tuple of the relevant ordinates of the left, 59 right, top and bottom sides of the described rectangle""" 60 (x, y) = centre.to_pair() 61 (half_width, half_height) = (size.scale(0.5)).to_pair() 62 left = x - half_width 63 right = x + half_width 64 top = y - half_height 65 bottom = y + half_height 66 return (left, right, top, bottom) 67 68def box(cr, centre, size): 69 """Draw a simple box""" 70 (left, right, top, bottom) = centre_size_to_sides(centre, size) 71 cr.move_to(left, top) 72 cr.line_to(right, top) 73 cr.line_to(right, bottom) 74 cr.line_to(left, bottom) 75 cr.close_path() 76 77def stroke_and_fill(cr, colour): 78 """Stroke with the current colour then fill the same path with the 79 given colour""" 80 join = cr.get_line_join() 81 cr.set_line_join(gtk.gdk.JOIN_ROUND) 82 cr.close_path() 83 cr.set_source_color(backgroundColour) 84 cr.stroke_preserve() 85 cr.set_source_color(colour) 86 cr.fill() 87 cr.set_line_join(join) 88 89def striped_box(cr, centre, size, colours): 90 """Fill a rectangle (without outline) striped with the colours given""" 91 num_colours = len(colours) 92 if num_colours == 0: 93 box(cr, centre, size) 94 cr.set_source_color(backgroundColour) 95 cr.fill() 96 elif num_colours == 1: 97 box(cr, centre, size) 98 stroke_and_fill(cr, colours[0]) 99 else: 100 (left, right, top, bottom) = centre_size_to_sides(centre, size) 101 (width, height) = size.to_pair() 102 x_stripe_width = width / num_colours 103 half_x_stripe_width = x_stripe_width / 2.0 104 # Left triangle 105 cr.move_to(left, bottom) 106 cr.line_to(left + half_x_stripe_width, bottom) 107 cr.line_to(left + x_stripe_width + half_x_stripe_width, top) 108 cr.line_to(left, top) 109 stroke_and_fill(cr, colours[0]) 110 # Stripes 111 for i in xrange(1, num_colours - 1): 112 xOffset = x_stripe_width * i 113 cr.move_to(left + xOffset - half_x_stripe_width, bottom) 114 cr.line_to(left + xOffset + half_x_stripe_width, bottom) 115 cr.line_to(left + xOffset + x_stripe_width + 116 half_x_stripe_width, top) 117 cr.line_to(left + xOffset + x_stripe_width - 118 half_x_stripe_width, top) 119 stroke_and_fill(cr, colours[i]) 120 # Right triangle 121 cr.move_to((right - x_stripe_width) - half_x_stripe_width, bottom) 122 cr.line_to(right, bottom) 123 cr.line_to(right, top) 124 cr.line_to((right - x_stripe_width) + half_x_stripe_width, top) 125 stroke_and_fill(cr, colours[num_colours - 1]) 126 127def speech_bubble(cr, top_left, size, unit): 128 """Draw a speech bubble with 'size'-sized internal space with its 129 top left corner at Point(2.0 * unit, 2.0 * unit)""" 130 def local_arc(centre, angleFrom, angleTo): 131 cr.arc(centre.x, centre.y, unit, angleFrom * math.pi, 132 angleTo * math.pi) 133 134 cr.move_to(*top_left.to_pair()) 135 cr.rel_line_to(unit * 2.0, unit) 136 cr.rel_line_to(size.x, 0.0) 137 local_arc(top_left + Point(size.x + unit * 2.0, unit * 2.0), -0.5, 0.0) 138 cr.rel_line_to(0.0, size.y) 139 local_arc(top_left + Point(size.x + unit * 2.0, size.y + unit * 2.0), 140 0, 0.5) 141 cr.rel_line_to(-size.x, 0.0) 142 local_arc(top_left + Point(unit * 2.0, size.y + unit * 2.0), 0.5, 1.0) 143 cr.rel_line_to(0, -size.y) 144 cr.close_path() 145 146def open_bottom(cr, centre, size): 147 """Draw a box with left, top and right sides""" 148 (left, right, top, bottom) = centre_size_to_sides(centre, size) 149 cr.move_to(left, bottom) 150 cr.line_to(left, top) 151 cr.line_to(right, top) 152 cr.line_to(right, bottom) 153 154def fifo(cr, centre, size): 155 """Draw just the vertical sides of a box""" 156 (left, right, top, bottom) = centre_size_to_sides(centre, size) 157 cr.move_to(left, bottom) 158 cr.line_to(left, top) 159 cr.move_to(right, bottom) 160 cr.line_to(right, top) 161 162def cross(cr, centre, size): 163 """Draw a cross parallel with the axes""" 164 (left, right, top, bottom) = centre_size_to_sides(centre, size) 165 (x, y) = centre.to_pair() 166 cr.move_to(left, y) 167 cr.line_to(right, y) 168 cr.move_to(x, top) 169 cr.line_to(x, bottom) 170 171class Blob(object): 172 """Blob super class""" 173 def __init__(self, picChar, unit, topLeft, colour, size = Point(1,1)): 174 self.picChar = picChar 175 self.unit = unit 176 self.displayName = unit 177 self.nameLoc = 'top' 178 self.topLeft = topLeft 179 self.colour = colour 180 self.size = size 181 self.border = 1.0 182 self.dataSelect = model.BlobDataSelect() 183 self.shorten = 0 184 185 def render(self, cr, view, event, select, time): 186 """Render this blob with the given event's data. Returns either 187 None or a pair of (centre, size) in device coordinates for the drawn 188 blob. The return value can be used to detect if mouse clicks on 189 the canvas are within the blob""" 190 return None 191 192class Block(Blob): 193 """Blocks are rectangular blogs colourable with a 2D grid of striped 194 blocks. visualDecoder specifies how event data becomes this coloured 195 grid""" 196 def __init__(self, picChar, unit, topLeft=Point(0,0), 197 colour=colours.black, 198 size=Point(1,1)): 199 super(Block,self).__init__(picChar, unit, topLeft, colour, 200 size = size) 201 # {horiz, vert} 202 self.stripDir = 'horiz' 203 # {LR, RL}: LR means the first strip will be on the left/top, 204 # RL means the first strip will be on the right/bottom 205 self.stripOrd = 'LR' 206 # Number of blank strips if this is a frame 207 self.blankStrips = 0 208 # {box, fifo, openBottom} 209 self.shape = 'box' 210 self.visualDecoder = None 211 212 def render(self, cr, view, event, select, time): 213 # Find the right event, visuals and sizes for things 214 if event is None or self.displayName.startswith('_'): 215 event = model.BlobEvent(self.unit, time) 216 217 if self.picChar in event.visuals: 218 strips = event.visuals[self.picChar].to_striped_block( 219 select & self.dataSelect) 220 else: 221 strips = [[[colours.unknownColour]]] 222 223 if self.stripOrd == 'RL': 224 strips.reverse() 225 226 if len(strips) == 0: 227 strips = [[colours.errorColour]] 228 print 'Problem with the colour of event:', event 229 230 num_strips = len(strips) 231 strip_proportion = 1.0 / num_strips 232 first_strip_offset = (num_strips / 2.0) - 0.5 233 234 # Adjust blocks with 'shorten' attribute to the length of the data 235 size = Point(*self.size.to_pair()) 236 if self.shorten != 0 and self.size.x > (num_strips * self.shorten): 237 size.x = num_strips * self.shorten 238 239 box_size = size - view.blobIndentFactor.scale(2) 240 241 # Now do cr sensitive things 242 cr.save() 243 cr.scale(*view.pitch.to_pair()) 244 cr.translate(*self.topLeft.to_pair()) 245 cr.translate(*(size - Point(1,1)).scale(0.5).to_pair()) 246 247 translated_centre = Point(*cr.user_to_device(0.0, 0.0)) 248 translated_size = \ 249 Point(*cr.user_to_device_distance(*size.to_pair())) 250 251 # The 2D grid is a grid of strips of blocks. Data [[1,2],[3]] 252 # is 2 strips of 2 and 1 blocks respectively. 253 # if stripDir == 'horiz', strips are stacked vertically 254 # from top to bottom if stripOrd == 'LR' or bottom to top if 255 # stripOrd == 'RL'. 256 # if stripDir == 'vert', strips are stacked horizontally 257 # from left to right if stripOf == 'LR' or right to left if 258 # stripOrd == 'RL'. 259 260 strip_is_horiz = self.stripDir == 'horiz' 261 262 if strip_is_horiz: 263 strip_step_base = Point(1.0,0.0) 264 block_step_base = Point(0.0,1.0) 265 else: 266 strip_step_base = Point(0.0,1.0) 267 block_step_base = Point(1.0,0.0) 268 269 strip_size = (box_size * (strip_step_base.scale(strip_proportion) + 270 block_step_base)) 271 strip_step = strip_size * strip_step_base 272 strip_centre = Point(0,0) - (strip_size * 273 strip_step_base.scale(first_strip_offset)) 274 275 cr.set_line_width(view.midLineWidth / view.pitch.x) 276 277 # Draw the strips and their blocks 278 for strip_index in xrange(0, num_strips): 279 num_blocks = len(strips[strip_index]) 280 block_proportion = 1.0 / num_blocks 281 firstBlockOffset = (num_blocks / 2.0) - 0.5 282 283 block_size = (strip_size * 284 (block_step_base.scale(block_proportion) + 285 strip_step_base)) 286 block_step = block_size * block_step_base 287 block_centre = (strip_centre + strip_step.scale(strip_index) - 288 (block_size * block_step_base.scale(firstBlockOffset))) 289 290 for block_index in xrange(0, num_blocks): 291 striped_box(cr, block_centre + 292 block_step.scale(block_index), block_size, 293 strips[strip_index][block_index]) 294 295 cr.set_font_size(0.7) 296 if self.border > 0.5: 297 weight = cairo.FONT_WEIGHT_BOLD 298 else: 299 weight = cairo.FONT_WEIGHT_NORMAL 300 cr.select_font_face('Helvetica', cairo.FONT_SLANT_NORMAL, 301 weight) 302 303 xb, yb, width, height, dx, dy = cr.text_extents(self.displayName) 304 305 text_comfort_space = 0.15 306 307 if self.nameLoc == 'left': 308 # Position text vertically along left side, top aligned 309 cr.save() 310 cr.rotate(- (math.pi / 2.0)) 311 text_point = Point(size.y, size.x).scale(0.5) * Point(-1, -1) 312 text_point += Point(max(0, size.y - width), 0) 313 text_point += Point(-text_comfort_space, -text_comfort_space) 314 else: # Including top 315 # Position text above the top left hand corner 316 text_point = size.scale(0.5) * Point(-1,-1) 317 text_point += Point(0.00, -text_comfort_space) 318 319 if (self.displayName != '' and 320 not self.displayName.startswith('_')): 321 cr.set_source_color(self.colour) 322 cr.move_to(*text_point.to_pair()) 323 cr.show_text(self.displayName) 324 325 if self.nameLoc == 'left': 326 cr.restore() 327 328 # Draw the outline shape 329 cr.save() 330 if strip_is_horiz: 331 cr.rotate(- (math.pi / 2.0)) 332 box_size = Point(box_size.y, box_size.x) 333 334 if self.stripOrd == "RL": 335 cr.rotate(math.pi) 336 337 if self.shape == 'box': 338 box(cr, Point(0,0), box_size) 339 elif self.shape == 'openBottom': 340 open_bottom(cr, Point(0,0), box_size) 341 elif self.shape == 'fifo': 342 fifo(cr, Point(0,0), box_size) 343 cr.restore() 344 345 # Restore scale and stroke the outline 346 cr.restore() 347 cr.set_source_color(self.colour) 348 cr.set_line_width(view.thickLineWidth * self.border) 349 cr.stroke() 350 351 # Return blob size/position 352 if self.unit == '_': 353 return None 354 else: 355 return (translated_centre, translated_size) 356 357class Key(Blob): 358 """Draw a key to the special (and numeric colours) with swatches of the 359 colours half as wide as the key""" 360 def __init__(self, picChar, unit, topLeft, colour=colours.black, 361 size=Point(1,1)): 362 super(Key,self).__init__(picChar, unit, topLeft, colour, size = size) 363 self.colours = 'BBBB' 364 self.displayName = unit 365 366 def render(self, cr, view, event, select, time): 367 cr.save() 368 cr.scale(*view.pitch.to_pair()) 369 cr.translate(*self.topLeft.to_pair()) 370 # cr.translate(*(self.size - Point(1,1)).scale(0.5).to_pair()) 371 half_width = self.size.x / 2.0 372 cr.translate(*(self.size - Point(1.0 + half_width,1.0)).scale(0.5). 373 to_pair()) 374 375 num_colours = len(self.colours) 376 cr.set_line_width(view.midLineWidth / view.pitch.x) 377 378 blob_size = (Point(half_width,0.0) + 379 (self.size * Point(0.0,1.0 / num_colours))) 380 blob_step = Point(0.0,1.0) * blob_size 381 first_blob_centre = (Point(0.0,0.0) - 382 blob_step.scale((num_colours / 2.0) - 0.5)) 383 384 cr.set_source_color(self.colour) 385 cr.set_line_width(view.thinLineWidth / view.pitch.x) 386 387 blob_proportion = 0.8 388 389 real_blob_size = blob_size.scale(blob_proportion) 390 391 cr.set_font_size(0.8 * blob_size.y * blob_proportion) 392 cr.select_font_face('Helvetica', cairo.FONT_SLANT_NORMAL, 393 cairo.FONT_WEIGHT_BOLD) 394 395 for i in xrange(0, num_colours): 396 centre = first_blob_centre + blob_step.scale(i) 397 box(cr, centre, real_blob_size) 398 399 colour_char = self.colours[i] 400 if colour_char.isdigit(): 401 cr.set_source_color(colours.number_to_colour( 402 int(colour_char))) 403 label = '...' + colour_char 404 else: 405 cr.set_source_color(model.special_state_colours[colour_char]) 406 label = model.special_state_names[colour_char] 407 408 cr.fill_preserve() 409 cr.set_source_color(self.colour) 410 cr.stroke() 411 412 xb, yb, width, height, dx, dy = cr.text_extents(label) 413 414 text_left = (centre + (Point(0.5,0.0) * blob_size) + 415 Point(0.0, height / 2.0)) 416 417 cr.move_to(*text_left.to_pair()) 418 cr.show_text(label) 419 420class Arrow(Blob): 421 """Draw a left or right facing arrow""" 422 def __init__(self, unit, topLeft, colour=colours.black, 423 size=Point(1.0,1.0), direc='right'): 424 super(Arrow,self).__init__(unit, unit, topLeft, colour, size = size) 425 self.direc = direc 426 427 def render(self, cr, view, event, select, time): 428 cr.save() 429 cr.scale(*view.pitch.to_pair()) 430 cr.translate(*self.topLeft.to_pair()) 431 cr.translate(*(self.size - Point(1,1)).scale(0.5).to_pair()) 432 cr.scale(*self.size.to_pair()) 433 (blob_indent_x, blob_indent_y) = \ 434 (view.blobIndentFactor / self.size).to_pair() 435 left = -0.5 - blob_indent_x 436 right = 0.5 + blob_indent_x 437 438 thickness = 0.2 439 flare = 0.2 440 441 if self.direc == 'left': 442 cr.rotate(math.pi) 443 444 cr.move_to(left, -thickness) 445 cr.line_to(0.0, -thickness) 446 cr.line_to(0.0, -(thickness + flare)) 447 cr.line_to(right, 0) 448 # Break arrow to prevent the point ruining the appearance of boxes 449 cr.move_to(right, 0) 450 cr.line_to(0.0, (thickness + flare)) 451 cr.line_to(0.0, +thickness) 452 cr.line_to(left, +thickness) 453 454 cr.restore() 455 456 # Draw arrow a bit more lightly than the standard line width 457 cr.set_line_width(cr.get_line_width() * 0.75) 458 cr.set_source_color(self.colour) 459 cr.stroke() 460 461 return None 462