Exemplo n.º 1
0
    def __init__(self,
                 in_port,
                 name=None,
                 parent=None,
                 side=SnappedSide.RIGHT):
        self.handle = Handle(connectable=True)
        self.port = RectanglePointPort(self.handle.pos, self)
        self._is_in_port = in_port
        self._side = None
        self.direction = None
        self.side = side
        self._parent = ref(parent)
        self._view = None

        self.text_color = gui_config.gtk_colors['LABEL']
        self.fill_color = gui_config.gtk_colors['LABEL']

        self._incoming_handles = []
        self._outgoing_handles = []
        self._connected_connections = []
        self._tmp_incoming_connected = False
        self._tmp_outgoing_connected = False

        self._name = name

        self.label_print_inside = True

        self._port_image_cache = ImageCache()
        self._label_image_cache = ImageCache()
        self._last_label_size = self.port_side_size, self.port_side_size
        self._last_label_relative_pos = 0, 0
Exemplo n.º 2
0
    def __init__(self, state_m, size, custom_background_color,
                 background_color, hierarchy_level):
        super(StateView, self).__init__(size[0], size[1])
        assert isinstance(state_m, AbstractStateModel)
        # Reapply size, as Gaphas sets default minimum size to 1, which is too large for highly nested states
        self.min_width = self.min_height = 0
        self.width = size[0]
        self.height = size[1]
        self.custom_background_color = custom_background_color
        self.background_color = background_color
        self.background_changed = False

        self._c_min_w = self._constraints[0]
        self._c_min_h = self._constraints[1]

        self.is_root_state_of_library = state_m.state.is_root_state_of_library

        self._state_m = ref(state_m)
        self.hierarchy_level = hierarchy_level

        self._income = None
        self._outcomes = []
        self._inputs = []
        self._outputs = []
        self._scoped_variables = []
        self._scoped_variables_ports = []

        self.keep_rect_constraints = {}
        self.port_constraints = {}

        self._moving = False

        self._view = None

        self.__symbol_size_cache = {}
        self._image_cache = ImageCache()

        self._border_width = Variable(
            min(self.width, self.height) /
            constants.BORDER_WIDTH_STATE_SIZE_FACTOR)
        border_width_constraint = BorderWidthConstraint(
            self._handles[NW].pos, self._handles[SE].pos, self._border_width,
            constants.BORDER_WIDTH_STATE_SIZE_FACTOR)
        self._constraints.append(border_width_constraint)

        # Initialize NameView
        name_meta = state_m.get_meta_data_editor()['name']
        if not contains_geometric_info(name_meta['size']):
            name_width = self.width - 2 * self._border_width
            name_height = self.height * 0.4
            name_meta = state_m.set_meta_data_editor(
                'name.size', (name_width, name_height))['name']
        name_size = name_meta['size']

        self._name_view = NameView(state_m.state.name, name_size)

        if not contains_geometric_info(name_meta['rel_pos']):
            name_meta['rel_pos'] = (self.border_width, self.border_width)
        name_pos = name_meta['rel_pos']
        self.name_view.matrix.translate(*name_pos)
Exemplo n.º 3
0
    def __init__(self, hierarchy_level):
        from rafcon.gui.mygaphas.segment import Segment
        super(PerpLine, self).__init__()
        self._from_handle = self.handles()[0]
        self._to_handle = self.handles()[1]
        self._segment = Segment(self, view=self.canvas)

        self.hierarchy_level = hierarchy_level

        self._from_port = None
        self._from_waypoint = None
        self._from_port_constraint = None
        self._to_port = None
        self._to_waypoint = None
        self._to_port_constraint = None
        self._waypoint_constraints = []

        self._arrow_color = None
        self._line_color = None

        self._parent = None
        self._parent_state_v = None
        self._view = None

        self._label_image_cache = ImageCache()
        self._last_label_size = 0, 0
Exemplo n.º 4
0
    def __init__(self, name, size):
        super(NameView, self).__init__(size[0], size[1])
        # Reapply size, as Gaphas sets default minimum size to 1, which is too large for highly nested states
        self.min_width = self.min_height = 0
        self.width = size[0]
        self.height = size[1]

        self._name = None
        self.name = name

        self.moving = False

        self._view = None

        self._image_cache = ImageCache(multiplicator=1.5)
Exemplo n.º 5
0
 def __init__(self, hierarchy_level):
     super(PerpLine, self).__init__()
     self._from_handle = self.handles()[0]
     self._to_handle = self.handles()[1]
     self.hierarchy_level = hierarchy_level
     self._from_port = None
     self._from_waypoint = None
     self._from_port_constraint = None
     self._to_port = None
     self._to_waypoint = None
     self._to_port_constraint = None
     self._waypoint_constraints = []
     self._arrow_color = None
     self._line_color = None
     self._parent = None
     self._parent_state_v = None
     self._view = None
     self._label_image_cache = ImageCache()
     self._last_label_size = 0, 0
Exemplo n.º 6
0
class NameView(Element):

    def __init__(self, name, size):
        super(NameView, self).__init__(size[0], size[1])
        # Reapply size, as Gaphas sets default minimum size to 1, which is too large for highly nested states
        self.min_width = self.min_height = 0
        self.width = size[0]
        self.height = size[1]

        self._name = None
        self.name = name

        self.moving = False

        self._view = None

        self._image_cache = ImageCache(multiplicator=1.5)

    def remove(self):
        self.canvas.remove(self)

    def update_minimum_size(self):
        min_side_length = min(self.parent.width, self.parent.height) / constants.MAXIMUM_NAME_TO_PARENT_STATE_SIZE_RATIO
        if min_side_length != self.min_width:
            self.min_width = min_side_length
        if min_side_length != self.min_height:
            self.min_height = min_side_length

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        assert isinstance(name, basestring)
        self._name = name

    @property
    def parent(self):
        return self.canvas.get_parent(self)

    @property
    def model(self):
        return self.parent.model

    @property
    def position(self):
        _, _, _, _, x0, y0 = self.matrix
        return x0, y0

    @position.setter
    def position(self, pos):
        self.matrix = Matrix(x0=pos[0], y0=pos[1])

    @property
    def view(self):
        if not self._view:
            self._view = self.canvas.get_first_view()
        return self._view

    @property
    def transparency(self):
        if self.parent.show_content(with_content=True):
            return gui_config.get_config_value('SHOW_CONTENT_LIBRARY_NAME_TRANSPARENCY', 0.5)
        return self.parent.transparency

    def apply_meta_data(self):
        name_meta = self.parent.model.get_meta_data_editor()['name']
        # logger.info("name rel_pos {}".format(name_meta['rel_pos']))
        # logger.info("name size {}".format(name_meta['size']))
        self.position = name_meta['rel_pos']
        # print "name pos from meta", name_meta['rel_pos']
        self.width = name_meta['size'][0]
        self.height = name_meta['size'][1]

    def draw(self, context):
        # Do not draw if
        # * state (or its parent) is currently moved
        # * core element is no longer existing (must have just been removed)
        # * is root state of a library (drawing would hide the LibraryState itself)
        if not self.model.state or self.moving or self.parent.model.state.is_root_state_of_library:
            return

        width = self.width
        height = self.height
        view_width, view_height = self.view.get_matrix_i2v(self).transform_distance(width, height)
        if min(view_width, view_height) < constants.MINIMUM_NAME_SIZE_FOR_DISPLAY and not context.draw_all:
            return
        font_transparency = self.transparency

        c = context.cairo
        parameters = {
            'name': self.name,
            'selected': context.selected,
            'transparency': font_transparency
        }

        upper_left_corner = (0, 0)
        current_zoom = self.view.get_zoom_factor()
        from_cache, image, zoom = self._image_cache.get_cached_image(width, height, current_zoom, parameters)
        # The parameters for drawing haven't changed, thus we can just copy the content from the last rendering result
        if from_cache:
            self._image_cache.copy_image_to_context(c, upper_left_corner)

        # Parameters have changed or nothing in cache => redraw
        else:
            c = self._image_cache.get_context_for_image(current_zoom)

            if context.selected:
                # Draw light background color if selected
                c.rectangle(0, 0, width, height)
                c.set_source_rgba(*gap_draw_helper.get_col_rgba(gui_config.gtk_colors['LABEL'], transparency=.9))
                c.fill_preserve()
                c.set_source_rgba(0, 0, 0, 0)
                c.stroke()

            c.set_antialias(cairo.ANTIALIAS_SUBPIXEL)

            layout = c.create_layout()
            layout.set_wrap(WRAP_WORD)
            layout.set_width(int(round(BASE_WIDTH * SCALE)))
            layout.set_text(self.name)

            def set_font_description(font_size):
                font = FontDescription(font_name + " " + str(font_size))
                layout.set_font_description(font)

            font_name = constants.INTERFACE_FONT

            zoom_scale = BASE_WIDTH / width
            scaled_height = height * zoom_scale
            font_size_parameters = {"text": self.name, "height": scaled_height}
            font_size = self.view.value_cache.get_value("font_size", font_size_parameters)

            if font_size:
                set_font_description(font_size)
            else:
                available_size = (BASE_WIDTH * SCALE, scaled_height * SCALE)
                word_count = len(self.name.split(" "))
                # Set max font size to available height
                max_font_size = scaled_height * 0.9
                # Calculate minimum size that is still to be drawn
                min_name_height = max_font_size / 10.
                # Calculate line height if all words are wrapped
                line_height = max_font_size / word_count
                # Use minimum if previous values and add safety margin
                min_font_size = min(line_height * 0.5, min_name_height)

                # Iteratively calculate font size by always choosing the average of the maximum and minimum size
                working_font_size = None
                current_font_size = (max_font_size + min_font_size) / 2.
                set_font_description(current_font_size)

                while True:
                    logical_extents = layout.get_size()
                    width_factor = logical_extents[0] / available_size[0]
                    height_factor = logical_extents[1] / available_size[1]
                    max_factor = max(width_factor, height_factor)

                    if max_factor > 1:  # font size too large
                        max_font_size = current_font_size
                    elif max_factor > 0.9:  # font size fits!
                        break
                    else:  # font size too small
                        # Nevertheless store the font size in case we do not find anything better
                        if not working_font_size or current_font_size > working_font_size:
                            working_font_size = current_font_size
                        min_font_size = current_font_size
                    if 0.99 < min_font_size / max_font_size < 1.01:  # Stop criterion: changes too small
                        if working_font_size:
                            current_font_size = working_font_size
                            set_font_description(current_font_size)
                        break
                    current_font_size = (max_font_size + min_font_size) / 2.
                    set_font_description(current_font_size)
                self.view.value_cache.store_value("font_size", current_font_size, font_size_parameters)

            c.move_to(*self.handles()[NW].pos)
            c.set_source_rgba(*get_col_rgba(gui_config.gtk_colors['STATE_NAME'], font_transparency))
            c.save()
            # The pango layout has a fixed width and needs to be fitted to the context size
            c.scale(1. / zoom_scale, 1. / zoom_scale)
            c.update_layout(layout)
            c.show_layout(layout)
            c.restore()

            # Copy image surface to current cairo context
            self._image_cache.copy_image_to_context(context.cairo, upper_left_corner, zoom=current_zoom)
