com.alee.laf.combobox.WebComboBoxUI Maven / Gradle / Ivy
/*
* This file is part of WebLookAndFeel library.
*
* WebLookAndFeel library is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* WebLookAndFeel library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WebLookAndFeel library. If not, see .
*/
package com.alee.laf.combobox;
import com.alee.extended.layout.AbstractLayoutManager;
import com.alee.global.StyleConstants;
import com.alee.laf.WebLookAndFeel;
import com.alee.laf.button.WebButton;
import com.alee.laf.list.WebListUI;
import com.alee.laf.scroll.WebScrollBarUI;
import com.alee.laf.scroll.WebScrollPaneUI;
import com.alee.laf.text.WebTextFieldUI;
import com.alee.utils.LafUtils;
import com.alee.utils.SwingUtils;
import com.alee.utils.laf.ShapeProvider;
import com.alee.utils.swing.BorderMethods;
import com.alee.utils.swing.RendererListener;
import com.alee.utils.swing.WebDefaultCellEditor;
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.ListUI;
import javax.swing.plaf.ScrollBarUI;
import javax.swing.plaf.ScrollPaneUI;
import javax.swing.plaf.basic.BasicComboBoxUI;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.plaf.basic.ComboPopup;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
/**
* Custom UI for JComboBox component.
*
* @author Mikle Garin
*/
public class WebComboBoxUI extends BasicComboBoxUI implements ShapeProvider, BorderMethods
{
/**
* Default combobox renderer.
*/
protected static ListCellRenderer DEFAULT_RENDERER;
private ImageIcon expandIcon = WebComboBoxStyle.expandIcon;
private ImageIcon collapseIcon = WebComboBoxStyle.collapseIcon;
private int iconSpacing = WebComboBoxStyle.iconSpacing;
private boolean drawBorder = WebComboBoxStyle.drawBorder;
private int round = WebComboBoxStyle.round;
private int shadeWidth = WebComboBoxStyle.shadeWidth;
private boolean drawFocus = WebComboBoxStyle.drawFocus;
private boolean mouseWheelScrollingEnabled = WebComboBoxStyle.mouseWheelScrollingEnabled;
private boolean useFirstValueAsPrototype = false;
private WebButton arrow = null;
private MouseWheelListener mouseWheelListener = null;
private RendererListener rendererListener = null;
private PropertyChangeListener rendererChangeListener = null;
private PropertyChangeListener enabledStateListener = null;
@SuppressWarnings ("UnusedParameters")
public static ComponentUI createUI ( final JComponent c )
{
return new WebComboBoxUI ();
}
/**
* {@inheritDoc}
*/
@Override
public void installUI ( final JComponent c )
{
super.installUI ( c );
final JComboBox comboBox = ( JComboBox ) c;
// Default settings
SwingUtils.setOrientation ( comboBox );
comboBox.setFocusable ( true );
LookAndFeel.installProperty ( comboBox, WebLookAndFeel.OPAQUE_PROPERTY, Boolean.FALSE );
// Updating border
updateBorder ();
// Drfault renderer
if ( !( comboBox.getRenderer () instanceof WebComboBoxCellRenderer ) )
{
comboBox.setRenderer ( new WebComboBoxCellRenderer () );
}
// Rollover scrolling listener
mouseWheelListener = new MouseWheelListener ()
{
@Override
public void mouseWheelMoved ( final MouseWheelEvent e )
{
if ( mouseWheelScrollingEnabled && comboBox.isEnabled () && SwingUtils.hasFocusOwner ( comboBox ) )
{
// Changing selection in case index actually changed
final int index = comboBox.getSelectedIndex ();
final int newIndex = Math.min ( Math.max ( 0, index + e.getWheelRotation () ), comboBox.getModel ().getSize () - 1 );
if ( newIndex != index )
{
comboBox.setSelectedIndex ( newIndex );
}
}
}
};
comboBox.addMouseWheelListener ( mouseWheelListener );
// Renderer change listener
// Used to provide feedback from the renderer
rendererListener = new RendererListener ()
{
@Override
public void repaint ()
{
comboBox.repaint ();
listBox.repaint ();
}
@Override
public void revalidate ()
{
isMinimumSizeDirty = true;
comboBox.revalidate ();
listBox.revalidate ();
}
};
installRendererListener ( comboBox.getRenderer () );
rendererChangeListener = new PropertyChangeListener ()
{
@Override
public void propertyChange ( final PropertyChangeEvent e )
{
uninstallRendererListener ( e.getOldValue () );
installRendererListener ( e.getNewValue () );
}
};
comboBox.addPropertyChangeListener ( WebLookAndFeel.RENDERER_PROPERTY, rendererChangeListener );
// Enabled property change listener
// This is a workaround to allow box renderer properly inherit enabled state
enabledStateListener = new PropertyChangeListener ()
{
@Override
public void propertyChange ( final PropertyChangeEvent evt )
{
listBox.setEnabled ( comboBox.isEnabled () );
}
};
comboBox.addPropertyChangeListener ( WebLookAndFeel.ENABLED_PROPERTY, enabledStateListener );
}
/**
* {@inheritDoc}
*/
@Override
public void uninstallUI ( final JComponent c )
{
comboBox.removePropertyChangeListener ( WebLookAndFeel.ENABLED_PROPERTY, enabledStateListener );
comboBox.removePropertyChangeListener ( WebLookAndFeel.RENDERER_PROPERTY, rendererChangeListener );
uninstallRendererListener ( comboBox.getRenderer () );
rendererListener = null;
c.removeMouseWheelListener ( mouseWheelListener );
mouseWheelListener = null;
arrow = null;
super.uninstallUI ( c );
}
@Override
public void updateBorder ()
{
// Preserve old borders
if ( SwingUtils.isPreserveBorders ( comboBox ) )
{
return;
}
final Insets m = new Insets ( 0, 0, 0, 0 );
if ( drawBorder )
{
m.top += shadeWidth + 1;
m.left += shadeWidth + 1;
m.bottom += shadeWidth + 1;
m.right += shadeWidth + 1;
}
comboBox.setBorder ( LafUtils.createWebBorder ( m ) );
}
/**
* Installs RendererListener into specified renderer if possible.
*
* @param renderer RendererListener to install
*/
protected void installRendererListener ( final Object renderer )
{
if ( renderer != null && renderer instanceof WebComboBoxCellRenderer )
{
( ( WebComboBoxCellRenderer ) renderer ).addRendererListener ( rendererListener );
}
}
/**
* Uninstalls RendererListener from specified renderer if possible.
*
* @param renderer RendererListener to uninstall
*/
protected void uninstallRendererListener ( final Object renderer )
{
if ( renderer != null && renderer instanceof WebComboBoxCellRenderer )
{
( ( WebComboBoxCellRenderer ) renderer ).removeRendererListener ( rendererListener );
}
}
/**
* {@inheritDoc}
*/
@Override
protected void installComponents ()
{
comboBox.setLayout ( createLayoutManager () );
arrowButton = createArrowButton ();
comboBox.add ( arrowButton, "1,0" );
if ( arrowButton != null )
{
configureArrowButton ();
}
if ( comboBox.isEditable () )
{
addEditor ();
}
comboBox.add ( currentValuePane, "0,0" );
}
/**
* {@inheritDoc}
*/
@Override
protected ComboBoxEditor createEditor ()
{
final ComboBoxEditor editor = super.createEditor ();
final Component e = editor.getEditorComponent ();
e.addFocusListener ( new FocusAdapter ()
{
@Override
public void focusGained ( final FocusEvent e )
{
comboBox.repaint ();
}
@Override
public void focusLost ( final FocusEvent e )
{
comboBox.repaint ();
}
} );
if ( e instanceof JComponent )
{
( ( JComponent ) e ).setOpaque ( false );
}
if ( e instanceof JTextField )
{
final JTextField textField = ( JTextField ) e;
final WebTextFieldUI textFieldUI = new WebTextFieldUI ();
textFieldUI.setDrawBorder ( false );
textField.setUI ( textFieldUI );
textField.setMargin ( new Insets ( 0, 1, 0, 1 ) );
}
return editor;
}
/**
* {@inheritDoc}
*/
@Override
protected JButton createArrowButton ()
{
arrow = new WebButton ();
arrow.setUndecorated ( true );
arrow.setDrawFocus ( false );
arrow.setMoveIconOnPress ( false );
arrow.setName ( "ComboBox.arrowButton" );
arrow.setIcon ( expandIcon );
arrow.setLeftRightSpacing ( iconSpacing );
return arrow;
}
/**
* {@inheritDoc}
*/
@Override
public void configureArrowButton ()
{
super.configureArrowButton ();
if ( arrowButton != null )
{
arrowButton.setFocusable ( false );
}
}
/**
* {@inheritDoc}
*/
@Override
protected ComboPopup createPopup ()
{
return new BasicComboPopup ( comboBox )
{
@Override
protected JScrollPane createScroller ()
{
final JScrollPane scroll = super.createScroller ();
if ( WebLookAndFeel.isInstalled () )
{
scroll.setOpaque ( false );
scroll.getViewport ().setOpaque ( false );
}
final ScrollPaneUI scrollPaneUI = scroll.getUI ();
if ( scrollPaneUI instanceof WebScrollPaneUI )
{
final WebScrollPaneUI webScrollPaneUI = ( WebScrollPaneUI ) scrollPaneUI;
webScrollPaneUI.setDrawBorder ( false );
final ScrollBarUI scrollBarUI = scroll.getVerticalScrollBar ().getUI ();
if ( scrollBarUI instanceof WebScrollBarUI )
{
final WebScrollBarUI webScrollBarUI = ( WebScrollBarUI ) scrollBarUI;
webScrollBarUI.setMargin ( WebComboBoxStyle.scrollBarMargin );
webScrollBarUI.setPaintButtons ( WebComboBoxStyle.scrollBarButtonsVisible );
webScrollBarUI.setPaintTrack ( WebComboBoxStyle.scrollBarTrackVisible );
}
}
LafUtils.setScrollBarStyleId ( scroll, "combo-box" );
return scroll;
}
@Override
protected JList createList ()
{
final JList list = super.createList ();
// list.setOpaque ( false );
final ListUI listUI = list.getUI ();
if ( listUI instanceof WebListUI )
{
final WebListUI webListUI = ( WebListUI ) listUI;
webListUI.setHighlightRolloverCell ( false );
webListUI.setDecorateSelection ( false );
}
return list;
}
@Override
protected void configurePopup ()
{
super.configurePopup ();
// Button updater
addPopupMenuListener ( new PopupMenuListener ()
{
/**
* {@inheritDoc}
*/
@Override
public void popupMenuWillBecomeVisible ( final PopupMenuEvent e )
{
arrow.setIcon ( collapseIcon );
// Fix for combobox repaint on popup opening
comboBox.repaint ();
}
/**
* {@inheritDoc}
*/
@Override
public void popupMenuWillBecomeInvisible ( final PopupMenuEvent e )
{
arrow.setIcon ( expandIcon );
}
/**
* {@inheritDoc}
*/
@Override
public void popupMenuCanceled ( final PopupMenuEvent e )
{
arrow.setIcon ( expandIcon );
}
} );
}
@Override
public void show ()
{
comboBox.firePopupMenuWillBecomeVisible ();
setListSelection ( comboBox.getSelectedIndex () );
final Point location = getPopupLocation ();
show ( comboBox, location.x, location.y );
}
private void setListSelection ( final int selectedIndex )
{
if ( selectedIndex == -1 )
{
list.clearSelection ();
}
else
{
list.setSelectedIndex ( selectedIndex );
list.ensureIndexIsVisible ( selectedIndex );
}
}
private Point getPopupLocation ()
{
final Dimension comboSize = comboBox.getSize ();
comboSize.setSize ( comboSize.width - 2, getPopupHeightForRowCount ( comboBox.getMaximumRowCount () ) );
final Rectangle popupBounds = computePopupBounds ( 0, comboBox.getBounds ().height, comboSize.width, comboSize.height );
final Dimension scrollSize = popupBounds.getSize ();
scroller.setMaximumSize ( scrollSize );
scroller.setPreferredSize ( scrollSize );
scroller.setMinimumSize ( scrollSize );
list.revalidate ();
return popupBounds.getLocation ();
}
};
}
public boolean isComboboxCellEditor ()
{
if ( comboBox != null )
{
final Object cellEditor = comboBox.getClientProperty ( WebDefaultCellEditor.COMBOBOX_CELL_EDITOR );
return cellEditor != null && ( Boolean ) cellEditor;
}
else
{
return false;
}
}
/**
* {@inheritDoc}
*/
@Override
public Shape provideShape ()
{
if ( drawBorder )
{
return LafUtils.getWebBorderShape ( comboBox, shadeWidth, round );
}
else
{
return SwingUtils.size ( comboBox );
}
}
public void setEditorColumns ( final int columns )
{
if ( editor instanceof JTextField )
{
( ( JTextField ) editor ).setColumns ( columns );
}
}
public boolean isUseFirstValueAsPrototype ()
{
return useFirstValueAsPrototype;
}
public void setUseFirstValueAsPrototype ( final boolean use )
{
this.useFirstValueAsPrototype = use;
this.isMinimumSizeDirty = true;
comboBox.revalidate ();
}
public ImageIcon getExpandIcon ()
{
return expandIcon;
}
public void setExpandIcon ( final ImageIcon expandIcon )
{
this.expandIcon = expandIcon;
if ( arrow != null && !isPopupVisible ( comboBox ) )
{
arrow.setIcon ( expandIcon );
}
}
public ImageIcon getCollapseIcon ()
{
return collapseIcon;
}
public void setCollapseIcon ( final ImageIcon collapseIcon )
{
this.collapseIcon = collapseIcon;
if ( arrow != null && isPopupVisible ( comboBox ) )
{
arrow.setIcon ( collapseIcon );
}
}
public int getIconSpacing ()
{
return iconSpacing;
}
public void setIconSpacing ( final int iconSpacing )
{
this.iconSpacing = iconSpacing;
if ( arrow != null )
{
arrow.setLeftRightSpacing ( iconSpacing );
}
}
public boolean isDrawBorder ()
{
return drawBorder;
}
public void setDrawBorder ( final boolean drawBorder )
{
this.drawBorder = drawBorder;
updateBorder ();
}
public boolean isDrawFocus ()
{
return drawFocus;
}
public void setDrawFocus ( final boolean drawFocus )
{
this.drawFocus = drawFocus;
}
public int getRound ()
{
return round;
}
public void setRound ( final int round )
{
this.round = round;
}
public int getShadeWidth ()
{
return shadeWidth;
}
public void setShadeWidth ( final int shadeWidth )
{
this.shadeWidth = shadeWidth;
updateBorder ();
}
public boolean isMouseWheelScrollingEnabled ()
{
return mouseWheelScrollingEnabled;
}
public void setMouseWheelScrollingEnabled ( final boolean enabled )
{
this.mouseWheelScrollingEnabled = enabled;
}
/**
* {@inheritDoc}
*/
@Override
public void paint ( final Graphics g, final JComponent c )
{
hasFocus = comboBox.hasFocus ();
final Rectangle r = rectangleForCurrentValue ();
// Background
paintCurrentValueBackground ( g, r, hasFocus );
// Selected uneditable value
if ( !comboBox.isEditable () )
{
paintCurrentValue ( g, r, hasFocus );
}
}
/**
* {@inheritDoc}
*/
@Override
public void paintCurrentValueBackground ( final Graphics g, final Rectangle bounds, final boolean hasFocus )
{
final Graphics2D g2d = ( Graphics2D ) g;
if ( drawBorder )
{
// Border and background
comboBox.setBackground ( StyleConstants.selectedBgColor );
LafUtils.drawWebStyle ( g2d, comboBox,
drawFocus && SwingUtils.hasFocusOwner ( comboBox ) ? StyleConstants.fieldFocusColor : StyleConstants.shadeColor,
shadeWidth, round, true, !isPopupVisible ( comboBox ) );
}
else
{
// Simple background
final boolean pressed = isPopupVisible ( comboBox );
final Rectangle cb = SwingUtils.size ( comboBox );
g2d.setPaint ( new GradientPaint ( 0, shadeWidth, pressed ? StyleConstants.topSelectedBgColor : StyleConstants.topBgColor, 0,
comboBox.getHeight () - shadeWidth, pressed ? StyleConstants.bottomSelectedBgColor : StyleConstants.bottomBgColor ) );
g2d.fillRect ( cb.x, cb.y, cb.width, cb.height );
}
// Separator line
if ( comboBox.isEditable () )
{
final boolean ltr = comboBox.getComponentOrientation ().isLeftToRight ();
final Insets insets = comboBox.getInsets ();
final int lx = ltr ? comboBox.getWidth () - insets.right - arrow.getWidth () - 1 : insets.left + arrow.getWidth ();
g2d.setPaint ( comboBox.isEnabled () ? StyleConstants.borderColor : StyleConstants.disabledBorderColor );
g2d.drawLine ( lx, insets.top + 1, lx, comboBox.getHeight () - insets.bottom - 2 );
}
}
/**
* {@inheritDoc}
*/
@Override
public void paintCurrentValue ( final Graphics g, final Rectangle bounds, final boolean hasFocus )
{
final ListCellRenderer renderer = comboBox.getRenderer ();
final Component c;
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" ) );
}
c.setFont ( comboBox.getFont () );
if ( comboBox.isEnabled () )
{
c.setForeground ( comboBox.getForeground () );
c.setBackground ( comboBox.getBackground () );
}
else
{
c.setForeground ( UIManager.getColor ( "ComboBox.disabledForeground" ) );
c.setBackground ( UIManager.getColor ( "ComboBox.disabledBackground" ) );
}
boolean shouldValidate = false;
if ( c instanceof JPanel )
{
shouldValidate = true;
}
final int x = bounds.x;
final int y = bounds.y;
final int w = bounds.width;
final int h = bounds.height;
currentValuePane.paintComponent ( g, c, comboBox, x, y, w, h, shouldValidate );
}
/**
* {@inheritDoc}
*/
@Override
protected LayoutManager createLayoutManager ()
{
return new WebComboBoxLayout ();
}
/**
* {@inheritDoc}
*/
@Override
public Dimension getMinimumSize ( final JComponent c )
{
if ( !isMinimumSizeDirty )
{
return new Dimension ( cachedMinimumSize );
}
final Dimension size = getDisplaySize ();
final Insets insets = getInsets ();
// Calculate the width the button
final int buttonWidth = arrowButton.getPreferredSize ().width;
// Adjust the size based on the button width
size.height += insets.top + insets.bottom;
size.width += insets.left + insets.right + buttonWidth;
cachedMinimumSize.setSize ( size.width, size.height );
isMinimumSizeDirty = false;
return new Dimension ( size );
}
/**
* {@inheritDoc}
*/
@Override
protected Dimension getDisplaySize ()
{
Dimension result = new Dimension ();
// Use default renderer
ListCellRenderer renderer = comboBox.getRenderer ();
if ( renderer == null )
{
renderer = new DefaultListCellRenderer ();
}
final Object prototypeValue = comboBox.getPrototypeDisplayValue ();
if ( prototypeValue != null )
{
// Calculates the dimension based on the prototype value
result = getSizeForComponent ( renderer.getListCellRendererComponent ( listBox, prototypeValue, -1, false, false ) );
}
else
{
final ComboBoxModel model = comboBox.getModel ();
final int modelSize = model.getSize ();
Dimension d;
if ( modelSize > 0 )
{
if ( useFirstValueAsPrototype )
{
// Calculates the maximum height and width based on first element
final Object value = model.getElementAt ( 0 );
final Component c = renderer.getListCellRendererComponent ( listBox, value, -1, false, false );
d = getSizeForComponent ( c );
result.width = Math.max ( result.width, d.width );
result.height = Math.max ( result.height, d.height );
}
else
{
// Calculate the dimension by iterating over all the elements in the combo box list
for ( int i = 0; i < modelSize; i++ )
{
// Calculates the maximum height and width based on the largest element
final Object value = model.getElementAt ( i );
final Component c = renderer.getListCellRendererComponent ( listBox, value, -1, false, false );
d = getSizeForComponent ( c );
result.width = Math.max ( result.width, d.width );
result.height = Math.max ( result.height, d.height );
}
}
}
else
{
// Calculates the maximum height and width based on default renderer
result = getDefaultSize ();
if ( comboBox.isEditable () )
{
result.width = 100;
}
}
}
if ( comboBox.isEditable () )
{
final Dimension d = editor.getPreferredSize ();
result.width = Math.max ( result.width, d.width );
result.height = Math.max ( result.height, d.height );
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
protected Dimension getDefaultSize ()
{
// Calculates the height and width using the default text renderer
return getSizeForComponent ( getDefaultListCellRenderer ().getListCellRendererComponent ( listBox, " ", -1, false, false ) );
}
/**
* Returns default list cell renderer instance.
*
* @return default list cell renderer instance
*/
protected static ListCellRenderer getDefaultListCellRenderer ()
{
if ( DEFAULT_RENDERER == null )
{
DEFAULT_RENDERER = new WebComboBoxCellRenderer ();
}
return DEFAULT_RENDERER;
}
/**
* Returns renderer component preferred size.
*
* @param c renderer component
* @return renderer component preferred size
*/
protected Dimension getSizeForComponent ( final Component c )
{
currentValuePane.add ( c );
c.setFont ( comboBox.getFont () );
final Dimension d = c.getPreferredSize ();
currentValuePane.remove ( c );
return d;
}
/**
* Custom layout manager for WebComboBoxUI.
*/
protected class WebComboBoxLayout extends AbstractLayoutManager
{
/**
* {@inheritDoc}
*/
@Override
public Dimension preferredLayoutSize ( final Container parent )
{
return parent.getPreferredSize ();
}
/**
* {@inheritDoc}
*/
@Override
public Dimension minimumLayoutSize ( final Container parent )
{
return parent.getMinimumSize ();
}
/**
* {@inheritDoc}
*/
@Override
public void layoutContainer ( final Container parent )
{
final JComboBox cb = ( JComboBox ) parent;
final int width = cb.getWidth ();
final int height = cb.getHeight ();
if ( arrowButton != null )
{
final Insets insets = getInsets ();
final int buttonHeight = height - ( insets.top + insets.bottom );
final int buttonWidth = arrowButton.getPreferredSize ().width;
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 () );
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy