1 # Copyright (c) 2013 ARM Limited
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.
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.
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.
36 # Authors: Andrew Bardsley
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.
51 from point
import Point
54 from colours
import backgroundColour
, black
57 def 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()
63 right
= x
+ half_width
65 bottom
= y
+ half_height
66 return (left
, right
, top
, bottom
)
68 def box(cr
, centre
, size
):
69 """Draw a simple box"""
70 (left
, right
, top
, bottom
) = centre_size_to_sides(centre
, size
)
72 cr
.line_to(right
, top
)
73 cr
.line_to(right
, bottom
)
74 cr
.line_to(left
, bottom
)
77 def stroke_and_fill(cr
, colour
):
78 """Stroke with the current colour then fill the same path with the
80 join
= cr
.get_line_join()
81 cr
.set_line_join(gtk
.gdk
.JOIN_ROUND
)
83 cr
.set_source_color(backgroundColour
)
85 cr
.set_source_color(colour
)
87 cr
.set_line_join(join
)
89 def striped_box(cr
, centre
, size
, colours
):
90 """Fill a rectangle (without outline) striped with the colours given"""
91 num_colours
= len(colours
)
94 cr
.set_source_color(backgroundColour
)
96 elif num_colours
== 1:
98 stroke_and_fill(cr
, colours
[0])
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
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])
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
])
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])
127 def 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
,
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),
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
)
146 def 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
)
154 def 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
)
162 def 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()
169 cr
.line_to(x
, bottom
)
172 """Blob super class"""
173 def __init__(self
, picChar
, unit
, topLeft
, colour
, size
= Point(1,1)):
174 self
.picChar
= picChar
176 self
.displayName
= unit
178 self
.topLeft
= topLeft
182 self
.dataSelect
= model
.BlobDataSelect()
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"""
193 """Blocks are rectangular blogs colourable with a 2D grid of striped
194 blocks. visualDecoder specifies how event data becomes this coloured
196 def __init__(self
, picChar
, unit
, topLeft
=Point(0,0),
197 colour
=colours
.black
,
199 super(Block
,self
).__init
__(picChar
, unit
, topLeft
, colour
,
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
206 # Number of blank strips if this is a frame
208 # {box, fifo, openBottom}
210 self
.visualDecoder
= None
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
)
217 if self
.picChar
in event
.visuals
:
218 strips
= event
.visuals
[self
.picChar
].to_striped_block(
219 select
& self
.dataSelect
)
221 strips
= [[[colours
.unknownColour
]]]
223 if self
.stripOrd
== 'RL':
227 strips
= [[colours
.errorColour
]]
228 print 'Problem with the colour of event:', event
230 num_strips
= len(strips
)
231 strip_proportion
= 1.0 / num_strips
232 first_strip_offset
= (num_strips
/ 2.0) - 0.5
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
239 box_size
= size
- view
.blobIndentFactor
.scale(2)
241 # Now do cr sensitive things
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())
247 translated_centre
= Point(*cr
.user_to_device(0.0, 0.0))
249 Point(*cr
.user_to_device_distance(*size
.to_pair()))
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
256 # if stripDir == 'vert', strips are stacked horizontally
257 # from left to right if stripOf == 'LR' or right to left if
260 strip_is_horiz
= self
.stripDir
== 'horiz'
263 strip_step_base
= Point(1.0,0.0)
264 block_step_base
= Point(0.0,1.0)
266 strip_step_base
= Point(0.0,1.0)
267 block_step_base
= Point(1.0,0.0)
269 strip_size
= (box_size
* (strip_step_base
.scale(strip_proportion
) +
271 strip_step
= strip_size
* strip_step_base
272 strip_centre
= Point(0,0) - (strip_size
*
273 strip_step_base
.scale(first_strip_offset
))
275 cr
.set_line_width(view
.midLineWidth
/ view
.pitch
.x
)
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
283 block_size
= (strip_size
*
284 (block_step_base
.scale(block_proportion
) +
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
)))
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
])
295 cr
.set_font_size(0.7)
296 if self
.border
> 0.5:
297 weight
= cairo
.FONT_WEIGHT_BOLD
299 weight
= cairo
.FONT_WEIGHT_NORMAL
300 cr
.select_font_face('Helvetica', cairo
.FONT_SLANT_NORMAL
,
303 xb
, yb
, width
, height
, dx
, dy
= cr
.text_extents(self
.displayName
)
305 text_comfort_space
= 0.15
307 if self
.nameLoc
== 'left':
308 # Position text vertically along left side, top aligned
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
)
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
)
325 if self
.nameLoc
== 'left':
328 # Draw the outline shape
331 cr
.rotate(- (math
.pi
/ 2.0))
332 box_size
= Point(box_size
.y
, box_size
.x
)
334 if self
.stripOrd
== "RL":
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
)
345 # Restore scale and stroke the outline
347 cr
.set_source_color(self
.colour
)
348 cr
.set_line_width(view
.thickLineWidth
* self
.border
)
351 # Return blob size/position
355 return (translated_centre
, translated_size
)
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
,
362 super(Key
,self
).__init
__(picChar
, unit
, topLeft
, colour
, size
= size
)
363 self
.colours
= 'BBBB'
364 self
.displayName
= unit
366 def render(self
, cr
, view
, event
, select
, time
):
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).
375 num_colours
= len(self
.colours
)
376 cr
.set_line_width(view
.midLineWidth
/ view
.pitch
.x
)
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))
384 cr
.set_source_color(self
.colour
)
385 cr
.set_line_width(view
.thinLineWidth
/ view
.pitch
.x
)
387 blob_proportion
= 0.8
389 real_blob_size
= blob_size
.scale(blob_proportion
)
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
)
395 for i
in xrange(0, num_colours
):
396 centre
= first_blob_centre
+ blob_step
.scale(i
)
397 box(cr
, centre
, real_blob_size
)
399 colour_char
= self
.colours
[i
]
400 if colour_char
.isdigit():
401 cr
.set_source_color(colours
.number_to_colour(
403 label
= '...' + colour_char
405 cr
.set_source_color(model
.special_state_colours
[colour_char
])
406 label
= model
.special_state_names
[colour_char
]
409 cr
.set_source_color(self
.colour
)
412 xb
, yb
, width
, height
, dx
, dy
= cr
.text_extents(label
)
414 text_left
= (centre
+ (Point(0.5,0.0) * blob_size
) +
415 Point(0.0, height
/ 2.0))
417 cr
.move_to(*text_left
.to_pair())
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
)
427 def render(self
, cr
, view
, event
, select
, time
):
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
441 if self
.direc
== 'left':
444 cr
.move_to(left
, -thickness
)
445 cr
.line_to(0.0, -thickness
)
446 cr
.line_to(0.0, -(thickness
+ flare
))
448 # Break arrow to prevent the point ruining the appearance of boxes
450 cr
.line_to(0.0, (thickness
+ flare
))
451 cr
.line_to(0.0, +thickness
)
452 cr
.line_to(left
, +thickness
)
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
)