indexing
	description: "[

		A box that contains (in cells) other boxes aligned in a grid-like
		table structure. The number of columns and rows has to be specified during
		creation and can not be changed after that.
		Columns and rows provide alignment and a minimal size for each cell.
		Columns and rows will be resized to accommodate the largest child item, if neccessary.
		The total size of a table is the sum of all row-heights and column-widths.
		Alignment and size can be uniformly set over all cells but cells might resize
		during the next update to fit the contents, thus the total size might be larger
		than the desired size.
		A table has a colored, arbitrarily wide border that is initially hidden and of width 1.

	]"
	author: ""
	date: "$Date$"
	revision: "$Revision$"

class
	EM_VIZ_TABLE_BOX [B -> EM_VIZ_BOX]

inherit
	EM_VIZ_COLLECTION_BOX [B]
		rename
			count as cell_count
		redefine
			vz_render
		end

	EM_VIZ_RESIZEABLE
		redefine
			set_width,
			set_height,
			set_size
		end

	EM_VIZ_ALIGNABLE
		redefine
			set_alignment,
			set_horizontal_alignment,
			set_vertical_alignment
		end

	EM_VIZ_COLORED
		rename
			set_color as set_border_color,
			color as border_color
		end

create

	make

feature -- Initialization

	make (a_suggested_size: like size; a_dimensions: EM_VECTOR2I) is
			-- Make new table with `a_suggested_size' and `a_dimensions' columns and rows
		require
			positive_dimensions: 1 <= a_dimensions.x and 1 <= a_dimensions.y
		do
			make_collection
			make_alignable
			make_colored (Viz_options.default_border_color)

			create_columns (a_dimensions.x, a_suggested_size.x, horizontal_alignment)
			create_rows (a_dimensions.y, a_suggested_size.y, vertical_alignment)
			create_cells

			-- Set_width/set_height requires columns/rows to exist
			make_resizeable (a_suggested_size)

			set_border_hidden (False)
			set_border_width (1.0)
		end

