All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jgoodies.forms.layout.LayoutMap Maven / Gradle / Ivy

Go to download

The JGoodies Forms framework helps you lay out and implement elegant Swing panels quickly and consistently. It makes simple things easy and the hard stuff possible, the good design easy and the bad difficult.

There is a newer version: 1.9.0
Show newest version
/*
 * Copyright (c) 2002-2014 JGoodies Software GmbH. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  o Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  o Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 *  o Neither the name of JGoodies Software GmbH nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jgoodies.forms.layout;

import static com.jgoodies.common.base.Preconditions.checkNotNull;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import com.jgoodies.forms.util.LayoutStyle;


/**
 * Provides a hierarchical variable expansion useful to improve layout
 * consistency, style guide compliance, and layout readability.

* * A LayoutMap maps variable names to layout expression Strings. The FormLayout, * ColumnSpec, and RowSpec parsers expand variables before an encoded layout * specification is parsed and converted into ColumnSpec and RowSpec values. * Variables start with the '$' character. The variable name can be wrapped * by braces ('{' and '}'). For example, you can write: * new FormLayout("pref, $lcg, pref") or * new FormLayout("pref, ${lcg}, pref").

* * LayoutMaps build a chain; each LayoutMap has an optional parent map. * The root is defined by {@link LayoutMap#getRoot()}. Application-wide * variables should be defined in the root LayoutMap. If you want to override * application-wide variables locally, obtain a LayoutMap using {@code * new LayoutMap()}, configure it, and provide it as argument to the * FormLayout, ColumnSpec, and RowSpec constructors/factory methods.

* * By default the root LayoutMap provides the following associations: *

* * * * * * * * *
Variable NameAbbreviationsOrientationDescription
label-component-gaplcg, lcgapbothgap between a label and the labeled component
related-gaprg, rgapbothgap between two related components
unrelated-gapug, ugapbothgap between two unrelated components
buttonbhorizontalbutton column with minimum width
line-gaplg, lgapverticalgap between two lines
narrow-line-gapnlg, nlgapverticalnarrow gap between two lines
paragraphpg, pgapverticalgap between two paragraphs/sections

* * Examples: *

 * // Predefined variables
 * new FormLayout(
 *     "pref, $lcgap, pref, $rgap, pref",
 *     "p, $lgap, p, $lgap, p");
 *
 * // Custom variables
 * LayoutMap.getRoot().columnPut("half", "39dlu");
 * LayoutMap.getRoot().columnPut("full", "80dlu");
 * LayoutMap.getRoot().rowPut("table", "fill:0:grow");
 * LayoutMap.getRoot().rowPut("table50", "fill:50dlu:grow");
 * new FormLayout(
 *     "pref, $lcgap, $half, 2dlu, $half",
 *     "p, $lcgap, $table50");
 * new FormLayout(
 *     "pref, $lcgap, $full",
 *     "p, $lcgap, $table50");
 *
 * // Nested variables
 * LayoutMap.getRoot().columnPut("c-gap-c", "$half, 2dlu, $half");
 * new FormLayout(
 *     "pref, $lcgap, ${c-gap-c}", // -> "pref, $lcgap, $half, 2dlu, $half",
 *     "p, $lcgap, $table");
 * 
* * LayoutMap holds two internal Maps that associate key Strings with expression * Strings for the columns and rows respectively. Null values are not allowed.

* * Tips:

    *
  • You should carefully override predefined variables, * because variable users may expect that these don't change. *
  • Set custom variables in the root LayoutMap. *
  • Avoid aliases for custom variables. *
* * @author Karsten Lentzsch * @version $Revision: 1.24 $ * * @see FormLayout * @see ColumnSpec * @see RowSpec * * @since 1.2 */ public final class LayoutMap { /** * Marks a layout variable; used by the Forms parsers. */ private static final char VARIABLE_PREFIX_CHAR = '$'; /** * Maps column aliases to their default name, for example * {@code "rgap"} -> {@code "related-gap"}. */ private static final Map COLUMN_ALIASES = new HashMap(); /** * Maps row aliases to their default name, for example * {@code "rgap"} -> {@code "related-gap"}. */ private static final Map ROW_ALIASES = new HashMap(); /** * Holds the lazily initialized root map. */ private static LayoutMap root = null; // Instance Fields ******************************************************** /** * Refers to the parent map that is used to look up values * if this map contains no association for a given key. * The parent maps can build chains. */ private final LayoutMap parent; /** * Holds the raw associations from variable names to expressions. * The expression may contain variables that are not expanded. */ private final Map columnMap; /** * Holds the cached associations from variable names to expressions. * The expression are fully expanded and contain no variables. */ private final Map columnMapCache; /** * Holds the raw associations from variable names to expressions. * The expression may contain variables that are not expanded. */ private final Map rowMap; /** * Holds the cached associations from variable names to expressions. * The expression are fully expanded and contain no variables. */ private final Map rowMapCache; // Instance Creation ****************************************************** /** * Constructs a LayoutMap that has the root LayoutMap as parent. */ public LayoutMap() { this(getRoot()); } /** * Constructs a LayoutMap with the given optional parent. * * @param parent the parent LayoutMap, may be {@code null} */ public LayoutMap(LayoutMap parent) { this.parent = parent; columnMap = new HashMap(); rowMap = new HashMap(); columnMapCache = new HashMap(); rowMapCache = new HashMap(); } // Default **************************************************************** /** * Lazily initializes and returns the LayoutMap that is used * for variable expansion, if no custom LayoutMap is provided. * * @return the LayoutMap that is used, if no custom LayoutMap is provided */ public static synchronized LayoutMap getRoot() { if (root == null) { root = createRoot(); } return root; } // Column Mapping ********************************************************* /** * Returns {@code true} if this map or a parent map - if any - contains * a mapping for the specified key. * * @param key key whose presence in this LayoutMap chain is to be tested. * @return {@code true} if this map contains a column mapping * for the specified key. * * @throws NullPointerException if the key is {@code null}. * * @see Map#containsKey(Object) */ public boolean columnContainsKey(String key) { String resolvedKey = resolveColumnKey(key); return columnMap.containsKey(resolvedKey) || parent != null && parent.columnContainsKey(resolvedKey); } /** * Looks up and returns the String associated with the given key. * First looks for an association in this LayoutMap. If there's no * association, the lookup continues with the parent map - if any. * * @param key key whose associated value is to be returned. * @return the column String associated with the {@code key}, * or {@code null} if no LayoutMap in the parent chain * contains an association. * * @throws NullPointerException if {@code key} is {@code null} * * @see Map#get(Object) */ public String columnGet(String key) { String resolvedKey = resolveColumnKey(key); String cachedValue = columnMapCache.get(resolvedKey); if (cachedValue != null) { return cachedValue; } String value = columnMap.get(resolvedKey); if (value == null && parent != null) { value = parent.columnGet(resolvedKey); } if (value == null) { return null; } String expandedString = expand(value, true); columnMapCache.put(resolvedKey, expandedString); return expandedString; } /** * Associates the specified column String with the specified key * in this map. * If the map previously contained a mapping for this key, the old value * is replaced by the specified value. The value set in this map * overrides an association - if any - in the chain of parent LayoutMaps.

* * The {@code value} must not be {@code null}. To remove * an association from this map use {@link #columnRemove(String)}. * * @param key key with which the specified value is to be associated. * @param value column expression value to be associated with the specified key. * @return previous String associated with specified key, * or {@code null} if there was no mapping for key. * * @throws NullPointerException if the {@code key} or {@code value} * is {@code null}. * * @see Map#put(Object, Object) */ public String columnPut(String key, String value) { checkNotNull(value, "The column expression value must not be null."); String resolvedKey = resolveColumnKey(key); columnMapCache.clear(); return columnMap.put( resolvedKey, value.toLowerCase(Locale.ENGLISH)); } public String columnPut(String key, ColumnSpec value) { return columnPut(key, value.encode()); } public String columnPut(String key, Size value) { return columnPut(key, value.encode()); } /** * Removes the column value mapping for this key from this map if it is * present.

* * Returns the value to which the map previously associated the key, * or {@code null} if the map contained no mapping for this key. * The map will not contain a String mapping for the specified key * once the call returns. * * @param key key whose mapping is to be removed from the map. * @return previous value associated with specified key, or {@code null} * if there was no mapping for key. * * @throws NullPointerException if {@code key} is {@code null}. * * @see Map#remove(Object) */ public String columnRemove(String key) { String resolvedKey = resolveColumnKey(key); columnMapCache.clear(); return columnMap.remove(resolvedKey); } // Row Mapping ************************************************************ /** * Returns {@code true} if this map or a parent map - if any - contains * a RowSpec mapping for the specified key. * * @param key key whose presence in this LayoutMap chain is to be tested. * @return {@code true} if this map contains a row mapping * for the specified key. * * @throws NullPointerException if the key is {@code null}. * * @see Map#containsKey(Object) */ public boolean rowContainsKey(String key) { String resolvedKey = resolveRowKey(key); return rowMap.containsKey(resolvedKey) || parent != null && parent.rowContainsKey(resolvedKey); } /** * Looks up and returns the RowSpec associated with the given key. * First looks for an association in this LayoutMap. If there's no * association, the lookup continues with the parent map - if any. * * @param key key whose associated value is to be returned. * @return the row specification associated with the {@code key}, * or {@code null} if no LayoutMap in the parent chain * contains an association. * * @throws NullPointerException if {@code key} is {@code null} * * @see Map#get(Object) */ public String rowGet(String key) { String resolvedKey = resolveRowKey(key); String cachedValue = rowMapCache.get(resolvedKey); if (cachedValue != null) { return cachedValue; } String value = rowMap.get(resolvedKey); if (value == null && parent != null) { value = parent.rowGet(resolvedKey); } if (value == null) { return null; } String expandedString = expand(value, false); rowMapCache.put(resolvedKey, expandedString); return expandedString; } public String rowPut(String key, String value) { checkNotNull(value, "The row expression value must not be null."); String resolvedKey = resolveRowKey(key); rowMapCache.clear(); return rowMap.put( resolvedKey, value.toLowerCase(Locale.ENGLISH)); } /** * Associates the specified ColumnSpec with the specified key in this map. * If the map previously contained a mapping for this key, the old value * is replaced by the specified value. The RowSpec set in this map * override an association - if any - in the chain of parent LayoutMaps.

* * The RowSpec must not be {@code null}. To remove an association * from this map use {@link #rowRemove(String)}. * * @param key key with which the specified value is to be associated. * @param value ColumnSpec to be associated with the specified key. * @return previous ColumnSpec associated with specified key, * or {@code null} if there was no mapping for key. * * @throws NullPointerException if the {@code key} or {@code value} * is {@code null}. * * @see Map#put(Object, Object) */ public String rowPut(String key, RowSpec value) { return rowPut(key, value.encode()); } public String rowPut(String key, Size value) { return rowPut(key, value.encode()); } /** * Removes the row value mapping for this key from this map if it is * present.

* * Returns the value to which the map previously associated the key, * or {@code null} if the map contained no mapping for this key. * The map will not contain a String mapping for the specified key * once the call returns. * * @param key key whose mapping is to be removed from the map. * @return previous value associated with specified key, or {@code null} * if there was no mapping for key. * * @throws NullPointerException if {@code key} is {@code null}. * * @see Map#remove(Object) */ public String rowRemove(String key) { String resolvedKey = resolveRowKey(key); rowMapCache.clear(); return rowMap.remove(resolvedKey); } // Overriding Object Behavior ********************************************* /** * Returns a string representation of this LayoutMap that lists * the column and row associations. * * @return a string representation */ @Override public String toString() { StringBuffer buffer = new StringBuffer(super.toString()); buffer.append("\n Column associations:"); for (Entry entry : columnMap.entrySet()) { buffer.append("\n "); buffer.append(entry.getKey()); buffer.append("->"); buffer.append(entry.getValue()); } buffer.append("\n Row associations:"); for (Entry entry : rowMap.entrySet()) { buffer.append("\n "); buffer.append(entry.getKey()); buffer.append("->"); buffer.append(entry.getValue()); } return buffer.toString(); } // String Expansion ******************************************************* String expand(String expression, boolean horizontal) { int cursor = 0; int start = expression.indexOf(LayoutMap.VARIABLE_PREFIX_CHAR, cursor); if (start == -1) { // No variables return expression; } StringBuffer buffer = new StringBuffer(); do { buffer.append(expression.substring(cursor, start)); String variableName = nextVariableName(expression, start); buffer.append(expansion(variableName, horizontal)); cursor = start + variableName.length() + 1; start = expression.indexOf(LayoutMap.VARIABLE_PREFIX_CHAR, cursor); } while (start != -1); buffer.append(expression.substring(cursor)); return buffer.toString(); } private static String nextVariableName(String expression, int start) { int length = expression.length(); if (length <= start) { FormSpecParser.fail(expression, start, "Missing variable name after variable char '$'."); } if (expression.charAt(start + 1) == '{') { int end = expression.indexOf('}', start + 1); if (end == -1) { FormSpecParser.fail(expression, start, "Missing closing brace '}' for variable."); } return expression.substring(start + 1, end + 1); } int end = start + 1; while (end < length && Character.isUnicodeIdentifierPart(expression.charAt(end))) { end++; } return expression.substring(start + 1, end); } private String expansion(String variableName, boolean horizontal) { String key = stripBraces(variableName); String expansion = horizontal ? columnGet(key) : rowGet(key); if (expansion == null) { String orientation = horizontal ? "column" : "row"; throw new IllegalArgumentException("Unknown " + orientation + " layout variable \"" + key + "\""); } return expansion; } private static String stripBraces(String variableName) { return variableName.charAt(0) == '{' ? variableName.substring(1, variableName.length() - 1) : variableName; } // Helper Code ************************************************************ private static String resolveColumnKey(String key) { checkNotNull(key, "The column key must not be null."); String lowercaseKey = key.toLowerCase(Locale.ENGLISH); String defaultKey = COLUMN_ALIASES.get(lowercaseKey); return defaultKey == null ? lowercaseKey : defaultKey; } private static String resolveRowKey(String key) { checkNotNull(key, "The row key must not be null."); String lowercaseKey = key.toLowerCase(Locale.ENGLISH); String defaultKey = ROW_ALIASES.get(lowercaseKey); return defaultKey == null ? lowercaseKey : defaultKey; } private static LayoutMap createRoot() { LayoutMap map = new LayoutMap(null); // Column variables map.columnPut( "label-component-gap", new String[]{"lcg", "lcgap"}, FormSpecs.LABEL_COMPONENT_GAP_COLSPEC); map.columnPut( "related-gap", new String[]{"rg", "rgap"}, FormSpecs.RELATED_GAP_COLSPEC); map.columnPut( "unrelated-gap", new String[]{"ug", "ugap"}, FormSpecs.UNRELATED_GAP_COLSPEC); map.columnPut( "button", new String[]{"b"}, FormSpecs.BUTTON_COLSPEC); map.columnPut( "growing-button", new String[]{"gb"}, FormSpecs.GROWING_BUTTON_COLSPEC); map.columnPut( "dialog-margin", new String[]{"dm", "dmargin"}, ColumnSpec.createGap(LayoutStyle.getCurrent().getDialogMarginX())); map.columnPut( "tabbed-dialog-margin", new String[]{"tdm", "tdmargin"}, ColumnSpec.createGap(LayoutStyle.getCurrent().getTabbedDialogMarginX())); map.columnPut( "glue", FormSpecs.GLUE_COLSPEC.toShortString()); // Row variables map.rowPut( "label-component-gap", new String[]{"lcg", "lcgap"}, FormSpecs.LABEL_COMPONENT_GAP_ROWSPEC); map.rowPut( "related-gap", new String[]{"rg", "rgap"}, FormSpecs.RELATED_GAP_ROWSPEC); map.rowPut( "unrelated-gap", new String[]{"ug", "ugap"}, FormSpecs.UNRELATED_GAP_ROWSPEC); map.rowPut( "narrow-line-gap", new String[]{"nlg", "nlgap"}, FormSpecs.NARROW_LINE_GAP_ROWSPEC); map.rowPut( "line-gap", new String[]{"lg", "lgap"}, FormSpecs.LINE_GAP_ROWSPEC); map.rowPut( "paragraph-gap", new String[]{"pg", "pgap"}, FormSpecs.PARAGRAPH_GAP_ROWSPEC); map.rowPut( "dialog-margin", new String[]{"dm", "dmargin"}, RowSpec.createGap(LayoutStyle.getCurrent().getDialogMarginY())); map.rowPut( "tabbed-dialog-margin", new String[]{"tdm", "tdmargin"}, RowSpec.createGap(LayoutStyle.getCurrent().getTabbedDialogMarginY())); map.rowPut( "button", new String[]{"b"}, FormSpecs.BUTTON_ROWSPEC); map.rowPut( "glue", FormSpecs.GLUE_ROWSPEC); return map; } private void columnPut(String key, String[] aliases, ColumnSpec value) { ensureLowerCase(key); columnPut(key, value); for (String aliase : aliases) { ensureLowerCase(aliase); COLUMN_ALIASES.put(aliase, key); } } private void rowPut(String key, String[] aliases, RowSpec value) { ensureLowerCase(key); rowPut(key, value); for (String aliase : aliases) { ensureLowerCase(aliase); ROW_ALIASES.put(aliase, key); } } private static void ensureLowerCase(String str) { String lowerCase = str.toLowerCase(Locale.ENGLISH); if (!lowerCase.equals(str)) { throw new IllegalArgumentException( "The string \"" + str + "\" should be lower case."); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy