| /******************************************************************************* |
| * Copyright (c) 2012, 2013, 2014 Original authors and others. |
| * 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: |
| * Original authors and others - initial API and implementation |
| * Dirk Fauth <dirk.fauth@googlemail.com> - Added percentage sizing |
| * Dirk Fauth <dirk.fauth@googlemail.com> - Added scaling |
| * neal zhang <nujiah001@126.com> - change some methods and fields visibility |
| ******************************************************************************/ |
| package org.eclipse.nebula.widgets.nattable.layer; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.StringTokenizer; |
| import java.util.TreeMap; |
| |
| import org.eclipse.nebula.widgets.nattable.persistence.IPersistable; |
| |
| /** |
| * This class stores the size configuration of rows/columns within the NatTable. |
| * |
| * Mixed mode (fixed/percentage sizing):<br> |
| * The mixed mode is only working if percentage sizing is enabled globally, and |
| * the fixed sized positions are marked separately. |
| */ |
| public class SizeConfig implements IPersistable { |
| |
| public static final String PERSISTENCE_KEY_DEFAULT_SIZE = ".defaultSize"; //$NON-NLS-1$ |
| public static final String PERSISTENCE_KEY_DEFAULT_SIZES = ".defaultSizes"; //$NON-NLS-1$ |
| public static final String PERSISTENCE_KEY_SIZES = ".sizes"; //$NON-NLS-1$ |
| public static final String PERSISTENCE_KEY_RESIZABLE_BY_DEFAULT = ".resizableByDefault"; //$NON-NLS-1$ |
| public static final String PERSISTENCE_KEY_RESIZABLE_INDEXES = ".resizableIndexes"; //$NON-NLS-1$ |
| public static final String PERSISTENCE_KEY_PERCENTAGE_SIZING = ".percentageSizing"; //$NON-NLS-1$ |
| public static final String PERSISTENCE_KEY_PERCENTAGE_SIZING_INDEXES = ".percentageSizingIndexes"; //$NON-NLS-1$ |
| |
| /** |
| * The global default size of this {@link SizeConfig}. |
| */ |
| protected int defaultSize; |
| /** |
| * Map that contains default sizes per column. |
| */ |
| protected final Map<Integer, Integer> defaultSizeMap = new TreeMap<Integer, Integer>(); |
| /** |
| * Map that contains sizes per column. |
| */ |
| protected final Map<Integer, Integer> sizeMap = new TreeMap<Integer, Integer>(); |
| /** |
| * Map that contains the resizable information per row/column. |
| */ |
| protected final Map<Integer, Boolean> resizablesMap = new TreeMap<Integer, Boolean>(); |
| /** |
| * The global resizable information of this {@link SizeConfig}. |
| */ |
| protected boolean resizableByDefault = true; |
| /** |
| * Map that contains the percentage sizing information per row/column. |
| */ |
| protected final Map<Integer, Boolean> percentageSizingMap = new TreeMap<Integer, Boolean>(); |
| /** |
| * Flag to tell whether the sizing is done for pixel or percentage values. |
| */ |
| protected boolean percentageSizing = false; |
| /** |
| * The available space needed for percentage calculation on resizing. |
| */ |
| protected int availableSpace = -1; |
| /** |
| * Map that contains the real pixel size. Will only be used on percentage |
| * sizing. This map is not persisted as it will be calculated on resize. |
| */ |
| protected final Map<Integer, Integer> realSizeMap = new TreeMap<Integer, Integer>(); |
| /** |
| * Map that contains the cached aggregated sizes. |
| */ |
| protected final Map<Integer, Integer> aggregatedSizeCacheMap = new HashMap<Integer, Integer>(); |
| /** |
| * Flag that indicates if the aggregated size cache is valid or if it needs |
| * to get recalculated. |
| */ |
| protected boolean isAggregatedSizeCacheValid = true; |
| /** |
| * The {@link IDpiConverter} that is used for scaling DPI conversion. |
| */ |
| protected IDpiConverter dpiConverter; |
| |
| /** |
| * Create a new {@link SizeConfig} with the given default size. |
| * |
| * @param defaultSize |
| * The default size to use. |
| */ |
| public SizeConfig(int defaultSize) { |
| this.defaultSize = defaultSize; |
| } |
| |
| // Persistence |
| |
| @Override |
| public void saveState(String prefix, Properties properties) { |
| properties.put(prefix + PERSISTENCE_KEY_DEFAULT_SIZE, String.valueOf(this.defaultSize)); |
| saveMap(this.defaultSizeMap, prefix + PERSISTENCE_KEY_DEFAULT_SIZES, properties); |
| saveMap(this.sizeMap, prefix + PERSISTENCE_KEY_SIZES, properties); |
| properties.put(prefix + PERSISTENCE_KEY_RESIZABLE_BY_DEFAULT, String.valueOf(this.resizableByDefault)); |
| saveMap(this.resizablesMap, prefix + PERSISTENCE_KEY_RESIZABLE_INDEXES, properties); |
| properties.put(prefix + PERSISTENCE_KEY_PERCENTAGE_SIZING, String.valueOf(this.percentageSizing)); |
| saveMap(this.percentageSizingMap, prefix + PERSISTENCE_KEY_PERCENTAGE_SIZING_INDEXES, properties); |
| } |
| |
| private void saveMap(Map<Integer, ?> map, String key, Properties properties) { |
| if (map.size() > 0) { |
| StringBuilder strBuilder = new StringBuilder(); |
| for (Integer index : map.keySet()) { |
| strBuilder.append(index); |
| strBuilder.append(':'); |
| strBuilder.append(map.get(index)); |
| strBuilder.append(','); |
| } |
| properties.setProperty(key, strBuilder.toString()); |
| } |
| } |
| |
| @Override |
| public void loadState(String prefix, Properties properties) { |
| // ensure to cleanup the current states prior loading new ones |
| this.defaultSizeMap.clear(); |
| this.sizeMap.clear(); |
| this.resizablesMap.clear(); |
| this.aggregatedSizeCacheMap.clear(); |
| |
| String persistedDefaultSize = properties.getProperty(prefix + PERSISTENCE_KEY_DEFAULT_SIZE); |
| if (persistedDefaultSize != null && persistedDefaultSize.length() > 0) { |
| this.defaultSize = Integer.valueOf(persistedDefaultSize).intValue(); |
| } |
| |
| String persistedResizableDefault = properties.getProperty(prefix + PERSISTENCE_KEY_RESIZABLE_BY_DEFAULT); |
| if (persistedResizableDefault != null && persistedResizableDefault.length() > 0) { |
| this.resizableByDefault = Boolean.valueOf(persistedResizableDefault).booleanValue(); |
| } |
| |
| String persistedPercentageSizing = properties.getProperty(prefix + PERSISTENCE_KEY_PERCENTAGE_SIZING); |
| if (persistedPercentageSizing != null && persistedPercentageSizing.length() > 0) { |
| setPercentageSizing(Boolean.valueOf(persistedPercentageSizing).booleanValue()); |
| } |
| |
| loadBooleanMap(prefix + PERSISTENCE_KEY_RESIZABLE_INDEXES, properties, this.resizablesMap); |
| loadIntegerMap(prefix + PERSISTENCE_KEY_DEFAULT_SIZES, properties, this.defaultSizeMap); |
| loadIntegerMap(prefix + PERSISTENCE_KEY_SIZES, properties, this.sizeMap); |
| loadBooleanMap(prefix + PERSISTENCE_KEY_PERCENTAGE_SIZING_INDEXES, properties, this.percentageSizingMap); |
| } |
| |
| private void loadIntegerMap(String key, Properties properties, Map<Integer, Integer> map) { |
| String property = properties.getProperty(key); |
| if (property != null) { |
| map.clear(); |
| |
| StringTokenizer tok = new StringTokenizer(property, ","); //$NON-NLS-1$ |
| while (tok.hasMoreTokens()) { |
| String token = tok.nextToken(); |
| int separatorIndex = token.indexOf(':'); |
| map.put(Integer.valueOf(token.substring(0, separatorIndex)), |
| Integer.valueOf(token.substring(separatorIndex + 1))); |
| } |
| } |
| } |
| |
| private void loadBooleanMap(String key, Properties properties, Map<Integer, Boolean> map) { |
| String property = properties.getProperty(key); |
| if (property != null) { |
| StringTokenizer tok = new StringTokenizer(property, ","); //$NON-NLS-1$ |
| while (tok.hasMoreTokens()) { |
| String token = tok.nextToken(); |
| int separatorIndex = token.indexOf(':'); |
| map.put(Integer.valueOf(token.substring(0, separatorIndex)), |
| Boolean.valueOf(token.substring(separatorIndex + 1))); |
| } |
| } |
| } |
| |
| // Default size |
| |
| /** |
| * Set the default size that should be used in case there is no position |
| * based size configured. |
| * |
| * @param size |
| * The default size to set. |
| */ |
| public void setDefaultSize(int size) { |
| if (size < 0) { |
| throw new IllegalArgumentException("size < 0"); //$NON-NLS-1$ |
| } |
| this.defaultSize = size; |
| this.isAggregatedSizeCacheValid = false; |
| } |
| |
| /** |
| * @return The default size that is used in case there is no position based |
| * size configured. |
| */ |
| public int getDefaultSize() { |
| return upScale(this.defaultSize); |
| } |
| |
| public void setDefaultSize(int position, int size) { |
| if (this.defaultSize < 0) { |
| throw new IllegalArgumentException("size < 0"); //$NON-NLS-1$ |
| } |
| this.defaultSizeMap.put(position, size); |
| this.isAggregatedSizeCacheValid = false; |
| } |
| |
| private int getDefaultSize(int position) { |
| Integer size = this.defaultSizeMap.get(position); |
| if (size != null) { |
| return size.intValue(); |
| } else { |
| return this.defaultSize; |
| } |
| } |
| |
| // Size |
| |
| public int getAggregateSize(int position) { |
| if (position < 0) { |
| return -1; |
| } else if (position == 0) { |
| return 0; |
| } else if (isAllPositionsSameSize() && !isPercentageSizing()) { |
| // if percentage sizing is used, the sizes in defaultSize are used |
| // as percentage values and not as pixel values, therefore another |
| // value needs to be considered |
| return upScale(position * this.defaultSize); |
| } else { |
| // See if the cache is valid, if not clear it. |
| if (!this.isAggregatedSizeCacheValid) { |
| this.aggregatedSizeCacheMap.clear(); |
| this.isAggregatedSizeCacheValid = true; |
| } |
| |
| if (!this.aggregatedSizeCacheMap.containsKey(position)) { |
| int aggregatedSize = calculateAggregatedSize(position); |
| this.aggregatedSizeCacheMap.put(position, aggregatedSize); |
| } |
| return this.aggregatedSizeCacheMap.get(position); |
| } |
| } |
| |
| public int getSize(int position) { |
| Integer size = null; |
| if (isPercentageSizing()) { |
| size = this.realSizeMap.get(position); |
| } else { |
| if (this.sizeMap.containsKey(position)) { |
| size = upScale(this.sizeMap.get(position)); |
| } |
| } |
| if (size != null) { |
| return size.intValue(); |
| } else { |
| return upScale(getDefaultSize(position)); |
| } |
| } |
| |
| /** |
| * Sets the given size for the given position. This method can be called |
| * manually for configuration via {@link DataLayer} and will be called on |
| * resizing within the rendered UI. This is why there is a check for |
| * percentage configuration. If this {@link SizeConfig} is configured to not |
| * use percentage sizing, the size is taken as is. If percentage sizing is |
| * enabled, the given size will be calculated to percentage value based on |
| * the already known pixel values. |
| * <p> |
| * If you want to use percentage sizing you should use |
| * {@link SizeConfig#setPercentage(int, int)} for manual size configuration |
| * to avoid unnecessary calculations. |
| * |
| * @param position |
| * The position for which the size should be set. |
| * @param size |
| * The size in pixels to set for the given position. |
| */ |
| public void setSize(int position, int size) { |
| if (size < 0) { |
| throw new IllegalArgumentException("size < 0"); //$NON-NLS-1$ |
| } |
| if (isPositionResizable(position)) { |
| // check whether the given value should be remembered as is or if it |
| // needs to be calculated |
| if (!isPercentageSizing(position)) { |
| this.sizeMap.put(position, downScale(size)); |
| } else { |
| if (this.availableSpace > 0) { |
| Double percentage = ((double) size * 100) / this.availableSpace; |
| |
| Integer oldValue = this.sizeMap.get(position); |
| int diff = percentage.intValue(); |
| if (oldValue != null) { |
| diff = diff - oldValue; |
| } else { |
| // there was no percentage value before |
| // we need to calculate the before value out of the |
| // realSizeMap otherwise the resizing effect would |
| // have strange effects |
| if (this.realSizeMap.containsKey(position)) { |
| Double calculated = ((double) this.realSizeMap.get(position) * 100) / this.availableSpace; |
| diff = diff - calculated.intValue(); |
| } |
| |
| } |
| |
| this.sizeMap.put(position, percentage.intValue()); |
| |
| // check the adjacent positions for percentage corrections |
| int nextPosition = position + 1; |
| while (diff != 0 && this.realSizeMap.containsKey(nextPosition)) { |
| diff = updateAdjacentPosition(nextPosition, diff); |
| nextPosition++; |
| } |
| |
| int previousPosition = position - 1; |
| while (diff != 0 |
| && this.realSizeMap.containsKey(previousPosition)) { |
| diff = updateAdjacentPosition(previousPosition, diff); |
| previousPosition--; |
| } |
| |
| if (diff != 0 && oldValue == null) { |
| // if the diff is not 0 and there was no size value set |
| // before we will remove the prior set value again |
| // this is because the position was configured as the |
| // only percentage sizing position with no specified |
| // value, which technically means that it should always |
| // take the remaining space |
| this.sizeMap.remove(position); |
| } |
| } |
| } |
| |
| calculatePercentages(this.availableSpace, this.realSizeMap.size()); |
| this.isAggregatedSizeCacheValid = false; |
| } |
| } |
| |
| /** |
| * This method is used for resizing in percentage mode. If a position that |
| * is configured for percentage sizing is resized, the size diff needs to be |
| * added/removed from adjacent cells, to ensure consistent size calculation. |
| * |
| * @param position |
| * The position to update. |
| * @param diff |
| * The diff that should be applied to the position |
| * @return The remaining diff or 0 if the diff could be completely applied |
| * to the position. |
| */ |
| private int updateAdjacentPosition(int position, int diff) { |
| if (this.sizeMap.containsKey(position) || this.realSizeMap.containsKey(position)) { |
| if (isPercentageSizing(position)) { |
| if (this.sizeMap.containsKey(position)) { |
| // there is a follow-up position that is configured for |
| // percentage sizing |
| // and there is value specified for that position |
| int currentValue = this.sizeMap.get(position); |
| if (diff < currentValue) { |
| this.sizeMap.put(position, currentValue - diff); |
| return 0; |
| } else { |
| diff = diff - (currentValue + 1); |
| // never accept a percentage value < 1 as then the |
| // position would disappear |
| this.sizeMap.put(position, 1); |
| } |
| } |
| |
| return 0; |
| } |
| } |
| |
| return diff; |
| } |
| |
| /** |
| * Will set the given percentage size information for the given position and |
| * will set the given position to be sized via percentage value. |
| * |
| * @param position |
| * The positions whose percentage sizing information should be |
| * set. |
| * @param percentage |
| * The percentage value to set, always dependent on the available |
| * space for percentage sizing, which can be less than the real |
| * available space in case there are also positions that are |
| * configured for fixed size. |
| */ |
| public void setPercentage(int position, int percentage) { |
| if (percentage < 0) { |
| throw new IllegalArgumentException("percentage < 0"); //$NON-NLS-1$ |
| } |
| if (isPositionResizable(position)) { |
| this.percentageSizingMap.put(position, Boolean.TRUE); |
| this.sizeMap.put(position, percentage); |
| this.realSizeMap.put(position, calculatePercentageValue(percentage, this.availableSpace)); |
| calculatePercentages(this.availableSpace, this.realSizeMap.size()); |
| } |
| } |
| |
| // Resizable |
| |
| /** |
| * @return The global resizable information of this {@link SizeConfig}. |
| */ |
| public boolean isResizableByDefault() { |
| return this.resizableByDefault; |
| } |
| |
| /** |
| * Checks if there is a special resizable configuration for the given |
| * position. If not the global resizable information is returned. |
| * |
| * @param position |
| * The position of the row/column for which the resizable |
| * information is requested. |
| * @return <code>true</code> if the given row/column position is resizable, |
| * <code>false</code> if not. |
| */ |
| public boolean isPositionResizable(int position) { |
| Boolean resizable = this.resizablesMap.get(position); |
| if (resizable != null) { |
| return resizable.booleanValue(); |
| } |
| return this.resizableByDefault; |
| } |
| |
| /** |
| * Sets the resizable configuration for the given row/column position. |
| * |
| * @param position |
| * The position of the row/column for which the resizable |
| * configuration should be set. |
| * @param resizable |
| * <code>true</code> if the given row/column position should be |
| * resizable, <code>false</code> if not. |
| */ |
| public void setPositionResizable(int position, boolean resizable) { |
| this.resizablesMap.put(position, resizable); |
| } |
| |
| /** |
| * Sets the global resizable configuration. Will reset all special resizable |
| * configurations. |
| * |
| * @param resizableByDefault |
| * <code>true</code> if all rows/columns should be resizable, |
| * <code>false</code> if no row/column should be resizable. |
| */ |
| public void setResizableByDefault(boolean resizableByDefault) { |
| this.resizablesMap.clear(); |
| this.resizableByDefault = resizableByDefault; |
| } |
| |
| // All positions same size |
| |
| public boolean isAllPositionsSameSize() { |
| return this.defaultSizeMap.size() == 0 && this.sizeMap.size() == 0; |
| } |
| |
| /** |
| * @return <code>true</code> if the size of at least one position is |
| * interpreted in percentage, <code>false</code> if the size of all |
| * positions is interpreted by pixel. |
| */ |
| public boolean isPercentageSizing() { |
| if (!this.percentageSizingMap.isEmpty()) { |
| for (Boolean pSize : this.percentageSizingMap.values()) { |
| if (pSize) |
| return true; |
| } |
| } |
| return this.percentageSizing; |
| } |
| |
| /** |
| * @param percentageSizing |
| * <code>true</code> if the size of the positions should be |
| * interpreted percentaged, <code>false</code> if the size of the |
| * positions should be interpreted by pixel. |
| */ |
| public void setPercentageSizing(boolean percentageSizing) { |
| this.percentageSizing = percentageSizing; |
| this.isAggregatedSizeCacheValid = false; |
| } |
| |
| /** |
| * Checks if there is a special percentage sizing configuration for the |
| * given position. If not the global percentage sizing information is |
| * returned. |
| * |
| * @param position |
| * The position of the row/column for which the percentage sizing |
| * information is requested. |
| * @return <code>true</code> if the given row/column position is sized by |
| * percentage value, <code>false</code> if not. |
| */ |
| public boolean isPercentageSizing(int position) { |
| Boolean percentageSizing = this.percentageSizingMap.get(position); |
| if (percentageSizing != null) { |
| return percentageSizing; |
| } |
| return this.percentageSizing; |
| } |
| |
| /** |
| * Sets the percentage sizing configuration for the given row/column |
| * position. |
| * |
| * @param position |
| * The position of the row/column for which the percentage sizing |
| * configuration should be set. |
| * @param percentageSizing |
| * <code>true</code> if the given row/column position should be |
| * interpreted in percentage, <code>false</code> if not. |
| */ |
| public void setPercentageSizing(int position, boolean percentageSizing) { |
| this.percentageSizingMap.put(position, percentageSizing); |
| this.isAggregatedSizeCacheValid = false; |
| } |
| |
| /** |
| * Will calculate the real pixel values for the positions if percentage |
| * sizing is enabled. |
| * |
| * @param space |
| * The space that is available for rendering. |
| * @param positionCount |
| * The number of positions that should be handled by this |
| * {@link SizeConfig} |
| */ |
| public void calculatePercentages(int space, int positionCount) { |
| if (isPercentageSizing()) { |
| this.isAggregatedSizeCacheValid = false; |
| this.availableSpace = space; |
| |
| int percentageSpace = calculateAvailableSpace(space); |
| |
| int sum = 0; |
| int real = 0; |
| int realSum = 0; |
| int fixedSum = 0; |
| List<Integer> noInfoPositions = new ArrayList<Integer>(); |
| Integer positionValue = null; |
| for (int i = 0; i < positionCount; i++) { |
| positionValue = this.sizeMap.get(i); |
| if (positionValue != null) { |
| if (isPercentageSizing(i)) { |
| sum += positionValue; |
| real = calculatePercentageValue(positionValue, |
| percentageSpace); |
| } else { |
| real = positionValue; |
| fixedSum += real; |
| } |
| realSum += real; |
| this.realSizeMap.put(i, real); |
| } else { |
| // remember the position for which no size information |
| // exists needed to calculate the size for those positions |
| // dependent on the remaining space |
| noInfoPositions.add(i); |
| } |
| } |
| |
| int[] correction = correctPercentageValues(sum, positionCount); |
| if (correction != null) { |
| sum = correction[0]; |
| realSum = correction[1] + fixedSum; |
| } |
| |
| if (!noInfoPositions.isEmpty()) { |
| // now calculate the size for the remaining columns |
| double remaining = new Double(space) - realSum; |
| Double remainingColSpace = remaining / noInfoPositions.size(); |
| for (Integer position : noInfoPositions) { |
| sum += (remainingColSpace / space) * 100; |
| this.realSizeMap.put(position, remainingColSpace.intValue()); |
| } |
| // If there are positions for which no size information exist, |
| // the size config will use 100 percent of the available space |
| // on percentage sizing. To handle rounding issues just set the |
| // sum to 100 for correct calculation results. |
| sum = 100; |
| } |
| if (sum == 100) { |
| // check if the sum of the calculated values is the same as the |
| // given space if not distribute the missing pixels to some of |
| // the other columns this is needed because of rounding issues |
| // on 100% with odd-numbered pixel values |
| int valueSum = 0; |
| int lastPos = -1; |
| for (Map.Entry<Integer, Integer> entry : this.realSizeMap.entrySet()) { |
| valueSum += entry.getValue(); |
| lastPos = Math.max(lastPos, entry.getKey()); |
| } |
| |
| if (space > 0 && valueSum < space) { |
| // distribute the missing pixels |
| int missingPixels = (space - valueSum); |
| int pos = 0; |
| for (int i = missingPixels; i > 0; i--) { |
| if (!this.realSizeMap.containsKey(pos)) { |
| // there are more missing pixels than columns |
| // start over at position 0 |
| pos = 0; |
| } |
| int posValue = this.realSizeMap.get(pos); |
| this.realSizeMap.put(pos, posValue + 1); |
| pos++; |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * @param percentage |
| * The percentage value. |
| * @param space |
| * The available space |
| * @return The percentage value of the given space. |
| */ |
| private int calculatePercentageValue(int percentage, int space) { |
| double factor = (double) percentage / 100; |
| return new Double(space * factor).intValue(); |
| } |
| |
| /** |
| * Calculates the available space for percentage size calculation. This is |
| * necessary to support mixed mode of sizing, e.g. if two columns are |
| * configured to have fixed size of 50 pixels and one column that should |
| * take the rest of the available space of 500 pixels, the available space |
| * for percentage sizing is 400 pixels. |
| * |
| * @param space |
| * The whole available space for rendering. |
| * @return The available space for percentage sizing. Might be negative if |
| * the width of all fixed sized positions is greater than the |
| * available space. |
| */ |
| protected int calculateAvailableSpace(int space) { |
| if (!this.percentageSizingMap.isEmpty()) { |
| if (this.percentageSizing) { |
| for (Map.Entry<Integer, Boolean> entry : this.percentageSizingMap |
| .entrySet()) { |
| if (!entry.getValue()) { |
| if (this.sizeMap.containsKey(entry.getKey())) |
| space -= this.sizeMap.get(entry.getKey()); |
| } |
| } |
| } |
| } |
| return space; |
| } |
| |
| /** |
| * This method is used to correct the calculated percentage values in case a |
| * user configured more than 100 percent. In that case the set percentage |
| * values are scaled down to not exceed 100 percent. |
| * |
| * @param sum |
| * The sum of all configured percentage sized positions. |
| * @param positionCount |
| * The number of positions to check. |
| * @return Integer array with the sum value at first position and the new |
| * calculated real pixel sum at second position in case a |
| * corrections took place. Will return <code>null</code> in case no |
| * correction happened. |
| */ |
| protected int[] correctPercentageValues(int sum, int positionCount) { |
| Map<Integer, Integer> toModify = new TreeMap<Integer, Integer>(); |
| for (int i = 0; i < positionCount; i++) { |
| Integer positionValue = this.sizeMap.get(i); |
| if (positionValue != null && isPercentageSizing(i)) { |
| toModify.put(i, this.realSizeMap.get(i)); |
| } |
| } |
| |
| // if the sum is greater than 100 we need to normalize the percentage |
| // values |
| if (sum > 100) { |
| // calculate the factor which needs to be used to normalize the |
| // values |
| double factor = Double.valueOf(100) / Double.valueOf(sum); |
| |
| // update the percentage size values by the calculated factor |
| int realSum = 0; |
| for (Map.Entry<Integer, Integer> mod : toModify.entrySet()) { |
| int oldValue = mod.getValue(); |
| int newValue = Double.valueOf(oldValue * factor).intValue(); |
| realSum += newValue; |
| this.realSizeMap.put(mod.getKey(), newValue); |
| } |
| |
| return new int[] { 100, realSum }; |
| } |
| |
| // the given sum is not greater than 100 so we do not have to modify |
| // anything |
| return null; |
| } |
| |
| private int calculateAggregatedSize(int position) { |
| int resizeAggregate = 0; |
| int resizedColumns = 0; |
| |
| Map<Integer, Integer> mapToUse = isPercentageSizing() ? this.realSizeMap : this.sizeMap; |
| |
| for (Integer resizedPosition : mapToUse.keySet()) { |
| if (resizedPosition.intValue() < position) { |
| resizedColumns++; |
| resizeAggregate += mapToUse.get(resizedPosition); |
| } else { |
| break; |
| } |
| } |
| |
| // also take into account the default size configuration per position |
| for (Integer defaultPosition : this.defaultSizeMap.keySet()) { |
| if (defaultPosition.intValue() < position) { |
| if (!mapToUse.containsKey(defaultPosition)) { |
| resizedColumns++; |
| resizeAggregate += this.defaultSizeMap.get(defaultPosition).intValue(); |
| } |
| } else { |
| break; |
| } |
| } |
| |
| int result = (position * this.defaultSize) + resizeAggregate - (resizedColumns * this.defaultSize); |
| return isPercentageSizing() ? result : upScale(result); |
| } |
| |
| /** |
| * Recalculate the percentage values for the given amount of columns. Needed |
| * for structural changes that aren't caused by a client are resize, e.g. |
| * adding a column. |
| * |
| * @param positionCount |
| * The number of columns that should be used to calculate the |
| * percentage values. |
| */ |
| public void updatePercentageValues(int positionCount) { |
| calculatePercentages(this.availableSpace, positionCount); |
| } |
| |
| /** |
| * Calculates the size value dependent on a possible configured scaling from |
| * pixel to DPI value. |
| * |
| * @param value |
| * The value that should be up scaled. |
| * @return The scaled value if a {@link IDpiConverter} is configured, the |
| * value itself if no {@link IDpiConverter} is set. |
| * |
| * @see IDpiConverter#convertPixelToDpi(int) |
| */ |
| protected int upScale(int value) { |
| if (this.dpiConverter == null) { |
| return value; |
| } |
| return this.dpiConverter.convertPixelToDpi(value); |
| } |
| |
| /** |
| * Calculates the size value dependent on a possible configured scaling from |
| * DPI to pixel value. |
| * |
| * <p> |
| * This method is used for percentage sizing calculations. |
| * </p> |
| * |
| * @param value |
| * The value that should be down scaled. |
| * @return The scaled value if a {@link IDpiConverter} is configured, the |
| * value itself if no {@link IDpiConverter} is set. |
| */ |
| protected int downScale(int value) { |
| if (this.dpiConverter == null) { |
| return value; |
| } |
| return this.dpiConverter.convertDpiToPixel(value); |
| } |
| |
| /** |
| * |
| * @param dpiConverter |
| * The {@link IDpiConverter} to use for size scaling. |
| */ |
| public void setDpiConverter(IDpiConverter dpiConverter) { |
| this.dpiConverter = dpiConverter; |
| } |
| } |