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

com.jgoodies.looks.windows.WindowsComboBoxUI Maven / Gradle / Ivy

Go to download

The JGoodies Looks make your Swing applications and applets look better. They have been optimized for readability, precise micro-design and usability. And they simplify the multi-platform support by using similar widget dimensions. In addition, many people have reviewed them as elegant.

The newest version!
/*
 * Copyright (c) 2001-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.looks.windows;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.ComboBoxEditor;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JTextField;
import javax.swing.ListCellRenderer;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
import javax.swing.plaf.basic.BasicComboBoxUI;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.plaf.basic.ComboPopup;

import com.jgoodies.common.base.SystemUtils;
import com.jgoodies.looks.Options;
import com.sun.java.swing.plaf.windows.WindowsTextFieldUI;

/**
 * The JGoodies Windows Look&Feel implementation of
 * {@link javax.swing.plaf.ComboBoxUI}.
 * Corrects the editor insets for editable combo boxes
 * as well as the render insets for non-editable combos. And it has
 * the same height as text fields - unless you change the renderer.

* * Also, this class offers to use the combo's popup prototype display value * to compute the popup menu width. This is an optional feature of * the JGoodies Windows L&f implemented via a client property key. * * @author Karsten Lentzsch * @version $Revision: 1.26 $ * * @see Options#COMBO_POPUP_PROTOTYPE_DISPLAY_VALUE_KEY */ public class WindowsComboBoxUI extends com.sun.java.swing.plaf.windows.WindowsComboBoxUI { private static final String CELL_EDITOR_KEY = "JComboBox.isTableCellEditor"; /** * Used to determine the minimum height of a text field, * which in turn is used to answer the combobox's minimum height. */ private static final JTextField PHANTOM = new JTextField("Phantom"); private static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0); private static final Border EMPTY_BORDER = new EmptyBorder(EMPTY_INSETS); private boolean tableCellEditor; private PropertyChangeListener propertyChangeListener; // ************************************************************************ public static ComponentUI createUI(JComponent b) { ensurePhantomHasWindowsUI(); return new WindowsComboBoxUI(); } /** * Ensures that the phantom text field has a Windows text field UI. */ private static void ensurePhantomHasWindowsUI() { if (!(PHANTOM.getUI() instanceof WindowsTextFieldUI)) { PHANTOM.updateUI(); } } // ************************************************************************ @Override public void installUI(JComponent c) { super.installUI(c); tableCellEditor = isTableCellEditor(); } @Override protected void installListeners() { super.installListeners(); propertyChangeListener = new TableCellEditorPropertyChangeHandler(); comboBox.addPropertyChangeListener(CELL_EDITOR_KEY, propertyChangeListener); } @Override protected void uninstallListeners() { super.uninstallListeners(); comboBox.removePropertyChangeListener(CELL_EDITOR_KEY, propertyChangeListener); propertyChangeListener = null; } /** * Creates the arrow button that is to be used in the combo box.

