note
	description:
		"Positions relative to other positions.%N%
		%Takes another relative point as origin and then defines a%N%
		%hor. & vert. scaling factor, x, y and angle.%N%
		%You can then access absolute scale_x, scale_y, x, y and angle%N%
		%which are recomputed only if invalidate_absolute_position has%N%
		%been called.%N%
		%You may also choose to specify a positioner. This is an agent%N%
		%that gets called everytime a recomputation is requested.%N%
		%When a positioner is installed, the other attributes are ignored.%N%
		%The x and y are transformed by the angle and scaling of the origin.%N%
		%This implies that the scale_x, scale_y and angle features of this%N%
		%object are only for propagation to referring points."
	legal: "See notice at end of class."
	status: "See notice at end of class."
	keywords: "point, position, location, origin"
	date: "$Date$"
	revision: "$Revision$"

class
	EV_RELATIVE_POINT

obsolete
	"Not needed in EV_MODEL. [2017-05-31]"

inherit
	EV_FIGURE_MATH
		export
			{NONE} all
		undefine
			default_create
		end

	IDENTIFIED
		export
			{NONE} free_id, id_freed
		undefine
			default_create,
			is_equal,
			copy
		end

create
	default_create,
	make_with_origin,
	make_with_origin_and_position,
	make_with_position,
	make_with_positioner

feature {NONE}  -- Initialization

	default_create
			-- Base origin.
		do
			create notify_list_ids.make (2)
			position_changed := True
			scale_x := 1
			scale_y := 1
		end

	make_with_origin (an_origin: EV_RELATIVE_POINT)
			-- Create with `an_origin' on position (0, 0).
		do
			default_create
			set_origin (an_origin)
		end

	make_with_origin_and_position (
		an_origin: EV_RELATIVE_POINT; new_x, new_y: INTEGER)
			-- Create with `an_origin' on position (`new_x', `new_y').
		do
			default_create
			set_origin (an_origin)
			set_x (new_x)
			set_y (new_y)
		end

	make_with_position (new_x, new_y: INTEGER)
			-- Create on position (`new_x', `new_y').
		do
			default_create
			set_x (new_x)
			set_y (new_y)
		end

	make_with_positioner (pos_agent: like positioner)
			-- Create with `pos_agent'.
		do
			default_create
			set_positioner (pos_agent)
		end

feature -- Access

	origin: detachable EV_RELATIVE_POINT
			-- Origin the position is relative to.
			-- If this is Void, x and y are relative to (0, 0).

	x: INTEGER
			-- X-coordinate relative to origin.
			--| This is in fact not the actual x coordinate relative to
			--| the origin.
			--| The actual place is a calculation including scale_x and angle.

	y: INTEGER
			-- Y-coordinate relative to origin.
			--| This is in fact not the actual x coordinate relative to
			--| the origin.
			--| The actual place is a calculation including scale_y and angle.

	angle: DOUBLE
			-- Angle in radians relative to origin.
			--| This angle has few to do with this point.
			--| It's main purpose is to
			--| propagate it to the points who have this point as origin.

	scale_x: DOUBLE
			-- Relative horizontal scaling factor.
			--| Only propagation purpose.

	scale_y: DOUBLE
			-- Relative vertical scaling factor.
			--| Only propagation purpose.

	positioner: detachable PROCEDURE [TUPLE [like Current]]
			-- Take special absolute positioning action.
			-- A positioner is expected to use the routines:
			-- set_x_abs, set_y_abs.
			-- set_angle_abs, set_scale_x_abs, set_scale_y_abs can be used too
			-- to propagate angle and scaling factor to referring points.
			--| When a positioner is used, origin is ignored.

	x_abs: INTEGER
			-- X relative to (0, 0). Updates if necessary.
		do
			calculate_absolute_position
			Result := last_x_abs
		ensure
			Result_assigned: Result = last_x_abs
		end

	y_abs: INTEGER
			-- Y relative to (0, 0). Updates if necessary.
		do
			calculate_absolute_position
			Result := last_y_abs
		ensure
			Result_assigned: Result = last_y_abs
		end

	absolute_coordinates: EV_COORDINATE
			-- Coordinates relative to (0, 0). Updates if necessary.
		do
			calculate_absolute_position
			create Result.set (last_x_abs, last_y_abs)
		end

	angle_abs: DOUBLE
			-- Angle relative to 0. Updates if necessary.
		do
			calculate_absolute_position
			Result := last_angle_abs
		ensure
			Result_assigned: Result = last_angle_abs
		end

	scale_x_abs: DOUBLE
			-- Final horizontal scaling factor. Updates if necessary.
		do
			calculate_absolute_position
			Result := last_scale_x_abs
		ensure
			Result_assigned: Result = last_scale_x_abs
		end

	scale_y_abs: DOUBLE
			-- Final vertical scaling factor. Updates if necessary.
		do
			calculate_absolute_position
			Result := last_scale_y_abs
		ensure
			Result_assigned: Result = last_scale_y_abs
		end

feature -- Status report

	has_positioner: BOOLEAN
			-- Is this point controlled by an agent?
		do
			Result := positioner /= Void
		ensure
			Result_assigned: Result = (positioner /= Void)
		end

	being_positioned: BOOLEAN
			-- Used for cycle detection of positioning agents.
			--| Is a precondition of set_x_abs, set_y_abs, set_angle_abs,
			--| set_scale_x_abs, set_scale_y_abs.

	relative_to (org: EV_RELATIVE_POINT): BOOLEAN
			-- Does this point have `org' as origin somehow?
			-- This is not the case when it is being positioned.
			--| Used in preconditions of x_rel_to and y_rel_to.
			--| A point is not relative to itself.
		do
			if origin = org then
				Result := True
			elseif origin = Void or else has_positioner then
				Result := False
			elseif attached origin as l_origin then
				Result := l_origin.relative_to (org)
			end
		end

feature -- Element change

	set_positioner (pos_agent: like positioner)
			-- Set a customized positioning routine.
		do
			positioner := pos_agent
			set_controlled_by_positioner (pos_agent /= Void)
			notify_of_position_change
		ensure
			positioner_assigned: positioner = pos_agent
		end

	set_origin (new_origin: EV_RELATIVE_POINT)
			-- Set point this point is relative to.
		require
			new_origin_not_void: new_origin /= Void
			no_dependence_circle: not new_origin.relative_to (Current)
				and new_origin /= Current
		local
			l_origin: like origin
		do
			l_origin := origin
			if l_origin /= Void then
				l_origin.notify_list_ids.prune (object_id)
			end
			l_origin := new_origin
			origin := l_origin
			l_origin.notify_list_ids.extend (object_id)
			notify_of_position_change
		end

	change_origin (new_origin: EV_RELATIVE_POINT)
			-- Set point this point is relative to.
			-- Do not change absolute coordinates if
			-- scaling factors and angle do not change.
		require
			new_origin_not_void: new_origin /= Void
			no_dependence_circle: not new_origin.relative_to (Current)
		do
			x := x_abs - new_origin.x_abs
			y := y_abs - new_origin.y_abs
			set_origin (new_origin)
		end

	set_x (new_x: INTEGER)
			-- Change relative horizontal position.
		do
			x := new_x
			notify_of_position_change
		end

	set_y (new_y: INTEGER)
			-- Change relative vertical position.
		do
			y := new_y
			notify_of_position_change
		end

	set_angle (new_angle: DOUBLE)
			-- Set angle of this point (in radians).
		do
			angle := new_angle
			notify_of_position_change
		end

	set_scale_x (new_scale_x: DOUBLE)
			-- Set relative horizontal scaling factor.
		do
			scale_x := new_scale_x
			notify_of_position_change
		end

	set_scale_y (new_scale_y: DOUBLE)
			-- Set relative vertical scaling factor.
		do
			scale_y := new_scale_y
			notify_of_position_change
		end

	set_scale (new_scale: DOUBLE)
			-- Set relative scaling factor.
		do
			scale_y := new_scale
			scale_x := new_scale
			notify_of_position_change
		end

	set_position (new_x, new_y: INTEGER)
			-- Set both `x' and `y'.
		do
			x := new_x
			y := new_y
			notify_of_position_change
		end

	set_x_abs (new_x_abs: INTEGER)
			-- Change absolute position.
		do
			last_x_abs := new_x_abs
		ensure
			last_x_abs_assigned: last_x_abs = new_x_abs
		end

	set_y_abs (new_y_abs: INTEGER)
			-- Change absolute position.
		do
			last_y_abs := new_y_abs
		ensure
			last_y_abs_assigned: last_y_abs = new_y_abs
		end

	set_angle_abs (new_angle_abs: DOUBLE)
			-- Change absolute angle.
		do
			last_angle_abs := new_angle_abs
		ensure
			last_angle_abs_assigned: last_angle_abs = new_angle_abs
		end

	set_scale_x_abs (new_scale_x_abs: DOUBLE)
			-- Change absolute horizontal scaling factor.
		do
			last_scale_x_abs := new_scale_x_abs
		ensure
			last_scale_x_abs_assigned: last_scale_x_abs = new_scale_x_abs
		end

	set_scale_y_abs (new_scale_y_abs: DOUBLE)
			-- Change absolute vertical scaling factor.
		do
			last_scale_y_abs := new_scale_y_abs
		ensure
			last_scale_y_abs_assigned: last_scale_y_abs = new_scale_y_abs
		end

