Beginning of a color code
[pysize.git] / pysize / ui / gtk / pysize_widget_draw.py
blob9a40688072041a0b3928939047e60f05b90d512f
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 Guillaume Chazarain <guichaz@yahoo.fr>
19 import pygtk
20 pygtk.require('2.0')
21 import gtk
22 assert gtk.pygtk_version >= (2, 8)
23 import pango
24 import gobject
25 import math
26 import cairo
27 import pangocairo
29 from pysize.ui.utils import human_unit, min_size_to_consider, sanitize_string
31 RADIUS = 20.0
32 LINE_WIDTH = 4.0
34 class PysizeWidget_Draw(object):
35 def __init__(self, options, args):
36 self.connect('expose-event', type(self)._expose_event)
37 self.modify_font(pango.FontDescription('Monospace 12'))
38 self.max_text_height = self.measure_font_height()
39 self.fast = options.fast
41 def measure_font_height(self):
42 w, h = self.create_pango_layout('a').get_pixel_size()
43 return h
45 def _get_requested_height(self):
46 return self.max_text_height * self.tree.root.size / \
47 min_size_to_consider(self.options.min_size)
49 def queue_node_redraw(self, node):
50 if node:
51 x0, x1, y0, y1 = map(int, node.rectangle)
52 self.queue_draw_area(x0, y0, x1 - x0, y1 - y0)
54 def _draw_text(self, context, text, x0, x1, y0, y1):
55 pl = self.create_pango_layout(text)
56 pl.set_alignment(pango.ALIGN_CENTER)
57 w = x1 - x0
58 h = y1 - y0
59 pl.set_width(int(w*pango.SCALE))
60 pl.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
61 real_w, real_h = pl.get_pixel_size()
62 if real_h > self.max_text_height:
63 self.max_text_height = real_h
64 if real_h > h:
65 return False
66 context.move_to(x0, y0+(h-real_h)/2.0)
67 context.set_source_rgb(0, 0, 0)
68 context.show_layout(pl)
69 return True
71 def _get_node_colors(self, node, colors):
72 def transform(colors, dr, dg, db):
73 return map(lambda (r, g, b): (r + dr, g + dg, b + db), colors)
75 if node.is_real() and not node.is_dir():
76 colors = transform(colors, 0.6, -0.6, -0.6)
78 if not node.is_real():
79 colors = transform(colors, 0, 0.4, 0)
81 size = node.get_useful_size() / (self.tree.root.children[0].size * 2.0)
82 colors = transform(colors, -size, -size, -size)
84 if node == self.cursor_node:
85 colors = transform(colors, 0.1, 0.1, 0.1)
86 if node in self.selected_nodes + [self.button_press_node]:
87 colors = transform(colors, -0.2, -0.2, -0.2)
88 return colors
90 def _draw_box(self, context, x0, x1, y0, y1, node):
91 if x0 == 0.0:
92 x0 += LINE_WIDTH/2.0
93 else:
94 x0 += LINE_WIDTH/4.0
95 x1 -= LINE_WIDTH/4.0
96 if y0 == 0.0:
97 y0 += LINE_WIDTH/2.0
98 else:
99 y0 += LINE_WIDTH/4.0
100 y1 -= LINE_WIDTH/4.0
101 node.rectangle = x0, x1, y0, y1
103 if self.fast:
104 context.rectangle(x0, y0, x1 - x0, y1 - y0)
105 context.set_source_rgb(0, 0, 0)
106 context.stroke_preserve()
108 colors = self._get_node_colors(node, ((0.2, 0.4, 1.0),))
109 context.set_source_rgb(*colors[0])
110 context.fill()
111 else:
112 context.new_path()
113 context.arc(x0 + RADIUS, y0 + RADIUS, RADIUS,
114 - math.pi, - math.pi / 2.0)
115 context.rel_line_to(x1 - x0 - 2*RADIUS, 0)
116 context.arc(x1 - RADIUS, y0 + RADIUS, RADIUS,
117 - math.pi / 2.0, 0)
118 context.rel_line_to(0, y1 - y0 - 2*RADIUS)
119 context.arc(x1 - RADIUS, y1 - RADIUS, RADIUS,
120 0, math.pi / 2.0)
121 context.rel_line_to(- x1 + x0 + 2*RADIUS, 0)
122 context.arc(x0 + RADIUS, y1 - RADIUS, RADIUS,
123 math.pi / 2.0, math.pi)
124 context.close_path()
126 context.set_source_rgb(0, 0, 0)
127 context.stroke_preserve()
129 gradient = cairo.LinearGradient(0, y0, 0, y1)
130 colors = self._get_node_colors(node, ((0.5, 0.4, 1.0),
131 (0.2, 0.4, 1.0)))
132 gradient.add_color_stop_rgb(0.0, *colors[0])
133 gradient.add_color_stop_rgb(1.0, *colors[1])
134 context.set_source(gradient)
135 context.fill()
137 name = sanitize_string(node.get_name())
138 size = human_unit(node.size)
139 self._draw_text(context, name + '\n' + size, x0, x1, y0, y1) or \
140 self._draw_text(context, name, x0, x1, y0, y1) or \
141 self._draw_text(context, size, x0, x1, y0, y1)
143 def _draw_boxes(self, context, node, depth, offset):
144 w = self.allocation.width
145 h = self.allocation.height
146 x0 = depth * (w - 1.0) / self.tree.height
147 x1 = (depth + 1.0) * (w - 1.0) / self.tree.height
148 y0 = (h - 1.0) * offset / self.tree.root.size
149 y1 = (h - 1.0) * (offset + node.size) / self.tree.root.size
151 self._draw_box(context, x0, x1, y0, y1, node)
152 depth += 1
153 for child in node.children:
154 self._draw_boxes(context, child, depth, offset)
155 offset += child.size
157 def _draw(self, context):
158 max_text_height_before = self.max_text_height
159 context.set_line_width(LINE_WIDTH)
160 offset = 0
161 for child in self.tree.root.children:
162 self._draw_boxes(context, child, 0, offset)
163 offset += child.size
164 if self.max_text_height != max_text_height_before:
165 self.schedule_new_tree()
167 def _expose_event(self, event):
168 context = self.window.cairo_create()
170 # set a clip region for the expose event
171 context.rectangle(event.area.x, event.area.y,
172 event.area.width, event.area.height)
173 context.clip()
175 self._draw(context)
176 return False
178 def max_number_of_nodes(self):
179 return max(2, self.allocation.height / self.max_text_height)
181 def _get_actual_min_size(self):
182 min_size = self.options.min_size
183 if min_size == 'auto':
184 min_size = self.tree.root.size * self.min_size_requested()
185 return int(min_size)
187 def _zoom(self, func):
188 min_size = self._get_actual_min_size()
189 self.options.min_size = func(min_size)
190 self.schedule_new_tree()
192 def zoom_fit(self):
193 self._zoom(lambda min_size: 'auto')
195 def zoom_in(self):
196 self._zoom(lambda min_size: str(int(min_size / 1.5)))
198 def zoom_out(self):
199 self._zoom(lambda min_size: str(int(min_size * 1.5)))