note description: "[ A EV_FIGURE_GROUP is an ARRAYED_LIST of EV_FIGURE. Since EV_FIGURE_GROUP is also an EV_FIGURE (composite pattern) you can rotate, scale and change the position of an EV_FIGURE_GROUP. All elements in the group are rotated around the center of the EV_FIGURE_GROUP. A EV_FIGURE can only be grouped in one group at the same time. ]" legal: "See notice at end of class." status: "See notice at end of class." date: "$Date: 2014-06-03 19:49:45 -0400 (Tue, 03 Jun 2014) $" revision: "$Revision: 44 $" class EV_MODEL_GROUP inherit EV_MODEL undefine point_count redefine rotate, create_interface_objects, default_create, recursive_transform, set_x, set_y, set_x_y, invalid_rectangle, update_rectangle, invalidate, validate, bounding_box end ARRAYED_LIST [EV_MODEL] rename make as list_make export {NONE} put_i_th undefine is_equal, copy redefine append, force, extend, replace, insert, prune_all, wipe_out, remove, merge_left, merge_right, make_from_array, default_create, swap, has, make_filled, list_make, -- I added these at, last, first end EV_MODEL_SINGLE_POINTED undefine default_create end create default_create, make_with_point, make_with_position create {EV_MODEL_GROUP} list_make, make_filled feature {NONE} -- Initialization create_interface_objects -- do create lookup_table.make (initiale_size) create point_array.make_empty (1) make_empty_area (initiale_size) Precursor {EV_MODEL} end default_create -- Create an empty EV_MODEL_GROUP. do Precursor {EV_MODEL} point_array.extend (create {EV_COORDINATE}.make (0, 0)) index := 0 is_grouped := True ensure then is_grouped: is_grouped not_is_in_group: not is_in_group end make_filled (n: INTEGER_32) -- do default_create Precursor {ARRAYED_LIST} (n) end list_make (n: INTEGER) -- do default_create Precursor {ARRAYED_LIST} (n) end feature -- Access angle: DOUBLE -- `Current' has to be rotated around (`x',`y') for -`angle' -- to be in upright position. do Result := current_angle ensure then Result_equal_current_angle: Result = current_angle end point_x: INTEGER -- x position of `point'. do Result := point_array.item (0).x end point_y: INTEGER -- y position of `point'. do Result := point_array.item (0).y end deep_elements: LIST [EV_MODEL] -- All elements in `Current' and in its subgroups. local l_group: detachable EV_MODEL_GROUP l_list: ARRAYED_LIST [EV_MODEL] do create l_list.make (0) from start until after loop l_group ?= item if l_group /= Void then l_list.append (l_group.deep_elements) end l_list.extend (item) forth end Result := l_list ensure Result_not_Void: Result /= Void end at alias "@" (i: INTEGER): like item assign put_i_th -- Item at `i'-th position, redefined by jjj to conform to `item' do check attached {like item} Precursor {ARRAYED_LIST} (i) as v then Result := v end end first: like item -- Item at first position, redefined by jjj to conform to `item' do check attached {like item} Precursor {ARRAYED_LIST} as v then Result := v end end last: like item -- Item at last position, redefined by jjj to conform to `item' do check attached {like item} Precursor {ARRAYED_LIST} as v then Result := v end end feature -- Status report is_rotatable: BOOLEAN -- Is this figure group rotatable? local l_area: like area i, nb: INTEGER do if is_grouped then Result := True l_area := area from i := 0 nb := count - 1 until i > nb or not Result loop Result := l_area.item (i).is_rotatable i := i + 1 end else Result := True end end is_scalable: BOOLEAN -- Is this figure group scalable? local l_area: like area i, nb: INTEGER do if is_grouped then Result := True l_area := area from i := 0 nb := count - 1 until i > nb or not Result loop Result := l_area.item (i).is_scalable i := i + 1 end else Result := True end end is_transformable: BOOLEAN -- Is this figure group transformable? local l_area: like area i, nb: INTEGER do if is_grouped then Result := True l_area := area from i := 0 nb := count - 1 until i > nb or not Result loop Result := l_area.item (i).is_transformable i := i + 1 end else Result := True end end is_grouped: BOOLEAN -- Is grouped? has (v: like item): BOOLEAN -- Does current include `v'? -- (based on v.id) do if v /= Void then Result := lookup_table.has (v.id) end end has_deep (figure: EV_MODEL): BOOLEAN -- Does any item contains `figure'? local grp: detachable EV_MODEL_GROUP do from start until after or Result loop grp ?= item if grp = Void then Result := figure = item elseif grp = figure then Result := True else Result := grp.has_deep (figure) end forth end end invalid_rectangle: detachable EV_RECTANGLE -- Rectangle that needs erasing. -- `Void' if no change is made. local r: detachable EV_RECTANGLE l_area: like area i, nb: INTEGER do if not valid then from l_area := area i := 0 nb := count - 1 until i > nb loop r := l_area [i].invalid_rectangle if r /= Void and then r.has_area and then l_area [i].is_show_requested then if Result = Void then if internal_invalid_rectangle = Void then create internal_invalid_rectangle end Result := internal_invalid_rectangle Result.copy (r) else Result.merge (r) end end i := i + 1 end end end update_rectangle: detachable EV_RECTANGLE -- Rectangle that needs redrawing. -- `Void' if no change is made. local r: detachable EV_RECTANGLE l_area: like area i, nb: INTEGER do if not valid and then is_show_requested and then is_grouped then from l_area := area i := 0 nb := count - 1 until i > nb loop r := l_area [i].update_rectangle if r /= Void then if Result = Void then if last_update_rectangle = Void then -- Reuse `local_update_rectangle' create last_update_rectangle end Result := last_update_rectangle Result.copy (r) else Result.merge (r) end end i := i + 1 end end end feature -- Visitor project (a_projector: EV_MODEL_DRAWING_ROUTINES) -- do across Current as ic_models loop if ic_models.item.is_show_requested then ic_models.item.project (a_projector) end end end feature -- Element change set_x (a_x: INTEGER) -- Set `x' to `an_x'. local a_delta_x: INTEGER do a_delta_x := a_x - x if a_delta_x /= 0 then projection_matrix.translate (a_delta_x, 0) recursive_transform (projection_matrix) if is_in_group and then attached group as l_group and then l_group.is_center_valid then l_group.center_invalidate end end center.set_x (a_x) is_center_valid := True end set_y (a_y: INTEGER) -- Set `y' to `an_y'. local a_delta_y: INTEGER do a_delta_y := a_y - y if a_delta_y /= 0 then projection_matrix.translate (0, a_delta_y) recursive_transform (projection_matrix) if is_in_group and then attached group as l_group and then l_group.is_center_valid then l_group.center_invalidate end end center.set_y (a_y) is_center_valid := True end set_x_y (a_x, a_y: INTEGER) -- Set `x' to `an_x'. local a_delta_x, a_delta_y: INTEGER do a_delta_x := a_x - x a_delta_y := a_y - y if a_delta_x /= 0 or a_delta_y /= 0 then projection_matrix.translate (a_delta_x, a_delta_y) recursive_transform (projection_matrix) if is_in_group and then attached group as l_group and then l_group.is_center_valid then l_group.center_invalidate end end center.set (a_x, a_y) is_center_valid := True end set_point_position (a_x, a_y: INTEGER) -- Set position of `point' to (`a_x', `a_y'). local a_delta_x, a_delta_y: DOUBLE do a_delta_x := a_x - point_array [0].x_precise a_delta_y := a_y - point_array [0].y_precise if a_delta_x /= 0 or a_delta_y /= 0 then projection_matrix.translate (a_delta_x, a_delta_y) recursive_transform (projection_matrix) if is_center_valid then center_invalidate end end end ungroup -- Ungroup all `figures'. require is_grouped: is_grouped not_is_in_group: not is_in_group do from start until after loop item.set_group (Void) forth end is_grouped := False current_angle := 0 center_invalidate ensure is_ungrouped: not is_grouped none_is_in_a_group: not there_exists (agent {EV_MODEL}.is_in_group) center_is_invalid: not is_center_valid end regroup -- Group the `figures' in the group. require not_is_grouped: not is_grouped not_is_in_group: not is_in_group do from start until after loop item.set_group (Current) forth end is_grouped := True center_invalidate ensure is_grouped: is_grouped all_grouped: for_all (agent {EV_MODEL}.is_in_group) center_is_invalid: not is_center_valid end -- jjj send_backward (a_figure: EV_MODEL) send_backward (a_figure: like item) -- so I could redefine `item' in my worlds -- Send `a_figure' one layer backwards. require a_figure_in_figures: has (a_figure) do if first /= a_figure then start search (a_figure) swap (index - 1) end full_redraw ensure a_figure_in_group: a_figure.group = Current a_figure_in_current: has (a_figure) end -- jjj bring_forward (a_figure: EV_MODEL) bring_forward (a_figure: like item) -- so I could redefine `item' in my worlds -- Bring `a_figure' one layer forwards. require a_figure_in_figures: has (a_figure) do if last /= a_figure then start search (a_figure) swap (index + 1) end full_redraw ensure a_figure_in_group: a_figure.group = Current a_figure_in_current: has (a_figure) end -- jjj send_to_back (a_figure: EV_MODEL) send_to_back (a_figure: like item) -- Send `a_figure' to the bottom most layer. -- I redefined this because `item' gets redefined in -- my classes. require a_figure_in_figures: has (a_figure) do if first /= a_figure then start search (a_figure) remove put_front (a_figure) end full_redraw ensure is_at_front: first = a_figure a_figure_in_group: a_figure.group = Current a_figure_in_current: has (a_figure) end -- jjj bring_to_front (a_figure: EV_MODEL) bring_to_front (a_figure: like item) -- Bring `a_figure' to the top most layer -- I redefined this because `item' gets redefined in -- my classes. require a_figure_in_figures: has (a_figure) do if last /= a_figure then start search (a_figure) remove extend (a_figure) end full_redraw ensure is_last: last = a_figure a_figure_in_group: a_figure.group = Current a_figure_in_current: has (a_figure) end -- jjj added feature send_to_index (fig: like item; a_index: INTEGER) -- Feature added to this override class that places `a_figure' -- at the position indicated by `a_index' require a_figure_in_figures: has (fig) do if a_index <= 1 then send_to_back (fig) elseif a_index >= count then bring_to_front (fig) else start search (fig) remove -- The following code was copied from `extend'; instead of -- callin Precursor {ARRAYED_LIST} we use `put_i_th' lookup_table.put (fig, fig.id) -- Precursor {ARRAYED_LIST} (fig) go_i_th (a_index) put_left (fig) fig.set_group (Current) center_invalidate invalidate full_redraw end end rotate (an_angle: DOUBLE) -- Rotate around the center for `an_angle'. do current_angle := current_angle + an_angle Precursor {EV_MODEL} (an_angle) ensure then angle_equal_an_angle: angle = old angle + an_angle end feature -- List change insert (fig: like item; i: INTEGER) -- Add `fig' to the group. do lookup_table.put (fig, fig.id) Precursor {ARRAYED_LIST} (fig, i) fig.set_group (Current) center_invalidate invalidate full_redraw ensure then fig_in_lookup_table: fig /= Void implies lookup_table.has (fig.id) fig_in_group: fig /= Void implies fig.group = Current end extend (fig: like item) -- Add `fig' to the group. do lookup_table.put (fig, fig.id) Precursor {ARRAYED_LIST} (fig) fig.set_group (Current) center_invalidate invalidate full_redraw ensure then fig_in_lookup_table: fig /= Void implies lookup_table.has (fig.id) fig_in_group: fig /= Void implies fig.group = Current end force (fig: like item) -- Add `fig' to the group. do lookup_table.put (fig, fig.id) Precursor {ARRAYED_LIST} (fig) fig.set_group (Current) center_invalidate invalidate full_redraw ensure then fig_in_lookup_table: fig /= Void implies lookup_table.has (fig.id) fig_in_group: fig /= Void implies fig.group = Current end replace (fig: like item) -- Replace current item by `fig'. do item.unreference_group lookup_table.remove (item.id) Precursor {ARRAYED_LIST} (fig) fig.set_group (Current) lookup_table.put (fig, fig.id) center_invalidate invalidate full_redraw ensure then fig_in_lookup_table: fig /= Void implies lookup_table.has (fig.id) item_not_in_lookup_table: not lookup_table.has (old item.id) fig_in_group: fig /= Void implies fig.group = Current item_not_in_group: not (old item).is_in_group end remove -- Remove `item' from figure. do item.unreference_group lookup_table.remove (item.id) Precursor {ARRAYED_LIST} center_invalidate invalidate full_redraw ensure then item_not_in_lookup_table: not lookup_table.has (old item.id) item_not_in_group: not (old item).is_in_group end prune_all (fig: like item) -- Remove `fig' from the group. do if has (fig) then fig.unreference_group lookup_table.remove (fig.id) end Precursor {ARRAYED_LIST} (fig) center_invalidate invalidate full_redraw ensure then item_not_in_lookup_table: fig /= Void implies not lookup_table.has (fig.id) end merge_left (other: ARRAYED_LIST [EV_MODEL]) -- Merge `other' into group before cursor. -- `other' will be empty afterwards. do insert_list_to_table (other) change_group (other) Precursor {ARRAYED_LIST} (other) center_invalidate invalidate full_redraw end merge_right (other: ARRAYED_LIST [EV_MODEL]) -- Merge `other' into group after cursor. -- `other' will be empty afterwards. do insert_list_to_table (other) change_group (other) Precursor {ARRAYED_LIST} (other) center_invalidate invalidate full_redraw end wipe_out -- Remove all items. do from start until after loop if item.is_in_group then item.unreference_group end forth end Precursor {ARRAYED_LIST} lookup_table.wipe_out center_invalidate invalidate full_redraw end append (s: SEQUENCE [EV_MODEL]) -- append (s: SEQUENCE [like item]) -- Append a copy of `s'. local l: like s l_cursor: CURSOR do if s = Current then l := s.twin else l := s end from resize (count + s.count) l_cursor := cursor l.start until l.exhausted loop extend (l.item) lookup_table.put (l.item, l.item.id) l.forth end go_to (l_cursor) center_invalidate invalidate full_redraw end -- jjj make_from_array (a: ARRAY [EV_MODEL]) make_from_array (a: ARRAY [like item]) -- Create list from array `a'. local i: INTEGER do wipe_out resize (a.count) from i := a.lower until i > a.upper loop if a.item (i) /= Void then extend (a.item (i)) end i := i + 1 end center_invalidate invalidate full_redraw end swap (i: INTEGER) -- Exchange item at `i'-th position with item -- at cursor position. local old_item: like item do old_item := item Precursor {ARRAYED_LIST} (i) lookup_table.put (old_item, old_item.id) old_item.set_group (Current) ensure then item_in_lookup_table: lookup_table.has (old item.id) i_in_lookup_table: lookup_table.has (i_th (i).id) item_in_group: (old item).group = Current i_th_in_group: i_th (i).group = Current end feature -- Status settings invalidate -- Some property of `Current' has changed. local l_area: like area i, nb: INTEGER do if valid then Precursor {EV_MODEL} from l_area := area i := 0 nb := count - 1 until i > nb loop if l_area [i].valid then l_area [i].invalidate end i := i + 1 end else Precursor {EV_MODEL} end end validate -- Validate `Current'. local l_area: like area i, nb: INTEGER l_figure: EV_MODEL l_rect: detachable EV_RECTANGLE do if not valid then if count > 0 then create l_rect l_area := area from i := 0 nb := count - 1 until i > nb loop l_figure := l_area.item (i) if not l_figure.valid then l_figure.validate end i := i + 1 end if world = Current then -- We do not want the origin of the world to be included in the update. l_rect := calculated_bounding_box if l_rect = Void then create l_rect end else update_rectangle_to_bounding_box (l_rect) end if internal_invalid_rectangle /= Void then internal_invalid_rectangle.copy (l_rect) else internal_invalid_rectangle := l_rect end elseif internal_invalid_rectangle /= Void then -- Reset any previous invalid rectangle. internal_invalid_rectangle.move_and_resize (0, 0, 0, 0) end valid := True end end feature -- Events position_on_figure (a_x, a_y: INTEGER): BOOLEAN -- Is the point on (`a_x', `a_y') on this figure? --| Used to generate events. -- Always returns `False', but descendants can override -- it to improve efficiency. do Result := False end bounding_box: EV_RECTANGLE -- Smallest orthogonal rectangular area `Current' fits in. local l_result: detachable EV_RECTANGLE do if attached internal_bounding_box as l_internal_bounding_box and then l_internal_bounding_box.has_area then Result := l_internal_bounding_box.twin else l_result := calculated_bounding_box if world = Current then -- If `Current' is the world then we need then we need the origin to be included so that its size is remembered. create Result.make (point_x, point_y, 0, 0) if l_result /= Void then Result.merge (l_result) end else if l_result /= Void then Result := l_result else create Result end end if attached internal_bounding_box as l_internal_bounding_box then l_internal_bounding_box.copy (Result) else internal_bounding_box := Result.twin end end end feature {EV_MODEL_GROUP} -- Figure group recursive_transform (a_transformation: EV_MODEL_TRANSFORMATION) -- Same as transform but without precondition -- is_transformable and without invalidating -- groups center local l_area: like area i, nb: INTEGER do a_transformation.project (point_array.item (0)) if is_grouped then from i := 0 nb := count - 1 l_area := area until i > nb loop l_area.item (i).recursive_transform (a_transformation) i := i + 1 end end invalidate is_center_valid := False end feature {NONE} -- Implementation calculated_bounding_box: detachable EV_RECTANGLE -- Smallest orthogonal rectangular area `Current' fits in. do if is_grouped then create Result update_rectangle_to_calculated_bounding_box (Result) end end update_rectangle_to_calculated_bounding_box (a_rectangle: EV_RECTANGLE) -- Smallest orthogonal rectangular area `Current' fits in. local l_area: like area i, nb: INTEGER l_bbox: like internal_bounding_box l_initial: BOOLEAN do if is_grouped then from create l_bbox l_initial := True l_area := area i := 0 nb := count - 1 until i > nb loop if l_area.item (i).is_show_requested then l_area [i].update_rectangle_to_bounding_box (l_bbox) if l_bbox.width > 0 and then l_bbox.height > 0 then if l_initial then a_rectangle.copy (l_bbox) l_initial := False else a_rectangle.merge (l_bbox) end end end i := i + 1 end end end current_angle: DOUBLE -- The rotating angle. set_center -- Set x and y such that they are in the -- center of the group. local l_bbox: like bounding_box do if is_grouped then l_bbox := bounding_box center.set_precise (l_bbox.left + l_bbox.width /2, l_bbox.top + l_bbox.height / 2) else center.set (0, 0) end is_center_valid := True end initiale_size: INTEGER = 5 -- Initialize size of `Current'. change_group (other: ARRAYED_LIST [EV_MODEL]) -- Change group of all figures in `other' to Current. -- Used by `merge_left' and `merge_right'. local n: INTEGER do from n := 1 until n > other.count loop other.i_th (n).set_group (Current) n := n + 1 end end full_redraw -- Request `invalid_rectangle' to be ignored. local w: detachable EV_MODEL_WORLD do w := world if w /= Void then w.full_redraw end end lookup_table: HASH_TABLE [EV_MODEL, INTEGER] -- Lookup table to search faster. insert_list_to_table (list: ARRAYED_LIST [EV_MODEL]) -- Insert list element to lookup_table. do from list.start until list.after loop lookup_table.put (list.item, list.item.id) list.forth end end invariant is_grouped_implies_all_grouped: is_grouped implies for_all (agent {EV_MODEL}.is_in_group) angle_equal_current_angle: angle = current_angle not_is_grouped_implies_angel_equals_zero: not is_grouped implies (angle = 0) 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_MODEL_GROUP