feature {EV_FIGURE, EV_RELATIVE_POINT} -- Implementation

	last_x_abs: INTEGER
			-- Last calculated x relative to (0, 0).

	last_y_abs: INTEGER
			-- Last calculated y relative to (0, 0).

	last_angle_abs: DOUBLE
			-- Last calculated cumulative angle (relative to 0).

	last_scale_x_abs: DOUBLE
			-- Last calculated final horizontal scaling factor.

	last_scale_y_abs: DOUBLE
			-- Last calculated final vertical scaling factor.

	x_transformed (ang, scale: DOUBLE): INTEGER
			-- X if point was rotated by `abs'.
			-- Used to add to absolute coordinate of origin.
			-- Formula: rx = (x cos ang - y sin ang) * scale
			--| This is used to add to the absolute coordinates of the origin
			--| to get this point's absolute coordinates.
			--| Same for y_transformed.
		do
			if ang = 0.0 and scale = 1.0 then
				Result := x
			elseif ang = 0.0 then
				Result := (x * scale).truncated_to_integer
			else
				Result := (((cosine (ang) * x) -
					(sine (ang) * y)) * scale).truncated_to_integer
			end
		end

	y_transformed (ang, scale: DOUBLE): INTEGER
			-- Y if point was rotated by `angle'.
			-- Used to add to absolute coordinate of origin.
			-- Formula: ry = (x sin ang + y cos ang) * scale
		do
			if ang = 0.0 and scale = 1.0 then
				Result := y
			elseif ang = 0.0 then
				Result := (y * scale).truncated_to_integer
			else
				Result := (((sine (ang) * x) +
					(cosine (ang) * y)) * scale).truncated_to_integer
			end
		end

	calculate_absolute_position
			-- Do absolute calculation if needed.
			-- Do nothing if absolute_position is already valid.
			--| It is not really necessary to call this directly.
			--| Only when you want every calculation to be done at the
			--| same time.
		local
			l_x_abs, l_y_abs: INTEGER
			l_scale_x_abs, l_scale_y_abs, l_angle_abs: DOUBLE
		do
			if position_changed or else controlled_by_positioner then
				if attached positioner as l_positioner then
					check
						positioning_agent_loop: not being_positioned
					end
					being_positioned := True
					l_positioner.call ([Current])
					being_positioned := False
				else
					if attached origin as l_origin then
						l_origin.calculate_absolute_position
						l_x_abs := l_origin.last_x_abs
						l_y_abs := l_origin.last_y_abs
						l_scale_x_abs := l_origin.last_scale_x_abs
						l_scale_y_abs := l_origin.last_scale_y_abs
						l_angle_abs := l_origin.last_angle_abs
					else
						l_scale_x_abs := 1
						l_scale_y_abs := 1
					end
					l_x_abs := l_x_abs + x_transformed (
						l_angle_abs, l_scale_x_abs
					)
					l_y_abs := l_y_abs + y_transformed (
						l_angle_abs, l_scale_y_abs
					)
					l_angle_abs := l_angle_abs + angle
					l_scale_x_abs := l_scale_x_abs * scale_x
					l_scale_y_abs := l_scale_y_abs * scale_y
					last_x_abs := l_x_abs
					last_y_abs := l_y_abs
					last_scale_x_abs := l_scale_x_abs
					last_scale_y_abs := l_scale_y_abs
					last_angle_abs := l_angle_abs
					position_changed := False
				end
			end
		end

	absolute_position_valid: BOOLEAN
			-- Are absolute coordinates of this figure valid?
			--| If they are not, calculate_absolute_position is called first.
		do
			Result := not position_changed
		end

	invalidate_absolute_position
			-- Force recalculation of absolute coordinates next time they
			-- are accessed.
		do
			position_changed := True
		end

feature {EV_RELATIVE_POINT} -- Implementation

	all_abs: TUPLE [INTEGER, INTEGER, DOUBLE, DOUBLE, DOUBLE]
		do
			calculate_absolute_position
			Result := [last_x_abs, last_y_abs, last_scale_x_abs,
				last_scale_y_abs, last_angle_abs]
		end

	set_all (ax, ay: INTEGER; sx, sy, an: DOUBLE)
		do
			last_x_abs := ax
			last_y_abs := ay
			last_scale_x_abs := sx
			last_scale_y_abs := sy
			last_angle_abs := an
		end

