lib/wx/shapes/shape_canvas.rb in wxruby3-shapes-0.9.0.pre.beta.3 vs lib/wx/shapes/shape_canvas.rb in wxruby3-shapes-0.9.5
- old
+ new
@@ -48,11 +48,11 @@
# for invoking of default event handlers/virtual functions otherwise the
# built in functionality wont be available.
# @see Wx::SF::Diagram
class ShapeCanvas < Wx::ScrolledWindow
- # Working modes
+ # Working modes
class MODE < Wx::Enum
# The shape canvas is in ready state (no operation is pending)
READY = self.new(0)
# Some shape handle is dragged
HANDLEMOVE = self.new(1)
@@ -190,41 +190,60 @@
FAILED_AND_CONTINUE_EDIT = self.new(2)
end
# Default values
module DEFAULT
- # Default value of Wx::SF::CanvasSettings @background_color data member
- BACKGROUNDCOLOR = Wx::Colour.new(240, 240, 240) if Wx::App.is_main_loop_running
- Wx.add_delayed_constant(self, :BACKGROUNDCOLOR) { Wx::Colour.new(240, 240, 240) }
+ class << self
+ # Default value of Wx::SF::CanvasSettings @background_color data member
+ def background_color; @bgcolor ||= Wx::Colour.new(240, 240, 240); end
+ # Default value of Wx::SF::CanvasSettings @common_hover_color data member
+ def hover_color; @hvrcolor ||= Wx::Colour.new(120, 120, 255); end
+ # Default value of Wx::SF::CanvasSettings @common_border_pen data member
+ def border_pen; @border_pen ||= Wx::BLACK_PEN.dup; end
+ # Default value of Wx::SF::CanvasSettings @common_fill_brush data member
+ def fill_brush; @fill_brush ||= Wx::WHITE_BRUSH.dup; end
+ # Default value of Wx::SF::CanvasSettings @common_line_pen data member
+ def line_pen; @line_pen ||= border_pen; end
+ # Default value of Wx::SF::CanvasSettings @common_arrow_fill data member
+ def arrow_fill; @arrow_fill ||= fill_brush; end
+ # Default value of Wx::SF::CanvasSettings @common_text_color data member
+ def text_color; @text_color ||= Wx::BLACK.dup; end
+ # Default value of Wx::SF::CanvasSettings @common_text_fill data member
+ def text_fill; @text_fill ||= Wx::TRANSPARENT_BRUSH.dup; end
+ # Default value of Wx::SF::CanvasSettings @common_text_border data member
+ def text_border; @text_border ||= Wx::TRANSPARENT_PEN.dup; end
+ # Default value of Wx::SF::CanvasSettings @common_text_font data member
+ def text_font; begin; @text_font = Wx::SWISS_FONT.dup; @text_font.point_size = 12; end unless @text_font; @text_font; end
+ # Default value of Wx::SF::CanvasSettings @common_control_fill data member
+ def control_fill; @ctrl_fill ||= Wx::TRANSPARENT_BRUSH.dup; end
+ # Default value of Wx::SF::CanvasSettings @common_control_border data member
+ def control_border; @ctrl_border ||= Wx::TRANSPARENT_PEN.dup; end
+ # Default value of Wx::SF::CanvasSettings @common_control_mod_fill data member
+ def control_mod_fill; @ctrl_mod_fill ||= Wx::Brush.new(Wx::BLUE, Wx::BrushStyle::BRUSHSTYLE_BDIAGONAL_HATCH); end
+ # Default value of Wx::SF::CanvasSettings @common_control_mod_border data member
+ def control_mod_border; @ctrl_mod_border ||= Wx::Pen.new(Wx::BLUE, 1, Wx::PenStyle::PENSTYLE_SOLID); end
+ # Default value of Wx::SF::CanvasSettings @grid_color data member
+ def grid_color; @gridcolor ||= Wx::Colour.new(200, 200, 200); end
+ # Default value of Wx::SF::CanvasSettings @gradient_from data member
+ def gradient_from; @gradcolor_from ||= Wx::Colour.new(240, 240, 240); end
+ # Default value of Wx::SF::CanvasSettings @gradient_to data member
+ def gradient_to; @gradcolor_to ||= Wx::Colour.new(200, 200, 255); end
+ # Default shadow colour
+ def shadow_color; @shadowcolor ||= Wx::Colour.new(150, 150, 150, 128); end
+ # Default value of Wx::SF::CanvasSettings @shadow_fill data member
+ def shadow_brush; @shadowbrush ||= Wx::Brush.new(shadow_color, Wx::BrushStyle::BRUSHSTYLE_SOLID); end
+ end
# Default value of Wx::SF::CanvasSettings @grid_size data member
- GRIDSIZE = Wx::Size.new(10, 10)
+ GRIDSIZE = 10
# Default value of Wx::SF::CanvasSettings @grid_line_mult data member
GRIDLINEMULT = 1
- # Default value of Wx::SF::CanvasSettings @grid_color data member
- GRIDCOLOR = Wx::Colour.new(200, 200, 200) if Wx::App.is_main_loop_running
- Wx.add_delayed_constant(self, :GRIDCOLOR) { Wx::Colour.new(200, 200, 200) }
# Default value of Wx::SF::CanvasSettings @grid_style data member
GRIDSTYLE = Wx::PenStyle::PENSTYLE_SOLID
- # Default value of Wx::SF::CanvasSettings @common_hover_color data member
- HOVERCOLOR = Wx::Colour.new(120, 120, 255) if Wx::App.is_main_loop_running
- Wx.add_delayed_constant(self, :HOVERCOLOR) { Wx::Colour.new(120, 120, 255) }
- # Default value of Wx::SF::CanvasSettings @gradient_from data member
- GRADIENT_FROM = Wx::Colour.new(240, 240, 240) if Wx::App.is_main_loop_running
- Wx.add_delayed_constant(self, :GRADIENT_FROM) { Wx::Colour.new(240, 240, 240) }
- # Default value of Wx::SF::CanvasSettings @gradient_to data member
- GRADIENT_TO = Wx::Colour.new(200, 200, 255) if Wx::App.is_main_loop_running
- Wx.add_delayed_constant(self, :GRADIENT_TO) { Wx::Colour.new(200, 200, 255) }
# Default value of Wx::SF::CanvasSettings @style data member
CANVAS_STYLE = STYLE::DEFAULT_CANVAS_STYLE
# Default value of Wx::SF::CanvasSettings @shadow_offset data member
SHADOWOFFSET = Wx::RealPoint.new(4, 4)
- # Default shadow colour
- SHADOWCOLOR = Wx::Colour.new(150, 150, 150, 128) if Wx::App.is_main_loop_running
- Wx.add_delayed_constant(self, :SHADOWCOLOR) { Wx::Colour.new(150, 150, 150, 128) }
- # Default value of Wx::SF::CanvasSettings @shadow_fill data member
- SHADOWBRUSH = Wx::Brush.new(SHADOWCOLOR.call, Wx::BrushStyle::BRUSHSTYLE_SOLID) if Wx::App.is_main_loop_running
- Wx.add_delayed_constant(self, :SHADOWBRUSH) { Wx::Brush.new(Wx::Colour.new(150, 150, 150, 128), Wx::BrushStyle::BRUSHSTYLE_SOLID) }
# Default value of Wx::SF::CanvasSettings @print_h_align data member
PRINT_HALIGN = HALIGN::CENTER
# Default value of Wx::SF::CanvasSettings @print_v_align data member
PRINT_VALIGN = VALIGN::MIDDLE
# Default value of Wx::SF::CanvasSettings @print_mode data member
@@ -316,11 +335,11 @@
def to_s
"#{major}.#{minor}.#{release}"
end
end
- include Serializable
+ include FIRM::Serializable
property :version_info
def initialize
# get version numbers as [major, minor, release]
@@ -348,40 +367,72 @@
end
# Auxiliary serializable class encapsulating the canvas properties.
class Settings
- include Serializable
+ include FIRM::Serializable
include DEFAULT
- property :scale, :min_scale, :max_scale, :background_color, :common_hover_color,
- :grid_size, :grid_line_mult, :grid_color, :grid_style,
+ property :scale, :min_scale, :max_scale, :background_color,
+ :common_hover_color, :common_border_pen, :common_fill_brush, :common_line_pen,
+ :common_arrow_fill, :common_text_color, :common_text_fill, :common_text_border, :common_text_font,
+ :common_control_fill, :common_control_border, :common_control_mod_fill, :common_control_mod_border,
+ :grid_line_mult, :grid_color, :grid_style,
:gradient_from, :gradient_to, :style, :shadow_offset, :shadow_fill,
:print_h_align, :print_v_align, :print_mode
+ property grid_size: ->(obj, *val) {
+ unless val.empty?
+ obj.grid_size = Wx::Size === val.first ? val.first.x : val.first
+ end
+ obj.grid_size
+ }
def initialize
@scale = 1.0
@min_scale = SCALE_MIN
@max_scale = SCALE_MAX
- @background_color = BACKGROUNDCOLOR
- @common_hover_color = HOVERCOLOR
- @grid_size = GRIDSIZE.dup
+ @background_color = DEFAULT.background_color
+
+ # common shape property
+ @common_hover_color = DEFAULT.hover_color
+ # common rect shape properties
+ @common_border_pen = DEFAULT.border_pen
+ @common_fill_brush = DEFAULT.fill_brush
+ # common line shape property
+ @common_line_pen = DEFAULT.line_pen
+ # common arrow property
+ @common_arrow_fill = DEFAULT.arrow_fill
+ # common text shape properties (fill and border overrule common rect properties)
+ @common_text_color = DEFAULT.text_color
+ @common_text_fill = DEFAULT.text_fill
+ @common_text_border = DEFAULT.text_border
+ @common_text_font = DEFAULT.text_font
+ # common control shape properties
+ @common_control_fill = DEFAULT.control_fill
+ @common_control_border = DEFAULT.control_border
+ @common_control_mod_fill = DEFAULT.control_mod_fill
+ @common_control_mod_border = DEFAULT.control_mod_border
+
+ @grid_size = GRIDSIZE
@grid_line_mult = GRIDLINEMULT
- @grid_color = GRIDCOLOR
+ @grid_color = DEFAULT.grid_color
@grid_style = GRIDSTYLE
- @gradient_from = GRADIENT_FROM
- @gradient_to = GRADIENT_TO
+ @gradient_from = DEFAULT.gradient_from
+ @gradient_to = DEFAULT.gradient_to
@style = CANVAS_STYLE
@shadow_offset = SHADOWOFFSET.dup
- @shadow_fill = SHADOWBRUSH
+ @shadow_fill = DEFAULT.shadow_brush
@print_h_align = PRINT_HALIGN
@print_v_align = PRINT_VALIGN
@print_mode = PRINT_MODE
end
- attr_accessor :scale, :min_scale, :max_scale, :background_color, :common_hover_color,
+ attr_accessor :scale, :min_scale, :max_scale, :background_color,
+ :common_hover_color, :common_border_pen, :common_fill_brush, :common_line_pen,
+ :common_arrow_fill, :common_text_color, :common_text_fill, :common_text_border, :common_text_font,
+ :common_control_fill, :common_control_border, :common_control_mod_fill, :common_control_mod_border,
:grid_size, :grid_line_mult, :grid_color, :grid_style,
:gradient_from, :gradient_to, :style, :shadow_offset, :shadow_fill,
:print_h_align, :print_v_align, :print_mode
end
@@ -439,11 +490,11 @@
self.diagram = diagram
save_canvas_state
end
-
+
# set up event handlers
evt_paint :_on_paint
evt_erase_background :_on_erase_background
evt_left_down :_on_left_down
evt_left_up :_on_left_up
@@ -478,19 +529,17 @@
set_drop_target(Wx::SF::CanvasDropTarget.new(Wx::SF::ShapeDataObject.new, self))
end
# initialize selection rectangle
@shp_selection = MultiSelRect.new
- @shp_selection.send(:set_id, nil)
@shp_selection.create_handles
@shp_selection.select(true)
@shp_selection.show(false)
@shp_selection.show_handles(true)
# initialize multi-edit rectangle
@shp_multi_edit = MultiSelRect.new
- @shp_multi_edit.send(:set_id, nil)
@shp_multi_edit.create_handles
@shp_multi_edit.select(true)
@shp_multi_edit.show(false)
@shp_multi_edit.show_handles(true)
@@ -521,55 +570,102 @@
end
# Load serialized canvas content (diagrams).
# @overload load_canvas(file)
# @param [String] file Full file name
+ # @param [Symbol,nil] format specifies the serialization format to use;
+ # determined from file extension if not specified defaulting to :json for unknown extensions
# @return [self]
# @overload load_canvas(io)
# @param [IO] io IO object
+ # @param [Symbol,nil] format specifies the serialization format to use (by default :json)
# @return [self]
- def load_canvas(io)
+ def load_canvas(io, format: nil)
# get IO stream to read from
- ios = io.is_a?(::String) ? File.open(io, 'r') : io
+ ios = if io.is_a?(::String)
+ format ||= case File.extname(io)
+ when '.json' then :json
+ when '.yaml', '.yml' then :yaml
+ when '.xml' then :xml
+ else
+ :json
+ end
+ File.open(io, 'r')
+ else
+ format ||= :json
+ io
+ end
+ old_diagram = @diagram
+ old_settings = @settings
begin
- _, @settings, diagram = Serializable.deserialize(ios)
- rescue SFException
+ begin
+ _, @settings, diagram = FIRM.deserialize(ios, format: format)
+ rescue SFException
+ ::Kernel.raise
+ rescue ::Exception
+ $stderr.puts "#{$!}\n#{$!.backtrace.join("\n")}\n"
+ ::Kernel.raise SFException, "Failed to load canvas: #{$!.message}"
+ ensure
+ ShapeCanvas.reset_compat_loading
+ ios.close if io.is_a?(::String) && ios
+ end
+ set_diagram(diagram)
+ clear_canvas_history
+ save_canvas_state
+ set_scale(@settings.scale)
+ update_virtual_size
+ refresh(false)
+ rescue Exception
+ $stderr.puts "#{$!}\n#{$!.backtrace.join("\n")}\n"
+ # restore previous state
+ @settings = old_settings
+ set_diagram(old_diagram)
+ clear_canvas_history
+ save_canvas_state
+ set_scale(@settings.scale)
+ update_virtual_size
+ refresh(false)
+ # propagate exception
::Kernel.raise
- rescue ::Exception
- ::Kernel.raise SFException, "Failed to load canvas: #{$!.message}"
- ensure
- ShapeCanvas.reset_compat_loading
- ios.close if io.is_a?(::String) && ios
end
- set_diagram(diagram)
- clear_canvas_history
- save_canvas_state
- set_scale(@settings.scale)
- update_virtual_size
- refresh(false)
@diagram.set_modified(false)
self
end
# Save canvas content (diagrams).
# @overload save_canvas(file, compact: true)
# @param [String] file Full file name
# @param [Boolean] compact specifies whether to write content in compact mode (true) or not (false)
+ # @param [Symbol,nil] format specifies the serialization format to use;
+ # determined from file extension if not specified defaulting to :json for unknown extensions
# @return [self]
# @overload save_canvas(io, compact: true)
# @param [IO] io IO object
# @param [Boolean] compact specifies whether to write content in compact mode (true) or not (false)
+ # @param [Symbol,nil] format specifies the serialization format to use (by default :json)
# @return [self]
- def save_canvas(io, compact: true)
+ def save_canvas(io, compact: true, format: nil)
return self unless @diagram
# get IO stream to write to
- ios = io.is_a?(::String) ? Tempfile.new(File.basename(io, '.*')) : io
+ ios = if io.is_a?(::String)
+ format ||= case File.extname(io)
+ when '.json' then :json
+ when '.yaml', '.yml' then :yaml
+ when '.xml' then :xml
+ else
+ :json
+ end
+ Tempfile.new(File.basename(io, '.*'))
+ else
+ format ||= :json
+ io
+ end
# write canvas data to temp file
begin
- [Version.new, @settings, @diagram].serialize(ios, pretty: !compact)
+ [Version.new, @settings, @diagram].serialize(ios, pretty: !compact, format: format)
rescue SFException
::Kernel.raise
rescue Exception
::Kernel.raise SFException, "Error writing canvas: #{$!.message}"
end
@@ -634,11 +730,11 @@
bmp_bb.left = (bmp_bb.left * scale).to_i
bmp_bb.top = (bmp_bb.top * scale).to_i
bmp_bb.width = (bmp_bb.width * scale).to_i
bmp_bb.height = (bmp_bb.height * scale).to_i
- bmp_bb.inflate!(@settings.grid_size * scale)
+ bmp_bb.inflate!(Wx::Size.new(@settings.grid_size, @settings.grid_size) * scale)
outbmp = Wx::Bitmap.new(bmp_bb.width, bmp_bb.height)
Wx::MemoryDC.draw_on(outbmp) do |mdc|
Wx::ScaledDC.draw_on(mdc, scale) do |outdc|
@@ -679,16 +775,16 @@
end
end
nil
end
- def _start_interactive_connection(lpos, src_shape_id, cpt)
+ def _start_interactive_connection(lpos, src_shape, cpt)
if @new_line_shape
@working_mode = MODE::CREATECONNECTION
@new_line_shape.send(:set_line_mode, LineShape::LINEMODE::UNDERCONSTRUCTION)
- @new_line_shape.set_src_shape_id(src_shape_id)
+ @new_line_shape.set_src_shape(src_shape)
# switch on the "under-construction" mode
@new_line_shape.send(:set_unfinished_point, lpos)
# assign starting point of new line shapes to the nearest connection point of
# connected shape if exists
@@ -730,19 +826,19 @@
shape_info = shape = pos = connection_point = nil
shape_klass = nil
case args.first
when Wx::SF::LineShape
shape = args.shift
- shape_klass = shape.class.name
+ shape_klass = shape.class
if args.first.is_a?(Wx::SF::ConnectionPoint)
connection_point = args.shift
end
pos = args.shift.to_point
when ::Class
shape_info = args.shift
pos = args.shift.to_point
- shape_klass = shape_info.name
+ shape_klass = shape_info
end
::Kernel.raise ArgumentError, "Invalid arguments #{args}" unless args.empty?
return ERRCODE::INVALID_INPUT unless pos
lpos = dp2lp(pos)
@@ -754,11 +850,11 @@
if @diagram.contains?(shape)
@new_line_shape = shape
else
@new_line_shape = @diagram.add_shape(shape, nil, Wx::DEFAULT_POSITION, INITIALIZE, DONT_SAVE_STATE)
end
- return _start_interactive_connection(lpos, connection_point.get_parent_shape.id, connection_point)
+ return _start_interactive_connection(lpos, connection_point.get_parent_shape, connection_point)
else
shape_under = get_shape_at_position(lpos)
if shape_info
@@ -766,22 +862,22 @@
shape_under = shape_under.get_parent_shape while shape_under &&
shape_under.has_style?(Shape::STYLE::PROPAGATE_INTERACTIVE_CONNECTION)
end
# start the connection's creation process if possible
- if shape_under&.id && shape_under.is_connection_accepted(shape_klass)
+ if shape_under && shape_under.is_connection_accepted(shape_klass)
if shape && @diagram.contains?(shape)
@new_line_shape = shape
else
if shape
err = @diagram.add_shape(shape, nil, Wx::DEFAULT_POSITION, INITIALIZE, DONT_SAVE_STATE)
else
err, shape = @diagram.create_shape(shape_info, DONT_SAVE_STATE)
end
@new_line_shape = (err == ERRCODE::OK ? shape : nil)
end
- return _start_interactive_connection(lpos, shape_under.id, shape_under.get_nearest_connection_point(lpos.to_real))
+ return _start_interactive_connection(lpos, shape_under, shape_under.get_nearest_connection_point(lpos.to_real))
else
return ERRCODE::NOT_ACCEPTED
end
end
@@ -804,11 +900,11 @@
# Select all shapes in the canvas
def select_all
return unless @diagram
- shapes = @diagram.get_shapes
+ shapes = @diagram.get_all_shapes
unless shapes.empty?
shapes.each { |shape| shape.select(true) }
validate_selection(get_selected_shapes)
@@ -824,20 +920,20 @@
# Deselect all shapes
def deselect_all
return unless @diagram
- @diagram.get_shapes.each { |shape| shape.select(false) }
+ @diagram.get_all_shapes.each { |shape| shape.select(false) }
@shp_multi_edit.show(false)
end
# Hide handles of all shapes
def hide_all_handles
return unless @diagram
- @diagram.get_shapes.each { |shape| shape.show_handles(false) }
+ @diagram.get_all_shapes.each { |shape| shape.show_handles(false) }
end
# Repaint the shape canvas.
# @param [Boolean] erase true if the canvas should be erased before repainting
# @param [Wx::Rect] rct Refreshed region (rectangle)
@@ -886,11 +982,11 @@
# @param [SHADOWMODE] style Shadow style
# @see SHADOWMODE
def show_shadows(show, style)
return unless @diagram
- shapes = @diagram.get_shapes
+ shapes = @diagram.get_all_shapes
shapes.each do |shape|
shape.remove_style(Shape::STYLE::SHOW_SHADOW) if show
case style
@@ -977,10 +1073,11 @@
validate_selection_for_clipboard(lst_selection,true)
unless lst_selection.empty?
data_obj = Wx::SF::ShapeDataObject.new(lst_selection)
+
clipboard.place(data_obj)
restore_prev_positions
end
end
@@ -1016,19 +1113,18 @@
Wx::Clipboard.open do |clipboard|
# read data object from the clipboard
data_obj = Wx::SF::ShapeDataObject.new
if clipboard.fetch(data_obj)
- # deserialize shapes
- new_shapes = Wx::SF::Serializable.deserialize(data_obj.get_data_here)
+ new_shapes = data_obj.get_as_shapes
# add new shapes to diagram and remove those that are not accepted
new_shapes.select! do |shape|
ERRCODE::OK == @diagram.add_shape(shape, nil, shape.get_relative_position, INITIALIZE, DONT_SAVE_STATE)
end
# verify newly added shapes (may remove shapes from list)
- @diagram.send(:check_new_shapes, new_shapes)
+ @diagram.send(:on_import, new_shapes)
update_virtual_size # update for new shapes
# call user-defined handler
on_paste(new_shapes)
@@ -1080,12 +1176,12 @@
# @return [Boolean]
def can_paste
return false unless has_style?(STYLE::CLIPBOARD)
Wx::Clipboard.open do |clipboard|
- return clipboard.supported?(Wx::DataFormat.new(Wx::SF::ShapeDataObject::DataFormatID))
- end
+ return clipboard.supported?(Wx::SF::ShapeDataObject::DataFormatID)
+ end rescue false
end
alias :can_paste? :can_paste
# Function returns true if undo operation can be done
# @return [Boolean]
@@ -1126,17 +1222,28 @@
# Restores given canvas state (unless nil given)
# @param [String,nil] state to restore
def restore_canvas_state(state)
return unless state
- set_diagram(Wx::SF::Serializable.deserialize(state))
+ set_diagram(FIRM.deserialize(state))
update_virtual_size
@diagram.set_modified
refresh(false)
end
protected :restore_canvas_state
+ # Restores current last saved canvas state.
+ def restore_current_state
+ return unless has_style?(STYLE::UNDOREDO)
+
+ clear_temporaries
+
+ restore_canvas_state(@canvas_history.current_state)
+ @shp_multi_edit.show(false)
+ end
+ protected :restore_current_state
+
# @!group Print methods
# Print current canvas content.
# @overload print(prompt = PROMPT)
# @param [Boolean] prompt If true (PROMPT) then the the native print dialog will be displayed before printing
@@ -1245,16 +1352,16 @@
# @overload lp2dp(rct)
# @param [Wx::Rect] rct Logical position (for example shape position)
# @return [Wx::Rect] Device position
def lp2dp(arg)
if arg.is_a?(Wx::Rect)
- x, y = calc_unscrolled_position(arg.x, arg.y)
+ x, y = calc_scrolled_position(arg.x, arg.y)
Wx::Rect.new((x*@settings.scale).to_i, (y*@settings.scale).to_i,
(arg.width*@settings.scale).to_i, (arg.height*@settings.scale).to_i)
else
arg = arg.to_point
- x, y = calc_unscrolled_position(arg.x, arg.y)
+ x, y = calc_scrolled_position(arg.x, arg.y)
Wx::Point.new((x*@settings.scale).to_i, (y*@settings.scale).to_i)
end
end
# Search for any shape located at the (mouse cursor) position (result used by #get_shape_under_cursor)
@@ -1279,11 +1386,11 @@
end
else
top_shape ||= shape
if shape.selected?
sel_shape ||= shape
- else
+ elsif !shape.has_selected_parent?
unsel_shape ||= shape
end
end
end
end
@@ -1341,11 +1448,11 @@
return handle if handle.visible? && handle.contains?(pos)
end
end
# ... then test normal handles
- @diagram.get_shapes.each do |shape|
+ @diagram.get_all_shapes.each do |shape|
# iterate through all shape's handles
if shape.has_style?(Shape::STYLE::SIZE_CHANGE)
shape.handles.each do |handle|
return handle if handle.visible? && handle.contains?(pos)
end
@@ -1379,11 +1486,11 @@
# @return [Array<Wx::SF::Shape>] shapes shape list
def get_selected_shapes(selection = [])
return selection unless @diagram
selection.clear
- @diagram.get_shapes.each do |shape|
+ @diagram.get_all_shapes.each do |shape|
selection << shape if shape.selected?
end
selection
end
@@ -1391,11 +1498,11 @@
# @return [Wx::Rect] Total bounding box
def get_total_bounding_box
virt_rct = nil
if @diagram
# calculate total bounding box (includes all shapes)
- @diagram.get_shapes.each_with_index do |shape, ix|
+ @diagram.get_all_shapes.each_with_index do |shape, ix|
if ix == 0
virt_rct = shape.get_bounding_box
else
virt_rct.union!(shape.get_bounding_box)
end
@@ -1405,18 +1512,18 @@
end
# Get bounding box of all selected shapes.
# @return [Wx::Rect] Selection bounding box
def get_selection_bb
- bb_rct = Wx::Rect.new
+ bb_rct = nil
# get selected shapes
get_selected_shapes.each do |shape|
- shape.get_complete_bounding_box(
- bb_rct,
- Shape::BBMODE::SELF | Shape::BBMODE::CHILDREN | Shape::BBMODE::CONNECTIONS | Shape::BBMODE::SHADOW)
+ bb_rct = shape.get_complete_bounding_box(bb_rct,
+ Shape::BBMODE::SELF | Shape::BBMODE::CHILDREN |
+ Shape::BBMODE::CONNECTIONS | Shape::BBMODE::SHADOW)
end
- bb_rct
+ bb_rct || Wx::Rect.new
end
# Align selected shapes in given directions.
#
# Shapes will be aligned according to most far shape in appropriate direction.
@@ -1436,11 +1543,11 @@
unless shape.is_a?(LineShape)
pos = shape.get_absolute_position
shape_bb = shape.get_bounding_box
if cnt == 0
- min_pos = pos
+ min_pos = pos.dup
max_pos = Wx::RealPoint.new(pos.x + shape_bb.width, pos.y + shape_bb.height)
else
min_pos.x = pos.x if pos.x < min_pos.x
min_pos.y = pos.y if pos.y < min_pos.y
max_pos.x = pos.x + shape_bb.width if (pos.x + shape_bb.width) > max_pos.x
@@ -1541,18 +1648,22 @@
# Set canvas background color.
# @param [Wx::Colour] col Background color
def set_canvas_colour(col)
@settings.background_color = col
end
+ alias :set_canvas_color :set_canvas_colour
alias :canvas_colour= :set_canvas_colour
+ alias :canvas_color= :set_canvas_colour
# Get canvas background color.
# @return [Wx::Colour] Background color
def get_canvas_colour
@settings.background_color
end
+ alias :get_canvas_color :get_canvas_colour
alias :canvas_colour :get_canvas_colour
+ alias :canvas_color :get_canvas_colour
# Set starting gradient color.
# @param [Wx::Colour] col Color
def set_gradient_from(col)
@settings.gradient_from = col
@@ -1578,21 +1689,22 @@
def get_gradient_to
@settings.gradient_to
end
alias :gradient_to :get_gradient_to
- # Get grid size.
- # @return [Wx::Size] Grid size
+ # Get grid size (px).
+ # @return [Integer] Grid size
def get_grid_size
@settings.grid_size
end
alias :grid_size :get_grid_size
- # Set grid size.
- # @param [Wx::Size] grid Grid size
- def set_grid_size(grid)
- @settings.grid_size = grid.to_size
+ # Set grid size (px).
+ # @param [Integer] sz Grid size
+ def set_grid_size(sz)
+ raise ArgumentError, 'Grid size must be integer > 0' if sz.to_i <= 0
+ @settings.grid_size = sz.to_i
end
alias :grid_size= :set_grid_size
# Set grid line multiple.
#
@@ -1614,18 +1726,22 @@
# Set grid color.
# @param [Wx::Colour] col Grid color
def set_grid_colour(col)
@settings.grid_color = col
end
+ alias :set_grid_color :set_grid_colour
alias :grid_colour= :set_grid_colour
+ alias :grid_color= :set_grid_colour
# Get grid color.
# @return [Wx::Colour] Grid color
def get_grid_colour
@settings.grid_color
end
+ alias :get_grid_color :get_grid_colour
alias :grid_colour :get_grid_colour
+ alias :grid_color :get_grid_colour
# Set grid line style.
# @param [Wx::PenStyle] style Line style
def set_grid_style(style)
@settings.grid_style = style
@@ -1652,13 +1768,23 @@
@settings.shadow_offset
end
alias :shadow_offset :get_shadow_offset
# Set shadow fill (used for shadows of non-text shapes only).
- # @param [Wx::Brush] brush Reference to brush object
- def set_shadow_fill(brush)
- @settings.shadow_fill = brush
+ # @overload set_shadow_fill(brush)
+ # @param [Wx::Brush] brush
+ # @overload set_shadow_fill(color, style=Wx::BrushStyle::BRUSHSTYLE_SOLID)
+ # @param [Wx::Colour,Symbol,String] color brush color
+ # @param [Wx::BrushStyle] style
+ # @overload set_shadow_fill(stipple_bitmap)
+ # @param [Wx::Bitmap] stipple_bitmap
+ def set_shadow_fill(*args)
+ @settings.shadow_fill = if args.size == 1 && Wx::Brush === args.first
+ args.first
+ else
+ Wx::Brush.new(*args)
+ end
end
alias :shadow_fill= :set_shadow_fill
# Get shadow fill.
# @return [Wx::Brush] Current shadow brush
@@ -1809,28 +1935,309 @@
@working_mode
end
alias :mode :get_mode
# Set default hover color.
- # @param [Wx::Colour] col Hover color.
+ # @param [Wx::Colour,Symbol,String] col Hover color.
def set_hover_colour(col)
- return unless @diagram
-
- @settings.common_hover_color = col
-
- # update Hover color in all existing shapes
- @diagram.get_shapes.each { |shape| shape.set_hover_colour(col) }
+ @settings.common_hover_color = Wx::Colour === col ? col : Wx::Colour.new(col)
end
+ alias :set_hover_color :set_hover_colour
alias :hover_colour= :set_hover_colour
+ alias :hover_color= :set_hover_colour
# Get default hover colour.
# @return [Wx::Colour] Hover colour
def get_hover_colour
@settings.common_hover_color
end
+ alias :get_hover_color :get_hover_colour
alias :hover_colour :get_hover_colour
+ alias :hover_color :get_hover_colour
+ # Set default fill brush.
+ # @overload set_fill_brush(brush)
+ # @param [Wx::Brush] brush
+ # @overload set_fill_brush(color, style=Wx::BrushStyle::BRUSHSTYLE_SOLID)
+ # @param [Wx::Colour,Symbol,String] color brush color
+ # @param [Wx::BrushStyle] style
+ # @overload set_fill_brush(stipple_bitmap)
+ # @param [Wx::Bitmap] stipple_bitmap
+ def set_fill_brush(*args)
+ @settings.common_fill_brush = if args.size == 1 && Wx::Brush === args.first
+ args.first
+ else
+ Wx::Brush.new(*args)
+ end
+ end
+ alias :fill_brush= :set_fill_brush
+
+ # Get default fill brush.
+ # @return [Wx::Brush] Fill brush
+ def get_fill_brush
+ @settings.common_fill_brush
+ end
+ alias :fill_brush :get_fill_brush
+
+ # Set default border pen.
+ # @overload set_border_pen(pen)
+ # @param [Wx::Pen] pen
+ # @overload set_border_pen(color, width=1, style=Wx::PenStyle::PENSTYLE_SOLID)
+ # @param [Wx::Colour,String,Symbol] color
+ # @param [Integer] width
+ # @param [Wx::PenStyle] style
+ def set_border_pen(*args)
+ @settings.common_border_pen = if args.size == 1 && Wx::Pen === args.first
+ args.first
+ else
+ Wx::Pen.new(*args)
+ end
+ end
+ alias :border_pen= :set_border_pen
+
+ # Get default border pen.
+ # @return [Wx::Pen]
+ def get_border_pen
+ @settings.common_border_pen
+ end
+ alias :border_pen :get_border_pen
+
+ # Set default line pen.
+ # @overload set_line_pen(pen)
+ # @param [Wx::Pen] pen
+ # @overload set_line_pen(color, width=1, style=Wx::PenStyle::PENSTYLE_SOLID)
+ # @param [Wx::Colour,String,Symbol] color
+ # @param [Integer] width
+ # @param [Wx::PenStyle] style
+ def set_line_pen(*args)
+ @settings.common_line_pen = if args.size == 1 && Wx::Pen === args.first
+ args.first
+ else
+ Wx::Pen.new(*args)
+ end
+ end
+ alias :line_pen= :set_line_pen
+
+ # Get default line pen.
+ # @return [Wx::Pen]
+ def get_line_pen
+ @settings.common_line_pen
+ end
+ alias :line_pen :get_line_pen
+
+ # Set default arrow fill brush.
+ # @overload set_arrow_fill(brush)
+ # @param [Wx::Brush] brush
+ # @overload set_arrow_fill(color, style=Wx::BrushStyle::BRUSHSTYLE_SOLID)
+ # @param [Wx::Colour,Symbol,String] color brush color
+ # @param [Wx::BrushStyle] style
+ # @overload set_arrow_fill(stipple_bitmap)
+ # @param [Wx::Bitmap] stipple_bitmap
+ def set_arrow_fill(*args)
+ @settings.common_arrow_fill = if args.size == 1 && Wx::Brush === args.first
+ args.first
+ else
+ Wx::Brush.new(*args)
+ end
+ end
+ alias :arrow_fill= :set_arrow_fill
+
+ # Get default arrow fill brush.
+ # @return [Wx::Brush] Fill brush
+ def get_arrow_fill
+ @settings.common_arrow_fill
+ end
+ alias :arrow_fill :get_arrow_fill
+
+ # Set default text color.
+ # @param [Wx::Colour,Symbol,String] col text color.
+ def set_text_colour(col)
+ @settings.common_text_color = Wx::Colour === col ? col : Wx::Colour.new(col)
+ end
+ alias :set_text_color= :set_text_colour
+ alias :text_colour= :set_text_colour
+ alias :text_color= :set_text_colour
+
+ # Get default text colour.
+ # @return [Wx::Colour] text colour
+ def get_text_colour
+ @settings.common_text_color
+ end
+ alias :get_text_color :get_text_colour
+ alias :text_colour :get_text_colour
+ alias :text_color :get_text_colour
+
+ # Set default text fill brush.
+ # @overload set_text_fill(brush)
+ # @param [Wx::Brush] brush
+ # @overload set_text_fill(color, style=Wx::BrushStyle::BRUSHSTYLE_SOLID)
+ # @param [Wx::Colour,Symbol,String] color brush color
+ # @param [Wx::BrushStyle] style
+ # @overload set_text_fill(stipple_bitmap)
+ # @param [Wx::Bitmap] stipple_bitmap
+ def set_text_fill(*args)
+ @settings.common_text_fill = if args.size == 1 && Wx::Brush === args.first
+ args.first
+ else
+ Wx::Brush.new(*args)
+ end
+ end
+ alias :text_fill= :set_text_fill
+
+ # Get default text fill brush.
+ # @return [Wx::Brush] Fill brush
+ def get_text_fill
+ @settings.common_text_fill
+ end
+ alias :text_fill :get_text_fill
+
+ # Set default text border.
+ # @overload set_text_border(pen)
+ # @param [Wx::Pen] pen
+ # @overload set_text_border(color, width=1, style=Wx::PenStyle::PENSTYLE_SOLID)
+ # @param [Wx::Colour,String,Symbol] color
+ # @param [Integer] width
+ # @param [Wx::PenStyle] style
+ def set_text_border(*args)
+ @settings.common_text_border = if args.size == 1 && Wx::Pen === args.first
+ args.first
+ else
+ Wx::Pen.new(*args)
+ end
+ end
+ alias :text_border= :set_text_border
+
+ # Get default text border.
+ # @return [Wx::Pen]
+ def get_text_border
+ @settings.common_text_border
+ end
+ alias :text_border :get_text_border
+
+ # Set default text font.
+ # @overload set_text_font(font)
+ # @param [Wx::Font] font
+ # @overload set_text_font(font_info)
+ # @param [Wx::FontInfo] font_info
+ # @overload set_text_font(pointSize, family, style, weight, underline=false, faceName=(''), encoding=Wx::FontEncoding::FONTENCODING_DEFAULT)
+ # @param pointSize [Integer] Size in points. See {Wx::Font#initialize}.
+ # @param family [Wx::FontFamily] The font family. See {Wx::Font#initialize}.
+ # @param style [Wx::FontStyle] One of {Wx::FontStyle::FONTSTYLE_NORMAL}, {Wx::FontStyle::FONTSTYLE_SLANT} and {Wx::FontStyle::FONTSTYLE_ITALIC}. See {Wx::Font#initialize}.
+ # @param weight [Wx::FontWeight] Font weight. One of the {Wx::FontWeight} enumeration values. See {Wx::Font#initialize}.
+ # @param underline [Boolean] The value can be true or false. See {Wx::Font#initialize}.
+ # @param faceName [String] An optional string specifying the face name to be used. See {Wx::Font#initialize}.
+ # @param encoding [Wx::FontEncoding] An encoding which may be one of the enumeration values of {Wx::FontEncoding}. See {Wx::Font#initialize}.
+ def set_text_font(*args)
+ @settings.common_text_font = if args.size == 1 && Wx::Font === args.first
+ args.first
+ else
+ Wx::Font.new(*args)
+ end
+ end
+ alias :text_font= :set_text_font
+
+ # Get default text font.
+ # @return [Wx::Font]
+ def get_text_font
+ @settings.common_text_font
+ end
+ alias :text_font :get_text_font
+
+ # Set default control fill brush.
+ # @overload set_control_fill(brush)
+ # @param [Wx::Brush] brush
+ # @overload set_control_fill(color, style=Wx::BrushStyle::BRUSHSTYLE_SOLID)
+ # @param [Wx::Colour,Symbol,String] color brush color
+ # @param [Wx::BrushStyle] style
+ # @overload set_control_fill(stipple_bitmap)
+ # @param [Wx::Bitmap] stipple_bitmap
+ def set_control_fill(*args)
+ @settings.common_control_fill = if args.size == 1 && Wx::Brush === args.first
+ args.first
+ else
+ Wx::Brush.new(*args)
+ end
+ end
+ alias :control_fill= :set_control_fill
+
+ # Get default control fill brush.
+ # @return [Wx::Brush] Fill brush
+ def get_control_fill
+ @settings.common_control_fill
+ end
+ alias :control_fill :get_control_fill
+
+ # Set default control border.
+ # @overload set_control_border(pen)
+ # @param [Wx::Pen] pen
+ # @overload set_control_border(color, width=1, style=Wx::PenStyle::PENSTYLE_SOLID)
+ # @param [Wx::Colour,String,Symbol] color
+ # @param [Integer] width
+ # @param [Wx::PenStyle] style
+ def set_control_border(*args)
+ @settings.common_control_border = if args.size == 1 && Wx::Pen === args.first
+ args.first
+ else
+ Wx::Pen.new(*args)
+ end
+ end
+ alias :control_border= :set_control_border
+
+ # Get default control border.
+ # @return [Wx::Pen]
+ def get_control_border
+ @settings.common_control_border
+ end
+ alias :control_border :get_control_border
+
+ # Set default control modification fill brush.
+ # @overload set_control_mod_fill(brush)
+ # @param [Wx::Brush] brush
+ # @overload set_control_mod_fill(color, style=Wx::BrushStyle::BRUSHSTYLE_SOLID)
+ # @param [Wx::Colour,Symbol,String] color brush color
+ # @param [Wx::BrushStyle] style
+ # @overload set_control_mod_fill(stipple_bitmap)
+ # @param [Wx::Bitmap] stipple_bitmap
+ def set_control_mod_fill(*args)
+ @settings.common_control_mod_fill = if args.size == 1 && Wx::Brush === args.first
+ args.first
+ else
+ Wx::Brush.new(*args)
+ end
+ end
+ alias :control_mod_fill= :set_control_mod_fill
+
+ # Get default control modification fill brush.
+ # @return [Wx::Brush] Fill brush
+ def get_control_mod_fill
+ @settings.common_control_mod_fill
+ end
+ alias :control_mod_fill :get_control_mod_fill
+
+ # Set default control modification border.
+ # @overload set_control_mod_border(pen)
+ # @param [Wx::Pen] pen
+ # @overload set_control_mod_border(color, width=1, style=Wx::PenStyle::PENSTYLE_SOLID)
+ # @param [Wx::Colour,String,Symbol] color
+ # @param [Integer] width
+ # @param [Wx::PenStyle] style
+ def set_control_mod_border(*args)
+ @settings.common_control_mod_border = if args.size == 1 && Wx::Pen === args.first
+ args.first
+ else
+ Wx::Pen.new(*args)
+ end
+ end
+ alias :control_mod_border= :set_control_mod_border
+
+ # Get default control modification border.
+ # @return [Wx::Pen]
+ def get_control_mod_border
+ @settings.common_control_mod_border
+ end
+ alias :control_mod_border :get_control_mod_border
+
# Get canvas history manager.
# @return [Wx::SF::CanvasHistory] the canvas history manager
# @see Wx::SF::CanvasHistory
def get_history_manager
@canvas_history
@@ -1841,12 +2248,12 @@
# @param [Wx::Point] pos Position which should be updated
# @return [Wx::Point] Updated position
def fit_position_to_grid(pos)
pos = pos.to_point
if has_style?(STYLE::GRID_USE)
- Wx::Point.new(pos.x / @settings.grid_size.x * @settings.grid_size.x,
- pos.y / @settings.grid_size.y * @settings.grid_size.y)
+ Wx::Point.new(pos.x / @settings.grid_size * @settings.grid_size,
+ pos.y / @settings.grid_size * @settings.grid_size)
else
pos
end
end
@@ -1903,11 +2310,11 @@
shape.move_by(dx, dy) unless shape.get_parent_shape
end
move_shapes_from_negatives
end
-
+
# Validate selection (remove redundantly selected shapes etc...).
# @param [Array<Wx::SF::Shape>] selection List of selected shapes that should be validated
def validate_selection(selection)
return unless @diagram
@@ -1929,25 +2336,53 @@
shape = shape.get_parent_shape while shape.get_parent_shape
@diagram.move_to_end(shape)
end
end
+ # Draws shapes intersecting the update region
+ def draw_shape_updates(dc, upd_rct, lst_to_draw, exclude_selected = false)
+ lst_selected = exclude_selected ? [] : nil
+ lst_lines_to_draw = []
+ # draw unselected non line-based shapes first...
+ lst_to_draw.each do |shape|
+ if exclude_selected && (shape.selected? || shape.has_selected_parent?)
+ lst_selected << shape
+ else
+ if !shape.is_a?(LineShape) || shape.stand_alone?
+ if shape.intersects?(upd_rct)
+ parent_shape = shape.get_parent_shape
+ if parent_shape
+ shape.draw(dc, WITHOUTCHILDREN) if !parent_shape.is_a?(LineShape) || parent_shape.stand_alone?
+ else
+ shape.draw(dc, WITHOUTCHILDREN)
+ end
+ end
+ else
+ lst_lines_to_draw << shape
+ end
+ end
+ end
+
+ # ... and draw connections
+ bb_rct = nil
+ lst_lines_to_draw.each do |line|
+ bb_rct = line.get_complete_bounding_box(bb_rct, Shape::BBMODE::SELF | Shape::BBMODE::CHILDREN | Shape::BBMODE::SHADOW)
+ line.draw(dc, line.get_line_mode == LineShape::LINEMODE::READY) if bb_rct.intersects(upd_rct)
+ end
+ lst_selected
+ end
+ private :draw_shape_updates
+
# Function responsible for drawing of the canvas's content to given DC. The default
# implementation draws actual objects managed by assigned diagram manager.
# @param [Wx::DC] dc device context where the shapes will be drawn to
# @param [Boolean] from_paint Set the argument to true if the dc argument refers to the Wx::PaintDC instance
# or derived classes (i.e. the function is called as a response to Wx::EVT_PAINT event)
def draw_content(dc, from_paint)
return unless @diagram
if from_paint
- # wxRect updRct
- bb_rct = Wx::Rect.new
- #
- # ShapeList m_lstToDraw
- lst_lines_to_draw = []
-
# get all existing shapes
lst_to_draw = @diagram.get_shapes(Shape, Shape::SEARCHMODE::DFS)
upd_rct = nil
# get the update rect list
@@ -1962,55 +2397,17 @@
end
end
upd_rct ||= Wx::Rect.new
if @working_mode == MODE::SHAPEMOVE
- # draw unselected non line-based shapes first...
- lst_to_draw.each do |shape|
- parent_shape = shape.get_parent_shape
+ # draw unselected shapes first and filter and return selected shapes
+ lst_selected = draw_shape_updates(dc, upd_rct, lst_to_draw, true)
- if !shape.is_a?(LineShape) || shape.stand_alone?
- if shape.intersects?(upd_rct)
- if parent_shape
- shape.draw(dc, WITHOUTCHILDREN) if !parent_shape.is_a?(LineShape) || parent_shape.stand_alone?
- else
- shape.draw(dc, WITHOUTCHILDREN)
- end
- end
- else
- lst_lines_to_draw << shape
- end
- end
-
- # ... and draw connections
- lst_lines_to_draw.each do |line|
- line.get_complete_bounding_box(bb_rct, Shape::BBMODE::SELF | Shape::BBMODE::CHILDREN | Shape::BBMODE::SHADOW)
- line.draw(dc, line.get_line_mode == LineShape::LINEMODE::READY) if bb_rct.intersects(upd_rct)
- end
+ # ... and now draw the selected shapes being moved
+ draw_shape_updates(dc, upd_rct, lst_selected)
else
- # draw parent shapes (children are processed by parent objects)
- lst_to_draw.each do |shape|
- parent_shape = shape.get_parent_shape
-
- if !shape.is_a?(LineShape) || shape.stand_alone?
- if shape.intersects?(upd_rct)
- if parent_shape
- shape.draw(dc, WITHOUTCHILDREN) if !parent_shape.is_a?(LineShape) || shape.stand_alone?
- else
- shape.draw(dc, WITHOUTCHILDREN)
- end
- end
- else
- lst_lines_to_draw << shape
- end
- end
-
- # draw connections
- lst_lines_to_draw.each do |line|
- line.get_complete_bounding_box(bb_rct, Shape::BBMODE::SELF | Shape::BBMODE::CHILDREN)
- line.draw(dc, line.get_line_mode == LineShape::LINEMODE::READY) if bb_rct.intersects(upd_rct)
- end
+ draw_shape_updates(dc, upd_rct, lst_to_draw)
end
# draw multiselection if necessary
@shp_selection.draw(dc) if @shp_selection.visible?
@shp_multi_edit.draw(dc) if @shp_multi_edit.visible?
@@ -2033,11 +2430,11 @@
# @param [Boolean] _from_paint Set the argument to true if the dc argument refers to the Wx::PaintDC instance
# or derived classes (i.e. the function is called as a response to Wx::EVT_PAINT event)
def draw_background(dc, _from_paint)
# erase background
if has_style?(STYLE::GRADIENT_BACKGROUND)
- bcg_size = @settings.grid_size + get_virtual_size
+ bcg_size = get_virtual_size.to_size + @settings.grid_size
if @settings.scale != 1.0
dc.gradient_fill_linear(Wx::Rect.new([0, 0], [(bcg_size.x/@settings.scale).to_i, (bcg_size.y/@settings.scale).to_i]),
@settings.gradient_from, @settings.gradient_to, Wx::SOUTH)
else
dc.gradient_fill_linear(Wx::Rect.new(Wx::Point.new(0, 0), bcg_size),
@@ -2048,14 +2445,14 @@
dc.clear
end
# show grid
if has_style?(STYLE::GRID_SHOW)
- linedist = @settings.grid_size.x * @settings.grid_line_mult
+ linedist = @settings.grid_size * @settings.grid_line_mult
if (linedist * @settings.scale) > 3.0
- grid_rct = Wx::Rect.new([0, 0], @settings.grid_size + get_virtual_size)
+ grid_rct = Wx::Rect.new([0, 0], get_virtual_size.to_size + @settings.grid_size)
max_x = (grid_rct.right/@settings.scale).to_i
max_y = (grid_rct.bottom/@settings.scale).to_i
dc.set_pen(Wx::Pen.new(@settings.grid_color, 1, @settings.grid_style))
(grid_rct.left..max_x).step(linedist) do |x|
@@ -2098,15 +2495,15 @@
# HINT: override it for custom actions...
return unless @diagram
_notify_canvas_change(CHANGE::FOCUS)
set_focus
-
+
lpos = dp2lp(event.get_position)
-
+
@can_save_state_on_mouse_up = false
-
+
case @working_mode
when MODE::READY
@selected_handle = get_topmost_handle_at_position(lpos)
if event.control_down && event.shift_down
@@ -2127,10 +2524,14 @@
if selected_shape
# perform selection
lst_selection = get_selected_shapes
+ if @selection_mode == SELECTIONMODE::NORMAL
+ save_canvas_state if @canvas_history.empty?
+ end
+
# cancel previous selections if necessary...
if @selection_mode == SELECTIONMODE::NORMAL && (selected_top_shape.nil? || !lst_selection.include?(selected_top_shape))
deselect_all
end
selected_top_shape.select(@selection_mode != SELECTIONMODE::REMOVE) if selected_top_shape
@@ -2157,11 +2558,11 @@
lst_selection.each do |shape|
shape.send(:_on_begin_drag, fit_pos)
# inform also connections assigned to the shape and its children
lst_connections.clear
- append_assigned_connections(shape, lst_connections, true)
+ append_assigned_connections(shape, lst_connections)
lst_connections.each do |line|
line.send(:_on_begin_drag, fit_pos)
end
end
@@ -2194,10 +2595,11 @@
end
# update canvas
invalidate_visible_rect
else
+ save_canvas_state if @canvas_history.empty?
if @selected_handle.get_parent_shape == @shp_multi_edit
if has_style?(STYLE::MULTI_SIZE_CHANGE)
@working_mode = MODE::MULTIHANDLEMOVE
else
@working_mode = MODE::READY
@@ -2227,32 +2629,32 @@
while shape_under && shape_under.has_style?(Shape::STYLE::PROPAGATE_INTERACTIVE_CONNECTION)
shape_under = shape_under.get_parent_shape
end
# finish connection's creation process if possible
if shape_under && !event.control_down
- if @new_line_shape.get_trg_shape_id.nil? && (shape_under != @new_line_shape) &&
- shape_under.get_id && (shape_under.is_connection_accepted(@new_line_shape.class))
+ if @new_line_shape.get_trg_shape.nil? && (shape_under != @new_line_shape) &&
+ (shape_under.is_connection_accepted(@new_line_shape.class))
# find out whether the target shape can be connected to the source shape
- source_shape = @diagram.find_shape(@new_line_shape.get_src_shape_id)
+ source_shape = @new_line_shape.get_src_shape
if source_shape &&
shape_under.is_src_neighbour_accepted(source_shape.class) &&
source_shape.is_trg_neighbour_accepted(shape_under.class)
- @new_line_shape.set_trg_shape_id(shape_under.get_id)
+ @new_line_shape.set_trg_shape(shape_under)
@new_line_shape.set_ending_connection_point(shape_under.get_nearest_connection_point(lpos.to_real))
# inform user that the line is completed
case on_pre_connection_finished(@new_line_shape)
when PRECON_FINISH_STATE::OK
when PRECON_FINISH_STATE::FAILED_AND_CANCEL_LINE
- @new_line_shape.set_trg_shape_id(nil)
+ @new_line_shape.set_trg_shape(nil)
@diagram.remove_shape(@new_line_shape)
@working_mode = MODE::READY
@new_line_shape = nil
return
when PRECON_FINISH_STATE::FAILED_AND_CONTINUE_EDIT
- @new_line_shape.set_trg_shape_id(nil)
+ @new_line_shape.set_trg_shape(nil)
return
end
@new_line_shape.create_handles
# switch off the "under-construction" mode
@@ -2268,21 +2670,21 @@
save_canvas_state
end
end
else
- if @new_line_shape.get_src_shape_id
+ if @new_line_shape.get_src_shape
fit_pos = fit_position_to_grid(lpos)
@new_line_shape.get_control_points << Wx::RealPoint.new(fit_pos.x, fit_pos.y)
end
end
end
else
@working_mode = MODE::READY
end
-
+
refresh_invalidated_rect
end
# Event handler called when the canvas is double-clicked by
# the left mouse button. The function can be overridden if necessary.
@@ -2325,11 +2727,11 @@
# @param [Wx::MouseEvent] event Mouse event
# @see _on_left_up
def on_left_up(event)
# HINT: override it for custom actions...
lpos = dp2lp(event.get_position)
-
+
case @working_mode
when MODE::MULTIHANDLEMOVE, MODE::HANDLEMOVE
# resize parent shape to fit all its children if necessary
if @selected_handle.get_parent_shape.get_parent_shape
@selected_handle.get_parent_shape.get_parent_shape.update
@@ -2341,52 +2743,56 @@
when Shape::Handle::TYPE::LINESTART, Shape::Handle::TYPE::LINEEND
line = @selected_handle.get_parent_shape
line.send(:set_line_mode, LineShape::LINEMODE::READY)
parent_shape = get_shape_under_cursor
+ # propagate request for interactive connection if requested
+ while parent_shape && parent_shape.has_style?(Shape::STYLE::PROPAGATE_INTERACTIVE_CONNECTION)
+ parent_shape = parent_shape.get_parent_shape
+ end
if parent_shape && (parent_shape != line) && (parent_shape.is_connection_accepted(line.class))
if @selected_handle.get_type == Shape::Handle::TYPE::LINESTART
- trg_shape = @diagram.find_shape(line.get_trg_shape_id)
+ trg_shape = line.get_trg_shape
if trg_shape && parent_shape.is_trg_neighbour_accepted(trg_shape.class)
- line.set_src_shape_id(parent_shape.get_id)
+ line.set_src_shape(parent_shape)
end
else
- src_shape = @diagram.find_shape(line.get_src_shape_id)
+ src_shape = line.get_src_shape
if src_shape && parent_shape.is_src_neighbour_accepted(src_shape.class)
- line.set_trg_shape_id(parent_shape.get_id)
+ line.set_trg_shape(parent_shape)
end
end
end
end
@selected_handle.send(:_on_end_drag, lpos)
@selected_handle = nil
- save_canvas_state if @can_save_state_on_mouse_up
+ save_canvas_state if @can_save_state_on_mouse_up
when MODE::SHAPEMOVE
lst_selection = get_selected_shapes
-
+
lst_selection.each do |shape|
# notify shape
shape.send(:_on_end_drag, lpos)
# reparent based on new position
- reparent_shape(shape, lpos)
+ reparent_dropped_shape(shape, lpos)
end
-
+
if lst_selection.size>1
@shp_multi_edit.show(true)
@shp_multi_edit.show_handles(true)
else
@shp_multi_edit.show(false)
end
-
+
move_shapes_from_negatives
save_canvas_state if @can_save_state_on_mouse_up
-
+
when MODE::MULTISELECTION
lst_selection = get_selected_shapes
sel_rect = @shp_selection.get_bounding_box
@current_shapes.each do |shape|
@@ -2414,11 +2820,11 @@
@shp_multi_edit.show_handles(true)
end
@shp_selection.show(false)
end
-
+
if @working_mode != MODE::CREATECONNECTION
# update canvas
@working_mode = MODE::READY
update_multiedit_size
update_virtual_size
@@ -2440,23 +2846,26 @@
def on_right_down(event)
# HINT: override it for custom actions...
_notify_canvas_change(CHANGE::FOCUS)
set_focus
-
+
lpos = dp2lp(event.get_position)
-
+
if @working_mode == MODE::READY
deselect_all
-
+
shape = get_shape_under_cursor
+ while shape && shape.has_style?(Shape::STYLE::PROPAGATE_SELECTION)
+ shape = shape.get_parent_shape
+ end
if shape
shape.select(true)
shape.on_right_click(lpos)
end
end
-
+
refresh(false)
end
# Event handler called when the canvas is double-clicked by
# the right mouse button. The function can be overridden if necessary.
@@ -2470,13 +2879,13 @@
def on_right_double_click(event)
# HINT: override it for custom actions...
_notify_canvas_change(CHANGE::FOCUS)
set_focus
-
+
lpos = dp2lp(event.get_position)
-
+
if @working_mode == MODE::READY
shape = get_shape_under_cursor
shape.on_right_double_click(lpos) if shape
end
@@ -2506,13 +2915,13 @@
# @param [Wx::MouseEvent] event Mouse event
# @see _on_mouse_move
def on_mouse_move(event)
# HINT: override it for custom actions...
return unless @diagram
-
+
lpos = dp2lp(event.get_position)
-
+
case @working_mode
when MODE::READY, MODE::CREATECONNECTION
unless event.dragging
# send event to multiedit shape
@shp_multi_edit.send(:_on_mouse_move, lpos) if @shp_multi_edit.visible?
@@ -2520,21 +2929,17 @@
# send event to all user shapes
@current_shapes.each { |shape| shape.send(:_on_mouse_move, lpos) }
# update unfinished line if any
if @new_line_shape
- line_rct = Wx::Rect.new
- upd_line_rct = Wx::Rect.new
- @new_line_shape.get_complete_bounding_box(line_rct, Shape::BBMODE::SELF | Shape::BBMODE::CHILDREN)
+ line_rct = @new_line_shape.get_complete_bounding_box(nil, Shape::BBMODE::SELF | Shape::BBMODE::CHILDREN)
@new_line_shape.send(:set_unfinished_point, fit_position_to_grid(lpos))
@new_line_shape.update
- @new_line_shape.get_complete_bounding_box(upd_line_rct, Shape::BBMODE::SELF | Shape::BBMODE::CHILDREN)
+ line_rct = @new_line_shape.get_complete_bounding_box(line_rct, Shape::BBMODE::SELF | Shape::BBMODE::CHILDREN)
- line_rct.union!(upd_line_rct)
-
invalidate_rect(line_rct)
end
end
when MODE::HANDLEMOVE, MODE::MULTIHANDLEMOVE, MODE::SHAPEMOVE
@@ -2550,12 +2955,12 @@
end
end
unless @working_mode == MODE::MULTIHANDLEMOVE
if event.dragging
if has_style?(STYLE::GRID_USE)
- return if (event.get_position.x - @prev_mouse_pos.x).abs < @settings.grid_size.x &&
- (event.get_position.y - @prev_mouse_pos.y).abs < @settings.grid_size.y
+ return if (event.get_position.x - @prev_mouse_pos.x).abs < @settings.grid_size &&
+ (event.get_position.y - @prev_mouse_pos.y).abs < @settings.grid_size
end
@prev_mouse_pos = event.get_position
if event.control_down || event.shift_down
lst_selection = get_selected_shapes
@@ -2570,11 +2975,11 @@
shape.send(:_on_dragging, fit_position_to_grid(lpos))
# move also connections assigned to this shape and its children
lst_connections.clear
- append_assigned_connections(shape, lst_connections,true)
+ append_assigned_connections(shape, lst_connections)
lst_connections.each { |line| line.send(:_on_dragging, fit_position_to_grid(lpos)) }
# update connections assigned to this shape
lst_connections = @diagram.get_assigned_connections(shape, LineShape, Shape::CONNECTMODE::BOTH)
@@ -2605,11 +3010,11 @@
@shp_selection.set_relative_position(selection_pos)
@shp_selection.set_rect_size(selection_size)
invalidate_visible_rect
end
-
+
refresh_invalidated_rect
end
# Event handler called when the mouse wheel position is changed.
# The function can be overridden if necessary.
@@ -2619,22 +3024,21 @@
# this function from overridden methods if the default canvas behaviour
# should be preserved.
# @param [Wx::MouseEvent] event Mouse event
def on_mouse_wheel(event)
# HINT: override it for custom actions...
-
if event.control_down
scale = get_scale
- scale += (event.get_wheel_rotation/(event.get_wheel_delta*10)).to_f
+ scale += event.get_wheel_rotation.to_f/(event.get_wheel_delta*10)
scale = @settings.min_scale if scale < @settings.min_scale
scale = @settings.max_scale if scale > @settings.max_scale
-
+
set_scale(scale)
refresh(false)
end
-
+
event.skip
end
# Event handler called when any key is pressed.
# The function can be overridden if necessary.
@@ -2648,11 +3052,11 @@
def on_key_down(event)
# HINT: override it for custom actions...
return unless @diagram
lst_selection = get_selected_shapes
-
+
case event.get_key_code
when Wx::K_DELETE
# send event to selected shapes
lst_selection.delete_if do |shape|
if shape.has_style?(Shape::STYLE::PROCESS_DEL)
@@ -2682,11 +3086,18 @@
line = @selected_handle.get_parent_shape
line.send(:set_line_mode, LineShape::LINEMODE::READY)
@selected_handle = nil
end
+ restore_current_state
+ when MODE::MULTIHANDLEMOVE
+ restore_current_state
+
+ when MODE::SHAPEMOVE
+ restore_current_state
+
else
# send event to selected shapes
lst_selection.each { |shape| shape.send(:_on_key, event.get_key_code) }
end
@working_mode = MODE::READY
@@ -2694,20 +3105,20 @@
when Wx::K_LEFT, Wx::K_RIGHT, Wx::K_UP, Wx::K_DOWN
lst_connections = []
lst_selection.each do |shape|
shape.send(:_on_key, event.get_key_code)
-
+
# inform also connections assigned to this shape
lst_connections.clear
- append_assigned_connections(shape, lst_connections, true)
-
+ append_assigned_connections(shape, lst_connections)
+
lst_connections.each do |line|
line.send(:_on_key, event.get_key_code) unless line.selected?
end
end
-
+
# send the event to multiedit ctrl if displayed
@shp_multi_edit.send(:_on_key, event.get_key_code) if @shp_multi_edit.visible?
refresh_invalidated_rect
save_canvas_state
@@ -2725,13 +3136,13 @@
# @param [Wx::SF::EditTextShape] shape Changed Wx::SF::EditTextShape object
# @see Wx::SF::EditTextShape#edit_label
# @see Wx::SF::ShapeTextEvent
def on_text_change(shape)
# HINT: override it for custom actions...
-
+
# ... standard implementation generates the Wx::SF::EVT_SF_TEXT_CHANGE event.
- id = shape ? shape.get_id : nil
+ id = shape ? shape.object_id : nil
event = ShapeTextEvent.new(Wx::SF::EVT_SF_TEXT_CHANGE, id)
event.set_shape(shape)
event.set_text(shape.get_text)
process_event(event)
@@ -2743,13 +3154,13 @@
# @param [Wx::SF::LineShape,nil] connection new connection object (nil if cancelled)
# @see start_interactive_connection
# @see Wx::SF::ShapeEvent
def on_connection_finished(connection)
# HINT: override to perform user-defined actions...
-
+
# ... standard implementation generates the Wx::SF::EVT_SF_LINE_DONE event.
- id = connection ? connection.get_id : -1
+ id = connection ? connection.object_id : -1
event = ShapeEvent.new(Wx::SF::EVT_SF_LINE_DONE, id)
event.set_shape(connection)
process_event(event)
end
@@ -2764,14 +3175,14 @@
# if the generated event has been vetoed the connection creation is cancelled
# @see start_interactive_connection
# @see Wx::SF::ShapeEvent
def on_pre_connection_finished(connection)
# HINT: override to perform user-defined actions...
-
+
# ... standard implementation generates the Wx::SF::EVT_SF_LINE_DONE event.
- id = connection ? connection.get_id : -1
-
+ id = connection ? connection.object_id : -1
+
event = ShapeEvent.new(Wx::SF::EVT_SF_LINE_BEFORE_DONE, id)
event.set_shape(connection)
process_event(event)
return PRECON_FINISH_STATE::FAILED_AND_CANCEL_LINE if event.vetoed?
@@ -2790,14 +3201,14 @@
# @param [Array<Wx::SF::Shape>] dropped a list containing the dropped data
# @see Wx::SF::CanvasDropTarget
# @see Wx::SF::ShapeDropEvent
def on_drop(x, y, deflt, dropped)
# HINT: override it for custom actions...
-
+
# ... standard implementation generates the Wx::SF::EVT_SF_ON_DROP event.
return unless has_style?(STYLE::DND)
-
+
# create the drop event and process it
event = ShapeDropEvent.new(Wx::SF::EVT_SF_ON_DROP, x, y, self, deflt, Wx::ID_ANY)
event.set_dropped_shapes(dropped)
process_event(event)
end
@@ -2810,14 +3221,14 @@
# @param [Array<Wx::SF::Shape>] pasted a list containing the pasted data
# @see Wx::SF::ShapeCanvas#paste
# @see Wx::SF::ShapePasteEvent
def on_paste(pasted)
# HINT: override it for custom actions...
-
+
# ... standard implementation generates the Wx::SF::EVT_SF_ON_PASTE event.
return unless has_style?(STYLE::CLIPBOARD)
-
+
# create the drop event and process it
event = ShapePasteEvent.new(Wx::SF::EVT_SF_ON_PASTE, self, Wx::ID_ANY)
event.set_pasted_shapes(pasted)
process_event(event)
end
@@ -2840,50 +3251,69 @@
# Validate selection so the shapes in the given list can be processed by the clipboard functions
# @param [Array<Wx::SF::Shape>] selection
# @param [Boolean] storeprevpos
def validate_selection_for_clipboard(selection, storeprevpos)
- selection.dup.each do |shape|
+ # first remove any shapes not eligible for copying
+ selection.reject! do |shape|
+ do_reject = false
if shape.get_parent_shape
# remove child shapes without parent in the selection and without STYLE::PARENT_CHANGE style
# defined from the selection
if !shape.has_style?(Shape::STYLE::PARENT_CHANGE) && !selection.include?(shape.get_parent_shape)
- selection.delete(shape)
+ do_reject = true
else
# convert relative position to absolute position if the shape is copied
# without its parent
unless selection.include?(shape.get_parent_shape)
store_prev_position(shape) if storeprevpos
shape.set_relative_position(shape.get_absolute_position)
end
end
+ elsif LineShape === shape && !shape.stand_alone?
+ # remove any stand alone LineShape for which the source or target shape are not included in the selection
+ # (or any of it's child shapes)
+ unless (selection.include?(shape.get_src_shape) ||
+ selection.any? { |selshp| selshp.include_child_shape?(shape.get_src_shape, true) }) &&
+ (selection.include?(shape.get_trg_shape) ||
+ selection.any? { |selshp| selshp.include_child_shape?(shape.get_trg_shape, true) })
+ do_reject = true
+ end
end
-
- append_assigned_connections(shape, selection, false)
+ do_reject
end
+
+ # now append all connections for which source AND target are included in the selection
+ selection.each do |shape|
+ append_assigned_connections(shape, selection, children_only: false, complete_only: true)
+ end
end
- # Append connections assigned to shapes in given list to this list as well
- # @param [Wx::SF::Shape] shape
- # @param [Array<Wx::SF::Shape>] selection
- # @param [Boolean] childrenonly
- def append_assigned_connections(shape, selection, childrenonly)
+ # Append connections assigned to shapes in given list to this list as well
+ # @param [Wx::SF::Shape] shape shape
+ # @param [Array<Wx::SF::Shape>] selection selected shapes
+ # @param [Boolean] children_only appends connections from child shapes only
+ # @param [Boolean] complete_only append complete (src and trg shapes in selection) only
+ def append_assigned_connections(shape, selection, children_only: true, complete_only: false)
# add connections assigned to copied topmost shapes and their children to the copy list
lst_children = shape.get_child_shapes(ANY, RECURSIVE)
-
+
# get connections assigned to the parent shape
- lst_connections = @diagram.get_assigned_connections(shape, LineShape, Shape::CONNECTMODE::BOTH) unless childrenonly
+ lst_connections = @diagram.get_assigned_connections(shape, LineShape, Shape::CONNECTMODE::BOTH) unless children_only
lst_connections ||= []
- # get connections assigned to its child shape
+ # get connections assigned to its child shape(s)
lst_children.each do |child|
- # get connections assigned to the child shape
+ # get connections assigned to the child shape(s)
@diagram.get_assigned_connections(child, LineShape, Shape::CONNECTMODE::BOTH, lst_connections)
end
-
+
# insert connections to the copy list
lst_connections.each do |line|
- selection << line unless selection.include?(line)
+ selection << line unless selection.include?(line) ||
+ (complete_only &&
+ !(selection.include?(line.get_src_shape) &&
+ selection.include?(line.get_trg_shape)))
end
end
# Remove given shape for temporary containers
# @param [Wx::SF::Shape] shape
@@ -2895,11 +3325,11 @@
@selected_shape_under_cursor = nil if @selected_shape_under_cursor == shape
@topmost_shape_under_cursor = nil if @topmost_shape_under_cursor == shape
end
end
- # Clear all temporary containers
+ # Clear all temporary containers
def clear_temporaries
@current_shapes.clear
@new_line_shape = nil
@unselected_shape_under_cursor = nil
@selected_shape_under_cursor = nil
@@ -2907,23 +3337,46 @@
end
# Assign give shape to parent at given location (if exists)
# @param [Wx::SF::Shape] shape
# @param [Wx::Point] parentpos
- def reparent_shape(shape, parentpos)
+ def reparent_dropped_shape(shape, parentpos)
return unless @diagram
- # is shape dropped into accepting shape?
- parent_shape = get_shape_at_position(parentpos, 1, SEARCHMODE::UNSELECTED)
- parent_shape = nil if parent_shape && !parent_shape.is_child_accepted(shape.class)
-
- # set new parent
+ # set new parent if possible
if shape.has_style?(Shape::STYLE::PARENT_CHANGE) && !shape.is_a?(LineShape)
+ # is shape dropped into accepting shape?
+
+ # get all shapes at drop position in reversed z-order
+ shapes_at_pos = get_shapes_at_position(parentpos).reverse
+ # see if we can find a non-LineShape drop target
+ parent_shape = shapes_at_pos.find do |s|
+ # consider non-LineShapes that are unselected and not the dropped shape itself or one of it's (grand-)children
+ !s.is_a?(Wx::SF::LineShape) && !s.selected? && shape != s && !shape.include_child_shape?(s)
+ end
+ # if none found consider line shapes
+ parent_shape = shapes_at_pos.find do |s|
+ # consider LineShapes that are unselected and not the dropped shape itself or one of it's (grand-)children
+ s.is_a?(Wx::SF::LineShape) && !s.selected? && shape != s && !shape.include_child_shape?(s)
+ end unless parent_shape
+ # In case the matching shape does not accept the dropped child and has style PROPAGATE_DROPPING
+ # see if this shape has a parent that does also matches the position and DOES accept the child.
+ # This allows dropping shapes onto child shapes inside a (container) shapes like
+ # grids and/or boxes.
+ while parent_shape && !parent_shape.is_child_accepted(shape.class)
+ parent_shape = parent_shape.has_style?(Shape::STYLE::PROPAGATE_DROPPING) ? parent_shape.parent_shape : nil
+ parent_shape = nil if parent_shape && !parent_shape.get_bounding_box.contains?(parentpos)
+ end
+ # parent_shape = nil if parent_shape && !parent_shape.is_child_accepted(shape.class)
+
prev_parent = shape.get_parent_shape
-
+
if parent_shape
- if parent_shape.get_parent_shape != shape
+ # in rare cases (where childs are expanded to fill a parent and have PROPAGATE_SELECTION)
+ # the matched drop parent may actually a child of the shape being dropped
+ # guard against that (since that would lead to illegal circular references)
+ if parent_shape != shape && !shape.include_child_shape?(parent_shape, true)
# update relative position to new parent
apos = shape.get_absolute_position - parent_shape.get_absolute_position
shape.set_relative_position(apos)
# reparent
@diagram.reparent_shape(shape, parent_shape)
@@ -2936,13 +3389,13 @@
shape.move_by(prev_parent.get_absolute_position) if prev_parent
# reparent
@diagram.reparent_shape(shape, parent_shape)
end
end
-
+
prev_parent.update if prev_parent
- parent_shape.update if parent_shape
+ parent_shape.update if parent_shape && parent_shape != prev_parent
end
end
# Store previous shape's position modified in validate_selection_for_clipboard() function
# @param [Wx::SF::Shape] shape
@@ -3001,96 +3454,96 @@
when MODE::HANDLEMOVE
when MODE::MULTIHANDLEMOVE
else
@working_mode = MODE::READY
end
-
+
event.skip
end
# Event handler called when the mouse pointer enters the canvas window.
# @param [Wx::MouseEvent] event Mouse event
def _on_enter_window(event)
@prev_mouse_pos = event.get_position
-
+
lpos = dp2lp(event.get_position)
-
+
case @working_mode
when MODE::MULTISELECTION
unless event.left_is_down
update_multiedit_size
@shp_multi_edit.show(false)
@working_mode = MODE::READY
-
+
invalidate_visible_rect
end
when MODE::HANDLEMOVE
unless event.left_is_down
if @selected_handle
if @selected_handle.get_parent_shape.is_a?(LineShape)
@selected_handle.get_parent_shape.send(:set_line_mode, LineShape::LINEMODE::READY)
end
-
+
@selected_handle.send(:_on_end_drag, lpos)
-
+
save_canvas_state
@working_mode = MODE::READY
@selected_handle = nil
-
+
invalidate_visible_rect
end
end
when MODE::MULTIHANDLEMOVE
unless event.left_is_down
if @selected_handle
@selected_handle.send(:_on_end_drag, lpos)
-
+
save_canvas_state
@working_mode = MODE::READY
-
+
invalidate_visible_rect
end
end
when MODE::SHAPEMOVE
unless event.left_is_down
lst_selection = get_selected_shapes
-
+
move_shapes_from_negatives
update_virtual_size
-
+
if lst_selection.size > 1
update_multiedit_size
@shp_multi_edit.show(true)
@shp_multi_edit.show_handles(true)
end
-
+
lst_selection.each { |shape| shape.send(:_on_end_drag, lpos) }
@working_mode = MODE::READY
-
+
invalidate_visible_rect
end
end
-
+
refresh_invalidated_rect
-
+
event.skip
end
# Event handler called when the canvas size has changed.
# @param [Wx::SizeEvent] event Size event
def _on_resize(event)
refresh(false) if has_style?(STYLE::GRADIENT_BACKGROUND)
event.skip
end
-
+
# original private event handlers
-
+
# Original private event handler called when the canvas is clicked by
# left mouse button. The handler calls user-overridable event handler function
# and skips the event for next possible processing.
# @param [Wx::MouseEvent] event Mouse event
# @see Wx::SF::ShapeCanvas#on_left_down
@@ -3202,11 +3655,11 @@
# @param [Wx::DragResult] deflt Drag result
# @param [Wx::ShapeDataObject] data a data object encapsulating dropped data
# @see Wx::SF::CanvasDropTarget
def _on_drop(x, y, deflt, data)
if data && Wx::SF::ShapeDataObject === data
- lst_new_content = Wx::SF::Serializable.deserialize(data.get_data_here)
+ lst_new_content = data.get_as_shapes
if lst_new_content && !lst_new_content.empty?
lst_parents_to_update = []
lpos = dp2lp(Wx::Point.new(x, y))
dx = 0
@@ -3215,16 +3668,24 @@
dx = lpos.x - @dnd_started_at.x
dy = lpos.y - @dnd_started_at.y
end
parent = @diagram.get_shape_at_position(lpos, 1, SEARCHMODE::UNSELECTED)
+ # In case the located shape does not accept ANY children and has style PROPAGATE_DROPPING
+ # see if this shape has a parent that does also match the position and DOES accept children.
+ # This allows dropping shapes onto child shapes inside a (container) shapes like
+ # grids and/or boxes.
+ while parent&.does_not_accept_children?
+ parent = parent.has_style?(Shape::STYLE::PROPAGATE_DROPPING) ? parent.parent_shape : nil
+ parent = nil if parent && !parent.get_bounding_box.contains?(lpos)
+ end
# add each shape to diagram keeping only those that are accepted
lst_new_content.select! do |shape|
shape.move_by(dx, dy)
# do not reparent connection lines
- rc = if shape.is_a?(LineShape) && !shape.stand_alone?
+ rc = if (shape.is_a?(LineShape) && !shape.stand_alone?) || parent.nil?
@diagram.add_shape(shape,
nil,
lp2dp(shape.get_absolute_position.to_point),
INITIALIZE,
DONT_SAVE_STATE)
@@ -3237,11 +3698,11 @@
end
rc == ERRCODE::OK # keep or remove?
end
# verify newly added shapes (may remove shapes from list)
- @diagram.send(:check_new_shapes, lst_new_content)
+ @diagram.send(:on_import, lst_new_content)
update_virtual_size # update for new shapes
# notify parents and collect for update
lst_new_content.each do |shape|
@@ -3264,10 +3725,10 @@
# call user-defined drop handler
on_drop(x, y, deflt, lst_new_content)
end
end
end
-
+
end
def _notify_canvas_change(change, *args)
@diagram.get_all_shapes.each { |shape| shape.send(:_on_canvas, change, *args) } if @diagram
end