feature -- Access

	border_hidden: BOOLEAN
			-- Should cell border be hidden?

	border_width: DOUBLE
			-- Width of border if visible

	column_count: INTEGER
			-- How many columns

	row_count: INTEGER
			-- How many rows

	infix "@", item (an_index: EM_VECTOR2I): B is
			-- Item in cell at `an_index'
		require
			valid_index: valid_index (an_index)
		do
			Result := cells.item (cell_index (an_index))
		end

	as_linear: DS_LINEAR [B] is
			-- Expose items in linear list
		do
			Result := cells
		end

feature -- Measurement

	cell_count: INTEGER is
			-- How many cells?
		do
			Result := cells.count
		end

	cell_size (an_index: EM_VECTOR2I): like size is
			-- Size of cell at `an_index'
		require
			valid_index: valid_index (an_index)
		do
			Result.set (
				column_width (an_index.x),
				row_height (an_index.y),
				depth
			)
		end

	cell_alignment (an_index: EM_VECTOR2I): like alignment is
			-- Alignment of cell at `an_index'
		require
			valid_index: valid_index (an_index)
		do
			Result.set (
				column_alignment (an_index.x),
				row_alignment (an_index.y),
				depth_alignment
			)
		end

	column_width (a_column: INTEGER): DOUBLE is
			-- Width of `a_column'
		require
			valid_index: 1 <= a_column and a_column <= column_count
		do
			Result := column (a_column).width
		end

	column_alignment (a_column: INTEGER): EM_ALIGNMENT is
			-- Alignment of `a_column'
		require
			valid_index: 1 <= a_column and a_column <= column_count
		do
			Result := column (a_column).alignment
		end

	row_height (a_row: INTEGER): DOUBLE is
			-- Height of `a_row'
		require
			valid_index: 1 <= a_row and a_row <= row_count
		do
			Result := row (a_row).height
		end

	row_alignment (a_row: INTEGER): EM_ALIGNMENT is
			-- Alignment of `a_row'
		require
			valid_index: 1 <= a_row and a_row <= row_count
		do
			Result := row (a_row).alignment
		end

feature -- Status report

	is_empty: BOOLEAN is
			-- Table is never empty
		do
			Result := false
		end

	has (b: B): BOOLEAN is
			-- Has box `b'?
		do
			Result := cells.has (b)
		end

	valid_index (a_index: EM_VECTOR2I): BOOLEAN is
			-- Is `a_index' valid?
		do
			Result :=
				1 <= a_index.x and a_index.x <= column_count and
				1 <= a_index.y and a_index.y <= row_count
		end

feature -- Element change

	set_border_hidden (hide: like border_hidden) is
			-- Set border hidden or visible
		do
			border_hidden := hide
			expire
		ensure
			set: border_hidden = hide
			expired: needs_update
		end

	set_border_width (a_width: like border_width) is
			-- Set `border_width' to `a_width'
		require
			positive_width: 0.0 < a_width
		do
			border_width := a_width
			expire
		ensure
			set: border_width = a_width
			expired: needs_update
		end

	put (a_box: like item; a_index: EM_VECTOR2I) is
			-- Put `a_box' into cell at `a_index'
		require
			valid_index: valid_index (a_index)
			orphan_box: a_box /= Void implies a_box.parent = Void
		do
			cast_out (item (a_index))
			cells.replace (a_box, cell_index (a_index))
			adopt (a_box)
			expire
		ensure
			has: has (a_box)
			expired: needs_update
		end

	set_column_width (a_column: INTEGER; a_width: like column_width) is
			-- Set width of `a_column' to `a_width'
		require
			valid_index: 1 <= a_column and a_column <= column_count
		do
			column (a_column).set_width (a_width)
			expire
		ensure
			expired: needs_update
		end

	set_column_alignment (a_column: INTEGER; an_alignment: like column_alignment) is
			-- Set alignment of `a_column' to `an_alignment'
		require
			valid_index: 1 <= a_column and a_column <= column_count
		do
			column (a_column).set_alignment (an_alignment)
			if an_alignment /= horizontal_alignment then
				set_horizontal_alignment (Align_user_specified)
			end
			expire
		ensure
			expired: needs_update
		end

	set_row_height (a_row: INTEGER; a_height: like row_height) is
			-- Set height of `a_row' to `a_height'
		require
			valid_index: 1 <= a_row and a_row <= row_count
		do
			row (a_row).set_height (a_height)
			expire
		ensure
			expired: needs_update
		end

	set_row_alignment (a_row: INTEGER; an_alignment: like row_alignment) is
			-- Set alignment of `a_row' to `an_alignment'
		require
			valid_index: 1 <= a_row and a_row <= row_count
		do
			row (a_row).set_alignment (an_alignment)
			if an_alignment /= vertical_alignment then
				set_vertical_alignment (Align_user_specified)
			end
			expire
		ensure
			expired: needs_update
		end

	set_width (a_total_width: like width) is
			-- Set width of table to `a_total_width'
		local
			c: INTEGER
		do
			from
				c := 1
			until
				c > column_count
			loop
				set_column_width (c, a_total_width / column_count)
				c := c + 1
			end

			Precursor (a_total_width)
		end

	set_height (a_total_height: like height) is
			-- Set height of table to `a_total_height'
		local
			r: INTEGER
		do
			from
				r := 1
			until
				r > row_count
			loop
				set_row_height (r, a_total_height / row_count)
				r := r + 1
			end

			Precursor (a_total_height)
		end

	set_size (a_total_size: like size) is
			-- Set size of table to `a_total_size'
		do
			set_width (a_total_size.x)
			set_height (a_total_size.y)
			set_depth (a_total_size.z)
		end

	set_horizontal_alignment (an_uniform_alignment: like horizontal_alignment) is
			-- Set `an_uniform_alignment' for all columns
		local
			c: INTEGER
		do
			if not an_uniform_alignment.is_user_specified then
				from
					c := 1
				until
					c > column_count
				loop
					set_column_alignment (c, an_uniform_alignment)
					c := c + 1
				end
			end

			Precursor (an_uniform_alignment)
		end

	set_vertical_alignment (an_uniform_alignment: like vertical_alignment) is
			-- Set `an_uniform_alignment' for all rows
		local
			r: INTEGER
		do
			if not an_uniform_alignment.is_user_specified then
				from
					r := 1
				until
					r > row_count
				loop
					set_row_alignment (r, an_uniform_alignment)
					r := r + 1
				end
			end

			Precursor (an_uniform_alignment)
		end

	set_alignment (an_uniform_alignment: like alignment) is
			-- Set `an_uniform_alignment' for all cells
		do
			set_vertical_alignment (an_uniform_alignment.x)
			set_horizontal_alignment (an_uniform_alignment.y)
			set_depth_alignment (an_uniform_alignment.z)
		end

