# Wx::SF::Shape - base shape class # Copyright (c) M.J.N. Corino, The Netherlands require 'wx/shapes/serializable' require 'wx/shapes/shapes/manager_shape' require 'set' module Wx::SF class ERRCODE < Wx::Enum OK = self.new(0) NOT_CREATED = self.new(1) NOT_ACCEPTED = self.new(2) INVALID_INPUT = self.new(3) end # Base class for all shapes providing fundamental functionality and publishing set # of virtual functions which must be defined by the user in derived shapes. This class # shouldn't be used as it is. # # Shape objects derived from this class use hierarchical approach. It means that every # shape must have defined parent shape (can be NULL for topmost shapes). An absolute # shape position is then calculated as a summation of all relative positions of all parent # shapes. Also the size of the parent shape can be limited be a bounding box of all # children shapes. # # This class also declares set of virtual functions used as event handlers for various # events (moving, sizing, drawing, mouse events, serialization and deserialization requests, ...) # mostly triggered by a parent shape canvas. class Shape include FIRM::Serializable property :active, :visibility, :style, :accepted_children, :accepted_connections, :accepted_src_neighbours, :accepted_trg_neighbours, :relative_position, :h_align, :v_align, :h_border, :v_border, :custom_dock_point, :connection_points, :user_data property({ hover_colour: :serialize_hover_colour }, optional: true) property child_shapes: :serialize_child_shapes class SEARCHMODE < Wx::Enum # Depth-First-Search algorithm DFS = self.new(0) # Breadth-First-Search algorithm BFS = self.new(1) end # Bit flags for Wx::SF::Shape get_complete_bounding_box function class BBMODE < Wx::Enum SELF = self.new(1) CHILDREN = self.new(2) CONNECTIONS = self.new(4) SHADOW = self.new(8) ALL = self.new(15) end # Search mode flags for get_assigned_connections function class CONNECTMODE < Wx::Enum # Search for connection starting in examined shape STARTING = self.new(0) # Search for connection ending in examined shape ENDING = self.new(1) # Search for both starting and ending connections BOTH = self.new(2) end # Flags for set_v_align function class VALIGN < Wx::Enum NONE = self.new(0) TOP = self.new(1) MIDDLE = self.new(2) BOTTOM = self.new(3) EXPAND = self.new(4) LINE_START = self.new(5) LINE_END = self.new(6) end # Flags for set_h_align function class HALIGN < Wx::Enum NONE = self.new(0) LEFT = self.new(1) CENTER = self.new(2) RIGHT = self.new(3) EXPAND = self.new(4) LINE_START = self.new(5) LINE_END = self.new(6) end # Basic shape's styles used with set_style function class STYLE < Wx::Enum # Interactive parent change is allowed PARENT_CHANGE = self.new(1) # Interactive position change is allowed POSITION_CHANGE = self.new(2) # Interactive size change is allowed SIZE_CHANGE = self.new(4) # Shape is highlighted at mouse hovering HOVERING = self.new(8) # Shape is highlighted at shape dragging HIGHLIGHTING = self.new(16) # Shape is always inside its parent ALWAYS_INSIDE = self.new(32) # Shape is not drawn. Does not apply to children. # Should be combined with PROPAGATE_DRAGGING | PROPAGATE_SELECTION | PROPAGATE_INTERACTIVE_CONNECTION | PROPAGATE_HOVERING | PROPAGATE_HIGHLIGHTING | PROPAGATE_DROPPING # in most cases. NOT_DRAWN = self.new(64) # The DEL key is processed by the shape (not by the shape canvas) PROCESS_DEL = self.new(128) # Show handles if the shape is selected SHOW_HANDLES = self.new(256) # Show shadow under the shape SHOW_SHADOW = self.new(512) # Lock children relative position if the parent is resized LOCK_CHILDREN = self.new(1024) # Emit events (catchable in shape canvas) EMIT_EVENTS = self.new(2048) # Propagate mouse dragging event to parent shape PROPAGATE_DRAGGING = self.new(4096) # Propagate selection to parent shape (it means this shape cannot be selected because its focus is redirected to its parent shape) PROPAGATE_SELECTION = self.new(8192) # Propagate interactive connection request to parent shape (it means this shape cannot be connected interactively because this feature is redirected to its parent shape) PROPAGATE_INTERACTIVE_CONNECTION = self.new(16384) # Do no resize the shape to fit its children automatically NO_FIT_TO_CHILDREN = self.new(32768) # Propagate hovering to parent. PROPAGATE_HOVERING = self.new(65536) # Propagate highlighting to parent. PROPAGATE_HIGHLIGHTING = self.new(131072) # Propagate dropping to parent. PROPAGATE_DROPPING = self.new(262144) # Default shape style DEFAULT_SHAPE_STYLE = PARENT_CHANGE | POSITION_CHANGE | SIZE_CHANGE | HOVERING | HIGHLIGHTING | SHOW_HANDLES | ALWAYS_INSIDE # Shortcut for all propagation options PROPAGATE_ALL = PROPAGATE_DRAGGING | PROPAGATE_SELECTION | PROPAGATE_INTERACTIVE_CONNECTION | PROPAGATE_HOVERING | PROPAGATE_HIGHLIGHTING | PROPAGATE_DROPPING end # Default values module DEFAULT class << self # Default value of Wx::SF::Shape @hoverColor data member def hover_colour; Wx::Colour.new(120, 120, 255); end end # Default value of Wx::SF::Shape @visible data member VISIBILITY = true # Default value of Wx::SF::Shape @active data member ACTIVITY = true # Default value of Wx::SF::Shape @relativePosition data member POSITION = Wx::RealPoint.new(0, 0) # Default value of Wx::SF::Shape @vAlign data member VALIGN = VALIGN::NONE # Default value of Wx::SF::Shape @hAlign data member HALIGN = HALIGN::NONE # Default value of Wx::SF::Shape @vBorder data member VBORDER = 0.0 # Default value of Wx::SF::Shape @hBorder data member HBORDER = 0.0 # Default value of Wx::SF::Shape @style data member DEFAULT_STYLE = STYLE::DEFAULT_SHAPE_STYLE # Default value of Wx::SF::Shape @customDockPoint data member DOCK_POINT = -3 end # Provide Shape and derivatives with component set container class << self def component_shapes @component_shapes ||= ::Set.new end end # Declare a component shape property for the shape class. # @overload component(*comp_id) # Specifies one or more serialized component properties. # The serialization framework will determine the availability of setter and getter methods # automatically by looking for methods "#{comp_id}=(v)", "set_#{comp_id}(v)" or "#{comp_id}(v)" # for setters and "#{comp_id}()" or "get_#{comp_id}" for getters. # @param [String,Symbol] comp_id id of component property # @overload component(hash) # Specifies one or more serialized component properties with associated setter/getter method ids/procs/lambda-s. # @example # component( # prop_a: ->(obj, *val) { # obj.my_prop_a_setter(val.first) unless val.empty? # obj.my_prop_a_getter # }, # prop_b: Proc.new { |obj, *val| # obj.my_prop_b_setter(val.first) unless val.empty? # obj.my_prop_b_getter # }, # prop_c: :serialization_method) # Procs with setter support MUST accept 1 or 2 arguments (1 for getter, 2 for setter). # @note Use `*val` to specify the optional value argument for setter requests instead of `val=nil` # to be able to support setting explicit nil values. # @param [Hash] hash a hash of pairs of property ids and getter/setter procs def self.component(*args) args.flatten.each do |arg| if arg.is_a?(::Hash) arg.each_pair do |pn, pp| # define serialized property for component (checks for duplicates) property({pn => pp}, force: true) # get the property definition and register as component component_shapes << self.serializer_properties.last end else # define serialized property for component (checks for duplicates) property(arg, force: true) # get the property definition and register as component component_shapes << self.serializer_properties.last end end # check if the current class already has the appropriate support unless self.const_defined?(:ComponentSerializerMethods) class << self def disable_component_serialize(obj) component_shapes.each { |pd| pd.get(obj).disable_serialize } superclass.disable_component_serialize(obj) if superclass.respond_to?(:disable_component_serialize) end # override the #new method def new(*) instance = super disable_component_serialize(instance) instance end end self.class_eval <<~__CODE module ComponentSerializerMethods def from_serialized(hash) super(hash) #{self.name}.component_shapes.each { |pd| pd.get(self).set_parent_shape(self) } self end protected :from_serialized end include ComponentSerializerMethods __CODE end end # Constructor # @param [Wx::RealPoint, Wx::Point] pos Initial relative position # @param [Diagram] diagram containing diagram def initialize(pos = DEFAULT::POSITION, diagram: nil) ::Kernel.raise ArgumentError, "Invalid arguments pos: #{pos}, diagram: #{diagram}" unless Wx::RealPoint === pos && (diagram.nil? || Wx::SF::Diagram === diagram) @diagram = diagram @parent_shape = nil @child_shapes = ShapeList.new @components = ::Set.new # by default the common canvas hover colour will be used @hover_color = nil @selected = false @mouse_over = false @first_move = false @highlight_parent = false @user_data = nil # archived properties @visible = DEFAULT::VISIBILITY @active = DEFAULT::ACTIVITY @style = DEFAULT::DEFAULT_STYLE @v_align = DEFAULT::VALIGN @h_align = DEFAULT::HALIGN @v_border = DEFAULT::VBORDER @h_border = DEFAULT::HBORDER @custom_dock_point = DEFAULT::DOCK_POINT @relative_position = Wx::RealPoint === pos ? pos.dup : pos.to_real_point @handles = [] @connection_pts = [] @accepted_children = ::Set.new @accepted_connections = ::Set.new @accepted_src_neighbours = ::Set.new @accepted_trg_neighbours = ::Set.new end # Set managing diagram # @param [Wx::SF::Diagram] diagram def set_diagram(diagram) if @diagram != diagram @diagram = diagram @child_shapes.each { |child| child.set_diagram(diagram) } end self end # Get managing diagram # @return [Wx::SF::Diagram] def get_diagram @diagram end alias :diagram :get_diagram # Get the shape canvas of the parent diagram # @return [Wx::SF::ShapeCanvas,nil] def get_parent_canvas @diagram ? @diagram.get_shape_canvas : nil end # Add a child shape # @param [Wx::SF::Shape] shape def add_child(shape) @child_shapes << shape if shape end private :add_child # Remove a child shape # @param [Wx::SF::Shape] shape def remove_child(shape) @child_shapes.delete(shape) if shape end private :remove_child # Adds child shape is accepted. Removes the child shape as a toplevel diagram shape if appropriate. # @param [Wx::SF::Shape] child child shape to add # @return [Wx::SF::Shape,nil] added child shape or nil if not accepted def add_child_shape(child) raise SFException, 'Illegal attempt to add self as Shape child' if child == self if is_child_accepted(child.class) if child.get_diagram child.get_diagram.reparent_shape(child, self) else child.set_parent_shape(self) end child.update return child end nil end # Returns true if the given shape is included in the child shapes list. # Performs a recursive search in case :recursive is true. # @param [shape] shape shape to match # @param [Boolean] recursive pass true to search recursively, false for non-recursive # @return [Boolean] true if included, otherwise false def include_child_shape?(shape, recursive = false) @child_shapes.include?(shape, recursive) end # Set parent shape object. # @param [Wx::SF::Shape] parent # @note Note that this does not check this shape against the acceptance list of the parent. Use #add_child_shape if that is required. # @note Note that this does not add (if parent == nil) or remove (if parent != nil) the shape from the diagram's # toplevel shapes. Use Diagram#reparent_shape when that is needed. def set_parent_shape(parent) raise SFException, 'Illegal to set Shape parent to self' if parent == self raise SFException, 'Illegal to set Shape parent to (grand-)child of self' if parent && include_child_shape?(parent, true) @parent_shape.send(:remove_child, self) if @parent_shape parent.send(:add_child, self) if parent set_diagram(parent.get_diagram) if parent @parent_shape = parent end alias :parent_shape= :set_parent_shape # Get parent shape # @return [Wx::SF::Shape,nil] parent shape def get_parent_shape @parent_shape end alias :parent_shape :get_parent_shape # Get pointer to the topmost parent shape # @return [Wx::SF::Shape] topmost parent shape def get_grand_parent_shape @parent_shape ? @parent_shape.get_grand_parent_shape : self end alias :grand_parent_shape :get_grand_parent_shape # Refresh (redraw) the shape # @param [Boolean] delayed If true then the shape canvas will be invalidated rather than refreshed. # @see ShapeCanvas#invalidate_rect # @see ShapeCanvas#refresh_invalidated_rect def refresh(delayed = false) refresh_rect(get_bounding_box, delayed) end # Draw shape. Default implementation tests basic shape visual states # (normal/ready, mouse is over the shape, dragged shape can be accepted) and # call appropriate virtual functions (DrawNormal, DrawHover, DrawHighlighted) # for its visualisation. The function can be overridden if necessary. # @param [Wx::DC] dc Reference to a device context where the shape will be drawn to # @param [Boolean] children true if the shape's children should be drawn as well def draw(dc, children = WITHCHILDREN) return unless @diagram && @diagram.shape_canvas return unless @visible unless has_style?(STYLE::NOT_DRAWN) # draw the shape shadow if required draw_shadow(dc) if !@selected && has_style?(STYLE::SHOW_SHADOW) # first, draw itself if @mouse_over && (@highlight_parent || has_style?(STYLE::HOVERING)) if @highlight_parent draw_highlighted(dc) @highlight_parent = false else draw_hover(dc) end else draw_normal(dc) end draw_selected(dc) if @selected # ... then draw connection points ... @connection_pts.each { |cpt| cpt.draw(dc) } unless has_style?(STYLE::PROPAGATE_INTERACTIVE_CONNECTION) end # ... then draw child shapes if children @child_shapes.each { |child| child.draw(dc) } end end # Test whether the given point is inside the shape. The function # can be overridden if necessary. # @param [Wx::Point] pos Examined point # @return [Boolean] true if the point is inside the shape area, otherwise false def contains?(pos) # HINT: overload it for custom actions... get_bounding_box.contains?(pos) end # Test whether the shape is completely inside given rectangle. The function # can be overridden if necessary. # @param [Wx::Rect] rct Examined rectangle # @return [Boolean] true if the shape is completely inside given rectangle, otherwise false def inside?(rct) # HINT: overload it for custom actions... rct.contains?(get_bounding_box) end # Test whether the given rectangle intersects the shape. # @param [Wx::Rect] rct Examined rectangle # @return [Boolean] true if the examined rectangle intersects the shape, otherwise false def intersects?(rct) # HINT: overload it for custom actions... rct.intersects(get_bounding_box) end # Get the shape's absolute position in the canvas (calculated as a summation # of all relative positions in the shapes' hierarchy. The function can be overridden if necessary. # @return [Wx::RealPoint] Shape's position def get_absolute_position # HINT: overload it for custom actions... if @parent_shape @relative_position + get_parent_absolute_position else @relative_position end end # Get intersection point of the shape border and a line leading from # 'start' point to 'finish' point. Default implementation does nothing. The function can be overridden if necessary. # @param [Wx::RealPoint] _start Starting point of the virtual intersection line # @param [Wx::RealPoint] _finish Ending point of the virtual intersection line # @return [Wx::RealPoint] Intersection point def get_border_point(_start, _finish) # HINT: overload it for custom actions... Wx::RealPoint.new end # Get shape's center. Default implementation does nothing. The function can be overridden if necessary. # @return [Wx::RealPoint] Center point def get_center # HINT: overload it for custom actions... bb = get_bounding_box Wx::RealPoint.new(bb.left + bb.width/2, bb.top + bb.height/2) end # Function called by the framework responsible for creation of shape handles # at the creation time. Default implementation does nothing. The function can be overridden if necessary. def create_handles # HINT: overload it for custom actions... end # Show/hide shape handles. Hidden handles are inactive. # @param [Boolean] show true for showing, false for hiding def show_handles(show) @handles.each { |h| h.show(show) } end # Set shape's style. # # Default value is STYLE::PARENT_CHANGE | STYLE::POSITION_CHANGE | STYLE::SIZE_CHANGE | STYLE::HOVERING | STYLE::HIGHLIGHTING | STYLE::SHOW_HANDLES | STYLE::ALWAYS_INSIDE # @param [Integer] style Combination of the shape's styles # @see STYLE def set_style(style) @style = style end alias :style= :set_style # Get current shape style. # @return [Integer] shape style def get_style @style end alias :style :get_style def add_style(style) @style |= style end def remove_style(style) @style &= ~style end def contains_style(style) @style.allbits?(style) end alias :contains_style? :contains_style alias :has_style? :contains_style # Find out whether this shape has some children. # @return [Boolean] true if the parent shape has children, otherwise false def has_children !@child_shapes.empty? end alias :has_children? :has_children # Get children of given type. # @param [Class,nil] type Child shape type (if nil then all children are returned) # @param [Array] list list where all found child shapes will be appended # @return [Array] list with appended child shapes def get_children(type, list) @child_shapes.each_with_object(list) { |child, lst| lst << child if type.nil? || type === child } end # Get all children of given type recursively (i.e. children of children of .... ). # @param [Class,nil] type Child shape type (if nil then all children are returned) # @param [Array] list list where all found child shapes will be appended # @param [SEARCHMODE] mode Search mode. User can choose Depth-First-Search or Breadth-First-Search algorithm (BFS is default) # @see SEARCHMODE def get_children_recursively(type, mode = SEARCHMODE::BFS, list = []) @child_shapes.each do |child| list << child if type.nil? || type === child child.get_children_recursively(type, mode, list) if mode == SEARCHMODE::DFS end if mode == SEARCHMODE::BFS @child_shapes.each { |child| child.get_children_recursively(type, mode, list) } end list end # Get child shapes associated with this (parent) shape. # @param [Class,nil] type Type of searched child shapes (nil for any type) # @param [Boolean] recursive Set this flag true if also children of children of ... should be found (also RECURSIVE or NORECURSIVE constants can be used). # @param [SEARCHMODE] mode Search mode (has sense only for recursive search) # @param [Array] list of child shapes to fill # @return [Array] list of child shapes filled def get_child_shapes(type, recursive = NORECURSIVE, mode = SEARCHMODE::BFS, list = []) if recursive get_children_recursively(type, mode, list) else get_children(type, list) end end # Get neighbour shapes connected to this shape. # @param [Class,nil] shape_info Line object type # @param [CONNECTMODE] condir Connection direction # @param [Boolean] direct Set this flag to true if only closest shapes should be found, otherwise also shapes connected by forked lines will be found (also constants DIRECT and INDIRECT can be used) # @param [Array] neighbours List of neighbour shapes # @return [Array] list of neighbour shapes filled # @see CONNECTMODE def get_neighbours(shape_info, condir, direct = DIRECT, neighbours = []) unless Wx::SF::LineShape === self _get_neighbours(shape_info, condir, direct, neighbours) # delete starting object if necessary (can be added in a case of complex connection network) neighbours.delete(self) end neighbours end # Get list of connections assigned to this shape. # @note For proper functionality the shape must be managed by a diagram manager. # @param [Class] shape_info Line object type # @param [CONNECTMODE] mode Search mode # @param [Array] lines shape list where all found connections will be stored # @return [Array] list of connection shapes filled # @see CONNECTMODE def get_assigned_connections(shape_info, mode, lines = []) @diagram.get_assigned_connections(self, shape_info, mode, lines) if @diagram lines end # Get shape's bounding box. The function can be overridden if necessary. # @return [Wx::Rect] Bounding rectangle def get_bounding_box # HINT: overload it for custom actions... Wx::Rect.new end # Get shape's bounding box which includes also associated child shapes and connections. # @param [Wx::Rect, nil] rct bounding rectangle # @param [BBMODE] mask Bit mask of object types which should be included into calculation # @return [Wx::Rect] returned bounding box # @see BBMODE def get_complete_bounding_box(rct, mask = BBMODE::ALL) _get_complete_bounding_box(rct, mask) end # Scale the shape size in both directions. The function can be overridden if necessary # (new implementation should call default one or scale shape's children manually if necessary). # @overload scale(x,y, children: WITHCHILDREN) # @param [Float] x Horizontal scale factor # @param [Float] y Vertical scale factor # @param [Boolean] children true if the shape's children should be scaled as well, otherwise the shape will be updated after scaling via #update function. # @overload scale(scale, children: WITHCHILDREN) # @param [Wx::RealPoint] scale scale factors # @param [Boolean] children true if the shape's children should be scaled as well, otherwise the shape will be updated after scaling via #update function. def scale(*args, children: WITHCHILDREN) # HINT: overload it for custom actions... x, y = (args.size == 1 ? args.first : args) scale_children(x, y) if children @diagram.set_modified(true) if @diagram # self.update end # Scale shape's children # @param [Float] x Horizontal scale factor # @param [Float] y Vertical scale factor # @see Scale def scale_children(x, y) lst_children = get_child_shapes(ANY, RECURSIVE) lst_children.each do |shape| if shape.has_style?(STYLE::SIZE_CHANGE) && shape.is_a?(Wx::SF::TextShape) shape.scale(x, y, children: WITHOUTCHILDREN) end if shape.has_style?(STYLE::POSITION_CHANGE) && (shape.get_v_align == VALIGN::NONE || shape.get_h_align == HALIGN::NONE) shape.set_relative_position(shape.get_relative_position.x*x, shape.get_relative_position.y*y) end # re-align shapes which have set any alignment mode shape.do_alignment end end # Move the shape to the given absolute position. The function can be overridden if necessary. # @overload move_to(x,y) # @param [Float] x X coordinate # @param [Float] y Y coordinate # @overload move_to(pos) # @param [Wx::RealPoint] pos New absolute position def move_to(*args) # HINT: overload it for custom actions... pos = (args.size == 1 ? args.first.to_real_point : Wx::RealPoint.new(*args)) @relative_position = pos - get_parent_absolute_position @diagram.set_modified(true) if @diagram end # Move the shape by the given offset. The function can be overridden if necessary. # @overload move_by(x,y) # @param [Float] x X offset # @param [Float] y Y offset # @overload move_by(delta) # @param [Wx::RealPoint] delta Offset def move_by(*args) # HINT: overload it for custom actions... x, y = (args.size == 1 ? args.first : args) @relative_position.x += x @relative_position.y += y @diagram.set_modified(true) if @diagram end # Returns true if this shape manages (size/position/alignment) of it's child shapes. # Returns false by default. # @return [Boolean] def is_manager false end alias :manager? :is_manager # Returns true if this shape is managed (size/position/alignment) by it's parent shape. # @return [Boolean] def is_managed !!@parent_shape&.is_manager end alias :managed? :is_managed # Update the shape's position in order to its alignment def do_alignment # align to parent unless parent is manager unless @parent_shape.nil? || managed? if @parent_shape.is_a?(Wx::SF::LineShape) line_pos = get_parent_absolute_position parent_bb = Wx::Rect.new(line_pos.x.to_i, line_pos.y.to_i, 1, 1) else parent_bb = @parent_shape.get_bounding_box end shape_bb = get_bounding_box # do vertical alignment case @v_align when VALIGN::TOP @relative_position.y = @v_border when VALIGN::MIDDLE @relative_position.y = parent_bb.height/2 - shape_bb.height/2 when VALIGN::BOTTOM @relative_position.y = parent_bb.height - shape_bb.height - @v_border when VALIGN::EXPAND if has_style?(STYLE::SIZE_CHANGE) @relative_position.y = @v_border scale(1.0, ((parent_bb.height - 2*@v_border)/shape_bb.height).to_f) end when VALIGN::LINE_START if @parent_shape.is_a?(Wx::SF::LineShape) line_start, line_end = @parent_shape.get_line_segment(0) if line_end.y >= line_start.y @relative_position.y = line_start.y - line_pos.y + @v_border else @relative_position.y = line_start.y - line_pos.y - shape_bb.height - @v_border end end when VALIGN::LINE_END if @parent_shape.is_a?(Wx::SF::LineShape) line_start, line_end = @parent_shape.get_line_segment(parent.get_control_points.get_count) if line_end.y >= line_start.y @relative_position.y = line_end.y - line_pos.y - shape_bb.height - @v_border else @relative_position.y = line_end.y - line_pos.y + @v_border end end end # do horizontal alignment case @h_align when HALIGN::LEFT @relative_position.x = @h_border when HALIGN::CENTER @relative_position.x = parent_bb.width/2 - shape_bb.width/2 when HALIGN::RIGHT @relative_position.x = parent_bb.width - shape_bb.width - @h_border when HALIGN::EXPAND if has_style?(STYLE::SIZE_CHANGE) @relative_position.x = @h_border scale(((parent_bb.width - 2*@h_border)/shape_bb.width).to_f, 1.0) end when HALIGN::LINE_START if @parent_shape.is_a?(Wx::SF::LineShape) line_start, line_end = @parent_shape.get_line_segment(0) if line_end.x >= line_start.x @relative_position.x = line_start.x - line_pos.x + @h_border else @relative_position.x = line_start.x - line_pos.x - shape_bb.width - @h_border end end when HALIGN::LINE_END if @parent_shape.is_a?(Wx::SF::LineShape) line_start, line_end = @parent_shape.get_line_segment(@parent_shape.get_control_points.get_count) if line_end.x >= line_start.x @relative_position.x = line_end.x - line_pos.x - shape_bb.width - @h_border else @relative_position.x = line_end.x - line_pos.x + @h_border end end end end end # Update shape (align all child shapes and resize it to fit them) def update(recurse = true) # do self-alignment do_alignment # do alignment of shape's children (if required) @child_shapes.each { |child| child.do_alignment } # fit the shape to its children fit_to_children unless has_style?(STYLE::NO_FIT_TO_CHILDREN) # do it recursively on all parent shapes if recurse && (parent = get_parent_shape) parent.update(recurse) end end # Resize the shape to bound all child shapes. The function can be overridden if necessary. def fit_to_children # HINT: overload it for custom actions... end # Function returns true if the shape is selected, otherwise returns false def selected? @selected end # Returns true if any (grand-)parent is selected? def has_selected_parent? @parent_shape&.selected? || @parent_shape&.has_selected_parent? end alias :selected_parent? :has_selected_parent? # Set the shape as a selected/deselected one # @param [Boolean] state Selection state (true is selected, false is deselected) def select(state) @selected = state show_handles(state && has_style?(STYLE::SHOW_HANDLES)) end # Set shape's relative position. Absolute shape's position is then calculated # as a summation of the relative positions of this shape and all parent shapes in the shape's # hierarchy. # @overload set_relative_position(pos) # @param [Wx::RealPoint] pos New relative position # @overload set_relative_position(x,y) # @param [Float] x Horizontal coordinate of new relative position # @param [Float] y Vertical coordinate of new relative position # @see #move_to def set_relative_position(*arg) x, y = (arg.size == 1 ? arg.first.to_real_point : arg) @relative_position.x = x @relative_position.y = y end # Get shape's relative position. # @return [Wx::RealPoint] Current relative position # @see #get_absolute_position def get_relative_position @relative_position end # Set vertical alignment of this shape inside its parent # @param [VALIGN] val Alignment type # @see VALIGN def set_v_align(val) @v_align = val end alias :v_align= :set_v_align # Get vertical alignment of this shape inside its parent # @return [VALIGN] Alignment type # @see VALIGN def get_v_align @v_align end alias :v_align :get_v_align # Set horizontal alignment of this shape inside its parent # @param [HALIGN] val Horizontal type # @see HALIGN def set_h_align(val) @h_align = val end alias :h_align= :set_h_align # Get horizontal alignment of this shape inside its parent # @return [HALIGN] Alignment type # @see HALIGN def get_h_align @h_align end alias :h_align :get_h_align # Set vertical border between this shape and its parent (if vertical # alignment is set). # @param [Float] border Vertical border # @see #set_v_align def set_v_border(border) @v_border = border end alias :v_border= :set_v_border # Get vertical border between this shape and its parent (if vertical # alignment is set). # @return [Float] Vertical border # @see #set_v_align def get_v_border @v_border end alias :v_border :get_v_border # Set horizontal border between this shape and its parent (if horizontal # alignment is set). # @param [Float] border Horizontal border # @see #set_h_align def set_h_border(border) @h_border = border end alias :h_border= :set_h_border # Get horizontal border between this shape and its parent (if horizontal # alignment is set). # @return [Float] Vertical border # @see #set_h_align def get_h_border @h_border end alias :h_border :get_h_border # Set custom dock point used if the shape is child shape of a line shape. # @param [Integer] dp Custom dock point def set_custom_dock_point(dp) @custom_dock_point = dp end alias :custom_dock_point= :set_custom_dock_point # Get custom dock point used if the shape is child shape of a line shape. # @return [Integer] Custom dock point def get_custom_dock_point @custom_dock_point end alias :custom_dock_point :get_custom_dock_point # Determine whether this shape is ancestor of given child shape. # @param [Wx::SF::Shape] child child shape. # @return true if this shape is parent of given child shape, otherwise false def ancestor?(child) @child_shapes.include?(child) || @child_shapes.any? { |c| c.ancestor?(child) } end # Determine whether this shape is descendant of given parent shape. # @param [Wx::SF::Shape] parent parent shape # @return true if this shape is a child of given parent shape, otherwise false def descendant?(parent) parent && parent.ancestor?(self) end # Associate user data with the shape. # If the data object is properly set then its marked properties will be serialized # together with the parent shape. This means the user data must either be a serializable # core type or a FIRM::Serializable. # @param [Object] data user data def set_user_data(data) @user_data = data end alias :user_data= :set_user_data # Get associated user data. # @return [Object,nil] user data def get_user_data @user_data end alias :user_data :get_user_data # Get shape's diagram canvas # @return [Wx::SF::ShapeCanvas,nil] shape canvas if assigned via diagram, otherwise nil # @see Wx::SF::Diagram def get_shape_canvas return nil unless @diagram @diagram.shape_canvas end alias :shape_canvas :get_shape_canvas # Get the shape's visibility status # @return [Boolean] true if the shape is visible, otherwise false def visible? @visible end alias :visibility :visible? # Show/hide shape # @param [Boolean] show Set the parameter to true if the shape should be visible, otherwise use false def show(show) @visible = show end alias :set_visibility :show # Set shape's hover color # @param [Wx::Colour,String,Symbol] col Hover color def set_hover_colour(col) @hover_color = Wx::Colour === col ? col : Wx::Colour.new(col) end alias :hover_colour= :set_hover_colour # Get shape's hover color # @return [Wx::Colour] Current hover color def get_hover_colour @hover_color || (@diagram&.shape_canvas ? @diagram.shape_canvas.hover_colour : DEFAULT.hover_colour) end alias :hover_colour :get_hover_colour # Function returns value of a shape's activation flag. # Non-active shapes are visible, but don't receive (process) any events. # @return [Boolean] true if the shape is active, otherwise false def active? @active end alias :active :active? # Shape's activation/deactivation # Deactivated shapes are visible, but don't receive (process) any events. # @param [Boolean] active true for activation, false for deactivation # @see #show def activate(active) @active = active end alias :set_active :activate # Tells whether the given shape type is accepted by this shape (it means # whether this shape can be its parent). # # The function is typically used by the framework for determination whether a dropped # shape can be assigned to an underlying shape as its child. # @param [Class] type Class of examined shape object # @return [Boolean] true if the shape type is accepted, otherwise false. def is_child_accepted(type) @accepted_children.include?(type) || @accepted_children.include?(ACCEPT_ALL) end alias :child_accepted? :is_child_accepted # Returns true if *all* currently dragged shapes can be accepted # as children of this shape. # @return [Boolean] # @see #is_shape_accepted def accept_currently_dragged_shapes return false unless get_shape_canvas unless @accepted_children.include?(ACCEPT_ALL) lst_selection = get_shape_canvas.get_selected_shapes return false if lst_selection.any? { |shape| !@accepted_children.include?(shape.class) } end true end # Add given shape type to an acceptance list. The acceptance list contains class # names of the shapes which can be accepted as children of this shape. # Note: Constant value {Wx::SF::ACCEPT_ALL} behaves like any class. # @param [Class] type Class of accepted shape object # @see #is_child_accepted def accept_child(type) ::Kernel.raise ArgumentError, 'Class or ACCEPT_ALL expected' unless type.is_a?(::Class) @accepted_children << type end # Get shape types acceptance list. # @return [Set] String set with class names of accepted shape types. # @see #is_child_accepted def get_accepted_children @accepted_children end alias :accepted_children :get_accepted_children # Tells whether the shape does not accept ANY children # @return [Boolean] true if no children accepted, false otherwise def does_not_accept_children? @accepted_children.empty? end alias :no_children_accepted? :does_not_accept_children? alias :accepts_no_children? :does_not_accept_children? # Tells whether the given connection type is accepted by this shape (it means # whether this shape can be connected to another one by a connection of given type). # # The function is typically used by the framework during interactive connection creation. # @param [Class] type Class of examined connection object # @return true if the connection type is accepted, otherwise false. def is_connection_accepted(type) @accepted_connections.include?(type) || @accepted_connections.include?(ACCEPT_ALL) end alias :connection_accepted? :is_connection_accepted # Add given connection type to an acceptance list. The acceptance list contains class # names of the connection which can be accepted by this shape. # Note: Constant value {Wx::SF::ACCEPT_ALL} behaves like any class. # @param [Class] type Class of accepted connection object # @see #is_connection_accepted def accept_connection(type) ::Kernel.raise ArgumentError, 'Class or ACCEPT_ALL expected' unless type.is_a?(::Class) @accepted_connections << type end # Get connection types acceptance list. # @return [Set] String set with class names of accepted connection types. # @see #is_connection_accepted def get_accepted_connections @accepted_connections end alias :accepted_connections :get_accepted_connections # Tells whether the given shape type is accepted by this shape as its source neighbour(it means # whether this shape can be connected from another one of given type). # # The function is typically used by the framework during interactive connection creation. # @param [Class] type Class of examined connection object # @return true if the shape type is accepted, otherwise false. def is_src_neighbour_accepted(type) @accepted_src_neighbours.include?(type) || @accepted_src_neighbours.include?(ACCEPT_ALL) end alias :src_neighbour_accepted? :is_src_neighbour_accepted # Add given shape type to an source neighbours' acceptance list. The acceptance list contains class # names of the shape types which can be accepted by this shape as its source neighbour. # Note: Constant value {Wx::SF::ACCEPT_ALL} behaves like any class. # @param [Class] type Class of accepted connection object # @see #is_src_neighbour_accepted def accept_src_neighbour(type) ::Kernel.raise ArgumentError, 'Class or ACCEPT_ALL expected' unless type.is_a?(::Class) @accepted_src_neighbours << type end # Get source neighbour types acceptance list. # @return [Set] String set with class names of accepted source neighbours types. # @see #is_src_neighbour_accepted def get_accepted_src_neighbours @accepted_src_neighbours end alias :accepted_src_neighbours :get_accepted_src_neighbours # Tells whether the given shape type is accepted by this shape as its target neighbour(it means # whether this shape can be connected to another one of given type). # # The function is typically used by the framework during interactive connection creation. # @param [Class] type Class of examined connection object # @return [Boolean] true if the shape type is accepted, otherwise false. def is_trg_neighbour_accepted(type) @accepted_trg_neighbours.include?(type) || @accepted_trg_neighbours.include?(ACCEPT_ALL) end alias :trg_neighbour_accepted? :is_trg_neighbour_accepted # Add given shape type to an target neighbours' acceptance list. The acceptance list contains class # names of the shape types which can be accepted by this shape as its target neighbour. # Note: Constant value {Wx::SF::ACCEPT_ALL} behaves like any class. # @param [Class] type Class of accepted connection object # @see #is_trg_neighbour_accepted def accept_trg_neighbour(type) ::Kernel.raise ArgumentError, 'Class or ACCEPT_ALL expected' unless type.is_a?(::Class) @accepted_trg_neighbours << type end # Get target neighbour types acceptance list. # @return [Set] String set with class names of accepted target neighbours types. # @see #is_trg_neighbour_accepted def get_accepted_trg_neighbours @accepted_trg_neighbours end alias :accepted_trg_neighbours :get_accepted_trg_neighbours # Clear shape object acceptance list # @see #accept_child def clear_accepted_childs @accepted_children.clear end # Clear connection object acceptance list # @see #accept_connection def clear_accepted_connections @accepted_connections.clear end # Clear source neighbour objects acceptance list # @see #accept_src_neighbour def clear_accepted_src_neighbours @accepted_src_neighbours.clear end # Clear target neighbour objects acceptance list # @see #accept_trg_neighbour def clear_accepted_trg_neighbours @accepted_trg_neighbours.clear end # Get list of currently assigned shape handles. # @return [Array] handle list def get_handles @handles end alias :handles :get_handles # Get shape handle. # @param [Wx::SF::Shape::Handle::TYPE] type Handle type # @param [Integer] id Handle ID (useful only for line control points) # @return [Wx::SF::Shape::Handle,nil] shape handle object if exist # @see Wx::SF::Shape::Handle def get_handle(type, id = -1) @handles.find { |h| h.type == type && (id == -1 || h.id == id) } end alias :handle :get_handle # Add new handle to the shape. # # The function creates new instance of shape handle (if it doesn't exist yet) # and inserts it into handle list. # @param [Wx::SF::Shape::Handle::TYPE] type Handle type # @param [Integer] id Handle ID (useful only for line control points) # @see Wx::SF::Shape::Handle def add_handle(type, id = -1) unless get_handle(type, id) @handles << Handle.new(self, type, id) end end # Remove given shape handle (if exists). # @param [Wx::SF::Shape::Handle::TYPE] type Handle type # @param [Integer] id Handle ID (useful only for line control points) # @see Wx::SF::Shape::Handle def remove_handle(type, id = -1) @handles.delete_if { |h| h.type == type && (id == -1 || h.id == id) } end # Get reference to connection points list. # @return [Array] connection points list def get_connection_points @connection_pts end alias :connection_points :get_connection_points # Get connection point of given type assigned to the shape. # @param [Wx::SF::ConnectionPoint::CPTYPE] type Connection point type # @param [Integer, nil] id Optional connection point ID # @return [Wx::SF::ConnectionPoint,nil] connection point if exists, otherwise nil # @see Wx::SF::ConnectionPoint::CPTYPE def get_connection_point(type, id = nil) @connection_pts.find { |cp| cp.type == type && cp.id == id } end alias :connection_point :get_connection_point # Get connection point closest to the given position. # @param [Wx::RealPoint] pos Position # @return [Wx::SF::ConnectionPoint,nil] closest connection point if exists, otherwise nil def get_nearest_connection_point(pos) pos = pos.to_real_point min_dist = Float::MAX @connection_pts.inject(nil) do |nearest, cp| if (curr_dist = pos.distance_to(cp.get_connection_point)) < min_dist min_dist = curr_dist nearest = cp end nearest end end alias :nearest_connection_point :get_nearest_connection_point # Assign connection point of given type to the shape. # @overload add_connection_point(type, persistent: true) # @param [Wx::SF::ConnectionPoint::CPTYPE] type Connection point type # @param [Boolean] persistent true if the connection point should be serialized # @return [Wx::SF::ConnectionPoint, nil] new connection point if succeeded, otherwise nil # @overload add_connection_point(relpos, id=-1, persistent: true) # @param [Wx::RealPoint] relpos Relative position in percentages # @param [Integer] id connection point ID # @param [Boolean] persistent true if the connection point should be serialized # @return [Wx::SF::ConnectionPoint, nil] new connection point if succeeded, otherwise nil # @overload add_connection_point(cp, persistent: true) # @param [Wx::SF::ConnectionPoint] cp connection point (shape will take the ownership) # @param [Boolean] persistent true if the connection point should be serialized # @return [Wx::SF::ConnectionPoint, nil] added connection point if succeeded, otherwise nil # @see Wx::SF::ConnectionPoint::CPTYPE def add_connection_point(arg, *rest, persistent: true) cp = nil case arg when ConnectionPoint::CPTYPE unless get_connection_point(arg) cp = ConnectionPoint.new(self, arg) cp.disable_serialize unless persistent end when Wx::RealPoint, ::Array cp = ConnectionPoint.new(self, arg.to_real_point, *rest) cp.disable_serialize unless persistent when ConnectionPoint cp = arg cp.disable_serialize unless persistent else ::Kernel.raise ArgumentError, "Invalid arguments: arg: #{arg}, rest: #{rest}" end @connection_pts << cp if cp cp end # Remove connection point of given type from the shape (if present). # @param [Wx::SF::ConnectionPoint::CPTYPE] type Connection point type # @see Wx::SF::ConnectionPoint::CPTYPE def remove_connection_point(type) @connection_pts.delete_if { |cp| cp.type == type } end # Event handler called when the shape is clicked by # the left mouse button. The function can be overridden if necessary. # # The function is called by the framework (by the shape canvas). # Default implementation emits Wx::SF::EVT_SF_SHAPE_LEFT_DOWN event. # @param [Wx::Point] pos Current mouse position # @see Wx::SF::ShapeCanvas def on_left_click(pos) # HINT: overload it for custom actions... if has_style?(STYLE::EMIT_EVENTS) && get_shape_canvas evt = Wx::SF::ShapeMouseEvent.new(Wx::SF::EVT_SF_SHAPE_LEFT_DOWN, self.object_id) evt.set_shape(self) evt.set_mouse_position(pos) get_shape_canvas.get_event_handler.process_event(evt) end end # Event handler called when the shape is clicked by # the right mouse button. The function can be overridden if necessary. # # The function is called by the framework (by the shape canvas). # Default implementation emits Wx::SF::EVT_SF_SHAPE_RIGHT_DOWN event. # @param [Wx::Point] pos Current mouse position # @see Wx::SF::ShapeCanvas def on_right_click(pos) # HINT: overload it for custom actions... if has_style?(STYLE::EMIT_EVENTS) && get_shape_canvas evt = Wx::SF::ShapeMouseEvent.new(Wx::SF::EVT_SF_SHAPE_RIGHT_DOWN, self.object_id) evt.set_shape(self) evt.set_mouse_position(pos) get_shape_canvas.get_event_handler.process_event(evt) end end # Event handler called when the shape is double-clicked by # the left mouse button. The function can be overridden if necessary. # # The function is called by the framework (by the shape canvas). # Default implementation emits Wx::SF::EVT_SF_SHAPE_LEFT_DCLICK event. # @param [Wx::Point] pos Current mouse position # @see Wx::SF::ShapeCanvas def on_left_double_click(pos) # HINT: overload it for custom actions... if has_style?(STYLE::EMIT_EVENTS) && get_shape_canvas evt = Wx::SF::ShapeMouseEvent.new(Wx::SF::EVT_SF_SHAPE_LEFT_DCLICK, self.object_id) evt.set_shape(self) evt.set_mouse_position(pos) get_shape_canvas.get_event_handler.process_event(evt) end end # Event handler called when the shape is double-clicked by # the right mouse button. The function can be overridden if necessary. # # The function is called by the framework (by the shape canvas). # Default implementation emits Wx::SF::EVT_SF_SHAPE_RIGHT_DCLICK event. # @param [Wx::Point] pos Current mouse position # @see Wx::SF::ShapeCanvas def on_right_double_click(pos) # HINT: overload it for custom actions... if has_style?(STYLE::EMIT_EVENTS) && get_shape_canvas evt = Wx::SF::ShapeMouseEvent.new(Wx::SF::EVT_SF_SHAPE_RIGHT_DCLICK, self.object_id) evt.set_shape(self) evt.set_mouse_position(pos) get_shape_canvas.get_event_handler.process_event(evt) end end # Event handler called at the beginning of the shape dragging process. # The function can be overridden if necessary. # # The function is called by the framework (by the shape canvas). # Default implementation emits Wx::SF::EVT_SF_SHAPE_DRAG_BEGIN event. # @param [Wx::Point] pos Current mouse position # @see Wx::SF::ShapeCanvas def on_begin_drag(pos) # HINT: overload it for custom actions... if has_style?(STYLE::EMIT_EVENTS) && get_shape_canvas evt = Wx::SF::ShapeMouseEvent.new(Wx::SF::EVT_SF_SHAPE_DRAG_BEGIN, self.object_id) evt.set_shape(self) evt.set_mouse_position(pos) get_shape_canvas.get_event_handler.process_event(evt) end end # Event handler called during the shape dragging process. # The function can be overridden if necessary. # # The function is called by the framework (by the shape canvas). # Default implementation emits Wx::SF::EVT_SF_SHAPE_DRAG event. # @param [Wx::Point] pos Current mouse position # @see Wx::SF::ShapeCanvas def on_dragging(pos) # HINT: overload it for custom actions... if has_style?(STYLE::EMIT_EVENTS) && get_shape_canvas evt = Wx::SF::ShapeMouseEvent.new(Wx::SF::EVT_SF_SHAPE_DRAG, self.object_id) evt.set_shape(self) evt.set_mouse_position(pos) get_shape_canvas.get_event_handler.process_event(evt) end end # Event handler called at the end of the shape dragging process. # The function can be overridden if necessary. # # The function is called by the framework (by the shape canvas). # Default implementation emits Wx::SF::EVT_SF_SHAPE_DRAG_END event. # @param [Wx::Point] pos Current mouse position # @see Wx::SF::ShapeCanvas def on_end_drag(pos) # HINT: overload it for custom actions... if has_style?(STYLE::EMIT_EVENTS) && get_shape_canvas evt = Wx::SF::ShapeMouseEvent.new(Wx::SF::EVT_SF_SHAPE_DRAG_END, self.object_id) evt.set_shape(self) evt.set_mouse_position(pos) get_shape_canvas.get_event_handler.process_event(evt) end end # Event handler called when the user started to drag the shape handle. # The function can be overridden if necessary. # # The function is called by the framework (by the shape canvas). # Default implementation emits Wx::SF::EVT_SF_SHAPE_HANDLE_BEGIN event. # @param [Wx::SF::Shape::Handle] handle dragged handle def on_begin_handle(handle) # HINT: overload it for custom actions... if has_style?(STYLE::EMIT_EVENTS) && get_shape_canvas evt = Wx::SF::ShapeHandleEvent.new(Wx::SF::EVT_SF_SHAPE_HANDLE_BEGIN, self.object_id) evt.set_shape(self) evt.set_handle(handle) get_shape_canvas.get_event_handler.process_event(evt) end end # Event handler called during dragging of the shape handle. # The function can be overridden if necessary. # # The function is called by the framework (by the shape canvas). # Default implementation emits Wx::SF::EVT_SF_SHAPE_HANDLE event. # @param [Wx::SF::Shape::Handle] handle dragged handle def on_handle(handle) # HINT: overload it for custom actions... if has_style?(STYLE::EMIT_EVENTS) && get_shape_canvas evt = Wx::SF::ShapeHandleEvent.new(Wx::SF::EVT_SF_SHAPE_HANDLE, self.object_id) evt.set_shape(self) evt.set_handle(handle) get_shape_canvas.get_event_handler.process_event(evt) end end # Event handler called when the user finished dragging of the shape handle. # The function can be overridden if necessary. # # The function is called by the framework (by the shape canvas). # Default implementation emits Wx::SF::EVT_SF_SHAPE_HANDLE_END event. # @param [Wx::SF::Shape::Handle] handle dragged handle def on_end_handle(handle) # HINT: overload it for custom actions... if has_style?(STYLE::EMIT_EVENTS) && get_shape_canvas evt = Wx::SF::ShapeHandleEvent.new(Wx::SF::EVT_SF_SHAPE_HANDLE_END, self.object_id) evt.set_shape(self) evt.set_handle(handle) get_shape_canvas.get_event_handler.process_event(evt) end end # Event handler called when a mouse pointer enters the shape area. # The function can be overridden if necessary. # # The function is called by the framework (by the shape canvas). # Default implementation emits Wx::SF::EVT_SF_SHAPE_MOUSE_ENTER event. # @param [Wx::Point] pos Current mouse position def on_mouse_enter(pos) # HINT: overload it for custom actions... if has_style?(STYLE::EMIT_EVENTS) && get_shape_canvas evt = Wx::SF::ShapeMouseEvent.new(Wx::SF::EVT_SF_SHAPE_MOUSE_ENTER, self.object_id) evt.set_shape(self) evt.set_mouse_position(pos) get_shape_canvas.get_event_handler.process_event(evt) end end # Event handler called when a mouse pointer moves above the shape area. # The function can be overridden if necessary. # # The function is called by the framework (by the shape canvas). # Default implementation emits Wx::SF::EVT_SF_SHAPE_MOUSE_OVER event. # @param [Wx::Point] pos Current mouse position def on_mouse_over(pos) # HINT: overload it for custom actions... if has_style?(STYLE::EMIT_EVENTS) && get_shape_canvas evt = Wx::SF::ShapeMouseEvent.new(Wx::SF::EVT_SF_SHAPE_MOUSE_OVER, self.object_id) evt.set_shape(self) evt.set_mouse_position(pos) get_shape_canvas.get_event_handler.process_event(evt) end end # Event handler called when a mouse pointer leaves the shape area. # The function can be overridden if necessary. # # The function is called by the framework (by the shape canvas). # Default implementation emits Wx::SF::EVT_SF_SHAPE_MOUSE_LEAVE event. # @param [Wx::Point] pos Current mouse position def on_mouse_leave(pos) # HINT: overload it for custom actions... if has_style?(STYLE::EMIT_EVENTS) && get_shape_canvas evt = Wx::SF::ShapeMouseEvent.new(Wx::SF::EVT_SF_SHAPE_MOUSE_LEAVE, self.object_id) evt.set_shape(self) evt.set_mouse_position(pos) get_shape_canvas.get_event_handler.process_event(evt) end end # Event handler called when any key is pressed (in the shape canvas). # The function can be overridden if necessary. # # The function is called by the framework (by the shape canvas). # Default implementation emits Wx::SF::EVT_SF_SHAPE_KEYDOWN event. # @param [Integer] key The key code # @return The function must return true if the default event routine should be called # as well, otherwise false # @see Wx::SF::Shape::_on_key def on_key(key) # HINT: overload it for custom actions... if has_style?(STYLE::EMIT_EVENTS) && get_shape_canvas evt = Wx::SF::ShapeKeyEvent.new(Wx::SF::EVT_SF_SHAPE_KEYDOWN, self.object_id) evt.set_shape(self) evt.set_key_code(key) get_shape_canvas.get_event_handler.process_event(evt) end true end # Event handler called when any shape is dropped above this shape (and the dropped # shape is accepted as a child of this shape). The function can be overridden if necessary. # # The function is called by the framework (by the shape canvas). # Default implementation emits Wx::SF::EVT_SF_SHAPE_CHILD_DROP event. # @param [Wx::RealPoint] _pos Relative position of dropped shape # @param [Wx::SF::Shape] child dropped shape def on_child_dropped(_pos, child) # HINT: overload it for custom actions... if has_style?(STYLE::EMIT_EVENTS) && get_shape_canvas evt = Wx::SF::ShapeChildDropEvent.new(Wx::SF::EVT_SF_SHAPE_CHILD_DROP, self.object_id) evt.set_shape(self) evt.set_child_shape(child) get_shape_canvas.get_event_handler.process_event(evt) end end def to_s "#<#{self.class}:#{self.object_id}#{@parent_shape ? " parent=#{@parent_shape.object_id}" : ''}>" end def inspect to_s end protected # called after the shape has been newly imported/pasted/dropped # allows for checking stale links # by default does nothing def on_import # nothing end # Draw the shape in the normal way. The function can be overridden if necessary. # @param [Wx::DC] _dc Reference to device context where the shape will be drawn to def draw_normal(_dc) # HINT: overload it for custom actions... end # Draw the shape in the selected way. The function can be overridden if necessary. # @param [Wx::DC] dc Reference to device context where the shape will be drawn to def draw_selected(dc) # HINT: overload it for custom actions... if has_style?(STYLE::SHOW_HANDLES) @handles.each { |h| h.send(:draw, dc) } end end # Draw the shape in the hover mode (the mouse cursor is above the shape). # The function can be overridden if necessary. # @param [Wx::DC] _dc Reference to device context where the shape will be drawn to def draw_hover(_dc) # HINT: overload it for custom actions... end # Draw the shape in the highlighted mode (another shape is dragged over this # shape and this shape will accept the dragged one if it will be dropped on it). # The function can be overridden if necessary. # @param [Wx::DC] _dc Reference to device context where the shape will be drawn to def draw_highlighted(_dc) # HINT: overload it for custom actions... end # Draw shadow under the shape. The function can be overridden if necessary. # @param [Wx::DC] _dc Reference to device context where the shadow will be drawn to def draw_shadow(_dc) # HINT: overload it for custom actions... end # Repaint the shape # @param [Wx::Rect] rct Canvas portion that should be updated # @param [Boolean] delayed If true then the shape canvas will be rather invalidated than refreshed. # @see Wx::SF::ShapeCanvas#invalidate_rect # @see Wx::SF::ShapeCanvas#refresh_invalidated_rect def refresh_rect(rct, delayed = false) if get_shape_canvas if delayed get_shape_canvas.invalidate_rect(rct) else get_shape_canvas.refresh_canvas(false, rct) end end end # Get absolute position of the shape parent. # @return [Wx::RealPoint] Absolute position of the shape parent if exists, otherwise 0,0 def get_parent_absolute_position if @parent_shape if @parent_shape.is_a?(Wx::SF::LineShape) && @custom_dock_point != DEFAULT::DOCK_POINT return @parent_shape.get_dock_point_position(@custom_dock_point) else return @parent_shape.get_absolute_position end end Wx::RealPoint.new(0, 0) end private # Auxiliary function called by GetNeighbours function. # @param [Class,nil] shape_info Line object type # @param [CONNECTMODE] condir Connection direction # @param [Boolean] direct Set this flag to TRUE if only closest shapes should be found, # otherwise also shapes connected by forked lines will be found (also # constants DIRECT and INDIRECT can be used) # @param [Array 1) && lst_selection.include?(self) f_refresh_all = true end prev_bb = nil unless f_refresh_all prev_bb = get_complete_bounding_box(prev_bb, BBMODE::SELF | BBMODE::CONNECTIONS | BBMODE::CHILDREN | BBMODE::SHADOW) end if on_key(key) case key when Wx::K_LEFT move_by(-dx, 0) if has_style?(STYLE::POSITION_CHANGE) when Wx::K_RIGHT move_by(dx, 0) if has_style?(STYLE::POSITION_CHANGE) when Wx::K_UP move_by(0, -dy) if has_style?(STYLE::POSITION_CHANGE) when Wx::K_DOWN move_by(0, dy) if has_style?(STYLE::POSITION_CHANGE) end end if !f_refresh_all curr_bb = get_complete_bounding_box(prev_bb, BBMODE::SELF | BBMODE::CONNECTIONS | BBMODE::CHILDREN | BBMODE::SHADOW) refresh_rect(curr_bb, DELAYED) else canvas.refresh(false) end end end # Original protected event handler called during dragging of the shape handle. # The function is called by the framework (by the shape canvas). # Default implementation manages the child shapes' alignment (if set). # @param [Wx::SF::Shape::Handle] handle dragged handle def _on_handle(handle) return unless @diagram if @parent_shape prev_bb = get_grand_parent_shape.get_complete_bounding_box(nil) else prev_bb = get_complete_bounding_box(nil) end # call appropriate user-defined handler on_handle(handle) # align children @child_shapes.each do |child| if child.get_v_align != VALIGN::NONE || child.get_h_align != HALIGN::NONE child.do_alignment end end # update shape update if @parent_shape curr_bb = get_grand_parent_shape.get_complete_bounding_box(nil) else curr_bb = get_complete_bounding_box(nil) end # refresh shape refresh_rect(curr_bb.union!(prev_bb), DELAYED) end # Event handler called by ShapeCanvas to request,report canvas changes. # Default implementation does nothing. # @param [ShapeCanvas::CHANGE] _change change type indicator # @param [Array] _args any additional arguments # @return [Boolean] def _on_canvas(_change, *_args) # overridden in some derived shapes true end # Sets accepted children. Exclusively for deserialization. def set_accepted_children(set) @accepted_children.replace(set.collect { |e| e.is_a?(::String) ? ::Object.const_get(e) : e }) end # Sets accepted connection. Exclusively for deserialization. def set_accepted_connections(set) @accepted_connections.replace(set.collect { |e| e.is_a?(::String) ? ::Object.const_get(e) : e }) end # Sets accepted src neighbours. Exclusively for deserialization. def set_accepted_src_neighbours(set) @accepted_src_neighbours.replace(set.collect { |e| e.is_a?(::String) ? ::Object.const_get(e) : e }) end # Sets accepted trg neighbours. Exclusively for deserialization. def set_accepted_trg_neighbours(set) @accepted_trg_neighbours.replace(set.collect { |e| e.is_a?(::String) ? ::Object.const_get(e) : e }) end # Sets connection points. Exclusively for deserialization. def set_connection_points(list) @connection_pts.replace(list) @connection_pts.each { |cp| cp.parent_shape = self } end def update_child_parents @child_shapes.each do |shape| shape.instance_variable_set(:@parent_shape, self) end end # (de-)serialize child shapes. Exclusively for deserialization. def serialize_child_shapes(*val) unless val.empty? @child_shapes = val.first # @parent_shape is not serialized, instead we rely on child shapes being (de-)serialized # by their parent (child shapes restored before restoring parent child list) and let # the parent reset the @parent_shape attributes of their children. # That way the links never get out of sync. update_child_parents end @child_shapes end # (de-)serialize hover colour; allows for nil values def serialize_hover_colour(*val) @hover_colour = val.first unless val.empty? @hover_colour end public # Returns intersection point of two lines (if any) # @param [Wx::RealPoint] from1 # @param [Wx::RealPoint] to1 # @param [Wx::RealPoint] from2 # @param [Wx::RealPoint] to2 # @return [Wx::RealPoint,nil] intersection point or nil def self.lines_intersection(from1, to1, from2, to2) # create line 1 info a1 = to1.y - from1.y b1 = from1.x - to1.x c1 = -a1*from1.x - b1*from1.y # create line 2 info a2 = to2.y - from2.y b2 = from2.x - to2.x c2 = -a2*from2.x - b2*from2.y # check, whether the lines are parallel... ka = a1 / a2 kb = b1 / b2 return nil if ka == kb xi = (b1*c2 - c1*b2) / (a1*b2 - a2*b1) yi = -(a1*c2 - a2*c1) / (a1*b2 - a2*b1) if ((from1.x - xi) * (xi - to1.x) >= 0.0) && ((from2.x - xi) * (xi - to2.x) >= 0.0) && ((from1.y - yi) * (yi - to1.y) >= 0.0) && ((from2.y - yi) * (yi - to2.y) >= 0.0) return Wx::RealPoint.new(xi, yi) end nil end # Allow shapes to call class method as instance method. def lines_intersection(*args) Shape.lines_intersection(*args) end end # class Shape end # module Wx::SF require 'wx/shapes/shape_handle' Dir[File.join(__dir__, 'shapes', '*.rb')].each do |f| require "wx/shapes/shapes/#{File.basename(f, '.rb')}" end