Java tutorial
/* * Copyright (c) 1995 - 2008 Sun Microsystems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of Sun Microsystems nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * A application that requires the following files: * ConversionPanel.java * ConverterRangeModel.java * FollowerRangeModel.java * Unit.java */ import java.awt.Color; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.NumberFormat; import javax.swing.BorderFactory; import javax.swing.BoundedRangeModel; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JComboBox; import javax.swing.JFormattedTextField; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; import javax.swing.text.NumberFormatter; public class Converter { ConversionPanel metricPanel, usaPanel; Unit[] metricDistances = new Unit[3]; Unit[] usaDistances = new Unit[4]; final static boolean MULTICOLORED = false; // Specify the look and feel to use. Valid values: // null (use the default), "Metal", "System", "Motif", "GTK+" final static String LOOKANDFEEL = null; ConverterRangeModel dataModel = new ConverterRangeModel(); JPanel mainPane; /** * Create the ConversionPanels (one for metric, another for U.S.). I used * "U.S." because although Imperial and U.S. distance measurements are the * same, this program could be extended to include volume measurements, which * aren't the same. */ public Converter() { // Create Unit objects for metric distances, and then // instantiate a ConversionPanel with these Units. metricDistances[0] = new Unit("Centimeters", 0.01); metricDistances[1] = new Unit("Meters", 1.0); metricDistances[2] = new Unit("Kilometers", 1000.0); metricPanel = new ConversionPanel(this, "Metric System", metricDistances, dataModel); // Create Unit objects for U.S. distances, and then // instantiate a ConversionPanel with these Units. usaDistances[0] = new Unit("Inches", 0.0254); usaDistances[1] = new Unit("Feet", 0.305); usaDistances[2] = new Unit("Yards", 0.914); usaDistances[3] = new Unit("Miles", 1613.0); usaPanel = new ConversionPanel(this, "U.S. System", usaDistances, new FollowerRangeModel(dataModel)); // Create a JPanel, and add the ConversionPanels to it. mainPane = new JPanel(); mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.PAGE_AXIS)); if (MULTICOLORED) { mainPane.setOpaque(true); mainPane.setBackground(new Color(255, 0, 0)); } mainPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); mainPane.add(Box.createRigidArea(new Dimension(0, 5))); mainPane.add(metricPanel); mainPane.add(Box.createRigidArea(new Dimension(0, 5))); mainPane.add(usaPanel); mainPane.add(Box.createGlue()); resetMaxValues(true); } public void resetMaxValues(boolean resetCurrentValues) { double metricMultiplier = metricPanel.getMultiplier(); double usaMultiplier = usaPanel.getMultiplier(); int maximum = ConversionPanel.MAX; if (metricMultiplier > usaMultiplier) { maximum = (int) (ConversionPanel.MAX * (usaMultiplier / metricMultiplier)); } dataModel.setMaximum(maximum); if (resetCurrentValues) { dataModel.setDoubleValue(maximum); } } private static void initLookAndFeel() { String lookAndFeel = null; if (LOOKANDFEEL != null) { if (LOOKANDFEEL.equals("Metal")) { lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); } else if (LOOKANDFEEL.equals("System")) { lookAndFeel = UIManager.getSystemLookAndFeelClassName(); } else if (LOOKANDFEEL.equals("Motif")) { lookAndFeel = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"; } else if (LOOKANDFEEL.equals("GTK+")) { // new in 1.4.2 lookAndFeel = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel"; } else { System.err.println("Unexpected value of LOOKANDFEEL specified: " + LOOKANDFEEL); lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); } try { UIManager.setLookAndFeel(lookAndFeel); } catch (ClassNotFoundException e) { System.err.println("Couldn't find class for specified look and feel:" + lookAndFeel); System.err.println("Did you include the L&F library in the class path?"); System.err.println("Using the default look and feel."); } catch (UnsupportedLookAndFeelException e) { System.err.println("Can't use the specified look and feel (" + lookAndFeel + ") on this platform."); System.err.println("Using the default look and feel."); } catch (Exception e) { System.err.println("Couldn't get specified look and feel (" + lookAndFeel + "), for some reason."); System.err.println("Using the default look and feel."); e.printStackTrace(); } } } /** * Create the GUI and show it. For thread safety, this method should be * invoked from the event-dispatching thread. */ private static void createAndShowGUI() { // Set the look and feel. initLookAndFeel(); // Create and set up the window. JFrame frame = new JFrame("Converter"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Create and set up the content pane. Converter converter = new Converter(); converter.mainPane.setOpaque(true); // content panes must be opaque frame.setContentPane(converter.mainPane); // Display the window. frame.pack(); frame.setVisible(true); } public static void main(String[] args) { // Schedule a job for the event-dispatching thread: // creating and showing this application's GUI. javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } } /* * Copyright (c) 1995 - 2008 Sun Microsystems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of Sun Microsystems nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Works in 1.1+Swing, 1.4, and all releases in between. Used by the Converter * example. */ /** * Based on the source code for DefaultBoundedRangeModel, this class stores its * value as a double, rather than an int. The minimum value and extent are * always 0. */ class ConverterRangeModel implements BoundedRangeModel { protected ChangeEvent changeEvent = null; protected EventListenerList listenerList = new EventListenerList(); protected int maximum = 10000; protected int minimum = 0; protected int extent = 0; protected double value = 0.0; protected double multiplier = 1.0; protected boolean isAdjusting = false; public ConverterRangeModel() { } public double getMultiplier() { return multiplier; } public void setMultiplier(double multiplier) { this.multiplier = multiplier; fireStateChanged(); } public int getMaximum() { return maximum; } public void setMaximum(int newMaximum) { setRangeProperties(value, extent, minimum, newMaximum, isAdjusting); } public int getMinimum() { return (int) minimum; } public void setMinimum(int newMinimum) { System.out.println("In ConverterRangeModel setMinimum"); // Do nothing. } public int getValue() { return (int) getDoubleValue(); } public void setValue(int newValue) { setDoubleValue((double) newValue); } public double getDoubleValue() { return value; } public void setDoubleValue(double newValue) { setRangeProperties(newValue, extent, minimum, maximum, isAdjusting); } public int getExtent() { return (int) extent; } public void setExtent(int newExtent) { // Do nothing. } public boolean getValueIsAdjusting() { return isAdjusting; } public void setValueIsAdjusting(boolean b) { setRangeProperties(value, extent, minimum, maximum, b); } public void setRangeProperties(int newValue, int newExtent, int newMin, int newMax, boolean newAdjusting) { setRangeProperties((double) newValue, newExtent, newMin, newMax, newAdjusting); } public void setRangeProperties(double newValue, int unusedExtent, int unusedMin, int newMax, boolean newAdjusting) { if (newMax <= minimum) { newMax = minimum + 1; } if (Math.round(newValue) > newMax) { // allow some rounding error newValue = newMax; } boolean changeOccurred = false; if (newValue != value) { value = newValue; changeOccurred = true; } if (newMax != maximum) { maximum = newMax; changeOccurred = true; } if (newAdjusting != isAdjusting) { maximum = newMax; isAdjusting = newAdjusting; changeOccurred = true; } if (changeOccurred) { fireStateChanged(); } } /* * The rest of this is event handling code copied from * DefaultBoundedRangeModel. */ public void addChangeListener(ChangeListener l) { listenerList.add(ChangeListener.class, l); } public void removeChangeListener(ChangeListener l) { listenerList.remove(ChangeListener.class, l); } protected void fireStateChanged() { Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ChangeListener.class) { if (changeEvent == null) { changeEvent = new ChangeEvent(this); } ((ChangeListener) listeners[i + 1]).stateChanged(changeEvent); } } } } /* * Copyright (c) 1995 - 2008 Sun Microsystems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of Sun Microsystems nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Works in 1.1+Swing, 1.4, and all releases in between. Used by the Converter * example. */ /** * Implements a model whose data is actually in another model (the "source * model"). The follower model adjusts the values obtained from the source model * (or set in the follower model) to be in a different unit of measure. * */ class FollowerRangeModel extends ConverterRangeModel implements ChangeListener { ConverterRangeModel sourceModel; // the real model /** Creates a FollowerRangeModel that gets its state from sourceModel. */ public FollowerRangeModel(ConverterRangeModel sourceModel) { this.sourceModel = sourceModel; sourceModel.addChangeListener(this); } // The only method in the ChangeListener interface. public void stateChanged(ChangeEvent e) { fireStateChanged(); } public int getMaximum() { int modelMax = sourceModel.getMaximum(); double multiplyBy = sourceModel.getMultiplier() / this.getMultiplier(); return (int) (modelMax * multiplyBy); } public void setMaximum(int newMaximum) { sourceModel.setMaximum((int) (newMaximum * (this.getMultiplier() / sourceModel.getMultiplier()))); } public int getValue() { return (int) getDoubleValue(); } public void setValue(int newValue) { setDoubleValue((double) newValue); } public double getDoubleValue() { return sourceModel.getDoubleValue() * sourceModel.getMultiplier() / this.getMultiplier(); } public void setDoubleValue(double newValue) { sourceModel.setDoubleValue(newValue * this.getMultiplier() / sourceModel.getMultiplier()); } public int getExtent() { return super.getExtent(); } public void setExtent(int newExtent) { super.setExtent(newExtent); } public void setRangeProperties(int value, int extent, int min, int max, boolean adjusting) { double multiplyBy = this.getMultiplier() / sourceModel.getMultiplier(); sourceModel.setRangeProperties(value * multiplyBy, extent, min, (int) (max * multiplyBy), adjusting); } } /* * Copyright (c) 1995 - 2008 Sun Microsystems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of Sun Microsystems nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Works in 1.1+Swing, 1.4, and all releases in between. Used by the Converter * example. */ class Unit { String description; double multiplier; Unit(String description, double multiplier) { super(); this.description = description; this.multiplier = multiplier; } public String toString() { String s = "Meters/" + description + " = " + multiplier; return s; } } /* * Copyright (c) 1995 - 2008 Sun Microsystems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of Sun Microsystems nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * A 1.4 class used by the Converter example. */ class ConversionPanel extends JPanel implements ActionListener, ChangeListener, PropertyChangeListener { JFormattedTextField textField; JComboBox unitChooser; JSlider slider; ConverterRangeModel sliderModel; Converter controller; Unit[] units; String title; NumberFormat numberFormat; final static boolean MULTICOLORED = false; final static int MAX = 10000; ConversionPanel(Converter myController, String myTitle, Unit[] myUnits, ConverterRangeModel myModel) { if (MULTICOLORED) { setOpaque(true); setBackground(new Color(0, 255, 255)); } setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(myTitle), BorderFactory.createEmptyBorder(5, 5, 5, 5))); // Save arguments in instance variables. controller = myController; units = myUnits; title = myTitle; sliderModel = myModel; // Create the text field format, and then the text field. numberFormat = NumberFormat.getNumberInstance(); numberFormat.setMaximumFractionDigits(2); NumberFormatter formatter = new NumberFormatter(numberFormat); formatter.setAllowsInvalid(false); formatter.setCommitsOnValidEdit(true);// seems to be a no-op -- // aha -- it changes the value property but doesn't cause the result to // be parsed (that happens on focus loss/return, I think). // textField = new JFormattedTextField(formatter); textField.setColumns(10); textField.setValue(new Double(sliderModel.getDoubleValue())); textField.addPropertyChangeListener(this); // Add the combo box. unitChooser = new JComboBox(); for (int i = 0; i < units.length; i++) { // Populate it. unitChooser.addItem(units[i].description); } unitChooser.setSelectedIndex(0); sliderModel.setMultiplier(units[0].multiplier); unitChooser.addActionListener(this); // Add the slider. slider = new JSlider(sliderModel); sliderModel.addChangeListener(this); // Make the text field/slider group a fixed size // to make stacked ConversionPanels nicely aligned. JPanel unitGroup = new JPanel() { public Dimension getMinimumSize() { return getPreferredSize(); } public Dimension getPreferredSize() { return new Dimension(150, super.getPreferredSize().height); } public Dimension getMaximumSize() { return getPreferredSize(); } }; unitGroup.setLayout(new BoxLayout(unitGroup, BoxLayout.PAGE_AXIS)); if (MULTICOLORED) { unitGroup.setOpaque(true); unitGroup.setBackground(new Color(0, 0, 255)); } unitGroup.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); unitGroup.add(textField); unitGroup.add(slider); // Create a subpanel so the combo box isn't too tall // and is sufficiently wide. JPanel chooserPanel = new JPanel(); chooserPanel.setLayout(new BoxLayout(chooserPanel, BoxLayout.PAGE_AXIS)); if (MULTICOLORED) { chooserPanel.setOpaque(true); chooserPanel.setBackground(new Color(255, 0, 255)); } chooserPanel.add(unitChooser); chooserPanel.add(Box.createHorizontalStrut(100)); // Put everything together. setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS)); add(unitGroup); add(chooserPanel); unitGroup.setAlignmentY(TOP_ALIGNMENT); chooserPanel.setAlignmentY(TOP_ALIGNMENT); } // Don't allow this panel to get taller than its preferred size. // BoxLayout pays attention to maximum size, though most layout // managers don't. public Dimension getMaximumSize() { return new Dimension(Integer.MAX_VALUE, getPreferredSize().height); } /** * Returns the multiplier (units/meter) for the currently selected unit of * measurement. */ public double getMultiplier() { return sliderModel.getMultiplier(); } public double getValue() { return sliderModel.getDoubleValue(); } /** Updates the text field when the main data model is updated. */ public void stateChanged(ChangeEvent e) { int min = sliderModel.getMinimum(); int max = sliderModel.getMaximum(); double value = sliderModel.getDoubleValue(); NumberFormatter formatter = (NumberFormatter) textField.getFormatter(); formatter.setMinimum(new Double(min)); formatter.setMaximum(new Double(max)); textField.setValue(new Double(value)); } /** * Responds to the user choosing a new unit from the combo box. */ public void actionPerformed(ActionEvent e) { // Combo box event. Set new maximums for the sliders. int i = unitChooser.getSelectedIndex(); sliderModel.setMultiplier(units[i].multiplier); controller.resetMaxValues(false); } /** * Detects when the value of the text field (not necessarily the same number * as you'd get from getText) changes. */ public void propertyChange(PropertyChangeEvent e) { if ("value".equals(e.getPropertyName())) { Number value = (Number) e.getNewValue(); sliderModel.setDoubleValue(value.doubleValue()); } } }