package App::Asciio::stripes::section_wirl_arrow ; use base App::Asciio::stripes::stripes ; use strict; use warnings; use List::Util qw(min max) ; use Readonly ; use Clone ; use App::Asciio::stripes::wirl_arrow ; #----------------------------------------------------------------------------- # the idea is to reuse wirl arrow implementation as much as possible #----------------------------------------------------------------------------- Readonly my $DEFAULT_ARROW_TYPE => [ ['origin', '', '*', '', '', '', 1], ['up', '|', '|', '', '', '^', 1], ['down', '|', '|', '', '', 'v', 1], ['left', '-', '-', '', '', '<', 1], ['upleft', '|', '|', '.', '-', '<', 1], ['leftup', '-', '-', '\'', '|', '^', 1], ['downleft', '|', '|', '\'', '-', '<', 1], ['leftdown', '-', '-', '.', '|', 'v', 1], ['right', '-', '-','', '', '>', 1], ['upright', '|', '|', '.', '-', '>', 1], ['rightup', '-', '-', '\'', '|', '^', 1], ['downright', '|', '|', '\'', '-', '>', 1], ['rightdown', '-', '-', '.', '|', 'v', 1], ['45', '/', '/', '', '', '^', 1, ], ['135', '\\', '\\', '', '', 'v', 1, ], ['225', '/', '/', '', '', 'v', 1, ], ['315', '\\', '\\', '', '', '^', 1, ], ] ; # constants for connector overlays Readonly my $body_index => 2 ; Readonly my $connection_index => 3 ; Readonly my $up_index=> 1 ; Readonly my $left_index=> 3 ; Readonly my $leftup_index => 5 ; Readonly my $leftdown_index => 7 ; sub new { my ($class, $element_definition) = @_ ; my $self = bless {}, __PACKAGE__ ; $self->setup ( $element_definition->{ARROW_TYPE} || Clone::clone($DEFAULT_ARROW_TYPE), $element_definition->{POINTS}, $element_definition->{DIRECTION}, $element_definition->{ALLOW_DIAGONAL_LINES}, $element_definition->{EDITABLE}, $element_definition->{NOT_CONNECTABLE_START}, $element_definition->{NOT_CONNECTABLE_END}, ) ; return $self ; } #----------------------------------------------------------------------------- sub setup { my ($self, $arrow_type, $points, $direction, $allow_diagonal_lines, $editable, $not_connectable_start, $not_connectable_end) = @_ ; if('ARRAY' eq ref $points && @{$points} > 0) { my ($start_x, $start_y, $arrows) = (0, 0, []) ; my $points_offsets ; my $arrow_index = 0 ; # must have a numeric index or 'undo' won't work for my $point (@{$points}) { my ($x, $y, $point_direction) = @{$point} ; my $arrow = new App::Asciio::stripes::wirl_arrow ({ ARROW_TYPE => $arrow_type, END_X => $x - $start_x, END_Y => $y - $start_y, DIRECTION => $point_direction || $direction, ALLOW_DIAGONAL_LINES => $allow_diagonal_lines, EDITABLE => $editable, }) ; $points_offsets->[$arrow_index++] = [$start_x, $start_y] ; push @{$arrows}, $arrow ; ($start_x, $start_y) = ($x, $y) ; } $self->set ( POINTS_OFFSETS => $points_offsets, ARROWS => $arrows, # keep data to allow section insertion later ARROW_TYPE => $arrow_type, DIRECTION => $direction, ALLOW_DIAGONAL_LINES => $allow_diagonal_lines, EDITABLE => $editable, NOT_CONNECTABLE_START => $not_connectable_start, NOT_CONNECTABLE_END => $not_connectable_end, ) ; my ($width, $height) = $self->get_width_and_height() ; $self->set ( WIDTH => $width, HEIGHT => $height, ) ; } else { die "Bad 'section wirl arrow' defintion! Expecting points array." ; } } #----------------------------------------------------------------------------- my %diagonal_direction_to_overlay_character = ( (map {$_ => q{\\}} qw( down-right right-down up-left left-up)), (map {$_ => q{/}} qw( down-left left-down up-right right-up)), ) ; my %diagonal_non_diagonal_to_overlay_character = ( (map {$_ => q{.}} qw( down-right right-down up-left left-up)), (map {$_ => q{'}} qw( down-left left-down up-right right-up)), ) ; sub get_mask_and_element_stripes { my ($self) = @_ ; my @mask_and_element_stripes ; my $arrow_index = 0 ; for my $arrow(@{$self->{ARROWS}}) { push @mask_and_element_stripes, map { $_->{X_OFFSET} += $self->{POINTS_OFFSETS}[$arrow_index][0] ; $_->{Y_OFFSET} += $self->{POINTS_OFFSETS}[$arrow_index][1]; $_ ; } $arrow->get_mask_and_element_stripes() ; $arrow_index++ ; } # handle connections my ($previous_direction) = ($self->{ARROWS}[0]{DIRECTION} =~ /^([^-]+)-/) ; $previous_direction ||= $self->{ARROWS}[0]{DIRECTION} ; my $previous_was_diagonal ; $arrow_index = 0 ; for my $arrow(@{$self->{ARROWS}}) { # the whole computation can be skipped if a single section is present my ($connection, $d1, $d2) ; unless(($d1, $d2) = ($arrow->{DIRECTION} =~ /^([^-]+)-(.*)$/)) { $d1 = $arrow->{DIRECTION}; } if($self->{ALLOW_DIAGONAL_LINES} && $arrow->{WIDTH} == $arrow->{HEIGHT}) { # this arrow is diagonal if ( $previous_was_diagonal && ($previous_was_diagonal eq $arrow->{DIRECTION} || $previous_was_diagonal eq "$d2-$d1") ) { # two diagonals going in the same direction $connection = $diagonal_direction_to_overlay_character{$arrow->{DIRECTION}} ; } else { # previous non diagonal or two diagonals not going in the same direction $connection = ($d1 eq 'up' || $d2 eq 'up') ? q{'} : q{.} ; } $previous_was_diagonal = $arrow->{DIRECTION} ; } else { # straight or angled arrow if(defined $previous_was_diagonal) { $connection = $previous_was_diagonal =~ /down/ ? q{'} : q{.} ; } else { if($previous_direction ne $d1) { $connection = $self->{ARROW_TYPE}[$left_index][$body_index] ; # for left and right, up down cases handled below if($d1 eq 'down') { $connection = $self->{ARROW_TYPE}[$leftdown_index][$connection_index] ; } elsif($d1 eq 'up') { $connection = $self->{ARROW_TYPE}[$leftup_index][$connection_index] ; } elsif($previous_direction eq 'down') { $connection = $self->{ARROW_TYPE}[$leftup_index][$connection_index] ; } elsif($previous_direction eq 'up') { $connection = $self->{ARROW_TYPE}[$leftdown_index][$connection_index] ; } } } $previous_direction = defined $d2 ? $d2 : $d1 ; $previous_was_diagonal = undef ; } if($arrow_index != 0 && defined $connection) # first character of the first section is always right { # overlay the first character of this arrow push @mask_and_element_stripes, { X_OFFSET => $self->{POINTS_OFFSETS}[$arrow_index][0], Y_OFFSET => $self->{POINTS_OFFSETS}[$arrow_index][1], WIDTH => 1, HEIGHT => 1, TEXT => $connection, } ; } $arrow_index++ ; } return(@mask_and_element_stripes) ; } #----------------------------------------------------------------------------- sub get_selection_action { my ($self, $x, $y) = @_ ; my $action = 'move' ; my $arrow_index = 0 ; for my $arrow(@{$self->{ARROWS}}) { my ($start_connector, $end_connector) = $arrow->get_connector_points() ; $start_connector->{X} += $self->{POINTS_OFFSETS}[$arrow_index][0] ; $start_connector->{Y} += $self->{POINTS_OFFSETS}[$arrow_index][1] ; if($x == $start_connector->{X} && $y == $start_connector->{Y}) { $action = 'resize' ; last ; } $end_connector->{X} += $self->{POINTS_OFFSETS}[$arrow_index][0] ; $end_connector->{Y} += $self->{POINTS_OFFSETS}[$arrow_index][1] ; if($x == $end_connector->{X} && $y == $end_connector->{Y}) { $action = 'resize' ; last ; } $arrow_index++ ; } return $action ; } #----------------------------------------------------------------------------- sub is_autoconnect_enabled { my ($self) = @_ ; return ! $self->{AUTOCONNECT_DISABLED} ; } #----------------------------------------------------------------------------- sub enable_autoconnect { my ($self, $enable) = @_ ; $self->{AUTOCONNECT_DISABLED} = !$enable ; } #----------------------------------------------------------------------------- sub allow_connection { my ($self, $which, $connect) = @_ ; if($which eq 'start') { $self->{NOT_CONNECTABLE_START} = !$connect ; } else { $self->{NOT_CONNECTABLE_END} = !$connect ; } } #----------------------------------------------------------------------------- sub is_connection_allowed { my ($self, $which) = @_ ; if($which eq 'start') { return(! $self->{NOT_CONNECTABLE_START}) ; } else { return(! $self->{NOT_CONNECTABLE_END}) ; } } #----------------------------------------------------------------------------- sub are_diagonals_allowed { my ($self, $allow) = @_ ; return $self->{ALLOW_DIAGONAL_LINES} ; } #----------------------------------------------------------------------------- sub allow_diagonals { my ($self, $allow) = @_ ; $self->{ALLOW_DIAGONAL_LINES} = $allow ; for my $arrow(@{$self->{ARROWS}}) { $arrow->{ALLOW_DIAGONAL_LINES} = $allow ; } } #----------------------------------------------------------------------------- sub get_connector_points { my ($self) = @_ ; my(@all_connector_points) = $self->get_all_points() ; my(@connector_points) ; push @connector_points, $all_connector_points[0] unless $self->{NOT_CONNECTABLE_START} ; push @connector_points, $all_connector_points[-1] unless $self->{NOT_CONNECTABLE_END} ; return(@connector_points) ; } sub get_extra_points { my ($self) = @_ ; my(@all_connector_points) = $self->get_all_points() ; shift @all_connector_points unless $self->{NOT_CONNECTABLE_START} ; pop @all_connector_points unless $self->{NOT_CONNECTABLE_END} ; return(@all_connector_points) ; } sub get_all_points { my ($self) = @_ ; my(@connector_points) ; my $arrow_index = 0 ; for my $arrow(@{$self->{ARROWS}}) { my ($start_connector, $end_connector) = $arrow->get_connector_points() ; if($arrow == $self->{ARROWS}[0]) { $start_connector->{X} += $self->{POINTS_OFFSETS}[$arrow_index][0] ; $start_connector->{Y} += $self->{POINTS_OFFSETS}[$arrow_index][1] ; $start_connector->{NAME} .= "section_$arrow_index" ; push @connector_points, $start_connector ; } $end_connector->{X} += $self->{POINTS_OFFSETS}[$arrow_index][0] ; $end_connector->{Y} += $self->{POINTS_OFFSETS}[$arrow_index][1] ; $end_connector->{NAME} .= "section_$arrow_index" ; push @connector_points, $end_connector ; $arrow_index++ ; } return(@connector_points) ; } #----------------------------------------------------------------------------- sub get_named_connection { my ($self, $name) = @_ ; my $connection ; my $arrow_index = 0 ; for my $arrow(@{$self->{ARROWS}}) { my ($start_connector, $end_connector) = $arrow->get_connector_points() ; if($arrow == $self->{ARROWS}[0]) { $start_connector->{NAME} .= "section_$arrow_index" ; if($name eq $start_connector->{NAME}) { $start_connector->{X} += $self->{POINTS_OFFSETS}[$arrow_index][0] ; $start_connector->{Y} += $self->{POINTS_OFFSETS}[$arrow_index][1] ; $connection = $start_connector ; last ; } } $end_connector->{NAME} .= "section_$arrow_index" ; if($name eq $end_connector->{NAME}) { $end_connector->{X} += $self->{POINTS_OFFSETS}[$arrow_index][0] ; $end_connector->{Y} += $self->{POINTS_OFFSETS}[$arrow_index][1] ; $connection = $end_connector ; last ; } $arrow_index++ ; } return $connection ; } #----------------------------------------------------------------------------- sub move_connector { my ($self, $connector_name, $x_offset, $y_offset, $hint) = @_ ; my $connection = $self->get_named_connection($connector_name) ; (my $no_section_connetor_name = $connector_name) =~ s/section_.*// ; if($connection) { my ($x_offset, $y_offset, $width, $height, undef) = $self->resize ( $connection->{X}, $connection->{Y}, $connection->{X} + $x_offset, $connection->{Y} + $y_offset, $hint, #~ [$no_section_connetor_name, $connector_name], [$connector_name, $no_section_connetor_name], ) ; return ( $x_offset, $y_offset, $width, $height, $self->get_named_connection($connector_name) ) ; } else { die "unknown connector '$connector_name'!\n" ; } } #----------------------------------------------------------------------------- sub resize { my ($self, $reference_x, $reference_y, $new_x, $new_y, $hint, $connector_name_array) = @_ ; Readonly my $MULTI_WIRL_CONNECTOR_NAME_INDEX => 0 ; Readonly my $WIRL_CONNECTOR_NAME_INDEX => 1 ; my ($start_element, $start_element_index, $end_element, $end_element_index) ; # find elements connected by the connector if(defined $connector_name_array) { ($start_element, $start_element_index, $end_element, $end_element_index, $connector_name_array) = $self->find_elements_for_connector_named($connector_name_array) ; } else { ($start_element, $start_element_index, $end_element, $end_element_index, $connector_name_array) = $self->find_elements_for_connector_at($reference_x, $reference_y) ; } my ($start_x_offset, $start_y_offset) = (0, 0) ; if(defined $start_element) { my $is_start ; if(defined $connector_name_array) { if ( $connector_name_array->[$WIRL_CONNECTOR_NAME_INDEX] eq 'start' || $connector_name_array->[$MULTI_WIRL_CONNECTOR_NAME_INDEX] eq 'startsection_0' ) { $is_start++ ; } } else { if($reference_x == 0 && $reference_y == 0) { $is_start++ ; } } if($is_start) { #~ print "Moving start connector\n" ; ($start_x_offset, $start_y_offset) = $start_element->resize ( 0, 0, $new_x, $new_y, $hint, $connector_name_array->[$WIRL_CONNECTOR_NAME_INDEX] ) ; my $arrow_index = 0 ; for my $arrow(@{$self->{ARROWS}}) { # offsets all other wirl_arrow start offsets if($arrow == $start_element) { } else { $self->{POINTS_OFFSETS}[$arrow_index][0] -= $start_x_offset ; $self->{POINTS_OFFSETS}[$arrow_index][1] -= $start_y_offset ; } $arrow_index++ ; } } else { my $start_element_x_offset = $self->{POINTS_OFFSETS}[$start_element_index][0] ; my $start_element_y_offset = $self->{POINTS_OFFSETS}[$start_element_index][1] ; my ($x_offset, $y_offset) = $start_element ->resize ( $reference_x - $start_element_x_offset, $reference_y - $start_element_y_offset, $new_x - $start_element_x_offset, $new_y - $start_element_y_offset, $hint, $connector_name_array->[$WIRL_CONNECTOR_NAME_INDEX] ) ; $self->{POINTS_OFFSETS}[$start_element_index][0] += $x_offset ; $self->{POINTS_OFFSETS}[$start_element_index][1] += $y_offset ; if(defined $end_element) { my ($x_offset, $y_offset) = $end_element->resize(0, 0, $new_x - $reference_x, $new_y - $reference_y) ; $self->{POINTS_OFFSETS}[$end_element_index][0] += $x_offset ; $self->{POINTS_OFFSETS}[$end_element_index][1] += $y_offset ; } } } my ($width, $height) = $self->get_width_and_height() ; $self->set(WIDTH => $width, HEIGHT => $height) ; return($start_x_offset, $start_y_offset, $width, $height, $connector_name_array) ; } sub find_elements_for_connector_at { my ($self, $reference_x, $reference_y) = @_ ; my ($start_element, $start_element_index, $end_element, $end_element_index, $connector_name, $wirl_connector_name) ; my $arrow_index = 0 ; for my $arrow(@{$self->{ARROWS}}) { my ($start_connector, $end_connector) = $arrow->get_connector_points() ; if($reference_x == 0 && $reference_y == 0) { ($start_element, $start_element_index) = ($arrow, $arrow_index) ; $wirl_connector_name = $start_connector->{NAME} ; $connector_name = $wirl_connector_name . "section_$arrow_index" ; last ; } if(defined $start_element) { ($end_element, $end_element_index) = ($arrow, $arrow_index) ; last ; } if ( $reference_x == $end_connector->{X} + $self->{POINTS_OFFSETS}[$arrow_index][0] && $reference_y == $end_connector->{Y} + $self->{POINTS_OFFSETS}[$arrow_index][1] ) { ($start_element, $start_element_index) = ($arrow, $arrow_index) ; $wirl_connector_name = $end_connector->{NAME} ; $connector_name = $wirl_connector_name . "section_$arrow_index" ; } $arrow_index++ ; } return($start_element, $start_element_index, $end_element, $end_element_index, [$connector_name, $wirl_connector_name]) } sub find_elements_for_connector_named { my ($self, $connector_name_array) = @_ ; my ($connector_name, $wirl_connector_name) = @{$connector_name_array} ; my ($start_element, $start_element_index, $end_element, $end_element_index) ; my $arrow_index = 0 ; for my $arrow(@{$self->{ARROWS}}) { my ($start_connector, $end_connector) = $arrow->get_connector_points() ; if($connector_name eq $start_connector->{NAME} . "section_$arrow_index" ) { ($start_element, $start_element_index) = ($arrow, $arrow_index) ; last ; } if(defined $start_element) { ($end_element, $end_element_index) = ($arrow, $arrow_index) ; last ; } if($connector_name eq $end_connector->{NAME} . "section_$arrow_index") { ($start_element, $start_element_index) = ($arrow, $arrow_index) ; } $arrow_index++ ; } return($start_element, $start_element_index, $end_element, $end_element_index, $connector_name_array) ; } #----------------------------------------------------------------------------- sub get_number_of_sections { my ($self) = @_ ; return scalar(@{$self->{ARROWS}}) ; } #----------------------------------------------------------------------------- sub get_section_direction { my ($self, $section_index) = @_ ; if(exists($self->{ARROWS}[$section_index])) { return $self->{ARROWS}[$section_index]{DIRECTION} ; } else { return ; } } #----------------------------------------------------------------------------- sub prepend_section { my ($self, $extend_x, $extend_y) = @_ ; my $arrow = new App::Asciio::stripes::wirl_arrow ({ END_X => -$extend_x, END_Y => -$extend_y, ARROW_TYPE => $self->{ARROW_TYPE}, DIRECTION => $self->{DIRECTION}, ALLOW_DIAGONAL_LINES => $self->{ALLOW_DIAGONAL_LINES}, EDITABLE => $self->{EDITABLE}, }) ; my $arrow_index = 0 ; for my $arrow(@{$self->{ARROWS}}) { $self->{POINTS_OFFSETS}[$arrow_index][0] += -$extend_x ; $self->{POINTS_OFFSETS}[$arrow_index][1] += -$extend_y ; $arrow_index++ ; } unshift @{$self->{POINTS_OFFSETS}}, [0, 0] ; unshift @{$self->{ARROWS}}, $arrow ; my ($width, $height) = $self->get_width_and_height() ; $self->set(WIDTH => $width, HEIGHT => $height,) ; } sub append_section { my ($self, $extend_x, $extend_y) = @_ ; my $last_point = $self->get_points()->[-1] ; my $arrow = new App::Asciio::stripes::wirl_arrow ({ END_X => $extend_x - $last_point->[0], END_Y => $extend_y - $last_point->[1], ARROW_TYPE => $self->{ARROW_TYPE}, DIRECTION => $self->{DIRECTION}, ALLOW_DIAGONAL_LINES => $self->{ALLOW_DIAGONAL_LINES}, EDITABLE => $self->{EDITABLE}, }) ; my ($start_x, $start_y) = @{$self->{POINTS_OFFSETS}[-1]} ; my ($start_connector, $end_connector) = $self->{ARROWS}[-1]->get_connector_points() ; $start_x += $end_connector->{X} ; $start_y += $end_connector->{Y} ; push @{$self->{POINTS_OFFSETS}}, [$start_x, $start_y] ; push @{$self->{ARROWS}}, $arrow ; my ($width, $height) = $self->get_width_and_height() ; $self->set(WIDTH => $width, HEIGHT => $height,) ; } #----------------------------------------------------------------------------- sub remove_last_section { my ($self) = @_ ; return if @{$self->{ARROWS}} == 1 ; pop @{$self->{POINTS_OFFSETS}} ; pop @{$self->{ARROWS}} ; my ($width, $height) = $self->get_width_and_height() ; $self->set(WIDTH => $width, HEIGHT => $height,) ; } #----------------------------------------------------------------------------- sub remove_first_section { my ($self) = @_ ; return(0, 0) if @{$self->{ARROWS}} == 1 ; my $second_arrow_x_offset = $self->{POINTS_OFFSETS}[1][0] ; my $second_arrow_y_offset = $self->{POINTS_OFFSETS}[1][1] ; shift @{$self->{POINTS_OFFSETS}} ; shift @{$self->{ARROWS}} ; my $arrow_index = 0 ; for my $arrow(@{$self->{ARROWS}}) { $self->{POINTS_OFFSETS}[$arrow_index][0] -= $second_arrow_x_offset ; $self->{POINTS_OFFSETS}[$arrow_index][1] -= $second_arrow_y_offset ; $arrow_index++ ; } my ($width, $height) = $self->get_width_and_height() ; $self->set(WIDTH => $width, HEIGHT => $height,) ; return($second_arrow_x_offset, $second_arrow_y_offset) ; } #----------------------------------------------------------------------------- sub change_section_direction { my ($self, $x, $y) = @_ ; if(1 == @{$self->{ARROWS}}) { my $direction = $self->{ARROWS}[0]->get_section_direction(0) ; if($direction =~ /(.*)-(.*)/) { $self->{ARROWS}[0]->resize(0, 0, 0, 0, "$2-$1") ; } } else { my $index = 0 ; for my $arrow(@{$self->{ARROWS}}) { if ( $self->is_over_element ( $arrow, $x, $y, 0, @{$self->{POINTS_OFFSETS}[$index]} ) ) { my $direction = $arrow->get_section_direction($index) ; if($direction =~ /(.*)-(.*)/) { $arrow->resize(0, 0, 0, 0, "$2-$1") ; } } $index++ ; } } } sub is_over_element { my ($self, $element, $x, $y, $field, $element_offset_x, $element_offset_y, ) = @_ ; die "Error: 'is_over_element' needs position!" unless defined $x && defined $y ; $field ||= 0 ; my $is_under = 0 ; for my $mask_strip ($element->get_mask_and_element_stripes()) { my $stripe_x = $element_offset_x + $mask_strip->{X_OFFSET} ; my $stripe_y = $element_offset_y + $mask_strip->{Y_OFFSET} ; if ( $stripe_x - $field <= $x && $x < $stripe_x + $mask_strip->{WIDTH} + $field && $stripe_y - $field <= $y && $y < $stripe_y + $mask_strip->{HEIGHT} + $field ) { $is_under++ ; last ; } } return($is_under) ; } #----------------------------------------------------------------------------- sub get_width_and_height { my ($self) = @_ ; my ($smallest_x, $biggest_x, $smallest_y, $biggest_y) = (0, 0, 0, 0) ; my $arrow_index = 0 ; for my $start_point (@{$self->{POINTS_OFFSETS}}) { my ($x, $y) = @{$start_point} ; my ($start_connector, $end_connector) = $self->{ARROWS}[$arrow_index]->get_connector_points() ; $x += $end_connector->{X} ; $y += $end_connector->{Y} ; $smallest_x = min($smallest_x, $x) ; $smallest_y = min($smallest_y, $y) ; $biggest_x = max($biggest_x, $x) ; $biggest_y = max($biggest_y, $y) ; $arrow_index++ ; } return(($biggest_x - $smallest_x) + 1, ($biggest_y - $smallest_y) + 1) ; } #----------------------------------------------------------------------------- sub get_arrow_type { my ($self) = @_ ; return($self->{ARROW_TYPE}) ; } #----------------------------------------------------------------------------- sub set_arrow_type { my ($self, $arrow_type) = @_ ; $self->setup($arrow_type, $self->get_points(), $self->{DIRECTION}, $self->{ALLOW_DIAGONAL_LINES}, $self->{EDITABLE}) ; } #----------------------------------------------------------------------------- sub get_points { my ($self) = @_ ; my @points ; my $arrow_index = 0 ; for my $point_offset (@{$self->{POINTS_OFFSETS}}) { my ($x_offset, $y_offset) = @{$point_offset} ; my ($start_connector, $end_connector) = $self->{ARROWS}[$arrow_index]->get_connector_points() ; push @points, [$x_offset + $end_connector->{X}, $y_offset + $end_connector->{Y}] ; $arrow_index++ ; } return \@points ; } #----------------------------------------------------------------------------- sub edit { my ($self) = @_ ; return unless $self->{EDITABLE} ; # add section # remove section # handle offset array } #----------------------------------------------------------------------------- 1 ;