note description: "[ Objects that represent an EV_DIALOG. The original version of this class was generated by EiffelBuild. ]" legal: "See notice at end of class." status: "See notice at end of class." date: "$Date$" revision: "$Revision$" class GB_TABLE_POSITIONER inherit GB_TABLE_POSITIONER_IMP GB_CONSTANTS export {NONE} all undefine default_create, copy, is_equal end EV_STOCK_COLORS rename implementation as stock_color_implementation export {NONE} all undefine default_create, copy, is_equal end GB_SHARED_PIXMAPS export {NONE} all undefine default_create, copy end create make_with_editor feature {NONE} -- Initialization user_initialization -- called by `initialize'. -- Any custom user initialization that -- could not be performed in `initialize', -- (due to regeneration of implementation class) -- can be added here. do set_icon_pixmap (Icon_build_window @ 1) set_title ("EV_TABLE child positioner") end components: GB_INTERNAL_COMPONENTS -- Access to a set of internal components for an EiffelBuild instance. make_with_editor (an_editor: GB_EV_TABLE_EDITOR_CONSTRUCTOR; a_components: GB_INTERNAL_COMPONENTS) -- Create `Current' and assign `an_editor' to `editor' and `a_components' to `components'. require a_components_not_void: a_components /= Void local list_item: EV_LIST_ITEM table_content: LINEAR [EV_WIDGET] do components := a_components default_create editor := an_editor first := editor.first selected_item := Void drawing_area.pointer_motion_actions.extend (agent track_movement) drawing_area.pointer_button_press_actions.extend (agent button_pressed) drawing_area.pointer_button_release_actions.extend (agent button_released) create world create pixmap drawing_area.resize_actions.extend (agent update_pixmap_size) drawing_area.resize_actions.extend (agent (x, y, w, h: INTEGER_32) do draw_widgets end) create projector.make_with_buffer (world, pixmap, drawing_area) split_area.enable_item_expand (split_area.first) split_area.disable_item_expand (split_area.second) create layout_rows_entry.make (editor, rows_parent, rows_string, gb_ev_table_rows, gb_ev_table_rows_tooltip, agent set_rows_and_draw (?), agent valid_row_value (?), components) create layout_columns_entry.make (editor, columns_parent, columns_string, gb_ev_table_columns, gb_ev_table_columns_tooltip, agent set_columns_and_draw (?), agent valid_column_value (?), components) layout_rows_entry.set_text (first.rows.out) layout_columns_entry.set_text (first.columns.out) -- Now create a list item for all children. table_content := first.linear_representation from table_content.start until table_content.off loop -- Note that this is slow, as we have to loop through objects -- to get a match, while inside this loop. list_item := editor.named_list_item_from_widget (table_content.item) list_item.set_data (table_content.item) list_item.select_actions.extend (agent item_selected (table_content.item)) list_item.deselect_actions.extend (agent check_unselect) list.extend (list_item) table_content.forth end ok_button.select_actions.extend (agent hide) show_actions.extend (agent initialize_sizing) selected_item_color := red ensure components_set: components = a_components end editor: GB_EV_TABLE_EDITOR_CONSTRUCTOR first: EV_TABLE user_create_interface_objects -- Create any auxilliary objects needed for MAIN_WINDOW. -- Initialization for these objects must be performed in `user_initialization'. do -- Create attached types defined in class here, initialize them in `user_initialization'. end feature {GB_EV_TABLE_EDITOR_CONSTRUCTOR} -- Implementation update -- Update `Current' to reflect a change in another editor. do selected_item := Void update_prompt draw_widgets end feature {NONE} -- Implementation initialize_sizing -- Set up automatic re-sizing when window is re-sized. -- Also initialize size of scrollable area for the first time. do adjust_scrollable_area scrollable_area.resize_actions.extend (agent (x, y, w, h: INTEGER_32) do adjust_scrollable_area end) end adjust_scrollable_area -- Set initial size of `drawing_area' relative to `scrollable_area' do drawing_area.set_minimum_size (scrollable_area.width.max (first.columns * grid_size + diagram_border * 2), scrollable_area.height.max (first.rows * grid_size + diagram_border * 2)) end update_pixmap_size (x, y, a_width, a_height: INTEGER) -- Resize `pixmap' to `width', `height'. do -- A pixmap is 1x1 as default, -- and you can not set the size to 0x0. -- Why is this? if a_width >= 1 and a_height >=1 then pixmap.set_size (width, height) end end draw_widgets -- Draw representation of all widgets and grid if shown. local relative_pointa, relative_pointb: EV_RELATIVE_POINT figure_rectangle: EV_FIGURE_RECTANGLE widgets: LINEAR [EV_WIDGET] widget: EV_WIDGET do -- Remove all previous figures from `world'. world.wipe_out -- We must draw the grid if necessary. draw_grid widgets := first.linear_representation from widgets.start until widgets.off loop widget := widgets.item if widget /= selected_item then create relative_pointa.make_with_position ((first.item_column_position (widget) - 1) * grid_size + diagram_border, (first.item_row_position (widget) - 1) * grid_size + diagram_border) create relative_pointb.make_with_position ((first.item_column_position (widget) - 1) * grid_size + (first.item_column_span (widget)) * grid_size + diagram_border, (first.item_row_position (widget) - 1) * grid_size + (first.item_row_span (widget)) * grid_size + diagram_border) create figure_rectangle.make_with_points (relative_pointa, relative_pointb) figure_rectangle.set_foreground_color (black) figure_rectangle.remove_background_color world.extend (figure_rectangle) end widgets.forth end if draw_greyed_widget then create relative_pointa.make_with_position (grey_x * grid_size + diagram_border, grey_y * grid_size + diagram_border) create relative_pointb.make_with_position (grey_x * grid_size + grey_x_span * grid_size + diagram_border, grey_y * grid_size + grey_y_span * grid_size + diagram_border) create figure_rectangle.make_with_points (relative_pointa, relative_pointb) figure_rectangle.remove_background_color figure_rectangle.set_foreground_color (grey) world.extend (figure_rectangle) end if selected_item /= Void then create relative_pointa.make_with_position ((first.item_column_position (selected_item) - 1) * grid_size + diagram_border, (first.item_row_position (selected_item) - 1) * grid_size + diagram_border) create relative_pointb.make_with_position ((first.item_column_position (selected_item) - 1) * grid_size + (first.item_column_span (selected_item)) * grid_size + diagram_border, (first.item_row_position (selected_item) - 1) * grid_size + (first.item_row_span (selected_item)) * grid_size + diagram_border) create figure_rectangle.make_with_points (relative_pointa, relative_pointb) figure_rectangle.remove_background_color figure_rectangle.set_foreground_color (selected_item_color) figure_rectangle.set_line_width (highlighted_width) world.extend (figure_rectangle) end projector.project end draw_grid -- Draw snap to grid in `world'. local counter: INTEGER figure_line: EV_FIGURE_LINE color: EV_COLOR relative_point: EV_RELATIVE_POINT rows_size, columns_size: INTEGER do -- Create a light green for the grid color. create color.make_with_8_bit_rgb (196, 244, 204) -- compute commonly requested values. rows_size := first.rows * grid_size columns_size := first.columns * grid_size from counter := 0 until counter > columns_size loop create figure_line.make_with_positions (counter + diagram_border, diagram_border, counter + diagram_border, rows_size + diagram_border) figure_line.set_foreground_color (color) create relative_point.make_with_position (drawing_area.width + diagram_border, counter + diagram_border) world.extend (figure_line) counter := counter + grid_size end from counter := 0 until counter > rows_size loop create figure_line.make_with_positions (diagram_border, counter + diagram_border, columns_size + diagram_border, counter + diagram_border) figure_line.set_foreground_color (color) world.extend (figure_line) counter := counter + grid_size end end set_item_position_and_span (v: EV_WIDGET; a_column, a_row, columns, rows: INTEGER) -- Move `v' to `a_column', `a_row' and resize to `columns', `rows'. do editor.set_item_position_and_span (v, a_column, a_row, columns, rows) end track_movement (a_x_position, a_y_position: INTEGER; x_tilt, y_tilt, pressure: REAL_64; s_x, s_y: INTEGER_32) -- Track `x', `y' position of cursor, and position widgets -- as necessary. local new_x, new_y: INTEGER column_position, row_position, end_row_position, end_column_position: INTEGER new_column, new_row: INTEGER end_position, current_x_position, current_y_position: INTEGER x, y: INTEGER column_pos, row_pos, column_span, row_span: INTEGER orig_column_pos, orig_row_pos, orig_column_span, orig_row_span: INTEGER do -- Transform coordinates to take into account offset of actual diagram. x := a_x_position - diagram_border y := a_y_position - diagram_border if selected_item /= Void and not resizing_widget and not moving_widget then column_position := (first.item_column_position (selected_item) - 1) * grid_size row_position := (first.item_row_position (selected_item) - 1) * grid_size end_column_position := column_position + first.item_column_span (selected_item) * grid_size end_row_position := row_position + first.item_row_span (selected_item) * grid_size if close_to (x, y, end_column_position, end_row_position) or close_to (x, y, column_position, row_position) then if not resizing_widget then set_all_pointer_styles (sizenwse_cursor) end elseif close_to (x, y, column_position, end_row_position) or close_to (x, y, end_column_position, row_position) then if not resizing_widget then set_all_pointer_styles (sizenesw_cursor) end elseif close_to_line (x, y, end_row_position, column_position + accuracy_value, end_column_position - accuracy_value) or close_to_line (x, y, row_position, column_position + accuracy_value, end_column_position - accuracy_value) then if not resizing_widget then set_all_pointer_styles (sizens_cursor) end elseif close_to_line (y, x, column_position, row_position + accuracy_value, end_row_position - accuracy_value) or close_to_line (y, x, end_column_position, row_position + accuracy_value, end_row_position - accuracy_value) then if not resizing_widget then set_all_pointer_styles (sizewe_cursor) end elseif x > column_position and x < end_column_position and y > row_position and y < end_row_position then if not resizing_widget then set_all_pointer_styles (sizeall_cursor) end else if not resizing_widget or not moving_widget then set_all_pointer_styles (standard_cursor) end end end if resizing_widget then -- Store original values so that we only need to set -- any values that changed during the resizing. These -- four values will be changed during the resizing as necessary, -- so that afterwards, we can use these to position the widget. column_pos := first.item_column_position (selected_item) row_pos := first.item_row_position (selected_item) column_span := first.item_column_span (selected_item) row_span := first.item_row_span (selected_item) -- Store the original values for comparing with the values above. -- if one or more have changed, then we can reposition the -- selected item. orig_column_pos := column_pos orig_row_pos := row_pos orig_column_span := column_span orig_row_span := row_span if x_scale /= 0 then if x_offset = 0 then end_position := (original_column + original_column_span) new_x := x + half_grid_size - ((x + half_grid_size) \\ grid_size) current_x_position := (((new_x // grid_size) + 1).max (1)).min (end_position - 1) if not first.area_clear_excluding_widget (selected_item, current_x_position, first.item_row_position (selected_item), end_position - current_x_position, first.item_row_span (selected_item)) then current_x_position := first_filled_horizontal_space (selected_item, end_position - 1, first.item_row_position (selected_item), first.item_row_span (selected_item)) end column_pos := current_x_position column_span := end_position - current_x_position else x := x - column_position new_x := x + half_grid_size - ((x + half_grid_size) \\ grid_size) new_column := ((new_x // grid_size) - first.item_column_position (selected_item) + 1).min (first.columns - first.item_column_position (selected_item) + 1).max (1) if first.item_column_span (selected_item)/= new_column and first.area_clear_excluding_widget (selected_item, first.item_column_position (selected_item), first.item_row_position (selected_item), new_column, first.item_row_span (selected_item)) then --new_column.max (1).min (first.columns - first.item_column_position (selected_item) + 1), first.item_row_span (selected_item)) then column_span := new_column end end end if y_scale /= 0 then if y_offset = 0 then end_position := (original_row + original_row_span) new_y := y + half_grid_size - ((y + half_grid_size) \\ grid_size) current_y_position := (((new_y // grid_size) + 1).max (1)).min (end_position - 1) if not first.area_clear_excluding_widget (selected_item, first.item_column_position (selected_item), current_y_position, first.item_column_span (selected_item), end_position - current_y_position) then current_y_position := first_filled_vertical_space (selected_item, end_position - 1, first.item_column_position (selected_item), first.item_column_span (selected_item)) end row_pos := current_y_position row_span := end_position - current_y_position else y := y - row_position new_y := y + half_grid_size - ((y + half_grid_size) \\ grid_size) new_row := ((new_y // grid_size) - first.item_row_position (selected_item) + 1).min (first.rows - first.item_row_position (selected_item) + 1).max (1) if first.item_row_span (selected_item) /= new_row and first.area_clear_excluding_widget (selected_item, first.item_column_position (selected_item), first.item_row_position (selected_item), first.item_column_span (selected_item), new_row) then row_span := new_row end end end -- Now actually perform the placing of the widget. if orig_column_pos /= column_pos or orig_row_pos /= row_pos or orig_row_span /= row_span or orig_column_span /= column_span then -- We only position and redraw if the size or position has really changed. set_item_position_and_span (selected_item, column_pos, row_pos, column_span, row_span) draw_widgets end end if moving_widget then new_x := x - ((x - x_offset) \\ grid_size) new_y := y - ((y - y_offset) \\ grid_size) x := ((new_x - x_offset) // grid_size + 1).min (first.columns - first.item_column_span (selected_item) + 1).max (1) y := ((new_y - y_offset) // grid_size + 1).min (first.rows - first.item_row_span (selected_item) + 1).max (1) if (first.item_column_position (selected_item) /= x or first.item_row_position (selected_item) /= y) then if first.area_clear_excluding_widget (selected_item, x, y, first.item_column_span (selected_item), first.item_row_span (selected_item)) then set_item_position_and_span (selected_item, x, y, first.item_column_span (selected_item), first.item_row_span (selected_item)) draw_greyed_widget := False else draw_greyed_widget := True grey_x := x - 1 grey_y := y - 1 grey_x_span := first.item_column_span (selected_item) grey_y_span := first.item_row_span (selected_item) end end draw_widgets end update_editors end first_filled_horizontal_space (widget: EV_WIDGET; a_column, a_row, a_row_span: INTEGER): INTEGER -- `Result' is lowest column value, counting down from `a_column' which has the rows -- `a_row' to `a_row' + `a_row_span' free from items. local row_counter, column_counter: INTEGER do from column_counter := a_column until column_counter = 0 or Result /= 0 loop from row_counter := a_row until row_counter = a_row + a_row_span or Result /= 0 loop if first.item_at_position (column_counter, row_counter) /= Void and first.item_at_position (column_counter, row_counter) /= widget then Result := column_counter + 1 end row_counter := row_counter + 1 end column_counter := column_counter - 1 end end first_filled_vertical_space (widget: EV_WIDGET; a_row, a_column, a_column_span: INTEGER): INTEGER -- `Result' is lowest row value, counting down from `a_row' which has the columns -- `a_column' to `a_column' + `a_column_span' free from items. local row_counter, column_counter: INTEGER do from row_counter := a_row until row_counter = 0 or Result /= 0 loop from column_counter := a_column until column_counter = a_column + a_column_span or Result /= 0 loop if first.item_at_position (column_counter, row_counter) /= Void and first.item_at_position (column_counter, row_counter) /= widget then Result := row_counter + 1 end column_counter := column_counter + 1 end row_counter := row_counter - 1 end end close_to (current_x, current_y, desired_x, desired_y: INTEGER): BOOLEAN -- Is position `current_x', `current_y' within `accuracy_value' of `desired_x', `desired_y'. do if (current_x - desired_x).abs < accuracy_value and (current_y - desired_y).abs < accuracy_value then Result := True end end close_to_line (coordinate_a, coordinate_b, line_offset, line_start, line_end: INTEGER): BOOLEAN do if coordinate_a > line_start and coordinate_a < line_end and (coordinate_b - line_offset).abs < accuracy_value then Result := True end end half_grid_size: INTEGER -- Half size of current grid. do Result := grid_size // 2 end button_pressed (a_x_position, a_y_position, a_button: INTEGER; x_tilt, y_tilt, pressure: REAL_64; s_x, s_y: INTEGER_32) -- A button has been pressed. If `a_button' = 1 then -- check for movement/resizing. local column_position, row_position, column_span, row_span, end_column_position, end_row_position: INTEGER x, y: INTEGER do -- We need to make `selected_item' Void and redraw it -- as black in all other editors referencing `Current'. update_editors -- Transform coordinates to take into account offset of actual diagram. x := a_x_position - diagram_border y := a_y_position - diagram_border if selected_item /= Void then -- We must store the original size and position of `selected_item'. -- This is necessary, as in `track_movement', we may move and re-size -- `selected_item', although still need to perform the calculations on -- the current cursor position against the position of `selected_item' -- when the resizing began. original_column := first.item_column_position (selected_item) original_row := first.item_row_position (selected_item) original_column_span := first.item_column_span (selected_item) original_row_span := first.item_row_span (selected_item) -- Now perform some calculations just once ready for later. column_position := (first.item_column_position (selected_item) - 1) * grid_size row_position := (first.item_row_position (selected_item) - 1) * grid_size end_column_position := column_position + first.item_column_span (selected_item) * grid_size end_row_position := row_position + first.item_row_span (selected_item) * grid_size row_span := first.item_row_span (selected_item) * grid_size column_span := first.item_column_span (selected_item) * grid_size if a_button = 1 and not resizing_widget and not moving_widget then -- Unset this, if this is not the case, as we have 8 checks which would need it -- assigning otherwise resizing_widget := True if close_to (x, y, end_column_position, end_row_position) then x_offset := column_span y_offset := row_span x_scale := 1; y_scale := 1 elseif close_to (x, y, column_position, row_position) then x_offset := 0 y_offset := 0 x_scale := 1; y_scale := 1 elseif close_to (x, y, column_position, end_row_position) then x_offset := 0 y_offset := row_span x_scale := 1; y_scale := 1 elseif close_to (x, y, end_column_position, row_position) then x_offset := column_span y_offset := 0 x_scale := 1; y_scale := 1 elseif close_to_line (x, y, end_row_position, column_position + accuracy_value, end_column_position - accuracy_value) then x_offset := x - column_position y_offset := row_span x_scale := 0; y_scale := 1 elseif close_to_line (x, y, row_position, column_position + accuracy_value, end_column_position - accuracy_value) then x_offset := x y_offset := 0 x_scale := 0; y_scale := 1 elseif close_to_line (y, x, column_position, row_position + accuracy_value, end_row_position - accuracy_value) then x_offset := 0 y_offset := y x_scale := 1; y_scale := 0 elseif close_to_line (y, x, end_column_position, row_position + accuracy_value, end_row_position - accuracy_value) then x_offset := column_span y_offset := y x_scale := 1; y_scale := 0 elseif x > column_position and x < end_column_position and y > row_position and y < end_row_position then moving_widget := True resizing_widget := False x_offset := x - column_position y_offset := y - row_position else resizing_widget := False end if resizing_widget or moving_widget then drawing_area.enable_capture end end end -- We need to highlight a widget if the action is -- to select a widget. if a_button = 1 and not resizing_widget and not moving_widget then x := x // grid_size + 1 y := y // grid_size + 1 -- Only perform the query if valid coordinates. if x <= first.columns and y <= first.rows then selected_item := first.item_at_position (x, y) if selected_item /= Void then list.retrieve_item_by_data (selected_item, True).enable_select end update_prompt end draw_widgets end end button_released (x, y, a_button: INTEGER; x_tilt, y_tilt, pressure: REAL_64; s_x, s_y: INTEGER_32) -- A button has been released on `drawing_area' -- If `a_button' = 1, check for end of resize/movement. do draw_greyed_widget := False if a_button = 1 then if resizing_widget then resizing_widget := False set_all_pointer_styles (standard_cursor) drawing_area.disable_capture elseif moving_widget then moving_widget := False set_all_pointer_styles (standard_cursor) drawing_area.disable_capture end draw_widgets end end update_editors -- Update all editors referencing `object'. do editor.update_editors end set_all_pointer_styles (cursor: EV_POINTER_STYLE) -- Assign a pointer style to all figures in -- `world' and `drawing_area'. do from world.start until world.off loop world.item.set_pointer_style (cursor) world.forth end drawing_area.set_pointer_style (cursor) end set_rows (row_value: INTEGER) -- Resize table to accommodate `row_value' rows. do editor.set_rows (row_value) update_editors end set_columns (column_value: INTEGER) -- Resize table to accommodate `column_value' columns. do editor.set_columns (column_value) update_editors end set_rows_and_draw (row_value: INTEGER) -- Resize table to accommodate `row_value' rows. do editor.set_rows (row_value) drawing_area.set_minimum_height (grid_size * row_value + diagram_border * 2) layout_rows_entry.set_text (row_value.out) update_editors draw_widgets update_prompt end set_columns_and_draw (column_value: INTEGER) -- Resize table to accommodate `column_value' columns. do editor.set_columns (column_value) drawing_area.set_minimum_width (grid_size * column_value + diagram_border * 2) layout_columns_entry.set_text (column_value.out) update_editors draw_widgets update_prompt end valid_row_value (new_value: INTEGER): BOOLEAN -- Is `new_value' a valid row size for table. do Result := first.rows_resizable_to (new_value) end valid_column_value (new_value: INTEGER): BOOLEAN -- Is `new_value' a valid column size for table. do Result := first.columns_resizable_to (new_value) end valid_spacing (new_value: INTEGER): BOOLEAN -- Is `new_value' a valid spacing value? do Result := new_value > 0 end item_selected (widget: EV_WIDGET) -- Item representing `widget' in `list' has -- become selected, so update_display to -- reflect this. do selected_item := widget draw_widgets end check_unselect -- If no item is selected in `list' any more, -- then we must remove the highlighting from -- the display. do if list.selected_items.count = 0 then selected_item := Void draw_widgets end end table_minimal_full: BOOLEAN -- Is table represented by `Current', -- 1x1 and full? do Result := first.count = 1 and first.rows = 1 and first.columns = 1 end update_prompt -- Update `prompt_label'. do if table_minimal_full then prompt_label.set_text (full_prompt) elseif selected_item /= Void then prompt_label.set_text (position_prompt) else prompt_label.set_text (select_prompt) end end feature {NONE} -- Attributes resizing_widget: BOOLEAN -- Is a widget currently being resized? moving_widget: BOOLEAN -- Is a widget currently being moved? x_offset, y_offset: INTEGER -- Offsets used to hold cursor distance from -- point being targeted. x_scale, y_scale: INTEGER -- Amount to scale movement in the X or Y axis by. -- Should be 1 or 0. 1 means full movement, 0 means -- that axis is ignored. accuracy_value: INTEGER = 3 -- Value which determines how close pointer must be -- to lines/points for resizing. projector: EV_DRAWING_AREA_PROJECTOR -- Projector used for `world' pixmap: EV_PIXMAP -- Pixmap for double buffering `world'. world: EV_FIGURE_WORLD -- Figure world containg all widget representations. grid_size: INTEGER = 20 -- Size of grid representing the table. highlighted_width: INTEGER = 3 -- Width of line used to draw highlighted item. selected_item: EV_WIDGET -- Item that is currently selected for movement. selected_item_color: EV_COLOR -- Color used to draw `selected_item'. diagram_border: INTEGER = 25 -- Size of border around table representation in diagram. draw_greyed_widget: BOOLEAN -- Should a greyed representation of the desired widget position/ -- size be drawn grey_x, grey_y, grey_x_span, grey_y_span: INTEGER -- Table coordinates for `draw_greyed_widget'. original_column, original_row, original_column_span, original_row_span: INTEGER -- Temporary variables used to keep original position of `selected_item' -- when button was pressed. These values are used in `track_movement' to -- calculate new size/position of `selected_item', based on the current mouse -- position. select_prompt: STRING = "Please select desired widget." -- Prompt to help user. full_prompt: STRING = "Child fills table. Resize table to manipulate." -- Prompt when table is full. position_prompt: STRING = "Position highlighted widget." -- Prompt when widget is selected. rows_string: STRING = "Rows" columns_string: STRING = "Columns" layout_rows_entry, layout_columns_entry: GB_INTEGER_INPUT_FIELD; note copyright: "Copyright (c) 1984-2018, Eiffel Software" license: "GPL version 2 (see http://www.eiffel.com/licensing/gpl.txt)" licensing_options: "http://www.eiffel.com/licensing" copying: "[ This file is part of Eiffel Software's Eiffel Development Environment. Eiffel Software's Eiffel Development Environment is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2 of the License (available at the URL listed under "license" above). Eiffel Software's Eiffel Development Environment is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Eiffel Software's Eiffel Development Environment; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ]" 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