/* * Universal Media Server, for streaming any media to DLNA * compatible renderers based on the http://www.ps3mediaserver.org. * Copyright (C) 2012 UMS developers. * * This program is a 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; version 2 * of the License only. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package net.pms.newgui.components; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.text.NumberFormat; import java.text.ParseException; import javax.swing.FocusManager; import javax.swing.JComponent; import javax.swing.JFormattedTextField; import javax.swing.JSpinner; import javax.swing.JTextField; import javax.swing.JToolTip; import javax.swing.SpinnerDateModel; import javax.swing.SpinnerListModel; import javax.swing.SpinnerModel; import javax.swing.SpinnerNumberModel; import javax.swing.text.DefaultFormatterFactory; import javax.swing.text.NumberFormatter; import net.pms.PMS; /** * A subclass of JSpinner that implements support for {@link SpinnerIntModel} * and mouse wheel scrolling when {@link SpinnerIntModel} is used. * * @author Nadahar */ @SuppressWarnings({ "serial", "rawtypes" }) public class CustomJSpinner extends javax.swing.JSpinner { /** * Creates a new <code>CustomJSpinner</code> with default instance of * {@link SpinnerIntModel} (that is, a model with value 0, step size 1, and * no upper or lower limit). * * @see SpinnerIntModel */ public CustomJSpinner(boolean enterMoveFocus) { this(new SpinnerIntModel(0), enterMoveFocus); } /** * Creates a new <code>JSpinner with the specified model. The * {@link #createEditor(SpinnerModel)} method is used to create an editor * that is suitable for the model. * * @param model * the model (<code>null</code> not permitted). * * @throws NullPointerException * if <code>model</code> is <code>null</code>. */ public CustomJSpinner(SpinnerModel model, boolean enterMoveFocus) { super(model); if (model instanceof SpinnerIntModel) { this.addMouseWheelListener(new MouseWheelRoll( this, ((SpinnerIntModel) model).getMinimum(), ((SpinnerIntModel) model).getMaximum(), ((SpinnerIntModel) model).getStepSize() )); } if (enterMoveFocus) { if (this.getEditor() instanceof CustomJSpinner.IntegerEditor) { ((CustomJSpinner.IntegerEditor) this.getEditor()).getTextField().addKeyListener(new KeyListener() { @Override public void keyTyped(KeyEvent e) {} @Override public void keyReleased(KeyEvent e) {} @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { FocusManager.getCurrentManager().focusNextComponent(); } } }); } } } protected JComponent createEditor(SpinnerModel model) { if (model instanceof SpinnerDateModel) { return new DateEditor(this); } else if (model instanceof SpinnerNumberModel) { return new NumberEditor(this); } else if (model instanceof SpinnerListModel) { return new ListEditor(this); } else if (model instanceof SpinnerIntModel) { return new IntegerEditor(this); } else { return new DefaultEditor(this); } } public JToolTip createToolTip() { JToolTip tip = new HyperLinkToolTip(); tip.setComponent(this); return tip; } protected static class MouseWheelRoll implements MouseWheelListener { private int minimum, maximum, stepSize; private CustomJSpinner spinner; private MouseWheelRoll(CustomJSpinner spinner, int minimum, int maximum, int stepSize) { this.spinner = spinner; this.minimum = minimum; this.maximum = maximum; this.stepSize = stepSize; } @Override public void mouseWheelMoved(MouseWheelEvent e) { if (!spinner.isEnabled()) { return; } if (e.getScrollType() != MouseWheelEvent.WHEEL_UNIT_SCROLL) { return; } int value = (Integer) spinner.getValue(); value -= e.getWheelRotation() * stepSize; if (e.getWheelRotation() < 0 && value == minimum + stepSize && minimum % stepSize != 0) { value = ((minimum / stepSize) * stepSize) + stepSize; } else if (e.getWheelRotation() > 0 && value == maximum - stepSize && maximum % stepSize != 0) { value = (maximum / stepSize) * stepSize; } value = Math.min(Math.max(value, minimum),maximum); spinner.setValue(value); } } /** * An editor for a <code>JSpinner</code> whose model is a * <code>SpinnerIntModel</code>. The value of the editor is * displayed with a <code>JFormattedTextField</code> whose format * is defined by a <code>NumberFormatter</code> instance whose * <code>minimum</code> and <code>maximum</code> properties * are mapped to the <code>SpinnerIntModel</code>. */ public static class IntegerEditor extends DefaultEditor { /** * Construct a <code>JSpinner</code> editor that supports displaying * and editing the value of a <code>SpinnerIntModel</code> * with a <code>JFormattedTextField</code>. <code>This</code> * <code>IntegerEditor</code> becomes both a <code>ChangeListener</code> * on the spinner and a <code>PropertyChangeListener</code> * on the new <code>JFormattedTextField</code>. * * @param spinner the spinner whose model <code>this</code> editor will monitor * @exception IllegalArgumentException if the spinners model is not * an instance of <code>SpinnerIntModel</code> * * @see #getModel * @see #getFormat * @see SpinnerIntModel */ public IntegerEditor(JSpinner spinner) { this(spinner, NumberFormat.getIntegerInstance(PMS.getLocale())); } /** * Construct a <code>JSpinner</code> editor that supports displaying * and editing the value of a <code>SpinnerIntModel</code> * with a <code>JFormattedTextField</code>. <code>This</code> * <code>IntegerEditor</code> becomes both a <code>ChangeListener</code> * on the spinner and a <code>PropertyChangeListener</code> * on the new <code>JFormattedTextField</code>. * * @param spinner the spinner whose model <code>this</code> editor will monitor * @param format the <code>NumberFormat</code> object that's used to display * and parse the value of the text field. * @exception IllegalArgumentException if the spinners model is not * an instance of <code>SpinnerIntModel</code> * * @see #getTextField * @see SpinnerIntModel * @see java.text.DecimalFormat */ private IntegerEditor(JSpinner spinner, NumberFormat format) { super(spinner); if (!(spinner.getModel() instanceof SpinnerIntModel)) { throw new IllegalArgumentException( "model not a SpinnerIntModel"); } format.setGroupingUsed(false); format.setMaximumFractionDigits(0); SpinnerIntModel model = (SpinnerIntModel)spinner.getModel(); NumberFormatter formatter = new IntegerEditorFormatter(model, format); DefaultFormatterFactory factory = new DefaultFormatterFactory(formatter); JFormattedTextField ftf = getTextField(); ftf.setEditable(true); ftf.setFormatterFactory(factory); ftf.setHorizontalAlignment(JTextField.RIGHT); try { String minString = formatter.valueToString(model.getMinimum()); String maxString = formatter.valueToString(model.getMaximum()); // Trying to approximate the width difference between "m" and "0" by multiplying with 0.7 ftf.setColumns((int) Math.round(0.7 * Math.max(maxString.length(), minString.length()))); } catch (ParseException e) { // Nothing to do, the component width will simply be the default } } /** * This subclass of javax.swing.NumberFormatter maps the minimum/maximum * properties to a SpinnerIntModel and initializes the valueClass * of the NumberFormatter to match the type of the initial models value. */ private static class IntegerEditorFormatter extends NumberFormatter { private final SpinnerIntModel model; IntegerEditorFormatter(SpinnerIntModel model, NumberFormat format) { super(format); this.model = model; setValueClass(model.getValue().getClass()); } public Comparable getMinimum() { return model.getMinimum(); } public Comparable getMaximum() { return model.getMaximum(); } } /** * Return our spinner ancestor's <code>SpinnerIntModel</code>. * * @return <code>getSpinner().getModel()</code> * @see #getSpinner * @see #getTextField */ public SpinnerIntModel getModel() { return (SpinnerIntModel)(getSpinner().getModel()); } } }