1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import wx
20
21 from timelinelib.canvas.data.exceptions import TimelineIOError
22 from timelinelib.canvas.drawing.utils import darken_color
23 from timelinelib.db.utils import safe_locking
24 from timelinelib import DEBUG_ENABLED
25 from timelinelib.monitoring import Monitoring
26 from timelinelib.general.observer import Observable
27 from timelinelib.repositories.categories import CategoriesFacade
28 from timelinelib.wxgui.components.font import Font
29 from timelinelib.wxgui.dialogs.editcategory.view import EditCategoryDialog
30 import timelinelib.wxgui.utils as gui_utils
31
32
34
35 - def __init__(self, parent, name=None, size=(100, 100)):
51
53 self.db = None
54 self.view_properties = None
55 self.model.set_categories(None)
56
61
64
67
69 return self.db is not None and self.view_properties is not None
70
72 wx.BufferedPaintDC(self, self.buffer_image, wx.BUFFER_VIRTUAL_AREA)
73
76
78 def edit_function():
79 self._edit_category()
80 safe_locking(self.parent, edit_function)
81
90
92 def edit_function():
93 self._store_hit_info(event)
94 for (menu_item, should_be_enabled_fn) in self.context_menu_items:
95 menu_item.Enable(should_be_enabled_fn(self.last_hit_info))
96 self.PopupMenu(self.context_menu)
97 safe_locking(self.parent, edit_function)
98
100 self._edit_category()
101
104
109
113
117
121
125
129
133
137
141
145
150
152 (x, y) = self.CalcUnscrolledPosition(event.GetX(), event.GetY())
153 self.last_hit_info = self.model.hit(x, y)
154
161
163 width, height = self.GetVirtualSizeTuple()
164 self.buffer_image = wx.EmptyBitmap(width, height)
165 memdc = wx.BufferedDC(None, self.buffer_image)
166 memdc.SetBackground(wx.Brush(self.GetBackgroundColour(), wx.PENSTYLE_SOLID))
167 memdc.Clear()
168 memdc.BeginDrawing()
169 self.monitoring.timer_start()
170 self.renderer.render(memdc)
171 self.monitoring.timer_end()
172 if DEBUG_ENABLED:
173 (width, height) = self.GetSizeTuple()
174 redraw_time = self.monitoring.timer_elapsed_ms
175 self.monitoring.count_category_redraw()
176 memdc.SetTextForeground((255, 0, 0))
177 memdc.SetFont(Font(10, weight=wx.FONTWEIGHT_BOLD))
178 memdc.DrawText("Redraw count: %d" % self.monitoring._category_redraw_count, 10, height - 35)
179 memdc.DrawText("Last redraw time: %.3f ms" % redraw_time, 10, height - 20)
180 memdc.EndDrawing()
181 del memdc
182
186
188 def add_item(name, callback, should_be_enabled_fn):
189 item = wx.MenuItem(self.context_menu, wx.ID_ANY, name)
190 self.context_menu.AppendItem(item)
191 self.Bind(wx.EVT_MENU, callback, item)
192 self.context_menu_items.append((item, should_be_enabled_fn))
193 return item
194 self.context_menu_items = []
195 self.context_menu = wx.Menu()
196 add_item(
197 _("Edit..."),
198 self._on_menu_edit,
199 lambda hit_info: hit_info.has_category())
200 add_item(
201 _("Add..."),
202 self._on_menu_add,
203 lambda hit_info: self._has_timeline_view())
204 add_item(
205 _("Delete"),
206 self._on_menu_delete,
207 lambda hit_info: hit_info.has_category())
208 self.context_menu.AppendSeparator()
209 add_item(
210 _("Check All"),
211 self._on_menu_check_all,
212 lambda hit_info: self._has_timeline_view())
213 add_item(
214 _("Check children"),
215 self._on_menu_check_children,
216 lambda hit_info: hit_info.has_category())
217 add_item(
218 _("Check all children"),
219 self._on_menu_check_all_children,
220 lambda hit_info: hit_info.has_category())
221 add_item(
222 _("Check all parents"),
223 self._on_menu_check_parents,
224 lambda hit_info: hit_info.has_category())
225 add_item(
226 _("Check parents for checked children"),
227 self._on_menu_check_parents_for_checked_children,
228 lambda hit_info: self._has_timeline_view())
229 self.context_menu.AppendSeparator()
230 add_item(
231 _("Uncheck All"),
232 self._on_menu_uncheck_all,
233 lambda hit_info: self._has_timeline_view())
234 add_item(
235 _("Uncheck children"),
236 self._on_menu_uncheck_children,
237 lambda hit_info: hit_info.has_category())
238 add_item(
239 _("Uncheck all children"),
240 self._on_menu_uncheck_all_children,
241 lambda hit_info: hit_info.has_category())
242 add_item(
243 _("Uncheck all parents"),
244 self._on_menu_uncheck_parents,
245 lambda hit_info: hit_info.has_category())
246
247
249
250 INNER_PADDING = 2
251 TRIANGLE_SIZE = 8
252
254 self.window = window
255 self.model = model
256
258 self.dc = dc
259 self._render_items(self.model.items)
260 del self.dc
261
263 self.dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT))
264 for item in items:
265 self._render_item(item)
266
268 if item["has_children"]:
269 self._render_arrow(item)
270 self._render_checkbox(item)
271 self._render_name(item)
272 self._render_color_box(item)
273
275 self.dc.SetBrush(wx.Brush(wx.Colour(100, 100, 100), wx.PENSTYLE_SOLID))
276 self.dc.SetPen(wx.Pen(wx.Colour(100, 100, 100), 0, wx.PENSTYLE_SOLID))
277 offset = self.TRIANGLE_SIZE / 2
278 center_x = item["x"] + 2 * self.INNER_PADDING + offset
279 center_y = item["y"] + self.model.ITEM_HEIGHT_PX / 2 - 1
280 if item["expanded"]:
281 open_polygon = [
282 wx.Point(center_x - offset, center_y - offset),
283 wx.Point(center_x + offset, center_y - offset),
284 wx.Point(center_x, center_y + offset),
285 ]
286 self.dc.DrawPolygon(open_polygon)
287 else:
288 closed_polygon = [
289 wx.Point(center_x - offset, center_y - offset),
290 wx.Point(center_x - offset, center_y + offset),
291 wx.Point(center_x + offset, center_y),
292 ]
293 self.dc.DrawPolygon(closed_polygon)
294
296 x = item["x"] + self.TRIANGLE_SIZE + 4 * self.INNER_PADDING + 20
297 (_, h) = self.dc.GetTextExtent(item["name"])
298 if item["actually_visible"]:
299 self.dc.SetTextForeground(self.window.GetForegroundColour())
300 else:
301 self.dc.SetTextForeground((150, 150, 150))
302 self.dc.DrawText(item["name"],
303 x + self.INNER_PADDING,
304 item["y"] + (self.model.ITEM_HEIGHT_PX - h) / 2)
305
307 (w, h) = (17, 17)
308 bouning_rect = wx.Rect(item["x"] + self.model.INDENT_PX,
309 item["y"] + (self.model.ITEM_HEIGHT_PX - h) / 2,
310 w,
311 h)
312 if item["visible"]:
313 flag = wx.CONTROL_CHECKED
314 else:
315 flag = 0
316 renderer = wx.RendererNative.Get()
317 renderer.DrawCheckBox(self.window, self.dc, bouning_rect, flag)
318
329
330
332
333 ITEM_HEIGHT_PX = 22
334 INDENT_PX = 15
335
343
346
348 self.view_width = view_width
349 self.view_height = view_height
350 self._update_items()
351
359
360 - def hit(self, x, y):
369
377
384
388
392
394 self.items = []
395 self.y = 0
396 self._update_from_tree(self._list_to_tree(self._get_categories()))
397 self._notify(None)
398
404
410
412 for (category, child_tree) in category_tree:
413 expanded = category.get_id() not in self.collapsed_category_ids
414 self.items.append({
415 "id": category.get_id(),
416 "name": category.get_name(),
417 "color": category.get_color(),
418 "visible": self._is_category_visible(category),
419 "x": indent_level * self.INDENT_PX,
420 "y": self.y,
421 "width": self.view_width - indent_level * self.INDENT_PX,
422 "expanded": expanded,
423 "has_children": len(child_tree) > 0,
424 "actually_visible": self._is_event_with_category_visible(category),
425 "category": category,
426 })
427 self.y += self.ITEM_HEIGHT_PX
428 if expanded:
429 self._update_from_tree(child_tree, indent_level + 1)
430
433
436
437
439
440 - def __init__(self, categories, category, is_on_arrow, is_on_checkbox):
445
447 return self._category is not None
448
450 return self._category
451
454
457
459 return self._categories.get_parents(self._category)
460
463
465 return self._is_on_arrow
466
468 return self._is_on_checkbox
469
470
475
476
481
482
484 delete_warning = _("Are you sure you want to "
485 "delete category '%s'?") % cat.name
486 if cat.parent is None:
487 update_warning = _("Events belonging to '%s' will no longer "
488 "belong to a category.") % cat.name
489 else:
490 update_warning = _("Events belonging to '%s' will now belong "
491 "to '%s'.") % (cat.name, cat.parent.name)
492 question = "%s\n\n%s" % (delete_warning, update_warning)
493 if gui_utils._ask_question(question, parent_ctrl) == wx.YES:
494 db.delete_category(cat)
495