feature {EV_FIGURE, EV_RELATIVE_POINT} -- Implementation

	--| These features are to determine the x, y and angle relative
	--| to a given point. That point can be positioned, but no positioner can
	--| be in between this point and that point.
	--| enforced by precondition: (this point).originated_from (that point)
	--| Use these functions for projection purposes.
	--| If for a rectangle, point_a is originated from point_b,
	--| Use point_b.x_rel_to (point_a) as a width for it,
	--| and use the angle on point_a as orientation.

	angle_rel_to (pnt: EV_RELATIVE_POINT): DOUBLE
			-- Get angle relative to `pnt'.
			-- This point must originate from `pnt'.
		require
			pnt_exists: pnt /= Void
			relative_to_pnt: relative_to (pnt)
		do
			if attached origin as l_origin and then l_origin /= pnt then
				Result := l_origin.angle_rel_to (pnt)
			end
			if attached origin as l_origin then
				Result := Result + l_origin.angle
			end
		end

	x_rel_to (pnt: EV_RELATIVE_POINT): INTEGER
			-- X relative to `pnt'.
			-- This point must originate from `pnt'.
		require
			pnt_exists: pnt /= Void
			relative_to_pnt: relative_to (pnt)
		do
			if attached origin as l_origin and then l_origin /= pnt then
				Result := l_origin.x_rel_to (pnt)
			end
			Result := Result + x_transformed (angle_rel_to (pnt), scale_x_abs)
		end

	y_rel_to (pnt: EV_RELATIVE_POINT): INTEGER
			-- Y relative to `pnt'.
			-- This point must originate from `pnt'.
		require
			pnt_exists: pnt /= Void
			relative_to_pnt: relative_to (pnt)
		do
			if attached origin as l_origin and then l_origin /= pnt then
				Result := l_origin.y_rel_to (pnt)
			end
			Result := Result + y_transformed (angle_rel_to (pnt), scale_y_abs)
		end

feature -- Representation

	out_abs: STRING
			-- A string with absolute coordinates.
		do
			Result := "(" + x_abs.out + ", " + y_abs.out + ")"
		end

	out_rel: STRING
			-- A string with all relative coordinates of origins.
		do
			if attached origin as l_origin then
				Result := l_origin.out_rel + "+"
			else
				Result := ""
			end
			Result := Result + "(" + x.out + ", " + y.out + ")"
		end

feature {EV_FIGURE, EV_RELATIVE_POINT, EV_PROJECTOR} -- Implementation

	position_changed: BOOLEAN
			-- Is position updated since last calculation?

	controlled_by_positioner: BOOLEAN
			-- Checks whether this position is somehow controlled by a
			-- positioner.

	set_controlled_by_positioner (f: BOOLEAN)
		local
			nl: like notify_list
		do
			controlled_by_positioner := f
			if f /= (positioner /= Void) then
				nl := notify_list
				from
					nl.start
				until
					nl.after
				loop
					nl.item.set_controlled_by_positioner (f)
					nl.forth
				end
			end
		end

	notify_list: LINKED_LIST [EV_RELATIVE_POINT]
			-- Immediate dependent points.
		local
			i: detachable EV_RELATIVE_POINT
		do
			create Result.make
			from
				notify_list_ids.start
			until
				notify_list_ids.after
			loop
				i ?= id_object (notify_list_ids.item)
				if i /= Void then
					Result.extend (i)
				end
				notify_list_ids.forth
			end
		end

	notify_list_ids: ARRAYED_LIST [INTEGER]
			-- Immediate dependent points and figures using weak referencing.

	notify_of_position_change
			-- Set flag `position_changed' and in all referring positions.
		local
			i: detachable EV_RELATIVE_POINT
			f: detachable EV_FIGURE
			ids: like notify_list_ids
		do
			if not position_changed then
				position_changed := True

				ids := notify_list_ids
				from
					ids.start
				until
					ids.after
				loop
					i ?= id_object (ids.item)
					if i /= Void then
						i.notify_of_position_change
						ids.forth
					else
						f ?= id_object (ids.item)
						if f /= Void then
							f.invalidate
							ids.forth
						else
							-- Remove object id from list.
							ids.remove
						end
					end
				end
			end
		end

invariant
	notify_list_exists: notify_list_ids /= Void
	has_origin_implies_origin_notifies:
		attached origin as l_origin implies l_origin.notify_list_ids.has (object_id)

note
	copyright:	"Copyright (c) 1984-2006, Eiffel Software and others"
	license:	"Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
	source: "[
			 Eiffel Software
			 356 Storke Road, Goleta, CA 93117 USA
			 Telephone 805-685-1006, Fax 805-685-6869
			 Website http://www.eiffel.com
			 Customer support http://support.eiffel.com
		]"




end -- class EV_RELATIVE_POINT