/*
* @(#)DateField.java 1.137 02/10/09 @(#)
*
* Copyright (c) 1999-2002 Sun Microsystems, Inc. All rights reserved.
* PROPRIETARY/CONFIDENTIAL
* Use is subject to license terms.
*/
package javax.microedition.lcdui;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import com.sun.midp.lcdui.Resource;
import com.sun.midp.lcdui.Text;
/**
* A <code>DateField</code> is an editable component for presenting
* date and time (calendar)
* information that may be placed into a <code>Form</code>. Value for
* this field can be
* initially set or left unset. If value is not set then the UI for the field
* shows this clearly. The field value for "not initialized
* state" is not valid
* value and <code>getDate()</code> for this state returns <code>null</code>.
* <p>
* Instance of a <code>DateField</code> can be configured to accept
* date or time information
* or both of them. This input mode configuration is done by
* <code>DATE</code>, <code>TIME</code> or
* <code>DATE_TIME</code> static fields of this
* class. <code>DATE</code> input mode allows to set only
* date information and <code>TIME</code> only time information
* (hours, minutes). <code>DATE_TIME</code>
* allows to set both clock time and date values.
* <p>
* In <code>TIME</code> input mode the date components of
* <code>Date</code> object
* must be set to the "zero epoch" value of January 1, 1970.
* <p>
* Calendar calculations in this field are based on default locale and defined
* time zone. Because of the calculations and different input modes date object
* may not contain same millisecond value when set to this field and get back
* from this field.
* @since MIDP 1.0
*/
public class DateField extends Item {
/**
* Input mode for date information (day, month, year). With this mode this
* <code>DateField</code> presents and allows only to modify date
* value. The time
* information of date object is ignored.
*
* <P>Value <code>1</code> is assigned to <code>DATE</code>.</P>
*/
public static final int DATE = 1;
/**
* Input mode for time information (hours and minutes). With this mode this
* <code>DateField</code> presents and allows only to modify
* time. The date components
* should be set to the "zero epoch" value of January 1, 1970 and
* should not be accessed.
*
* <P>Value <code>2</code> is assigned to <code>TIME</code>.</P>
*/
public static final int TIME = 2;
/**
* Input mode for date (day, month, year) and time (minutes, hours)
* information. With this mode this <code>DateField</code>
* presents and allows to modify
* both time and date information.
*
* <P>Value <code>3</code> is assigned to <code>DATE_TIME</code>.</P>
*/
public static final int DATE_TIME = 3;
/**
* Creates a <code>DateField</code> object with the specified
* label and mode. This call
* is identical to <code>DateField(label, mode, null)</code>.
*
* @param label item label
* @param mode the input mode, one of <code>DATE</code>, <code>TIME</code>
* or <code>DATE_TIME</code>
* @throws IllegalArgumentException if the input <code>mode's</code>
* value is invalid
*/
public DateField(String label, int mode) {
this(label, mode, null);
}
/**
* Creates a date field in which calendar calculations are based
* on specific
* <code>TimeZone</code> object and the default calendaring system for the
* current locale.
* The value of the <code>DateField</code> is initially in the
* "uninitialized" state.
* If <code>timeZone</code> is <code>null</code>, the system's
* default time zone is used.
*
* @param label item label
* @param mode the input mode, one of <code>DATE</code>, <code>TIME</code>
* or <code>DATE_TIME</code>
* @param timeZone a specific time zone, or <code>null</code> for the
* default time zone
* @throws IllegalArgumentException if the input <code>mode's</code> value
* is invalid
*/
public DateField(String label, int mode, java.util.TimeZone timeZone) {
super(label);
synchronized (Display.LCDUILock) {
if ((mode != DATE) && (mode != TIME) && (mode != DATE_TIME)) {
throw new IllegalArgumentException("Invalid input mode");
}
this.mode = mode;
if (timeZone == null) {
timeZone = TimeZone.getDefault();
}
this.currentDate = Calendar.getInstance(timeZone);
} // synchronized
}
/**
* Returns date value of this field. Returned value is
* <code>null</code> if field
* value is
* not initialized. The date object is constructed according the rules of
* locale specific calendaring system and defined time zone.
*
* In <code>TIME</code> mode field the date components are set to
* the "zero
* epoch" value of January 1, 1970. If a date object that presents time
* beyond one day from this "zero epoch" then this field
* is in "not
* initialized" state and this method returns <code>null</code>.
*
* In <code>DATE</code> mode field the time component of the calendar is set
* to zero when
* constructing the date object.
*
* @return date object representing time or date depending on input mode
* @see #setDate
*/
public java.util.Date getDate() {
synchronized (Display.LCDUILock) {
// NOTE:
// defensive copy of the Date object is necessary
// because CLDC's Calendar returns a reference to an internal,
// shared Date object. See bugID: 4479408.
return (initialized ?
new java.util.Date(currentDate.getTime().getTime()) : null);
} // synchronized
}
/**
* Sets a new value for this field. <code>null</code> can be
* passed to set the field
* state to "not initialized" state. The input mode of
* this field defines
* what components of passed <code>Date</code> object is used.<p>
*
* In <code>TIME</code> input mode the date components must be set
* to the "zero
* epoch" value of January 1, 1970. If a date object that presents time
* beyond one day then this field is in "not initialized" state.
* In <code>TIME</code> input mode the date component of
* <code>Date</code> object is ignored and time
* component is used to precision of minutes.<p>
*
* In <code>DATE</code> input mode the time component of
* <code>Date</code> object is ignored.<p>
*
* In <code>DATE_TIME</code> input mode the date and time
* component of <code>Date</code> are used but
* only to precision of minutes.
*
* @param date new value for this field
* @see #getDate
*/
public void setDate(java.util.Date date) {
synchronized (Display.LCDUILock) {
if (date == null) {
initialized = false;
} else {
currentDate.setTime(date);
if (mode == TIME) {
// NOTE:
// It is unclear from the spec what should happen
// when DateField with TIME mode is set to
// a value that is on a day other than 1/1/1970.
//
// Two possible interpretations of the spec are:
// 1. DateField is put into the "uninitialized" state; or
// 2. The time portion of the DateField is set to the
// time-of-day portion of the Date object passed in,
// and the date portion of the DateField is set
// to 1/1/1970.
//
// Currently we are using the first approach.
initialized =
(currentDate.get(Calendar.YEAR) == 1970) &&
(currentDate.get(Calendar.MONTH) == Calendar.JANUARY)
&& (currentDate.get(Calendar.DATE) == 1);
} else {
// Currently spec does not prohibit from losing
// irrelevant for that mode information
// so we always zero out hours and minutes
// NOTE: the specification doesn't prohibit
// the loss of information irrelevant to
// the current input mode, so we always zero out the
// hours and minutes.
if (mode == DATE) {
currentDate.set(Calendar.HOUR, 0);
currentDate.set(Calendar.MINUTE, 0);
}
initialized = true;
}
// always ignore seconds and milliseconds
currentDate.set(Calendar.SECOND, 0);
currentDate.set(Calendar.MILLISECOND, 0);
}
invalidate();
} // synchronized
}
/**
* Gets input mode for this date field. Valid input modes are
* <code>DATE</code>, <code>TIME</code> and <code>DATE_TIME</code>.
*
* @return input mode of this field
* @see #setInputMode
*/
public int getInputMode() {
// SYNC NOTE: return of atomic value, no locking necessary
return mode;
}
/**
* Set input mode for this date field. Valid input modes are
* <code>DATE</code>, <code>TIME</code> and <code>DATE_TIME</code>.
*
* @param mode the input mode, must be one of <code>DATE</code>,
* <code>TIME</code> or <code>DATE_TIME</code>
* @throws IllegalArgumentException if an invalid value is specified
* @see #getInputMode
*/
public void setInputMode(int mode) {
if ((mode != DATE) && (mode != TIME) && (mode != DATE_TIME)) {
throw new IllegalArgumentException("Invalid input mode");
}
synchronized (Display.LCDUILock) {
if (this.mode != mode) {
int oldMode = this.mode;
this.mode = mode;
// While the input mode is changed
// some irrelevant values for new mode could be lost.
// Currently that is allowed by the spec.
// So for TIME mode we make sure that time is set
// on a zero epoch date
// and for DATE mode we zero out hours and minutes
if (mode == TIME) {
currentDate.set(Calendar.YEAR, 1970);
currentDate.set(Calendar.MONTH, Calendar.JANUARY);
currentDate.set(Calendar.DATE, 1);
} else if (mode == DATE) {
currentDate.set(Calendar.HOUR, 0);
currentDate.set(Calendar.MINUTE, 0);
}
invalidate();
}
} // synchronized
}
// package private
/**
* Determine if this Item should have a newline after it
*
* @return true if it should have a newline after
*/
boolean equateNLA() {
if (super.equateNLA()) {
return true;
}
return ((layout & Item.LAYOUT_2) != Item.LAYOUT_2);
}
/**
* Determine if this Item should have a newline before it
*
* @return true if it should have a newline before
*/
boolean equateNLB() {
if (super.equateNLB()) {
return true;
}
return ((layout & Item.LAYOUT_2) != Item.LAYOUT_2);
}
/**
* Called from the Date Editor to save the selected Date.
*
* @param date The Date object to which current date should be set.
*/
void saveDate(java.util.Date date) {
initialized = true;
currentDate.setTime(date);
invalidate();
}
/**
* Get the minimum width of this Item
*
* @return the minimum width
*/
int callMinimumWidth() {
return Screen.CONTENT_FONT.stringWidth("Www,99 Www 0000") + 2;
}
/**
* Get the preferred width of this Item
*
* @param h the tentative content height in pixels, or -1 if a
* tentative height has not been computed
* @return the preferred width
*/
int callPreferredWidth(int h) {
return callMinimumWidth();
}
/**
* Get the minimum height of this Item
*
* @return the minimum height
*/
int callMinimumHeight() {
return callPreferredHeight(-1);
}
/**
* Get the preferred height of this Item
*
* @param w the tentative content width in pixels, or -1 if a
* tentative width has not been computed
* @return the preferred height
*/
int callPreferredHeight(int w) {
if (mode == DATE_TIME) {
return getLabelHeight(w) +
(Screen.CONTENT_HEIGHT * 2) + LABEL_PAD;
} else {
return getLabelHeight(w) + Screen.CONTENT_HEIGHT;
}
}
/**
* Paint this DateField
*
* @param g the Graphics object to be used for rendering the item
* @param width current width of the item in pixels
* @param height current height of the item in pixels
*/
void callPaint(Graphics g, int width, int height) {
// draw label
int labelHeight = super.paintLabel(g, width) + LABEL_PAD;
g.translate(0, labelHeight);
int offset = 0;
String str;
switch (mode) {
case TIME:
case DATE_TIME:
str = toString(TIME);
if (highlight == 0 && hasFocus) {
g.fillRect(2, 0, Screen.CONTENT_FONT.stringWidth(str),
Screen.CONTENT_HEIGHT);
g.setColor(Display.FG_H_COLOR);
}
g.drawString(str, 2, 0,
Graphics.LEFT | Graphics.TOP);
g.setColor(Display.FG_COLOR);
if (mode == TIME) {
break;
}
offset = Screen.CONTENT_HEIGHT;
case DATE:
str = toString(DATE);
if ((highlight == 0 && mode == DATE && hasFocus) ||
(highlight == 1 && mode == DATE_TIME && hasFocus)) {
g.fillRect(2, offset, Screen.CONTENT_FONT.stringWidth(str),
Screen.CONTENT_HEIGHT);
g.setColor(Display.FG_H_COLOR);
}
g.drawString(toString(DATE), 2, offset,
Graphics.LEFT | Graphics.TOP);
g.setColor(Display.FG_COLOR);
}
g.translate(0, -labelHeight);
}
/**
* Called by the system to traverse this DateField
*
* @param dir the direction of traversal
* @param viewportWidth the width of the container's viewport
* @param viewportHeight the height of the container's viewport
* @param visRect passes the visible rectangle into the method, and
* returns the updated traversal rectangle from the method
* @return true if internal traversal had occurred, false if traversal
* should proceed out
*/
boolean callTraverse(int dir, int viewportWidth, int viewportHeight,
int[] visRect) {
super.callTraverse(dir, viewportWidth, viewportHeight, visRect);
int numEls = (mode == DATE_TIME) ? 2 : 1;
if (!traversedIn) {
traversedIn = true;
if (highlight == -1) {
switch (dir) {
case Canvas.UP:
highlight = numEls - 1;
break;
case Canvas.DOWN:
highlight = 0;
break;
case CustomItem.NONE:
}
}
} else {
if (dir == Canvas.UP) {
if (highlight > 0) {
highlight--;
} else {
return false;
}
} else if (dir == Canvas.DOWN) {
if (highlight < (numEls - 1)) {
highlight++;
} else {
return false;
}
}
}
visRect[Y] = getLabelHeight(visRect[WIDTH]) + LABEL_PAD;
if (highlight > 0) {
visRect[Y] += Screen.CONTENT_HEIGHT;
}
visRect[HEIGHT] = Screen.CONTENT_HEIGHT;
repaint();
return true;
}
/**
* Called by the system to indicate traversal has left this Item
*/
void callTraverseOut() {
super.callTraverseOut();
traversedIn = false;
}
/**
* Called by the system to signal a key press
*
* @param keyCode the key code of the key that has been pressed
*/
void callKeyPressed(int keyCode) {
if (keyCode != Display.KEYCODE_SELECT) {
return;
}
Screen returnScreen = getOwner();
if (editor == null) {
editor = new EditScreen(returnScreen, this);
}
switch (mode) {
case DATE:
if (!initialized) {
currentDate.set(Calendar.HOUR, 0);
currentDate.set(Calendar.MINUTE, 0);
currentDate.set(Calendar.SECOND, 0);
currentDate.set(Calendar.MILLISECOND, 0);
}
editor.setDateTime(currentDate.getTime(), DATE);
break;
case TIME:
editor.setDateTime(initialized ? currentDate.getTime() : EPOCH,
TIME);
break;
case DATE_TIME:
editor.setDateTime(currentDate.getTime(),
(highlight < 1) ? TIME : DATE);
}
returnScreen.resetToTop = false;
returnScreen.currentDisplay.setCurrent(editor);
}
/**
* Get the am/pm text given a Calandar
*
* @param calendar The Calendar object to retrieve the time from
* @return String The am/pm text based on the time in the calendar
*/
static String ampmString(Calendar calendar) {
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
if (hour >= 12) {
return ((minute == 0) && (hour == 12)) ? "noon" : "PM";
} else {
return ((minute == 0) && (hour == 00)) ? "mid." : "AM";
}
}
/**
* Get the day of the week text given a Calendar
*
* @param calendar The Calendar object to retrieve the date from
* @return String The day of the week text based on the date in the
* calendar
*/
static String dayOfWeekString(Calendar calendar) {
String str;
switch (calendar.get(Calendar.DAY_OF_WEEK)) {
case Calendar.SUNDAY: str = "Sun"; break;
case Calendar.MONDAY: str = "Mon"; break;
case Calendar.TUESDAY: str = "Tue"; break;
case Calendar.WEDNESDAY: str = "Wed"; break;
case Calendar.THURSDAY: str = "Thu"; break;
case Calendar.FRIDAY: str = "Fri"; break;
case Calendar.SATURDAY: str = "Sat"; break;
default:
str = Integer.toString(calendar.get(Calendar.DAY_OF_WEEK));
}
return str;
}
/**
* Translate the mode of a DateField into a readable string
*
* @param mode The mode to translate
* @return String A human readable string representing the mode of the
* DateField
*/
String toString(int mode) {
if (mode == DATE) {
if (!initialized) {
return Resource.getString("<date>");
}
return Resource.getDateString(
dayOfWeekString(currentDate),
twoDigits(currentDate.get(Calendar.DATE)),
MONTH_NAMES[currentDate.get(Calendar.MONTH)].substring(0, 3),
Integer.toString(currentDate.get(Calendar.YEAR)));
} else if (mode == TIME) {
if (!initialized) {
return Resource.getString("<time>");
}
if (CLOCK_USES_AM_PM) {
return Resource.getTimeString(
twoDigits(currentDate.get(Calendar.HOUR)),
twoDigits(currentDate.get(Calendar.MINUTE)),
twoDigits(currentDate.get(Calendar.SECOND)),
ampmString(currentDate));
} else {
return Resource.getTimeString(
twoDigits(currentDate.get(Calendar.HOUR_OF_DAY)),
twoDigits(currentDate.get(Calendar.MINUTE)),
twoDigits(currentDate.get(Calendar.SECOND)),
null);
}
} else {
if (!initialized) {
return Resource.getString("<date/time>");
}
return Resource.getDateTimeString(
dayOfWeekString(currentDate),
twoDigits(currentDate.get(Calendar.DATE)),
MONTH_NAMES[currentDate.get(Calendar.MONTH)].substring(0, 3),
Integer.toString(currentDate.get(Calendar.YEAR)),
twoDigits(currentDate.get(Calendar.HOUR_OF_DAY)),
twoDigits(currentDate.get(Calendar.MINUTE)),
twoDigits(currentDate.get(Calendar.SECOND)),
ampmString(currentDate));
}
}
/**
* Static zero epoch Date - used as a default value for
* uninitialized DateFields with TIME mode.
*/
static final java.util.Date EPOCH = new java.util.Date(0);
// REMIND: Needs to be localized?
/**
* Static array holding the names of the 12 months
*/
static final String MONTH_NAMES[] = {
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
};
/**
* table of trigonometric functions, in 16.16 fixed point
*/
static final int TRIG_TABLE[] = {
65535, // cos 0
65525, // cos 1
65495, // cos 2
65445, // cos 3
65375, // cos 4
65285, // cos 5
65175, // cos 6
65046, // cos 7
64897, // cos 8
64728, // cos 9
64539, // cos 10
64330, // cos 11
64102, // cos 12
63855, // cos 13
63588, // cos 14
63301, // cos 15
62996, // cos 16
62671, // cos 17
62327, // cos 18
61964, // cos 19
61582, // cos 20
61182, // cos 21
60762, // cos 22
60325, // cos 23
59869, // cos 24
59394, // cos 25
58902, // cos 26
58392, // cos 27
57863, // cos 28
57318, // cos 29
56754, // cos 30
56174, // cos 31
55576, // cos 32
54962, // cos 33
54330, // cos 34
53683, // cos 35
53018, // cos 36
52338, // cos 37
51642, // cos 38
50930, // cos 39
50202, // cos 40
49459, // cos 41
48701, // cos 42
47929, // cos 43
47141, // cos 44
46340, // cos 45
45524, // cos 46
44694, // cos 47
43851, // cos 48
42994, // cos 49
42125, // cos 50
41242, // cos 51
40347, // cos 52
39439, // cos 53
38520, // cos 54
37589, // cos 55
36646, // cos 56
35692, // cos 57
34728, // cos 58
33753, // cos 59
32767, // cos 60
31771, // cos 61
30766, // cos 62
29752, // cos 63
28728, // cos 64
27696, // cos 65
26655, // cos 66
25606, // cos 67
24549, // cos 68
23485, // cos 69
22414, // cos 70
21336, // cos 71
20251, // cos 72
19160, // cos 73
18063, // cos 74
16961, // cos 75
15854, // cos 76
14742, // cos 77
13625, // cos 78
12504, // cos 79
11380, // cos 80
10251, // cos 81
9120, // cos 82
7986, // cos 83
6850, // cos 84
5711, // cos 85
4571, // cos 86
3429, // cos 87
2287, // cos 88
1143, // cos 89
0 // cos 90
};
/**
* Utility method to return the cosine of an angle
*
* @param angle The angle to compute the cosine of
* @return int The cosine of the angle
*/
static int cos(int angle) {
angle += 360000;
angle %= 360;
if (angle >= 270) {
return TRIG_TABLE[360 - angle];
} else if (angle >= 180) {
return -TRIG_TABLE[angle - 180];
} else if (angle >= 90) {
return -TRIG_TABLE[180 - angle];
} else {
return TRIG_TABLE[angle];
}
}
/**
* Utility method to return the sin of an angle
*
* @param angle The angle to compute the sin of
* @return int The sin of the angle
*/
static int sin(int angle) {
return cos(angle - 90);
}
// private
/**
* A utility method to return a numerical digit as two digits
* if it is less than 10
*
* @param n The number to convert
* @return String The String representing the number in two digits
*/
private static String twoDigits(int n) {
if (n == 0) {
return "00";
} else if (n < 10) {
return "0" + n;
} else {
return "" + n;
}
}
/**
* The highlight of this DateField
*/
private int highlight = -1;
/**
* A flag indicating a prior call to callTraverse()
*/
private boolean traversedIn;
/**
* A flag indicating the initialization state of this DateField
*/
private boolean initialized; // = false;
/**
* The mode of this DateField
*/
private int mode;
/**
* The editor for this DateField
*/
private EditScreen editor = null;
/**
* The last saved date.
* This is used for making the last saved date bold.
*/
private Calendar currentDate;
/**
* Flag to signal the clock representation uses AM and PM notation
*/
private static final boolean CLOCK_USES_AM_PM = true;
/**
* The image representing an up arrow
*/
private static final Image ARROW_UP;
/**
* The image representing an down arrow
*/
private static final Image ARROW_DOWN;
/**
* The image representing an left arrow
*/
private static final Image ARROW_LEFT;
/**
* The image representing an right arrow
*/
private static final Image ARROW_RIGHT;
static {
/*
* Initialize the icons necessary for the date editor.
*/
ARROW_UP = ImmutableImage.createIcon("date_up.png");
ARROW_DOWN = ImmutableImage.createIcon("date_down.png");
ARROW_LEFT = ImmutableImage.createIcon("date_left.png");
ARROW_RIGHT = ImmutableImage.createIcon("date_right.png");
}
/**
* The EditScreen class is a special editor for a DateField.
* It can edit both date and time.
*/
class EditScreen extends Screen implements CommandListener {
/**
* The calendar holding the date/time for this editor
*/
Calendar calendar;
/**
* The mode of this editor (Date, Time, Date & Time)
*/
int mode;
/**
* The DateField being edited by this editor
*/
DateField field;
/**
* The encapsulating Screen holding the DateField being
* edited by this editor (allows us to return to this screen
* when we are done editing).
*/
Screen returnScreen;
/**
* The selected time element
*/
int timeSel = 0;
/**
* Flag to signal whether a select action transfers focus or not
* (false by default)
*/
private static final boolean SELECT_TRANSFERS_FOCUS = false;
/**
* Special command to go "back" from the editor to the DateField
*/
Command Back = new Command(
Resource.getString("Back"), Command.BACK, 0);
/**
* Special command to "ok" the changes done in the editor
*/
Command OK = new Command
(Resource.getString("Save"), Command.OK, 1);
/**
* Flag to format am/pm string
*/
private boolean ampmAfterTime = Resource.isAMPMafterTime();
/**
* The width and height of the editor
*/
private int width, height;
/**
* The initial highlight
*/
private int highlight;
/**
* The last day of the month
*/
private int lastDay;
/**
* The day offset
*/
private int dayOffset;
/**
* Create a new EditScreen
*
* @param returnScreen The Screen to return to from the editor
* @param field The DateField this EditScreen is editing
*/
EditScreen(Screen returnScreen, DateField field) {
super(field.getLabel());
this.returnScreen = returnScreen;
this.field = field;
this.calendar = Calendar.getInstance();
addCommand(OK);
addCommand(Back);
setCommandListener(this);
}
/**
* Handle a command action
*
* @param cmd The Command to handle
* @param s The Displayable with the Command
*/
public void commandAction(Command cmd, Displayable s) {
Form form = null;
Item item = null;
synchronized (Display.LCDUILock) {
if (cmd == OK) {
field.saveDate(calendar.getTime());
item = field;
form = (Form)item.getOwner();
}
currentDisplay.setCurrent(returnScreen);
} // synchronized
// SYNC NOTE: Move the call to the application's
// ItemStateChangedListener outside the lock
if (form != null) {
form.itemStateChanged(item);
}
}
/**
* Set the current date and time
*
* @param currentValue The Date to set the editor to
* @param mode The operating mode of the DateField
*/
void setDateTime(Date currentValue, int mode) {
calendar.setTime(currentValue);
this.mode = mode;
if (CLOCK_USES_AM_PM && !ampmAfterTime) {
timeSel = Calendar.AM_PM;
} else {
timeSel = Calendar.HOUR;
}
// highlight = calendar.get(Calendar.DATE);
highlight = -1; // highlight the year to start with
this.callRepaint(); // Call Screen.callRepaint()
}
/**
* notify this editor it is being shown on the given Display
*
* @param d the Display showing this Form
*/
void callShowNotify(Display d) {
super.callShowNotify(d);
layout();
setDayOffset();
lastDay = daysInMonth(calendar.get(Calendar.MONTH),
calendar.get(Calendar.YEAR));
}
/**
* Paint the content of this editor
*
* @param g The Graphics object to paint to
* @param target the target Object of this repaint
*/
void callPaint(Graphics g, Object target) {
super.callPaint(g, target);
g.translate(viewport[X], viewport[Y] + 5);
if (mode == TIME) {
paintClock(g);
} else {
paintCalendar(g);
}
g.translate(-viewport[X], -(viewport[Y] + 5));
}
/**
* Layout the content of this editor given the width/height
*/
void layout() {
super.layout();
this.width = viewport[WIDTH];
this.height = viewport[HEIGHT];
}
/**
* Initialize the highlight of this editor
*
* @param vpY
* @param vpH
* @return int Always returns 0
*/
int initHilight(int vpY, int vpH) {
if (mode == DATE) {
int hilightBottom = highlightY(true);
if (hilightBottom > vpH) {
return (hilightBottom - vpH);
}
}
return 0;
}
/**
* Handle a key press
*
* @param keyCode the key which was pressed
*/
void callKeyPressed(int keyCode) {
int gameAction = Display.getGameAction(keyCode);
switch (gameAction) {
case Canvas.FIRE:
selectFired();
break;
case Canvas.UP:
case Canvas.DOWN:
case Canvas.LEFT:
case Canvas.RIGHT:
if (mode == DATE) {
traverseDate(gameAction, bounds[Y],
bounds[Y] + bounds[HEIGHT]);
} else {
traverseClock(gameAction, bounds[Y],
bounds[Y] + bounds[HEIGHT]);
}
this.callRepaint(); // Call Screen.callRepaint()
break;
}
}
/**
* Handle a key repeat
*
* @param keyCode the key which was repeated
*/
void callKeyRepeated(int keyCode) {
int gameAction = Display.getGameAction(keyCode);
switch (gameAction) {
case Canvas.UP:
case Canvas.DOWN:
case Canvas.LEFT:
case Canvas.RIGHT:
if (mode == DATE) {
traverseDate(gameAction, bounds[Y],
bounds[Y] + bounds[HEIGHT]);
} else {
traverseClock(gameAction, bounds[Y],
bounds[Y] + bounds[HEIGHT]);
}
this.callRepaint(); // Call Screen.callRepaint()
}
}
/**
* Handle a selection
*/
void selectFired() {
if (!SELECT_TRANSFERS_FOCUS) {
return;
}
synchronized (Display.LCDUILock) {
if (mode == DATE) {
if (highlight > 0) {
highlight = -1;
} else if (highlight == -1) {
highlight = 0;
} else {
highlight = calendar.get(Calendar.DATE);
}
if (highlight > 0) {
calendar.set(Calendar.DATE, highlight);
}
} else {
if (timeSel == Calendar.MINUTE) {
timeSel = Calendar.HOUR;
} else {
timeSel = Calendar.MINUTE;
}
}
this.callRepaint(); // Call Screen.callRepaint()
} // synchronized
}
/**
* Paint the clock
*
* @param g The Graphics to paint to
*/
void paintClock(Graphics g) {
int hour = calendar.get(Calendar.HOUR) % 12;
int minute = calendar.get(Calendar.MINUTE);
g.setColor(Display.ERASE_COLOR);
g.fillRect(0, 0, width, height);
int digits_height = large.getHeight()
+ ARROW_UP.getHeight()
+ ARROW_DOWN.getHeight()
+ 2;
int clockSize = height - digits_height;
if (width < clockSize) {
clockSize = width;
}
if (60 < clockSize) {
clockSize = 60;
}
// For reference, the above if statements are replacing
// this Math() call below.
// Math.min(60, Math.min(width, height - digits_height));
g.translate((width - clockSize) / 2,
(height - (clockSize + digits_height)) / 2);
g.setColor(Display.FG_COLOR);
g.drawRoundRect(0, 0, clockSize, clockSize,
clockSize / 2, clockSize / 2);
g.drawLine(clockSize / 2, 0, clockSize / 2, 5);
g.drawLine(clockSize / 2, clockSize,
clockSize / 2, clockSize - 5);
g.drawLine(0, clockSize / 2, 5, clockSize / 2);
g.drawLine(clockSize, clockSize / 2,
clockSize - 5, clockSize / 2);
int minuteAngle = 90 - (minute * 6);
int hourAngle = 90 - (hour * 30 + (minute / 2));
g.translate(clockSize / 2, clockSize / 2);
g.drawLine(0, 0,
(cos(hourAngle)*clockSize / 4) >> 16,
-(sin(hourAngle)*clockSize / 4) >> 16);
g.drawLine(0, 0,
(cos(minuteAngle)*(clockSize / 2 - 10)) >> 16,
-(sin(minuteAngle)*(clockSize / 2 - 10)) >> 16);
g.translate(0, clockSize / 2 + 2 + ARROW_UP.getHeight());
g.setFont(large);
if (CLOCK_USES_AM_PM) {
String ampm = Resource.getString(ampmString(calendar));
if (hour == 0) {
hour = 12;
}
String timeString;
if (ampmAfterTime) {
timeString =
twoDigits(hour) + ":" + twoDigits(minute) + " " + ampm;
} else {
timeString =
ampm + " " + twoDigits(hour) + ":" + twoDigits(minute);
}
g.translate(-large.stringWidth(timeString) / 2, 0);
g.drawString(timeString,
0, 0, Graphics.LEFT | Graphics.TOP);
int dX;
int w;
int h = large.getBaselinePosition() + 1;
int offset;
int len;
if (ampmAfterTime) {
if (timeSel == Calendar.HOUR) {
offset = 0;
len = 2;
} else if (timeSel == Calendar.MINUTE) {
offset = 3;
len = 2;
} else {
offset = 6;
len = timeString.length() - offset;
}
} else {
offset = ampm.length();
if (timeSel == Calendar.HOUR) {
offset += 1;
len = 2;
} else if (timeSel == Calendar.MINUTE) {
offset += 4;
len = 2;
} else {
len = offset;
offset = 0;
}
}
dX = large.substringWidth(timeString, 0, offset);
w = large.substringWidth(timeString, offset, len);
g.fillRect(dX, 1, w, h -1);
g.setColor(Display.FG_H_COLOR);
g.drawSubstring(timeString, offset, len,
dX, 0, Graphics.LEFT | Graphics.TOP);
if (ampmAfterTime) {
if (timeSel != Calendar.HOUR) {
g.drawImage(ARROW_LEFT,
-1, h / 2 + 2,
Graphics.RIGHT | Graphics.VCENTER);
}
if (timeSel != Calendar.AM_PM) {
g.drawImage(ARROW_RIGHT,
large.stringWidth(timeString) + 1,
h / 2 + 2,
Graphics.LEFT | Graphics.VCENTER);
}
} else {
if (timeSel != Calendar.AM_PM) {
g.drawImage(ARROW_LEFT,
-1, h / 2 + 2,
Graphics.RIGHT | Graphics.VCENTER);
}
if (timeSel != Calendar.MINUTE) {
g.drawImage(ARROW_RIGHT,
large.stringWidth(timeString) + 1,
h / 2 + 2,
Graphics.LEFT | Graphics.VCENTER);
}
}
g.drawImage(ARROW_UP,
dX + w / 2, 0, Graphics.HCENTER | Graphics.BOTTOM);
g.drawImage(ARROW_DOWN,
dX + w / 2, h + 1,
Graphics.HCENTER | Graphics.TOP);
} else {
g.drawString(":", 0, 0, Graphics.LEFT | Graphics.TOP);
hour = calendar.get(Calendar.HOUR_OF_DAY);
String str = hour + "";
int w = large.stringWidth(str);
int h = large.getBaselinePosition() + 1;
g.translate(-1, 0);
if (timeSel == Calendar.HOUR) {
g.setColor(Display.BG_H_COLOR);
g.fillRect(-w, 1, w + 1, h - 1);
g.setColor(Display.FG_H_COLOR);
g.drawImage(ARROW_UP, -w / 2, 0,
Graphics.HCENTER | Graphics.BOTTOM);
g.drawImage(ARROW_DOWN,
-w / 2, h + 1,
Graphics.HCENTER | Graphics.TOP);
} else {
g.setColor(Display.FG_COLOR);
g.drawImage(ARROW_LEFT, -(w + 1), h / 2,
Graphics.RIGHT | Graphics.VCENTER);
}
g.drawString(str, 0, 0,
Graphics.RIGHT | Graphics.TOP);
str = twoDigits(minute);
w = large.stringWidth(str);
g.translate(1 + large.charWidth(':'), 0);
if (timeSel == Calendar.MINUTE) {
g.setColor(Display.BG_H_COLOR);
g.fillRect(0, 1, w + 1, h - 1);
g.setColor(Display.FG_H_COLOR);
g.drawImage(ARROW_UP, w / 2, 0,
Graphics.HCENTER | Graphics.BOTTOM);
g.drawImage(ARROW_DOWN, w / 2, h + 1,
Graphics.HCENTER | Graphics.TOP);
} else {
g.setColor(Display.FG_COLOR);
g.drawImage(ARROW_RIGHT, w + 2, h / 2,
Graphics.LEFT | Graphics.VCENTER);
}
g.drawString(twoDigits(minute), 0, 0,
Graphics.LEFT | Graphics.TOP);
}
}
/**
* Traverse the clock
*
* @param action
* @param top
* @param bottom
* @return int
*/
int traverseClock(int action, int top, int bottom) {
int hrInc = 1;
switch (action) {
case Canvas.LEFT:
if (timeSel == Calendar.MINUTE) {
timeSel = Calendar.HOUR;
return 0;
} else if (CLOCK_USES_AM_PM) {
if (timeSel == Calendar.AM_PM) {
timeSel = Calendar.MINUTE;
} else if (timeSel == Calendar.HOUR) {
timeSel = Calendar.AM_PM;
}
return 0;
}
return -1;
case Canvas.RIGHT:
if (timeSel == Calendar.HOUR) {
timeSel = Calendar.MINUTE;
return 0;
} else if (CLOCK_USES_AM_PM) {
if (timeSel == Calendar.MINUTE) {
timeSel = Calendar.AM_PM;
} else if (timeSel == Calendar.AM_PM) {
timeSel = Calendar.HOUR;
}
return 0;
}
return -1;
case Canvas.UP:
if (CLOCK_USES_AM_PM && (timeSel == Calendar.AM_PM)) {
hrInc = 12;
} else if (timeSel == Calendar.MINUTE) {
int m = calendar.get(Calendar.MINUTE);
if (m == 59) {
calendar.set(Calendar.MINUTE, 0);
} else {
calendar.set(Calendar.MINUTE, m + 1);
hrInc = 0;
}
}
calendar.set(Calendar.HOUR_OF_DAY,
(hrInc + calendar.get(Calendar.HOUR_OF_DAY)) % 24);
return 0;
case Canvas.DOWN:
if (CLOCK_USES_AM_PM && (timeSel == Calendar.AM_PM)) {
hrInc = 12;
} else if (timeSel == Calendar.MINUTE) {
int m = calendar.get(Calendar.MINUTE);
if (m == 0) {
calendar.set(Calendar.MINUTE, 59);
} else {
calendar.set(Calendar.MINUTE, m - 1);
hrInc = 0;
}
}
hrInc = 24 - hrInc;
calendar.set(Calendar.HOUR_OF_DAY,
(hrInc + calendar.get(Calendar.HOUR_OF_DAY)) % 24);
return 0;
}
return -1;
} // traverseClock()
/**
* Traverse the Date editor
*
* @param action
* @param top
* @param bottom
* @return int
*/
int traverseDate(int action, int top, int bottom) {
int scrollHeight = 0;
switch (action) {
case Canvas.LEFT:
if (highlight == 1) {
return -1;
}
if (highlight > 1) {
highlight--;
if (calendar.get(Calendar.DAY_OF_WEEK) ==
Calendar.SUNDAY) {
int topOfHighlight = highlightY(false);
if (topOfHighlight < top) {
scrollHeight = top - topOfHighlight;
}
}
break;
}
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
if (highlight == 0) {
if (month > Calendar.JANUARY) {
int day = calendar.get(Calendar.DATE);
lastDay = daysInMonth(month - 1, year);
if (day > lastDay) {
calendar.set(Calendar.DATE, lastDay);
}
calendar.set(Calendar.MONTH, month - 1);
} else {
lastDay = 31;
calendar.set(Calendar.MONTH, Calendar.DECEMBER);
calendar.set(Calendar.YEAR, year - 1);
}
} else if (highlight == -1) {
calendar.set(Calendar.YEAR, year - 1);
lastDay = daysInMonth(month, year);
}
setDayOffset();
break;
case Canvas.RIGHT:
if (highlight == lastDay) {
return -1;
}
if ((highlight > 0) && (highlight < lastDay)) {
highlight++;
if (calendar.get(Calendar.DAY_OF_WEEK) ==
Calendar.SATURDAY) {
int bottomOfHighlight = highlightY(true);
if (bottomOfHighlight > bottom) {
scrollHeight = bottomOfHighlight - bottom;
}
}
break;
}
year = calendar.get(Calendar.YEAR);
month = calendar.get(Calendar.MONTH);
if (highlight == 0) {
if (month < Calendar.DECEMBER) {
int day = calendar.get(Calendar.DATE);
lastDay = daysInMonth(month + 1, year);
if (day > lastDay) {
calendar.set(Calendar.DATE, lastDay);
}
calendar.set(Calendar.MONTH, month + 1);
} else {
calendar.set(Calendar.MONTH, Calendar.JANUARY);
calendar.set(Calendar.YEAR, year + 1);
}
} else if (highlight == -1) {
calendar.set(Calendar.YEAR, year + 1);
lastDay = daysInMonth(month, year);
}
setDayOffset();
break;
case Canvas.UP:
if (highlight == -1) {
return -1;
}
if (highlight == 0) {
highlight = -1;
} else if (highlight <= 7) {
highlight = 0;
} else {
highlight -= 7;
}
int topOfHighlight = highlightY(false);
if (topOfHighlight < top) {
scrollHeight = top - topOfHighlight;
}
break;
case Canvas.DOWN:
if (highlight == lastDay) {
return -1;
}
if (highlight == -1) {
highlight = 0;
} else if (highlight == 0) {
highlight = 1;
} else if ((highlight + 7) <= lastDay) {
highlight += 7;
} else if ((highlight + 7) > lastDay) {
highlight = lastDay;
}
int bottomOfHighlight = highlightY(true);
if (bottomOfHighlight > bottom) {
scrollHeight = bottomOfHighlight - bottom;
}
break;
default:
return -1;
}
if (highlight > 0) {
calendar.set(Calendar.DATE, highlight);
}
return scrollHeight;
} // traverseDate()
/**
* Utility method to calculate the number of days
* in a month
*
* @param month The month to use
* @param year The year the month occurs in
* @return int The number of days in the month
*/
private int daysInMonth(int month, int year) {
switch (month) {
case Calendar.JANUARY:
case Calendar.MARCH:
case Calendar.MAY:
case Calendar.JULY:
case Calendar.AUGUST:
case Calendar.OCTOBER:
case Calendar.DECEMBER:
return 31;
case Calendar.FEBRUARY:
if (((year % 400) == 0)
|| (((year & 3) == 0) && ((year % 100) != 0))) {
return 29;
}
return 28;
case Calendar.APRIL:
case Calendar.JUNE:
case Calendar.SEPTEMBER:
case Calendar.NOVEMBER:
default:
return 30;
}
}
/**
* The "large" Font
*/
Font large = Font.getFont(Font.FACE_SYSTEM,
Font.STYLE_PLAIN,
Font.SIZE_LARGE);
/**
* The "regular" font
*/
Font regular = Font.getFont(Font.FACE_SYSTEM,
Font.STYLE_PLAIN,
Font.SIZE_SMALL);
/**
* The "bold" font
*/
Font bold = Font.getFont(Font.FACE_SYSTEM,
Font.STYLE_BOLD,
Font.SIZE_SMALL);
/**
* Set the highlight
*
* @param addLineHeight
* @return int
*/
int highlightY(boolean addLineHeight) {
if (highlight == -1) {
return addLineHeight ? regular.getBaselinePosition() : 0;
} else if (highlight == 0) {
return regular.getBaselinePosition()
+ (addLineHeight ? regular.getHeight() : 0);
} else {
int line = 1 + (highlight + dayOffset - 2) / 7;
return (regular.getBaselinePosition() + 1) * line +
regular.getHeight() +
(addLineHeight ? regular.getBaselinePosition() : 0);
}
}
/**
* Paint the Calendar
*
* @param g The Graphics context to paint to
*/
void paintCalendar(Graphics g) {
g.setColor(Display.ERASE_COLOR);
g.fillRect(g.getClipX(), g.getClipY(),
g.getClipWidth(), g.getClipHeight());
boolean currentDateOnDisplay = false;
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int selection = calendar.get(Calendar.DATE);
// check if currentDate is on display
int currYear = currentDate.get(Calendar.YEAR);
int currMonth = currentDate.get(Calendar.MONTH);
int currDate = currentDate.get(Calendar.DATE);
if ((currYear == year) && (currMonth == month)) {
currentDateOnDisplay = true;
}
g.setFont(regular);
int w = regular.stringWidth("0000");
int h = regular.getBaselinePosition();
g.translate(0, -1);
if (highlight == -1) {
g.setColor(Display.BG_H_COLOR);
g.fillRect(((width - w) / 2) - 1, 1, w + 1, h);
g.setColor(Display.FG_H_COLOR);
} else {
g.setColor(Display.FG_COLOR);
}
g.drawString("" + year, width / 2, 0,
Graphics.TOP | Graphics.HCENTER);
g.drawImage(ARROW_LEFT, (width - w) / 2 - 2, h / 2,
Graphics.VCENTER | Graphics.RIGHT);
g.drawImage(ARROW_RIGHT, (width + w) / 2 + 2, h / 2,
Graphics.VCENTER | Graphics.LEFT);
g.translate(0, h + 1);
w = regular.stringWidth(Resource.getString(MONTH_NAMES[month]));
h = regular.getHeight();
if (highlight == 0) {
g.setColor(Display.BG_H_COLOR);
g.fillRect(((width - w) / 2) - 1, 1, w + 2, h);
g.setColor(Display.FG_H_COLOR);
} else {
g.setColor(Display.FG_COLOR);
}
g.setFont(regular);
g.drawString(Resource.getString(MONTH_NAMES[month]), width / 2, 0,
Graphics.TOP | Graphics.HCENTER);
g.drawImage(ARROW_LEFT, (width - w) / 2 - 2, h / 2,
Graphics.VCENTER | Graphics.RIGHT);
g.drawImage(ARROW_RIGHT, (width + w) / 2 + 2, h / 2,
Graphics.VCENTER | Graphics.LEFT);
g.translate(0, h);
int o = width / 14;
int rem = width % 14 / 2;
int x = o * (dayOffset * 2 - 1) + rem;
int y = 0;
int lastCol = 14 * o;
h = regular.getBaselinePosition() + 1;
for (int i = 1; i <= lastDay; ++i) {
String str = "" + i;
if (i == highlight) {
w = regular.stringWidth(str);
g.setColor(Display.BG_H_COLOR);
g.fillRect(x - w / 2 - 1, y + 1, w + 1, h - 1);
g.setColor(Display.FG_H_COLOR);
} else {
g.setColor(Display.FG_COLOR);
}
g.setFont(((currentDateOnDisplay) &&
(i == currDate) &&
(i != highlight)) ? bold : regular);
g.drawString(str, x, y, Graphics.TOP | Graphics.HCENTER);
x += 2 * o;
if (x > lastCol) {
x = o + rem;
y += h;
}
}
}
/**
* Set the day offset
*/
private void setDayOffset() {
Date save = calendar.getTime();
calendar.set(Calendar.DATE, 1);
dayOffset = calendar.get(Calendar.DAY_OF_WEEK);
if (Resource.getFirstDayOfWeek() != Calendar.SUNDAY) {
dayOffset = (dayOffset == 1) ? 7 : (dayOffset - 1);
}
calendar.setTime(save);
}
/**
* Determine if this editor is the edit screen for the
* given Displayable.
*
* @param d The Displayable to check
* @return boolean True if the Displayable is equal to this editor's
* return screen (ie, the screen containing the
* originating field being edited)
*/
boolean isEditScreen(Displayable d) {
return d == returnScreen;
}
} // EditScreen
}
|