| /******************************************************************************* |
| * Copyright (c) 2012, 2020 Original authors and others. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Original authors and others - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.nebula.widgets.nattable.group.model; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * An implementation of {@link IRowGroup}. |
| * |
| * @author Stefan Bolton |
| * |
| * @param <T> |
| * the type of the row objects. |
| */ |
| public class RowGroup<T> implements IRowGroup<T> { |
| |
| private String groupName; |
| private boolean collapsed; |
| private boolean collapseable; |
| |
| // The model is needed to a) notify of any changes made (so it can notify |
| // IRowGroupModelListeners) and b) it keeps a 'master-map' of all the row |
| // position-to-row objects added into any of the row groups. |
| private final RowGroupModel<T> rowGroupModel; |
| |
| // The rows and static rows in this group. |
| private List<T> rowMembers; |
| private List<T> staticRowMembers; |
| |
| // A list of child groups. |
| private List<IRowGroup<T>> childGroups; |
| |
| // Null for top-level groups. |
| private IRowGroup<T> parentGroup; |
| |
| // Arbitrary data tagged onto a group. |
| private Map<String, Object> data; |
| |
| private static final String DEFAULT_DATA_KEY = "defaultKey"; //$NON-NLS-1$ |
| |
| public RowGroup(final RowGroupModel<T> rowGroupModel, final String groupName) { |
| this.rowGroupModel = rowGroupModel; |
| init(groupName, false); |
| this.collapseable = true; |
| } |
| |
| public RowGroup(final RowGroupModel<T> rowGroupModel, |
| final String groupName, final boolean collapsed) { |
| this.rowGroupModel = rowGroupModel; |
| init(groupName, collapsed); |
| } |
| |
| private void init(final String groupName, final boolean collapsed) { |
| this.groupName = groupName; |
| this.collapsed = collapsed; |
| this.collapseable = true; |
| this.rowMembers = Collections.synchronizedList(new ArrayList<T>()); |
| this.staticRowMembers = Collections |
| .synchronizedList(new ArrayList<T>()); |
| this.childGroups = Collections |
| .synchronizedList(new ArrayList<IRowGroup<T>>()); |
| this.data = new HashMap<>(); |
| } |
| |
| @Override |
| public String getGroupName() { |
| return this.groupName; |
| } |
| |
| @Override |
| public Object getData() { |
| return this.data.get(DEFAULT_DATA_KEY); |
| } |
| |
| @Override |
| public Object getData(String key) { |
| return this.data.get(key); |
| } |
| |
| @Override |
| public void setData(Object data) { |
| this.data.put(DEFAULT_DATA_KEY, data); |
| } |
| |
| @Override |
| public void setData(String key, Object data) { |
| this.data.put(key, data); |
| } |
| |
| @Override |
| public boolean isCollapsed() { |
| return this.collapsed; |
| } |
| |
| @Override |
| public boolean isCollapseable() { |
| return this.collapseable; |
| } |
| |
| public void setCollapseable(boolean collapseable) { |
| this.collapseable = collapseable; |
| } |
| |
| @Override |
| public void collapse() { |
| if (isCollapseable() && !isCollapsed()) { |
| this.collapsed = true; |
| this.rowGroupModel.notifyListeners(); |
| } |
| } |
| |
| @Override |
| public void expand() { |
| if (isCollapseable() && isCollapsed()) { |
| this.collapsed = false; |
| this.rowGroupModel.notifyListeners(); |
| } |
| } |
| |
| private void addMemberRowAndCache(final List<T> rows, final T row) { |
| rows.add(row); |
| this.rowGroupModel.addMemberRow(row, this); |
| } |
| |
| @Override |
| public void addMemberRow(final T row) { |
| addMemberRowAndCache(this.rowMembers, row); |
| this.rowGroupModel.notifyListeners(); |
| } |
| |
| @Override |
| public void addStaticMemberRow(final T row) { |
| addMemberRowAndCache(this.staticRowMembers, row); |
| this.rowGroupModel.notifyListeners(); |
| } |
| |
| @Override |
| public void addMemberRows(final List<T> rows) { |
| for (T row : rows) { |
| addMemberRowAndCache(this.rowMembers, row); |
| } |
| this.rowGroupModel.notifyListeners(); |
| } |
| |
| private boolean removeMemberRowFromCache(final T row) { |
| boolean removed = false; |
| |
| // Remove the row from our group. |
| if (row != null) { |
| removed = this.rowMembers.remove(row); |
| |
| // Try removing a static row instead. |
| if (!removed) { |
| removed = this.staticRowMembers.remove(row); |
| } |
| |
| if (removed) { |
| // Bump row positions to compensate. |
| this.rowGroupModel.removeMemberRow(row); |
| |
| if ((getOwnMemberRows(false).isEmpty()) |
| && (getRowGroups().isEmpty())) { |
| // If there are no more member rows, then clean-up any |
| // static rows and remove the group from the model. |
| for (T staticRow : this.getOwnStaticMemberRows()) { |
| this.rowGroupModel.removeMemberRow(staticRow); |
| } |
| |
| this.staticRowMembers.clear(); |
| this.rowGroupModel.removeRowGroup(this); |
| |
| if (this.parentGroup != null) { |
| this.parentGroup.removeRowGroup(this); |
| } |
| } |
| |
| } else { |
| // Try sub-groups. |
| synchronized (this.childGroups) { |
| for (final IRowGroup<T> rowGroup : this.childGroups) { |
| removed = ((RowGroup<T>) rowGroup).removeMemberRow(row); |
| |
| if (removed) { |
| // Remove empty child groups from the model. |
| if (rowGroup.getOwnMemberRows(false).isEmpty()) { |
| this.childGroups.remove(rowGroup); |
| } |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| return removed; |
| } |
| |
| @Override |
| public boolean removeMemberRow(final T row) { |
| |
| boolean removed = removeMemberRowFromCache(row); |
| |
| if (removed) { |
| this.rowGroupModel.notifyListeners(); |
| } |
| |
| return removed; |
| } |
| |
| @Override |
| public void removeMemberRows(final List<T> rows) { |
| boolean removed = false; |
| |
| for (T row : rows) { |
| removed |= removeMemberRowFromCache(row); |
| } |
| |
| if (removed) { |
| this.rowGroupModel.notifyListeners(); |
| } |
| } |
| |
| @Override |
| public IRowGroup<T> getParentGroup() { |
| return this.parentGroup; |
| } |
| |
| @Override |
| public void setParentGroup(IRowGroup<T> parentGroup) { |
| this.parentGroup = parentGroup; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.gresham.darwin.ui.widgets.grid.data.rowgroups.IRowGroup# |
| * addMemberRowGroup |
| * (com.gresham.darwin.ui.widgets.grid.data.rowgroups.IRowGroup) |
| */ |
| @Override |
| public void addRowGroup(final IRowGroup<T> rowGroup) { |
| rowGroup.setParentGroup(this); |
| this.childGroups.add(rowGroup); |
| this.rowGroupModel.notifyListeners(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.gresham.darwin.ui.widgets.grid.data.rowgroups.IRowGroup# |
| * removeMemberRowGroup |
| * (com.gresham.darwin.ui.widgets.grid.data.rowgroups.IRowGroup) |
| */ |
| @Override |
| public boolean removeRowGroup(final IRowGroup<T> rowGroup) { |
| // Remove all members in the group. |
| rowGroup.setParentGroup(null); |
| rowGroup.clear(); |
| boolean removed = this.childGroups.remove(rowGroup); |
| this.rowGroupModel.notifyListeners(); |
| return removed; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.gresham.darwin.ui.widgets.grid.data.rowgroups.IRowGroup# |
| * getMemnerRowGroups() |
| */ |
| @Override |
| public List<IRowGroup<T>> getRowGroups() { |
| return Collections.unmodifiableList(this.childGroups); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.gresham.darwin.ui.widgets.grid.data.rowgroups.IRowGroup# |
| * getOwnMemberRows () |
| */ |
| @Override |
| public List<T> getOwnMemberRows(final boolean includeStaticRows) { |
| List<T> rows = new ArrayList<>(this.rowMembers); |
| |
| if (includeStaticRows) { |
| rows.addAll(this.staticRowMembers); |
| } |
| |
| return Collections.unmodifiableList(rows); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.gresham.darwin.ui.widgets.grid.data.rowgroups.IRowGroup# |
| * getOwnStaticMemberRows() |
| */ |
| @Override |
| public List<T> getOwnStaticMemberRows() { |
| return Collections.unmodifiableList(this.staticRowMembers); |
| } |
| |
| @Override |
| public void clear() { |
| synchronized (this.childGroups) { |
| for (final IRowGroup<T> rowGroup : this.childGroups) { |
| rowGroup.clear(); |
| } |
| } |
| |
| synchronized (this.rowMembers) { |
| for (T row : new ArrayList<T>(this.rowMembers)) { |
| removeMemberRow(row); |
| } |
| } |
| |
| synchronized (this.staticRowMembers) { |
| for (T row : new ArrayList<T>(this.staticRowMembers)) { |
| removeMemberRow(row); |
| } |
| } |
| } |
| |
| @Override |
| public List<T> getMemberRows(final boolean includeStaticRows) { |
| final List<T> memberRows = new ArrayList<>(); |
| |
| // Return all the member rows from nested groups. |
| synchronized (this.childGroups) { |
| for (final IRowGroup<T> rowGroup : this.childGroups) { |
| memberRows.addAll(rowGroup.getMemberRows(includeStaticRows)); |
| } |
| } |
| |
| // Add all of our immediate child rows. |
| memberRows.addAll(getOwnMemberRows(includeStaticRows)); |
| |
| return Collections.unmodifiableList(memberRows); |
| } |
| |
| @Override |
| public List<T> getStaticMemberRows() { |
| final List<T> staticMemberRows = new ArrayList<>(); |
| |
| // Return all the member rows from nested groups. |
| synchronized (this.childGroups) { |
| for (final IRowGroup<T> rowGroup : this.childGroups) { |
| staticMemberRows.addAll(rowGroup.getStaticMemberRows()); |
| } |
| } |
| |
| // Add all of our immediate child rows. |
| staticMemberRows.addAll(getOwnStaticMemberRows()); |
| |
| return Collections.unmodifiableList(staticMemberRows); |
| } |
| |
| @Override |
| public IRowGroup<T> getRowGroupForRow(final T row) { |
| IRowGroup<T> group = null; |
| |
| if (getOwnMemberRows(true).contains(row)) { |
| group = this; |
| |
| } else { |
| synchronized (this.childGroups) { |
| for (final IRowGroup<T> rowGroup : this.childGroups) { |
| group = rowGroup.getRowGroupForRow(row); |
| |
| if (group != null) { |
| break; |
| } |
| } |
| } |
| } |
| |
| return group; |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| |
| boolean empty = true; |
| |
| synchronized (this.childGroups) { |
| for (final IRowGroup<T> rowGroup : this.childGroups) { |
| empty &= rowGroup.isEmpty(); |
| } |
| } |
| |
| return (empty && ((this.rowMembers.size() + this.staticRowMembers.size()) == 0)); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(String.format("Name : %s\n", getGroupName())); //$NON-NLS-1$ |
| sb.append(String.format("Collapsed : %s\n", isCollapsed())); //$NON-NLS-1$ |
| sb.append("Members : \n"); //$NON-NLS-1$ |
| |
| for (T row : getOwnMemberRows(false)) { |
| sb.append(String.format("%s", row.toString())); //$NON-NLS-1$ |
| } |
| |
| for (T row : getOwnStaticMemberRows()) { |
| sb.append(String.format("*%s", row.toString())); //$NON-NLS-1$ |
| } |
| |
| if (!this.childGroups.isEmpty()) { |
| sb.append(String.format("Start Child Groups for [%s] :- \n", getGroupName())); //$NON-NLS-1$ |
| for (final IRowGroup<T> rowGroup : this.childGroups) { |
| sb.append(rowGroup.toString()); |
| } |
| sb.append(String.format("End Child Groups for [%s]\n", getGroupName())); //$NON-NLS-1$ |
| } |
| |
| return sb.toString(); |
| } |
| } |