/*
* Copyright (c) 2012, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.ui.spinner;
import com.codename1.io.Util;
import com.codename1.l10n.L10NManager;
import com.codename1.l10n.SimpleDateFormat;
import com.codename1.ui.Button;
import com.codename1.ui.Command;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Dialog;
import com.codename1.ui.Display;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.GridLayout;
import com.codename1.ui.list.DefaultListModel;
import java.util.Calendar;
import java.util.Date;
/**
* <p>{@code Picker} is a component and API that allows either popping up a spinner or
* using the native picker API when applicable. This is quite important for some
* platforms where the native spinner behavior is very hard to replicate.</p>
*
* <script src="https://gist.github.com/codenameone/5e437d82812dfcbdf092.js"></script>
* <img src="https://www.codenameone.com/img/developer-guide/components-picker.png" alt="Picker UI" />
* <img src="https://www.codenameone.com/img/developer-guide/components-picker-date-time-on-simulator.png" alt="Date And Time Picker On the simulator" />
* <img src="https://www.codenameone.com/img/developer-guide/components-picker-date-android.png" alt="Android native date picker" />
* <img src="https://www.codenameone.com/img/developer-guide/components-picker-strings-android.png" alt="Android native String picker" />
* <img src="https://www.codenameone.com/img/developer-guide/components-picker-time-android.png" alt="Android native time picker" />
*
*
* @author Shai Almog
*/
public class Picker extends Button {
private int type = Display.PICKER_TYPE_DATE;
private Object value = new Date();
private boolean showMeridiem;
private Object metaData;
private Object renderingPrototype = "XXXXXXXXXXXXXX";
private SimpleDateFormat formatter;
private int preferredPopupWidth;
private int preferredPopupHeight;
/**
* Default constructor
*/
public Picker() {
setUIID("TextField");
addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if(Display.getInstance().isNativePickerTypeSupported(type)) {
setEnabled(false);
Object val = Display.getInstance().showNativePicker(type, Picker.this, value, metaData);
if(val != null) {
value = val;
updateValue();
}
setEnabled(true);
} else {
Dialog pickerDlg = new Dialog();
pickerDlg.setDisposeWhenPointerOutOfBounds(true);
pickerDlg.setLayout(new BorderLayout());
Calendar cld = Calendar.getInstance();
switch(type) {
case Display.PICKER_TYPE_STRINGS:
GenericSpinner gs = new GenericSpinner();
if(renderingPrototype != null) {
gs.setRenderingPrototype((String)renderingPrototype);
}
String[] strArr = (String[])metaData;
gs.setModel(new DefaultListModel((Object[])strArr));
if(value != null) {
int slen = strArr.length;
for(int iter = 0 ; iter < slen ; iter++) {
if(strArr[iter].equals(value)) {
gs.getModel().setSelectedIndex(iter);
break;
}
}
}
if (showDialog(pickerDlg, gs)) {
value = gs.getValue();
}
break;
case Display.PICKER_TYPE_DATE:
DateSpinner ds = new DateSpinner();
if(value == null) {
cld.setTime(new Date());
} else {
cld.setTime((Date)value);
}
ds.setStartYear(1900);
ds.setCurrentDay(cld.get(Calendar.DAY_OF_MONTH));
ds.setCurrentMonth(cld.get(Calendar.MONTH) + 1);
ds.setCurrentYear(cld.get(Calendar.YEAR));
if (showDialog(pickerDlg, ds)) {
cld.set(Calendar.DAY_OF_MONTH, ds.getCurrentDay());
cld.set(Calendar.MONTH, ds.getCurrentMonth() - 1);
cld.set(Calendar.YEAR, ds.getCurrentYear());
value = cld.getTime();
}
break;
case Display.PICKER_TYPE_TIME:
int v = ((Integer)value).intValue();
int hour = v / 60;
int minute = v % 60;
TimeSpinner ts = new TimeSpinner();
ts.setShowMeridiem(isShowMeridiem());
if(showMeridiem && hour > 12) {
ts.setCurrentMeridiem(true);
ts.setCurrentHour(hour - 12);
} else {
ts.setCurrentHour(hour);
}
ts.setCurrentMinute(minute);
if (showDialog(pickerDlg, ts)) {
if(isShowMeridiem()) {
int offset = 0;
if(ts.getCurrentHour() == 12) {
if(!ts.isCurrentMeridiem()) {
offset = 12;
}
} else {
if(ts.isCurrentMeridiem()) {
offset = 12;
}
}
hour = ts.getCurrentHour() + offset;
} else {
hour = ts.getCurrentHour();
}
value = new Integer(hour * 60 + ts.getCurrentMinute());
}
break;
case Display.PICKER_TYPE_DATE_AND_TIME:
DateTimeSpinner dts = new DateTimeSpinner();
cld.setTime((Date)value);
dts.setCurrentDate((Date)value);
dts.setShowMeridiem(isShowMeridiem());
if(isShowMeridiem() && dts.isCurrentMeridiem()) {
dts.setCurrentHour(cld.get(Calendar.HOUR));
} else {
dts.setCurrentHour(cld.get(Calendar.HOUR_OF_DAY));
}
dts.setCurrentMinute(cld.get(Calendar.MINUTE));
if (showDialog(pickerDlg, dts)) {
cld.setTime(dts.getCurrentDate());
if(isShowMeridiem() && dts.isCurrentMeridiem()) {
cld.set(Calendar.AM_PM, Calendar.PM);
cld.set(Calendar.HOUR, dts.getCurrentHour());
} else {
cld.set(Calendar.HOUR_OF_DAY, dts.getCurrentHour());
}
cld.set(Calendar.MINUTE, dts.getCurrentMinute());
value = cld.getTime();
}
break;
}
updateValue();
}
}
private boolean showDialog(Dialog pickerDlg, Component c) {
pickerDlg.addComponent(BorderLayout.CENTER, c);
Button ok = new Button(new Command("OK"));
final boolean[] userCanceled = new boolean[1];
Button cancel = new Button(new Command("Cancel") {
@Override
public void actionPerformed(ActionEvent evt) {
userCanceled[0] = true;
super.actionPerformed(evt);
}
});
Container buttons = GridLayout.encloseIn(2, cancel, ok);
pickerDlg.addComponent(BorderLayout.SOUTH, buttons);
if(Display.getInstance().isTablet()) {
pickerDlg.showPopupDialog(Picker.this);
} else {
pickerDlg.show();
}
return !userCanceled[0];
}
});
updateValue();
}
/**
* Sets the type of the picker to one of Display.PICKER_TYPE_DATE, Display.PICKER_TYPE_DATE_AND_TIME, Display.PICKER_TYPE_STRINGS or
* Display.PICKER_TYPE_TIME
* @param type the type
*/
public void setType(int type) {
this.type = type;
switch(type) {
case Display.PICKER_TYPE_DATE:
case Display.PICKER_TYPE_DATE_AND_TIME:
if(!(value instanceof Date)) {
value = new Date();
}
break;
case Display.PICKER_TYPE_STRINGS:
if(!Util.instanceofObjArray(value)) {
setStrings(new String[] {" "});
}
break;
case Display.PICKER_TYPE_TIME:
if(!(value instanceof Integer)) {
setTime(0);
}
break;
}
}
/**
* Returns the type of the picker
* @return one of Display.PICKER_TYPE_DATE, Display.PICKER_TYPE_DATE_AND_TIME, Display.PICKER_TYPE_STRINGS or
* Display.PICKER_TYPE_TIME
*/
public int getType() {
return type;
}
/**
* Returns the date, this value is used both for type date/date and time. Notice that this
* value isn't used for time
* @return the date object
*/
public Date getDate() {
return (Date)value;
}
/**
* Sets the date, this value is used both for type date/date and time. Notice that this
* value isn't used for time. Notice that this value will have no effect if the picker
* is currently showing.
*
* @param d the new date
*/
public void setDate(Date d) {
value = d;
updateValue();
}
private String twoDigits(int i) {
if(i < 10) {
return "0" + i;
}
return "" + i;
}
/**
* <p>Sets the string entries for the string picker. <br>
* sample usage for this method below:</p>
*
* <script src="https://gist.github.com/codenameone/47602e679f61712693bd.js"></script>
* @param strs string array
*/
public void setStrings(String... strs) {
this.type = Display.PICKER_TYPE_STRINGS;
int slen = strs.length;
for (int i = 0; i < slen; i++) {
String str = strs[i];
strs[i] = getUIManager().localize(str, str);
}
metaData = strs;
if(!(value instanceof String)) {
value = null;
}
updateValue();
}
/**
* Returns the String array matching the metadata
* @return a string array
*/
public String[] getStrings() {
return (String[])metaData;
}
/**
* Sets the current value in a string array picker
* @param str the current value
*/
public void setSelectedString(String str) {
value = str;
updateValue();
}
/**
* Returns the current string
* @return the selected string
*/
public String getSelectedString() {
return (String) value;
}
/**
* Returns the index of the selected string
* @return the selected string offset or -1
*/
public int getSelectedStringIndex() {
int offset = 0;
for(String s : (String[])metaData) {
if(s == value) {
return offset;
}
offset++;
}
return -1;
}
/**
* Returns the index of the selected string
* @param index sets the index of the selected string
*/
public void setSelectedStringIndex(int index) {
value = ((String[])metaData)[index];
updateValue();
}
/**
* Updates the display value of the picker, subclasses can override this to invoke
* set text with the right value
*/
protected void updateValue() {
if(value == null) {
setText("...");
return;
}
if(getFormatter() != null) {
setText(formatter.format(value));
return;
}
switch(type) {
case Display.PICKER_TYPE_STRINGS:
value = getUIManager().localize(value.toString(), value.toString());
setText(value.toString());
break;
case Display.PICKER_TYPE_DATE:
setText(L10NManager.getInstance().formatDateShortStyle((Date)value));
break;
case Display.PICKER_TYPE_TIME:
int v = ((Integer)value).intValue();
int hour = v / 60;
int minute = v % 60;
if(showMeridiem) {
String text;
if(hour >= 12) {
text = "pm";
} else {
text = "am";
}
setText(twoDigits(hour <= 12 ? hour : hour - 12) + ":" + twoDigits(minute) + text);
} else {
setText(twoDigits(hour) + ":" + twoDigits(minute));
}
break;
case Display.PICKER_TYPE_DATE_AND_TIME:
setText(L10NManager.getInstance().formatDateTimeShort((Date)value));
break;
}
}
/**
* This value is only used for time type and is ignored in the case of date and time where
* both are embedded within the date.
* @param time the time value as minutes since midnight e.g. 630 is 10:30am
*/
public void setTime(int time) {
value = new Integer(time);
updateValue();
}
/**
* Convenience method equivalent to invoking setTime(hour * 60 + minute);
* @param hour the hour in 24hr format
* @param minute the minute within the hour
*/
public void setTime(int hour, int minute) {
setTime(hour * 60 + minute);
}
/**
* This value is only used for time type and is ignored in the case of date and time where
* both are embedded within the date.
*
* @return the time value as minutes since midnight e.g. 630 is 10:30am
*/
public int getTime() {
return ((Integer)value).intValue();
}
/**
* Indicates whether hours should be rendered as AM/PM or 24hr format
* @return the showMeridiem
*/
public boolean isShowMeridiem() {
return showMeridiem;
}
/**
* Indicates whether hours should be rendered as AM/PM or 24hr format
* @param showMeridiem the showMeridiem to set
*/
public void setShowMeridiem(boolean showMeridiem) {
this.showMeridiem = showMeridiem;
updateValue();
}
/**
* When using a lightweight spinner this will be used as the rendering prototype
* @return the renderingPrototype
*/
public Object getRenderingPrototype() {
return renderingPrototype;
}
/**
* When using a lightweight spinner this will be used as the rendering prototype
* @param renderingPrototype the renderingPrototype to set
*/
public void setRenderingPrototype(Object renderingPrototype) {
this.renderingPrototype = renderingPrototype;
}
/**
* Allows us to define a date format for the display of dates/times
* @return the defined formatter
*/
public SimpleDateFormat getFormatter() {
return formatter;
}
/**
* Allows us to define a date format for the display of dates/times
*
* @param formatter the new formatter
*/
public void setFormatter(SimpleDateFormat formatter) {
this.formatter = formatter;
updateValue();
}
/**
* The preferred width of the popup dialog for the picker. This will only
* be used on devices where the popup width and height are configurable, such
* as the iPad or tablets. On iPhone, the picker always spans the width of the
* screen along the bottom.
* @param width The preferred width of the popup.
*/
public void setPreferredPopupWidth(int width) {
this.preferredPopupWidth = width;
}
/**
* The preferred height of the popup dialog for the picker. This will only
* be used on devices where the popup width and height are configurable, such
* as the iPad or tablets. On iPhone, the picker always spans the width of the
* screen along the bottom.
* @param height The preferred height of the popup.
*/
public void setPreferredPopupHeight(int height) {
this.preferredPopupHeight = height;
}
/**
* The preferred width of the popup dialog. This will only
* be used on devices where the popup width and height are configurable, such
* as the iPad or tablets. On iPhone, the picker always spans the width of the
* screen along the bottom.
* @return
*/
public int getPreferredPopupWidth() {
return preferredPopupWidth;
}
/**
* The preferred height of the popup dialog. This will only
* be used on devices where the popup width and height are configurable, such
* as the iPad or tablets. On iPhone, the picker always spans the width of the
* screen along the bottom.
* @return
*/
public int getPreferredPopupHeight() {
return preferredPopupHeight;
}
/**
* {@inheritDoc}
*/
public String[] getPropertyNames() {
return new String[] {"Strings"};
}
/**
* {@inheritDoc}
*/
public Class[] getPropertyTypes() {
return new Class[] { String[].class };
}
/**
* {@inheritDoc}
*/
public String[] getPropertyTypeNames() {
return new String[] {"String []"};
}
/**
* {@inheritDoc}
*/
public Object getPropertyValue(String name) {
if(name.equals("Strings")) {
return getStrings();
}
return null;
}
/**
* {@inheritDoc}
*/
public String setPropertyValue(String name, Object value) {
if(name.equals("Strings")) {
setStrings((String[])value);
return null;
}
return super.setPropertyValue(name, value);
}
/**
* Returns the value which works for all picker types
* @return the value object
*/
public Object getValue() {
return value;
}
}