~ubuntu-branches/ubuntu/oneiric/python-chaco/oneiric

« back to all changes in this revision

Viewing changes to enthought/chaco/label.py

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2011-07-08 20:38:02 UTC
  • mfrom: (7.2.3 sid)
  • Revision ID: james.westby@ubuntu.com-20110708203802-5t32e0ldv441yh90
Tags: 4.0.0-1
* New upstream release
* debian/control:
  - Depend on python-traitsui (Closes: #633604)
  - Bump Standards-Version to 3.9.2
* Update debian/watch file
* Remove debian/patches/* -- no longer needed

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
""" Defines the Label class.
2
 
"""
3
 
 
4
 
from __future__ import with_statement
5
 
 
6
 
# Major library imports
7
 
from math import cos, sin, pi
8
 
from numpy import array, dot
9
 
 
10
 
# Enthought library imports
11
 
from enthought.enable.api import black_color_trait, transparent_color_trait
12
 
from enthought.kiva.traits.kiva_font_trait import KivaFont
13
 
from enthought.traits.api import Any, Bool, Enum, Float, HasTraits, Int, \
14
 
                                 List, Str, on_trait_change
15
 
 
16
 
 
17
 
class Label(HasTraits):
18
 
    """ A label used by overlays.
19
 
 
20
 
    Label is not a Component; it's just an object encapsulating text settings
21
 
    and appearance attributes.  It can be used by components that need text
22
 
    labels to store state, perform layout, and render the text.
23
 
    """
24
 
 
25
 
    # The anchor point is the position on the label that is placed at the
26
 
    # label's position.  The label is also rotated relative to this point.
27
 
    # "Left" refers to the left edge of the text's bounding box (including
28
 
    # margin), while "center" refers to the horizontal and vertical center
29
 
    # of the bounding box.
30
 
    # TODO: Implement this and test thoroughly
31
 
    #anchor = Enum("left", "right", "top", "bottom", "center",
32
 
    #              "top left", "top right", "bottom left", "bottom right")
33
 
 
34
 
    # The label text.  Carriage returns (\n) are always connverted into
35
 
    # line breaks.
36
 
    text = Str
37
 
 
38
 
    # The angle of rotation of the label.
39
 
    rotate_angle = Float(0)
40
 
 
41
 
    # The color of the label text.
42
 
    color = black_color_trait
43
 
 
44
 
    # The background color of the label.
45
 
    bgcolor = transparent_color_trait
46
 
 
47
 
    # The width of the label border. If it is 0, then it is not shown.
48
 
    border_width = Int(0)
49
 
 
50
 
    # The color of the border.
51
 
    border_color = black_color_trait
52
 
 
53
 
    # Whether or not the border is visible
54
 
    border_visible = Bool(True)
55
 
 
56
 
    # The font of the label text.
57
 
    font = KivaFont("modern 10")
58
 
 
59
 
    # Number of pixels of margin around the label, for both X and Y dimensions.
60
 
    margin = Int(2)
61
 
 
62
 
    # Number of pixels of spacing between lines of text.
63
 
    line_spacing = Int(5)
64
 
 
65
 
 
66
 
    #------------------------------------------------------------------------
67
 
    # Private traits
68
 
    #------------------------------------------------------------------------
69
 
 
70
 
    _bounding_box = List()
71
 
    _position_cache_valid = Bool(False)
72
 
    _line_xpos = Any()
73
 
    _line_ypos = Any()
74
 
    _rot_matrix = Any()
75
 
 
76
 
    def __init__(self, **traits):
77
 
        HasTraits.__init__(self, **traits)
78
 
        self._bounding_box = [0,0]
79
 
        return
80
 
 
81
 
    def _calc_line_positions(self, gc):
82
 
        if not self._position_cache_valid:
83
 
            with gc:
84
 
                gc.set_font(self.font)
85
 
                # The bottommost line starts at postion (0,0).
86
 
                x_pos = []
87
 
                y_pos = []
88
 
                self._bounding_box = [0,0]
89
 
                margin = self.margin
90
 
                prev_y_pos = margin
91
 
                prev_y_height = -self.line_spacing
92
 
                max_width = 0
93
 
                for line in self.text.split("\n")[::-1]:
94
 
                    if line != "":
95
 
                        (width, height, descent, leading) = gc.get_full_text_extent(line)
96
 
                        ascent = height - abs(descent)
97
 
                        if width > max_width:
98
 
                            max_width = width
99
 
                        new_y_pos = prev_y_pos + prev_y_height + self.line_spacing
100
 
                    else:
101
 
                        # For blank lines, we use the height of the previous line, if there
102
 
                        # is one.  The width is 0.
103
 
                        leading = 0
104
 
                        if prev_y_height != -self.line_spacing:
105
 
                            new_y_pos = prev_y_pos + prev_y_height + self.line_spacing
106
 
                            ascent = prev_y_height
107
 
                        else:
108
 
                            new_y_pos = prev_y_pos
109
 
                            ascent = 0
110
 
                    x_pos.append(-leading + margin)
111
 
                    y_pos.append(new_y_pos)
112
 
                    prev_y_pos = new_y_pos
113
 
                    prev_y_height = ascent
114
 
 
115
 
            self._line_xpos = x_pos[::-1]
116
 
            self._line_ypos = y_pos[::-1]
117
 
            border_width = self.border_width if self.border_visible else 0
118
 
            self._bounding_box[0] = max_width + 2*margin + 2*border_width
119
 
            self._bounding_box[1] = prev_y_pos + prev_y_height + margin + 2*border_width
120
 
            self._position_cache_valid = True
121
 
        return
122
 
 
123
 
    def get_width_height(self, gc):
124
 
        """ Returns the width and height of the label, in the rotated frame of 
125
 
        reference.
126
 
        """
127
 
        self._calc_line_positions(gc)
128
 
        width, height = self._bounding_box
129
 
        return width, height
130
 
 
131
 
    def get_bounding_box(self, gc):
132
 
        """ Returns a rectangular bounding box for the Label as (width,height).
133
 
        """
134
 
        width, height = self.get_width_height(gc)
135
 
        if self.rotate_angle in (90.0, 270.0):
136
 
            return (height, width)
137
 
        elif self.rotate_angle in (0.0, 180.0):
138
 
            return (width, height)
139
 
        else:
140
 
            angle = self.rotate_angle
141
 
            return (abs(width*cos(angle))+abs(height*sin(angle)), 
142
 
                    abs(height*sin(angle))+abs(width*cos(angle)))
143
 
 
144
 
    def get_bounding_poly(self, gc):
145
 
        """
146
 
        Returns a list [(x0,y0), (x1,y1),...] of tuples representing a polygon
147
 
        that bounds the label.
148
 
        """
149
 
        width, height = self.get_width_height(gc)
150
 
        offset = array(self.get_bounding_box(gc))/2.
151
 
        # unrotated points relative to centre
152
 
        base_points = [
153
 
            array([[-width/2.], [-height/2.]]),
154
 
            array([[-width/2.], [height/2.]]),
155
 
            array([[width/2.], [height/2.]]),
156
 
            array([[width/2.], [-height/2.]]),
157
 
            array([[-width/2.], [-height/2.]]),
158
 
        ]
159
 
        # rotate about centre, and offset to bounding box coords
160
 
        points = [dot(self.get_rotation_matrix(), point).transpose()[0]+offset
161
 
            for point in base_points]
162
 
        return points
163
 
     
164
 
    def get_rotation_matrix(self):
165
 
        return array([[cos(self.rotate_angle), -sin(self.rotate_angle)],
166
 
            [sin(self.rotate_angle), cos(self.rotate_angle)]])
167
 
 
168
 
    def draw(self, gc):
169
 
        """ Draws the label.
170
 
 
171
 
        This method assumes the graphics context has been translated to the
172
 
        correct position such that the origin is at the lower left-hand corner
173
 
        of this text label's box.
174
 
        """
175
 
        # For this version we're not supporting rotated text.
176
 
        self._calc_line_positions(gc)
177
 
            
178
 
        with gc:
179
 
            bb_width, bb_height = self.get_bounding_box(gc)
180
 
 
181
 
            # Rotate label about center of bounding box
182
 
            width, height = self._bounding_box
183
 
            gc.translate_ctm(bb_width/2.0, bb_height/2.0)
184
 
            gc.rotate_ctm(pi/180.0*self.rotate_angle)
185
 
            gc.translate_ctm(-width/2.0, -height/2.0)
186
 
 
187
 
            # Draw border and fill background
188
 
            if self.bgcolor != "transparent":
189
 
                gc.set_fill_color(self.bgcolor_)
190
 
                gc.rect(0, 0, width, height)
191
 
                gc.fill_path()
192
 
            if self.border_visible and self.border_width > 0:
193
 
                gc.set_stroke_color(self.border_color_)
194
 
                gc.set_line_width(self.border_width)
195
 
                border_offset = (self.border_width-1)/2.0
196
 
                gc.rect(border_offset, border_offset, width-2*border_offset, height-2*border_offset)
197
 
                gc.stroke_path()
198
 
 
199
 
            gc.set_fill_color(self.color_)
200
 
            gc.set_stroke_color(self.color_)
201
 
            gc.set_font(self.font)
202
 
            if self.font.size<=8.0:
203
 
                gc.set_antialias(0)
204
 
            else:
205
 
                gc.set_antialias(1)
206
 
 
207
 
            lines = self.text.split("\n")
208
 
            if self.border_visible:
209
 
                gc.translate_ctm(self.border_width, self.border_width)
210
 
            width, height = self.get_width_height(gc)
211
 
 
212
 
            for i, line in enumerate(lines):
213
 
                if line == "":
214
 
                    continue
215
 
                x_offset = round(self._line_xpos[i])
216
 
                y_offset = round(self._line_ypos[i])
217
 
                gc.set_text_position(x_offset, y_offset)
218
 
                gc.show_text(line)
219
 
 
220
 
        return
221
 
 
222
 
    @on_trait_change("font,margin,text,rotate_angle")
223
 
    def _invalidate_position_cache(self):
224
 
        self._position_cache_valid = False
225