/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Liquid Look and Feel * * * * Author, Miroslav Lazarevic * * * * For licensing information and credits, please refer to the * * comment in file com.birosoft.liquid.LiquidLookAndFeel * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ package com.birosoft.liquid; import java.awt.AWTEvent; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; 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.border.EmptyBorder; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicSpinnerUI; import javax.swing.text.InternationalFormatter; public class LiquidSpinnerUI 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 LiquidSpinnerUI(); } protected Component createPreviousButton() { JButton b = new SpecialUIButton(new LiquidSpinnerButtonUI( SwingConstants.SOUTH)); b.setFocusable(false); b.addActionListener(previousButtonHandler); b.addMouseListener(previousButtonHandler); return b; } protected Component createNextButton() { JButton b = new SpecialUIButton(new LiquidSpinnerButtonUI( SwingConstants.NORTH)); b.setFocusable(false); b.addActionListener(nextButtonHandler); b.addMouseListener(nextButtonHandler); return b; } /** * @see javax.swing.plaf.basic.BasicSpinnerUI#createEditor() */ protected JComponent createEditor() { JComponent editor = super.createEditor(); if (editor instanceof JSpinner.DefaultEditor) { JSpinner.DefaultEditor de = (JSpinner.DefaultEditor) editor; // This is pretty silly: But it's the only way I've found (so far) // to find the real size of the editor de.getTextField().setBorder(new EmptyBorder(0, 0, 0, 0)); //half,0,diff-half,0)); Dimension prefSize = de.getPreferredSize(); int compHeight = prefSize.height; int height = LiquidSpinnerButtonUI.getSkin().getVsize() * 2; int diff = height - compHeight; if (diff > 0) { int half = diff / 2; de.getTextField().setBorder(new EmptyBorder(half, 0, diff - half, 0)); } } return editor; } /** * 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 { /** * */ private static final long serialVersionUID = 1151070756396883763L; 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(60, 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(); } } } } } }