feature {NONE} -- Implementation

	vz_render (viewing_direction: EM_VECTOR3D) is
			-- Render back-to-front
		do
			if viewing_direction.z > 0.0 then
				setup_local_coordinate_frame
				render_contents (viewing_direction)
				revert_local_coordinate_frame
				if not border_hidden then
					render_border
				end
			else
				if not border_hidden then
					render_border
				end
				setup_local_coordinate_frame
				render_contents (viewing_direction)
				revert_local_coordinate_frame
			end
		end

	render_contents (viewing_direction: EM_VECTOR3D) is
			-- Render back-to-front
		local
			reverse_x, reverse_y: BOOLEAN
			c, r: INTEGER
			i: EM_VECTOR2I
			b: EM_VIZ_BOX
		do
			reverse_x := viewing_direction.x > 0.0
			reverse_y := viewing_direction.y > 0.0

			from
				c := 1
			until
				c > column_count
			loop
				from
					r := 1
				until
					r > row_count
				loop
					if reverse_x then
						i.set_x (column_count - c + 1)
					else
						i.set_x (c)
					end
					if reverse_y then
						i.set_y (row_count - r + 1)
					else
						i.set_y (r)
					end
					b := item (i)
					if b /= Void then
						b.render (viewing_direction)
					end
					r := r + 1
				end
				c := c + 1
			end
		end

	render_border is
			-- Draw table border
		local
			c, r: INTEGER
			x, y, w, h: DOUBLE
			bs: like border_width
		do
			setup_local_coordinate_frame

			w := width
			h := height
			bs := border_width / 2

			vz_push_attributes (Vz_polygon_bit)
			vz_set_culling (False, Vz_front_and_back)

			vz_normal3d (0, 0, -1)
			vz_color (border_color)
			vz_begin_quads
				-- Vertical lines
				from
					c := 1
					x := 0.0
					vz_vertex2d (x-bs,  -bs)
					vz_vertex2d (x+bs,  -bs)
					vz_vertex2d (x+bs, h+bs)
					vz_vertex2d (x-bs, h+bs)
				until
					c > column_count
				loop
					x := x + column_width (c)
					vz_vertex2d (x-bs,  -bs)
					vz_vertex2d (x+bs,  -bs)
					vz_vertex2d (x+bs, h+bs)
					vz_vertex2d (x-bs, h+bs)
					c := c + 1
				end
				-- Horizontal lines
				from
					r := 1
					y := 0.0
					vz_vertex2d (w+bs, y-bs)
					vz_vertex2d (w+bs, y+bs)
					vz_vertex2d ( -bs, y+bs)
					vz_vertex2d ( -bs, y-bs)
				until
					r > row_count
				loop
					y := y + row_height (r)
					vz_vertex2d (w+bs, y-bs)
					vz_vertex2d (w+bs, y+bs)
					vz_vertex2d ( -bs, y+bs)
					vz_vertex2d ( -bs, y-bs)
					r := r + 1
				end
			vz_end

			vz_pop_attributes
			revert_local_coordinate_frame
		end

	resize_container is
			-- Resize container to fit contents
		local
			c, r: INTEGER
			b: B
			w, h, d: DOUBLE
		do
			d := 0.0

			from c := 1 until c > column_count loop
				from r := 1 until r > row_count loop
					b := item ([c, r])
					if b /= Void then
						row (r).set_height (b.height.max (row_height (r)))
						column (c).set_width (b.width.max (column_width (c)))
						d := b.depth.max (d)
					end
					r := r + 1
				end
				c := c + 1
			end
			from
				w := 0.0
				c := 1
			until c > column_count loop
				w := w + column_width (c)
				c := c + 1
			end
			from
				h := 0.0
				r := 1
			until r > row_count loop
				h := h + row_height (r)
				r := r + 1
			end
			set_size_internal ([w, h, d])
		end

	realign_contents is
			-- Realign contents after resizing container
		local
			c, r: INTEGER
			b: B
			bwin: EM_INTERVAL_3D
			xmin, xmax, ymin, ymax, zmin, zmax: DOUBLE
		do
			zmin := 0.0
			zmax := depth
			from
				r := 1
				ymin := 0.0
			until
				r > row_count
			loop
				ymax := ymin + row_height (r)
				from
					c := 1
					xmin := 0.0
				until
					c > column_count
				loop
					xmax := xmin + column_width (c)
					b := item ([c, r])
					if b /= Void then
						bwin.set ([xmin, ymin, zmin], [xmax, ymax, zmax])
						b.realign (bwin, cell_alignment ([c, r]))
					end
					xmin := xmax
					c := c + 1
				end
				ymin := ymax
				r := r + 1
			end
		end

	frozen cell_index (i: EM_VECTOR2I): INTEGER is
			-- Cell index from `i'
		do
			Result := i.x + (row_count - i.y)*column_count
			-- column_count + (row_count - 1)*column_count = row_count * column_count
			-- 1 + (row_count - row_count)*column_count = 1
		end

	frozen column (c: INTEGER): EM_VIZ_TABLE_COLUMN is
			-- `c'-th column
		do
			Result := columns @ c
		end

	frozen row (r: INTEGER): EM_VIZ_TABLE_ROW is
			-- `r'-th row
		do
			Result := rows @ r
		end

	create_columns (count: INTEGER; total_width: like width; default_alignment: EM_ALIGNMENT) is
			-- Create `count' columns with `total_width' and `default_alignment'
		require
			natural_count: 1 <= count
		do
			column_count := count
			from
				create columns.make (count)
			until
				columns.is_full
			loop
				columns.put_last (create {EM_VIZ_TABLE_COLUMN}.make (total_width / count, default_alignment))
			end
		end

	create_rows (count: INTEGER; total_height: like height; default_alignment: EM_ALIGNMENT) is
			-- Create `count' rows with `total_height' and `default_alignment'
		require
			natural_count: 1 <= count
		do
			row_count := count
			from
				create rows.make (count)
			until
				rows.is_full
			loop
				rows.put_last (create {EM_VIZ_TABLE_ROW}.make (total_height / count, default_alignment))
			end
		end

	create_cells is
			-- Create internal storage
		do
			from
				create cells.make (row_count * column_count)
			until
				cells.is_full
			loop
				cells.put_last (Void)
			end
		end

	cells: DS_ARRAYED_LIST [B]
			-- Table cells (storage)

	rows: DS_ARRAYED_LIST [EM_VIZ_TABLE_ROW]
			-- Table rows

	columns: DS_ARRAYED_LIST [EM_VIZ_TABLE_COLUMN]
			-- Table columns

invariant

	columns_exist: columns /= Void
	rows_exist: rows /= Void
	cells_exist: cells /= Void
	consistent_counts: rows.count = row_count and columns.count = column_count

end