1 # This program is free software; you can redistribute it and/or modify
2 # it under the terms of the GNU General Public License as published by
3 # the Free Software Foundation; either version 2 of the License, or
4 # (at your option) any later version.
6 # This program is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # GNU Library General Public License for more details.
11 # You should have received a copy of the GNU General Public License
12 # along with this program; if not, write to the Free Software
13 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15 # See the COPYING file for license information.
17 # Copyright (c) 2006, 2007, 2008 Guillaume Chazarain <guichaz@gmail.com>
22 assert gtk
.pygtk_version
>= (2, 8)
27 from pysize
.ui
.utils
import human_unit
, sanitize_string
28 from pysize
.ui
.gtk
.colors
import get_node_colors
33 class PysizeWidget_Draw(object):
34 def __init__(self
, options
, args
):
35 self
.connect('expose-event', type(self
)._expose
_event
)
36 self
.modify_font(pango
.FontDescription('Monospace 12'))
37 self
.max_text_height
= self
.measure_font_height()
39 def measure_font_height(self
):
40 w
, h
= self
.create_pango_layout('a').get_pixel_size()
43 def get_requested_height(self
):
44 return self
.max_text_height
* self
.tree
.root
.size
/ self
.min_size
46 def queue_node_redraw(self
, node
):
47 if node
and node
.rectangle
:
48 x0
, x1
, y0
, y1
= map(int, node
.rectangle
)
49 self
.queue_draw_area(x0
- LINE_WIDTH
, y0
- LINE_WIDTH
,
50 x1
- x0
+ 2*LINE_WIDTH
, y1
- y0
+ 2*LINE_WIDTH
)
52 def _make_draw_labels_lambda(self
, context
, text
, (x0
, x1
, y0
, y1
),
54 pl
= self
.create_pango_layout(text
)
55 pl
.set_alignment(pango
.ALIGN_CENTER
)
58 pl
.set_width(int(w
*pango
.SCALE
))
60 ellipse_mode
= pango
.ELLIPSIZE_END
62 ellipse_mode
= pango
.ELLIPSIZE_NONE
63 pl
.set_ellipsize(ellipse_mode
)
64 real_w
, real_h
= pl
.get_pixel_size()
65 line_count
= pl
.get_line_count()
66 line_height
= float(real_h
) / line_count
67 if line_height
> self
.max_text_height
:
68 self
.max_text_height
= line_height
69 if line_count
== text
.count('\n') + 1 and real_w
<= w
and real_h
<= h
:
70 y0
+= (h
- real_h
) / 2.0
72 context
.move_to(x0
, y0
)
73 context
.show_layout(pl
)
76 def _draw_box(self
, context
, x0
, x1
, y0
, y1
, node
, interpol
):
77 is_selected
= node
in self
.selected_nodes
78 colors
= get_node_colors(node
, node
== self
.cursor_node
, is_selected
,
80 context
.set_source_rgb(0, 0, 0)
81 first_time
= not node
.rectangle
94 node
.rectangle
= x0
, x1
, y0
, y1
95 context
.arc(x0
+ RADIUS
, y0
+ RADIUS
, RADIUS
,
96 - math
.pi
, - math
.pi
/ 2.0)
97 context
.rel_line_to(x1
- x0
- 2*RADIUS
, 0)
98 context
.arc(x1
- RADIUS
, y0
+ RADIUS
, RADIUS
,
100 context
.rel_line_to(0, y1
- y0
- 2*RADIUS
)
101 context
.arc(x1
- RADIUS
, y1
- RADIUS
, RADIUS
,
103 context
.rel_line_to(- x1
+ x0
+ 2*RADIUS
, 0)
104 context
.arc(x0
+ RADIUS
, y1
- RADIUS
, RADIUS
,
105 math
.pi
/ 2.0, math
.pi
)
107 node
.cairo_box_path
= context
.copy_path()
109 context
.append_path(node
.cairo_box_path
)
110 context
.stroke_preserve()
112 gradient
= cairo
.LinearGradient(0, y0
, 0, y1
)
114 gradient
.add_color_stop_rgb(0.0, *colors
[0])
115 gradient
.add_color_stop_rgb(1.0, *colors
[1])
116 context
.set_source(gradient
)
120 context
.set_source_rgb(1, 1, 1)
122 context
.set_source_rgb(0, 0, 0)
124 name
= sanitize_string(node
.get_name())
125 size
= human_unit(node
.size
)
126 position
= x0
, x1
, y0
, y1
127 attempt
= lambda text
, pos
, *flags
: \
128 self
._make
_draw
_labels
_lambda
(context
, text
, pos
, *flags
) or \
129 self
._make
_draw
_labels
_lambda
(context
, text
,
130 (pos
[0] - 1, pos
[1] + 1, pos
[2] - 1, pos
[3] + 1),
132 node
.draw_labels_lambda
= attempt(name
+ '\n' + size
, position
) or \
133 attempt(name
+ ' ' + size
, position
, False) or \
134 attempt(name
, position
) or \
135 attempt(size
, position
) or \
136 (lambda context
: None)
137 node
.draw_labels_lambda(context
)
140 def _intersect(clip
, x0
, x1
, y0
, y1
):
141 cx0
, cx1
, cy0
, cy1
= clip
.x
, clip
.x
+ clip
.width
, \
142 clip
.y
, clip
.y
+ clip
.height
143 return x0
<= cx1
and x1
>= cx0
and y0
<= cy1
and y1
>= cy0
145 def _draw_boxes(self
, context
, clip
, node
, depth
, offset
, interpol
):
146 w
= self
.allocation
.width
147 h
= self
.allocation
.height
148 x0
= depth
* (w
- 1.0) / (self
.tree
.height
or 1)
149 x1
= (depth
+ 1.0) * (w
- 1.0) / (self
.tree
.height
or 1)
150 y0
= (h
- 1.0) * offset
/ self
.tree
.root
.size
151 y1
= (h
- 1.0) * (offset
+ node
.size
) / self
.tree
.root
.size
153 if self
._intersect
(clip
, x0
, x1
, y0
, y1
):
154 self
._draw
_box
(context
, x0
, x1
, y0
, y1
, node
, interpol
)
156 for child
in node
.children
:
157 self
._draw
_boxes
(context
, clip
, child
, depth
, offset
, interpol
)
160 def _draw(self
, context
, clip
):
161 max_text_height_before
= self
.max_text_height
162 if self
.tree
.root
.children
:
163 max_size
= max(self
.tree
.root
.children
[0].size
,
164 self
.tree
.root
.children
[-1].size
)
165 min_size
= self
.tree
.root
.minimum_node_size()
166 diff
= max(1, max_size
- min_size
)
167 def interpol(small
, big
, x
):
168 return small
+ (x
- min_size
) * (big
- small
) / diff
170 def interpol(small
, big
, x
):
172 context
.set_line_width(LINE_WIDTH
)
174 for child
in self
.tree
.root
.children
or [self
.tree
.root
]:
176 self
._draw
_boxes
(context
, clip
, child
, 0, offset
, interpol
)
178 if self
.max_text_height
!= max_text_height_before
:
179 self
.schedule_new_tree()
181 def _expose_event(self
, event
):
182 context
= self
.window
.cairo_create()
184 # set a clip region for the expose event
185 context
.rectangle(event
.area
.x
, event
.area
.y
,
186 event
.area
.width
, event
.area
.height
)
189 self
._draw
(context
, event
.area
)
192 def max_number_of_nodes(self
):
193 return max(2, self
.allocation
.height
/ self
.max_text_height
)
195 def _get_actual_min_size(self
):
196 min_size
= self
.min_size
197 if min_size
== 'auto':
198 min_size
= self
.tree
.root
.size
* self
.min_size_requested()
201 def _zoom(self
, func
):
202 min_size
= self
._get
_actual
_min
_size
()
203 self
.min_size
= func(min_size
)
204 self
.schedule_new_tree()
207 self
._zoom
(lambda min_size
: 'auto')
210 self
._zoom
(lambda min_size
: min_size
/ 1.5)
213 self
._zoom
(lambda min_size
: min_size
* 1.5)