示例#1
0
 def mouse_move(self, point):
     vector = point - self.__old_point
     self.__box += vector
     if self.__box.x1 < 0:
         self.__box -= Vector(self.__box.x1, 0)
     if self.__box.y1 < 0:
         self.__box -= Vector(0, self.__box.y1)
     self.__old_point = point
示例#2
0
    def snap_rectangle(self, rectangle):
        snapped_vertically = []
        snapped_horizontally = []

        center = rectangle.center
        snapped_center = self.snap_point(center)
        if snapped_center.snapped_horizontally:
            snapped_horizontally.append(
                (snapped_center, (snapped_center.point - center)))
        if snapped_center.snapped_vertically:
            snapped_vertically.append(
                (snapped_center, (snapped_center.point - center)))

        for point in rectangle.left_center, rectangle.right_center:
            snapped_point = self.__snap_point_horizontally(point)
            if snapped_point.snapped_horizontally:
                snapped_horizontally.append(
                    (snapped_point, (snapped_point.point - point)))

        for point in rectangle.top_center, rectangle.bottom_center:
            snapped_point = self.__snap_point_vertically(point)
            if snapped_point.snapped_vertically:
                snapped_vertically.append(
                    (snapped_point, (snapped_point.point - point)))

        best_vertical_indicators = None
        best_vertical_delta = float('inf')
        best_horizontal_indicators = None
        best_horizontal_delta = float('inf')

        for snapping, vector in snapped_vertically:
            if abs(vector.y) < abs(best_vertical_delta):
                best_vertical_delta = vector.y
                best_vertical_indicators = snapping.vertical_indicators

        for snapping, vector in snapped_horizontally:
            if abs(vector.x) < abs(best_horizontal_delta):
                best_horizontal_delta = vector.x
                best_horizontal_indicators = snapping.horizontal_indicators

        if best_horizontal_indicators is not None and best_vertical_indicators is not None:
            return SnappedRectangle(
                rectangle + Vector(best_horizontal_delta, best_vertical_delta),
                best_horizontal_indicators, best_vertical_indicators)
        elif best_horizontal_indicators is not None:
            return SnappedRectangle(
                rectangle + Vector(best_horizontal_delta, 0),
                horizontal_indicators=best_horizontal_indicators)
        elif best_vertical_indicators is not None:
            return SnappedRectangle(
                rectangle + Vector(0, best_vertical_delta),
                vertical_indicators=best_vertical_indicators)
        else:
            return SnappedRectangle(rectangle)
示例#3
0
    def _do(self, ruler):
        if self.__diagram.contains(self.__connection_object):
            raise CommandNotDone

        self.__connection_visual = self.__diagram.show(self.__connection_object)
        
        if self.__connection_visual.source is self.__connection_visual.destination:
            rect = self.__connection_visual.source.get_bounds(ruler)
            point1 = rect.right_center + Vector(30, -5)
            point2 = rect.right_center + Vector(30, 5)
            self.__connection_visual.add_point(ruler, None, point1)
            self.__connection_visual.add_point(ruler, None, point2)
示例#4
0
    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))
示例#5
0
    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)
示例#6
0
    def __init__(self, painter):
        """
        Painter used by the canvas
        
        :type painter: QPainter
        """

        self.__painter = painter
        self.__ruler = QTRuler()
        self.__zoom = 1
        self.__delta = Vector(0, 0)

        self.__zoom_set = False
        self.__delta_set = False
示例#7
0
 def draw(self, canvas):
     canvas.zoom(self.ZOOM_FACTOR ** self.__zoom)
     self.__diagram.draw(canvas, self.__selection)
     if self.__current_action is not None:
         if self.__current_action.box is not None:
             canvas.draw_rectangle(
                 self.__current_action.box,
                 self.SELECTION_RECTANGLE_BORDER,
                 self.SELECTION_RECTANGLE_FILL,
                 self.SELECTION_RECTANGLE_WIDTH
             )
         if self.__current_action.path is not None:
             canvas.draw_path(
                 self.__current_action.path,
                 fg=self.SELECTION_RECTANGLE_BORDER,
                 line_width=self.SELECTION_RECTANGLE_WIDTH
             )
         
         for indicator in self.__current_action.horizontal_snapping_indicators:
             size_vector = Vector(0, self.SNAPPING_INDICATOR_SIZE / 2)
             canvas.draw_line(
                 indicator - size_vector,
                 indicator + size_vector,
                 self.SNAPPING_INDICATOR_COLOR,
                 line_width=self.SNAPPING_INDICATOR_WIDTH,
                 line_style=self.SNAPPING_INDICATOR_STYLE
             )
         
         for indicator in self.__current_action.vertical_snapping_indicators:
             size_vector = Vector(self.SNAPPING_INDICATOR_SIZE / 2, 0)
             canvas.draw_line(
                 indicator - size_vector,
                 indicator + size_vector,
                 self.SNAPPING_INDICATOR_COLOR,
                 line_width=self.SNAPPING_INDICATOR_WIDTH,
                 line_style=self.SNAPPING_INDICATOR_STYLE
             )
示例#8
0
 def __draw(self, bounds, output):
     painter = QPainter()
     painter.begin(output)
     painter.setRenderHints(QPainter.Antialiasing
                            | QPainter.TextAntialiasing)
     canvas = QTPainterCanvas(painter)
     canvas.zoom(self.__zoom)
     canvas.translate(
         Vector(-bounds.x1 + self.__padding, -bounds.y1 + self.__padding))
     if isinstance(self.__diagram_or_selection, Diagram):
         self.__diagram_or_selection.draw(canvas,
                                          transparent=self.__transparent)
     else:
         self.__diagram_or_selection.draw_selected(
             canvas, transparent=self.__transparent)
     painter.end()
示例#9
0
 def _do(self, ruler):
     aligned_x = self.__align(self.__horizontal, self.__align_to.x1, self.__align_to.x2)
     aligned_y = self.__align(self.__vertical, self.__align_to.y1, self.__align_to.y2)
     
     for element in self.__elements:
         bounds = element.get_bounds(ruler)
         
         if self.__horizontal is None:
             vector_x = 0
         else:
             vector_x = aligned_x - self.__align(self.__horizontal, bounds.x1, bounds.x2)
         
         if self.__vertical is None:
             vector_y = 0
         else:
             vector_y = aligned_y - self.__align(self.__vertical, bounds.y1, bounds.y2)
         
         old_position = element.get_position(ruler)
         new_position = old_position + Vector(vector_x, vector_y)
         
         self.__element_positions.append((element, old_position, new_position))
     
     self._redo(ruler)
示例#10
0
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
示例#11
0
 def draw(self, canvas, shadow):
     self.__child.draw(canvas, ShadowInfo(self.__color,
                                          Vector(self.__padding, self.__padding)))
     self.__child.draw(canvas, None)