/*********************************************************************************
* TotalCross Software Development Kit *
* Copyright (C) 2000-2001 Allan C. Solomon *
* Copyright (C) 2001-2012 SuperWaba Ltda. *
* All Rights Reserved *
* *
* This library and virtual machine 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. *
* *
* This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 *
* A copy of this license is located in file license.txt at the root of this *
* SDK or can be downloaded here: *
* http://www.gnu.org/licenses/lgpl-3.0.txt *
* *
*********************************************************************************/
package totalcross.ui.dialog;
import totalcross.sys.*;
import totalcross.ui.*;
import totalcross.ui.event.*;
import totalcross.ui.font.*;
import totalcross.ui.gfx.*;
import totalcross.util.*;
/** Class used to input a time from the user.
* Correctly handles the AM/PM depending on Settings.is24Hour.
* <br><br>
* When the window closes, a PRESSED event is sent to the caller, and the time can be retrieved using getTime. Here's a sample:
* <pre>
* TimeBox tb;
* public void initUI()
* {
* try
* {
* (tb=new TimeBox()).popupNonBlocking();
* }
* catch (Exception e) {e.printStackTrace();}
* }
*
*
* public void onEvent(Event e)
* {
* if (e.type == ControlEvent.PRESSED && e.target == tb)
* Vm.debug("ret: "+tb.getTime());
* }
* </pre>
* The time can be entered also using the arrow keys and by typing the numbers directly.
* @see #getTime()
* @since TotalCross 1.22
*/
public class TimeBox extends Window
{
protected Button btOK, btClear;
protected Radio btAM,btPM;
protected Visor visor;
private RadioGroupController rg;
private int pos;
private Time sentTime;
private char []chars;
private Button []btNumbers = new Button[10];
/** Used in the button. Change it if you want to localize the text. */
public static String okCaption = "OK";
/** Used in the button. Change it if you want to localize the text. */
public static String clearCaption = "Clear";
/** Set to false to disable the buttons instead of hiding them if they are not a valid number to appear at the current position.
* By default, the buttons are hidden, but setting this to false can disable them instead.
*/
public static boolean hideIfInvalid = true;
/** Constructs a TimeBox with time set to midnight. */
public TimeBox()
{
this(new Time(0,0,0,0,0,0,0));
}
/** Constructs a TimeBox with the given time. If the time is invalid, it is set to midnight. */
public TimeBox(Time time)
{
super(uiAndroid ? "" : " ", uiAndroid ? ROUND_BORDER : RECT_BORDER);
setTime(time);
uiAdjustmentsBasedOnFontHeightIsSupported = false;
fadeOtherWindows = Settings.fadeOtherWindows;
transitionEffect = Settings.enableWindowTransitionEffects ? TRANSITION_OPEN : TRANSITION_NONE;
setBackColor(UIColors.timeboxBack);
}
/** Set the time, if it was not yet set in the constructor. */
public void setTime(Time time)
{
this.sentTime = time;
if (!time.isValid())
time.hour = time.minute = time.second = 0;
}
public void onFontChanged()
{
visor.setFont(Font.getFont(this.font.name, this.font.style == 1, this.font.size+2));
}
private class Visor extends Container
{
private int w0,wp;
public Visor()
{
setBorderStyle(uiVista ? BORDER_SIMPLE : BORDER_LOWERED);
setBackColor(UIColors.timeboxVisorBack);
}
public void onFontChanged()
{
w0 = fm.charWidth('0');
wp = (w0 - fm.charWidth(':'))/2;
}
public void onPaint(Graphics g)
{
super.onPaint(g);
int xx = (this.width-w0*8)/2;
int yy = (this.height-fmH)/2;
g.backColor = UIColors.timeboxVisorCursor;
g.fillRect(pos * w0 + xx-2, yy, w0+2,fmH);
g.foreColor = getForeColor();
int sh = TimeBox.this.textShadowColor;
for (int i =0; i < chars.length; i++, xx += w0)
g.drawText(chars, i, 1, i == 2 || i == 5 ? xx+wp : xx, yy, sh != -1, sh);
}
public int getPreferredHeight()
{
return fmH + 4;
}
}
protected void onPopup()
{
pos = 0;
removeAll();
setInsets(0, 0, 0, 5);
Button.commonGap = Settings.fingerTouch && fmH > 15 ? fmH*2/3 : fmH/2;
btNumbers[7] = new Button("7");
int wh = btNumbers[7].getPreferredWidth();
setRect(CENTER, CENTER, wh * 3 + 2 + getClientRect().x*2 + 10, WILL_RESIZE);
visor = new Visor();
onFontChanged();
add(visor, LEFT + 5, TOP + 5, FILL - 5, PREFERRED);
add(btNumbers[7], LEFT + 5, AFTER + 5, wh, wh);
add(btNumbers[8] = new Button("8"), AFTER + 1, SAME, SAME, SAME);
add(btNumbers[9] = new Button("9"), AFTER + 1, SAME, SAME, SAME, btNumbers[8]);
add(btNumbers[4] = new Button("4"), LEFT + 5, AFTER + 1, SAME, SAME);
add(btNumbers[5] = new Button("5"), AFTER + 1, SAME, SAME, SAME);
add(btNumbers[6] = new Button("6"), AFTER + 1, SAME, SAME, SAME);
add(btNumbers[1] = new Button("1"), LEFT + 5, AFTER + 1, SAME, SAME);
add(btNumbers[2] = new Button("2"), AFTER + 1, SAME, SAME, SAME);
add(btNumbers[3] = new Button("3"), AFTER + 1, SAME, SAME, SAME);
add(btNumbers[0] = new Button("0"), SAME, AFTER + 1, wh * 3 + 2, SAME, btNumbers[1]);
// trick: store in appId the button's value (+10)
btNumbers[1].appId = 11; btNumbers[2].appId = 12; btNumbers[3].appId = 13;
btNumbers[4].appId = 14; btNumbers[5].appId = 15; btNumbers[6].appId = 16;
btNumbers[7].appId = 17; btNumbers[8].appId = 18; btNumbers[9].appId = 19; btNumbers[0].appId = 10;
if (!Settings.is24Hour)
{
Spacer l;
add(l = new Spacer(), CENTER, AFTER + 2);
rg = new RadioGroupController();
add(btAM = new Radio("am", rg), LEFT + 5, SAME+fmH/2, l.getX() - 5, PREFERRED);
add(btPM = new Radio("pm", rg), AFTER, SAME+fmH/2, FILL - 5, PREFERRED, l);
add(new Spacer(1,fmH/2),CENTER,AFTER+2);
btAM.leftJustify = btPM.leftJustify = true;
btAM.clearValueInt = 1;
}
Spacer l;
add(l = new Spacer(), CENTER, AFTER + 2);
add(btOK = new Button(okCaption), LEFT + 5, SAME, l.getX() - 5, wh);
add(btClear = new Button(clearCaption), AFTER, SAME, FILL - 5, wh, l);
btOK.setBackColor(UIColors.timeboxOk);
btClear.setBackColor(UIColors.timeboxClear);
Button.commonGap = 0;
if (Settings.is24Hour)
tabOrder = new Vector(new Control[]{btNumbers[9],btNumbers[8],btNumbers[7],btNumbers[6],btNumbers[5],btNumbers[4],btNumbers[3],btNumbers[2],btNumbers[1],btNumbers[0],btOK,btClear});
else
{
rg.setSelectedIndex(sentTime.hour <= 11 ? 0 : 1);
if (sentTime.hour >= 12)
sentTime.hour -= 12;
tabOrder = new Vector(new Control[]{btNumbers[9],btNumbers[8],btNumbers[7],btNumbers[6],btNumbers[5],btNumbers[4],btNumbers[3],btNumbers[2],btNumbers[1],btNumbers[0],btAM,btPM,btOK,btClear});
}
String s = Convert.zeroPad(sentTime.hour,2)+":"+Convert.zeroPad(sentTime.minute,2)+":"+Convert.zeroPad(sentTime.second,2);
chars = s.toCharArray();
enableButtons();
resizeHeight();
}
/** Returns the time placed in this control, which is the time passed in the constructor (if any) updated with the hours, minutes and seconds. */
public Time getTime()
{
sentTime.hour = (chars[0]-'0')*10 + (chars[1]-'0');
sentTime.minute = (chars[3]-'0')*10 + (chars[4]-'0');
sentTime.second = (chars[6]-'0')*10 + (chars[7]-'0');
if (!Settings.is24Hour && rg.getSelectedIndex() == 1)
sentTime.hour += 12;
return sentTime;
}
public void reposition()
{
setRect(CENTER,CENTER,KEEP,KEEP);
}
public void onEvent(Event event)
{
switch (event.type)
{
case KeyEvent.KEY_PRESS:
{
KeyEvent ke = (KeyEvent)event;
if ('0' <= ke.key && ke.key <= '9')
{
if (isShown((char)ke.key))
onKey((char)ke.key);
}
break;
}
case KeyEvent.SPECIAL_KEY_PRESS:
{
KeyEvent ke = (KeyEvent)event;
if (ke.isUpKey())
{
chars[pos]++;
if (chars[pos] > '9' || !isShown(chars[pos]))
chars[pos] = '0';
Window.needsPaint = true;
}
else
if (ke.isDownKey())
{
chars[pos]--;
if (chars[pos] < '0')
{
chars[pos] = (char)('9'+1);
do
{
chars[pos]--;
}
while (!isShown(chars[pos]));
}
Window.needsPaint = true;
}
else
if (ke.isNextKey())
{
if (++pos == 8)
pos = 0;
if (pos == 2 || pos == 5)
pos++;
enableButtons();
}
else
if (ke.isPrevKey())
{
if (--pos == -1)
pos = 7;
if (pos == 2 || pos == 5)
pos--;
enableButtons();
}
else
if (ke.isActionKey())
doClose();
else
if (ke.key == SpecialKeys.ESCAPE)
doClear();
break;
}
case ControlEvent.PRESSED:
{
Control c = (Control) event.target;
if (c.appId > 0)
onKey((char)(c.appId-10 + '0'));
else
if (event.target == btOK)
doClose();
else
if (event.target == btClear)
doClear();
break;
}
}
}
private void doClear()
{
clear();
chars[0] = chars[1] = chars[3] = chars[4] = chars[6] = chars[7] = '0';
pos = 0;
enableButtons();
}
private void doClose()
{
for (pos = 0; pos < 8; pos++) // check all positions before closing
{
if (pos == 2 || pos == 5) pos++;
enableButtons();
}
unpop();
}
private void onKey(char key)
{
chars[pos++] = key;
if (pos == 2 || pos == 5)
pos++; // skip the :
if (pos == 8) // start over
pos = 0;
enableButtons();
}
protected void postUnpop()
{
postPressedEvent();
}
private final static int H1 = 0;
private final static int H2 = 1;
private final static int M2 = 4;
private final static int S2 = 7;
private void enableButtons()
{
// 01234567
// 00:00:00
boolean aboveH1 = pos > H1;
boolean aboveH2 = pos > H2;
boolean on2 = pos == H2 || pos == M2 || pos == S2;
if (Settings.is24Hour)
{
boolean fourOrMore = aboveH2 || (aboveH1 && chars[0] <= '1');
show(3, aboveH1);
show(4, fourOrMore);
show(5, fourOrMore);
show(6, on2 && fourOrMore);
show(7, on2 && fourOrMore);
show(8, on2 && fourOrMore);
show(9, on2 && fourOrMore);
}
else
{
boolean ok2_5 = aboveH2 || (aboveH1 && chars[0] == '0');
show(2, ok2_5);
show(3, ok2_5);
show(4, ok2_5);
show(5, ok2_5);
show(6, ok2_5 && on2);
show(7, ok2_5 && on2);
show(8, ok2_5 && on2);
show(9, ok2_5 && on2);
}
if (!isShown(chars[pos])) // if the char is invalid, reset to 0
chars[pos] = '0';
Window.needsPaint = true;
}
private void show(int idx, boolean ok)
{
Button b = btNumbers[idx];
if (hideIfInvalid)
b.setVisible(ok);
else
b.setEnabled(ok);
}
private boolean isShown(char c)
{
Button b = btNumbers[c - '0'];
return hideIfInvalid ? b.isVisible() : b.isEnabled();
}
}