/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package com.bearsoft.gui.grid.columns; import com.bearsoft.gui.grid.events.insets.InsetAfterLastChangedEvent; import com.bearsoft.gui.grid.events.insets.InsetChangeListener; import com.bearsoft.gui.grid.events.insets.InsetPreFirstChangedEvent; import com.bearsoft.gui.grid.insets.LinearInset; import com.bearsoft.gui.grid.insets.InsetPart; import com.bearsoft.gui.grid.insets.InsetPart.PartKind; import com.bearsoft.gui.grid.exceptions.GridException; import java.util.HashSet; import java.util.ArrayList; import java.util.Enumeration; import java.util.ConcurrentModificationException; import java.util.List; import java.util.Set; import javax.swing.event.ChangeEvent; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.event.TableColumnModelEvent; import javax.swing.event.TableColumnModelListener; /** * This column model implementation intended to create insetted column space around * delegate column model. It raises events from itself and from delegate model as if they * appeared straight in this model. * The feature is there are extra columns on the left and on the right. * As a consequence this model has no any information about how to fill inset columns. * This problem is solved with pluggable <code>InsettedColumnsFactory</code> interface. * @see InsettedColumnsFactory * @author Gala */ public class InsettedColumnModel implements TableColumnModel, ListSelectionListener { public static final String LEFT_INSET_WRONG_COLUMNS_MSG = "Columns count in the left inset is wrong. Inset value is %d and actual columns count is %d"; public static final String RIGHT_INSET_WRONG_COLUMNS_MSG = "Columns count in the right inset is wrong. Inset value is %d and actual columns count is %d"; // insetted attributes protected LinearInset inset; protected TableColumnModel delegate; protected List<TableColumn> leftInsetColumns = new ArrayList<>(); protected List<TableColumn> rightInsetColumns = new ArrayList<>(); protected InsettedColumnsFactory columnsFactory = new DefaultInsettedColumnsFactory(); // colums standard attributes protected int columnMargin = 0; protected boolean columnSelectionAllowed = true; protected ListSelectionModel selectionModel; protected Set<TableColumnModelListener> listeners = new HashSet<>(); protected class InsetListener implements InsetChangeListener { @Override public void insetPreFirstChanged(InsetPreFirstChangedEvent anEvent) { if (anEvent.getNewValue() > anEvent.getOldValue()) { assert leftInsetColumns.size() == anEvent.getOldValue(); generateLeftInsetColumns(anEvent.getOldValue(), anEvent.getNewValue()); /* for(int i=anEvent.getOldValue();i<anEvent.getNewValue();i++) { TableColumn col = columnsFactory.createLeft(inset.getPreFirst(), 0); leftInsetColumns.add(0, col); fireColumnAdded(0); } */ assert leftInsetColumns.size() == anEvent.getNewValue(); } else if (anEvent.getNewValue() < anEvent.getOldValue()) { assert leftInsetColumns.size() == anEvent.getOldValue(); while (leftInsetColumns.size() > anEvent.getNewValue()) { leftInsetColumns.remove(0); fireColumnRemoved(0); } assert leftInsetColumns.size() == anEvent.getNewValue(); } } @Override public void insetAfterLastChanged(InsetAfterLastChangedEvent anEvent) { if (anEvent.getNewValue() > anEvent.getOldValue()) { assert rightInsetColumns.size() == anEvent.getOldValue(); generateRightInsetColumns(anEvent.getOldValue(), anEvent.getNewValue()); /* for (int i = anEvent.getOldValue(); i < anEvent.getNewValue(); i++) { TableColumn col = columnsFactory.createRight(inset.getAfterLast(), rightInsetColumns.size()); rightInsetColumns.add(col); fireColumnAdded(getColumnCount() - 1); } * */ assert rightInsetColumns.size() == anEvent.getNewValue(); } else if (anEvent.getNewValue() < anEvent.getOldValue()) { assert rightInsetColumns.size() == anEvent.getOldValue(); while (rightInsetColumns.size() > anEvent.getNewValue()) { int col2Remove = getColumnCount() - 1; rightInsetColumns.remove(rightInsetColumns.size() - 1); fireColumnRemoved(col2Remove); } assert rightInsetColumns.size() == anEvent.getNewValue(); } } } protected class DelegatedColumnModelListener implements TableColumnModelListener { @Override public void columnAdded(TableColumnModelEvent e) { fireColumnAdded(inset.toOuterSpace(new InsetPart(PartKind.CONTENT, e.getToIndex()), LinearInset.EMPTY_CONTENT)); } @Override public void columnRemoved(TableColumnModelEvent e) { fireColumnRemoved(inset.toOuterSpace(new InsetPart(PartKind.CONTENT, e.getFromIndex()), LinearInset.EMPTY_CONTENT)); } @Override public void columnMoved(TableColumnModelEvent e) { fireColumnMoved(inset.toOuterSpace(new InsetPart(PartKind.CONTENT, e.getFromIndex()), LinearInset.EMPTY_CONTENT), inset.toOuterSpace(new InsetPart(PartKind.CONTENT, e.getToIndex()), LinearInset.EMPTY_CONTENT)); } @Override public void columnMarginChanged(ChangeEvent e) { Object oSource = e.getSource(); assert oSource == delegate; int oldMargin = columnMargin; columnMargin = delegate.getColumnMargin(); // Swing fires columnMarginChanged event when any column's width changes // this fact has no any reason. It seems that it's simply bad work of swing's programmers. // So, we have no any outgo, but unconditionally fire this event too. //if (oldMargin != columnMargin) { fireColumnMarginChanged(columnMargin); //} } @Override public void columnSelectionChanged(ListSelectionEvent e) { fireSelectionModelChanged(); } } protected class ColumnsEnumerator implements Enumeration<TableColumn> { protected int columnIndex = 0; @Override public boolean hasMoreElements() { return columnIndex < getColumnCount(); } @Override public TableColumn nextElement() { if (columnIndex >= 0 && columnIndex < leftInsetColumns.size()) { return leftInsetColumns.get(columnIndex++); } else if (columnIndex >= leftInsetColumns.size() && columnIndex < leftInsetColumns.size() + delegate.getColumnCount()) { return delegate.getColumn(columnIndex++ - leftInsetColumns.size()); } else if (columnIndex >= leftInsetColumns.size() + delegate.getColumnCount() && columnIndex < getColumnCount()) { return rightInsetColumns.get(columnIndex++ - leftInsetColumns.size() - delegate.getColumnCount()); } else { throw new ConcurrentModificationException(); } } } /** * Insetted column model constructor. It takes a <code>InsettedColumnsFactory</code> instance as a parameter. * @param aDelegate A column model all significant work is delegated to. * @param aInset Insetted column space definition. * @param aColumnsFactory A <code>InsettedColumnsFactory</code> instance to be used as insetted columns space filler. * @see TableColumnModel * @see LinearInset * @see InsettedColumnsFactory */ public InsettedColumnModel(TableColumnModel aDelegate, LinearInset aInset, InsettedColumnsFactory aColumnsFactory) { super(); delegate = aDelegate; inset = aInset; columnsFactory = aColumnsFactory; columnSelectionAllowed = delegate.getColumnSelectionAllowed(); columnMargin = delegate.getColumnMargin(); inset.addInsetChangeListener(new InsetListener()); delegate.addColumnModelListener(new DelegatedColumnModelListener()); generateLeftInsetColumns(0, inset.getPreFirst()); generateRightInsetColumns(0, inset.getAfterLast()); } /** * Insetted column model constructor. * @param aDelegate A column model all significant work is delegated to. * @param aInset Insetted column space definition. * @see TableColumnModel * @see LinearInset * @see InsettedColumnsFactory */ public InsettedColumnModel(TableColumnModel aDelegate, LinearInset aInset) { this(aDelegate, aInset, new DefaultInsettedColumnsFactory()); } protected void checkColumns() throws GridException { if (leftInsetColumns.size() != inset.getPreFirst()) { throw new GridException(String.format(LEFT_INSET_WRONG_COLUMNS_MSG, inset.getPreFirst(), leftInsetColumns.size())); } if (rightInsetColumns.size() != inset.getAfterLast()) { throw new GridException(String.format(RIGHT_INSET_WRONG_COLUMNS_MSG, inset.getAfterLast(), rightInsetColumns.size())); } } /** * * @param aMin Inclusive * @param aMax Exclusive */ protected void generateLeftInsetColumns(int aMin, int aMax) { for (int i = aMin; i < aMax; i++) { TableColumn col = columnsFactory.createLeft(inset.getPreFirst(), 0); leftInsetColumns.add(0, col); fireColumnAdded(0); } } protected void generateRightInsetColumns(int aMin, int aMax) { for (int i = aMin; i < aMax; i++) { TableColumn col = columnsFactory.createRight(inset.getAfterLast(), rightInsetColumns.size()); rightInsetColumns.add(col); fireColumnAdded(getColumnCount() - 1); } } protected int getLeftInsetSelectedColumnCount() { int selected = 0; for (int i = 0; i < leftInsetColumns.size(); i++) { if (selectionModel.isSelectedIndex(i)) { selected++; } } return selected; } protected int getContentSelectedColumnCount() { int selected = 0; for (int i = 0; i < delegate.getColumnCount(); i++) { if (selectionModel.isSelectedIndex(leftInsetColumns.size() + i)) { selected++; } } return selected; } protected int getRightInsetSelectedColumnCount() { int selected = 0; for (int i = 0; i < rightInsetColumns.size(); i++) { if (selectionModel.isSelectedIndex(leftInsetColumns.size() + delegate.getColumnCount() + i)) { selected++; } } return selected; } /** * {@inheritDoc} */ @Override public void addColumn(TableColumn aColumn) { delegate.addColumn(aColumn); // the event will raise by itself } /** * {@inheritDoc} */ @Override public void removeColumn(TableColumn column) { delegate.removeColumn(column); // the event will raise by itself } /** * {@inheritDoc} */ @Override public void moveColumn(int sourceIndex, int destIndex) { InsetPart sourcePart = inset.toInnerSpace(sourceIndex, delegate.getColumnCount()); InsetPart destPart = inset.toInnerSpace(destIndex, delegate.getColumnCount()); if (sourcePart.getKind() == destPart.getKind()) { if (sourcePart.getKind() == PartKind.BEFORE) { TableColumn destCol = leftInsetColumns.get(destIndex); TableColumn sourceCol = leftInsetColumns.get(sourceIndex); leftInsetColumns.set(destIndex, sourceCol); leftInsetColumns.set(sourceIndex, destCol); fireColumnMoved(sourceIndex, destIndex); } else if (sourcePart.getKind() == PartKind.AFTER) { TableColumn destCol = rightInsetColumns.get(destIndex); TableColumn sourceCol = rightInsetColumns.get(sourceIndex); rightInsetColumns.set(destIndex, sourceCol); rightInsetColumns.set(sourceIndex, destCol); fireColumnMoved(sourceIndex, destIndex); } else { delegate.moveColumn(sourcePart.getValue(), destPart.getValue()); // the event will raise by itself } } } /** * {@inheritDoc} */ @Override public void setColumnMargin(int aMargin) { columnMargin = aMargin; delegate.setColumnMargin(aMargin); // the event will raise by itself } /** * {@inheritDoc} */ @Override public int getColumnCount() { return delegate.getColumnCount() + leftInsetColumns.size() + rightInsetColumns.size(); } /** * {@inheritDoc} */ @Override public Enumeration<TableColumn> getColumns() { return new ColumnsEnumerator(); } /** * {@inheritDoc} */ @Override public int getColumnIndex(Object aColumnId) { for (int i = 0; i < leftInsetColumns.size(); i++) { Object id = leftInsetColumns.get(i).getIdentifier(); if (id.equals(aColumnId)) { return i; } } int delegateIndex = delegate.getColumnIndex(aColumnId); if (delegateIndex >= 0 && delegateIndex < delegate.getColumnCount()) { return delegateIndex + leftInsetColumns.size(); } for (int i = 0; i < rightInsetColumns.size(); i++) { Object id = rightInsetColumns.get(i).getIdentifier(); if (id.equals(aColumnId)) { return leftInsetColumns.size() + delegate.getColumnCount() + i; } } return -1; } /** * {@inheritDoc} */ @Override public TableColumn getColumn(int aColumnIndex) { if (aColumnIndex >= 0 && aColumnIndex < leftInsetColumns.size()) { return leftInsetColumns.get(aColumnIndex); } else if (aColumnIndex >= leftInsetColumns.size() && aColumnIndex < leftInsetColumns.size() + delegate.getColumnCount()) { return delegate.getColumn(aColumnIndex - leftInsetColumns.size()); } else // columnIndex >= leftInsetColumns.size()+delegate.getColumnCount() && columnIndex < getColumnCount() { return rightInsetColumns.get(aColumnIndex - leftInsetColumns.size() - delegate.getColumnCount()); } } /** * {@inheritDoc} */ @Override public int getColumnMargin() { return columnMargin; } /** * {@inheritDoc} */ @Override public int getColumnIndexAtX(int xPosition) { int width = 0; for (int i = 0; i < getColumnCount(); i++) { TableColumn col = getColumn(i); if (xPosition >= width && xPosition < width + col.getWidth()) { return i; } width += col.getWidth(); } return -1; } /** * {@inheritDoc} */ @Override public int getTotalColumnWidth() { int width = 0; for (int i = 0; i < leftInsetColumns.size(); i++) { width += leftInsetColumns.get(i).getWidth(); } width += delegate.getTotalColumnWidth(); for (int i = 0; i < rightInsetColumns.size(); i++) { width += rightInsetColumns.get(i).getWidth(); } return width; } /** * {@inheritDoc} */ @Override public void setColumnSelectionAllowed(boolean aAllowed) { columnSelectionAllowed = aAllowed; delegate.setColumnSelectionAllowed(aAllowed); // the event will raise by itself } /** * {@inheritDoc} */ @Override public boolean getColumnSelectionAllowed() { return columnSelectionAllowed; } /** * {@inheritDoc} */ @Override public int[] getSelectedColumns() { List<Integer> selected = new ArrayList<>(); for (int i = 0; i < getColumnCount(); i++) { if (selectionModel.isSelectedIndex(i)) { selected.add(i); } } int[] sArray = new int[selected.size()]; for (int j = 0; j < sArray.length; j++) { sArray[j] = selected.get(j); } return sArray; } /** * {@inheritDoc} */ @Override public int getSelectedColumnCount() { if (delegate.getSelectionModel() != null) { return getLeftInsetSelectedColumnCount() + delegate.getSelectedColumnCount() + getRightInsetSelectedColumnCount(); } else { return getLeftInsetSelectedColumnCount() + getContentSelectedColumnCount() + getRightInsetSelectedColumnCount(); } } /** * {@inheritDoc} */ @Override public void setSelectionModel(ListSelectionModel newModel) { if (selectionModel != null) { selectionModel.removeListSelectionListener(this); } selectionModel = newModel; selectionModel.addListSelectionListener(this); fireSelectionModelChanged(); } /** * {@inheritDoc} */ @Override public ListSelectionModel getSelectionModel() { return selectionModel; } /** * {@inheritDoc} */ @Override public void addColumnModelListener(TableColumnModelListener l) { listeners.add(l); } /** * {@inheritDoc} */ @Override public void removeColumnModelListener(TableColumnModelListener l) { listeners.remove(l); } @Override public void valueChanged(ListSelectionEvent e) { fireColumnSelectionChanged(e); } protected void fireSelectionModelChanged() { ListSelectionEvent selEvent = new ListSelectionEvent(this, 0, getColumnCount() - 1, false); fireColumnSelectionChanged(selEvent); } protected void fireColumnSelectionChanged(ListSelectionEvent e) { listeners.forEach((l) -> { l.columnSelectionChanged(e); }); } protected void fireColumnMarginChanged(int aMargin) { ChangeEvent event = new ChangeEvent(this); listeners.forEach((l) -> { l.columnMarginChanged(event); }); } protected void fireColumnRemoved(int aPosition) { TableColumnModelEvent event = new TableColumnModelEvent(this, aPosition, 0); listeners.forEach((l) -> { l.columnRemoved(event); }); } protected void fireColumnAdded(int aPosition) { TableColumnModelEvent event = new TableColumnModelEvent(this, 0, aPosition); listeners.forEach((l) -> { l.columnAdded(event); }); } protected void fireColumnMoved(int sourceIndex, int destIndex) { TableColumnModelEvent event = new TableColumnModelEvent(this, sourceIndex, destIndex); listeners.forEach((l) -> { l.columnMoved(event); }); } }