/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* 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.FocusEvent;
import java.awt.event.FocusListener;
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.ButtonModel;
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.UIResource;
import javax.swing.plaf.basic.BasicSpinnerUI;
import javax.swing.text.InternationalFormatter;
/**
* TinySpinnerUI
*
* @version 1.0
* @author Hans Bickel
*/
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();
}
}
}
}
}
}