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

com.jgoodies.forms.util.DefaultUnitConverter 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.util;

import static com.jgoodies.common.base.Preconditions.checkNotBlank;
import static com.jgoodies.common.internal.Messages.MUST_NOT_BE_BLANK;

import java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.UIManager;

/**
 * This is the default implementation of the {@link UnitConverter} interface.
 * It converts horizontal and vertical dialog base units to pixels.

* * The horizontal base unit is equal to the average width, in pixels, * of the characters in the system font; the vertical base unit is equal * to the height, in pixels, of the font. * Each horizontal base unit is equal to 4 horizontal dialog units; * each vertical base unit is equal to 8 vertical dialog units.

* * The DefaultUnitConverter computes dialog base units using a default font * and a test string for the average character width. You can configure * the font and the test string via the bound Bean properties * defaultDialogFont and averageCharacterWidthTestString. * See also Microsoft's suggestion for a custom computation * custom computation. * More information how to use dialog units in screen design can be found * in Microsoft's * Design * Specifications and Guidelines.

* * Since the Forms 1.1 this converter logs font information at * the {@code CONFIG} level. * * @version $Revision: 1.23 $ * @author Karsten Lentzsch * @see UnitConverter * @see com.jgoodies.forms.layout.Size * @see com.jgoodies.forms.layout.Sizes */ public final class DefaultUnitConverter extends AbstractUnitConverter { public static final String PROPERTY_AVERAGE_CHARACTER_WIDTH_TEST_STRING = "averageCharacterWidthTestString"; public static final String PROPERTY_DEFAULT_DIALOG_FONT = "defaultDialogFont"; /** * @since 1.6 */ public static final String OLD_AVERAGE_CHARACTER_TEST_STRING = "X"; /** * @since 1.4 */ public static final String MODERN_AVERAGE_CHARACTER_TEST_STRING = "abcdefghijklmnopqrstuvwxyz0123456789"; /** * @since 1.4 */ public static final String BALANCED_AVERAGE_CHARACTER_TEST_STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private static final Logger LOGGER = Logger.getLogger(DefaultUnitConverter.class.getName()); /** * Holds the sole instance that will be lazily instantiated. */ private static DefaultUnitConverter instance; /** * Holds the string that is used to compute the average character width. * Since 1.6 the default value is the balanced average character test * string, where it was just "X" before. */ private String averageCharWidthTestString = BALANCED_AVERAGE_CHARACTER_TEST_STRING; /** * Holds a custom font that is used to compute the global dialog base units. * If not set, a fallback font is is lazily created in method * #getCachedDefaultDialogFont, which in turn looks up a font * in method #lookupDefaultDialogFont. */ private Font defaultDialogFont; // Cached ***************************************************************** /** * Holds the lazily created cached global dialog base units that are used * if a component is not (yet) available - for example in a Border. */ private DialogBaseUnits cachedGlobalDialogBaseUnits = null; /** * Holds the horizontal dialog base units that are valid * for the FontMetrics stored in {@code cachedFontMetrics}. */ private DialogBaseUnits cachedDialogBaseUnits = null; /** * Holds the FontMetrics used to compute the per-component dialog units. * The latter are valid, if a FontMetrics equals this stored metrics. */ private FontMetrics cachedFontMetrics = null; /** * Holds a cached default dialog font that is used as fallback, * if no default dialog font has been set. * * @see #getDefaultDialogFont() * @see #setDefaultDialogFont(Font) */ private Font cachedDefaultDialogFont = null; // Instance Creation and Access ******************************************* /** * Constructs a DefaultUnitConverter and registers * a listener that handles changes in the look&feel. */ private DefaultUnitConverter() { } /** * Lazily instantiates and returns the sole instance. * * @return the lazily instantiated sole instance */ public static DefaultUnitConverter getInstance() { if (instance == null) { instance = new DefaultUnitConverter(); } return instance; } // Access to Bound Properties ********************************************* /** * Returns the string used to compute the average character width. * By default it is initialized to * {@link #BALANCED_AVERAGE_CHARACTER_TEST_STRING}. * * @return the test string used to compute the average character width */ public String getAverageCharacterWidthTestString() { return averageCharWidthTestString; } /** * Sets a string that will be used to compute the average character width. * By default it is initialized to * {@link #BALANCED_AVERAGE_CHARACTER_TEST_STRING}. You can provide * other test strings, for example: *

    *
  • "Xximeee"
  • *
  • "ABCEDEFHIJKLMNOPQRSTUVWXYZ"
  • *
  • "abcdefghijklmnopqrstuvwxyz"
  • *
* * @param newTestString the test string to be used * @throws NullPointerException if {@code newTestString} is {@code null} * @throws IllegalArgumentException if {@code newTestString} is empty or whitespace */ public void setAverageCharacterWidthTestString(String newTestString) { checkNotBlank(newTestString, MUST_NOT_BE_BLANK, "test string"); String oldTestString = averageCharWidthTestString; averageCharWidthTestString = newTestString; firePropertyChange( PROPERTY_AVERAGE_CHARACTER_WIDTH_TEST_STRING, oldTestString, newTestString); } /** * Returns the dialog font that is used to compute the dialog base units. * If a default dialog font has been set using * {@link #setDefaultDialogFont(Font)}, this font will be returned. * Otherwise a cached fallback will be lazily created. * * @return the font used to compute the dialog base units */ public Font getDefaultDialogFont() { return defaultDialogFont != null ? defaultDialogFont : getCachedDefaultDialogFont(); } /** * Sets a dialog font that will be used to compute the dialog base units. * * @param newFont the default dialog font to be set */ public void setDefaultDialogFont(Font newFont) { Font oldFont = defaultDialogFont; // Don't use the getter defaultDialogFont = newFont; clearCache(); firePropertyChange(PROPERTY_DEFAULT_DIALOG_FONT, oldFont, newFont); } // Implementing Abstract Superclass Behavior ****************************** /** * Returns the cached or computed horizontal dialog base units. * * @param component a Component that provides the font and graphics * @return the horizontal dialog base units */ @Override protected double getDialogBaseUnitsX(Component component) { return getDialogBaseUnits(component).x; } /** * Returns the cached or computed vertical dialog base units * for the given component. * * @param component a Component that provides the font and graphics * @return the vertical dialog base units */ @Override protected double getDialogBaseUnitsY(Component component) { return getDialogBaseUnits(component).y; } // Compute and Cache Global and Components Dialog Base Units ************** /** * Lazily computes and answer the global dialog base units. * Should be re-computed if the l&f, platform, or screen changes. * * @return a cached DialogBaseUnits object used globally if no container is available */ private DialogBaseUnits getGlobalDialogBaseUnits() { if (cachedGlobalDialogBaseUnits == null) { cachedGlobalDialogBaseUnits = computeGlobalDialogBaseUnits(); } return cachedGlobalDialogBaseUnits; } /** * Looks up and returns the dialog base units for the given component. * In case the component is {@code null} the global dialog base units * are answered.

* * Before we compute the dialog base units we check whether they * have been computed and cached before - for the same component * {@code FontMetrics}. * * @param c the component that provides the graphics object * @return the DialogBaseUnits object for the given component */ private DialogBaseUnits getDialogBaseUnits(Component c) { FormUtils.ensureValidCache(); if (c == null) { // || (font = c.getFont()) == null) { // logInfo("Missing font metrics: " + c); return getGlobalDialogBaseUnits(); } FontMetrics fm = c.getFontMetrics(getDefaultDialogFont()); if (fm.equals(cachedFontMetrics)) { return cachedDialogBaseUnits; } DialogBaseUnits dialogBaseUnits = computeDialogBaseUnits(fm); cachedFontMetrics = fm; cachedDialogBaseUnits = dialogBaseUnits; return dialogBaseUnits; } /** * Computes and returns the horizontal dialog base units. * Honors the font, font size and resolution.

* * Implementation Note: 14dluY map to 22 pixel for 8pt Tahoma on 96 dpi. * I could not yet manage to compute the Microsoft compliant font height. * Therefore this method adds a correction value that seems to work * well with the vast majority of desktops.

* * TODO: Revise the computation of vertical base units as soon as * there are more information about the original computation * in Microsoft environments. * * @param metrics the FontMetrics used to measure the dialog font * @return the horizontal and vertical dialog base units */ private DialogBaseUnits computeDialogBaseUnits(FontMetrics metrics) { double averageCharWidth = computeAverageCharWidth(metrics, averageCharWidthTestString); int ascent = metrics.getAscent(); double height = ascent > 14 ? ascent : ascent + (15 - ascent) / 3; DialogBaseUnits dialogBaseUnits = new DialogBaseUnits(averageCharWidth, height); if (LOGGER.isLoggable(Level.CONFIG)) { LOGGER.config( "Computed dialog base units " + dialogBaseUnits + " for: " + metrics.getFont()); } return dialogBaseUnits; } /** * Computes the global dialog base units. The current implementation * assumes a fixed 8pt font and on 96 or 120 dpi. A better implementation * should ask for the main dialog font and should honor the current * screen resolution.

* * Should be re-computed if the l&f, platform, or screen changes. * * @return a DialogBaseUnits object used globally if no container is available */ private DialogBaseUnits computeGlobalDialogBaseUnits() { LOGGER.config("Computing global dialog base units..."); Font dialogFont = getDefaultDialogFont(); FontMetrics metrics = createDefaultGlobalComponent().getFontMetrics(dialogFont); DialogBaseUnits globalDialogBaseUnits = computeDialogBaseUnits(metrics); return globalDialogBaseUnits; } /** * Lazily creates and returns a fallback for the dialog font * that is used to compute the dialog base units. * This fallback font is cached and will be reset if the L&F changes. * * @return the cached fallback font used to compute the dialog base units */ private Font getCachedDefaultDialogFont() { FormUtils.ensureValidCache(); if (cachedDefaultDialogFont == null) { cachedDefaultDialogFont = lookupDefaultDialogFont(); } return cachedDefaultDialogFont; } /** * Looks up and returns the font used by buttons. * First, tries to request the button font from the UIManager; * if this fails a JButton is created and asked for its font. * * @return the font used for a standard button */ private static Font lookupDefaultDialogFont() { Font buttonFont = UIManager.getFont("Button.font"); return buttonFont != null ? buttonFont : new JButton().getFont(); } /** * Creates and returns a component that is used to lookup the default * font metrics. The current implementation creates a {@code JPanel}. * Since this panel has no parent, it has no toolkit assigned. And so, * requesting the font metrics will end up using the default toolkit * and its deprecated method {@code ToolKit#getFontMetrics()}.

* * TODO: Consider publishing this method and providing a setter, so that * an API user can set a realized component that has a toolkit assigned. * * @return a component used to compute the default font metrics */ private static Component createDefaultGlobalComponent() { return new JPanel(null); } /** * Invalidates the caches. Resets the global dialog base units, * clears the Map from {@code FontMetrics} to dialog base units, * and resets the fallback for the default dialog font. * This is invoked after a change of the look&feel. */ void clearCache() { cachedGlobalDialogBaseUnits = null; cachedFontMetrics = null; cachedDefaultDialogFont = null; } // Helper Code ************************************************************ /** * Describes horizontal and vertical dialog base units. */ private static final class DialogBaseUnits { final double x; final double y; DialogBaseUnits(double dialogBaseUnitsX, double dialogBaseUnitsY) { this.x = dialogBaseUnitsX; this.y = dialogBaseUnitsY; } @Override public String toString() { return "DBU(x=" + x + "; y=" + y + ")"; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy