/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.codename1.ui.spinner;
import com.codename1.ui.Component;
import com.codename1.ui.Display;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.List;
import com.codename1.ui.TextArea;
import com.codename1.ui.TextField;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.list.DefaultListCellRenderer;
import com.codename1.ui.list.ListCellRenderer;
import com.codename1.ui.list.ListModel;
import com.codename1.ui.plaf.Style;
import com.codename1.ui.plaf.UIManager;
import java.util.Calendar;
import java.util.Date;
/**
* A spinner allows us to select a numeric, date or time value using the arrow keys
* in a similar way to a list or a combo box.
*
* @author Shai Almog
* @deprecated use the Numeric/Date/Time spinners instead
*/
class Spinner extends List {
/**
* Value for create date renderer represnting Day-Month-4 Digit Year
*/
public static final int DATE_FORMAT_DD_MM_YYYY = 1;
/**
* Value for create date renderer represnting Month-Day-4 Digit Year
*/
public static final int DATE_FORMAT_MM_DD_YYYY = 2;
/**
* Value for create date renderer represnting Day-Month-2 Digit Year
*/
public static final int DATE_FORMAT_DD_MM_YY = 11;
/**
* Value for create date renderer represnting Month-Day-2 Digit Year
*/
public static final int DATE_FORMAT_MM_DD_YY = 12;
/**
* Value for create date renderer represnting Day Of Week, Month, Day
*/
public static final int DATE_FORMAT_DOW_MON_DD = 13;
/**
* Value for create date renderer represnting Day Of Week, Month, Day, Year
*/
public static final int DATE_FORMAT_DOW_MON_DD_YY = 14;
/**
* The image appearing on the side of the spinner widget to indicate its "spinnability"
*/
private static Image spinnerHandle;
private long lastKeyInteraction = -1;
private TextField quickType = new TextField();
private boolean monthFirst;
private int currentInputAlign = LEFT;
private static int inputSkipDelay = 2000;
private boolean ios7Mode;
/**
* Creates a new time spinner instance, time is an integer represented in seconds
* since mindnight
*
* @param min lowest value allowed in seconds since midnight
* @param max maximum value allowed in seconds since midnight
* @param currentValue the starting value in seconds since midnight
* @param step increments in the spinner (in seconds)
* @param twentyFourHours show the value as 24 hour values or AM/PM
* @param showSeconds show the value of the seconds as well or hide it
* @return new spinner instance
* @deprecated use TimeSpinner
*/
public static Spinner createTime(int min, int max, int currentValue, int step, boolean twentyFourHours, boolean showSeconds) {
Spinner s = new Spinner(new SpinnerNumberModel(min, max, currentValue, step),
DateTimeRenderer.createTimeRenderer(twentyFourHours, showSeconds));
return s;
}
/**
* Creates a new date spinner instance
*
* @param min lowest value allowed
* @param max maximum value allowed
* @param currentValue the starting value for the mode
* @param separatorChar character to separate the entries during rendering
* @param format formatting type for the field
* @return new spinner instance
* @deprecated use DateSpinner
*/
public static Spinner createDate(long min, long max, long currentValue, char separatorChar, int format) {
Spinner s = new Spinner(new SpinnerDateModel(min, max, currentValue), DateTimeRenderer.createDateRenderer(separatorChar, format));
s.monthFirst = format == DATE_FORMAT_MM_DD_YY || format == DATE_FORMAT_MM_DD_YYYY;
return s;
}
/**
* Creates a new numeric spinner instance
*
* @param min lowest value allowed
* @param max maximum value allowed
* @param currentValue the starting value for the mode
* @param step the value by which we increment the entries in the model
* @return new spinner instance
* @deprecated use NumericSpinner
*/
public static Spinner create(int min, int max, int currentValue, int step) {
Spinner s = new Spinner(new SpinnerNumberModel(min, max, currentValue, step), new SpinnerRenderer<Object>());
s.setRenderingPrototype(new Integer(max * 10));
return s;
}
/**
* Creates a new numeric spinner instance
*
* @param min lowest value allowed
* @param max maximum value allowed
* @param currentValue the starting value for the mode
* @param step the value by which we increment the entries in the model
* @return new spinner instance
* @deprecated use NumericSpinner
*/
public static Spinner create(double min, double max, double currentValue, double step) {
Spinner s = new Spinner(new SpinnerNumberModel(min, max, currentValue, step), new SpinnerRenderer<Object>() {
public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected) {
if(value != null && value instanceof Double) {
// round the number in the spinner to two digits
double d = ((Double)value).doubleValue();
long l = (long)d;
long r = (long)(d * 100);
r %= 100;
if(r == 0) {
value = "" + l;
} else {
value = "" + l + "." + r;
}
}
return super.getListCellRendererComponent(list, value, index, isSelected);
}
});
s.setRenderingPrototype(new Double(max * 10));
return s;
}
/**
* {@inheritDoc}
*/
protected boolean isStickyDrag() {
return true;
}
/**
* Creates a new spinner instance with the given spinner model
*
* @param spinner model such as SpinnerDateModel or SpinnerNumberModel
*/
Spinner(ListModel model, ListCellRenderer rendererInstance) {
super(model);
ios7Mode = UIManager.getInstance().isThemeConstant("ios7SpinnerBool", false);
if(ios7Mode) {
super.setMinElementHeight(6);
}
SpinnerRenderer.iOS7Mode = ios7Mode;
setRenderer(rendererInstance);
setUIID("Spinner");
setFixedSelection(FIXED_CENTER);
setOrientation(VERTICAL);
setInputOnFocus(false);
setIsScrollVisible(false);
initSpinnerRenderer();
quickType.setReplaceMenu(false);
quickType.setInputModeOrder(new String[]{"123"});
quickType.setFocus(true);
quickType.setRTL(false);
quickType.setAlignment(LEFT);
quickType.setConstraint(TextField.NUMERIC);
setIgnoreFocusComponentWhenUnfocused(true);
setRenderingPrototype(model.getItemAt(model.getSize() - 1));
if (getRenderer() instanceof DateTimeRenderer) {
quickType.setColumns(2);
}
}
void initSpinnerRenderer() {
DefaultListCellRenderer render = ((DefaultListCellRenderer) super.getRenderer());
render.setRTL(false);
setRTL(false);
render.setShowNumbers(false);
render.setUIID("SpinnerRenderer");
Component bgFocus = render.getListFocusComponent(this);
bgFocus.setUIID("SpinnerRendererFocus");
bgFocus.getSelectedStyle().setBgTransparency(0);
bgFocus.getUnselectedStyle().setBgTransparency(0);
render.setAlwaysRenderSelection(true);
}
void updateToDefaultRTL() {
boolean r = getUIManager().getLookAndFeel().isRTL();
DefaultListCellRenderer render = ((DefaultListCellRenderer) super.getRenderer());
render.setRTL(r);
setRTL(r);
}
/**
* {@inheritDoc}
*/
protected void initComponent() {
getComponentForm().registerAnimated(this);
boolean n = UIManager.getInstance().isThemeConstant("spinnerFocusBool", false);
setIgnoreFocusComponentWhenUnfocused(!n);
}
/**
* {@inheritDoc}
*/
protected void deinitialize() {
getComponentForm().deregisterAnimated(this);
}
/**
* {@inheritDoc}
*/
protected Dimension calcScrollSize() {
return super.calcPreferredSize();
}
/**
* {@inheritDoc}
*/
protected Dimension calcPreferredSize() {
int boxWidth = 0;
int verticalPadding = getStyle().getVerticalPadding();
int horizontalPadding = getStyle().getHorizontalPadding();
Object prototype = getRenderingPrototype();
int selectedHeight;
ListCellRenderer renderer = getRenderer();
Component cmp;
if (prototype != null) {
cmp = renderer.getListCellRendererComponent(this, prototype, 0, true);
} else {
if (getModel().getSize() > 0) {
cmp = renderer.getListCellRendererComponent(this, getModel().getItemAt(0), 0, true);
} else {
cmp = renderer.getListCellRendererComponent(this, null, 0, true);
}
}
selectedHeight = cmp.getPreferredH();
if(spinnerHandle != null) {
if(spinnerHandle.getHeight() > selectedHeight) {
selectedHeight = spinnerHandle.getHeight();
}
boxWidth += spinnerHandle.getWidth();
}
Dimension d;
if(Display.getInstance().isTouchScreenDevice()) {
if(ios7Mode) {
d = new Dimension(cmp.getPreferredW() + boxWidth + horizontalPadding, (selectedHeight * 8 + verticalPadding));
} else {
d = new Dimension(cmp.getPreferredW() + boxWidth + horizontalPadding, (selectedHeight * getUIManager().getThemeConstant("spinnerElementsInt", 3) + verticalPadding));
}
} else {
d = new Dimension(cmp.getPreferredW() + boxWidth + horizontalPadding, (selectedHeight + verticalPadding));
}
Style style = getStyle();
if(style.getBorder() != null) {
d.setWidth(Math.max(style.getBorder().getMinimumWidth(), d.getWidth()));
d.setHeight(Math.max(style.getBorder().getMinimumHeight(), d.getHeight()));
}
return d;
}
/**
* {@inheritDoc}
*/
public void keyPressed(int code) {
int game = Display.getInstance().getGameAction(code);
if (game > 0) {
super.keyPressed(code);
} else {
quickType.keyPressed(code);
lastKeyInteraction = System.currentTimeMillis();
}
}
/**
* Set the value of the spinner to a number or a date based on the spinner type
*
* @param o a number or a date
*/
public void setValue(Object o) {
ListModel m = getModel();
if (m instanceof SpinnerDateModel) {
((SpinnerDateModel) m).setValue((Date) o);
} else {
if(m instanceof SpinnerNumberModel) {
((SpinnerNumberModel) m).setValue(o);
} else {
int size = m.getSize();
for(int iter = 0 ; iter < size ; iter++) {
Object c = m.getItemAt(iter);
if(c == o || c != null && c.equals(o)) {
m.setSelectedIndex(iter);
return;
}
}
}
}
}
/**
* Returns the value of the spinner to a number or a date based on the spinner type
*
* @return a number or a date
*/
public Object getValue() {
ListModel m = getModel();
if (m instanceof SpinnerDateModel) {
return ((SpinnerDateModel) m).getValue();
}
return ((SpinnerNumberModel) m).getValue();
}
/**
* {@inheritDoc}
*/
public void keyReleased(int code) {
int game = Display.getInstance().getGameAction(code);
if (game > 0) {
super.keyReleased(code);
} else {
try {
quickType.keyReleased(code);
lastKeyInteraction = System.currentTimeMillis();
String t = quickType.getText();
if(t.length() == 0) {
return;
}
if (getRenderer() instanceof DateTimeRenderer) {
// is this is a time input or a date input?
if(getModel() instanceof SpinnerNumberModel) {
int time = ((Integer)getValue()).intValue();
int seconds = time % 60;
int minutes = time / 60;
int hours = minutes / 60;
minutes %= 60;
switch (currentInputAlign) {
case LEFT:
hours = Integer.parseInt(t);
if(((DateTimeRenderer)getRenderer()).isTwentyFourHours()) {
if(hours > 24) {
return;
}
} else {
if(hours > 12) {
return;
}
}
break;
case CENTER:
minutes = Integer.parseInt(t);
if(minutes > 59) {
return;
}
break;
case RIGHT:
seconds = Integer.parseInt(t);
if(seconds > 59) {
return;
}
break;
}
int actual = seconds + minutes * 60 + hours * 60 * 60;
setValue(new Integer(actual));
// update the spinner positioning if we have two characters
if (quickType.getText().length() > 1) {
quickType.setText("");
switch (currentInputAlign) {
case LEFT:
currentInputAlign = CENTER;
break;
case CENTER:
if(((DateTimeRenderer)getRenderer()).isShowSeconds()) {
currentInputAlign = RIGHT;
} else {
currentInputAlign = LEFT;
lastKeyInteraction = -1;
}
break;
case RIGHT:
currentInputAlign = LEFT;
lastKeyInteraction = -1;
break;
}
}
} else {
Calendar c = Calendar.getInstance();
c.setTime((Date) getValue());
switch (currentInputAlign) {
case LEFT:
if (monthFirst) {
c.set(Calendar.MONTH, Integer.parseInt(t) - 1);
} else {
c.set(Calendar.DAY_OF_MONTH, Integer.parseInt(t));
}
break;
case CENTER:
if (monthFirst) {
c.set(Calendar.DAY_OF_MONTH, Integer.parseInt(t));
} else {
c.set(Calendar.MONTH, Integer.parseInt(t) - 1);
}
break;
case RIGHT:
int y = c.get(Calendar.YEAR);
c.set(Calendar.YEAR, y - (y % 100) + Integer.parseInt(t));
break;
}
setValue(c.getTime());
// update the spinner positioning if we have two characters
if (quickType.getText().length() > 1) {
quickType.setText("");
switch (currentInputAlign) {
case LEFT:
currentInputAlign = CENTER;
break;
case CENTER:
currentInputAlign = RIGHT;
break;
case RIGHT:
currentInputAlign = LEFT;
lastKeyInteraction = -1;
break;
}
}
}
return;
} else {
SpinnerNumberModel n = (SpinnerNumberModel) getModel();
if (n.realValues) {
double val = Double.parseDouble(t);
if(val > ((SpinnerNumberModel)getModel()).getMax() ||
val < ((SpinnerNumberModel)getModel()).getMin()) {
return;
}
setValue(new Double(val));
} else {
int val = Integer.parseInt(t);
if(val > ((SpinnerNumberModel)getModel()).getMax() ||
val < ((SpinnerNumberModel)getModel()).getMin()) {
return;
}
setValue(new Integer(val));
}
}
int modelSize = getModel().getSize();
for (int iter = 0; iter < modelSize; iter++) {
String v = getModel().getItemAt(iter).toString();
if (v.startsWith(t)) {
setSelectedIndex(iter);
return;
}
}
// easier to ignore exceptions than build "proper" error handling
} catch(IllegalArgumentException ignore) {
ignore.printStackTrace();
}
}
}
/**
* {@inheritDoc}
*/
public void paint(Graphics g) {
super.paint(g);
if(spinnerHandle != null) {
Style s = getStyle();
g.drawImage(spinnerHandle, getX() + getWidth() - spinnerHandle.getWidth() - s.getHorizontalPadding(),
getY() + s.getPaddingTop());
}
if (System.currentTimeMillis() - inputSkipDelay < lastKeyInteraction || quickType.isPendingCommit()) {
quickType.setWidth(Math.min(getWidth(), quickType.getPreferredW()));
quickType.setHeight(Math.min(getHeight(), quickType.getPreferredH()));
Style s = quickType.getStyle();
quickType.setY(getScrollY() + getY());
// positioning based on date/time
if (getRenderer() instanceof DateTimeRenderer) {
switch (currentInputAlign) {
case LEFT:
quickType.setX(getX());
break;
case RIGHT:
quickType.setX(getX() + quickType.getStyle().getFont().charWidth(TextArea.getWidestChar()) * 4 + s.getMarginRightNoRTL());
break;
default:
quickType.setX(getX() + quickType.getStyle().getFont().charWidth(TextArea.getWidestChar()) * 2 + s.getMarginLeftNoRTL());
break;
}
} else {
quickType.setX(getX());
}
quickType.paintComponent(g, true);
}
}
/**
* {@inheritDoc}
*/
public boolean animate() {
boolean val = super.animate();
if (lastKeyInteraction != -1) {
quickType.animate();
if (System.currentTimeMillis() - inputSkipDelay > lastKeyInteraction && !quickType.isPendingCommit()) {
lastKeyInteraction = -1;
quickType.clear();
currentInputAlign = LEFT;
}
return true;
}
return val;
}
/**
* The image appearing on the side of the spinner widget to indicate its "spinnability"
*
* @return the spinnerHandle
*/
public static Image getSpinnerHandle() {
return spinnerHandle;
}
/**
* The image appearing on the side of the spinner widget to indicate its "spinnability"
*
* @param aSpinnerHandle the spinnerHandle to set
*/
public static void setSpinnerHandle(Image aSpinnerHandle) {
spinnerHandle = aSpinnerHandle;
}
/**
* Indicates the time after which the skip input area for entering spinner values manually will disappear
*
* @return the inputSkipDelay
*/
public static int getInputSkipDelay() {
return inputSkipDelay;
}
/**
* Indicates the time after which the skip input area for entering spinner values manually will disappear
*
* @param aInputSkipDelay the time for disappearing
*/
public static void setInputSkipDelay(int aInputSkipDelay) {
inputSkipDelay = aInputSkipDelay;
}
/**
* {@inheritDoc}
*/
protected boolean shouldRenderSelection() {
if(!isIgnoreFocusComponentWhenUnfocused()) {
return true;
}
return super.shouldRenderSelection();
}
public void repaint() {
if(getParent() instanceof BaseSpinner){
getParent().repaint();
}else{
super.repaint();
}
}
}