Exemple #1
0
 def assign_bounds(self, bounds):
     x = bounds.x1
     y = bounds.y1
     w = bounds.width
     h = bounds.height
     
     w_inner = self.__child_size.width
     h_inner = self.__child_size.height
     
     if self.__horizontal == HorizontalAlignment.center:
         x += (w - w_inner) // 2
         w = w_inner
     elif self.__horizontal == HorizontalAlignment.left:
         w = w_inner
     elif self.__horizontal == HorizontalAlignment.right:
         x += w - w_inner
         w = w_inner
     
     if self.__vertical == VerticalAlignment.center:
         y += (h - h_inner) // 2
         h = h_inner
     elif self.__vertical == VerticalAlignment.top:
         h = h_inner
     elif self.__vertical == VerticalAlignment.bottom:
         y += h - h_inner
         h = h_inner
     
     self.__child.assign_bounds(Rectangle(x, y, w, h))
Exemple #2
0
 def resize(self, new_size):
     if new_size is None:
         self.__size = self.__object.get_minimal_size()
     else:
         self.__size = new_size
     self.__object.assign_bounds(
         Rectangle.from_point_size(self.__position, self.__size))
Exemple #3
0
    def mouse_move(self, point):
        x1 = self.__orig_box.x1
        y1 = self.__orig_box.y1
        x2 = self.__orig_box.x2
        y2 = self.__orig_box.y2

        vector = point - self.__old_point

        min_size = self.__element.get_minimal_size(self.application.ruler)

        if self.__horizontal == SelectionPointPosition.first:
            x1 += vector.x
            if x1 < 0:
                x1 = 0
            if x2 - x1 < min_size.width:
                x1 = x2 - min_size.width
        elif self.__horizontal == SelectionPointPosition.last:
            x2 += vector.x
            if x2 - x1 < min_size.width:
                x2 = x1 + min_size.width

        if self.__vertical == SelectionPointPosition.first:
            y1 += vector.y
            if y1 < 0:
                y1 = 0
            if y2 - y1 < min_size.height:
                y1 = y2 - min_size.height
        elif self.__vertical == SelectionPointPosition.last:
            y2 += vector.y
            if y2 - y1 < min_size.height:
                y2 = y1 + min_size.height

        self.__box = Rectangle(x1, y1, x2 - x1, y2 - y1)
Exemple #4
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)
Exemple #5
0
 def assign_bounds(self, bounds):
     self.__child.assign_bounds(
         Rectangle(
             bounds.x1 + self.__left,
             bounds.y1 + self.__top,
             bounds.width - self.__left - self.__right,
             bounds.height - self.__top - self.__bottom
         )
     )
Exemple #6
0
 def draw(self, canvas, shadow):
     if shadow:
         canvas.draw_rectangle(
             Rectangle.from_point_size(self.__position, self.__size),
             None,
             shadow.color
         )
     else:
         canvas.draw_image(self.__position, self.__image)
Exemple #7
0
    def assign_bounds(self, bounds):
        x = bounds.x1
        y = bounds.y1

        y_cur = y
        for height, row in zip(self.__rows, self.__table):
            x_cur = x
            for width, child in zip(self.__columns, row):
                child.assign_bounds(Rectangle(x_cur, y_cur, width, height))
                x_cur += width
            y_cur += height
Exemple #8
0
 def draw(self, canvas, shadow):
     if shadow:
         canvas.draw_rectangle(
             Rectangle.from_point_size(
                 self.__rectangle.top_left + shadow.shift,
                 self.__rectangle.size,
             ), None, shadow.color)
     else:
         canvas.draw_rectangle(self.__rectangle, self.__border, self.__fill)
         if self.__child is not None:
             self.__child.draw(canvas, None)
Exemple #9
0
 def assign_bounds(self, bounds):
     position = bounds.top_left
     whole_size = bounds.size
     
     if self.__children:
         deltas = self.__compute_deltas(
             [self._get_size_component(size) for size in self.__children_sizes],
             self._get_size_component(whole_size)
         )
         for size, delta, child in zip(self.__children_sizes, deltas, self.__children):
             new_size = self._new_size(size, whole_size, delta)
             child.child.assign_bounds(Rectangle.from_point_size(position, new_size))
             position = self._new_position(position, new_size)
Exemple #10
0
    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)
Exemple #11
0
    def move(self, ruler, new_position):
        self.__cache.ensure_valid(ruler=ruler)

        new_center = Rectangle.from_point_size(
            new_position, self.__cached_appearance.get_minimal_size()).center

        new_index = None
        new_distance = float('inf')
        new_position = None
        new_angle = None

        points = list(self.__connection().get_points(ruler))

        old_point = points.pop(0)
        for idx, point in enumerate(points):
            line = Line.from_point_point(old_point, point)

            point_on_line = line.get_nearest_point_to(new_center)

            cur_distance = (new_center - point_on_line).length

            if new_distance > cur_distance:
                new_distance = cur_distance
                new_index = idx
                if point.x == old_point.x:
                    if point.y == old_point.y:
                        new_position = 0
                    else:
                        new_position = (point_on_line.y -
                                        old_point.y) / (point.y - old_point.y)
                else:
                    new_position = (point_on_line.x -
                                    old_point.x) / (point.x - old_point.x)

                first_angle = math.atan2(new_center.y - point_on_line.y,
                                         new_center.x - point_on_line.x)
                second_angle = math.atan2(point.y - old_point.y,
                                          point.x - old_point.x)
                new_angle = first_angle - second_angle

            old_point = point

        self.__distance = new_distance
        self.__line_index = new_index
        self.__line_position = new_position
        self.__angle = new_angle

        self.__cache.invalidate()
Exemple #12
0
 def mouse_move(self, point):
     self.__box = Rectangle.from_point_point(self.__box.top_left, point)
Exemple #13
0
    def get_bounds(self, ruler):
        self.__cache.ensure_valid(ruler=ruler)

        return Rectangle.from_point_size(self.__position, self.__size)
Exemple #14
0
 def move(self, new_position):
     self.__position = new_position
     self.__object.assign_bounds(
         Rectangle.from_point_size(self.__position, self.__size))
Exemple #15
0
 def get_bounds(self, ruler):
     return Rectangle.combine_bounds(
         visual.get_bounds(ruler)
         for visual in chain(self.__elements, self.__connections))
Exemple #16
0
 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)
Exemple #17
0
 def mouse_down(self, point):
     self.__box = Rectangle.from_point_point(point, point)
Exemple #18
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