Hi,
I've been lurking around on #classpath a while ago and promised to implement
JSpinner for classpath, and now finals are done and I finally finished it.
Since the original javax.swing.Timer doesn't conform to SUN's javadoc nor even
work, and JSpinner depends on it, so I fixed^Wrewrote it as well. Tested with
sablevm and classpath cvs. Patch generated against classpath cvs.
The first diff is for Timer and fix a couple components to call the right
constructor. The rest are one diff for JSpinner and a couple added files.
I've already sent off my email to FSF and am now waiting for their respond, so
consider this email a solicitation for comments. I will resend the patches once
the necessary paperworks are done.
-khc
Index: javax/swing/Timer.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/Timer.java,v
retrieving revision 1.7
diff -u -r1.7 Timer.java
--- javax/swing/Timer.java 29 Apr 2004 07:00:34 -0000 1.7
+++ javax/swing/Timer.java 24 May 2004 03:44:54 -0000
@@ -41,138 +41,436 @@
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.Serializable;
+import java.util.Comparator;
import java.util.EventListener;
-
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+import javax.swing.SwingUtilities;
import javax.swing.event.EventListenerList;
+/**
+ * This timer class is similar to <code>java.util.Timer, except that it only
+ * uses one thread for all the timers, and timers fire events using swing's
+ * event dispatch queue.
+ * The original author is unknown, but I almost rewrote it to conform to
+ * SUN's javadoc
+ *
+ * @author Ka-Hing Cheung
+ */
public class Timer implements Serializable
{
protected EventListenerList listenerList = new EventListenerList();
-
- int ticks;
- static boolean verbose;
- boolean running;
- boolean repeat_ticks = true;
- long interval, init_delay;
-
- class Waker extends Thread
- {
- public void run()
- {
- running = true;
- try {
- sleep(init_delay);
-
- while (running)
- {
- sleep(interval);
-
- if (verbose)
- {
- System.out.println("javax.swing.Timer -> clocktick");
- }
- ticks++;
- fireActionPerformed();
-
- if (! repeat_ticks)
- break;
- }
- running = false;
- } catch (Exception e) {
- System.out.println("swing.Timer::" + e);
- }
- }
+ /**
+ * Creates a Timer which invokes the listener at specific intervals
+ *
+ * @param delay interval to fire events to listeners
+ * @param listener default listener
+ * @see #setInitialDelay
+ */
+ public Timer(int delay, ActionListener listener)
+ {
+ setDelay(delay);
+ if(listener != null)
+ addActionListener(listener);
}
+ /**
+ * Adds an <code>ActionListener</code> to the list of listeners to invoke
+ *
+ * @param listener the <code>ActionListener</code> to add
+ * @see java.awt.event.ActionListener
+ */
public void addActionListener(ActionListener listener)
{
- listenerList.add (ActionListener.class, listener);
+ listenerList.add(ActionListener.class, listener);
}
-
+
+ /**
+ * Removes an <code>ActionListener</code>
+ *
+ * @param listener the listener to remove
+ * @see java.awt.event.ActionListener
+ */
public void removeActionListener(ActionListener listener)
{
- listenerList.remove (ActionListener.class, listener);
+ listenerList.remove(ActionListener.class, listener);
}
/**
+ * Gets the listeners that are of a specific type
+ *
+ * @param listenerType the type
+ * @return the array of listeners
+ * @see #getActionListeners
* @since 1.3
*/
- public EventListener[] getListeners (Class listenerType)
+ public EventListener[] getListeners(Class listenerType)
{
- return listenerList.getListeners (listenerType);
+ return listenerList.getListeners(listenerType);
}
/**
+ * Get all the listeners that are <code>ActionListener</code>
+ * @return the listeners
+ * @see #getListeners
* @since 1.4
*/
- public ActionListener[] getActionListeners ()
+ public ActionListener[] getActionListeners()
{
return (ActionListener[]) listenerList.getListeners (ActionListener.class);
}
- protected void fireActionPerformed (ActionEvent event)
+ /**
+ * fires the specific event to the listeners
+ *
+ * @param event the event to fire
+ * @see #isCoalesce
+ * @see #addActionListener
+ */
+ protected void fireActionPerformed(ActionEvent event)
{
- ActionListener[] listeners = getActionListeners();
+ final ActionEvent evt = event;
+ synchronized(this)
+ {
+ final ActionListener[] listeners = getActionListeners();
- for (int i = 0; i < listeners.length; i++)
+ for (int i = 0; i < listeners.length; i++)
+ {
+ final int which = i;
+ if(!(eventQueued && isCoalesce()))
+ {
+ eventQueued = true;
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ listeners[which].actionPerformed(evt);
+ eventQueued = false;
+ }
+ });
+ }
+ }
+ }
+ }
+
+ /**
+ * Restarts the timer
+ *
+ * @see #start
+ * @see #stop
+ */
+ public void restart()
+ {
+ synchronized(Timer.class)
{
- listeners [i].actionPerformed (event);
+ if(!isRunning())
+ {
+ start();
+ }
+ else
+ {
+ running = false;
+ restart = true;
+ }
}
}
- void fireActionPerformed ()
+ /**
+ * Check if this timer repeatly fires.
+ *
+ * @return true if this timer repeats
+ * @see #setRepeats
+ */
+ public boolean isRepeats()
+ {
+ return repeat;
+ }
+
+ /**
+ * Check if this timer coalesce events
+ *
+ * @return true if this timer coalesce events
+ * @see #setCoalesce
+ */
+ public boolean isCoalesce()
+ {
+ return coalesce;
+ }
+
+ /**
+ * If true, then this timer coalesce events. This may happen if 1) there are
+ * too many timers 2) there are too many listeners. In that case an
+ * <code>ActionListener</code> may receive multiple
+ * <code>actionPerformed</code> with no delays between them. However, if
+ * coalesce is true, this will not happen.
+ * By default, this is true.
+ *
+ * @param flag true if this timer should coalesce events
+ * @see #fireActionPerformed
+ */
+ public void setCoalesce(boolean flag)
{
- fireActionPerformed (new ActionEvent (this, ticks, "Timer"));
+ coalesce = flag;
}
+ /**
+ * If enabled, log some output to the stdout whenever this timer ticks.
+ * By default, false.
+ *
+ * @param flag true if this timer should log whenever it ticks
+ */
public static void setLogTimers(boolean flag)
{
verbose = flag;
}
+ /**
+ * Checks if this timer should log whenever it ticks
+ *
+ * @return true if this timer logs when it ticks
+ * @see #setLogTimers
+ */
public static boolean getLogTimers()
{
return verbose;
}
-
+
+ /**
+ * Sets the time that this timer should wait before firing
+ *
+ * @param delay the delay time in millisecond
+ * @see #setInitialDelay
+ */
public void setDelay(int delay)
{
interval = delay;
}
+ /**
+ * Gets the delay time
+ *
+ * @return the delay time
+ * @see #setDelay
+ */
public int getDelay()
{
- return (int)interval;
+ return interval;
}
+ /**
+ * Sets the time that this timer should wait before firing the first time.
+ * By default this is the same as the regular delay.
+ *
+ * @param initialDelay the initial delay
+ * @see #setDelay
+ */
public void setInitialDelay(int initialDelay)
{
init_delay = initialDelay;
}
+ /**
+ * Sets if this timer should repeat. By default, true.
+ *
+ * @param flag true if this timer should repeatly fires
+ */
public void setRepeats(boolean flag)
{
- repeat_ticks = flag;
+ repeat = flag;
}
+ /**
+ * Checks if this timer is running
+ *
+ * @return true if this timer is running
+ * @see #start
+ */
public boolean isRunning()
{
return running;
}
+ /**
+ * Starts this timer if it's not started already.
+ *
+ * @see #isRunning
+ * @see #stop
+ */
public void start()
{
- if (isRunning())
+ synchronized(Timer.class)
{
- System.err.println("attempt to start a running timer");
- return;
+ if (!isRunning())
+ {
+ if(init_delay == -1)
+ init_delay = interval;
+
+ ticks = 0;
+ running = true;
+
+ putPendingTimer(this);
+ }
}
- new Waker().start();
}
+ /**
+ * Gets the initial delay
+ *
+ * @return the initial delay
+ * @see #setInitialDelay
+ */
+ public int getInitialDelay()
+ {
+ return init_delay == -1 ? interval : init_delay;
+ }
+
+ /**
+ * Stops the timer if it's running.
+ *
+ * @see #isRunning
+ * @see #start
+ */
public void stop()
{
- running = false;
+ synchronized(Timer.class)
+ {
+ if(isRunning())
+ {
+ running = false;
+ stopping = true;
+ }
+ }
}
+
+ private int ticks = 0;
+ private static boolean verbose = false;
+ private boolean running = false;
+ private boolean repeat = true;
+ private int interval, init_delay = -1;
+ private boolean coalesce = true;
+
+ private boolean eventQueued = false;
+ private boolean restart = false;
+ private boolean stopping = false;
+
+ private long nextExecTime;
+
+ private static synchronized void putPendingTimer(Timer t)
+ {
+ if(t.ticks == 0)
+ t.nextExecTime = t.getInitialDelay() + System.currentTimeMillis();
+ else
+ t.nextExecTime = t.getDelay() + System.currentTimeMillis();
+
+ allTimers.add(t);
+
+ if(waker == null)
+ {
+ waker = new Waker();
+ waker.start();
+ }
+ else
+ {
+ if(allTimers.size() == 1)
+ Timer.class.notify();
+ }
+ }
+
+ private static class Waker extends Thread
+ {
+ public void run()
+ {
+ while(true)
+ {
+ List removedTimers = new LinkedList();
+
+ synchronized(Timer.class)
+ {
+ while(allTimers.isEmpty())
+ {
+ try
+ {
+ Timer.class.wait();
+ } catch (InterruptedException _) {}
+ }
+
+ Iterator iter = allTimers.iterator();
+ while(iter.hasNext())
+ {
+ Timer t = (Timer)iter.next();
+
+ if(t.nextExecTime < System.currentTimeMillis())
+ {
+ if(t.running && !t.stopping)
+ {
+ if(verbose)
+ System.out.println("Timer ticked " + t.ticks +
+ " times.");
+
+ t.fireActionPerformed(new ActionEvent(t,
+ t.ticks,
+ "Timer"));
+ t.ticks++;
+ if(t.repeat)
+ {
+ removedTimers.add(t);
+ }
+ else
+ {
+ t.ticks = 0;
+ t.running = false;
+ }
+ }
+ else
+ {
+ t.ticks = 0;
+ if(t.restart)
+ {
+ removedTimers.add(t);
+ t.restart = false;
+ }
+ t.stopping = false;
+ }
+ iter.remove();
+ }
+ else
+ {
+ /* since the TreeSet is sorted, if it's too early to
+ execute one of the timers, then it's too early to
+ execute all the subsequent timers
+ */
+ break;
+ }
+ }
+
+ iter = removedTimers.iterator();
+ while(iter.hasNext())
+ {
+ putPendingTimer((Timer)iter.next());
+ }
+
+ }
+
+ try
+ {
+ sleep(50);
+ }
+ catch (InterruptedException _) {}
+ }
+ }
+ }
+
+ private static TreeSet allTimers = new TreeSet(new Comparator()
+ {
+ public int compare(Object a, Object b)
+ {
+ return (int)(((Timer)a).nextExecTime - ((Timer)b).nextExecTime);
+ }
+ });
+
+ private static Waker waker;
}
Index: javax/swing/plaf/basic/BasicProgressBarUI.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicProgressBarUI.java,v
retrieving revision 1.2
diff -u -r1.2 BasicProgressBarUI.java
--- javax/swing/plaf/basic/BasicProgressBarUI.java 17 Apr 2004 23:24:46 -0000
1.2
+++ javax/swing/plaf/basic/BasicProgressBarUI.java 24 May 2004 03:44:55 -0000
@@ -134,7 +134,7 @@
}
/** The timer used to move the bouncing box. */
- private transient Timer animationTimer = new Timer();
+ private transient Timer animationTimer;
// The total number of frames must be an even number.
@@ -793,7 +793,7 @@
{
progressBar = (JProgressBar) c;
- animationTimer = new Timer();
+ animationTimer = new Timer(0, null);
animationTimer.setRepeats(true);
installDefaults();
Index: javax/swing/plaf/basic/BasicScrollBarUI.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicScrollBarUI.java,v
retrieving revision 1.2
diff -u -r1.2 BasicScrollBarUI.java
--- javax/swing/plaf/basic/BasicScrollBarUI.java 29 Apr 2004 07:00:34 -0000
1.2
+++ javax/swing/plaf/basic/BasicScrollBarUI.java 24 May 2004 03:44:55 -0000
@@ -950,8 +950,7 @@
trackRect = new Rectangle();
thumbRect = new Rectangle();
- scrollTimer = new Timer();
- scrollTimer.setDelay(200);
+ scrollTimer = new Timer(200, null);
scrollTimer.setRepeats(true);
installComponents();
Index: javax/swing/plaf/basic/BasicSliderUI.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicSliderUI.java,v
retrieving revision 1.2
diff -u -r1.2 BasicSliderUI.java
--- javax/swing/plaf/basic/BasicSliderUI.java 29 Apr 2004 07:00:34 -0000 1.2
+++ javax/swing/plaf/basic/BasicSliderUI.java 24 May 2004 03:44:55 -0000
@@ -618,8 +618,7 @@
insetCache = slider.getInsets();
leftToRightCache = ! slider.getInverted();
- scrollTimer = new Timer();
- scrollTimer.setDelay(200);
+ scrollTimer = new Timer(200, null);
scrollTimer.setRepeats(true);
installDefaults(slider);
Index: javax/swing/Makefile.am
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/Makefile.am,v
retrieving revision 1.12
diff -u -r1.12 Makefile.am
--- javax/swing/Makefile.am 14 Feb 2004 22:47:01 -0000 1.12
+++ javax/swing/Makefile.am 24 May 2004 03:47:27 -0000
@@ -8,6 +8,7 @@
AbstractCellEditor.java \
AbstractListModel.java \
AbstractSet.java \
+AbstractSpinnerModel.java \
Action.java \
ActionMap.java \
BorderFactory.java \
@@ -73,6 +74,7 @@
JSeparator.java \
JSlider.java \
JSplitPane.java \
+JSpinner.java \
JTabbedPane.java \
JTable.java \
JTextField.java \
@@ -106,6 +108,7 @@
SizeSequence.java \
SingleSelectionModel.java \
SpinnerModel.java \
+SpinnerNumberModel.java \
SwingConstants.java \
SwingUtilities.java \
Timer.java \
Index: javax/swing/plaf/basic/BasicLookAndFeel.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicLookAndFeel.java,v
retrieving revision 1.8
diff -u -r1.8 BasicLookAndFeel.java
--- javax/swing/plaf/basic/BasicLookAndFeel.java 21 May 2004 07:54:34 -0000
1.8
+++ javax/swing/plaf/basic/BasicLookAndFeel.java 24 May 2004 03:47:27 -0000
@@ -130,6 +130,7 @@
"SeparatorUI", "javax.swing.plaf.basic.BasicSeparatorUI",
"SliderUI", "javax.swing.plaf.basic.BasicSliderUI",
"SplitPaneUI", "javax.swing.plaf.basic.BasicSplitPaneUI",
+ "SpinnerUI", "javax.swing.plaf.basic.BasicSpinnerUI",
"StandardDialogUI", "javax.swing.plaf.basic.BasicStandardDialogUI",
"TabbedPaneUI", "javax.swing.plaf.basic.BasicTabbedPaneUI",
"TableHeaderUI", "javax.swing.plaf.basic.BasicTableHeaderUI",
Index: javax/swing/plaf/basic/Makefile.am
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/Makefile.am,v
retrieving revision 1.6
diff -u -r1.6 Makefile.am
--- javax/swing/plaf/basic/Makefile.am 17 Apr 2004 23:24:46 -0000 1.6
+++ javax/swing/plaf/basic/Makefile.am 24 May 2004 03:47:27 -0000
@@ -23,6 +23,7 @@
BasicSliderUI.java \
BasicSplitPaneDivider.java \
BasicSplitPaneUI.java \
+BasicSpinnerUI.java \
BasicTabbedPaneUI.java \
BasicTextUI.java \
BasicToggleButtonUI.java \
/* JSpinner.java --
Copyright (C) 2002, 2004 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., 59 Temple Place, Suite 330, Boston, MA
02111-1307 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;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.ParseException;
import javax.swing.border.EtchedBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;
import javax.swing.plaf.SpinnerUI;
import java.util.EventListener;
/**
* A JSpinner is a component which typically contains a numeric value and a
* way to manipulate the value.
*
* @author Ka-Hing Cheung
* @version 1.0
*/
public class JSpinner extends JComponent
{
public static class StubEditor extends JLabel implements ChangeListener
{
public StubEditor(JSpinner spinner)
{
this.spinner = spinner;
setBorder(new EtchedBorder());
setHorizontalAlignment(SwingConstants.TRAILING);
stateChanged(null); /* fill in the label */
}
public void stateChanged(ChangeEvent evt)
{
setText(String.valueOf(spinner.getValue()));
}
private JLabel label;
private JButton up;
private JButton down;
private JSpinner spinner;
}
public static class DefaultEditor extends JPanel
implements ChangeListener, PropertyChangeListener, LayoutManager
{
public DefaultEditor(JSpinner spinner)
{
spinner.addChangeListener(this);
} /* TODO */
public void commitEdit()
{
} /* TODO */
public void dismiss(JSpinner spinner)
{
spinner.removeChangeListener(this);
}
/*
public JFormattedTextField getTextField()
{
return null;
} /* TODO */
public void layoutContainer(Container parent)
{
} /* TODO */
public Dimension minimumLayoutSize(Container parent)
{
return null;
} /* TODO */
public Dimension preferredLayoutSize(Container parent)
{
return null;
} /* TODO */
public void propertyChange(PropertyChangeEvent evt)
{
} /* TODO */
public void stateChanged(ChangeEvent evt)
{
} /* TODO */
/* no-ops */
public void removeLayoutComponent(Component child) {}
public void addLayoutComponent(String name, Component child) {}
}
/**
* Creates a JSpinner with <code>SpinnerNumberModel</code>
*
* @see javax.swing.SpinnerNumberModel
*/
public JSpinner()
{
this(new SpinnerNumberModel());
}
/**
* Creates a JSpinner with the specific model and sets the default editor
*/
public JSpinner(SpinnerModel model)
{
this.model = model;
listenerList = new EventListenerList();
model.addChangeListener(listener);
setEditor(createEditor(model));
updateUI();
}
/**
* If the editor is <code>JSpinner.DefaultEditor</code>, then forwards the
* call to it, otherwise do nothing.
*/
public void commitEdit() throws ParseException
{
if(editor instanceof DefaultEditor)
((DefaultEditor)editor).commitEdit();
}
/**
* Gets the current editor
*
* @return the current editor
* @see #setEditor
*/
public JComponent getEditor()
{
return editor;
}
/**
* Changes the current editor to the new editor. This methods should remove
* the old listeners (if any) and adds the new listeners (if any).
*
* @param editor the new editor
* @see #getEditor
*/
public void setEditor(JComponent editor)
{
if(editor == null)
throw new IllegalArgumentException("editor may not be null");
if(this.editor instanceof DefaultEditor)
((DefaultEditor)editor).dismiss(this);
else if(this.editor instanceof ChangeListener)
removeChangeListener((ChangeListener)this.editor);
if(editor instanceof ChangeListener)
addChangeListener((ChangeListener)editor);
this.editor = editor;
}
/**
* Gets the underly model.
*
* @return the underly model
*/
public SpinnerModel getModel()
{
return model;
}
/**
* Gets the next value without changing the current value.
*
* @return the next value
* @see javax.swing.SpinnerModel#getNextValue
*/
public Object getNextValue()
{
return model.getNextValue();
}
/**
* Gets the previous value without changing the current value.
*
* @return the previous value
* @see javax.swing.SpinnerModel#getPreviousValue
*/
public Object getPreviousValue()
{
return model.getPreviousValue();
}
/**
* Gets the <code>SpinnerUI</code> that handles this spinner
*
* @return the <code>SpinnerUI</code>
*/
public SpinnerUI getUI()
{
return (SpinnerUI)ui;
}
/**
* Gets the current value of the spinner, according to the underly model, not
* the UI.
*
* @return the current value
* @see javax.swing.SpinnerModel#getValue
*/
public Object getValue()
{
return model.getValue();
}
/**
* This method returns a name to identify which look and feel class will be
* the UI delegate for this spinner.
*
* @return The UIClass identifier. "SpinnerUI"
*/
public String getUIClassID()
{
return "SpinnerUI";
}
/**
* This method resets the spinner's UI delegate to the default UI for the
* current look and feel.
*/
public void updateUI()
{
setUI((SpinnerUI) UIManager.getUI(this));
}
/**
* This method sets the spinner's UI delegate.
*
* @param ui The spinner's UI delegate.
*/
public void setUI(SpinnerUI ui)
{
super.setUI(ui);
}
/**
* Adds a <code>ChangeListener</code>
*
* @param listener the listener to add
*/
public void addChangeListener(ChangeListener listener)
{
listenerList.add(ChangeListener.class, listener);
}
/**
* Remove a particular listener
*
* @param listener the listener to remove
*/
public void removeChangeListener(ChangeListener listener)
{
listenerList.remove(ChangeListener.class, listener);
}
/**
* Gets all the <code>ChangeListener</code>s
*
* @return all the <code>ChangeListener</code>s
*/
public ChangeListener[] getChangeListeners()
{
EventListener[] listeners =
listenerList.getListeners(ChangeListener.class);
ChangeListener[] ret = new ChangeListener[listeners.length];
System.arraycopy(listeners, 0, ret, 0, ret.length);
return ret;
}
/**
* Fires a <code>ChangeEvent</code> to all the <code>ChangeListener</code>s
* added to this <code>JSpinner</code>
*/
protected void fireStateChanged() {
EventListener[] listeners =
listenerList.getListeners(ChangeListener.class);
ChangeEvent evt = new ChangeEvent(this);
for(int i = listeners.length - 1; i >= 0; i--)
{
((ChangeListener)listeners[i]).stateChanged(evt);
}
}
/**
* Creates an editor for this <code>JSpinner</code>. Really, it should be a
* <code>JSpinner.DefaultEditor</code>, but since that should be implemented
* by a JFormattedTextField, and one is not written, I am just using a dummy
* one backed by a JLabel.
*
* @return the default editor
*/
protected JComponent createEditor(SpinnerModel model)
{
return new StubEditor(this);
} /* TODO */
private SpinnerModel model;
private JComponent editor;
private EventListenerList listenerList;
private ChangeListener listener = new ChangeListener()
{
public void stateChanged(ChangeEvent evt)
{
fireStateChanged();
}
};
}
/* AbstractSpinnerModel.java --
Copyright (C) 2002, 2004 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., 59 Temple Place, Suite 330, Boston, MA
02111-1307 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;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;
import java.util.EventListener;
/**
* AbstractSpinnerModel
* @author Ka-Hing Cheung
* @version 1.0
*/
public abstract class AbstractSpinnerModel implements SpinnerModel
{
protected EventListenerList listenerList;
/**
* Creates an <code>AbstractSpinnerModel
*/
public AbstractSpinnerModel()
{
listenerList = new EventListenerList();
}
/**
* Adds a <code>ChangeListener</code>
*
* @param listener the listener to add
*/
public void addChangeListener(ChangeListener listener)
{
listenerList.add(ChangeListener.class, listener);
}
/**
* Gets all the listeners that are of a particular type
*
* @param c the type of listener
* @return the listeners that are of the specific type
*/
public EventListener[] getListeners(Class c)
{
return listenerList.getListeners(c);
}
/**
* Gets all the <code>ChangeListener</code>s
*
* @return all the <code>ChangeListener</code>s
*/
public ChangeListener[] getChangeListeners()
{
EventListener[] listeners =
listenerList.getListeners(ChangeListener.class);
ChangeListener[] ret = new ChangeListener[listeners.length];
System.arraycopy(listeners, 0, ret, 0, ret.length);
return ret;
}
/**
* Remove a particular listener
*
* @param listener the listener to remove
*/
public void removeChangeListener(ChangeListener listener)
{
listenerList.remove(ChangeListener.class, listener);
}
/**
* Fires a <code>ChangeEvent</code> to all the <code>ChangeListener</code>s
* added to this model
*/
protected void fireStateChanged() {
EventListener[] listeners =
listenerList.getListeners(ChangeListener.class);
ChangeEvent evt = new ChangeEvent(this);
for(int i = listeners.length - 1; i >= 0; i--)
{
((ChangeListener)listeners[i]).stateChanged(evt);
}
}
}
/* SpinnerNumberModel.java --
Copyright (C) 2002, 2004 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., 59 Temple Place, Suite 330, Boston, MA
02111-1307 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;
/**
* SpinnerNumberModel
* @author Ka-Hing Cheung
* @version 1.0
*/
public class SpinnerNumberModel extends AbstractSpinnerModel
{
/**
* Creates a <code>SpinnerNumberModel</code> with initial value 0, step 1,
* and no maximum nor minimum.
*/
public SpinnerNumberModel()
{
this(new Integer(0), null, null, new Integer(1));
}
/**
* Creates a <code>SpinnerNumberModel</code> with double precision
*
* @param value the initial value
* @param minimum the minimum value
* @param maximum the maximum value
* @param stepSize the step size
* @throws IllegalArgumentException if minimum <= value <= maximum does not
* hold
*/
public SpinnerNumberModel(double value, double minimum, double maximum,
double stepSize)
{
this(new Double(value), new Double(minimum), new Double(maximum),
new Double(stepSize));
}
/**
* Creates a <code>SpinnerNumberModel</code> with integer precision
*
* @param value the initial value
* @param minimum the minimum value
* @param maximum the maximum value
* @param stepSize the step size
* @throws IllegalArgumentException if minimum <= value <= maximum does not
* hold
*/
public SpinnerNumberModel(int value, int minimum, int maximum, int stepSize)
{
this(new Integer(value), new Integer(minimum), new Integer(maximum),
new Integer(stepSize));
}
/**
* Creates a <code>SpinnerNumberModel</code> with <code>Number</code>s and
* <code>Comparable</code>s.
*
* @param value the initial value
* @param minimum the minimum value, if null there's no minimum
* @param maximum the maximum value, if null there's no maximum
* @param stepSize the step size
* @throws IllegalArgumentException if minimum <= value <= maximum does not
* hold
*/
public SpinnerNumberModel(Number value, Comparable minimum,
Comparable maximum, Number stepSize)
{
if(stepSize == null)
throw new IllegalArgumentException("stepSize may not be null");
if(value == null)
throw new IllegalArgumentException("value may not be null");
if(minimum != null) {
if(minimum.compareTo(value) > 0)
throw new IllegalArgumentException("minimum is not <= value");
} else {
minimum = new Comparable()
{
public int compareTo(Object obj)
{
return -1;
}
};
}
if(maximum != null) {
if(maximum.compareTo(value) < 0)
throw new IllegalArgumentException("maximum is not >= value");
} else {
maximum = new Comparable()
{
public int compareTo(Object obj)
{
return 1;
}
};
}
this.value = value;
this.stepSize = stepSize;
this.minimum = minimum;
this.maximum = maximum;
}
/**
* Sets the new value and fire a change event
*
* @param value the new value
* @throws IllegalArgumentException if minimum <= value <= maximum does not
* hold
*/
public void setValue(Object value)
{
if(value == null || !(value instanceof Number))
throw new IllegalArgumentException("value must be a Number");
this.value = (Number)value;
fireStateChanged();
}
/**
* Gets the current value
*
* @return the current value
*/
public Object getValue()
{
return value;
}
/**
* Gets the next value without changing the current value, or null if the
* current value is maximum.
*
* @return the next value
*/
public Object getNextValue()
{
Number num;
if(value instanceof Double)
{
num = new Double(value.doubleValue() + stepSize.doubleValue());
}
else if(value instanceof Float)
{
num = new Double(value.floatValue() + stepSize.floatValue());
}
else if(value instanceof Long)
{
num = new Long(value.longValue() + stepSize.longValue());
}
else if(value instanceof Integer)
{
num = new Integer(value.intValue() + stepSize.intValue());
}
else if(value instanceof Short)
{
num = new Short((short)(value.shortValue() + stepSize.shortValue()));
}
else
{
num = new Byte((byte)(value.byteValue() + stepSize.byteValue()));
}
return maximum.compareTo(num) >= 0 ? num : null;
}
/**
* Gets the previous value without changing the current value, or null if the
* current value is minimum.
*
* @return the previous value
*/
public Object getPreviousValue()
{
Number num;
if(value instanceof Double)
{
num = new Double(value.doubleValue() - stepSize.doubleValue());
}
else if(value instanceof Float)
{
num = new Double(value.floatValue() - stepSize.floatValue());
}
else if(value instanceof Long)
{
num = new Long(value.longValue() - stepSize.longValue());
}
else if(value instanceof Integer)
{
num = new Integer(value.intValue() - stepSize.intValue());
}
else if(value instanceof Short)
{
num = new Short((short)(value.shortValue() - stepSize.shortValue()));
}
else
{
num = new Byte((byte)(value.byteValue() - stepSize.byteValue()));
}
return maximum.compareTo(num) >= 0 ? num : null;
}
private Number value;
private Comparable minimum;
private Comparable maximum;
private Number stepSize;
}
/* SpinnerUI.java --
Copyright (C) 2003 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., 59 Temple Place, Suite 330, Boston, MA
02111-1307 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.MouseEvent;
import java.awt.event.MouseAdapter;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JSpinner;
import javax.swing.Timer;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.plaf.SpinnerUI;
import javax.swing.plaf.ComponentUI;
/**
* @since 1.4
* @see javax.swing.JSpinner
* @author Ka-Hing Cheung
*/
public class BasicSpinnerUI extends SpinnerUI
{
/**
* Creates a new <code>ComponentUI</code> for the specified
* <code>JComponent</code>
*
* @return a ComponentUI
*/
public static ComponentUI createUI(JComponent c)
{
return new BasicSpinnerUI();
}
/**
* Creates an editor component. Really, it just returns
* <code>JSpinner.getEditor()</code>
*
* @return a JComponent as an editor
* @see javax.swing.JSpinner#getEditor
*/
protected JComponent createEditor()
{
return spinner.getEditor();
}
/**
* Creates a <code>LayoutManager</code> 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 <code>PropertyChangeListener</code> that will be attached
* by <code>installListeners</code>. It should watch for the "editor"
* property, when it's changed, replace the old editor with the new one,
* probably by calling <code>replaceEditor</code>
*
* @return a PropertyChangeListener
* @see #replaceEditor
*/
protected PropertyChangeListener createPropertyChangeListener()
{
return new PropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent evt)
{
if("editor".equals(evt.getPropertyName()))
{
BasicSpinnerUI.this.replaceEditor((JComponent)evt.getOldValue(),
(JComponent)evt.getNewValue());
}
}
};
}
/**
* Called by <code>installUI</code>. This should set various defaults
* obtained from <code>UIManager.getLookAndFeelDefaults</code>, as well as
* set the layout obtained from <code>createLayout</code>
*
* @see #javax.swing.UIManager#getLookAndFeelDefaults
* @see #createLayout
* @see #installUI
*/
protected void installDefaults()
{
/* most of it copied from BasicLabelUI, I don't know what keys are
available, so someone may want to update this. Hence: TODO
*/
UIDefaults defaults = UIManager.getLookAndFeelDefaults();
/*
spinner.setForeground(defaults.getColor("Spinner.foreground"));
spinner.setBackground(defaults.getColor("Spinner.background"));
spinner.setFont(defaults.getFont("Spinner.font"));
spinner.setBorder(defaults.getBorder("Spinner.border"));
*/
spinner.setLayout(createLayout());
}
/*
* Called by <code>installUI</code>, which basically adds the
* <code>PropertyChangeListener</code> created by
* <code>createPropertyChangeListener</code>
*
* @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)
{
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 = false;
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)
{
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 = false;
Timer timer = new Timer(50, new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
decrement();
}
});
});
}
/**
* Install this UI to the <code>JComponent</code>, which in reality,
* is a <code>JSpinner</code>. Calls <code>installDefaults</code>,
* <code>installListeners</code>, and also adds the buttons and
* editor.
*
* @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(),
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 <code>installDefaults</code>. Called by
* <code>uninstallUI</code>
*/
protected void uninstallDefaults()
{
spinner.setLayout(null);
}
/**
* The reverse of <code>installListeners</code>, called by
* <code>uninstallUI</code>
*/
protected void uninstallListeners()
{
spinner.removePropertyChangeListener(listener);
}
/**
* Called when the current L&F is replaced with another one, should call
* <code>uninstallDefaults</code> and <code>uninstallListeners</code> as well
* as remove the next/previous buttons and the editor
*/
public void uninstallUI(JComponent c)
{
super.uninstallUI(c);
uninstallDefaults();
uninstallListeners();
c.removeAll();
}
/**
* The spinner for this UI
*/
protected JSpinner spinner;
private PropertyChangeListener listener = createPropertyChangeListener();
private class DefaultLayoutManager implements LayoutManager
{
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 = minSize(editor);
Dimension n = minSize(next);
Dimension p = minSize(previous);
Dimension s = spinner.getPreferredSize();
int x = l2r ? i.left : i.right, y = i.top;
int w = Math.max(p.width, n.width);
int h = Math.max(p.height, n.height);
h = Math.max(h, e.height / 2);
int e_width = s.width - w;
if(l2r)
{
setBounds(editor, x, y + (s.height - e.height) / 2, e_width,
e.height);
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, w, h);
x += w;
y -= h;
setBounds(editor, x, y, e_width, e.height);
}
}
}
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;
int otherHeight = 0;
if(next != null)
{
Dimension tmp = next.getMinimumSize();
nextWidth = tmp.width;
otherHeight += tmp.height;
}
if(previous != null)
{
Dimension tmp = previous.getMinimumSize();
previousWidth = tmp.width;
otherHeight += tmp.height;
}
d.height = Math.max(d.height, otherHeight);
d.width += Math.max(nextWidth, previousWidth);
return d;
}
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;
int otherHeight = 0;
if(next != null)
{
Dimension tmp = next.getPreferredSize();
nextWidth = tmp.width;
otherHeight += tmp.height;
}
if(previous != null)
{
Dimension tmp = previous.getPreferredSize();
previousWidth = tmp.width;
otherHeight += tmp.height;
}
d.height = Math.max(d.height, otherHeight);
d.width += Math.max(nextWidth, previousWidth);
return d;
}
public void removeLayoutComponent(Component child)
{
if(child == editor)
editor = null;
else if(child == next)
next = null;
else if(previous == child)
previous = null;
}
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;
}
private Dimension minSize(Component c)
{
if(c == null)
return new Dimension();
else
return c.getMinimumSize();
}
private void setBounds(Component c, int x, int y, int w, int h)
{
if(c != null)
c.setBounds(x, y, w, h);
}
private Component editor;
private Component next;
private Component previous;
}
}
_______________________________________________
Classpath mailing list
[EMAIL PROTECTED]
http://mail.gnu.org/mailman/listinfo/classpath