/*******************************************************************************
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Tiny Look and Feel * * (C) Copyright 2003 - 2007 Hans Bickel * * For
* licensing information and credits, please refer to the * comment in file
* de.muntjak.tinylookandfeel.TinyLookAndFeel * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
package de.muntjak.tinylookandfeel;
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.Container;
import java.awt.FocusTraversalPolicy;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.text.AttributedCharacterIterator;
import java.text.CharacterIterator;
import java.text.DateFormat;
import java.text.Format;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JSpinner;
import javax.swing.SpinnerDateModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicSpinnerUI;
import javax.swing.text.InternationalFormatter;
/**
* TinySpinnerUI
*
* @version 1.0
* @author Hans Bickel
*/
@SuppressWarnings (
{ "all" } )
public class TinySpinnerUI extends BasicSpinnerUI
{
/**
* The mouse/action listeners that are added to the spinner's arrow buttons.
* These listeners are shared by all spinner arrow buttons.
*
* @see #createNextButton
* @see #createPreviousButton
*/
private static final ArrowButtonHandler nextButtonHandler = new ArrowButtonHandler (
"increment", true );
private static final ArrowButtonHandler previousButtonHandler = new ArrowButtonHandler (
"decrement", false );
public static ComponentUI createUI ( JComponent c )
{
return new TinySpinnerUI ();
}
protected Component createPreviousButton ()
{
JButton b = new SpecialUIButton ( new TinySpinnerButtonUI (
SwingConstants.SOUTH ) );
b.putClientProperty ( "isSpinnerButton", Boolean.TRUE );
b.setFocusable ( false );
b.addActionListener ( previousButtonHandler );
b.addMouseListener ( previousButtonHandler );
return b;
}
protected Component createNextButton ()
{
JButton b = new SpecialUIButton ( new TinySpinnerButtonUI (
SwingConstants.NORTH ) );
b.putClientProperty ( "isSpinnerButton", Boolean.TRUE );
b.setFocusable ( false );
b.addActionListener ( nextButtonHandler );
b.addMouseListener ( nextButtonHandler );
return b;
}
/**
* Copy and paste from BasicSpinnerUI - sigh ! A handler for spinner arrow
* button mouse and action events. When a left mouse pressed event occurs we
* look up the (enabled) spinner that's the source of the event and start the
* autorepeat timer. The timer fires action events until any button is
* released at which point the timer is stopped and the reference to the
* spinner cleared. The timer doesn't start until after a 300ms delay, so
* often the source of the initial (and final) action event is just the button
* logic for mouse released - which means that we're relying on the fact that
* our mouse listener runs after the buttons mouse listener.
* <p>
* Note that one instance of this handler is shared by all slider previous
* arrow buttons and likewise for all of the next buttons, so it doesn't have
* any state that persists beyond the limits of a single button
* pressed/released gesture.
*/
private static class ArrowButtonHandler extends AbstractAction implements
MouseListener
{
final javax.swing.Timer autoRepeatTimer;
final boolean isNext;
JSpinner spinner = null;
ArrowButtonHandler ( String name, boolean isNext )
{
super ( name );
this.isNext = isNext;
autoRepeatTimer = new javax.swing.Timer ( 20, this );
autoRepeatTimer.setInitialDelay ( 300 );
}
private JSpinner eventToSpinner ( AWTEvent e )
{
Object src = e.getSource ();
while ( ( src instanceof Component ) && ! ( src instanceof JSpinner ) )
{
src = ( ( Component ) src ).getParent ();
}
return ( src instanceof JSpinner ) ? ( JSpinner ) src : null;
}
public void actionPerformed ( ActionEvent e )
{
JSpinner spinner = this.spinner;
if ( ! ( e.getSource () instanceof javax.swing.Timer ) )
{
// Most likely resulting from being in ActionMap.
spinner = eventToSpinner ( e );
}
if ( spinner != null )
{
try
{
int calendarField = getCalendarField ( spinner );
spinner.commitEdit ();
if ( calendarField != -1 )
{
( ( SpinnerDateModel ) spinner.getModel () )
.setCalendarField ( calendarField );
}
Object value = isNext ? spinner.getNextValue () : spinner
.getPreviousValue ();
if ( value != null )
{
spinner.setValue ( value );
select ( spinner );
}
}
catch ( IllegalArgumentException iae )
{
UIManager.getLookAndFeel ().provideErrorFeedback ( spinner );
}
catch ( ParseException pe )
{
UIManager.getLookAndFeel ().provideErrorFeedback ( spinner );
}
}
}
/**
* If the spinner's editor is a DateEditor, this selects the field
* associated with the value that is being incremented.
*/
private void select ( JSpinner spinner )
{
JComponent editor = spinner.getEditor ();
if ( editor instanceof JSpinner.DateEditor )
{
JSpinner.DateEditor dateEditor = ( JSpinner.DateEditor ) editor;
JFormattedTextField ftf = dateEditor.getTextField ();
Format format = dateEditor.getFormat ();
Object value;
if ( format != null && ( value = spinner.getValue () ) != null )
{
SpinnerDateModel model = dateEditor.getModel ();
DateFormat.Field field = DateFormat.Field.ofCalendarField ( model
.getCalendarField () );
if ( field != null )
{
try
{
AttributedCharacterIterator iterator = format
.formatToCharacterIterator ( value );
if ( !select ( ftf, iterator, field )
&& field == DateFormat.Field.HOUR0 )
{
select ( ftf, iterator, DateFormat.Field.HOUR1 );
}
}
catch ( IllegalArgumentException iae )
{
}
}
}
}
}
/**
* Selects the passed in field, returning true if it is found, false
* otherwise.
*/
private boolean select ( JFormattedTextField ftf,
AttributedCharacterIterator iterator, DateFormat.Field field )
{
int max = ftf.getDocument ().getLength ();
iterator.first ();
do
{
Map attrs = iterator.getAttributes ();
if ( attrs != null && attrs.containsKey ( field ) )
{
int start = iterator.getRunStart ( field );
int end = iterator.getRunLimit ( field );
if ( start != -1 && end != -1 && start <= max && end <= max )
{
ftf.select ( start, end );
}
return true;
}
}
while ( iterator.next () != CharacterIterator.DONE );
return false;
}
/**
* Returns the calendarField under the start of the selection, or -1 if
* there is no valid calendar field under the selection (or the spinner
* isn't editing dates.
*/
private int getCalendarField ( JSpinner spinner )
{
JComponent editor = spinner.getEditor ();
if ( editor instanceof JSpinner.DateEditor )
{
JSpinner.DateEditor dateEditor = ( JSpinner.DateEditor ) editor;
JFormattedTextField ftf = dateEditor.getTextField ();
int start = ftf.getSelectionStart ();
JFormattedTextField.AbstractFormatter formatter = ftf.getFormatter ();
if ( formatter instanceof InternationalFormatter )
{
Format.Field[] fields = ( ( InternationalFormatter ) formatter )
.getFields ( start );
for ( int counter = 0 ; counter < fields.length ; counter++ )
{
if ( fields [ counter ] instanceof DateFormat.Field )
{
int calendarField;
if ( fields [ counter ] == DateFormat.Field.HOUR1 )
{
calendarField = Calendar.HOUR;
}
else
{
calendarField = ( ( DateFormat.Field ) fields [ counter ] )
.getCalendarField ();
}
if ( calendarField != -1 )
{
return calendarField;
}
}
}
}
}
return -1;
}
public void mousePressed ( MouseEvent e )
{
if ( SwingUtilities.isLeftMouseButton ( e )
&& e.getComponent ().isEnabled () )
{
spinner = eventToSpinner ( e );
autoRepeatTimer.start ();
focusSpinnerIfNecessary ();
}
}
public void mouseReleased ( MouseEvent e )
{
autoRepeatTimer.stop ();
spinner = null;
}
public void mouseClicked ( MouseEvent e )
{
}
public void mouseEntered ( MouseEvent e )
{
}
public void mouseExited ( MouseEvent e )
{
}
/**
* Requests focus on a child of the spinner if the spinner doesn't have
* focus.
*/
private void focusSpinnerIfNecessary ()
{
Component fo = KeyboardFocusManager.getCurrentKeyboardFocusManager ()
.getFocusOwner ();
if ( spinner.isRequestFocusEnabled ()
&& ( fo == null || !SwingUtilities.isDescendingFrom ( fo, spinner ) ) )
{
Container root = spinner;
if ( !root.isFocusCycleRoot () )
{
root = root.getFocusCycleRootAncestor ();
}
if ( root != null )
{
FocusTraversalPolicy ftp = root.getFocusTraversalPolicy ();
Component child = ftp.getComponentAfter ( root, spinner );
if ( child != null
&& SwingUtilities.isDescendingFrom ( child, spinner ) )
{
child.requestFocus ();
}
}
}
}
}
}