Exemplo n.º 7
0
class StateView(Element):
    """ A State has 4 handles (for a start):
     NW +---+ NE
     SW +---+ SE
    """

    _map_handles_port_v = {}

    def __init__(self, state_m, size, hierarchy_level):
        super(StateView, self).__init__(size[0], size[1])
        assert isinstance(state_m, AbstractStateModel)
        # Reapply size, as Gaphas sets default minimum size to 1, which is too large for highly nested states
        self.min_width = self.min_height = 0
        self.width = size[0]
        self.height = size[1]

        self.is_root_state_of_library = state_m.state.is_root_state_of_library

        self._state_m = ref(state_m)
        self.hierarchy_level = hierarchy_level

        self._income = None
        self._outcomes = []
        self._inputs = []
        self._outputs = []
        self._scoped_variables = []
        self._scoped_variables_ports = []

        self.keep_rect_constraints = {}
        self.port_constraints = {}

        self._moving = False

        self._view = None

        self.__symbol_size_cache = {}
        self._image_cache = ImageCache()

        self._border_width = Variable(min(self.width, self.height) / constants.BORDER_WIDTH_STATE_SIZE_FACTOR)
        border_width_constraint = BorderWidthConstraint(self._handles[NW].pos, self._handles[SE].pos,
                                                        self._border_width, constants.BORDER_WIDTH_STATE_SIZE_FACTOR)
        self._constraints.append(border_width_constraint)

        # Initialize NameView
        name_meta = state_m.get_meta_data_editor()['name']
        if not contains_geometric_info(name_meta['size']):
            name_width = self.width * 0.8
            name_height = self.height * 0.4
            name_meta = state_m.set_meta_data_editor('name.size', (name_width, name_height))['name']
        name_size = name_meta['size']

        self._name_view = NameView(state_m.state.name, name_size)

        if not contains_geometric_info(name_meta['rel_pos']):
            name_meta['rel_pos'] = (self.border_width, self.border_width)
        name_pos = name_meta['rel_pos']
        self.name_view.matrix.translate(*name_pos)

    @property
    def selected(self):
        return self in self.view.selected_items

    @property
    def hovered(self):
        return self is self.view.hovered_item

    @property
    def view(self):
        if not self._view:
            self._view = self.canvas.get_first_view()
        return self._view

    def setup_canvas(self):
        self._income = self.add_income()

        canvas = self.canvas
        parent = self.parent

        self.update_minimum_size()

        # Draw NameView beneath all other state elements
        canvas.add(self.name_view, self, index=0)
        self.name_view.update_minimum_size()

        self.add_keep_rect_within_constraint(canvas, self, self._name_view)

        if parent is not None:
            assert isinstance(parent, StateView)
            self.add_keep_rect_within_constraint(canvas, parent, self)

        # Registers local constraints
        super(StateView, self).setup_canvas()

    def update_minimum_size(self):
        if not self.parent:
            self.min_width = 1
            self.min_height = 1
        else:
            min_side_length = min(self.parent.width, self.parent.height) / \
                              constants.MAXIMUM_CHILD_TO_PARENT_STATE_SIZE_RATIO
            if min_side_length != self.min_width:
                self.min_width = min_side_length
            if min_side_length != self.min_height:
                self.min_height = min_side_length

    def update_minimum_size_of_children(self):
        if self.canvas:
            for constraint in self.constraints:
                self.canvas.solver.request_resolve_constraint(constraint)
            if not self.canvas.solver._solving:
                self.canvas.solver.solve()
            for item in self.canvas.get_all_children(self):
                if isinstance(item, (StateView, NameView)):
                    item.update_minimum_size()

    def get_all_ports(self):
        port_list = [self.income]
        port_list += self.outcomes
        port_list += self.inputs
        port_list += self.outputs
        port_list += self.scoped_variables
        return port_list

    def get_logic_ports(self):
        port_list = [self.income]
        port_list += self.outcomes
        return port_list

    def get_data_ports(self):
        port_list = self.inputs
        port_list += self.outputs
        port_list += self.scoped_variables
        return port_list

    def remove(self):
        """Remove recursively all children and then the StateView itself
        """
        self.canvas.get_first_view().unselect_item(self)

        for child in self.canvas.get_children(self)[:]:
            child.remove()

        self.remove_income()
        for outcome_v in self.outcomes[:]:
            self.remove_outcome(outcome_v)
        for input_port_v in self.inputs[:]:
            self.remove_input_port(input_port_v)
        for output_port_v in self.outputs[:]:
            self.remove_output_port(output_port_v)
        for scoped_variable_port_v in self.scoped_variables[:]:
            self.remove_scoped_variable(scoped_variable_port_v)

        self.remove_keep_rect_within_constraint_from_parent()
        for constraint in self._constraints[:]:
            self.canvas.solver.remove_constraint(constraint)
            self._constraints.remove(constraint)
        self.canvas.remove(self)

    @staticmethod
    def add_keep_rect_within_constraint(canvas, parent, child):
        solver = canvas.solver

        child_nw = ItemProjection(child.handles()[NW].pos, child, parent)
        child_se = ItemProjection(child.handles()[SE].pos, child, parent)
        constraint = KeepRectangleWithinConstraint(parent.handles()[NW].pos, parent.handles()[SE].pos,
                                                   child_nw, child_se, child, lambda: parent.border_width)
        solver.add_constraint(constraint)
        parent.keep_rect_constraints[child] = constraint

    def remove_keep_rect_within_constraint_from_parent(self):
        canvas = self.canvas
        solver = canvas.solver

        name_constraint = self.keep_rect_constraints.pop(self.name_view)
        solver.remove_constraint(name_constraint)

        parent_state_v = self.parent
        if parent_state_v is not None and isinstance(parent_state_v, StateView):
            constraint = parent_state_v.keep_rect_constraints.pop(self)
            solver.remove_constraint(constraint)

    def has_selected_child(self):
        for child in self.canvas.get_children(self):
            if isinstance(child, StateView) and child.selected:
                return True
        return False

    @property
    def position(self):
        _, _, _, _, x0, y0 = self.matrix
        return x0, y0

    @position.setter
    def position(self, pos):
        self.matrix = Matrix(x0=pos[0], y0=pos[1])

    @property
    def show_data_port_label(self):
        return global_runtime_config.get_config_value("SHOW_DATA_FLOWS", True)

    @property
    def moving(self):
        return self._moving

    @moving.setter
    def moving(self, moving):
        assert isinstance(moving, bool)
        self._moving = moving
        for child in self.canvas.get_children(self):
            if isinstance(child, (StateView, NameView)):
                child.moving = moving

    @property
    def border_width(self):
        return self._border_width.value

    @property
    def parent(self):
        return self.canvas.get_parent(self)

    @property
    def corner_handles(self):
        return [self.handles()[NW], self.handles()[NE], self.handles()[SW], self.handles()[SE]]

    @property
    def aborted_preempted_handles(self):
        return [self.outcomes[-1].handle, self.outcomes[-2].handle]

    @property
    def model(self):
        return self._state_m()

    @model.setter
    def model(self, state_m):
        if self.model:
            self.canvas.exchange_model(self.model, state_m)
        self._state_m = ref(state_m)

    @property
    def income(self):
        return self._income

    @property
    def outcomes(self):
        return self._outcomes

    @property
    def outputs(self):
        return self._outputs

    @property
    def inputs(self):
        return copy(self._inputs)

    @property
    def scoped_variables(self):
        return self._scoped_variables_ports

    @property
    def name_view(self):
        return self._name_view

    @property
    def transparency(self):
        """Calculates the transparency for the state

        :return: State transparency
        :rtype: float
        """
        # TODO: Implement transparency logic here (e.g. for different viewing modes)
        return 0.

    def child_state_views(self):
        for child in self.canvas.get_children(self):
            if isinstance(child, StateView):
                yield child

    def show_content(self, with_content=False):
        """Checks if the state is a library with the `show_content` flag set

        :param with_content: If this parameter is `True`, the method return only True if the library represents a
          ContainerState
        :return: Whether the content of a library state is shown
        """
        if isinstance(self.model, LibraryStateModel) and self.model.show_content():
            return not with_content or isinstance(self.model.state_copy, ContainerStateModel)
        return False

    @staticmethod
    def get_state_drawing_area(state):
        assert isinstance(state, StateView)
        border_width = state.border_width

        state_nw_pos_x = state.handles()[NW].pos.x + border_width
        state_nw_pos_y = state.handles()[NW].pos.y + border_width
        state_nw_pos = Position((state_nw_pos_x, state_nw_pos_y))
        state_se_pos_x = state.handles()[SE].pos.x - border_width
        state_se_pos_y = state.handles()[SE].pos.y - border_width
        state_se_pos = Position((state_se_pos_x, state_se_pos_y))

        return state_nw_pos, state_se_pos

    def apply_meta_data(self, recursive=False):
        state_meta = self.model.get_meta_data_editor()

        self.position = state_meta['rel_pos']
        self.width = state_meta['size'][0]
        self.height = state_meta['size'][1]
        self.update_minimum_size_of_children()

        def update_port_position(port_v, meta_data):
            if isinstance(meta_data['rel_pos'], tuple) and len(meta_data['rel_pos']) == 2:
                port_v.handle.pos = meta_data['rel_pos']
                self.port_constraints[port_v].update_position(meta_data['rel_pos'])

        if isinstance(state_meta['income']['rel_pos'], tuple) and len(state_meta['income']['rel_pos']) == 2:
            update_port_position(self.income, state_meta['income'])
        for outcome_v in self.outcomes:
            update_port_position(outcome_v, outcome_v.model.get_meta_data_editor())
        for data_port_v in self.inputs + self.outputs:
            update_port_position(data_port_v, data_port_v.model.get_meta_data_editor())

        self.name_view.apply_meta_data()

        if isinstance(self.model, ContainerStateModel):
            for scoped_port_v in self.scoped_variables:
                update_port_position(scoped_port_v, scoped_port_v.model.get_meta_data_editor())
            for transition_m in self.model.transitions:
                transition_v = self.canvas.get_view_for_model(transition_m)
                transition_v.apply_meta_data()

            if recursive:
                for state_v in self.canvas.get_children(self):
                    if isinstance(state_v, StateView):
                        state_v.apply_meta_data(recursive=True)

    def draw(self, context):
        # Do not draw if
        # * state (or its parent) is currently moved
        # * core element is no longer existing (must have just been removed)
        # * is root state of a library (drawing would hide the LibraryState itself)
        if not self.model.state or self.moving and self.parent and self.parent.moving or \
                self.model.state.is_root_state_of_library:
            if not context.draw_all:
                return

        width = self.width
        height = self.height
        border_width = self.border_width
        view_width, view_height = self.view.get_matrix_i2v(self).transform_distance(width, height)
        if min(view_width, view_height) < constants.MINIMUM_STATE_SIZE_FOR_DISPLAY and self.parent and not \
                context.draw_all:
            return

        c = context.cairo
        nw = self._handles[NW].pos
        parameters = {
            'execution_state':  self.model.state.state_execution_status,
            'selected': self.selected,
            'moving': self.moving,
            'border_width': border_width,
            'transparency': self.transparency,
            'draw_all': context.draw_all
        }

        upper_left_corner = (nw.x.value, nw.y.value)
        current_zoom = self.view.get_zoom_factor()
        from_cache, image, zoom = self._image_cache.get_cached_image(width, height, current_zoom, parameters)

        # The parameters for drawing haven't changed, thus we can just copy the content from the last rendering result
        if from_cache:
            self._image_cache.copy_image_to_context(c, upper_left_corner)

        # Parameters have changed or nothing in cache => redraw
        else:
            c = self._image_cache.get_context_for_image(current_zoom)
            multiplicator = self._image_cache.multiplicator
            default_line_width = border_width / constants.BORDER_WIDTH_OUTLINE_WIDTH_FACTOR * multiplicator

            c.rectangle(nw.x, nw.y, width, height)

            state_background_color = gui_config.gtk_colors['STATE_BACKGROUND']
            state_border_color = gui_config.gtk_colors['STATE_BORDER']
            state_border_outline_color = gui_config.gtk_colors['STATE_BORDER_OUTLINE']

            if self.model.state.state_execution_status == StateExecutionStatus.WAIT_FOR_NEXT_STATE:
                state_border_color = gui_config.gtk_colors['STATE_WAITING_BORDER']
                state_border_outline_color = gui_config.gtk_colors['STATE_WAITING_BORDER_OUTLINE']
            elif self.model.state.active:
                state_border_color = gui_config.gtk_colors['STATE_ACTIVE_BORDER']
                state_border_outline_color = gui_config.gtk_colors['STATE_ACTIVE_BORDER_OUTLINE']
            elif self.selected:
                state_border_color = gui_config.gtk_colors['STATE_SELECTED_BORDER']
                state_border_outline_color = gui_config.gtk_colors['STATE_SELECTED_BORDER_OUTLINE']

            c.set_source_rgba(*get_col_rgba(state_border_color, self.transparency))
            c.fill_preserve()
            c.set_source_rgba(*get_col_rgba(state_border_outline_color, self.transparency))
            # The line gets cropped at the context border, therefore the line width must be doubled
            c.set_line_width(default_line_width * 2)
            c.stroke()

            inner_nw, inner_se = self.get_state_drawing_area(self)
            c.rectangle(inner_nw.x, inner_nw.y, inner_se.x - inner_nw.x, inner_se.y - inner_nw.y)
            c.set_source_rgba(*get_col_rgba(state_background_color))
            c.fill_preserve()
            c.set_source_rgba(*get_col_rgba(state_border_outline_color, self.transparency))
            c.set_line_width(default_line_width)
            c.stroke()

            # Copy image surface to current cairo context
            self._image_cache.copy_image_to_context(context.cairo, upper_left_corner, zoom=current_zoom)

        self._income.draw(context, self)

        for outcome_v in self._outcomes:
            highlight = self.model.state.active and outcome_v.model.outcome is self.model.state.final_outcome
            outcome_v.draw(context, self, highlight)

        for input_v in self._inputs:
            input_v.draw(context, self)

        for output_v in self._outputs:
            output_v.draw(context, self)

        for scoped_variable_v in self._scoped_variables_ports:
            scoped_variable_v.draw(context, self)

        if isinstance(self.model, LibraryStateModel) and not self.moving:
            symbol_transparency = 0.9 if self.show_content(with_content=True) else 0.75
            self._draw_symbol(context, constants.SIGN_LIB, gui_config.gtk_colors['STATE_NAME'], symbol_transparency)

        if self.moving:
            self._draw_symbol(context, constants.SIGN_ARROW, gui_config.gtk_colors['STATE_NAME'])

    def _draw_symbol(self, context, symbol, color, transparency=0.):
        c = context.cairo
        width = self.width
        height = self.height

        c.set_antialias(cairo.ANTIALIAS_SUBPIXEL)

        layout = c.create_layout()

        font_name = constants.ICON_FONT

        def set_font_description():
            layout.set_markup('<span font_desc="%s %s">&#x%s;</span>' %
                              (font_name,
                               font_size,
                               symbol))

        if symbol in self.__symbol_size_cache and \
                self.__symbol_size_cache[symbol]['width'] == width and \
                self.__symbol_size_cache[symbol]['height'] == height:
            font_size = self.__symbol_size_cache[symbol]['size']
            set_font_description()

        else:
            font_size = 30
            set_font_description()

            pango_size = (width * SCALE, height * SCALE)
            while layout.get_size()[0] > pango_size[0] or layout.get_size()[1] > pango_size[1]:
                font_size *= 0.9
                set_font_description()

            self.__symbol_size_cache[symbol] = {'width': width, 'height': height, 'size': font_size}

        c.move_to(width / 2. - layout.get_size()[0] / float(SCALE) / 2.,
                  height / 2. - layout.get_size()[1] / float(SCALE) / 2.)

        c.set_source_rgba(*gap_draw_helper.get_col_rgba(color, transparency))
        c.update_layout(layout)
        c.show_layout(layout)

    def get_transitions(self):
        transitions = []
        for child in self.canvas.get_children(self):
            if isinstance(child, TransitionView):
                transitions.append(child)
        return transitions

    def connect_connection_to_port(self, connection_v, port, as_target=True):
        handle = connection_v.to_handle() if as_target else connection_v.from_handle()
        if isinstance(port, IncomeView):
            self.connect_to_income(connection_v, handle)
        elif isinstance(port, OutcomeView):
            self.connect_to_outcome(port.outcome_id, connection_v, handle)
        elif isinstance(port, InputPortView):
            self.connect_to_input_port(port.port_id, connection_v, handle)
        elif isinstance(port, OutputPortView):
            self.connect_to_output_port(port.port_id, connection_v, handle)
        elif isinstance(port, ScopedVariablePortView):
            self.connect_to_scoped_variable_port(port.port_id, connection_v, handle)

    def connect_to_income(self, connection_v, handle):
        self._income.add_connected_handle(handle, connection_v)
        connection_v.set_port_for_handle(self._income, handle)
        self._connect_to_port(self._income.port, connection_v, handle)

    def connect_to_outcome(self, outcome_id, connection_v, handle):
        outcome_v = self.outcome_port(outcome_id)
        outcome_v.add_connected_handle(handle, connection_v)
        connection_v.set_port_for_handle(outcome_v, handle)
        self._connect_to_port(outcome_v.port, connection_v, handle)

    def connect_to_input_port(self, port_id, connection_v, handle):
        port_v = self.input_port(port_id)
        port_v.add_connected_handle(handle, connection_v)
        connection_v.set_port_for_handle(port_v, handle)
        self._connect_to_port(port_v.port, connection_v, handle)

    def connect_to_output_port(self, port_id, connection_v, handle):
        port_v = self.output_port(port_id)
        port_v.add_connected_handle(handle, connection_v)
        connection_v.set_port_for_handle(port_v, handle)
        self._connect_to_port(port_v.port, connection_v, handle)

    def connect_to_scoped_variable_port(self, scoped_variable_id, connection_v, handle):
        port_v = self.scoped_variable(scoped_variable_id)
        port_v.add_connected_handle(handle, connection_v)
        connection_v.set_port_for_handle(port_v, handle)
        self._connect_to_port(port_v.port, connection_v, handle)

    def _connect_to_port(self, port, connection_v, handle):
        c = port.constraint(self.canvas, connection_v, handle, self)
        self.canvas.connect_item(connection_v, handle, self, port, c)

    def income_port(self):
        return self._income

    def outcome_port(self, outcome_id):
        for outcome in self._outcomes:
            if outcome.outcome_id == outcome_id:
                return outcome
        raise AttributeError("Outcome with id '{0}' not found in state".format(outcome_id, self.model.state.name))

    def input_port(self, port_id):
        return self._data_port(self._inputs, port_id)

    def output_port(self, port_id):
        return self._data_port(self._outputs, port_id)

    def scoped_variable(self, scoped_variable_id):
        return self._data_port(self._scoped_variables_ports, scoped_variable_id)

    def get_port_for_handle(self, handle):
        if handle in self._map_handles_port_v:
            return self._map_handles_port_v[handle]
        return None

    def _data_port(self, port_list, port_id):
        for port in port_list:
            if port.port_id == port_id:
                return port
        raise AttributeError("Port with id '{0}' not found in state".format(port_id, self.model.state.name))

    def add_income(self):
        income_v = IncomeView(self)
        self._ports.append(income_v.port)
        self._handles.append(income_v.handle)
        self._map_handles_port_v[income_v.handle] = income_v

        port_meta = self.model.get_meta_data_editor()['income']
        if not contains_geometric_info(port_meta['rel_pos']):
            # print "generate rel_pos"
            # Position income on the top of the left state side
            income_v.side = SnappedSide.LEFT
            pos_x = 0
            pos_y = self._calculate_port_pos_on_line(1, self.height)
            port_meta = self.model.set_meta_data_editor('income.rel_pos', (pos_x, pos_y))['income']
        # print "add income", self.model, self.model.parent, port_meta['rel_pos']
        income_v.handle.pos = port_meta['rel_pos']
        self.add_rect_constraint_for_port(income_v)
        return income_v

    def remove_income(self):
        income_v = self._income
        del self._map_handles_port_v[income_v.handle]
        self._income = None
        self._ports.remove(income_v.port)
        self._handles.remove(income_v.handle)

        if income_v in self.port_constraints:
            self.canvas.solver.remove_constraint(self.port_constraints.pop(income_v))

    def add_outcome(self, outcome_m):
        outcome_v = OutcomeView(outcome_m, self)
        self.canvas.add_port(outcome_v)
        self._outcomes.append(outcome_v)
        self._ports.append(outcome_v.port)
        self._handles.append(outcome_v.handle)
        self._map_handles_port_v[outcome_v.handle] = outcome_v

        port_meta = outcome_m.get_meta_data_editor()
        if not contains_geometric_info(port_meta['rel_pos']):
            # print "generate rel_pos"
            if outcome_m.outcome.outcome_id < 0:
                # Position aborted/preempted in upper right corner
                outcome_v.side = SnappedSide.TOP
                pos_x = self.width - self._calculate_port_pos_on_line(abs(outcome_m.outcome.outcome_id), self.width)
                pos_y = 0
            else:
                # Distribute outcomes on the right side of the state, starting from top
                outcome_v.side = SnappedSide.RIGHT
                pos_x = self.width

                number_of_outcome = [o.model for o in self.outcomes if o.model.outcome.outcome_id >= 0].index(outcome_m) + 1
                pos_y = self._calculate_port_pos_on_line(number_of_outcome, self.height)
            port_meta = outcome_m.set_meta_data_editor('rel_pos', (pos_x, pos_y))
        # print "add outcome", self.model, self.model.parent, port_meta['rel_pos']
        outcome_v.handle.pos = port_meta['rel_pos']
        self.add_rect_constraint_for_port(outcome_v)

    def remove_outcome(self, outcome_v):
        del self._map_handles_port_v[outcome_v.handle]
        self._outcomes.remove(outcome_v)
        self._ports.remove(outcome_v.port)
        self._handles.remove(outcome_v.handle)

        self.canvas.remove_port(outcome_v)
        if outcome_v in self.port_constraints:
            self.canvas.solver.remove_constraint(self.port_constraints.pop(outcome_v))

    def add_input_port(self, port_m):
        input_port_v = InputPortView(self, port_m)
        self.canvas.add_port(input_port_v)
        self._inputs.append(input_port_v)
        self._ports.append(input_port_v.port)
        self._handles.append(input_port_v.handle)
        self._map_handles_port_v[input_port_v.handle] = input_port_v

        port_meta = port_m.get_meta_data_editor()
        if not contains_geometric_info(port_meta['rel_pos']):
            # print "generate rel_pos"
            # Distribute input ports on the left side of the state, starting from bottom
            input_port_v.side = SnappedSide.LEFT
            number_of_input = self.model.input_data_ports.index(port_m) + 1
            pos_x = 0
            pos_y = self.height - self._calculate_port_pos_on_line(number_of_input, self.height)
            port_meta = port_m.set_meta_data_editor('rel_pos', (pos_x, pos_y))
        # print "add input_port", self.model, self.model.parent, port_meta['rel_pos']
        input_port_v.handle.pos = port_meta['rel_pos']
        self.add_rect_constraint_for_port(input_port_v)

    def remove_input_port(self, input_port_v):
        del self._map_handles_port_v[input_port_v.handle]
        self._inputs.remove(input_port_v)
        self._ports.remove(input_port_v.port)
        self._handles.remove(input_port_v.handle)

        self.canvas.remove_port(input_port_v)
        if input_port_v in self.port_constraints:
            self.canvas.solver.remove_constraint(self.port_constraints.pop(input_port_v))

    def add_output_port(self, port_m):
        output_port_v = OutputPortView(self, port_m)
        self.canvas.add_port(output_port_v)
        self._outputs.append(output_port_v)
        self._ports.append(output_port_v.port)
        self._handles.append(output_port_v.handle)
        self._map_handles_port_v[output_port_v.handle] = output_port_v

        port_meta = port_m.get_meta_data_editor()
        if not contains_geometric_info(port_meta['rel_pos']):
            # Distribute output ports on the right side of the state, starting from bottom
            # print "generate rel_pos"
            output_port_v.side = SnappedSide.RIGHT
            number_of_output = self.model.output_data_ports.index(port_m) + 1
            pos_x = self.width
            pos_y = self.height - self._calculate_port_pos_on_line(number_of_output, self.height)
            port_meta = port_m.set_meta_data_editor('rel_pos', (pos_x, pos_y))
        # print "add output_port", self.model, self.model.parent, port_meta['rel_pos']
        output_port_v.handle.pos = port_meta['rel_pos']
        self.add_rect_constraint_for_port(output_port_v)

    def remove_output_port(self, output_port_v):
        del self._map_handles_port_v[output_port_v.handle]
        self._outputs.remove(output_port_v)
        self._ports.remove(output_port_v.port)
        self._handles.remove(output_port_v.handle)

        self.canvas.remove_port(output_port_v)
        if output_port_v in self.port_constraints:
            self.canvas.solver.remove_constraint(self.port_constraints.pop(output_port_v))

    def add_scoped_variable(self, scoped_variable_m):
        scoped_variable_port_v = ScopedVariablePortView(self, scoped_variable_m)
        self.canvas.add_port(scoped_variable_port_v)
        self._scoped_variables_ports.append(scoped_variable_port_v)
        self._ports.append(scoped_variable_port_v.port)
        self._handles.append(scoped_variable_port_v.handle)
        self._map_handles_port_v[scoped_variable_port_v.handle] = scoped_variable_port_v

        scoped_variable_port_v.handle.pos = self.width * (0.1 * len(self._scoped_variables_ports)), 0

        port_meta = scoped_variable_m.get_meta_data_editor()
        if not contains_geometric_info(port_meta['rel_pos']):
            # Distribute scoped variables on the top side of the state, starting from left
            # print "generate rel_pos"
            scoped_variable_port_v.side = SnappedSide.BOTTOM

            number_of_scoped_var = self.model.scoped_variables.index(scoped_variable_m) + 1
            pos_x = self._calculate_port_pos_on_line(number_of_scoped_var, self.width,
                                                     port_width=self.border_width * 4)
            pos_y = self.height
            port_meta = scoped_variable_m.set_meta_data_editor('rel_pos', (pos_x, pos_y))
        # print "add scoped_variable", self.model, self.model.parent, port_meta['rel_pos']
        scoped_variable_port_v.handle.pos = port_meta['rel_pos']

        self.add_rect_constraint_for_port(scoped_variable_port_v)

    def remove_scoped_variable(self, scoped_variable_port_v):
        del self._map_handles_port_v[scoped_variable_port_v.handle]
        self._scoped_variables_ports.remove(scoped_variable_port_v)
        self._ports.remove(scoped_variable_port_v.port)
        self._handles.remove(scoped_variable_port_v.handle)

        self.canvas.remove_port(scoped_variable_port_v)
        if scoped_variable_port_v in self.port_constraints:
            self.canvas.solver.remove_constraint(self.port_constraints.pop(scoped_variable_port_v))

    def add_rect_constraint_for_port(self, port):
        constraint = PortRectConstraint((self.handles()[NW].pos, self.handles()[SE].pos), port.pos, port)
        solver = self.canvas.solver
        solver.add_constraint(constraint)
        self.port_constraints[port] = constraint

    def _calculate_port_pos_on_line(self, port_num, side_length, port_width=None):
        """Calculate the position of a port on a line

        The position depends on the number of element. Elements are equally spaced. If the end of the line is
        reached, ports are stacked.
        :param int port_num: The number of the port of that type
        :param float side_length: The length of the side the element is placed on
        :param float port_width: The width of one port
        :return: The position on the line for the given port
        :rtype: float
        """
        if port_width is None:
            port_width = 2 * self.border_width
        border_size = self.border_width
        pos = 0.5 * border_size + port_num * port_width
        outermost_pos = max(side_length / 2., side_length - 0.5 * border_size - port_width)
        pos = min(pos, outermost_pos)
        return pos

    def resize_all_children(self, old_size, paste=False):
        def calc_new_rel_pos(old_rel_pos, old_parent_size, new_parent_size):
            new_rel_pos_x = old_rel_pos[0] * new_parent_size[0] / old_parent_size[0]
            new_rel_pos_y = old_rel_pos[1] * new_parent_size[1] / old_parent_size[1]
            return new_rel_pos_x, new_rel_pos_y

        def set_item_properties(item, size, rel_pos):
            prefix = 'name.' if isinstance(item, NameView) else ''
            item_m = item.model if isinstance(item, StateView) else item.parent.model
            item.width = size[0]
            item.height = size[1]
            item_m.set_meta_data_editor(prefix + 'size', size)
            if item is not self:
                item.position = rel_pos
                item_m.set_meta_data_editor(prefix + 'rel_pos', rel_pos)
            if isinstance(item, StateView):
                item.update_minimum_size_of_children()

        def resize_state_v(state_v, old_state_size, new_state_size, use_meta_data):
            width_factor = float(new_state_size[0]) / old_state_size[0]
            height_factor = float(new_state_size[1]) / old_state_size[1]

            # Set new state view properties
            old_state_rel_pos = state_v.position
            new_state_rel_pos = calc_new_rel_pos(old_state_rel_pos, old_state_size, new_state_size)
            set_item_properties(state_v, new_state_size, new_state_rel_pos)

            # Set new name view properties
            name_v = state_v.name_view
            if use_meta_data:
                old_name_size = state_v.model.get_meta_data_editor()['name']['size']
            else:
                old_name_size = (name_v.width, name_v.height)
            new_name_size = (old_name_size[0] * width_factor, old_name_size[1] * height_factor)
            old_name_rel_pos = state_v.model.get_meta_data_editor()['name']['rel_pos']
            new_name_rel_pos = calc_new_rel_pos(old_name_rel_pos, old_state_size, new_state_size)
            set_item_properties(name_v, new_name_size, new_name_rel_pos)

            def resize_child_state_v(child_state_v):
                if use_meta_data:
                    old_child_size = child_state_v.model.get_meta_data_editor()['size']
                else:
                    old_child_size = (child_state_v.width, child_state_v.height)

                new_child_size = (old_child_size[0] * width_factor, old_child_size[1] * height_factor)
                resize_state_v(child_state_v, old_child_size, new_child_size, use_meta_data)

            # Set new port view properties
            for port_v in state_v.get_all_ports():
                new_port_rel_pos = calc_new_rel_pos(port_v.handle.pos, old_state_size, new_state_size)
                port_v.handle.pos = new_port_rel_pos

            if isinstance(state_v.model, ContainerStateModel):
                for transition_v in state_v.get_transitions():
                    for waypoint in transition_v.waypoints:
                        old_rel_pos = self.canvas.get_matrix_i2i(transition_v, transition_v.parent).transform_point(
                            *waypoint.pos)
                        new_rel_pos = calc_new_rel_pos(old_rel_pos, old_state_size, new_state_size)
                        waypoint.pos = self.canvas.get_matrix_i2i(transition_v.parent, transition_v).transform_point(
                            *new_rel_pos)

                for child_state_v in state_v.child_state_views():
                    resize_child_state_v(child_state_v)
            elif state_v.show_content():
                state_copy_v = self.canvas.get_view_for_model(state_v.model.state_copy)
                resize_child_state_v(state_copy_v)

        new_size = (self.width, self.height)
        resize_state_v(self, old_size, new_size, paste)
