/*********************************************************************************
* 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.ui.*;
import totalcross.ui.event.*;
import totalcross.ui.font.*;
import totalcross.ui.gfx.*;
import totalcross.ui.image.*;
import totalcross.io.*;
import totalcross.sys.*;
import totalcross.util.*;
/**
The Calendar class displays a calendar with buttons used to advance the month and the year.
It uses the Date class for all operations.<br>
Instead of create a new instance (which consumes memory), you may
use the Edit's static field <i>calendar</i>
<p>
If there is something in the edit box which poped up the calendar, the clear
button will clear it. Cancel will leave whatever was in there.
<p>
The month can be changed via keyboard using the left/right keys, and the year can be changed using up/down keys.
*/
public class CalendarBox extends Window
{
private int day=-1,month,year;
private int sentDay,sentMonth,sentYear;
private Button btnToday, btnClear, btnCancel;
private ArrowButton btnMonthNext,btnMonthPrev,btnYearNext,btnYearPrev;
private PushButtonGroup pbgDays;
private String []tempDays = new String[42];
private StringBuffer sb = new StringBuffer(20);
private Label lYear,lMonth;
private String[] myMonthNames;
/** Defines the array length of year window. If array length is 20, and current year is 2010, the
* list goes from 2000 to 2019.
* @since TotalCross 1.66
*/
public static int YEAR_ARRAY_LENGTH = 20;
/** True if the user had canceled without selecting */
public boolean canceled;
/** The 7 week names painted in the control. */
public static String[] weekNames = {"S","M","T","W","T","F","S"}; // guich#573_39
/** The labels for Today, Clear and Cancel. */
public static String[] todayClearCancel = {"Today","Clear","Cancel"}; // guich@573_39
/** The labels between the arrows: Month, Year */
public static String[] yearMonth = new String[]{"year","month"};
/**
* Constructs Calendar set to the current day.
*/
public CalendarBox()
{
super(" ",uiAndroid ? ROUND_BORDER : RECT_BORDER); // no title yet
uiAdjustmentsBasedOnFontHeightIsSupported = false;
fadeOtherWindows = Settings.fadeOtherWindows;
transitionEffect = Settings.enableWindowTransitionEffects ? TRANSITION_OPEN : TRANSITION_NONE;
String[]defCaps = new String[42];
for (int i=0; i < 42; i++)
defCaps[i] = Convert.toString(i+10);
pbgDays = new PushButtonGroup(defCaps, false, -1, -1, fm.charWidth('@')-2, 6, true, PushButtonGroup.NORMAL);
}
private void setupUI() // guich@tc100b5_28
{
int hh = getClientRect().y+2;
int yearColor = Color.BRIGHT;
int monthColor = Color.BRIGHT;
started = true; // avoid calling the initUI method
yearColor = UIColors.calendarAction;
monthColor = UIColors.calendarAction;
setBackForeColors(UIColors.calendarBack, UIColors.calendarFore);
// compute the window's rect
Font bold = font.asBold();
Font mini = font.adjustedBy(-4);
int labH = bold.fm.height;
int arrowW = uiAndroid ? 2 * labH / 3 : labH / 2;
Label l;
Button.commonGap = 2;
btnToday = new Button(todayClearCancel[0]);
btnClear = new Button(todayClearCancel[1]);
btnCancel = new Button(todayClearCancel[2]);
btnToday.setFont(font);
btnClear.setFont(font);
btnCancel.setFont(font);
int btnH = btnCancel.getPreferredHeight();
Button.commonGap = 0;
int labelGap = Settings.screenWidth <= 320 ? 3 : 0;
lYear = new Label(Settings.screenWidth <= 320 ? yearMonth[0].substring(0,1) : yearMonth[0],CENTER);
lMonth = new Label(Settings.screenWidth <= 320 ? yearMonth[1].substring(0,1) : yearMonth[1],CENTER);
lYear.setFont(mini);
lMonth.setFont(mini);
pbgDays.insideGap = fm.charWidth('@')-2;
pbgDays.setFont(font);
int pbgW = uiAndroid ? (Math.min(fmH*16,Math.min(Settings.screenWidth,Settings.screenHeight))-20)/7*7 : pbgDays.getPreferredWidth();
int cellWH = pbgW / 7;
int captionW = bold.fm.getMaxWidth(Date.monthNames,0,Date.monthNames.length) + bold.fm.stringWidth("2011 ");
int titleW = 4*arrowW + labelGap*4 + lYear.getPreferredWidth() + lMonth.getPreferredWidth() + captionW; // guich@tc130: avoid problems if title is too small
setRect(CENTER,CENTER,Math.max(titleW,pbgW) + 10, 18+hh+labH+cellWH*6+btnH); // same gap in all corners
// arrows
btnYearPrev = new ArrowButton(Graphics.ARROW_LEFT,arrowW,yearColor);
btnYearNext = new ArrowButton(Graphics.ARROW_RIGHT,arrowW,yearColor);
btnMonthPrev = new ArrowButton(Graphics.ARROW_LEFT,arrowW,monthColor);
btnMonthNext = new ArrowButton(Graphics.ARROW_RIGHT,arrowW,monthColor);
btnYearPrev.setFont(font);
btnYearNext.setFont(font);
btnMonthPrev.setFont(font);
btnMonthNext.setFont(font);
btnYearPrev.setBorder(Button.BORDER_NONE);
btnYearNext.setBorder(Button.BORDER_NONE);
btnMonthPrev.setBorder(Button.BORDER_NONE);
btnMonthNext.setBorder(Button.BORDER_NONE);
btnYearPrev.transparentBackground = btnYearNext.transparentBackground = btnMonthPrev.transparentBackground = btnMonthNext.transparentBackground = true;
int bw = uiAndroid ? PREFERRED+fmH : PREFERRED;
int yb = (titleGap+titleFont.fm.height-labH)/2;
add(btnYearPrev,LEFT+2,yb, bw, PREFERRED);
add(lYear,AFTER,CENTER_OF,PREFERRED+labelGap*2,SAME);
add(btnYearNext,AFTER,yb, bw, PREFERRED);
add(btnMonthNext,RIGHT-2,yb, bw, PREFERRED);
add(lMonth,BEFORE,CENTER_OF,PREFERRED+labelGap*2,SAME);
add(btnMonthPrev,BEFORE,yb, bw, PREFERRED);
// change title alignment to use the area available between the buttons
titleAlign = CENTER;
// buttons
add(btnToday, LEFT+4, BOTTOM-4, PARENTSIZEMIN+30, PREFERRED);
add(btnClear, CENTER, SAME, PARENTSIZEMIN+30, PREFERRED);
add(btnCancel, RIGHT-4, SAME, PARENTSIZEMIN+30, PREFERRED);
// days
add(pbgDays);
if (!uiAndroid)
pbgDays.setSimpleBorder(true);
pbgDays.setRect(CENTER+3, BEFORE-4,pbgW,6*cellWH+1); // +3 = 6 (buttons that have joined sides) / 2
pbgDays.setCursorColor(Color.brighter(yearColor));
// weeks
int xx = pbgDays.getX();
for (int i =0; i < 7; i++)
{
l = new Label(weekNames[i]);
l.setFont(bold);
Rect r = pbgDays.rects[i];
add(l, xx+r.x+(r.width-l.getPreferredWidth())/2, hh);
}
lYear.transparentBackground = lMonth.transparentBackground = true;
lYear.setForeColor(backColor);
lMonth.setForeColor(backColor);
btnToday.setBackColor(UIColors.calendarAction);
btnClear.setBackColor(UIColors.calendarAction);
btnCancel.setBackColor(UIColors.calendarAction);
btnYearPrev.setBackColor(UIColors.calendarArrows);
btnYearNext.setBackColor(UIColors.calendarArrows);
btnMonthNext.setBackColor(UIColors.calendarArrows);
btnMonthPrev.setBackColor(UIColors.calendarArrows);
tabOrder = new Vector(new Control[]{pbgDays,btnToday,btnClear,btnCancel,btnYearPrev,btnYearNext,btnMonthPrev,btnMonthNext});
btnYearPrev.autoRepeat = btnYearNext.autoRepeat = btnMonthNext.autoRepeat = btnMonthPrev.autoRepeat = true; // guich@tc122_47
btnYearPrev.AUTO_DELAY = btnYearNext.AUTO_DELAY = 300;
}
/**
Returns the selected Date.
@return Date object set to the selected day, or null if an error occurs.
*/
public Date getSelectedDate()
{
if (day==-1)
return null;
try
{
return new Date(day,month,year);
} catch (InvalidDateException ide) {return null;}
}
/** Sets the current day to the Date specified. If its null, sets the date to today. */
public void setSelectedDate(Date d)
{
if (d == null) d = new Date();
sentMonth = month = d.getMonth();
sentYear = year = d.getYear();
sentDay = day = d.getDay();
// sets the pbg days
updateDays();
}
private void updateDays()
{
try
{
Date date = new Date(1,month,year);
int start = date.getDayOfWeek();
int end = start+date.getDaysInMonth();
pbgDays.setSelectedIndex(-1);
int d = 1;
String[] days = tempDays;
for (int i=0; i < 42; i++)
{
pbgDays.setColor(i,-1,-1); // erase all colors
days[i] = (start <= i && i < end) ? Convert.toString(d++) : "";
}
if (Settings.keyboardFocusTraversable)
days[end] = " x"; // add a way to get out of the pbg without closing it
// set sent color
if (year == sentYear && month == sentMonth)
pbgDays.setColor(sentDay-1+start, -1, UIColors.calendarAction);
pbgDays.setNames(days);
Window.needsPaint = true;
} catch (InvalidDateException ide) {}
}
public void onEvent(Event event)
{
try
{
switch (event.type)
{
case KeyEvent.SPECIAL_KEY_PRESS:
KeyEvent ke = (KeyEvent)event;
if (ke.key == SpecialKeys.KEYBOARD_ABC || ke.key == SpecialKeys.KEYBOARD_123) // closes this window without selecting any day
{
day = sentDay; // guich@tc100
unpop();
}
else
if (ke.isDownKey()) // guich@tc122_47: here to below
incYear(false);
else
if (ke.isUpKey())
incYear(true);
else
if (ke.isNextKey())
incMonth(true);
else
if (ke.isPrevKey())
incMonth(false);
break;
case ControlEvent.PRESSED:
if (event.target == btnToday)
{
setSelectedDate(null);
day = sentDay; // guich@401_14
unpop(); // guich@401_14
} else
if (event.target == btnClear)
{
day = -1;
unpop();
} else
if (event.target == btnCancel)
{
canceled = true;
unpop();
}
else
if (event.target == pbgDays && pbgDays.getSelectedIndex() >= 0)
{
try
{
Date date = new Date(Convert.toInt(pbgDays.getSelectedItem()),month,year);
day = date.getDay();
unpop(); // closes this window
} catch (Exception ide) {day = -1;}
}
else
if (event.target == btnMonthNext)
incMonth(true);
else
if (event.target == btnMonthPrev)
incMonth(false);
else
if (event.target == btnYearNext)
incYear(true);
else
if (event.target == btnYearPrev)
incYear(false);
break;
case PenEvent.PEN_UP:
if (event.target == lMonth)
{
if (myMonthNames == null) // monthNames have a initial month with spaces, so we remove it.
{
myMonthNames = new String[12];
for (int i = 0; i < 12; i++) myMonthNames[i] = Date.monthNames[i+1];
}
int sel = showPopup(yearMonth[1].toUpperCase(),myMonthNames,month-1);
if (sel >= 0)
{
month = sel+1;
updateDays();
}
}
else
if (event.target == lYear)
{
String[] yy = new String[YEAR_ARRAY_LENGTH];
int sel = -1;
for (int i = 0; i < yy.length; i++)
{
int y = year-yy.length/2+i;
yy[i] = String.valueOf(y);
if (y == year)
sel = i;
}
sel = showPopup(yearMonth[0].toUpperCase(),yy,sel);
if (sel >= 0)
{
year = Convert.toInt(yy[sel]);
updateDays();
}
}
}
}
catch (Exception ee)
{
MessageBox.showException(ee,true);
}
}
private int showPopup(String title, String[] items, int sel) throws IOException, ImageException
{
if (uiAndroid)
{
PopupMenu pm = new PopupMenu(title,items);
pm.setSelectedIndex(sel);
pm.popup();
return pm.getSelectedIndex();
}
else
{
ComboBoxDropDown cb = new ComboBoxDropDown(new ListBox(items));
cb.fullHeight = true;
cb.lb.setSelectedIndex(sel);
Rect r = getClientRect();
r.x += this.x; r.y += this.y;
cb.setRect(r);
cb.popup();
return cb.lb.getSelectedIndex();
}
}
private void incMonth(boolean inc)
{
if (inc)
{
if (++month == 13)
{
month=1;
year++;
}
}
else
{
if (--month == 0)
{
year--;
month=12;
}
}
updateDays();
}
private void incYear(boolean inc)
{
if (inc)
year++;
else
year--;
updateDays();
}
public void onPaint(Graphics g)
{
//Draw title with appropriote month and year
sb.setLength(0);
paintTitle(sb.append(Date.getMonthName(month)).append(' ').append(year).toString(),g);
}
protected void onPopup()
{
if (children == null)
setupUI();
canceled = false;
day = -1;
setTitle(Date.getMonthName(month) + " " + (new Date().getYear()));
if (sentDay <= 0) setSelectedDate(null); // guich@300_45: makes the sentDate the default
}
protected void onUnpop()
{
setFocus(this);
}
protected void postUnpop()
{
if (!canceled) // guich@580_27
postPressedEvent();
sentDay = 0;
}
}