drawingarea.py (17368B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\drawingpolygon\drawingarea.py 2 from drawingpolygon.cursors import CURSOR_SELECTOR_POSITION 3 from drawingpolygon.polygon import Polygon 4 from drawingpolygon.xbutton import XButton 5 from carbonui import uiconst 6 from carbonui.primitives.container import Container 7 from carbonui.uicore import uicore 8 from signals import Signal 9 from uthread2 import StartTasklet 10 11 class DrawingArea(Container): 12 default_max_polygons = 8 13 14 def ApplyAttributes(self, attributes): 15 super(DrawingArea, self).ApplyAttributes(attributes) 16 self.max_polygons = attributes.get('max_polygons', self.default_max_polygons) 17 self.buffer_size = attributes.get('buffer_size') 18 self.on_polygon_added = Signal(signalName='on_polygon_added') 19 self.on_polygon_deleted = Signal(signalName='on_polygon_deleted') 20 self.on_polygons_changed = Signal(signalName='on_polygons_changed') 21 self.on_wip_polygon_cleared = Signal(signalName='on_wip_polygon_cleared') 22 self.on_polygon_started = Signal(signalName='on_polygon_started') 23 self.on_polygon_selected = Signal(signalName='on_polygon_selected') 24 self.on_polygon_deselected = Signal(signalName='on_polygon_deselected') 25 self.on_point_added = Signal(signalName='on_point_added') 26 self.buffer = Container(name='buffer_container', parent=self.parent, align=uiconst.CENTER, idx=0) 27 self.content = DrawingContent(name='content_container', parent=self, align=uiconst.CENTER, state=uiconst.UI_NORMAL, drawing_area=self, buffer_area=self.buffer) 28 self.x_button = None 29 30 def SetSize(self, width, height): 31 super(DrawingArea, self).SetSize(width, height) 32 self.buffer.width = self.width + 2 * self.buffer_size 33 self.buffer.height = self.height + 2 * self.buffer_size 34 self.content.SetSize(width, height) 35 36 def get_polygons(self): 37 return [ polygon.get_data() for polygon in self.content.polygons ] 38 39 def get_time_spent_per_polygon(self): 40 return [ polygon.get_time_spent() for polygon in self.content.polygons ] 41 42 def get_mouse_position(self): 43 return self._constrain_to_area(uicore.uilib.x, uicore.uilib.y) 44 45 def transform_coordinates_to_proportions(self, x, y): 46 new_x, new_y = (0, 0) 47 if self.width: 48 new_x = float(x) / self.width 49 if self.height: 50 new_y = float(y) / self.height 51 return [new_x, 1.0 - new_y] 52 53 def transform_proportions_to_coordinates(self, x, y): 54 return (x * self.width, y * self.height) 55 56 def _constrain_to_area(self, x, y): 57 min_x, min_y = (0, 0) 58 max_x, max_y = self.width, self.height 59 return (sorted([min_x, max_x, x - self.absoluteLeft])[1], sorted([min_y, max_y, y - self.absoluteTop])[1]) 60 61 def is_position_in_area(self, x, y): 62 min_x, min_y = (0, 0) 63 max_x, max_y = self.width, self.height 64 return min_x <= x <= max_x and min_y <= y <= max_y 65 66 def is_there_another_vertex_in_range(self, polygon, vertex): 67 for other_vertex in polygon.vertices: 68 if other_vertex.order != vertex.order: 69 if vertex.is_within_range(other_vertex): 70 return True 71 72 return False 73 74 def add_x_button(self, button_function, x, y): 75 self.remove_x_button() 76 self.x_button = XButton(name='x_button', parent=self.buffer, align=uiconst.TOPLEFT, button_function=button_function) 77 self.x_button.left = self.buffer_size + x + 5 78 self.x_button.top = self.buffer_size + y - self.x_button.height - 5 79 80 def remove_x_button(self): 81 if self.x_button: 82 self.x_button.Close() 83 self.x_button = None 84 85 def toggle_polygon_selection(self, index): 86 self.content.toggle_polygon_selection(index) 87 88 def inject_click(self, x, y): 89 self.content.on_click(x, y) 90 91 def change_perimeter_color(self, perimeter_color): 92 self.content.change_perimeter_color(perimeter_color) 93 94 def inject_polygon(self, vertices, send_to_back = None, show_vertices = None, show_fill = None, perimeter_width = None, perimeter_color = None, fill_color = None, fill_bg_color = None): 95 self.content.inject_polygon(vertices, send_to_back, show_vertices, show_fill, perimeter_width, perimeter_color, fill_color, fill_bg_color) 96 97 def Enable(self, *args): 98 super(DrawingArea, self).Enable(*args) 99 self.buffer.SetState(uiconst.UI_PICKCHILDREN) 100 self.content.SetState(uiconst.UI_NORMAL) 101 102 def Disable(self, *args): 103 super(DrawingArea, self).Disable(*args) 104 self.buffer.Disable(*args) 105 self.content.Disable(*args) 106 self.remove_x_button() 107 108 def Close(self): 109 self.buffer.Close() 110 super(DrawingArea, self).Close() 111 112 def on_number_of_polygons_changed(self, number_of_polygons): 113 self.on_polygons_changed(number_of_polygons) 114 115 116 class CannotAddMorePolygons(Exception): 117 pass 118 119 120 class DrawingContent(Container): 121 MINIMUM_POINTS = 3 122 123 def ApplyAttributes(self, attributes): 124 super(DrawingContent, self).ApplyAttributes(attributes) 125 self.drawing_area = attributes.get('drawing_area') 126 self.buffer_area = attributes.get('buffer_area') 127 self.on_number_of_polygons_changed = Signal(signalName='on_number_of_polygons_changed') 128 self.on_number_of_polygons_changed.connect(self.drawing_area.on_number_of_polygons_changed) 129 self.on_polygon_added = Signal(signalName='on_polygon_added') 130 self.on_polygon_added.connect(self.drawing_area.on_polygon_added) 131 self.on_polygon_deleted = Signal(signalName='on_polygon_deleted') 132 self.on_polygon_deleted.connect(self.drawing_area.on_polygon_deleted) 133 self.on_wip_polygon_cleared = Signal(signalName='on_wip_polygon_cleared') 134 self.on_wip_polygon_cleared.connect(self.drawing_area.on_wip_polygon_cleared) 135 self.on_polygon_started = Signal(signalName='on_polygon_started') 136 self.on_polygon_started.connect(self.drawing_area.on_polygon_started) 137 self.on_polygon_selected = Signal(signalName='on_polygon_selected') 138 self.on_polygon_selected.connect(self.drawing_area.on_polygon_selected) 139 self.on_polygon_deselected = Signal(signalName='on_polygon_deselected') 140 self.on_polygon_deselected.connect(self.drawing_area.on_polygon_deselected) 141 self.on_point_added = Signal(signalName='on_point_added') 142 self.on_point_added.connect(self.drawing_area.on_point_added) 143 self.cursor = CURSOR_SELECTOR_POSITION 144 self.polygons = [] 145 self.active_polygon = None 146 self.selected_polygon = None 147 self._selection_update_thread = None 148 self._on_internal_click_thread = None 149 self._on_external_click_thread = None 150 self.mouse_up_cookie = uicore.event.RegisterForTriuiEvents(uiconst.UI_MOUSEUP, self.on_general_click) 151 self.mouse_move_cookie = uicore.event.RegisterForTriuiEvents(uiconst.UI_MOUSEMOVE, self.on_general_move) 152 self.esc_cookie = uicore.event.RegisterForTriuiEvents(uiconst.UI_KEYUP, self.on_general_key_up) 153 154 def Close(self): 155 self._release_control() 156 uicore.event.UnregisterForTriuiEvents(self.mouse_up_cookie) 157 uicore.event.UnregisterForTriuiEvents(self.mouse_move_cookie) 158 uicore.event.UnregisterForTriuiEvents(self.esc_cookie) 159 super(DrawingContent, self).Close() 160 161 def Disable(self, *args): 162 super(DrawingContent, self).Disable(*args) 163 self.clear() 164 self.select_all_polygons() 165 166 def SetSize(self, width, height): 167 previous_width, previous_height = self.width, self.height 168 super(DrawingContent, self).SetSize(width, height) 169 if previous_width < 1 or previous_height < 1: 170 return 171 x_scale = float(self.width) / previous_width 172 y_scale = float(self.height) / previous_height 173 for polygon in self.polygons: 174 polygon.scale(x_scale, y_scale) 175 176 self.clear() 177 178 def clear(self): 179 self._clear_selection() 180 self._clear_wip_polygon() 181 self._set_active_polygon(None) 182 183 def _add_new_polygon(self): 184 if self.drawing_area.max_polygons and len(self.polygons) >= self.drawing_area.max_polygons: 185 raise CannotAddMorePolygons() 186 new_polygon = self._add_polygon() 187 self._set_active_polygon(new_polygon) 188 self.on_polygon_started() 189 190 def change_perimeter_color(self, perimeter_color): 191 for polygon in self.polygons: 192 polygon.set_perimeter_color(perimeter_color) 193 194 def inject_polygon(self, points, send_to_back = None, show_vertices = None, show_fill = None, perimeter_width = None, perimeter_color = None, fill_color = None, fill_bg_color = None): 195 polygon = self._add_polygon(show_vertices, show_fill, perimeter_width, perimeter_color, fill_color, fill_bg_color) 196 for x_proportion, y_proportion in points: 197 x, y = self.drawing_area.transform_proportions_to_coordinates(x_proportion, y_proportion) 198 polygon.add_point(x, y) 199 200 polygon.autocomplete_polygon() 201 if send_to_back: 202 polygon.set_arrangement_order(-1) 203 204 def select_all_polygons(self): 205 for polygon in self.polygons: 206 polygon.select() 207 208 def invalidate_polygons(self, invalid_polygons): 209 for polygon in self.polygons: 210 is_valid = polygon.order not in invalid_polygons 211 polygon.update_validation(is_valid) 212 213 def _add_polygon(self, show_vertices = None, show_fill = None, perimeter_width = None, perimeter_color = None, fill_color = None, fill_bg_color = None): 214 new_polygon = Polygon(parent=self, area=self.drawing_area, order=len(self.polygons), show_vertices=show_vertices, show_fill=show_fill, perimeter_width=perimeter_width, perimeter_color=perimeter_color, fill_color_selected=fill_color, fill_bg_color_selected=fill_bg_color) 215 new_polygon.on_edit_started.connect(self._on_polygon_edit_started) 216 new_polygon.on_deleted.connect(self._on_polygon_deleted) 217 new_polygon.on_completed.connect(self._on_polygon_completed) 218 new_polygon.on_wip_segment_added.connect(self._take_control) 219 new_polygon.on_wip_segment_removed.connect(self._release_control) 220 self.polygons.append(new_polygon) 221 return new_polygon 222 223 def _set_active_polygon(self, polygon): 224 self.active_polygon = polygon 225 if self.active_polygon and not self.active_polygon.is_complete: 226 for polygon in self.polygons: 227 if polygon == self.active_polygon: 228 polygon.enable() 229 else: 230 polygon.disable() 231 232 else: 233 for polygon in self.polygons: 234 polygon.enable() 235 236 def _on_polygon_edit_started(self, polygon): 237 if self._selection_update_thread: 238 self._selection_update_thread.kill() 239 self._selection_update_thread = StartTasklet(self._update_polygon_selection, polygon) 240 241 def _on_polygon_completed(self, polygon): 242 if self._selection_update_thread: 243 self._selection_update_thread.kill() 244 if self.active_polygon: 245 self._deselect_polygon(self.active_polygon) 246 self._set_active_polygon(None) 247 self.on_polygon_added(polygon.order) 248 self.on_number_of_polygons_changed(len(self.polygons)) 249 250 def _on_polygon_deleted(self, index): 251 if index >= len(self.polygons): 252 return 253 polygon_to_delete = self.polygons[index] 254 is_complete = polygon_to_delete.is_complete 255 if self.active_polygon == polygon_to_delete: 256 self._set_active_polygon(None) 257 if self.selected_polygon == polygon_to_delete: 258 self.selected_polygon = None 259 del self.polygons[index] 260 for order, polygon in enumerate(self.polygons): 261 polygon.set_order(order) 262 263 self._update_polygon_arrangement() 264 self.on_number_of_polygons_changed(len(self.polygons)) 265 self.on_polygon_deleted(index, is_complete) 266 267 def toggle_polygon_selection(self, index): 268 if index >= len(self.polygons): 269 return 270 polygon = self.polygons[index] 271 if self.selected_polygon == polygon: 272 self._deselect_polygon(polygon) 273 if self.active_polygon == polygon: 274 self._set_active_polygon(None) 275 else: 276 self._clear_selection() 277 self._select_polygon(polygon) 278 self._set_active_polygon(polygon) 279 280 def _select_polygon(self, polygon): 281 polygon.select() 282 self.selected_polygon = polygon 283 self._update_polygon_arrangement() 284 self.on_polygon_selected(polygon.order) 285 286 def _deselect_polygon(self, polygon): 287 polygon.deselect() 288 self.selected_polygon = None 289 self._update_polygon_arrangement() 290 self.on_polygon_deselected(polygon.order) 291 292 def _clear_selection(self): 293 if self.selected_polygon: 294 self._deselect_polygon(self.selected_polygon) 295 296 def _update_polygon_arrangement(self): 297 for polygon in reversed(self.polygons): 298 if polygon == self.selected_polygon: 299 polygon.set_arrangement_order(0) 300 else: 301 polygon.set_arrangement_order(-1) 302 303 def _update_polygon_selection(self, polygon): 304 try: 305 if self.active_polygon == polygon: 306 return 307 if self.active_polygon and not self.active_polygon.destroyed: 308 if not self.active_polygon.is_complete: 309 return 310 self._deselect_polygon(self.active_polygon) 311 self._set_active_polygon(None) 312 if not polygon.destroyed: 313 self._set_active_polygon(polygon) 314 self._select_polygon(self.active_polygon) 315 finally: 316 self._selection_update_thread = None 317 318 def _clear_wip_polygon(self): 319 if self.active_polygon and not self.active_polygon.is_complete: 320 self.active_polygon.Close() 321 self._set_active_polygon(None) 322 self.on_wip_polygon_cleared() 323 324 def _take_control(self): 325 uicore.registry.RequestControl(self) 326 327 def _release_control(self): 328 uicore.registry.ReleaseControl(self) 329 330 def OnClick(self, *args): 331 x, y = self.drawing_area.get_mouse_position() 332 self.on_click(x, y) 333 334 def on_click(self, x, y): 335 if self._on_internal_click_thread: 336 self._on_internal_click_thread.kill() 337 self._on_internal_click_thread = StartTasklet(self._process_internal_left_click, x, y) 338 339 def _is_left_click(self, vkey): 340 if vkey == uiconst.MOUSELEFT: 341 return True 342 return False 343 344 def on_general_click(self, obj, eventID, param): 345 vkey, flag = param 346 x, y = uicore.uilib.x - self.absoluteLeft, uicore.uilib.y - self.absoluteTop 347 is_left_click = self._is_left_click(vkey) 348 if self.drawing_area.is_position_in_area(x, y): 349 if is_left_click: 350 return True 351 self.on_internal_right_click() 352 elif is_left_click: 353 self.on_external_left_click() 354 return True 355 356 def on_internal_right_click(self): 357 if self._on_internal_click_thread: 358 self._on_internal_click_thread.kill() 359 self._on_internal_click_thread = StartTasklet(self._process_internal_right_click) 360 361 def on_external_left_click(self): 362 if self._on_external_click_thread: 363 self._on_external_click_thread.kill() 364 self._on_external_click_thread = StartTasklet(self._process_external_left_click) 365 366 def _process_internal_left_click(self, x, y): 367 try: 368 if self.active_polygon: 369 if self.active_polygon.is_complete: 370 self._deselect_polygon(self.active_polygon) 371 self._add_new_polygon() 372 else: 373 self._add_new_polygon() 374 if self.active_polygon and not self.active_polygon.is_complete: 375 self.active_polygon.add_point(x, y) 376 self.on_point_added() 377 except CannotAddMorePolygons: 378 return 379 finally: 380 self._on_internal_click_thread = None 381 382 def _process_internal_right_click(self): 383 try: 384 self._clear_wip_polygon() 385 finally: 386 self._on_general_click_thread = None 387 388 def _process_external_left_click(self): 389 try: 390 if self.active_polygon: 391 if self.active_polygon.get_point_amount() >= self.MINIMUM_POINTS: 392 self.active_polygon.autocomplete_polygon() 393 else: 394 self._clear_wip_polygon() 395 finally: 396 self._on_general_click_thread = None 397 398 def on_general_move(self, *args): 399 x, y = uicore.uilib.x - self.absoluteLeft, uicore.uilib.y - self.absoluteTop 400 if not self.drawing_area.is_position_in_area(x, y): 401 return True 402 if self.active_polygon: 403 self.active_polygon.move_cursor() 404 return True 405 406 def on_general_key_up(self, _window, _event_id, key_data, *args): 407 key, _ = key_data 408 if key == uiconst.VK_ESCAPE: 409 self._clear_wip_polygon() 410 return True