/******************************************************************************* * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.spreadsheet.ui; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import org.simantics.spreadsheet.ClientModel; import org.simantics.spreadsheet.Spreadsheets; import org.simantics.utils.datastructures.Pair; import org.simantics.utils.datastructures.collections.CollectionUtils; import gnu.trove.map.hash.THashMap; public class ClientModelImpl implements ClientModel { final private Map> properties = new THashMap>(); final private Map> cells = new THashMap>(); private int maxRow = 0; private int maxColumn = 0; private Set clears = new HashSet(); // private ArrayList> modifications = new ArrayList>(); private ArrayList modLocation = new ArrayList(); private ArrayList modProperty = new ArrayList(); private ArrayList modValue = new ArrayList(); private Map spanMap = new HashMap(); public ClientModelImpl() { if(Spreadsheet.DEBUG) System.out.println("SimpleContainerTableModel.init"); Map sheetDimensions = new THashMap(); Map headers = new THashMap(); Map excel = new THashMap(); Map sources = new THashMap(); Map sheets = new THashMap(); Map context = new THashMap(); Map mode = new THashMap(); Map states = new THashMap(); sheetDimensions.put(DIMENSIONS_FIT_ROWS, false); sheetDimensions.put(DIMENSIONS_FIT_COLS, false); sheetDimensions.put(DIMENSIONS_COL_COUNT, 0); sheetDimensions.put(DIMENSIONS_ROW_COUNT, 0); excel.put(EXCEL_VISIBLE, false); headers.put(HEADERS_COL_WIDTHS, new int[] { }); headers.put(HEADERS_ROW_HEIGHTS, new int[] { }); headers.put(HEADERS_COL_LABELS, new String[] { }); headers.put(HEADERS_ROW_LABELS, new String[] { }); sources.put(SOURCES_AVAILABLE, new String[] { }); sources.put(SOURCES_CURRENT, ""); properties.put(DIMENSIONS, sheetDimensions); properties.put(HEADERS, headers); properties.put(EXCEL, excel); properties.put(SOURCES, sources); properties.put(SHEETS, sheets); properties.put(CONTEXT, context); properties.put(MODE, mode); properties.put(STATES, states); setProperty(EXCEL, EXCEL_VISIBLE, false); } CopyOnWriteArrayList listeners = new CopyOnWriteArrayList(); @Override public void addListener(ClientModelListener listener) { listeners.add(listener); listener.rows(getRows()); listener.columns(getColumns()); listener.rowLabels((String[])getPropertyAt(HEADERS, HEADERS_ROW_LABELS)); listener.columnWidths((int[])getPropertyAt(HEADERS, HEADERS_COL_WIDTHS)); listener.sources((String[])getPropertyAt(SOURCES, SOURCES_AVAILABLE), (String)getPropertyAt(SOURCES, SOURCES_CURRENT)); } @Override public void removeListener(ClientModelListener listener) { listeners.remove(listener); } private void fireFlush() { for(ClientModelListener listener : listeners) listener.flush(); } private void fireRows() { for(ClientModelListener listener : listeners) listener.rows(getRows()); } private void fireColumns() { for(ClientModelListener listener : listeners) listener.columns(getColumns()); } private void fireColumnWidths() { for(ClientModelListener listener : listeners) listener.columnWidths((int[])getPropertyAt(HEADERS, HEADERS_COL_WIDTHS)); } private void fireSources() { for(ClientModelListener listener : listeners) listener.sources((String[])getPropertyAt(SOURCES, SOURCES_AVAILABLE), (String)getPropertyAt(SOURCES, SOURCES_CURRENT)); } private void fireProperty(String location, String property, Object value) { for(ClientModelListener listener : listeners) listener.propertyChange(location, property, value); } private void fireCleared(String location) { for(ClientModelListener listener : listeners) listener.cleared(location); } @Override public T getPossiblePropertyAt(String location, String property) { try { T t = getPropertyAt(location, property); return t; } catch (Throwable e) { return null; } } @SuppressWarnings("unchecked") public synchronized T getPropertyAt(String location, String property) { if(Spreadsheet.DEBUG) System.out.println("SimpleContainerTableModel.getPropertyAt " + location + " " + property); Map props = properties.get(location); if(props != null) { return (T)props.get(property); } else { Map cls = cells.get(location); if(cls != null) { return (T)cls.get(property); } else { return null; } } } private final Set sizing = CollectionUtils.toSet( DIMENSIONS_COL_COUNT, DIMENSIONS_ROW_COUNT, DIMENSIONS_FIT_COLS, DIMENSIONS_FIT_ROWS); private boolean sizingProperty(String property) { return sizing.contains(property); } private boolean checkMaxRow(int row) { if((row+1) > maxRow) { maxRow = row+1; return true; } else { return false; } } private boolean checkMaxColumn(int column) { if((column+1) > maxColumn) { maxColumn = column+1; return true; } else { return false; } } @Override public synchronized void setProperty(final String location, final String property, final Object value) { assert(location != null); assert(property != null); modLocation.add(location); modProperty.add(property); modValue.add(value); } @Override public void clearAll() { clears.addAll(cells.keySet()); } @Override public synchronized void clear(String location) { if (location.startsWith("Style")) // Nothing to do for now.. return; clears.add(location); } @Override public synchronized void flush() { for(String location : clears) { Map cls = cells.remove(location); if(cls == null) return; long l = Spreadsheets.decodeCellCoded(location); int row = (int)(l & 0xffffffff) - 1; int column = (int)((l>>32) & 0xffffffff); if(checkMaxRow(row)) fireRows(); if(checkMaxColumn(column)) fireColumns(); removeSpan(row, column); fireCleared(location); } for(int i=0;i props = properties.get(location); if(props != null || location.startsWith("Style")) { if (location.startsWith("Style") && props == null) { props = new HashMap<>(); properties.put(location, props); } if(sizingProperty(property)) { int currentRows = getRows(); int currentCols = getColumns(); props.put(property, value); if(getRows() != currentRows) fireRows(); if(getColumns() != currentCols) fireColumns(); } else { props.put(property, value); if(property.equals(HEADERS_COL_WIDTHS)) fireColumnWidths(); if(property.equals(SOURCES_AVAILABLE) || property.equals(SOURCES_CURRENT)) fireSources(); } } else { Map cls = cells.get(location); if(cls == null) { cls = new HashMap(); cells.put(location, cls); } cls.put(property, value); long l = Spreadsheets.decodeCellCoded(location); int row = (int)(l & 0xffffffff) - 1; int column = (int)((l>>32) & 0xffffffff); if(checkMaxRow(row)) fireRows(); if(checkMaxColumn(column)) fireColumns(); boolean rowSpanProperty = property.equals(ROW_SPAN); boolean columnSpanProperty = property.equals(COLUMN_SPAN); if (rowSpanProperty || columnSpanProperty) { Rectangle span = getRootSpan(row, column); int size = (Integer)value; if (span == null) { if (size > 1) { if (rowSpanProperty) { span = createSpan(row, column, size, 1); } else { span = createSpan(row, column, 1, size); } } } else { if (rowSpanProperty) { span.height = size; } else { span.width = size; } if ((span.width == 1) && (span.height == 1)) { removeSpan(row, column); } } } } fireProperty(location, property, value); } clears.clear(); modLocation.clear(); modProperty.clear(); modValue.clear(); fireFlush(); } @Override public int getRows() { boolean fitRows = getPropertyAt(ClientModel.DIMENSIONS, ClientModel.DIMENSIONS_FIT_ROWS); if(fitRows) return maxRow; else return getPropertyAt(ClientModel.DIMENSIONS, ClientModel.DIMENSIONS_ROW_COUNT); } @Override public int getColumns() { boolean fitCols = getPropertyAt(ClientModel.DIMENSIONS, ClientModel.DIMENSIONS_FIT_COLS); if(fitCols) { return maxColumn; } else return getPropertyAt(ClientModel.DIMENSIONS, ClientModel.DIMENSIONS_COL_COUNT); } @Override public int[] getColumnWidths() { int[] data = getPropertyAt(HEADERS, HEADERS_COL_WIDTHS); return data.clone(); } @Override public int[] getRowHeights() { int[] data = getPropertyAt(HEADERS, HEADERS_ROW_HEIGHTS); return data.clone(); } @Override public synchronized Collection> listAll(String property) { ArrayList> result = new ArrayList>(); for(Map.Entry> entry : properties.entrySet()) { Object value = entry.getValue().get(property); if(value != null) result.add(Pair.make(entry.getKey(), value)); } for(Map.Entry> entry : cells.entrySet()) { Object value = entry.getValue().get(property); if(value != null) result.add(Pair.make(entry.getKey(), value)); } return result; } private long spanKey(int row, int column) { return (row & 0xffffffff) | (((long)(column & 0xffffffff)) << 32); } @Override public synchronized Rectangle getSpan(int row, int column) { for (Rectangle span : spanMap.values()) { if (span.contains(column, row)) { return new Rectangle(span); } } return null; } @Override public synchronized List getSpans() { List spans = new ArrayList(spanMap.size()); for (Rectangle span : spanMap.values()) { spans.add(new Rectangle(span)); } return spans; } private Rectangle getRootSpan(int row, int column) { return spanMap.get(spanKey(row, column)); } private Rectangle createSpan(int row, int column, int rowSpan, int columnSpan) { Rectangle span = new Rectangle(column, row, columnSpan, rowSpan); spanMap.put(spanKey(row, column), span); return span; } private void removeSpan(int row, int column) { spanMap.remove(spanKey(row, column)); } }