Exemplo n.º 8
0
class PortView(object):
    def __init__(self,
                 in_port,
                 name=None,
                 parent=None,
                 side=SnappedSide.RIGHT):
        self.handle = Handle(connectable=True)
        self.port = RectanglePointPort(self.handle.pos, self)
        self._is_in_port = in_port
        self._side = None
        self.direction = None
        self.side = side
        self._parent = ref(parent)
        self._view = None

        self.text_color = gui_config.gtk_colors['LABEL']
        self.fill_color = gui_config.gtk_colors['LABEL']

        self._incoming_handles = []
        self._outgoing_handles = []
        self._connected_connections = []
        self._tmp_incoming_connected = False
        self._tmp_outgoing_connected = False

        self._name = name

        self.label_print_inside = True

        self._port_image_cache = ImageCache()
        self._label_image_cache = ImageCache()
        self._last_label_size = self.port_side_size, self.port_side_size
        self._last_label_relative_pos = 0, 0

    def __getattr__(self, name):
        """Return parental attributes for unknown attributes

        The PortView class is now Gaphas item, however it is often treated like that. Therefore, several expected
        attributes are missing. In these cases, the corresponding attribute of the parental StateView is returned.

        :param str name: Name of teh requested attribute
        :return: Parental value of the attribute
        """
        try:
            return getattr(self.parent, name)
        except Exception:
            # This workarounds are needed because parent is a weak reference and if the the state is already destroy
            # the name of its parent can not be accessed even if the view of the port still exists
            # TODO D-check if ports can be destroyed proper before the state view is collected by the garbage collector
            if name == "name":
                return self._name
            raise

    def handles(self):
        return [self.handle]

    @property
    def side(self):
        return self._side

    @side.setter
    @observed
    def side(self, side):
        self._side = side
        self.direction = None
        if self.side is SnappedSide.LEFT:
            self.direction = Direction.RIGHT if self._is_in_port else Direction.LEFT
        elif self.side is SnappedSide.TOP:
            self.direction = Direction.DOWN if self._is_in_port else Direction.UP
        elif self.side is SnappedSide.RIGHT:
            self.direction = Direction.LEFT if self._is_in_port else Direction.RIGHT
        elif self.side is SnappedSide.BOTTOM:
            self.direction = Direction.UP if self._is_in_port else Direction.DOWN

    @property
    def port_side_size(self):
        parent = self.parent
        if not parent:
            logger.warning("PortView without parent: {}".format(self))
            return 1
        return parent.border_width

    @property
    def name(self):
        return self._name

    @property
    def parent(self):
        return self._parent()

    @property
    def pos(self):
        return self.handle.pos

    @property
    def handle_pos(self):
        return self.handle.pos

    @property
    def port_pos(self):
        return self.port.point

    @property
    def port_size(self):
        return self.port_side_size / 1.5, self.port_side_size

    @property
    def view(self):
        if not self._view:
            self._view = self.parent.canvas.get_first_view()
        return self._view

    def has_outgoing_connection(self):
        return len(self._outgoing_handles) > 0

    def has_incoming_connection(self):
        return len(self._incoming_handles) > 0

    def add_connected_handle(self, handle, connection_view, moving=False):
        from rafcon.gui.mygaphas.items.connection import ConnectionView
        assert isinstance(handle, Handle)
        assert isinstance(connection_view, ConnectionView)
        if not moving and handle is connection_view.from_handle(
        ) and handle not in self._outgoing_handles:
            self._outgoing_handles.append(handle)
            self._add_connection(connection_view)
        elif not moving and handle is connection_view.to_handle(
        ) and handle not in self._incoming_handles:
            self._incoming_handles.append(handle)
            self._add_connection(connection_view)

    def has_label(self):
        return False

    def is_selected(self):
        return self in self.parent.canvas.get_first_view().selected_items

    def _add_connection(self, connection_view):
        if connection_view not in self.connected_connections:
            self._connected_connections.append(ref(connection_view))

    def remove_connected_handle(self, handle):
        assert isinstance(handle, Handle)
        if handle in self._incoming_handles:
            self._incoming_handles.remove(handle)
            for conn in self.connected_connections:
                if conn.to_handle() is handle:
                    self._connected_connections.remove(ref(conn))
        elif handle in self._outgoing_handles:
            self._outgoing_handles.remove(handle)
            for conn in self.connected_connections:
                if conn.from_handle() is handle:
                    self._connected_connections.remove(ref(conn))

    def tmp_connect(self, handle, connection_view):
        if handle is connection_view.from_handle():
            self._tmp_outgoing_connected = True
        elif handle is connection_view.to_handle():
            self._tmp_incoming_connected = True

    def tmp_disconnect(self):
        self._tmp_incoming_connected = False
        self._tmp_outgoing_connected = False

    @property
    def connected(self):
        return self.connected_incoming or self.connected_outgoing

    @property
    def connected_outgoing(self):
        if len(self._outgoing_handles) == 0:
            return self._tmp_outgoing_connected
        return True

    @property
    def connected_incoming(self):
        if len(self._incoming_handles) == 0:
            return self._tmp_incoming_connected
        return True

    @property
    def connected_connections(self):
        return [connection() for connection in self._connected_connections]

    def get_port_area(self, view):
        """Calculates the drawing area affected by the (hovered) port
        """
        state_v = self.parent
        center = self.handle.pos
        margin = self.port_side_size / 4.
        if self.side in [SnappedSide.LEFT, SnappedSide.RIGHT]:
            height, width = self.port_size
        else:
            width, height = self.port_size
        upper_left = center[0] - width / 2 - margin, center[
            1] - height / 2 - margin
        lower_right = center[0] + width / 2 + margin, center[
            1] + height / 2 + margin
        port_upper_left = view.get_matrix_i2v(state_v).transform_point(
            *upper_left)
        port_lower_right = view.get_matrix_i2v(state_v).transform_point(
            *lower_right)
        size = port_lower_right[0] - port_upper_left[0], port_lower_right[
            1] - port_upper_left[1]
        return port_upper_left[0], port_upper_left[1], size[0], size[1]

    def draw(self, context, state):
        raise NotImplementedError

    def draw_port(self, context, fill_color, transparency, value=None):
        c = context.cairo
        view = self.parent.canvas.get_first_view()
        side_length = self.port_side_size
        position = self.pos

        # Do not draw ports below a certain threshold size
        matrix_i2v = view.get_matrix_i2v(self.parent)
        view_length, _ = matrix_i2v.transform_distance(side_length, 0)
        if view_length < constants.MINIMUM_PORT_SIZE_FOR_DISPLAY and not context.draw_all:
            return
        # Do not draw port outside of the view
        center = (position.x.value, position.y.value)
        view_center = matrix_i2v.transform_point(*center)
        if view_center[0] + view_length / 2. < 0 or \
                view_center[0] - view_length / 2. > view.get_allocation().width or \
                view_center[1] + view_length / 2. < 0 or \
                view_center[1] - view_length / 2. > view.get_allocation().height:
            if not context.draw_all:
                return

        parent_state_m = self.parent.model
        is_library_state_with_content_shown = self.parent.show_content()

        parameters = {
            'selected':
            self.is_selected(),
            'direction':
            self.direction,
            'side_length':
            side_length,
            'fill_color':
            fill_color,
            'transparency':
            transparency,
            'incoming':
            self.connected_incoming,
            'outgoing':
            self.connected_outgoing,
            'is_library_state_with_content_shown':
            is_library_state_with_content_shown
        }

        upper_left_corner = (position.x.value - side_length / 2.,
                             position.y.value - side_length / 2.)

        current_zoom = view.get_zoom_factor()
        from_cache, image, zoom = self._port_image_cache.get_cached_image(
            side_length, side_length, current_zoom, parameters)

        # The parameters for drawing haven't changed, thus we can just copy the content from the last rendering result
        if from_cache:
            # print("from cache")
            self._port_image_cache.copy_image_to_context(c, upper_left_corner)

        # Parameters have changed or nothing in cache => redraw
        else:
            # print("draw")
            c = self._port_image_cache.get_context_for_image(current_zoom)

            c.move_to(0, 0)

            if isinstance(parent_state_m, ContainerStateModel
                          ) or is_library_state_with_content_shown:
                self._draw_container_state_port(c, self.direction, fill_color,
                                                transparency)
            else:
                self._draw_simple_state_port(c, self.direction, fill_color,
                                             transparency)

            # Copy image surface to current cairo context
            self._port_image_cache.copy_image_to_context(context.cairo,
                                                         upper_left_corner,
                                                         zoom=current_zoom)

        if self.name and self.has_label():
            self.draw_name(context, transparency, value)

        if self.is_selected(
        ) or self.handle is view.hovered_handle or context.draw_all:
            context.cairo.move_to(*self.pos)
            self._draw_hover_effect(context.cairo, self.direction, fill_color,
                                    transparency)

    def draw_name(self, context, transparency, value):
        c = context.cairo
        port_height = self.port_size[1]
        label_position = self.side if not self.label_print_inside else self.side.opposite(
        )
        position = self.pos

        show_additional_value = False
        if global_runtime_config.get_config_value(
                "SHOW_DATA_FLOW_VALUE_LABELS", True) and value is not None:
            show_additional_value = True

        parameters = {
            'name': self.name,
            'port_height': port_height,
            'side': label_position,
            'transparency': transparency,
            'show_additional_value': show_additional_value
        }

        # add value to parameters only when value is shown on label
        if show_additional_value:
            parameters['value'] = value

        upper_left_corner = (position[0] + self._last_label_relative_pos[0],
                             position[1] + self._last_label_relative_pos[1])
        current_zoom = self.parent.canvas.get_first_view().get_zoom_factor()
        from_cache, image, zoom = self._label_image_cache.get_cached_image(
            self._last_label_size[0], self._last_label_size[1], current_zoom,
            parameters)
        # The parameters for drawing haven't changed, thus we can just copy the content from the last rendering result
        if from_cache and not context.draw_all:
            # print("draw port name from cache")
            self._label_image_cache.copy_image_to_context(c, upper_left_corner)

        # Parameters have changed or nothing in cache => redraw
        else:
            # print("draw port name")

            # First we have to do a "dry run", in order to determine the size of the new label
            c.move_to(position.x.value, position.y.value)
            extents = gap_draw_helper.draw_port_label(
                c,
                self,
                transparency,
                False,
                label_position,
                show_additional_value,
                value,
                only_extent_calculations=True)
            from rafcon.gui.mygaphas.utils.gap_helper import extend_extents
            extents = extend_extents(extents, factor=1.1)
            label_pos = extents[0], extents[1]
            relative_pos = label_pos[0] - position[0], label_pos[1] - position[
                1]
            label_size = extents[2] - extents[0], extents[3] - extents[1]

            # print(label_size[0], self.name, self.parent.model.state.name)
            # if label_size[0] < constants.MINIMUM_PORT_NAME_SIZE_FOR_DISPLAY and self.parent:
            #     return
            self._last_label_relative_pos = relative_pos
            self._last_label_size = label_size

            # The size information is used to update the caching parameters and retrieve an image with the correct size
            self._label_image_cache.get_cached_image(label_size[0],
                                                     label_size[1],
                                                     current_zoom,
                                                     parameters,
                                                     clear=True)
            c = self._label_image_cache.get_context_for_image(current_zoom)
            c.move_to(-relative_pos[0], -relative_pos[1])

            gap_draw_helper.draw_port_label(c, self, transparency, False,
                                            label_position,
                                            show_additional_value, value)

            # Copy image surface to current cairo context
            upper_left_corner = (position[0] + relative_pos[0],
                                 position[1] + relative_pos[1])
            self._label_image_cache.copy_image_to_context(context.cairo,
                                                          upper_left_corner,
                                                          zoom=current_zoom)

            # draw_all means, the bounding box of the state is calculated
            # As we are using drawing operation, not supported by Gaphas, we manually need to update the bounding box
            if context.draw_all:
                from gaphas.geometry import Rectangle
                view = self.parent.canvas.get_first_view()
                abs_pos = view.get_matrix_i2v(
                    self.parent).transform_point(*label_pos)
                abs_pos1 = view.get_matrix_i2v(self.parent).transform_point(
                    extents[2], extents[3])
                bounds = Rectangle(abs_pos[0],
                                   abs_pos[1],
                                   x1=abs_pos1[0],
                                   y1=abs_pos1[1])
                context.cairo._update_bounds(bounds)

    def _draw_simple_state_port(self, context, direction, color, transparency):
        """Draw the port of a simple state (ExecutionState, LibraryState)

        Connector for execution states can only be connected to the outside. Thus the connector fills the whole
        border of the state.

        :param context: Cairo context
        :param direction: The direction the port is pointing to
        :param color: Desired color of the port
        :param transparency: The level of transparency
        """
        c = context

        width, height = self.port_size
        c.set_line_width(self.port_side_size * 0.03 *
                         self._port_image_cache.multiplicator)

        # Save/restore context, as we move and rotate the connector to the desired pose
        c.save()
        c.rel_move_to(self.port_side_size / 2., self.port_side_size / 2.)
        PortView._rotate_context(c, direction)
        PortView._draw_single_connector(c, width, height)
        c.restore()

        # Colorize the generated connector path
        if self.connected:
            c.set_source_rgba(
                *gap_draw_helper.get_col_rgba(color, transparency))
        else:
            c.set_source_rgb(
                *gui_config.gtk_colors['PORT_UNCONNECTED'].to_floats())
        c.fill_preserve()
        c.set_source_rgba(*gap_draw_helper.get_col_rgba(color, transparency))
        c.stroke()

    def _draw_container_state_port(self, context, direction, color,
                                   transparency):
        """Draw the port of a container state

        Connector for container states are split in an inner connector and an outer connector.

        :param context: Cairo context
        :param direction: The direction the port is pointing to
        :param color: Desired color of the port
        :param float transparency: The level of transparency
        """
        c = context

        width, height = self.port_size
        c.set_line_width(self.port_side_size /
                         constants.BORDER_WIDTH_OUTLINE_WIDTH_FACTOR *
                         self._port_image_cache.multiplicator)

        # Save/restore context, as we move and rotate the connector to the desired pose
        cur_point = c.get_current_point()
        c.save()
        c.rel_move_to(self.port_side_size / 2., self.port_side_size / 2.)
        PortView._rotate_context(c, direction)
        PortView._draw_inner_connector(c, width, height)
        c.restore()

        if self.connected_incoming:
            c.set_source_rgba(
                *gap_draw_helper.get_col_rgba(color, transparency))
        else:
            c.set_source_rgb(
                *gui_config.gtk_colors['PORT_UNCONNECTED'].to_floats())
        c.fill_preserve()
        c.set_source_rgba(*gap_draw_helper.get_col_rgba(color, transparency))
        c.stroke()

        c.move_to(*cur_point)
        c.save()
        c.rel_move_to(self.port_side_size / 2., self.port_side_size / 2.)
        PortView._rotate_context(c, direction)
        PortView._draw_outer_connector(c, width, height)
        c.restore()

        if self.connected_outgoing:
            c.set_source_rgba(
                *gap_draw_helper.get_col_rgba(color, transparency))
        else:
            c.set_source_rgb(
                *gui_config.gtk_colors['PORT_UNCONNECTED'].to_floats())
        c.fill_preserve()
        c.set_source_rgba(*gap_draw_helper.get_col_rgba(color, transparency))
        c.stroke()

    def _draw_hover_effect(self, context, direction, color, transparency):
        c = context

        width, height = self.port_size
        c.set_line_width(self.port_side_size * 0.03 *
                         self._port_image_cache.multiplicator)
        margin = self.port_side_size / 4.

        # Save/restore context, as we move and rotate the connector to the desired pose
        c.save()
        PortView._rotate_context(c, direction)
        PortView._draw_rectangle(c, width + margin, height + margin)
        c.restore()

        c.set_source_rgba(*gap_draw_helper.get_col_rgba(color, transparency))
        c.stroke()

    @staticmethod
    def _draw_single_connector(context, width, height):
        """Draw the connector for execution states

        Connector for execution states can only be connected to the outside. Thus the connector fills the whole
        border of the state.

        :param context: Cairo context
        :param float port_size: The side length of the port
        """
        c = context
        # Current pos is center
        # Arrow is drawn upright

        arrow_height = height / 2.0

        # First move to bottom left corner
        c.rel_move_to(-width / 2., height / 2.)
        # Draw line to bottom right corner
        c.rel_line_to(width, 0)
        # Draw line to upper right corner
        c.rel_line_to(0, -(height - arrow_height))
        # Draw line to center top (arrow)
        c.rel_line_to(-width / 2., -arrow_height)
        # Draw line to upper left corner
        c.rel_line_to(-width / 2., arrow_height)
        # Draw line back to the origin (lower left corner)
        c.close_path()

    @staticmethod
    def _draw_inner_connector(context, width, height):
        """Draw the connector for container states

        Connector for container states can be connected from the inside and the outside. Thus the connector is split
        in two parts: A rectangle on the inside and an arrow on the outside. This methods draws the inner rectangle.

        :param context: Cairo context
        :param float port_size: The side length of the port
        """
        c = context
        # Current pos is center
        # Arrow is drawn upright

        gap = height / 6.
        connector_height = (height - gap) / 2.

        # First move to bottom left corner
        c.rel_move_to(-width / 2., height / 2.)

        # Draw inner connector (rectangle)
        c.rel_line_to(width, 0)
        c.rel_line_to(0, -connector_height)
        c.rel_line_to(-width, 0)
        c.close_path()

    @staticmethod
    def _draw_outer_connector(context, width, height):
        """Draw the outer connector for container states

        Connector for container states can be connected from the inside and the outside. Thus the connector is split
        in two parts: A rectangle on the inside and an arrow on the outside. This method draws the outer arrow.

        :param context: Cairo context
        :param float port_size: The side length of the port
        """
        c = context
        # Current pos is center
        # Arrow is drawn upright

        arrow_height = height / 2.5
        gap = height / 6.
        connector_height = (height - gap) / 2.

        # Move to bottom left corner of outer connector
        c.rel_move_to(-width / 2., -gap / 2.)

        # Draw line to bottom right corner
        c.rel_line_to(width, 0)
        # Draw line to upper right corner
        c.rel_line_to(0, -(connector_height - arrow_height))
        # Draw line to center top (arrow)
        c.rel_line_to(-width / 2., -arrow_height)
        # Draw line to upper left corner
        c.rel_line_to(-width / 2., arrow_height)
        # Draw line back to the origin (lower left corner)
        c.close_path()

    @staticmethod
    def _draw_rectangle(context, width, height):
        """Draw a rectangle

        Assertion: The current point is the center point of the rectangle

        :param context: Cairo context
        :param width: Width of the rectangle
        :param height: Height of the rectangle
        """
        c = context
        # First move to upper left corner
        c.rel_move_to(-width / 2., -height / 2.)
        # Draw closed rectangle
        c.rel_line_to(width, 0)
        c.rel_line_to(0, height)
        c.rel_line_to(-width, 0)
        c.close_path()

    @staticmethod
    def _rotate_context(context, direction):
        """Moves the current position to 'position' and rotates the context according to 'direction'

        :param context: Cairo context
        :param direction: Direction enum
        """
        if direction is Direction.UP:
            pass
        elif direction is Direction.RIGHT:
            context.rotate(deg2rad(90))
        elif direction is Direction.DOWN:
            context.rotate(deg2rad(180))
        elif direction is Direction.LEFT:
            context.rotate(deg2rad(-90))
