11
11
from enable.api import black_color_trait, transparent_color_trait
12
12
from kiva.constants import FILL
13
13
from kiva.trait_defs.kiva_font_trait import KivaFont
14
from traits.api import Any, Bool, Enum, Float, HasTraits, Int, \
15
List, Str, on_trait_change
14
from traits.api import (Any, Bool, Float, HasTraits, Int, List, Str,
18
18
class Label(HasTraits):
71
76
_bounding_box = List()
72
77
_position_cache_valid = Bool(False)
78
_text_needs_fitting = Bool(False)
75
81
_rot_matrix = Any()
77
83
def __init__(self, **traits):
78
HasTraits.__init__(self, **traits)
79
self._bounding_box = [0,0]
82
def _calc_line_positions(self, gc):
83
if not self._position_cache_valid:
85
gc.set_font(self.font)
86
# The bottommost line starts at postion (0,0).
89
self._bounding_box = [0,0]
92
prev_y_height = -self.line_spacing
94
for line in self.text.split("\n")[::-1]:
96
(width, height, descent, leading) = gc.get_full_text_extent(line)
97
ascent = height - abs(descent)
100
new_y_pos = prev_y_pos + prev_y_height + self.line_spacing
102
# For blank lines, we use the height of the previous line, if there
103
# is one. The width is 0.
105
if prev_y_height != -self.line_spacing:
106
new_y_pos = prev_y_pos + prev_y_height + self.line_spacing
107
ascent = prev_y_height
109
new_y_pos = prev_y_pos
111
x_pos.append(-leading + margin)
112
y_pos.append(new_y_pos)
113
prev_y_pos = new_y_pos
114
prev_y_height = ascent
116
self._line_xpos = x_pos[::-1]
117
self._line_ypos = y_pos[::-1]
118
border_width = self.border_width if self.border_visible else 0
119
self._bounding_box[0] = max_width + 2*margin + 2*border_width
120
self._bounding_box[1] = prev_y_pos + prev_y_height + margin + 2*border_width
121
self._position_cache_valid = True
84
super(Label, self).__init__(**traits)
85
self._bounding_box = [0, 0]
124
88
def get_width_height(self, gc):
125
89
""" Returns the width and height of the label, in the rotated frame of
92
self._fit_text_to_max_width(gc)
128
93
self._calc_line_positions(gc)
129
94
width, height = self._bounding_box
130
95
return width, height
143
108
abs(height*sin(angle))+abs(width*cos(angle)))
145
110
def get_bounding_poly(self, gc):
147
Returns a list [(x0,y0), (x1,y1),...] of tuples representing a polygon
148
that bounds the label.
111
""" Returns a list [(x0,y0), (x1,y1),...] of tuples representing a
112
polygon that bounds the label.
150
114
width, height = self.get_width_height(gc)
151
115
offset = array(self.get_bounding_box(gc))/2.
160
124
# rotate about centre, and offset to bounding box coords
161
125
points = [dot(self.get_rotation_matrix(), point).transpose()[0]+offset
162
for point in base_points]
126
for point in base_points]
165
129
def get_rotation_matrix(self):
166
130
return array([[cos(self.rotate_angle), -sin(self.rotate_angle)],
167
[sin(self.rotate_angle), cos(self.rotate_angle)]])
131
[sin(self.rotate_angle), cos(self.rotate_angle)]])
169
133
def draw(self, gc):
170
134
""" Draws the label.
193
160
gc.set_stroke_color(self.border_color_)
194
161
gc.set_line_width(self.border_width)
195
162
border_offset = (self.border_width-1)/2.0
196
gc.rect(border_offset, border_offset, width-2*border_offset, height-2*border_offset)
163
gc.rect(border_offset, border_offset,
164
width-2*border_offset, height-2*border_offset)
199
167
gc.set_fill_color(self.color_)
200
168
gc.set_stroke_color(self.color_)
201
169
gc.set_font(self.font)
202
if self.font.size<=8.0:
170
if self.font.size <= 8.0:
203
171
gc.set_antialias(0)
205
173
gc.set_antialias(1)
217
185
gc.set_text_position(x_offset, y_offset)
218
186
gc.show_text(line)
188
#------------------------------------------------------------------------
190
#------------------------------------------------------------------------
192
def _text_changed(self):
193
self._text_needs_fitting = (self.max_width > 0.0)
222
195
@on_trait_change("font,margin,text,rotate_angle")
223
196
def _invalidate_position_cache(self):
224
197
self._position_cache_valid = False
199
#------------------------------------------------------------------------
201
#------------------------------------------------------------------------
203
def _fit_text_to_max_width(self, gc):
204
""" Break the text into lines whose width is no greater than
207
if self._text_needs_fitting:
211
gc.set_font(self.font)
212
for line in self.text.split('\n'):
217
width = gc.get_full_text_extent(line)[0]
218
if width > self.max_width:
220
for word in line.split():
221
line_words.append(word)
222
test_line = ' '.join(line_words)
223
width = gc.get_full_text_extent(test_line)[0]
224
if width > self.max_width:
225
if len(line_words) > 1:
226
lines.append(' '.join(line_words[:-1]))
231
if len(line_words) > 0:
232
lines.append(' '.join(line_words))
235
self.trait_setq(text='\n'.join(lines))
236
self._text_needs_fitting = False
238
def _calc_line_positions(self, gc):
239
if not self._position_cache_valid:
241
gc.set_font(self.font)
242
# The bottommost line starts at postion (0, 0).
245
self._bounding_box = [0, 0]
248
prev_y_height = -self.line_spacing
250
for line in self.text.split("\n")[::-1]:
252
(width, height, descent, leading) = \
253
gc.get_full_text_extent(line)
254
ascent = height - abs(descent)
255
if width > max_width:
257
new_y_pos = prev_y_pos + prev_y_height \
260
# For blank lines, we use the height of the previous
261
# line, if there is one. The width is 0.
263
if prev_y_height != -self.line_spacing:
264
new_y_pos = prev_y_pos + prev_y_height \
266
ascent = prev_y_height
268
new_y_pos = prev_y_pos
270
x_pos.append(-leading + margin)
271
y_pos.append(new_y_pos)
272
prev_y_pos = new_y_pos
273
prev_y_height = ascent
275
self._line_xpos = x_pos[::-1]
276
self._line_ypos = y_pos[::-1]
277
border_width = self.border_width if self.border_visible else 0
278
self._bounding_box[0] = max_width + 2*margin + 2*border_width
279
self._bounding_box[1] = prev_y_pos + prev_y_height + margin \
281
self._position_cache_valid = True