Some refactoring
[pysize.git] / ui / gtk / pysize_widget.py
blobea4e67166b1425110bcd7feebe3e411f29e24d76
1 import math
2 import cairo
3 import pygtk
4 import pango
5 pygtk.require('2.0')
6 import gtk
7 assert gtk.pygtk_version >= (2, 8)
9 from core.pysize_fs_tree import pysize_tree
10 from ui.utils import human_unit
12 RADIUS = 20.0
13 LINE_WIDTH = 4.0
14 CAIRO_IS_FAST = True
16 class PysizeWidget(gtk.DrawingArea):
17 def __init__(self, options, args):
18 super(PysizeWidget, self).__init__()
19 self.connect('expose-event', self._expose)
20 self.connect('realize', self._realize)
21 self.connect('motion-notify-event', self._motion)
22 self.connect('configure-event', self._configure)
23 self.connect('key-press-event', self._key_press)
24 self.connect('button-press-event', self._button_press)
25 self.connect('button-release-event', self._button_release)
26 self.set_flags(gtk.CAN_FOCUS)
27 self.modify_font(pango.FontDescription('Monospace 12'))
28 self.options = options
29 self.args = args
30 self.needs_tree = True
31 self.tree = pysize_tree(args[0], options.max_depth, 1.0)
32 self.selected_items = []
33 self.cursor_item = None
34 self.button_press_node = None
36 def _get_node_here(self, event):
37 for node in self.tree.root:
38 if node.contains_point(event):
39 return node
41 def _button_press(self, widget, event):
42 node = self._get_node_here(event)
43 if not node is self.button_press_node:
44 self.button_press_node = node
45 self.queue_draw()
47 def _button_release(self, widget, event):
48 node = self._get_node_here(event)
49 same_as_press = node is self.button_press_node
50 self.button_press_node = None
51 if same_as_press:
52 if event.state & gtk.gdk.CONTROL_MASK:
53 self.selected_items.append(node)
54 else:
55 self.selected_items = [node]
56 self.queue_draw()
58 def _key_press(self, widget, event):
59 directions = {
60 gtk.keysyms.Left: self.tree.get_parent,
61 gtk.keysyms.Up: self.tree.get_previous_sibling,
62 gtk.keysyms.Right: self.tree.get_first_child,
63 gtk.keysyms.Down: self.tree.get_next_sibling
66 action = directions.get(event.keyval, None)
67 if action and not self.cursor_item is None:
68 new_selection = action(self.cursor_item)
69 if not new_selection in (None, self.tree.root):
70 self.cursor_item = new_selection
71 self.queue_draw()
73 def _realize(self, widget):
74 events = self.window.get_events()
75 events |= gtk.gdk.POINTER_MOTION_MASK
76 events |= gtk.gdk.POINTER_MOTION_HINT_MASK
77 events |= gtk.gdk.KEY_PRESS_MASK
78 events |= gtk.gdk.BUTTON_PRESS_MASK
79 events |= gtk.gdk.BUTTON_RELEASE_MASK
80 self.window.set_events(events)
81 self.window.get_pointer()
83 def _configure(self, widget, event):
84 self.needs_tree = True
86 def _motion(self, widget, event):
87 prev_selection = new_selection = self.cursor_item
88 for node in self.tree.root:
89 was_selected = node is prev_selection
90 if node.contains_point(event) != (node is prev_selection):
91 (x0, x1, y0, y1) = node.rectangle
92 x0 = int(x0 - LINE_WIDTH)
93 x1 = int(x1 + LINE_WIDTH)
94 y0 = int(y0 - LINE_WIDTH)
95 y1 = int(y1 + LINE_WIDTH)
96 if node is prev_selection:
97 new_selection = None
98 else:
99 new_selection = node
100 self.cursor_item = new_selection
101 widget.queue_draw_area(x0, y0, x1 - x0, y1 - y0)
102 if prev_selection is new_selection:
103 self.window.get_pointer()
105 def _expose(self, widget, event):
106 context = widget.window.cairo_create()
108 # set a clip region for the expose event
109 context.rectangle(event.area.x, event.area.y,
110 event.area.width, event.area.height)
111 context.clip()
113 self._draw(context)
114 self.window.get_pointer()
115 return False
117 def _draw_text(self, context, text, x0, x1, y0, y1):
118 pl = self.create_pango_layout(text)
119 w = x1 - x0
120 h = y1 - y0
121 pl.set_width(int(w*pango.SCALE))
122 pl.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
123 (real_w, real_h) = pl.get_pixel_size()
124 if real_w > w or real_h > h:
125 return False
126 if real_w+w/20.0 < w:
127 x0 += w/20.0
128 context.move_to(x0, y0+(h-real_h)/2.0)
129 context.set_source_rgb(0, 0, 0)
130 context.show_layout(pl)
131 return True
133 def _get_item_colors(self, item, colors):
134 if item is self.cursor_item:
135 colors = map(lambda c: c+0.2, colors)
136 if item in self.selected_items:
137 colors.reverse()
138 if item is self.button_press_node:
139 colors = map(lambda c: c+0.2, colors)
140 return colors
142 def _draw_box(self, context, x0, x1, y0, y1, item):
143 x0 += LINE_WIDTH/4.0
144 x1 -= LINE_WIDTH/4.0
145 y0 += LINE_WIDTH/4.0
146 y1 -= LINE_WIDTH/4.0
147 item.rectangle = (x0, x1, y0, y1)
149 if CAIRO_IS_FAST:
150 context.new_path()
151 context.arc(x0 + RADIUS, y0 + RADIUS, RADIUS,
152 - math.pi, - math.pi / 2.0)
153 context.rel_line_to(x1 - x0 - 2*RADIUS, 0)
154 context.arc(x1 - RADIUS, y0 + RADIUS, RADIUS,
155 - math.pi / 2.0, 0)
156 context.rel_line_to(0, y1 - y0 - 2*RADIUS)
157 context.arc(x1 - RADIUS, y1 - RADIUS, RADIUS,
158 0, math.pi / 2.0)
159 context.rel_line_to(- x1 + x0 + 2*RADIUS, 0)
160 context.arc(x0 + RADIUS, y1 - RADIUS, RADIUS,
161 math.pi / 2.0, math.pi)
162 context.close_path()
164 context.set_source_rgb(0, 0, 0)
165 context.stroke_preserve()
167 gradient = cairo.LinearGradient(0, y0, 0, y1)
168 colors = self._get_item_colors(item, [0.5, 0.5, 1.0, 0.0, 0.5, 1.0])
169 gradient.add_color_stop_rgb(0.0, *colors[:3])
170 gradient.add_color_stop_rgb(1.0, *colors[3:])
171 context.set_source(gradient)
172 context.fill()
173 else:
174 context.rectangle(x0, y0, x1 - x0, y1 - y0)
175 context.set_source_rgb(0, 0, 0)
176 context.stroke_preserve()
178 colors = self._get_item_colors(item, [0, 0.5, 1.0])
179 context.set_source_rgb(*colors)
180 context.fill()
182 name = item.get_name()
183 size = human_unit(item.size)
184 if not self._draw_text(context, name + '\n' + size, x0, x1, y0, y1):
185 first, second = name, size
186 if not item.is_real():
187 first, second = second, first
188 if not self._draw_text(context, first, x0, x1, y0, y1):
189 self._draw_text(context, second, x0, x1, y0, y1)
191 def _draw_boxes(self, context, item, depth, offset):
192 w = self.allocation.width
193 h = self.allocation.height
194 x0 = depth * (w - 1.0) / self.tree.height
195 x1 = (depth + 1.0) * (w - 1.0) / self.tree.height
196 y0 = (h - 1.0) * offset / self.tree.root.size
197 y1 = (h - 1.0) * (offset + item.size) / self.tree.root.size
199 self._draw_box(context, x0, x1, y0, y1, item)
200 depth += 1
201 for child in item.get_children():
202 self._draw_boxes(context, child, depth, offset)
203 offset += child.size
205 def _draw(self, context):
206 if self.needs_tree:
207 self.tree = pysize_tree(self.args[0], self.options.max_depth,
208 2*RADIUS /self.allocation.height)
209 self.needs_tree = False
210 if not self.tree.root:
211 return
212 context.set_line_width(LINE_WIDTH)
213 offset = 0
214 for child in self.tree.root.get_children():
215 self._draw_boxes(context, child, 0, offset)
216 offset += child.size