note description: "Representation of a row of an EV_GRID" legal: "See notice at end of class." status: "See notice at end of class." date: "$Date$" revision: "$Revision$" class EV_GRID_ROW_I inherit REFACTORING_HELPER undefine default_create, copy, is_equal end EV_GRID_ROW_ACTION_SEQUENCES_I export {EV_GRID_I} deselect_actions_internal end EV_DESELECTABLE_I export {EV_ANY, EV_ANY_I, EV_GRID_DRAWER_I} attached_interface redefine interface, is_selectable end HASHABLE create make feature {EV_ANY} -- Initialization old_make (an_interface: attached like interface) -- Create `Current' with interface `an_interface'. do assign_interface (an_interface) end make -- Initialize `Current'. do create subrows -- Set `internal_height' to -1 to signify that the parent grid should be queried for height unless explicitly set. internal_height := -1 depth_in_tree := 1 indent_depth_in_tree := 1 subrow_count_recursive := 0 expanded_subrow_count_recursive := 0 is_expanded := False hash_code := 0 is_show_requested := True set_is_initialized (True) end feature {EV_GRID_I, EV_GRID_ROW_I} -- Initialization index_of_first_item_dirty: BOOLEAN -- Should `index_of_first_item' be recalculated. flag_index_of_first_item_dirty_if_needed (a_column_index: INTEGER) -- Flag `index_of_first_item' to be recalculated on next call if needed based on `a_column_index'. require a_column_index_valid: a_column_index > 0 and then a_column_index <= count + 1 do if not index_of_first_item_dirty then -- We do not want to reset if already True. index_of_first_item_dirty := a_column_index <= index_of_first_item_internal or else index_of_first_item_internal = 0 end end index_of_first_item_internal: INTEGER -- Previous result of call to `index_of_first_item'. set_index (a_index: INTEGER) -- Set the index of row in grid. require a_index_greater_than_zero: a_index > 0 do index := a_index ensure index_set: index = a_index indexes_equivalent: attached parent_i as l_parent_i and then l_parent_i.rows.i_th (index) = Current index_less_than_row_count: attached parent as l_parent and then index <= l_parent.row_count end set_subrow_index (a_subrow_index: INTEGER) -- Set the index of row in parent row. require subrow_index_not_negative: a_subrow_index >= 0 do subrow_index := a_subrow_index ensure index_set: subrow_index = a_subrow_index indexes_equivalent: attached parent_row_i as l_parent_row_i and then a_subrow_index > 0 implies l_parent_row_i.subrows.i_th (subrow_index) = Current index_less_than_row_count: attached parent_row_i as l_parent_row_i and then subrow_index <= l_parent_row_i.subrow_count end set_parent_i (a_grid_i: EV_GRID_I; a_row_id: INTEGER) -- Make `Current' associated with `a_grid_i'. require a_grid_i_not_void: a_grid_i /= Void grid_not_already_set: parent_i = Void a_row_id_valid: a_row_id > 0 do parent_i := a_grid_i hash_code := a_row_id ensure parent_set: parent_i = a_grid_i row_id_set: hash_code = a_row_id end feature -- Access subrow (i: INTEGER): EV_GRID_ROW -- `i'-th child of Current. require is_parented: parent /= Void i_positive: i > 0 i_less_than_subrow_count: i <= subrow_count local l_row: detachable EV_GRID_ROW_I do l_row := subrows.i_th (i) check l_row /= Void then end Result := l_row.attached_interface ensure subrow_not_void: Result /= Void subrow_valid: (Result.parent /= Void) and then has_subrow (Result) and then Result.parent_row = attached_interface end has_subrow (a_row: EV_GRID_ROW): BOOLEAN -- Is `a_row' a child of Current? require a_row_not_void: a_row /= Void is_parented: parent /= Void do Result := subrows.has (a_row.implementation) ensure has_subrow_same_parent: Result implies ((a_row.parent /= Void and parent /= Void) and then a_row.parent = parent) end parent_row: detachable EV_GRID_ROW -- Parent row of Current if any, Void otherwise. require is_parented: parent /= Void do if attached parent_row_i as l_parent_row_i then Result := l_parent_row_i.interface end ensure result_void_when_tree_node_enabled: (parent = Void) or (attached parent as l_parent and then not l_parent.is_tree_enabled) implies Result = Void has_parent: Result /= Void implies Result.has_subrow (attached_interface) end parent: detachable EV_GRID -- Grid to which current row belongs. do if attached parent_i as l_parent_i then Result := l_parent_i.interface end end parent_row_root: detachable EV_GRID_ROW -- Parent row which is the root of the tree structure -- in which `Current' is contained. May be `Current' if -- `Current' is the root node of a tree structure. require is_parented: parent /= Void local current_parent: EV_GRID_ROW_I do if attached parent as l_parent and then l_parent.is_tree_enabled then -- If the tree is not enabled then `Result' must be False. if attached parent_row_i as l_parent_row_i then from current_parent := l_parent_row_i until current_parent.parent_row_i = Void loop if attached current_parent.parent_row_i as l_par_row_i then current_parent := l_par_row_i end end Result := current_parent.attached_interface else -- In this case, there is no `parent_row_i' as `Current' -- is the root node of a tree, so return `interface' Result := attached_interface end end ensure result_consistent_with_parent_tree_properties: (parent = Void or else (attached parent as l_parent and then not l_parent.is_tree_enabled)) = (Result = Void) end item (i: INTEGER): detachable EV_GRID_ITEM -- Item at `i'-th column, Void if none. require i_within_bounds: i > 0 and i <= count is_parented: parent /= Void do if attached parent_i as l_parent_i then Result := l_parent_i.item (i, index) end end selected_items: ARRAYED_LIST [EV_GRID_ITEM] -- All items selected in `Current'. require is_parented: parent /= Void local i, a_count: INTEGER a_item: detachable EV_GRID_ITEM_I temp_parent_i: like parent_i do from i := 1 a_count := count create Result.make (a_count) temp_parent_i := parent_i check temp_parent_i /= Void then end until i > a_count loop -- If `is_selected' then we need to make sure there are no Void items contained within `Current' a_item := temp_parent_i.item_internal (i, index) if a_item /= Void and then a_item.is_selected then Result.extend (a_item.attached_interface) end i := i + 1 end ensure result_not_void: Result /= Void valid_count: Result.count <= count end is_expanded: BOOLEAN -- Are subrows of `Current' displayed? height: INTEGER -- Height of `Current' when displayed in a `parent' -- that has `is_row_height_fixed' set to `False'. -- Note that `height' is ignored if the parent has -- `is_row_height_fixed' set to `True' and then all rows -- are displayed with identical heights set to `parent.row_height'. require is_parented: parent /= Void do if attached parent_i as l_parent_i then Result := internal_height if Result = -1 or else l_parent_i.is_row_height_fixed then Result := l_parent_i.row_height end end ensure result_not_negative: Result >= 0 end internal_height: INTEGER -- Internal storage for `Current'. is_selected: BOOLEAN -- Is objects state set to selected. do if attached parent_i as l_parent_i then if l_parent_i.is_row_selection_enabled or else index_of_first_item = 0 then Result := internal_is_selected else Result := internal_are_all_non_void_items_selected end end end is_selectable: BOOLEAN -- May the object be selected? do Result := parent_i /= Void end internal_is_selected: BOOLEAN -- Is `Current' selected in either row selection mode? internal_update_selection (a_selection_state: BOOLEAN) -- Set the selection state of all non void items in `Current' to `a_selection_state'. local a_item: detachable EV_GRID_ITEM_I i: INTEGER a_count: INTEGER a_index: INTEGER a_parent_i: detachable EV_GRID_I do from i := 1 a_index := index a_parent_i := parent_i check a_parent_i /= Void then end a_count := count until i > a_count loop a_item := a_parent_i.item_internal (i, a_index) if a_item /= Void then if a_selection_state then a_item.enable_select_internal else a_item.disable_select_internal end end i := i + 1 end end internal_are_all_non_void_items_selected: BOOLEAN -- Are all the non void items in `Current' selected? -- False if no non void items are present. local a_item: detachable EV_GRID_ITEM_I i: INTEGER a_count: INTEGER non_void_item_found: BOOLEAN a_index: INTEGER l_parent_i: detachable EV_GRID_I do from i := 1 Result := True a_count := count a_index := index l_parent_i := parent_i check l_parent_i /= Void then end until not Result or else i > a_count loop a_item := l_parent_i.item_internal (i, a_index) if a_item /= Void then non_void_item_found := True Result := a_item.is_selected end i := i + 1 end if not non_void_item_found then -- If there are no non-void items in row then it cannot be selected Result := False end end virtual_y_position: INTEGER -- Vertical offset of `Current' in relation to the -- the virtual area of `parent' grid in pixels. -- `Result' is 0 if `parent' is `Void'. do if is_locked and then attached parent_i as l_parent_i and then attached locked_row as l_locked_row then Result := l_parent_i.internal_client_y + l_locked_row.offset else Result := virtual_y_position_unlocked end ensure parent_void_implies_result_zero: parent = Void implies Result = 0 end virtual_y_position_unlocked: INTEGER -- Vertical offset of unlocked position of `Current', in relation to the -- virtual area of `parent' grid in pixels. -- If not `is_locked', then `virtual_y_position' = `unlocked_virtual_y_position'. -- If `is_locked' then `Result' is the "shadow" position where the row would -- be if not locked. do if attached parent_i as l_parent_i then -- If there is no parent then return 0. l_parent_i.perform_vertical_computation -- Recompute vertically if required. if attached l_parent_i.row_offsets as l_row_offsets then -- As `row_offsets' exists, we can look it up, -- otherwise it must be computed. Result := l_row_offsets @ (index) else Result := (index - 1) * l_parent_i.row_height end end ensure parent_void_implies_result_zero: parent = Void implies Result = 0 end is_expandable: BOOLEAN -- May `Current' be expanded? require is_parented: parent /= Void do Result := parent /= Void and (subrow_count > 0 or is_ensured_expandable) end hash_code: INTEGER -- Number to uniquely identify grid row within `parent_i'. -- Should be set to 0 if `Current' is not parented. feature -- Status report locked_row: detachable EV_GRID_LOCKED_ROW_I -- Locked row information for `Current'. -- `Void' if not locked. subrow_count: INTEGER -- Number of children. require is_parented: parent /= Void do Result := subrows.count ensure subrow_count_non_negative: subrow_count >= 0 subrow_count_in_range: attached parent as l_parent and then subrow_count <= (l_parent.row_count - index) end subrow_count_recursive: INTEGER -- Number of child rows and their child rows recursively. index: INTEGER -- Position of Current in `parent'. subrow_index: INTEGER -- Position of `Current' in `parent_row' if any. count: INTEGER -- Number of items in current. do if attached parent_i as l_parent_i then Result := l_parent_i.columns.count end ensure count_not_negative: count >= 0 end index_of_first_item: INTEGER -- Return the index of the first non `Void' item within `Current' -- or 0 if none. do if index_of_first_item_dirty then index_of_first_item_internal := index_of_first_item_value index_of_first_item_dirty := False end Result := index_of_first_item_internal ensure valid_result: Result >= 0 and Result <= count value_valid: index_of_first_item_internal = index_of_first_item_value end index_of_first_item_value: INTEGER -- Return the index of the first non `Void' item within `Current' -- or 0 if none. local counter: INTEGER current_row_list: detachable SPECIAL [detachable EV_GRID_ITEM_I] current_row_list_count: INTEGER a_item: detachable EV_GRID_ITEM_I column_list_area: SPECIAL [detachable EV_GRID_COLUMN_I] a_column: detachable EV_GRID_COLUMN_I a_physical_index: INTEGER row_count: INTEGER l_parent_i: like parent_i do l_parent_i := parent_i check l_parent_i /= Void then end current_row_list := l_parent_i.internal_row_data @ index check current_row_list /= Void then end column_list_area := l_parent_i.columns.area current_row_list_count := current_row_list.count row_count := count from counter := 0 until a_item /= Void or else counter = row_count loop a_column := (column_list_area @ counter) if a_column /= Void then a_physical_index := a_column.physical_index end if a_physical_index < current_row_list_count then a_item := current_row_list @ a_physical_index end counter := counter + 1 end if a_item /= Void then Result := counter end ensure valid_result: Result >= 0 and Result <= count end background_color: detachable EV_COLOR -- Color displayed as background of `Current' except where there are items contained that -- have a non-`Void' `background_color'. If `Void', `background_color' of `parent' is displayed.. foreground_color: detachable EV_COLOR -- Color displayed for foreground features of `Current' except where there are items contained that -- have a non-`Void' `foreground_color'. If `Void', `foreground_color' of `parent' is displayed. is_locked: BOOLEAN -- Is `Current' locked so that it no longer scrolls? -- Note that this could be implemented as a function -- "Result := parent_i.locked_rows.item (index) /= Void" but this has not been done -- so for performance reasons. locked_position: INTEGER -- Locked position of `Current' from top edge of viewable area of `parent'. -- `Result' is 0 if not `is_locked'. do if is_locked and then attached locked_row as l_locked_row then Result := l_locked_row.offset end ensure not_locked_implies_result_zero: not is_locked implies Result = 0 end feature -- Status setting lock_at_position (a_position: INTEGER) -- Ensure `is_locked' is `True' with the vertical offset from -- the top edge of the viewable area of `parent' set to `a_position'. local l_parent_i: like parent_i l_locked_row: like locked_row do l_parent_i := parent_i check l_parent_i /= Void then end if is_locked then unlock end is_locked := True create l_locked_row.make (l_parent_i, a_position, Current) locked_row := l_locked_row l_locked_row.set_locked_index (l_parent_i.locked_indexes.count + 1) l_parent_i.locked_indexes.extend (l_locked_row) l_parent_i.reposition_locked_row (Current) l_parent_i.redraw_row (Current) if l_parent_i.item_pebble_function /= Void then l_locked_row.drawing_area.set_pebble_function (agent l_parent_i.user_pebble_function_intermediary_locked (?, ?, l_locked_row)) end l_locked_row.drawing_area.drop_actions.set_veto_pebble_function (agent l_parent_i.veto_pebble_function_intermediary) l_locked_row.drawing_area.drop_actions.extend (agent l_parent_i.drop_action_intermediary) l_locked_row.drawing_area.set_accept_cursor (l_parent_i.attached_interface.accept_cursor) l_locked_row.drawing_area.set_deny_cursor (l_parent_i.attached_interface.deny_cursor) ensure is_locked: is_locked locked_position_set: locked_position = a_position end unlock -- Ensure `is_locked' is `False'. do if is_locked and then attached parent_i as l_parent_i then is_locked := False l_parent_i.unlock_row (Current) locked_row := Void end ensure not_is_locked: not is_locked end expand -- Display all subrows of `Current'. require is_parented: parent /= Void is_expandable: is_expandable local l_parent_i: like parent_i do if not is_expanded then is_expanded := True l_parent_i := parent_i check l_parent_i /= Void then end if subrow_count > 0 then -- If `Current' has children then compute and redraw. -- If `is_ensured_expandable' then it is possible that there -- are no children, so do not perform the calculations. update_parent_expanded_node_counts_recursively (contained_expanded_items_recursive) -- Update the expanded node counts for `Current' and all parent nodes. if displayed_in_grid_tree then -- Only recompute the row offsets if `Current' is visible -- otherwise the row offsets are already correct. l_parent_i.set_vertical_computation_required (index) end end if l_parent_i.row_expand_actions_internal /= Void then -- The expand actions are fired after we set vertical computation -- to ensure that if you query a dimension from within the actions, -- they are up to date. l_parent_i.row_expand_actions.call ([attached_interface]) end if expand_actions_internal /= Void then expand_actions.call (Void) end if is_ensured_expandable then is_ensured_expandable := False is_expanded := subrow_count > 0 end if l_parent_i /= Void then l_parent_i.redraw end end ensure is_expanded: is_expanded or subrow_count = 0 node_counts_correct: node_counts_correct end collapse -- Hide all subrows of `Current'. require is_parented: parent /= Void local l_parent_i: like parent_i do if is_expanded then l_parent_i := parent_i check l_parent_i /= Void then end is_expanded := False update_parent_expanded_node_counts_recursively (- contained_expanded_items_recursive) -- Update the expanded node counts for `Current' and all parent nodes. if displayed_in_grid_tree then -- Only recompute the row offsets if `Current' is visible -- otherwise the row offsets are already correct. l_parent_i.set_vertical_computation_required (index) end if l_parent_i.row_collapse_actions_internal /= Void then -- The collapse actions are fired after we set vertical computation -- to ensure that if you query a dimension from within the actions, -- they are up to date. l_parent_i.row_collapse_actions.call ([attached_interface]) end if collapse_actions_internal /= Void then collapse_actions_internal.call (Void) end if l_parent_i /= Void then l_parent_i.redraw end end ensure not_is_expanded: not is_expanded node_counts_correct: node_counts_correct end set_height (a_height: INTEGER) -- Assign `a_height' to `height'. require is_parented: parent /= Void not_is_row_height_fixed: attached parent as l_parent and then not l_parent.is_row_height_fixed height_valid: a_height >= 0 do internal_height := a_height if attached parent_i as l_parent_i then l_parent_i.set_vertical_computation_required (index) l_parent_i.redraw if is_locked then l_parent_i.reposition_locked_row (Current) end end ensure height_set: height = a_height end ensure_visible -- Ensure `Current' is visible in viewable area of `parent'. require parented: parent /= Void local virtual_y: INTEGER l_height: INTEGER do -- It is necessary to perform the recomputation immediately -- as this may show the horizontal or vertical scroll bar -- which affects the size of the viewable area in which `Current' -- is to be displayed. if attached parent_i as l_parent_i then l_parent_i.recompute_horizontal_scroll_bar l_parent_i.recompute_vertical_scroll_bar virtual_y := virtual_y_position l_height := height if virtual_y < l_parent_i.virtual_y_position or l_height >= l_parent_i.viewable_height then -- Rows is partially or completely hidden at the top of the grid, or -- the row is too tall to be fully visible in the grid. We simply -- show the top of the row at the top of the grid. l_parent_i.set_virtual_position (l_parent_i.virtual_x_position, virtual_y) elseif virtual_y + l_height > l_parent_i.virtual_y_position + l_parent_i.viewable_height then -- Partially visible at the bottom, we just make scroll up by one row so that -- the bottom of the row is at the bottom of the grid. l_parent_i.set_virtual_position (l_parent_i.virtual_x_position, virtual_y + l_height - l_parent_i.viewable_height) end end ensure parent_virtual_x_position_unchanged: attached old parent as l_old_parent and then attached parent as l_parent and then l_old_parent.virtual_x_position = l_parent.virtual_x_position to_implement_assertion ("old_is_visible_implies_vertical_position_not_changed") row_visible_in_parent: virtual_y_position >= l_parent.virtual_y_position and virtual_y_position + height <= l_parent.virtual_y_position + (l_parent.viewable_height).max (height) end set_background_color (a_color: like background_color) -- Set `background_color' with `a_color'. require is_parented: parent /= Void do background_color := a_color if attached parent_i as l_parent_i then l_parent_i.redraw_row (Current) end ensure background_color_set: background_color = a_color end set_foreground_color (a_color: like foreground_color) -- Set `foreground_color' with `a_color'. require is_parented: parent /= Void do foreground_color := a_color if attached parent_i as l_parent_i then l_parent_i.redraw_row (Current) end ensure foreground_color_set: foreground_color = a_color end ensure_expandable -- Ensure `Current' displays an expand pixmap to simulate a `row_count' greater than 0. -- May be used for dynamic behavior by filling subrows upon firing of `grid.row_expand_actions'. -- If no items are added to `Current' during the firing of `grid.row_expand_actions' then -- `Current' is no longer expandable. This may be re-instated by calling `ensure_expandable' again -- from `grid.row_expand_actions'. Note that once a subrow is added to `Current' after a call to -- `ensure_expandable', it is no longer be displayed as expandable if all subrows are then removed. -- In this case, you must explicitly call `ensure_expandable' again after removing all subrows. require parented: attached parent as l_parent parent_tree_enabled: l_parent.is_tree_enabled do is_ensured_expandable := True redraw ensure is_expandable: is_expandable end ensure_non_expandable -- Restore expanded state of `Current' after a call to `ensure_expandable'. Note that if a row -- has one or more subrows, it is always drawn as expanded, hence the "no_subrows_contained" precondition. require parented: parent /= Void no_subrows_contained: subrow_count = 0 do is_ensured_expandable := False redraw ensure not_is_expandable: not is_expandable end is_ensured_expandable: BOOLEAN -- May current be expanded even through it is empty? redraw -- Force all items within `Current' to be re-drawn when next idle. require parented: parent /= Void do if attached parent_i as l_parent_i then l_parent_i.redraw_row (Current) end end hide -- Prevent `Current' from being shown in `parent'. require parented: parent /= Void do if is_show_requested then if is_locked and then attached locked_row as l_locked_row then l_locked_row.widget.hide end is_show_requested := False if attached parent_i as l_parent_i then l_parent_i.adjust_non_displayed_row_count (1) if l_parent_i.non_displayed_row_count = 1 then l_parent_i.set_vertical_computation_required (1) else l_parent_i.set_vertical_computation_required (index) end l_parent_i.redraw end end end show -- Ensure `Current' is shown in `parent'. require parented: parent /= Void do if not is_show_requested then if is_locked and then attached locked_row as l_locked_row then l_locked_row.widget.show end is_show_requested := True if is_locked and then attached locked_row as l_locked_row then l_locked_row.widget.show end if attached parent_i as l_parent_i then l_parent_i.adjust_non_displayed_row_count (-1) l_parent_i.set_vertical_computation_required (index) l_parent_i.redraw end end end is_show_requested: BOOLEAN -- May `Current' be displayed? -- Will return `False' if `hide' has been called on `Current'. is_displayed: BOOLEAN -- Is `Current' visible on the screen? -- `True' when show requested and parent displayed. -- A row that is_displayed does not necessarily have to be visible on screen at that particular time. do Result := is_show_requested and then attached parent_i as l_parent_i and then l_parent_i.is_displayed end feature {EV_GRID_ROW, EV_ANY_I}-- Element change set_item (i: INTEGER; a_item: detachable EV_GRID_ITEM) -- Set item at `i'-th column to be `a_item'. -- If `a_item' is `Void', the current item (if any) is removed. require i_positive: i > 0 a_item_not_parented: a_item /= Void implies a_item.parent = Void is_parented: attached parent as l_parent valid_tree_structure: a_item /= Void and l_parent.is_tree_enabled and attached parent_row as l_parent_row implies i >= l_parent_row.index_of_first_item do if attached parent_i as l_parent_i then l_parent_i.set_item (i, index, a_item) end ensure item_set: item (i) = a_item end add_subrow (a_row: EV_GRID_ROW) -- Make `a_row' a child of Current. require is_parented: attached parent as l_parent a_row_not_void: a_row /= Void a_row_is_parented: a_row.parent /= Void a_row_is_not_current: a_row /= interface a_row_is_not_a_subrow: a_row.parent_row = Void same_parent: a_row.parent = parent parent_enabled_as_tree: l_parent.is_tree_enabled a_row_is_below_current: a_row.index > index all_rows_between_row_and_current_are_subrows: a_row.index = index + subrow_count_recursive + 1 row_not_empty_implies_row_index_of_first_item_greater_or_equal_to_index_of_first_item: a_row.index_of_first_item > 0 implies a_row.index_of_first_item >= index_of_first_item do add_subrows_internal (1, a_row.index, subrow_count + 1, False) ensure added: a_row.parent_row = interface subrow (subrow_count) = a_row node_counts_correct: node_counts_correct end insert_subrows (rows_to_insert, a_subrow_index: INTEGER) -- Add `rows_to_insert' rows to `parent' as a subrow of `Current' -- with index in subrows of `Current' given by `a_subrow_index'. require not_destroyed: not is_destroyed is_parented: attached parent as l_parent parent_enabled_as_tree: l_parent.is_tree_enabled rows_to_insert_positive: rows_to_insert >= 1 valid_subrow_index: a_subrow_index >= 1 and a_subrow_index <= subrow_count + 1 local l_subrow: detachable EV_GRID_ROW_I l_index: INTEGER do if a_subrow_index = 1 then l_index := index + 1 else -- There is a subrow before the current insert index so -- add at a position in `parent_i' based on the item before the insert -- As this item may have rows of it's own the subrow count recursive must also be added. l_subrow := subrows.i_th (a_subrow_index - 1) check l_subrow /= Void then end l_index := l_subrow.index + l_subrow.subrow_count_recursive + 1 end if attached parent as l_parent_i then l_parent_i.insert_new_rows_parented (rows_to_insert, l_index, attached_interface) end ensure subrow_count_increased: subrow_count = old subrow_count + rows_to_insert parent_row_count_increased: attached parent as l_parent and then l_parent.row_count = old attached_parent.row_count + rows_to_insert end remove_subrow (a_row: EV_GRID_ROW) -- Ensure that `a_row' is no longer a child row of `Current'. -- Does not remove `a_row' from `parent_i'. require is_parented: attached parent as l_parent a_row_not_void: a_row /= Void a_row_is_parented: a_row.parent /= Void a_row_is_not_current: a_row /= interface a_row_is_a_subrow: a_row.parent_row = interface same_parent: a_row.parent = parent parent_enabled_as_tree: l_parent.is_tree_enabled row_is_final_subrow_in_tree_structure: attached parent_row_root as l_parent_row_root and then (a_row.index + a_row.subrow_count_recursive = l_parent_row_root.index + l_parent_row_root.subrow_count_recursive) local row_imp: EV_GRID_ROW_I l_row_i: detachable EV_GRID_ROW_I i, last_changed_subrow_index: INTEGER l_parent_i: like parent_i do row_imp := a_row.implementation subrows.remove_i_th (subrow_count) row_imp.internal_set_parent_row (Void) row_imp.set_subrow_index (0) l_parent_i := parent_i check l_parent_i /= Void then end if row_imp.subrow_count > 0 then -- Now update the depth properties for each subrow of `a_row' (if any). -- `a_row' was already handled by the call to `internal_set_parent_row'. from i := row_imp.index + 1 last_changed_subrow_index := row_imp.index + row_imp.subrow_count_recursive + 1 until i > last_changed_subrow_index loop l_row_i := l_parent_i.rows.i_th (i) if l_row_i /= Void then l_row_i.update_depths_in_tree end i := i + 1 end end -- Decrease the node count for `Current' and all parents by 1 + the node count -- for the added subrow as this may also be a tree structure. update_parent_node_counts_recursively (- (row_imp.subrow_count_recursive + 1)) -- If `Current' is expanded then we must also decrease the expanded node count by -- 1 + the node count of the added subrow as this may also be a tree structure. if is_expanded then update_parent_expanded_node_counts_recursively ( - (row_imp.expanded_subrow_count_recursive + 1)) end l_parent_i.set_vertical_computation_required (index) l_parent_i.redraw_client_area ensure removed: a_row.parent_row = Void subrow_count_decreased: subrow_count = old subrow_count - 1 end add_subrows_internal (rows_to_insert, row_index: INTEGER; a_subrow_index: INTEGER; inserting_within_tree_structure: BOOLEAN) -- Make `a_row' a child of Current. `inserting_within_tree_structure' determines -- if `a_row' is to be added within an existing tree structure and is used to -- relax the preconditions that determine if a row may be added. require is_parented: attached parent as l_parent a_row_is_parented: l_parent.row (row_index).parent /= Void a_row_is_not_current: l_parent.row (row_index) /= interface a_row_is_not_a_subrow: l_parent.row (row_index).parent_row = Void same_parent: l_parent.row (row_index).parent = l_parent parent_enabled_as_tree: l_parent.is_tree_enabled a_row_is_below_current: l_parent.row (row_index).index > index all_rows_between_row_and_current_are_subrows: not inserting_within_tree_structure implies l_parent.row (row_index).index = index + subrow_count_recursive + 1 row_not_empty_implies_row_index_of_first_item_greater_or_equal_to_index_of_first_item: l_parent.row (row_index).index_of_first_item > 0 implies l_parent.row (row_index).index_of_first_item >= index_of_first_item local row_imp: EV_GRID_ROW_I i, j, l_subrow_index: INTEGER l_original_subrow_count: INTEGER l_parent_i: like parent_i l_subrows: like subrows do -- Reset `is_ensured_expandable' is_ensured_expandable := False l_subrows := subrows from i := row_index j := row_index + rows_to_insert l_subrow_index := a_subrow_index l_original_subrow_count := l_subrows.count l_subrows.resize (l_subrows.count + rows_to_insert) if a_subrow_index < l_original_subrow_count + 1 then -- Move the existing items as required to make space for the new. l_subrows.shift_items (a_subrow_index, a_subrow_index + rows_to_insert, l_original_subrow_count - a_subrow_index + 1) end l_parent_i := parent_i check l_parent_i /= Void then end until i = j loop row_imp := l_parent_i.row (i).implementation l_subrows.put_i_th (row_imp, l_subrow_index) row_imp.internal_set_parent_row (Current) row_imp.set_subrow_index (l_subrow_index) -- Increase the node count for `Current' and all parents by 1 + the node count -- for the added subrow as this may also be a tree structure. update_parent_node_counts_recursively (row_imp.subrow_count_recursive + 1) -- If `Current' is expanded then we must also update the expanded node count by -- 1 + the node count of the added subrow as this may also be a tree structure. if is_expanded then update_parent_expanded_node_counts_recursively (row_imp.expanded_subrow_count_recursive + 1) end i := i + 1 l_subrow_index := l_subrow_index + 1 end -- Update all indices of subrows in `Current'. update_subrow_indices (a_subrow_index + 1) l_parent_i.set_vertical_computation_required (index) l_parent_i.redraw_client_area ensure added: attached parent as l_parent and then l_parent.row (row_index).parent_row = interface subrow (a_subrow_index) = l_parent.row (row_index) node_counts_correct: node_counts_correct end enable_select -- Select the object. local l_parent_i: like parent_i do if not is_selected then l_parent_i := parent_i check l_parent_i /= Void then end if l_parent_i.is_row_selection_enabled or else index_of_first_item = 0 then if l_parent_i.is_single_row_selection_enabled then l_parent_i.remove_selection end internal_is_selected := True l_parent_i.add_row_to_selected_rows (Current) if l_parent_i.row_select_actions_internal /= Void then l_parent_i.row_select_actions.call ([attached_interface]) end if select_actions_internal /= Void then select_actions_internal.call (Void) end else internal_update_selection (True) end l_parent_i.redraw_row (Current) end end disable_select -- Deselect the object. do if attached parent_i as l_parent_i and then (l_parent_i.is_row_selection_enabled or else index_of_first_item = 0) then if internal_is_selected then internal_is_selected := False l_parent_i.remove_row_from_selected_rows (Current) if l_parent_i.row_deselect_actions_internal /= Void then l_parent_i.row_deselect_actions.call ([attached_interface]) end if deselect_actions_internal /= Void then deselect_actions_internal.call ([Void]) end end else internal_update_selection (False) end if attached parent_i as l_parent_i then l_parent_i.redraw_row (Current) end end destroy -- Destroy `Current'. do if attached parent_i as l_parent_i then l_parent_i.remove_row (index) end set_is_destroyed (True) end clear -- Remove all items from `Current'. require is_parented: parent /= Void local i: INTEGER a_parent_i: detachable EV_GRID_I a_index: INTEGER do from i := count a_parent_i := parent_i check a_parent_i /= Void then end a_index := index until i = 0 loop a_parent_i.internal_set_item (i, a_index, Void) i := i - 1 end ensure cleared: index_of_first_item = 0 end feature {EV_GRID_I, EV_GRID_ROW_I} -- Implementation update_for_item_removal (item_index: INTEGER) -- Update `Current' to reflect the fact that item at position -- `item_index' has been removed. require valid_item_index: item_index >= 1 and item_index <= count do if attached parent_i as l_parent_i and then l_parent_i.is_tree_enabled and parent_row_i /= Void then -- Check and update `indent_depth_in_tree'. update_depths_in_tree end end update_for_removal -- Update settings in `Current' to reflect the fact that -- it is being removed from `parent_i'. require is_parented: parent /= Void do if is_locked then unlock end clear -- Make sure row is unselected from grid before removal disable_select if attached parent_row_i as l_parent_row_i then l_parent_row_i.update_for_subrow_removal (Current) end unparent parent_row_i := Void subrow_index := 0 index := 0 ensure parent_i_void: parent_i = Void parent_row_i_void: parent_row_i = Void index_set_to_zero: index = 0 end update_for_subrow_removal (a_subrow: EV_GRID_ROW_I) -- Update `Current' to reflect the fact that `a_subrow' -- is being removed from `Current'. require a_row_not_void: a_subrow /= Void a_subrow.parent_row_i = Current do update_parent_node_counts_recursively (-1) if is_expanded then update_parent_expanded_node_counts_recursively (-1) end subrows.remove_i_th (a_subrow.subrow_index) update_subrow_indices (a_subrow.subrow_index) -- Set the expanded state to `False' if no subrows. if subrow_count = 0 then is_expanded := False end ensure subrow_count_decreased: subrow_count = old subrow_count - 1 subrow_count_recursive_decreased: subrow_count_recursive = old subrow_count_recursive - 1 subrow_not_contained_in_subrows: not subrows.has (a_subrow) not_expanded_when_no_subrows: subrow_count = 0 implies not is_expanded end update_subrow_indices (a_index: INTEGER) -- Recalculate subsequent subrow indexes starting from `a_index'. require valid_index: a_index > 0 and then a_index <= subrow_count + 1 local i, a_subrow_count: INTEGER row_i: detachable EV_GRID_ROW_I temp_rows: like subrows do -- Set subsequent indexes to their new values temp_rows := subrows from i := a_index a_subrow_count := temp_rows.count until i > a_subrow_count loop row_i := temp_rows @ i if row_i /= Void then row_i.set_subrow_index (i) end i := i + 1 end end feature {EV_GRID_ROW_I, EV_GRID_I} -- Implementation internal_set_parent_row (a_parent_row: detachable EV_GRID_ROW_I) -- Set the `parent_row' of `Current' do parent_row_i := a_parent_row update_depths_in_tree end update_depths_in_tree -- Update `depth_in_tree' and `indent_depth_in_tree' based -- on `Current' `Parent_row_i'. do if attached parent_row_i as l_parent_row_i then depth_in_tree := l_parent_row_i.depth_in_tree + 1 indent_depth_in_tree := l_parent_row_i.indent_depth_in_tree + 1 -- if parent_row_i.index_of_first_item /= 0 and index_of_first_item /= 0 and then parent_row_i.index_of_first_item /= index_of_first_item then -- indent_depth_in_tree := 1 -- end else depth_in_tree := 1 indent_depth_in_tree := 1 end ensure no_parent_implies_depths_set_to_one: parent_row_i = Void implies depth_in_tree = 1 and indent_depth_in_tree = 1 end feature {EV_GRID_ITEM_I, EV_GRID_ROW_I, EV_GRID_I, EV_GRID_DRAWER_I} -- Implementation subrows: EV_GRID_ARRAYED_LIST [detachable EV_GRID_ROW_I] -- All subrows of `Current'. feature {EV_GRID_I, EV_GRID_DRAWER_I, EV_GRID_ROW_I} -- Implementation parent_i: detachable EV_GRID_I -- Grid that `Current' resides in. parent_row_i: detachable EV_GRID_ROW_I -- Row in which `Current' is parented. depth_in_tree: INTEGER -- Depth of `Current' within a tree structure. indent_depth_in_tree: INTEGER set_subrow_count_recursive (a_count: INTEGER) -- Assign `a_count' to `subrow_count_recursive'. require a_count_non_negative: a_count >= 0 do subrow_count_recursive := a_count ensure subnode_count_set: subrow_count_recursive = a_count end set_expanded_subrow_count_recursive (a_count: INTEGER) -- Assign `a_count' to `expanded_subrow_count_recursive'. require a_count_non_negative: a_count >= 0 do expanded_subrow_count_recursive := a_count ensure expanded_subrow_count_set: expanded_subrow_count_recursive = a_count end expanded_subrow_count_recursive: INTEGER -- Number of expanded subrows contained within `Current' recursively. update_parent_node_counts_recursively (adjustment_value: INTEGER) -- For `Current' and all parent nodes of `Current' recursively, update their -- `subnode_count_recursive' by `adjustment_value'. require adjustment_value_not_zero: adjustment_value /= 0 local parent_row_imp: detachable EV_GRID_ROW_I do from parent_row_imp := Current until parent_row_imp = Void loop check parent_row_imp /= Void end parent_row_imp.set_subrow_count_recursive (parent_row_imp.subrow_count_recursive + adjustment_value) parent_row_imp := parent_row_imp.parent_row_i end end update_parent_expanded_node_counts_recursively (adjustment_value: INTEGER) -- For `Current' and all parent nodes of `Current' recursively, update their -- `subnode_count_recursive' by `adjustment_value'. require adjustment_value_not_zero: adjustment_value /= 0 local parent_row_imp: detachable EV_GRID_ROW_I do from parent_row_imp := Current until (parent_row_imp = Void) or else (parent_row_imp /= Current and not parent_row_imp.is_expanded) loop check parent_row_imp /= Void end parent_row_imp.set_expanded_subrow_count_recursive (parent_row_imp.expanded_subrow_count_recursive + adjustment_value) parent_row_imp := parent_row_imp.parent_row_i end end feature {EV_GRID_I} -- Implementation reset_tree_structure -- Restore all properties of `Current' associated with tree structures -- for disabling of the tree. This is far quicker than simply calling -- `remove_subrow' as parent depths do not need to be taken into -- account which requires iteration. do subrows.wipe_out depth_in_tree := 1 indent_depth_in_tree := 1 subrow_count_recursive := 0 expanded_subrow_count_recursive := 0 is_expanded := False parent_row_i := Void end feature {EV_ANY_I} -- Contract support node_counts_correct: BOOLEAN -- Are the node counts for `Current' in a valid state? -- This was originally written as class invariants, but as the node setting -- is performed recursively, we would need a global variable to turn this checking -- off. Instead we now check this function from the postcondition of and feature -- that may modify the expanded states of nodes. do Result := True if is_initialized and then subrows.count = 0 then Result := subrow_count_recursive = 0 end if is_initialized and then subrows.count = 0 then Result := Result and expanded_subrow_count_recursive = 0 end if is_initialized and then subrow_count > 0 then Result := Result and subrow_count_recursive >= subrow_count end if is_initialized and then subrow_count > 0 and then is_expanded then Result := Result and expanded_subrow_count_recursive >= subrow_count end if is_initialized and then subrow_count > 0 and then not is_expanded then Result := Result and expanded_subrow_count_recursive >= 0 end Result := Result and expanded_subrow_count_recursive <= subrow_count_recursive if attached parent_i as l_parent_i and then l_parent_i.uses_row_offsets then Result := Result and l_parent_i.visible_row_count >= 0 Result := Result and l_parent_i.visible_row_count <= l_parent_i.row_count end end feature {EV_GRID_I} -- Implementation unparent -- Sets` parent_i' to `Void'. do parent_i := Void -- Reset hash_code to respect invariant. hash_code := 0 ensure parent_void: parent = Void end feature {NONE} -- Implementation contained_expanded_items_recursive: INTEGER -- `Result' is sum of of expanded nodes for each of the child rows -- of `Current', and each child row themselves. This is used when expanding -- or collapsing items to determine how many nodes are now visible or hidden. local a_subrow_index: INTEGER l_row: detachable EV_GRID_ROW_I l_subrows: like subrows do l_subrows := subrows from a_subrow_index := 1 until a_subrow_index > subrow_count -- iterate all rows parented within `Current'. loop Result := Result + 1 -- Add one for the current row which is now hidden. l_row := l_subrows.i_th (a_subrow_index) if l_row /= Void then Result := Result + l_row.expanded_subrow_count_recursive -- Add the number of expanded items for that row. end a_subrow_index := a_subrow_index + 1 end ensure result_non_negative: Result >= 0 end displayed_in_grid_tree: BOOLEAN -- Is `Current' displayed in the tree of `grid'? -- If not parented at any level or one of these -- parents is not expanded then `Result' is `False'. local l_parent: detachable EV_GRID_ROW_I do Result := True from l_parent := parent_row_i until l_parent = Void or not Result loop check l_parent /= Void end if not l_parent.is_expanded then Result := False end l_parent := l_parent.parent_row_i end end feature -- Contract Support attached_parent: EV_GRID -- Grid to which current row belongs. -- Used for old evaluation require parent_i_attached: attached parent local l_parent: like parent do l_parent := parent check l_parent /= Void then end Result := l_parent end feature {EV_ANY, EV_ANY_I, EV_GRID_DRAWER_I} -- Implementation interface: detachable EV_GRID_ROW note option: stable attribute end -- Provides a common user interface to possibly dependent -- functionality implemented by `Current'. invariant -- We currently cannot hold this invariant during expand/collapse, thus -- it is commented until we find a better way to express it: -- no_subrows_implies_not_expanded: parent /= Void and then subrow_count = 0 implies not is_expanded subrows_not_void: is_initialized implies subrows /= Void hash_code_valid: is_initialized implies ((parent = Void and then hash_code = 0) or else (parent /= Void and then hash_code > 0)) note copyright: "Copyright (c) 1984-2019, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software 5949 Hollister Ave., Goleta, CA 93117 USA Telephone 805-685-1006, Fax 805-685-6869 Website http://www.eiffel.com Customer support http://support.eiffel.com ]" end