def _draw_handles(self, item, cairo, opacity=None): view = self.view cairo.save() i2v = view.get_matrix_i2v(item) if not opacity: opacity = 1 side_length = self._get_handle_side_length(item) line_width = side_length / constants.BORDER_WIDTH_OUTLINE_WIDTH_FACTOR * 2 cairo.set_line_width(line_width) for index, handle in enumerate(item.handles()): if index >= 4: break # Reset the current transformation cairo.identity_matrix() # Move to center of handle cairo.translate(*i2v.transform_point(*handle.pos)) cairo.rectangle(-side_length / 2., -side_length / 2., side_length, side_length) # Fill cairo.set_source_rgba(*get_col_rgba(self.fill_color, opacity=opacity)) cairo.fill_preserve() # Border cairo.set_source_rgba(*get_col_rgba(self.border_color, opacity=opacity)) cairo.stroke() cairo.restore()
def paint(self, context, selected): view = self.view item = self.item cr = context.cairo h = item.handles() side_length = get_side_length_of_resize_handle(self.view, item.parent) / 1.5 for h1, h2 in zip(h[1:-2], h[2:-1]): p1, p2 = h1.pos, h2.pos cx = (p1.x + p2.x) / 2 cy = (p1.y + p2.y) / 2 cr.save() cr.set_line_width(self.view.get_zoom_factor() / 4.) cr.identity_matrix() m = Matrix(*view.get_matrix_i2v(item)) cr.set_antialias(ANTIALIAS_NONE) cr.translate(*m.transform_point(cx, cy)) cr.rectangle(-side_length / 2., -side_length / 2., side_length, side_length) cr.set_source_rgba(*get_col_rgba(self.fill_color)) cr.fill_preserve() cr.set_source_rgba(*get_col_rgba(self.border_color)) cr.set_line_width(1) cr.stroke() cr.restore()
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 __init__(self, data_flow_m, hierarchy_level): super(DataFlowView, self).__init__(hierarchy_level) assert isinstance(data_flow_m, DataFlowModel) self._data_flow_m = None self.model = data_flow_m self._line_color = gap_draw_helper.get_col_rgba(gui_config.gtk_colors['DATA_LINE']) self._arrow_color = gap_draw_helper.get_col_rgba(gui_config.gtk_colors['DATA_PORT'])
def draw(self, context): if self.model.core_element and self.show_connection: if context.selected: self._line_color = gap_draw_helper.get_col_rgba( gui_config.gtk_colors['DATA_LINE_SELECTED']) else: self._line_color = gap_draw_helper.get_col_rgba( gui_config.gtk_colors['DATA_LINE']) super(DataFlowView, self).draw(context)
def draw(self, context): # Do not draw if the core element has already been destroyed if not self.model.core_element: return if not self.show_connection: return if context.selected: self._line_color = gap_draw_helper.get_col_rgba(gui_config.gtk_colors['DATA_LINE_SELECTED']) else: self._line_color = gap_draw_helper.get_col_rgba(gui_config.gtk_colors['DATA_LINE']) super(DataFlowView, self).draw(context)
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(self, context): if self.model.core_element and self.show_connection: if context.selected: self._line_color = gap_draw_helper.get_col_rgba( gui_config.gtk_colors['TRANSITION_LINE_SELECTED'], self.parent.transparency) else: self._line_color = gap_draw_helper.get_col_rgba( gui_config.gtk_colors['TRANSITION_LINE'], self.parent.transparency) self._arrow_color = gap_draw_helper.get_col_rgba( gui_config.gtk_colors['LABEL'], self.parent.transparency) super(TransitionView, self).draw(context)
def draw(self, context): # Do not draw if the core element has already been destroyed if not self.model.core_element: return if context.selected: self._line_color = gap_draw_helper.get_col_rgba(gui_config.gtk_colors['TRANSITION_LINE_SELECTED'], self.parent.transparency) else: self._line_color = gap_draw_helper.get_col_rgba(gui_config.gtk_colors['TRANSITION_LINE'], self.parent.transparency) self._arrow_color = gap_draw_helper.get_col_rgba(gui_config.gtk_colors['LABEL'], self.parent.transparency) super(TransitionView, self).draw(context)
def _paint_guides(self, context): # Code copied from gaphas.guide.GuidePainter try: guides = self.view.guides except AttributeError: return cr = context.cairo view = self.view allocation = view.get_allocation() w, h = allocation.width, allocation.height cr.save() try: # a width of 1 is hardly visible when using dashed lines cr.set_line_width(2) cr.set_dash([4], 1) guide_color = gui_config.gtk_colors['GUIDE_COLOR'] cr.set_source_rgba(*get_col_rgba(guide_color, 0.6)) for g in guides.vertical(): cr.move_to(g, 0) cr.line_to(g, h) cr.stroke() for g in guides.horizontal(): cr.move_to(0, g) cr.line_to(w, g) cr.stroke() finally: cr.restore()
def draw_name(self, context, transparency, only_calculate_size=False): """Draws the name of the port Offers the option to only calculate the size of the name. :param context: The context to draw on :param transparency: The transparency of the text :param only_calculate_size: Whether to only calculate the size :return: Size of the name :rtype: float, float """ c = context c.set_antialias(cairo.ANTIALIAS_SUBPIXEL) side_length = self.port_side_size layout = c.create_layout() font_name = constants.INTERFACE_FONT font_size = gap_draw_helper.FONT_SIZE font = FontDescription(font_name + " " + str(font_size)) layout.set_font_description(font) layout.set_text(self.name) ink_extents, logical_extents = layout.get_extents() extents = [extent / float(SCALE) for extent in logical_extents] real_name_size = extents[2], extents[3] desired_height = side_length * 0.75 scale_factor = real_name_size[1] / desired_height # Determine the size of the text, increase the width to have more margin left and right of the text margin = side_length / 4. name_size = real_name_size[0] / scale_factor, desired_height name_size_with_margin = name_size[0] + margin * 2, name_size[ 1] + margin * 2 # Only the size is required, stop here if only_calculate_size: return name_size_with_margin # Current position is the center of the port rectangle c.save() if self.side is SnappedSide.RIGHT or self.side is SnappedSide.LEFT: c.rotate(deg2rad(-90)) c.rel_move_to(-name_size[0] / 2, -name_size[1] / 2) c.scale(1. / scale_factor, 1. / scale_factor) c.rel_move_to(-extents[0], -extents[1]) c.set_source_rgba( *gap_draw_helper.get_col_rgba(self.text_color, transparency)) c.update_layout(layout) c.show_layout(layout) c.restore() return name_size_with_margin
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()
def _draw_symbol(self, context, symbol, color, transparency=0.): c = context.cairo cairo_context = c if isinstance(c, CairoBoundingBoxContext): cairo_context = c._cairo width = self.width height = self.height # c.set_antialias(Antialias.GOOD) layout = PangoCairo.create_layout(cairo_context) 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)) PangoCairo.update_layout(cairo_context, layout) PangoCairo.show_layout(cairo_context, layout)
def _draw_symbol(self, context, symbol, color, transparency=0.): c = context.cairo cairo_context = c if isinstance(c, CairoBoundingBoxContext): cairo_context = c._cairo width = self.width height = self.height # c.set_antialias(Antialias.GOOD) layout = PangoCairo.create_layout(cairo_context) def set_font_description(): set_label_markup(layout, symbol, is_icon=True, size=font_size) 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] * constants.ICON_STATE_FILL_FACTOR or \ layout.get_size()[1] > pango_size[1] * constants.ICON_STATE_FILL_FACTOR: 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)) PangoCairo.update_layout(cairo_context, layout) PangoCairo.show_layout(cairo_context, layout)
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)
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(self, context, state): # Do not draw if the core element has already been destroyed if not self.model.core_element: return c = context.cairo view = self.parent.canvas.get_first_view() side_length = self.port_side_size parameters = { 'name': self.name, 'side': self.side, 'side_length': side_length, 'selected': self.is_selected(), 'transparency': state.transparency } current_zoom = view.get_zoom_factor() from_cache, image, zoom = self._port_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: center_pos = self._get_port_center_position(self._last_label_span) upper_left_corner = center_pos[0] - self._last_label_size[0] / 2., \ center_pos[1] - self._last_label_size[1] / 2. self._port_image_cache.copy_image_to_context(c, upper_left_corner) # Parameters have changed or nothing in cache => redraw else: # First we have to do a "dry run", in order to determine the size of the port c.move_to(*self.pos) name_size = self.draw_name(c, state.transparency, only_calculate_size=True) extents = self._draw_rectangle_path(c, name_size[0], side_length, only_get_extents=True) port_size = extents[2] - extents[0], extents[3] - extents[1] self._last_label_size = port_size self._last_label_span = name_size[0] # The size information is used to update the caching parameters and retrieve a new context with an image # surface of the correct size self._port_image_cache.get_cached_image(port_size[0], port_size[1], current_zoom, parameters, clear=True) c = self._port_image_cache.get_context_for_image(current_zoom) # First, draw the filled rectangle # Set the current point to be in the center of the rectangle c.move_to(port_size[0] / 2., port_size[1] / 2.) self._draw_rectangle_path(c, name_size[0], side_length) c.set_line_width(self.port_side_size / 50. * self._port_image_cache.multiplicator) c.set_source_rgba(*gap_draw_helper.get_col_rgba( self.fill_color, state.transparency)) c.fill_preserve() c.stroke() # Second, write the text in the rectangle (scoped variable name) # Set the current point to be in the center of the rectangle c.move_to(port_size[0] / 2., port_size[1] / 2.) self.draw_name(c, state.transparency) # Copy image surface to current cairo context center_pos = self._get_port_center_position(name_size[0]) upper_left_corner = center_pos[0] - port_size[0] / 2., center_pos[ 1] - port_size[1] / 2. self._port_image_cache.copy_image_to_context(context.cairo, upper_left_corner, zoom=current_zoom) if self.is_selected( ) or self.handle is view.hovered_handle or context.draw_all: context.cairo.move_to( *self._get_port_center_position(self._last_label_span)) self._draw_hover_effect(context.cairo, self.direction, self.fill_color, state.transparency)
def __init__(self, hierarchy_level): super(TransitionPlaceholderView, self).__init__(hierarchy_level) self._line_color = gap_draw_helper.get_col_rgba( gui_config.gtk_colors['TRANSITION_LINE']) self._arrow_color = gap_draw_helper.get_col_rgba( gui_config.gtk_colors['LABEL'])
def __init__(self, hierarchy_level): super(DataFlowPlaceholderView, self).__init__(hierarchy_level) self._line_color = gap_draw_helper.get_col_rgba( gui_config.gtk_colors['DATA_LINE']) self._arrow_color = gap_draw_helper.get_col_rgba( gui_config.gtk_colors['DATA_PORT'])