lib/wx/shapes/shape.rb in wxruby3-shapes-0.9.0.pre.beta.3 vs lib/wx/shapes/shape.rb in wxruby3-shapes-0.9.5

- old
+ new

@@ -1,9 +1,10 @@ # 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 @@ -27,19 +28,20 @@ # 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 Serializable + include FIRM::Serializable - property :id, :active, :visibility, :style, + property :active, :visibility, :style, :accepted_children, :accepted_connections, :accepted_src_neighbours, :accepted_trg_neighbours, - :hover_colour, :relative_position, + :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) @@ -100,12 +102,14 @@ 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) - # User data is destroyed at the shape deletion - DELETE_USER_DATA = self.new(64) + # 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 @@ -122,25 +126,30 @@ 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 hovering to parent. + # 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 | DELETE_USER_DATA + 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 @hoverColor data member - HOVERCOLOUR = Wx::Colour.new(120, 120, 255) if Wx::App.is_main_loop_running - Wx.add_delayed_constant(self, :HOVERCOLOUR) { Wx::Colour.new(120, 120, 255) } # 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 @@ -170,11 +179,11 @@ # for setters and <code>"#{comp_id}()"</code> or <code>"get_#{comp_id}"</code> 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 - # property( + # 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| @@ -225,39 +234,28 @@ self end protected :from_serialized end include ComponentSerializerMethods - __CODE + __CODE end end - # @overload initialize() - # default constructor - # @overload initialize(pos, manager) - # @param [Wx::RealPoint] pos Initial relative position - # @param [Diagram] diagram containing diagram - def initialize(*args) - pos, diagram = args + # 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 - args.empty? || (Wx::RealPoint === pos && (diagram.nil? || Wx::SF::Diagram === diagram)) + Wx::RealPoint === pos && (diagram.nil? || Wx::SF::Diagram === diagram) - @id = Serializable::ID.new @diagram = diagram @parent_shape = nil @child_shapes = ShapeList.new @components = ::Set.new - if @diagram - if @diagram.shape_canvas - @hover_color = @diagram.shape_canvas.hover_colour - else - @hover_color = DEFAULT::HOVERCOLOUR; - end - else - @hover_color = DEFAULT::HOVERCOLOUR; - end + # by default the common canvas hover colour will be used + @hover_color = nil @selected = false @mouse_over = false @first_move = false @highlight_parent = false @@ -271,39 +269,21 @@ @h_align = DEFAULT::HALIGN @v_border = DEFAULT::VBORDER @h_border = DEFAULT::HBORDER @custom_dock_point = DEFAULT::DOCK_POINT - if pos && get_parent_shape - @relative_position = pos.to_real_point - get_parent_absolute_position - else - @relative_position = DEFAULT::POSITION.dup - end + @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 - # Get the shape's id - # @return [Wx::SF::Serializable::ID] - def get_id - @id - end - alias :id :get_id - - # Set the shape's id. Deserialization only. - # @param [Wx::SF::Serializable::ID] id - def set_id(id) - @id = id - end - private :set_id - # Set managing diagram # @param [Wx::SF::Diagram] diagram def set_diagram(diagram) if @diagram != diagram @diagram = diagram @@ -341,28 +321,41 @@ # 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, shape) + 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 @@ -381,11 +374,11 @@ @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 rather invalidated than refreshed. + # @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 @@ -398,30 +391,34 @@ # @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 - # draw the shape shadow if required - draw_shadow(dc) if !@selected && has_style?(STYLE::SHOW_SHADOW) + unless has_style?(STYLE::NOT_DRAWN) - # first, draw itself - if @mouse_over && (@highlight_parent || has_style?(STYLE::HOVERING)) - if @highlight_parent - draw_highlighted(dc) - @highlight_parent = false + # 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_hover(dc) + draw_normal(dc) end - else - draw_normal(dc) - end - draw_selected(dc) if @selected + draw_selected(dc) if @selected - # ... then draw connection points ... - @connection_pts.each { |cpt| cpt.draw(dc) } + # ... 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 @@ -458,15 +455,14 @@ # 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... - parent_shape = get_parent_shape - if parent_shape + if @parent_shape @relative_position + get_parent_absolute_position else - @relative_position.dup + @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. @@ -499,11 +495,11 @@ @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 | STYLE::DELETE_USER_DATA + # 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 @@ -521,11 +517,11 @@ end def remove_style(style) @style &= ~style end def contains_style(style) - (@style & style) != 0 + @style.allbits?(style) end alias :contains_style? :contains_style alias :has_style? :contains_style # Find out whether this shape has some children. @@ -608,11 +604,11 @@ Wx::Rect.new end # Get shape's bounding box which includes also associated child shapes and connections. - # @param [Wx::Rect] rct bounding rectangle + # @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) @@ -687,21 +683,35 @@ @relative_position.y += y @diagram.set_modified(true) if @diagram end - # Update the shape's position in order to its alignment - def do_alignment - parent = get_parent_shape + # 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 - if parent && !parent.is_a?(Wx::SF::GridShape) + # 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 - if parent.is_a?(Wx::SF::LineShape) + + # 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.get_bounding_box + parent_bb = @parent_shape.get_bounding_box end shape_bb = get_bounding_box # do vertical alignment @@ -720,23 +730,23 @@ @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.is_a?(Wx::SF::LineShape) - line_start, line_end = parent.get_line_segment(0) + 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.is_a?(Wx::SF::LineShape) - line_start, line_end = parent.get_line_segment(parent.get_control_points.get_count) + 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 @@ -760,24 +770,24 @@ @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.is_a?(Wx::SF::LineShape) - line_start, line_end = parent.get_line_segment(0) + 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.is_a?(Wx::SF::LineShape) - line_start, line_end = parent.get_line_segment(parent.get_control_points.get_count) + 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 @@ -786,23 +796,23 @@ end end end # Update shape (align all child shapes and resize it to fit them) - def update + 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 (parent = get_parent_shape) - parent.update + 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 @@ -812,15 +822,21 @@ # 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 && (@style & STYLE::SHOW_HANDLES) != 0) + 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. @@ -838,11 +854,11 @@ # Get shape's relative position. # @return [Wx::RealPoint] Current relative position # @see #get_absolute_position def get_relative_position - @relative_position.dup + @relative_position end # Set vertical alignment of this shape inside its parent # @param [VALIGN] val Alignment type # @see VALIGN @@ -940,11 +956,11 @@ 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 Wx::SF::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 @@ -988,11 +1004,11 @@ alias :hover_colour= :set_hover_colour # Get shape's hover color # @return [Wx::Colour] Current hover color def get_hover_colour - @hover_color + @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. @@ -1028,25 +1044,25 @@ # @return [Boolean] # @see #is_shape_accepted def accept_currently_dragged_shapes return false unless get_shape_canvas - unless is_child_accepted(ACCEPT_ALL) + 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.name) } + 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) || type == ACCEPT_ALL + ::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>] String set with class names of accepted shape types. @@ -1054,10 +1070,18 @@ 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 @@ -1071,11 +1095,11 @@ # 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) || type == ACCEPT_ALL + ::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>] String set with class names of accepted connection types. @@ -1100,11 +1124,11 @@ # 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) || type == ACCEPT_ALL + ::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>] String set with class names of accepted source neighbours types. @@ -1129,11 +1153,11 @@ # 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) || type == ACCEPT_ALL + ::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>] String set with class names of accepted target neighbours types. @@ -1212,14 +1236,14 @@ 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] id Optional connection point ID + # @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 = -1) + 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. @@ -1240,20 +1264,20 @@ # 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] new connection point + # @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] new connection point + # @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] added connection point + # @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 @@ -1290,11 +1314,11 @@ # @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.id) + 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 @@ -1308,11 +1332,11 @@ # @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.id) + 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 @@ -1326,11 +1350,11 @@ # @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.id) + 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 @@ -1344,11 +1368,11 @@ # @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.id) + 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 @@ -1362,11 +1386,11 @@ # @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.id) + 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 @@ -1380,11 +1404,11 @@ # @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.id) + 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 @@ -1398,11 +1422,11 @@ # @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.id) + 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 @@ -1415,11 +1439,11 @@ # @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.id) + 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 @@ -1432,11 +1456,11 @@ # @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.id) + 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 @@ -1449,11 +1473,11 @@ # @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.id) + 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 @@ -1466,11 +1490,11 @@ # @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.id) + 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 @@ -1483,11 +1507,11 @@ # @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.id) + 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 @@ -1500,11 +1524,11 @@ # @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.id) + 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 @@ -1520,11 +1544,11 @@ # @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.id) + 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 @@ -1540,27 +1564,34 @@ # @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.id) + 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}:#{id.to_i}#{parent_shape ? " parent=#{parent_shape.id.to_i}" : ''}>" + "#<#{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 @@ -1612,25 +1643,25 @@ 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 = get_parent_shape - if parent.is_a?(Wx::SF::LineShape) && @custom_dock_point != DEFAULT::DOCK_POINT - return parent.get_dock_point_position(@custom_dock_point) + 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.get_absolute_position + return @parent_shape.get_absolute_position end end Wx::RealPoint.new(0, 0) end private # Auxiliary function called by GetNeighbours function. - # @param [Class,nil] shapeInfo Line object type + # @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<Wx::SF::Shape] neighbours List to add neighbour shapes to @@ -1647,20 +1678,20 @@ # find opposite shapes in direct branches lst_connections.each do |line| case condir when CONNECTMODE::STARTING - opposite = @diagram.find_shape(line.get_trg_shape_id) + opposite = line.get_trg_shape when CONNECTMODE::ENDING - opposite = @diagram.find_shape(line.get_src_shape_id) + opposite = line.get_src_shape when CONNECTMODE::BOTH - if @id == line.get_src_shape_id - opposite = @diagram.find_shape(line.get_trg_shape_id) + if self == line.get_src_shape + opposite = line.get_trg_shape else - opposite = @diagram.find_shape(line.get_src_shape_id) + opposite = line.get_src_shape end end # add opposite shape to the list (if applicable) neighbours << opposite if opposite && !opposite.is_a?(Wx::SF::LineShape) && !neighbours.include?(opposite) @@ -1672,36 +1703,36 @@ processed << self if opposite.is_a?(Wx::SF::LineShape) case condir when CONNECTMODE::STARTING - opposite = @diagram.find_shape(opposite.get_src_shape_id) + opposite = opposite.get_src_shape if opposite.is_a?(Wx::SF::LineShape) opposite.__send__(:_get_neighbours, shape_info, condir, direct, neighbours, processed) elsif !neighbours.include?(opposite) neighbours << opposite end when CONNECTMODE::ENDING - opposite = @diagram.find_shape(opposite.get_trg_shape_id) + opposite = opposite.get_trg_shape if opposite.is_a?(Wx::SF::LineShape) opposite.__send__(:_get_neighbours, shape_info, condir, direct, neighbours, processed) elsif !neighbours.include?(opposite) neighbours << opposite end when CONNECTMODE::BOTH - opposite = @diagram.find_shape(opposite.get_src_shape_id) + opposite = opposite.get_src_shape if opposite.is_a?(Wx::SF::LineShape) opposite.__send__(:_get_neighbours, shape_info, condir, direct, neighbours, processed) elsif !neighbours.include?(opposite) neighbours << opposite end - opposite = @diagram.find_shape(opposite.get_trg_shape_id) + opposite = opposite.get_trg_shape if opposite.is_a?(Wx::SF::LineShape) opposite.__send__(:_get_neighbours, shape_info, condir, direct, neighbours, processed) elsif !neighbours.include?(opposite) neighbours << opposite end @@ -1713,11 +1744,11 @@ end end end # Auxiliary function called by GetCompleteBoundingBox function. - # @param [Wx::Rect] rct bounding rectangle to update + # @param [Wx::Rect, nil] rct bounding rectangle to update # @param [BBMODE] mask Bit mask of object types which should be included into calculation # @param [Set<Wx::SF::Shape] processed set to keep track of processed shapes # @return [Wx::Rect] bounding rectangle # @see BBMODE def _get_complete_bounding_box(rct, mask = BBMODE::ALL, processed = ::Set.new) @@ -1725,18 +1756,22 @@ return rct if processed.include?(self) processed << self # first, get bounding box of the current shape - if (mask & BBMODE::SELF) != 0 - if rct.is_empty - rct.assign(get_bounding_box.inflate!(@h_border.abs.to_i, @v_border.abs.to_i)) + if mask.allbits?(BBMODE::SELF) + if rct.nil? + rct = get_bounding_box.inflate!(@h_border.abs.to_i, @v_border.abs.to_i) else - rct.union!(get_bounding_box.inflate!(@h_border.abs.to_i, @v_border.abs.to_i)) + if rct.empty? + rct += get_bounding_box.inflate!(@h_border.abs.to_i, @v_border.abs.to_i) + else + rct.union!(get_bounding_box.inflate!(@h_border.abs.to_i, @v_border.abs.to_i)) + end # add also shadow offset if necessary - if (mask & BBMODE::SHADOW) != 0 && has_style?(STYLE::SHOW_SHADOW) && get_parent_canvas + if mask.allbits?(BBMODE::SHADOW) && has_style?(STYLE::SHOW_SHADOW) && !has_style(STYLE::NOT_DRAWN) && get_parent_canvas n_offset = get_parent_canvas.get_shadow_offset if n_offset.x < 0 rct.set_x(rct.x + n_offset.x.to_i) rct.set_width(rct.width - n_offset.x.to_i) @@ -1756,11 +1791,11 @@ mask |= BBMODE::SELF end # get list of all connection lines assigned to the shape and find their child shapes lst_children = [] - if (mask & BBMODE::CONNECTIONS) != 0 + if mask.allbits?(BBMODE::CONNECTIONS) lst_lines = get_assigned_connections(Wx::SF::LineShape, CONNECTMODE::BOTH) lst_lines.each do |line| # rct.union!(line.get_bounding_box) lst_children << line @@ -1769,19 +1804,19 @@ line.get_child_shapes(ANY, NORECURSIVE, SEARCHMODE::BFS, lst_children) end end # get children of this shape - if (mask & BBMODE::CHILDREN) != 0 + if mask.allbits?(BBMODE::CHILDREN) get_child_shapes(ANY, NORECURSIVE, SEARCHMODE::BFS, lst_children) # now, call this function for all children recursively... lst_children.each do |child| - child.send(:_get_complete_bounding_box, rct, mask, processed) + rct = child.send(:_get_complete_bounding_box, rct, mask, processed) end end - rct + rct || Wx::Rect.new end # Original protected event handler called when the mouse pointer is moving around the shape canvas. # The function is called by the framework (by the shape canvas). After processing the event # relevant overridable event handlers are called. @@ -1798,11 +1833,11 @@ # send the event to the shape handles too... @handles.each { |h| h.__send__(:_on_mouse_move, pos) } # send the event to the connection points too... - @connection_pts.each { |cp| cp.__send__(:_on_mouse_move, pos) } + @connection_pts.each { |cp| cp.__send__(:_on_mouse_move, pos) } unless has_style?(STYLE::PROPAGATE_INTERACTIVE_CONNECTION) # determine, whether the shape should be highlighted for any reason if canvas case canvas.get_mode when Wx::SF::ShapeCanvas::MODE::SHAPEMOVE @@ -1870,12 +1905,12 @@ return unless @active @first_move = true on_begin_drag(pos) - if get_parent_shape && has_style?(STYLE::PROPAGATE_DRAGGING) - get_parent_shape.__send__(:_on_begin_drag, pos) + if @parent_shape && has_style?(STYLE::PROPAGATE_DRAGGING) + @parent_shape.__send__(:_on_begin_drag, pos) end end # Original protected event handler called during a dragging process. # The function is called by the framework (by the shape canvas). After processing the event @@ -1889,30 +1924,30 @@ if @first_move @mouse_offset = Wx::RealPoint.new(pos.x, pos.y) - get_absolute_position end # get shape BB BEFORE movement and combine it with BB of assigned lines - prev_bb = get_complete_bounding_box(Wx::Rect.new, BBMODE::SELF | BBMODE::CONNECTIONS | BBMODE::CHILDREN | BBMODE::SHADOW) + prev_bb = get_complete_bounding_box(nil, BBMODE::SELF | BBMODE::CONNECTIONS | BBMODE::CHILDREN | BBMODE::SHADOW) move_to(pos.x - @mouse_offset.x, pos.y - @mouse_offset.y) on_dragging(pos) # GUI controls in child control shapes must be updated explicitly lst_child_ctrls = get_child_shapes(Wx::SF::ControlShape, RECURSIVE) lst_child_ctrls.each { |ctrl| ctrl.update_control } # get shape BB AFTER movement and combine it with BB of assigned lines - curr_bb = get_complete_bounding_box(Wx::Rect.new, BBMODE::SELF | BBMODE::CONNECTIONS | BBMODE::CHILDREN | BBMODE::SHADOW) + curr_bb = get_complete_bounding_box(nil, BBMODE::SELF | BBMODE::CONNECTIONS | BBMODE::CHILDREN | BBMODE::SHADOW) # update canvas refresh_rect(prev_bb.union!(curr_bb), DELAYED) @first_move = false end - if get_parent_shape && has_style?(STYLE::PROPAGATE_DRAGGING) - get_parent_shape.__send__(:_on_dragging, pos) + if @parent_shape && has_style?(STYLE::PROPAGATE_DRAGGING) + @parent_shape.__send__(:_on_dragging, pos) end end # Original protected event handler called at the end of dragging process. # The function is called by the framework (by the shape canvas). After processing the event @@ -1922,12 +1957,12 @@ def _on_end_drag(pos) return unless @active on_end_drag(pos) - if get_parent_shape && has_style?(STYLE::PROPAGATE_DRAGGING) - get_parent_shape.__send__(:_on_end_drag, pos) + if @parent_shape && has_style?(STYLE::PROPAGATE_DRAGGING) + @parent_shape.__send__(:_on_end_drag, pos) end end # Original protected event handler called when any key is pressed (in the shape canvas). # The function is called by the framework (by the shape canvas). @@ -1944,21 +1979,21 @@ dx = 1.0 dy = 1.0 f_refresh_all = false if canvas.has_style?(Wx::SF::ShapeCanvas::STYLE::GRID_USE) - dx = canvas.get_grid_size.x - dy = canvas.get_grid_size.y + dx = canvas.get_grid_size + dy = canvas.get_grid_size end lst_selection = canvas.get_selected_shapes if (lst_selection.size > 1) && lst_selection.include?(self) f_refresh_all = true end - prev_bb = Wx::Rect.new - if !f_refresh_all + 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 @@ -1975,14 +2010,13 @@ move_by(0, dy) if has_style?(STYLE::POSITION_CHANGE) end end if !f_refresh_all - curr_bb = get_complete_bounding_box(Wx::Rect.new, BBMODE::SELF | BBMODE::CONNECTIONS | BBMODE::CHILDREN | BBMODE::SHADOW) + curr_bb = get_complete_bounding_box(prev_bb, BBMODE::SELF | BBMODE::CONNECTIONS | BBMODE::CHILDREN | BBMODE::SHADOW) - prev_bb.union!(curr_bb) - refresh_rect(prev_bb, DELAYED) + refresh_rect(curr_bb, DELAYED) else canvas.refresh(false) end end end @@ -1993,13 +2027,13 @@ # @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(Wx::Rect.new) + prev_bb = get_grand_parent_shape.get_complete_bounding_box(nil) else - prev_bb = get_complete_bounding_box(Wx::Rect.new) + prev_bb = get_complete_bounding_box(nil) end # call appropriate user-defined handler on_handle(handle) @@ -2012,13 +2046,13 @@ # update shape update if @parent_shape - curr_bb = get_grand_parent_shape.get_complete_bounding_box(Wx::Rect.new) + curr_bb = get_grand_parent_shape.get_complete_bounding_box(nil) else - curr_bb = get_complete_bounding_box(Wx::Rect.new) + curr_bb = get_complete_bounding_box(nil) end # refresh shape refresh_rect(curr_bb.union!(prev_bb), DELAYED) end @@ -2060,23 +2094,32 @@ end def update_child_parents @child_shapes.each do |shape| shape.instance_variable_set(:@parent_shape, self) - shape.send(:update_child_parents) 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 @@ -2101,13 +2144,13 @@ 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) ) + 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