Exemplo n.º 9
0
class PerpLine(Line):
    def __init__(self, hierarchy_level):
        from rafcon.gui.mygaphas.segment import Segment
        super(PerpLine, self).__init__()
        self._from_handle = self.handles()[0]
        self._to_handle = self.handles()[1]
        self._segment = Segment(self, view=self.canvas)

        self.hierarchy_level = hierarchy_level

        self._from_port = None
        self._from_waypoint = None
        self._from_port_constraint = None
        self._to_port = None
        self._to_waypoint = None
        self._to_port_constraint = None
        self._waypoint_constraints = []

        self._arrow_color = None
        self._line_color = None

        self._parent = None
        self._parent_state_v = None
        self._view = None

        self._label_image_cache = ImageCache()
        self._last_label_size = 0, 0

    @property
    def name(self):
        if self.from_port:
            return self.from_port.name
        return None

    @property
    def waypoints(self):
        waypoints = []
        for handle in self.handles():
            if handle not in self.end_handles(include_waypoints=True):
                waypoints.append(handle)
        return waypoints

    @property
    def parent(self):
        return self.canvas.get_parent(self)

    @property
    def from_port(self):
        return self._from_port

    @property
    def to_port(self):
        return self._to_port

    @from_port.setter
    def from_port(self, port):
        assert isinstance(port, PortView)
        self._from_port = port
        if not self._from_waypoint:
            self._from_waypoint = self.add_perp_waypoint()
            self._from_port_constraint = KeepPortDistanceConstraint(self.from_handle().pos, self._from_waypoint.pos,
                                                                    port, lambda: self._head_length(self.from_port) +
                                                                                  self._head_offset(self.from_port),
                                                                    self.is_out_port(port))
            self.canvas.solver.add_constraint(self._from_port_constraint)

    @to_port.setter
    def to_port(self, port):
        assert isinstance(port, PortView)
        self._to_port = port
        if not self._to_waypoint:
            self._to_waypoint = self.add_perp_waypoint(begin=False)
            self._to_port_constraint = KeepPortDistanceConstraint(self.to_handle().pos, self._to_waypoint.pos, port,
                                                                  lambda: self._head_length(self.to_port) +
                                                                          self._head_offset(self.to_port),
                                                                  self.is_in_port(port))
            self.canvas.solver.add_constraint(self._to_port_constraint)

    def remove(self):
        self.reset_from_port()
        self.reset_to_port()
        self.remove_all_waypoints()
        self.canvas.remove(self)

    @property
    def view(self):
        if not self._view:
            self._view = self.canvas.get_first_view()
        return self._view

    def end_handles(self, include_waypoints=False):
        end_handles = [self.from_handle(), self.to_handle()]
        if include_waypoints:
            if self._from_waypoint:
                end_handles.insert(1, self._from_waypoint)
            if self._to_waypoint:
                end_handles.insert(-1, self._to_waypoint)
        return end_handles

    def reset_from_port(self):
        if self._from_port:
            self._from_port = None
            self.canvas.solver.remove_constraint(self._from_port_constraint)
            self._from_port_constraint = None
            self._handles.remove(self._from_waypoint)
            self._from_waypoint = None

    def reset_to_port(self):
        if self._to_port:
            self._to_port = None
            self.canvas.solver.remove_constraint(self._to_port_constraint)
            self._to_port_constraint = None
            self._handles.remove(self._to_waypoint)
            self._to_waypoint = None

    def from_handle(self):
        return self._from_handle

    def to_handle(self):
        return self._to_handle

    def get_parent_state_v(self):
        if not self._parent_state_v:
            if not self.from_port:
                return None
            if isinstance(self.from_port, (IncomeView, InputPortView, ScopedVariablePortView)):
                self._parent_state_v = self.from_port.parent
            else:
                self._parent_state_v = self.from_port.parent.parent
        return self._parent_state_v

    def draw_head(self, context, port):
        offset = self._head_offset(port)
        length = self._head_length(port)
        cr = context.cairo
        cr.move_to(offset, 0)
        cr.line_to(offset + length, 0)
        cr.set_source_rgba(*self._arrow_color)
        cr.set_line_width(self._calc_line_width(port))
        cr.set_line_cap(LINE_CAP_BUTT)
        cr.stroke()

    def draw_tail(self, context, port):
        offset = self._head_offset(port)
        length = self._head_length(port)
        cr = context.cairo
        cr.move_to(offset, 0)
        cr.line_to(offset + length, 0)
        cr.set_source_rgba(*self._arrow_color)
        cr.set_line_width(self._calc_line_width(port))
        cr.set_line_cap(LINE_CAP_BUTT)
        cr.stroke()

    def draw(self, context):
        if self.parent and self.parent.moving:
            return

        def draw_line_end(pos, angle, port, draw):
            cr.save()
            cr.translate(*pos)
            cr.rotate(angle)
            draw(context, port)
            cr.restore()
        self.line_width = self._calc_line_width()
        cr = context.cairo
        cr.set_line_cap(LINE_CAP_ROUND)
        cr.set_line_width(self.line_width)

        # Draw connection tail (line perpendicular to from_port)
        start_segment_index = 0
        if self.from_port:
            draw_line_end(self._handles[0].pos, self._head_angle, self.from_port, self.draw_tail)
            start_segment_index = 1

        # Draw connection head (line perpendicular to to_port)
        end_segment_index = len(self._handles)
        if self.to_port:
            draw_line_end(self._handles[-1].pos, self._tail_angle, self.to_port, self.draw_head)
            end_segment_index -= 1

        # Draw connection line from waypoint to waypoint
        cr.move_to(*self._handles[start_segment_index].pos)
        for h in self._handles[start_segment_index+1:end_segment_index]:
            cr.line_to(*h.pos)
        cr.set_source_rgba(*self._line_color)
        cr.stroke()

        if self.name and (isinstance(self.from_port, LogicPortView) or
                          global_gui_config.get_config_value("SHOW_NAMES_ON_DATA_FLOWS", default=True)):
            self._draw_name(context)

    def _draw_name(self, context):
        c = context.cairo

        if len(self._handles) % 2:  # uneven
            index = int(floor(len(self._handles) / 2))
            cx, cy = self._handles[index].pos
            angle = 0
        else:
            index = int(len(self._handles) / 2) - 1

            p1, p2 = self._handles[index].pos, self._handles[index + 1].pos

            cx = (p1.x + p2.x) / 2
            cy = (p1.y + p2.y) / 2

            if global_gui_config.get_config_value("ROTATE_NAMES_ON_CONNECTIONS", default=False):
                angle = atan2(p2.y - p1.y, p2.x - p1.x)
                if angle < -pi / 2.:
                    angle += pi
                elif angle > pi / 2.:
                    angle -= pi
            else:
                angle = 0

        # c.set_antialias(Antialias.GOOD)

        parameters = {
            'name': self.name,
            'line_width': self.line_width,
            'color': self._arrow_color
        }

        upper_left_corner = cx, cy
        current_zoom = self.view.get_zoom_factor()
        from_cache, image, zoom = self._label_image_cache.get_cached_image(self._last_label_size[0],
                                                                           self._last_label_size[1],
                                                                           current_zoom, parameters)

        # The parameters for drawing haven't changed, thus we can just copy the content from the last rendering result
        if from_cache:
            # print("draw port name from cache")
            self._label_image_cache.copy_image_to_context(c, upper_left_corner, angle)

        # Parameters have changed or nothing in cache => redraw
        else:
            # First retrieve pango layout to determine and store size of label
            cairo_context = c
            if isinstance(c, CairoBoundingBoxContext):
                cairo_context = c._cairo
            layout = get_text_layout(cairo_context, self.name, FONT_SIZE)

            ink_extents, logical_extents = layout.get_extents()
            extents = [extent / float(SCALE) for extent in [logical_extents.x, logical_extents.y,
                                                            logical_extents.width, logical_extents.height]]
            real_label_size = extents[2], extents[3]
            desired_height = self.line_width * 2.5
            scale_factor = real_label_size[1] / desired_height
            label_size = real_label_size[0] / scale_factor, desired_height

            self._last_label_size = label_size

            # The size information is used to update the caching parameters and retrieve a new context with an image
            # surface of the correct size
            self._label_image_cache.get_cached_image(label_size[0], label_size[1], current_zoom, parameters, clear=True)
            c = self._label_image_cache.get_context_for_image(current_zoom)
            cairo_context = c
            layout = get_text_layout(cairo_context, self.name, FONT_SIZE)

            c.set_source_rgba(*self._arrow_color)
            c.scale(1. / scale_factor, 1. / scale_factor)
            PangoCairo.update_layout(cairo_context, layout)
            PangoCairo.show_layout(cairo_context, layout)

            self._label_image_cache.copy_image_to_context(context.cairo, upper_left_corner, angle, zoom=current_zoom)

    def _calc_line_width(self, for_port=None):
        parent_state_v = self.get_parent_state_v()
        if not parent_state_v:
            return 0
        line_width = parent_state_v.border_width / constants.BORDER_WIDTH_LINE_WIDTH_FACTOR
        if for_port:
            return min(line_width, for_port.port_size[0])
        return line_width

    def _head_length(self, port):
        """Distance from the center of the port to the perpendicular waypoint"""
        if not port:
            return 0.
        parent_state_v = self.get_parent_state_v()
        if parent_state_v is port.parent:  # port of connection's parent state
            return port.port_size[1]
        return max(port.port_size[1] * 1.5, self._calc_line_width() / 1.3)

    def _head_offset(self, port):
        """How far away from the port center does the line begin"""
        if not port:
            return 0.
        return port.port_size[1] / 2

    def _update_ports(self):
        assert len(self._handles) >= 2, 'Not enough segments'
        self._ports = []
        handles = self._handles
        for h1, h2 in zip(handles[:-1], handles[1:]):
            self._ports.append(self._create_port(h1.pos, h2.pos))

    def _reversible_insert_handle(self, index, handle):
        super(PerpLine, self)._reversible_insert_handle(index, handle)
        self._keep_handle_in_parent_state(handle)

    def add_waypoint(self, pos):
        pos = self.canvas.get_matrix_i2i(self.parent, self).transform_point(*pos)
        handle = self._create_handle(pos)
        if self._to_waypoint:
            self._handles.insert(-2, handle)
        else:
            self._handles.insert(-1, handle)
        self._keep_handle_in_parent_state(handle)
        self._update_ports()
        return handle

    def remove_all_waypoints(self):
        waypoints = self.waypoints
        for waypoint in waypoints:
            self._handles.remove(waypoint)
        self._update_ports()
        for constraint in self._waypoint_constraints:
            self.canvas.solver.remove_constraint(constraint)
        self._waypoint_constraints = []

    def add_perp_waypoint(self, pos=(0, 0), begin=True):
        handle = self._create_handle(pos)
        if begin:
            self._handles.insert(1, handle)
        else:
            self._handles.insert(len(self._handles) - 1, handle)
        self._update_ports()
        return handle

    @staticmethod
    def is_in_port(port):
        return isinstance(port, (IncomeView, InputPortView))

    @staticmethod
    def is_out_port(port):
        return isinstance(port, (OutcomeView, OutputPortView))

    def point(self, pos):
        distance = super(PerpLine, self).point(pos)
        return distance - self.line_width / 1.5

    def _keep_handle_in_parent_state(self, handle):
        canvas = self.canvas
        parent = canvas.get_parent(self)
        solver = canvas.solver
        if parent is None:
            return
        handle_pos = ItemProjection(handle.pos, self, self.parent)
        constraint = KeepPointWithinConstraint(parent.handles()[NW].pos, parent.handles()[SE].pos,
                                               handle_pos, lambda: parent.border_width)
        solver.add_constraint(constraint)
        self._waypoint_constraints.append(constraint)