Detect unsuccessful attempts at running an uninstalled pysize
[pysize.git] / pysize / ui / gtk / pysize_widget_draw.py
blob8b4bc633e749b3a94d44d005b60c947425d8ddba
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>
19 import pygtk
20 pygtk.require('2.0')
21 import gtk
22 assert gtk.pygtk_version >= (2, 8)
23 import pango
24 import math
25 import cairo
27 from pysize.ui.utils import human_unit, sanitize_string
28 from pysize.ui.gtk.colors import get_node_colors
30 RADIUS = 10
31 LINE_WIDTH = 4
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()
41 return h
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),
53 accept_ellipse=True):
54 pl = self.create_pango_layout(text)
55 pl.set_alignment(pango.ALIGN_CENTER)
56 w = x1 - x0
57 h = y1 - y0
58 pl.set_width(int(w*pango.SCALE))
59 if accept_ellipse:
60 ellipse_mode = pango.ELLIPSIZE_END
61 else:
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
71 def draw(context):
72 context.move_to(x0, y0)
73 context.show_layout(pl)
74 return draw
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,
79 interpol)
80 context.set_source_rgb(0, 0, 0)
81 first_time = not node.rectangle
82 context.new_path()
83 if first_time:
84 if x0 == 0.0:
85 x0 += LINE_WIDTH/2.0
86 else:
87 x0 += LINE_WIDTH/4.0
88 x1 -= LINE_WIDTH/4.0
89 if y0 == 0.0:
90 y0 += LINE_WIDTH/2.0
91 else:
92 y0 += LINE_WIDTH/4.0
93 y1 -= LINE_WIDTH/4.0
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,
99 - math.pi / 2.0, 0)
100 context.rel_line_to(0, y1 - y0 - 2*RADIUS)
101 context.arc(x1 - RADIUS, y1 - RADIUS, RADIUS,
102 0, math.pi / 2.0)
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)
106 context.close_path()
107 node.cairo_box_path = context.copy_path()
108 else:
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)
117 context.fill()
119 if is_selected:
120 context.set_source_rgb(1, 1, 1)
121 else:
122 context.set_source_rgb(0, 0, 0)
123 if first_time:
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),
131 *flags)
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)
139 @staticmethod
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)
155 depth += 1
156 for child in node.children:
157 self._draw_boxes(context, clip, child, depth, offset, interpol)
158 offset += child.size
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
169 else:
170 def interpol(small, big, x):
171 return small
172 context.set_line_width(LINE_WIDTH)
173 offset = 0
174 for child in self.tree.root.children or [self.tree.root]:
175 if child.size:
176 self._draw_boxes(context, clip, child, 0, offset, interpol)
177 offset += child.size
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)
187 context.clip()
189 self._draw(context, event.area)
190 return False
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()
199 return int(min_size)
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()
206 def zoom_fit(self):
207 self._zoom(lambda min_size: 'auto')
209 def zoom_in(self):
210 self._zoom(lambda min_size: min_size / 1.5)
212 def zoom_out(self):
213 self._zoom(lambda min_size: min_size * 1.5)