def __show_connection(self, ruler, diagram, connection, data): visual = diagram.show(connection) for point in data['points']: visual.add_point(ruler, None, Point(point['x'], point['y'])) for label in visual.get_labels(): point = data['labels'][label.id] label.move(ruler, Point(point['x'], point['y'])) return visual
def add_to_path(self, context, builder): x1 = self.__x1(context) y1 = self.__y1(context) x2 = self.__x2(context) y2 = self.__y2(context) x = self.__x(context) y = self.__y(context) builder.cubic_to(Point(x1, y1), Point(x2, y2), Point(x, y))
def __load_connection_visual(self, diagram, node): connection = self.__connection_map[UUID(node.attrib["id"])] visual = diagram.show(connection) for child in node: if child.tag == "{{{0}}}Point".format(MODEL_NAMESPACE): position = Point(int(child.attrib["x"]), int(child.attrib["y"])) visual.add_point(self.__ruler, None, position) elif child.tag == "{{{0}}}Label".format(MODEL_NAMESPACE): label = visual.get_label(child.attrib["id"]) position = Point(int(child.attrib["x"]), int(child.attrib["y"])) label.move(self.__ruler, position) else: raise Exception
def contextMenuEvent(self, event): if event.reason() == QContextMenuEvent.Mouse: pos = event.pos() point = Point(pos.x(), pos.y()) self.__drawing_area.ensure_selection_at(point) self.__drawing_area.reset_action() self.__do_update() if self.__drawing_area.selection.is_element_selected: menu = CanvasElementMenu( self.__main_window, self.__drawing_area, self.__drawing_area.selection.selected_elements) elif self.__drawing_area.selection.is_connection_selected: menu = CanvasConnectionMenu( self.__main_window, self.__drawing_area, self.__drawing_area.selection.selected_connection) elif self.__drawing_area.selection.is_diagram_selected: menu = CanvasDiagramMenu( self.__main_window, self.__drawing_area, self.__drawing_area.selection.selected_diagram) else: raise Exception menu_pos = event.globalPos() menu.exec_(menu_pos)
def __create_appearance_object(self, ruler): self.__cached_appearance = self.__connection( ).object.create_label_object(self.__id, ruler) first_point = self.__connection().get_point(ruler, self.__line_index) second_point = self.__connection().get_point(ruler, self.__line_index + 1) line_vector = second_point - first_point vector_to_label = Vector.from_angle_length( line_vector.angle + self.__angle, self.__distance) position = first_point\ + line_vector * self.__line_position\ + vector_to_label\ - self.__cached_appearance.size.as_vector() / 2 x, y = round(position.x), round(position.y) if x < 0: x = 0 if y < 0: y = 0 self.__cached_appearance.move(Point(x, y))
def mouseDoubleClickEvent(self, event): pos = event.pos() point = Point(pos.x(), pos.y()) self.__drawing_area.ensure_selection_at(point) visual = self.__drawing_area.selection.get_lonely_selected_visual() if visual is None: return if not visual.object.has_ufl_dialog: return self.__drawing_area.set_action(None) self.unsetCursor() if not isinstance(visual, ElementVisual): PropertiesDialog.open_for(self.__main_window, visual.object) elif visual.object.type.default_action == DefaultElementAction.properties: PropertiesDialog.open_for(self.__main_window, visual.object) elif visual.object.type.default_action == DefaultElementAction.subdiagram: for diagram in visual.object.diagrams: if diagram is not self.__drawing_area.diagram: Application().tabs.select_tab(diagram) break else: PropertiesDialog.open_for(self.__main_window, visual.object)
def __load_element_visual(self, diagram, node): position = Point(int(node.attrib["x"]), int(node.attrib["y"])) size = Size(int(node.attrib["width"]), int(node.attrib["height"])) element = self.__element_map[UUID(node.attrib["id"])] visual = diagram.show(element) visual.move(self.__ruler, position) visual.resize(self.__ruler, size)
def load(self): definitions = { "ArrowDefinition": {}, "CornerDefinition": {}, "SideDefinition": {}, } for child in self.__xmlroot: if child.tag == "{{{0}}}ArrowDefinition".format(ADDON_NAMESPACE): definition = ArrowDefinition( child.attrib["id"], PathBuilder().from_string(child.attrib["path"]).build(), Point.parse(child.attrib["center"]), self.__parse_rotation(child.attrib["rotation"]) ) definitions["ArrowDefinition"][definition.id] = definition elif child.tag == "{{{0}}}CornerDefinition".format(ADDON_NAMESPACE): ornament = None if "ornament" in child.attrib: ornament = PathBuilder().from_string(child.attrib["ornament"]).build() definition = CornerDefinition( child.attrib["id"], PathBuilder().from_string(child.attrib["path"]).build(), ornament, Point.parse(child.attrib["center"]), child.attrib["corner"] ) definitions["CornerDefinition"][definition.id] = definition elif child.tag == "{{{0}}}SideDefinition".format(ADDON_NAMESPACE): ornament = None if "ornament" in child.attrib: ornament = PathBuilder().from_string(child.attrib["ornament"]).build() definition = SideDefinition( child.attrib["id"], PathBuilder().from_string(child.attrib["path"]).build(), ornament, Point.parse(child.attrib["center"]), Size(int(child.attrib["width"]), int(child.attrib["height"])), child.attrib["side"] ) definitions["SideDefinition"][definition.id] = definition return definitions
def dropEvent(self, event): mime_data = event.mimeData() if isinstance(mime_data, ProjectMimeData) and isinstance( mime_data.model_object, ElementObject): pos = event.pos() point = Point(pos.x(), pos.y()) element = mime_data.model_object command = ShowElementCommand(self.diagram, element, point) Application().commands.execute(command)
def __execute_menu_action(self, menu_item): self.__menu_selected = True global_pos = QCursor.pos() pos = self.mapFromGlobal(global_pos) point = Point(pos.x(), pos.y()) self.__drawing_area.execute_menu_action( menu_item, point, QApplication.keyboardModifiers() == Qt.ControlModifier, QApplication.keyboardModifiers() == Qt.ShiftModifier)
def __init__(self, diagram, object): self.__cache = ModelTemporaryDataCache(self.__create_appearance_object) self.__cache.depend_on(object.cache) self.__diagram = ref(diagram) self.__object = object self.__cached_appearance = None self.__position = Point(0, 0) self.__size = None self.__connections = WeakSet()
def mouseMoveEvent(self, event): pos = event.pos() point = Point(pos.x(), pos.y()) self.__drawing_area.mouse_move( point, QApplication.keyboardModifiers() == Qt.ControlModifier, QApplication.keyboardModifiers() == Qt.ShiftModifier) if self.__drawing_area.action_active: self.__do_update() else: self.__update_cursor()
def snap_point(self, point): best_horizontal = None best_horizontal_distance = self.MAXIMAL_DISTANCE best_vertical = None best_vertical_distance = self.MAXIMAL_DISTANCE for guideline in self.__center_guidelines: horizontal_distance = abs(guideline.x - point.x) vertical_distance = abs(guideline.y - point.y) if horizontal_distance < best_horizontal_distance: best_horizontal_distance = horizontal_distance best_horizontal = guideline if vertical_distance < best_vertical_distance: best_vertical_distance = vertical_distance best_vertical = guideline horizontal_indicators = set() vertical_indicators = set() if best_horizontal is not None and best_vertical is not None: point = Point(best_horizontal.x, best_vertical.y) elif best_horizontal is not None: point = Point(best_horizontal.x, point.y) elif best_vertical is not None: point = Point(point.x, best_vertical.y) if best_horizontal is not None: for guideline in self.__center_guidelines: if best_horizontal.x == guideline.x: horizontal_indicators.add(guideline) horizontal_indicators.add(point) if best_vertical is not None: for guideline in self.__center_guidelines: if best_vertical.y == guideline.y: vertical_indicators.add(guideline) vertical_indicators.add(point) return SnappedPoint(point, horizontal_indicators, vertical_indicators)
def mouseReleaseEvent(self, event): pos = event.pos() point = Point(pos.x(), pos.y()) if self.__mouse_down: self.__drawing_area.mouse_up( point, QApplication.keyboardModifiers() == Qt.ControlModifier, QApplication.keyboardModifiers() == Qt.ShiftModifier) self.__mouse_down = False self.__do_update() else: super().mousePressEvent(event)
def mouse_move(self, point): x, y = point.x, point.y if x < 0: x = 0 if y < 0: y = 0 point = Point(x, y) if self.__snapping is not None: self.__snapped = self.__snapping.snap_point(point) point = self.__snapped.point else: self.__snapped = None self.__points[self.__index] = point self.__build_path()
def mouse_move(self, point): if self.__source_element is not None: x, y = point.x, point.y if x < 0: x = 0 if y < 0: y = 0 point = Point(x, y) if self.__snapping is not None: self.__snapped = self.__snapping.snap_point(point) point = self.__snapped.point else: self.__snapped = None self.__last_point = point self.__build_path()
def __snap_point_horizontally(self, point): best = None best_distance = self.MAXIMAL_DISTANCE for guideline in self.__horizontal_guidelines: horizontal_distance = abs(guideline.x - point.x) if horizontal_distance < best_distance: best_distance = horizontal_distance best = guideline indicators = set() if best is not None: point = Point(best.x, point.y) for guideline in self.__horizontal_guidelines: if best.x == guideline.x: indicators.add(guideline) indicators.add(point) return SnappedPoint(point, horizontal_indicators=indicators) return SnappedPoint(point)
def __snap_point_vertically(self, point): best_vertical = None best_vertical_distance = self.MAXIMAL_DISTANCE for guideline in self.__vertical_guidelines: vertical_distance = abs(guideline.y - point.y) if vertical_distance < best_vertical_distance: best_vertical_distance = vertical_distance best_vertical = guideline vertical_indicators = set() if best_vertical is not None: point = Point(point.x, best_vertical.y) for guideline in self.__vertical_guidelines: if best_vertical.y == guideline.y: vertical_indicators.add(guideline) vertical_indicators.add(point) return SnappedPoint(point, vertical_indicators=vertical_indicators) return SnappedPoint(point)
class Selection: SELECTION_COLOR = Colors.blue SELECTION_SIZE = 2 SELECTION_POINT_COLOR = Colors.darkgray SELECTION_POINT_SIZE = 8 LABEL_LINE_MINIMAL_DISTANCE = 10 ICON_COLOR = Colors.darkgray ICON_COLOR_BACKGROUND = Colors.white.add_alpha(200) CONNECTION_ICON_SHIFT = Vector(8, 0) CONNECTION_ICON = PathBuilder()\ .move_to(Point(0, 5))\ .line_to(Point(11, 5))\ .move_to(Point(6, 0))\ .line_to(Point(11, 5))\ .line_to((Point(6, 10)))\ .build() CONNECTION_ICON_BOUNDS = Rectangle(0, 0, 11, 10) + CONNECTION_ICON_SHIFT def __init__(self, application, diagram): self.__selected = set() self.__application = application self.__diagram = diagram application.event_dispatcher.subscribe(ElementHiddenEvent, self.__something_removed) application.event_dispatcher.subscribe(ConnectionHiddenEvent, self.__something_removed) def __something_removed(self, event): new_selection = set() for selected_item in self.__selected: if self.__diagram.contains(selected_item): new_selection.add(selected_item) self.__selected = new_selection @property def selected_visuals(self): yield from self.__selected @property def selected_elements(self): for visual in self.__selected: if isinstance(visual, ElementVisual): yield visual @property def selected_connection(self): for visual in self.__selected: if isinstance(visual, ConnectionVisual): return visual return None @property def selected_diagram(self): if self.__selected: return None else: return self.__diagram @property def diagram(self): return self.__diagram def select(self, visual): self.__selected.clear() if visual is not None: if isinstance(visual, Iterable): self.__selected.update(visual) else: self.__selected.add(visual) self.__application.event_dispatcher.dispatch( SelectionChangedEvent(self.__diagram, self)) def select_all(self): self.__selected.clear() self.__selected.update(self.__diagram.elements) self.__application.event_dispatcher.dispatch( SelectionChangedEvent(self.__diagram, self)) def deselect_all(self): self.__selected.clear() self.__application.event_dispatcher.dispatch( SelectionChangedEvent(self.__diagram, self)) def add_to_selection(self, visual): if isinstance(visual, ConnectionVisual): self.__selected.clear() elif self.__selected and all( isinstance(sel, ConnectionVisual) for sel in self.__selected): self.__selected.clear() self.__selected.add(visual) self.__application.event_dispatcher.dispatch( SelectionChangedEvent(self.__diagram, self)) def remove_from_selection(self, visual): if visual in self.__selected: self.__selected.remove(visual) self.__application.event_dispatcher.dispatch( SelectionChangedEvent(self.__diagram, self)) def toggle_select(self, visual): if visual in self.__selected: self.remove_from_selection(visual) else: self.add_to_selection(visual) def select_at(self, point): object = self.__diagram.get_visual_at(self.__application.ruler, point) self.deselect_all() if object is not None: self.add_to_selection(object) def select_in_area(self, area): self.__selected.clear() for element in self.__diagram.elements: if element.get_bounds( self.__application.ruler).is_overlapping(area): self.__selected.add(element) self.__application.event_dispatcher.dispatch( SelectionChangedEvent(self.__diagram, self)) def draw_for(self, canvas, visual): if visual not in self.__selected: return if isinstance(visual, ElementVisual): bounds = visual.get_bounds(canvas.get_ruler()) if len(self.__selected) == 1: for pos_x, pos_y in self.__get_selection_points_positions( visual): self.__draw_selection_point(canvas, bounds, pos_x, pos_y) connection_icon_position = bounds.top_right + self.CONNECTION_ICON_SHIFT connection_icon = self.CONNECTION_ICON.transform( Transformation.make_translate(connection_icon_position)) canvas.draw_path(connection_icon, fg=self.ICON_COLOR, bg=self.ICON_COLOR_BACKGROUND, line_width=2) canvas.draw_rectangle(bounds, fg=self.SELECTION_COLOR, line_width=self.SELECTION_SIZE) elif isinstance(visual, ConnectionVisual): if len(self.__selected) == 1: fg_color = None bg_color = self.SELECTION_POINT_COLOR else: fg_color = self.SELECTION_POINT_COLOR bg_color = None for point in visual.get_points(canvas.get_ruler()): canvas.draw_rectangle(Rectangle.from_point_size( point - Vector(self.SELECTION_POINT_SIZE // 2, self.SELECTION_POINT_SIZE // 2), Size(self.SELECTION_POINT_SIZE, self.SELECTION_POINT_SIZE)), fg=fg_color, bg=bg_color) for label in visual.get_labels(): bounds = label.get_bounds(canvas.get_ruler()) if bounds.width and bounds.height: canvas.draw_rectangle(bounds, fg=self.SELECTION_COLOR, line_width=self.SELECTION_SIZE) line_to_connection = Line.from_point_point( label.get_nearest_point(canvas.get_ruler()), bounds.center) intersect = list(bounds.intersect(line_to_connection)) if intersect and ( line_to_connection.first - intersect[0] ).length > self.LABEL_LINE_MINIMAL_DISTANCE: canvas.draw_line(line_to_connection.first, intersect[0], fg=self.SELECTION_COLOR, line_width=self.SELECTION_SIZE, line_style=LineStyle.dot) def draw_selected(self, canvas, selection=False, transparent=False): if not transparent: self.__diagram.draw_background(canvas) if self.is_connection_selected: self.selected_connection.draw(canvas) if selection: self.draw_for(canvas, self.selected_connection) elif self.is_element_selected: elements = [ element for element in self.__diagram.elements if element in self.__selected ] for element in elements: element.draw(canvas) if selection: self.draw_for(canvas, element) for connection in self.__diagram.connections: if connection.source in self.__selected and connection.destination in self.__selected: connection.draw(canvas) if selection: self.draw_for(canvas, connection) else: raise Exception def __get_selection_points_positions(self, visual): resizable_x, resizable_y = visual.is_resizable( self.__application.ruler) if resizable_x and resizable_y: yield SelectionPointPosition.first, SelectionPointPosition.first yield SelectionPointPosition.first, SelectionPointPosition.last yield SelectionPointPosition.last, SelectionPointPosition.first yield SelectionPointPosition.last, SelectionPointPosition.last if resizable_x: yield SelectionPointPosition.first, SelectionPointPosition.center yield SelectionPointPosition.last, SelectionPointPosition.center if resizable_y: yield SelectionPointPosition.center, SelectionPointPosition.first yield SelectionPointPosition.center, SelectionPointPosition.last def __draw_selection_point(self, canvas, bounds, pos_x, pos_y): canvas.draw_rectangle(self.__get_selection_point(bounds, pos_x, pos_y), bg=self.SELECTION_POINT_COLOR) def __get_selection_point(self, bounds, pos_x, pos_y): x1, x2 = self.__compute_selection_point_position( pos_x, bounds.x1, bounds.width) y1, y2 = self.__compute_selection_point_position( pos_y, bounds.y1, bounds.height) return Rectangle(x1, y1, x2 - x1, y2 - y1) def __compute_selection_point_position(self, pos, start, size): if pos == SelectionPointPosition.first: return start, start + self.SELECTION_POINT_SIZE elif pos == SelectionPointPosition.center: return start + size // 2 - self.SELECTION_POINT_SIZE // 2, start + size // 2 + self.SELECTION_POINT_SIZE // 2 elif pos == SelectionPointPosition.last: return start + size - self.SELECTION_POINT_SIZE, start + size def get_action_at(self, position, shift_pressed): if len(self.__selected) == 1 and self.is_connection_selected: connection, = self.__selected action = self.__get_connection_action_at(connection, position, shift_pressed) if isinstance(action, (MoveConnectionPointAction, AddConnectionPointAction, RemoveConnectionPointAction)): return action visual = self.__diagram.get_visual_at(self.__application.ruler, position) if len(self.__selected) == 1 and self.is_element_selected: element, = self.__selected if visual is None or self.__diagram.get_z_order( visual) < self.__diagram.get_z_order(element): element_icon_action = self.__get_element_icon_action_at( element, position, shift_pressed) if element_icon_action is not None: return element_icon_action if visual not in self.__selected: return None if len(self.__selected) > 1: if shift_pressed: return None else: return MoveSelectionAction() if isinstance(visual, ElementVisual): return self.__get_element_action_at(visual, position, shift_pressed) elif isinstance(visual, ConnectionVisual): return self.__get_connection_action_at(visual, position, shift_pressed) else: return None def __get_element_action_at(self, element, position, shift_pressed): if shift_pressed: return None bounds = element.get_bounds(self.__application.ruler) for pos_x, pos_y in self.__get_selection_points_positions(element): if self.__get_selection_point(bounds, pos_x, pos_y).contains(position): return ResizeElementAction(element, pos_x, pos_y) return MoveSelectionAction() def __get_element_icon_action_at(self, element, position, shift_pressed): if shift_pressed: return None bounds = element.get_bounds(self.__application.ruler) connection_icon_bounds = self.CONNECTION_ICON_BOUNDS + bounds.top_right.as_vector( ) if connection_icon_bounds.contains(position): return AddUntypedConnectionAction(element) return None def __get_connection_action_at(self, connection, position, shift_pressed): found = None for idx, point in enumerate( connection.get_points(self.__application.ruler)): if idx > 0: # don't return it for first point if (position - point ).length < ConnectionVisual.MAXIMAL_CLICKABLE_DISTANCE: found = idx elif found is not None: # don't return it for last point if shift_pressed: return RemoveConnectionPointAction(connection, found) else: return MoveConnectionPointAction(connection, found) for label in connection.get_labels(): if label.get_bounds(self.__application.ruler).contains(position): if shift_pressed: return None else: return MoveConnectionLabelAction(connection, label.id) if shift_pressed: last = None for idx, point in enumerate( connection.get_points(self.__application.ruler)): if last is not None: distance = Line.from_point_point( last, point).get_distance_to(position) if distance < ConnectionVisual.MAXIMAL_CLICKABLE_DISTANCE: return AddConnectionPointAction(connection, idx) last = point return None def is_selection_at(self, position): visual = self.__diagram.get_visual_at(self.__application.ruler, position) return visual in self.__selected def is_selected(self, visual): return visual in self.__selected def get_bounds(self, include_connections=True): bounds = [ visual.get_bounds(self.__application.ruler) for visual in self.__selected ] if include_connections and self.is_element_selected: for connection in self.__diagram.connections: if connection.source in self.__selected and connection.destination in self.__selected: bounds.append( connection.get_bounds(self.__application.ruler)) return Rectangle.combine_bounds(bounds) @property def is_diagram_selected(self): return len(self.__selected) == 0 @property def is_connection_selected(self): return any( isinstance(visual, ConnectionVisual) for visual in self.__selected) @property def is_element_selected(self): return any( isinstance(visual, ElementVisual) for visual in self.__selected) @property def size(self): return len(self.__selected) def get_lonely_selected_visual(self): if len(self.__selected) != 1: return None for visual in self.__selected: return visual
def _new_position(self, position, size): return Point(position.x + size.width, position.y)
def __get_position(self, node): return Point(int(node.attrib.get("x", 0)), int(node.attrib.get("y", 0)))
def __init__(self, object): self.__object = object self.__position = Point(0, 0) self.__size = self.__object.get_minimal_size()
def add_to_path(self, context, builder): x = self.__x(context) y = self.__y(context) builder.move_to(Point(x, y))
def __show_element(self, ruler, diagram, element, data): visual = diagram.show(element) visual.move(ruler, Point(data['x'], data['y'])) visual.resize(ruler, Size(data['width'], data['height'])) return visual
def __transform_position(self, position): zoom = self.ZOOM_FACTOR**self.__zoom return Point(int(round(position.x / zoom)), int(round(position.y / zoom)))
def _new_position(self, position, size): return Point(position.x, position.y + size.height)