/* BasicSpinnerUI.java --
Copyright (C) 2003, 2004, 2005, 2006, Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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 2, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package javax.swing.plaf.basic;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JSpinner;
import javax.swing.LookAndFeel;
import javax.swing.Timer;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.SpinnerUI;
/**
* A UI delegate for the {@link JSpinner} component.
*
* @author Ka-Hing Cheung
*
* @since 1.4
*/
public class BasicSpinnerUI extends SpinnerUI
{
/**
* Creates a new BasicSpinnerUI
for the specified
* JComponent
*
* @param c the component (ignored).
*
* @return A new instance of {@link BasicSpinnerUI}.
*/
public static ComponentUI createUI(JComponent c)
{
return new BasicSpinnerUI();
}
/**
* Creates an editor component. Really, it just returns
* JSpinner.getEditor()
*
* @return a JComponent as an editor
*
* @see javax.swing.JSpinner#getEditor
*/
protected JComponent createEditor()
{
return spinner.getEditor();
}
/**
* Creates a LayoutManager
that layouts the sub components. The
* subcomponents are identifies by the constraint "Next", "Previous" and
* "Editor"
*
* @return a LayoutManager
*
* @see java.awt.LayoutManager
*/
protected LayoutManager createLayout()
{
return new DefaultLayoutManager();
}
/**
* Creates the "Next" button
*
* @return the next button component
*/
protected Component createNextButton()
{
JButton button = new BasicArrowButton(BasicArrowButton.NORTH);
return button;
}
/**
* Creates the "Previous" button
*
* @return the previous button component
*/
protected Component createPreviousButton()
{
JButton button = new BasicArrowButton(BasicArrowButton.SOUTH);
return button;
}
/**
* Creates the PropertyChangeListener
that will be attached by
* installListeners
. It should watch for the "editor"
* property, when it's changed, replace the old editor with the new one,
* probably by calling replaceEditor
*
* @return a PropertyChangeListener
*
* @see #replaceEditor
*/
protected PropertyChangeListener createPropertyChangeListener()
{
return new PropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent event)
{
// FIXME: Add check for enabled property change. Need to
// disable the buttons.
if ("editor".equals(event.getPropertyName()))
BasicSpinnerUI.this.replaceEditor((JComponent) event.getOldValue(),
(JComponent) event.getNewValue());
// FIXME: Handle 'font' property change
}
};
}
/**
* Called by installUI
. This should set various defaults
* obtained from UIManager.getLookAndFeelDefaults
, as well as
* set the layout obtained from createLayout
*
* @see javax.swing.UIManager#getLookAndFeelDefaults
* @see #createLayout
* @see #installUI
*/
protected void installDefaults()
{
LookAndFeel.installColorsAndFont(spinner, "Spinner.background",
"Spinner.foreground", "Spinner.font");
LookAndFeel.installBorder(spinner, "Spinner.border");
JComponent e = spinner.getEditor();
if (e instanceof JSpinner.DefaultEditor)
{
JSpinner.DefaultEditor de = (JSpinner.DefaultEditor) e;
de.getTextField().setBorder(null);
}
spinner.setLayout(createLayout());
spinner.setOpaque(true);
}
/*
* Called by installUI
, which basically adds the
* PropertyChangeListener
created by
* createPropertyChangeListener
*
* @see #createPropertyChangeListener
* @see #installUI
*/
protected void installListeners()
{
spinner.addPropertyChangeListener(listener);
}
/*
* Install listeners to the next button so that it increments the model
*/
protected void installNextButtonListeners(Component c)
{
c.addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent evt)
{
if (! spinner.isEnabled())
return;
increment();
timer.setInitialDelay(500);
timer.start();
}
public void mouseReleased(MouseEvent evt)
{
timer.stop();
}
void increment()
{
Object next = BasicSpinnerUI.this.spinner.getNextValue();
if (next != null)
BasicSpinnerUI.this.spinner.getModel().setValue(next);
}
volatile boolean mouseDown;
Timer timer = new Timer(50,
new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
increment();
}
});
});
}
/*
* Install listeners to the previous button so that it decrements the model
*/
protected void installPreviousButtonListeners(Component c)
{
c.addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent evt)
{
if (! spinner.isEnabled())
return;
decrement();
timer.setInitialDelay(500);
timer.start();
}
public void mouseReleased(MouseEvent evt)
{
timer.stop();
}
void decrement()
{
Object prev = BasicSpinnerUI.this.spinner.getPreviousValue();
if (prev != null)
BasicSpinnerUI.this.spinner.getModel().setValue(prev);
}
volatile boolean mouseDown;
Timer timer = new Timer(50,
new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
decrement();
}
});
});
}
/**
* Install this UI to the JComponent
, which in reality, is a
* JSpinner
. Calls installDefaults
,
* installListeners
, and also adds the buttons and editor.
*
* @param c DOCUMENT ME!
*
* @see #installDefaults
* @see #installListeners
* @see #createNextButton
* @see #createPreviousButton
* @see #createEditor
*/
public void installUI(JComponent c)
{
super.installUI(c);
spinner = (JSpinner) c;
installDefaults();
installListeners();
Component next = createNextButton();
Component previous = createPreviousButton();
installNextButtonListeners(next);
installPreviousButtonListeners(previous);
c.add(createEditor(), "Editor");
c.add(next, "Next");
c.add(previous, "Previous");
}
/**
* Replace the old editor with the new one
*
* @param oldEditor the old editor
* @param newEditor the new one to replace with
*/
protected void replaceEditor(JComponent oldEditor, JComponent newEditor)
{
spinner.remove(oldEditor);
spinner.add(newEditor);
}
/**
* The reverse of installDefaults
. Called by
* uninstallUI
*/
protected void uninstallDefaults()
{
spinner.setLayout(null);
}
/**
* The reverse of installListeners
, called by
* uninstallUI
*/
protected void uninstallListeners()
{
spinner.removePropertyChangeListener(listener);
}
/**
* Called when the current L&F is replaced with another one, should call
* uninstallDefaults
and uninstallListeners
as
* well as remove the next/previous buttons and the editor
*
* @param c DOCUMENT ME!
*/
public void uninstallUI(JComponent c)
{
super.uninstallUI(c);
uninstallDefaults();
uninstallListeners();
c.removeAll();
}
/** The spinner for this UI */
protected JSpinner spinner;
/** DOCUMENT ME! */
private PropertyChangeListener listener = createPropertyChangeListener();
/**
* A layout manager for the {@link JSpinner} component. The spinner has
* three subcomponents: an editor, a 'next' button and a 'previous' button.
*/
private class DefaultLayoutManager implements LayoutManager
{
/**
* Layout the spinners inner parts.
*
* @param parent The parent container
*/
public void layoutContainer(Container parent)
{
synchronized (parent.getTreeLock())
{
Insets i = parent.getInsets();
boolean l2r = parent.getComponentOrientation().isLeftToRight();
/*
-------------- --------------
| | n | | n | |
| e | - | or | - | e |
| | p | | p | |
-------------- --------------
*/
Dimension e = prefSize(editor);
Dimension n = prefSize(next);
Dimension p = prefSize(previous);
Dimension s = parent.getSize();
int x = l2r ? i.left : i.right;
int y = i.top;
int w = Math.max(p.width, n.width);
int h = (s.height - i.bottom) / 2;
int e_width = s.width - w - i.left - i.right;
if (l2r)
{
setBounds(editor, x, y, e_width, 2 * h);
x += e_width;
setBounds(next, x, y, w, h);
y += h;
setBounds(previous, x, y, w, h);
}
else
{
setBounds(next, x, y + (s.height - e.height) / 2, w, h);
y += h;
setBounds(previous, x, y + (s.height - e.height) / 2, w, h);
x += w;
y -= h;
setBounds(editor, x, y, e_width, e.height);
}
}
}
/**
* Calculates the minimum layout size.
*
* @param parent the parent.
*
* @return The minimum layout size.
*/
public Dimension minimumLayoutSize(Container parent)
{
Dimension d = new Dimension();
if (editor != null)
{
Dimension tmp = editor.getMinimumSize();
d.width += tmp.width;
d.height = tmp.height;
}
int nextWidth = 0;
int previousWidth = 0;
if (next != null)
{
Dimension tmp = next.getMinimumSize();
nextWidth = tmp.width;
}
if (previous != null)
{
Dimension tmp = previous.getMinimumSize();
previousWidth = tmp.width;
}
d.width += Math.max(nextWidth, previousWidth);
return d;
}
/**
* Returns the preferred layout size of the container.
*
* @param parent DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public Dimension preferredLayoutSize(Container parent)
{
Dimension d = new Dimension();
if (editor != null)
{
Dimension tmp = editor.getPreferredSize();
d.width += Math.max(tmp.width, 40);
d.height = tmp.height;
}
int nextWidth = 0;
int previousWidth = 0;
if (next != null)
{
Dimension tmp = next.getPreferredSize();
nextWidth = tmp.width;
}
if (previous != null)
{
Dimension tmp = previous.getPreferredSize();
previousWidth = tmp.width;
}
d.width += Math.max(nextWidth, previousWidth);
Insets insets = parent.getInsets();
d.width = d.width + insets.left + insets.right;
d.height = d.height + insets.top + insets.bottom;
return d;
}
/**
* DOCUMENT ME!
*
* @param child DOCUMENT ME!
*/
public void removeLayoutComponent(Component child)
{
if (child == editor)
editor = null;
else if (child == next)
next = null;
else if (previous == child)
previous = null;
}
/**
* DOCUMENT ME!
*
* @param name DOCUMENT ME!
* @param child DOCUMENT ME!
*/
public void addLayoutComponent(String name, Component child)
{
if ("Editor".equals(name))
editor = child;
else if ("Next".equals(name))
next = child;
else if ("Previous".equals(name))
previous = child;
}
/**
* DOCUMENT ME!
*
* @param c DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
private Dimension prefSize(Component c)
{
if (c == null)
return new Dimension();
else
return c.getPreferredSize();
}
/**
* Sets the bounds for the specified component.
*
* @param c the component.
* @param x the x-coordinate for the top-left of the component bounds.
* @param y the y-coordinate for the top-left of the component bounds.
* @param w the width of the bounds.
* @param h the height of the bounds.
*/
private void setBounds(Component c, int x, int y, int w, int h)
{
if (c != null)
c.setBounds(x, y, w, h);
}
/** The editor component. */
private Component editor;
/** The next button. */
private Component next;
/** The previous button. */
private Component previous;
}
}