* * Overridden to paint black triangles. */ @Override protected JButton createArrowButton() { return SystemUtils.IS_LAF_WINDOWS_XP_ENABLED ? super.createArrowButton() : new WindowsArrowButton(SwingConstants.SOUTH); } /** * Creates the editor that is to be used in editable combo boxes. * This method only gets called if a custom editor has not already * been installed in the JComboBox. */ @Override protected ComboBoxEditor createEditor() { return new com.jgoodies.looks.windows.WindowsComboBoxEditor.UIResource(tableCellEditor); } /** * Creates a layout manager for managing the components which * make up the combo box.

* * Overriden to use a layout that has a fixed width arrow button. * * @return an instance of a layout manager */ @Override protected LayoutManager createLayoutManager() { return new WindowsComboBoxLayoutManager(); } @Override protected void configureEditor() { super.configureEditor(); if (!comboBox.isEnabled()) { editor.setBackground(UIManager.getColor("ComboBox.disabledBackground")); } } /** * Creates a ComboPopup that honors the optional combo popup display value * that is used to compute the popup menu width. */ @Override protected ComboPopup createPopup() { return new WindowsComboPopup(comboBox); } /** * Creates the default renderer that will be used in a non-editiable combo * box. A default renderer will used only if a renderer has not been * explicitly set with {@code setRenderer}.

* * This method differs from the superclass implementation in that * it uses an empty border with the default left and right text insets, * the same as used by a combo box editor. * * @return a {@code ListCellRender} used for the combo box * @see javax.swing.JComboBox#setRenderer */ @Override protected ListCellRenderer createRenderer() { if (tableCellEditor) { return super.createRenderer(); } BasicComboBoxRenderer renderer = new BasicComboBoxRenderer.UIResource(); renderer.setBorder(UIManager.getBorder("ComboBox.rendererBorder")); return renderer; } /** * The minumum size is the size of the display area plus insets plus the button. */ @Override public Dimension getMinimumSize(JComponent c) { if (!isMinimumSizeDirty) { return new Dimension(cachedMinimumSize); } Dimension size = getDisplaySize(); Insets insets = getInsets(); size.height += insets.top + insets.bottom; int buttonWidth = getEditableButtonWidth(); size.width += insets.left + insets.right + buttonWidth; // The combo editor benefits from extra space for the caret. // To make editable and non-editable equally wide, // we always add 1 pixel. size.width += 1; // Honor corrections made in #paintCurrentValue ListCellRenderer renderer = comboBox.getRenderer(); if (renderer instanceof JComponent) { JComponent component = (JComponent) renderer; Insets rendererInsets = component.getInsets(); Insets editorInsets = UIManager.getInsets("ComboBox.editorInsets"); int offsetLeft = Math.max(0, editorInsets.left - rendererInsets.left); int offsetRight = Math.max(0, editorInsets.right - rendererInsets.right); // int offsetTop = Math.max(0, editorInsets.top - rendererInsets.top); // int offsetBottom = Math.max(0, editorInsets.bottom - rendererInsets.bottom); size.width += offsetLeft + offsetRight; //size.height += offsetTop + offsetBottom; } // The height is oriented on the JTextField height Dimension textFieldSize = PHANTOM.getMinimumSize(); size.height = SystemUtils.IS_OS_WINDOWS_6_OR_LATER && !SystemUtils.IS_LAF_WINDOWS_XP_ENABLED ? textFieldSize.height : Math.max(textFieldSize.height, size.height); cachedMinimumSize.setSize(size.width, size.height); isMinimumSizeDirty = false; return new Dimension(size); } /** * Delegates to #getMinimumSize(Component). * Overridden to return the same result in JDK 1.5 as in JDK 1.4. */ @Override public Dimension getPreferredSize(JComponent c) { return getMinimumSize(c); } /** * Paints the currently selected item. */ @Override public void paintCurrentValue(Graphics g, Rectangle bounds, boolean hasFocus) { ListCellRenderer renderer = comboBox.getRenderer(); Component c; boolean isVistaReadOnlyCombo = isVistaXPStyleReadOnlyCombo(); if (hasFocus && !isPopupVisible(comboBox)) { c = renderer.getListCellRendererComponent(listBox, comboBox.getSelectedItem(), -1, true, false); } else { c = renderer.getListCellRendererComponent(listBox, comboBox.getSelectedItem(), -1, false, false ); c.setBackground(UIManager.getColor("ComboBox.background")); } Border oldBorder = null; Rectangle originalBounds = new Rectangle(bounds); if (c instanceof JComponent && !tableCellEditor) { JComponent component = (JComponent) c; if (isRendererBorderRemovable(component)) { oldBorder = component.getBorder(); component.setBorder(EMPTY_BORDER); //new WindowsBorders.DashedBorder(c.getForeground(), 1)); } Insets rendererInsets = component.getInsets(); Insets editorInsets = UIManager.getInsets("ComboBox.editorInsets"); int offsetLeft = Math.max(0, editorInsets.left - rendererInsets.left); int offsetRight = Math.max(0, editorInsets.right - rendererInsets.right); int offsetTop = Math.max(0, editorInsets.top - rendererInsets.top); int offsetBottom = Math.max(0, editorInsets.bottom - rendererInsets.bottom); bounds.x += offsetLeft; bounds.y += offsetTop; bounds.width -= offsetLeft + offsetRight - 1; bounds.height -= offsetTop + offsetBottom; } c.setFont(comboBox.getFont()); if (hasFocus && !isPopupVisible(comboBox) && !isVistaReadOnlyCombo) { c.setForeground(listBox.getSelectionForeground()); c.setBackground(listBox.getSelectionBackground()); } else { if (comboBox.isEnabled()) { c.setForeground(comboBox.getForeground()); c.setBackground(comboBox.getBackground()); } else { c.setForeground(UIManager.getColor("ComboBox.disabledForeground")); c.setBackground(UIManager.getColor("ComboBox.disabledBackground")); } } // Fix for 4238829: should lay out the JPanel. boolean shouldValidate = c instanceof JPanel; Boolean oldOpaque = null; if (isVistaReadOnlyCombo && c instanceof JComponent && !(c instanceof DefaultListCellRenderer)) { oldOpaque = Boolean.valueOf(c.isOpaque()); ((JComponent) c).setOpaque(false); } currentValuePane.paintComponent(g, c, comboBox, bounds.x, bounds.y, bounds.width, bounds.height, shouldValidate); if (hasFocus) { Color oldColor = g.getColor(); g.setColor(comboBox.getForeground()); if (isVistaReadOnlyCombo) { int width = originalBounds.width - 2; if (width % 2 == 0) { width += 1; } WindowsUtils.drawRoundedDashedRect(g, originalBounds.x+1, originalBounds.y+1, width, originalBounds.height-2); } /*else { BasicGraphicsUtils.drawDashedRect(g, bounds.x, bounds.y, bounds.width, bounds.height); }*/ g.setColor(oldColor); } if (oldOpaque != null) { ((JComponent) c).setOpaque(oldOpaque.booleanValue()); } if (oldBorder != null) { ((JComponent) c).setBorder(oldBorder); } } /** * Checks and answer whether the border of the given renderer component * can be removed temporarily, so the combo's selection background will * be consistent with the default renderer and native appearance. * This test is invoked from {@code #paintCurrentValue}.

* * It is safe to remove an EmptyBorder if the component doesn't override * {@code #update}, {@code #paint} and {@code #paintBorder}. * Since we know the default renderer, we can remove its border.

* * Custom renderers may set a hint to make their border removable. * To do so, set the client property "isBorderRemovable" * to {@code Boolean.TRUE}. If this client property is set, * its value will be returned. If it is not set, {@code true} is returned * if and only if the component's border is an EmptyBorder. * * @param rendererComponent the renderer component to check * @return true if the component's border can be removed, false if not * @see #paintCurrentValue(Graphics, Rectangle, boolean) */ protected boolean isRendererBorderRemovable(JComponent rendererComponent) { if (rendererComponent instanceof BasicComboBoxRenderer.UIResource) { return true; } Object hint = rendererComponent.getClientProperty(Options.COMBO_RENDERER_IS_BORDER_REMOVABLE); if (hint != null) { return Boolean.TRUE.equals(hint); } Border border = rendererComponent.getBorder(); return border instanceof EmptyBorder; } private boolean isVistaXPStyleReadOnlyCombo() { return SystemUtils.IS_OS_WINDOWS_6_OR_LATER && SystemUtils.IS_LAF_WINDOWS_XP_ENABLED && !comboBox.isEditable(); } /** * Returns the area that is reserved for drawing the currently selected item. */ @Override protected Rectangle rectangleForCurrentValue() { int width = comboBox.getWidth(); int height = comboBox.getHeight(); Insets insets = getInsets(); int buttonWidth = getEditableButtonWidth(); if (arrowButton != null) { buttonWidth = arrowButton.getWidth(); } if (comboBox.getComponentOrientation().isLeftToRight()) { return new Rectangle( insets.left, insets.top, width - (insets.left + insets.right + buttonWidth), height - (insets.top + insets.bottom)); } return new Rectangle( insets.left + buttonWidth, insets.top , width - (insets.left + insets.right + buttonWidth), height - (insets.top + insets.bottom)); } // Helper Code ************************************************************ /** * Computes and returns the width of the arrow button in editable state. * * @return the width of the arrow button in editable state */ private static int getEditableButtonWidth() { return UIManager.getInt("ScrollBar.width"); } /** * Checks and answers if this UI's combo has a client property * that indicates that the combo is used as a table cell editor. * * @return {@code true} if the table cell editor client property * is set to {@code Boolean.TRUE}, {@code false} otherwise */ private boolean isTableCellEditor() { return Boolean.TRUE.equals(comboBox.getClientProperty(CELL_EDITOR_KEY)); } // Collaborator Classes *************************************************** /** * This layout manager handles the 'standard' layout of combo boxes. * It puts the arrow button to the right and the editor to the left. * If there is no editor it still keeps the arrow button to the right. * * Overriden to use a fixed arrow button width. */ private final class WindowsComboBoxLayoutManager extends BasicComboBoxUI.ComboBoxLayoutManager { @Override public void layoutContainer(Container parent) { JComboBox cb = (JComboBox) parent; int width = cb.getWidth(); int height = cb.getHeight(); Insets insets = getInsets(); int buttonWidth = getEditableButtonWidth(); int buttonHeight = height - (insets.top + insets.bottom); if (arrowButton != null) { if (cb.getComponentOrientation().isLeftToRight()) { arrowButton.setBounds( width - (insets.right + buttonWidth), insets.top, buttonWidth, buttonHeight); } else { arrowButton.setBounds( insets.left, insets.top, buttonWidth, buttonHeight); } } if (editor != null) { editor.setBounds(rectangleForCurrentValue()); } } } /** * Differs from the BasicComboPopup in that it uses the standard * popmenu border and honors an optional popup prototype display value. */ private static final class WindowsComboPopup extends BasicComboPopup { private WindowsComboPopup(JComboBox combo) { super(combo); } /** * Calculates the placement and size of the popup portion * of the combo box based on the combo box location and * the enclosing screen bounds. If no transformations are required, * then the returned rectangle will have the same values * as the parameters.

* * In addition to the superclass behavior, this class offers * to use the combo's popup prototype display value to compute * the popup menu width. This is an optional feature of * the JGoodies Windows L&f implemented via a client property key.

* * If a prototype is set, the popup width is the maximum of the * combobox width and the prototype based popup width. * For the latter the renderer is used to render the prototype. * The prototype based popup width is the prototype's width * plus the scrollbar width - if any. The scrollbar test checks * if there are more items than the combo's maximum row count. * * @param px starting x location * @param py starting y location * @param pw starting width * @param ph starting height * @return a rectangle which represents the placement and size of the popup * * @see Options#COMBO_POPUP_PROTOTYPE_DISPLAY_VALUE_KEY * @see JComboBox#getMaximumRowCount() */ @Override protected Rectangle computePopupBounds(int px, int py, int pw, int ph) { Rectangle defaultBounds = super.computePopupBounds(px, py, pw, ph); Object popupPrototypeDisplayValue = comboBox.getClientProperty( Options.COMBO_POPUP_PROTOTYPE_DISPLAY_VALUE_KEY); if (popupPrototypeDisplayValue == null) { return defaultBounds; } ListCellRenderer renderer = list.getCellRenderer(); Component c = renderer.getListCellRendererComponent( list, popupPrototypeDisplayValue, -1, true, true); pw = c.getPreferredSize().width; boolean hasVerticalScrollBar = comboBox.getItemCount() > comboBox.getMaximumRowCount(); if (hasVerticalScrollBar) { // Add the scrollbar width. JScrollBar verticalBar = scroller.getVerticalScrollBar(); pw += verticalBar.getPreferredSize().width; } Rectangle prototypeBasedBounds = super.computePopupBounds(px, py, pw, ph); return prototypeBasedBounds.width > defaultBounds.width ? prototypeBasedBounds : defaultBounds; } } // Handling Combo Changes ************************************************* /** * Listens to changes in the table cell editor client property * and updates the default editor - if any - to use the correct * insets for this case. */ private final class TableCellEditorPropertyChangeHandler implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { tableCellEditor = isTableCellEditor(); if (comboBox.getRenderer() == null || comboBox.getRenderer() instanceof UIResource) { comboBox.setRenderer(createRenderer()); } if (comboBox.getEditor() == null || comboBox.getEditor() instanceof UIResource) { comboBox.setEditor(createEditor()); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy