package com.habitsoft.kiyaa.widgets;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.TableSectionElement;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.dom.client.HasChangeHandlers;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.HasCloseHandlers;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.i18n.client.constants.DateTimeConstants;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.Widget;
import com.habitsoft.kiyaa.util.HoverStyleHandler;
public class Calendar extends ComplexPanel implements HasCloseHandlers<Calendar>, HasClickHandlers, HasChangeHandlers {
class CalendarButton extends Label {
public CalendarButton() {
setStylePrimaryName("button");
}
}
int year, month, dayOfMonth;
boolean closable = true;
TableElement table;
TableSectionElement thead;
TableSectionElement tbody;
TableSectionElement tfoot;
TableRowElement titleRow;
TableRowElement navRow;
TableRowElement dayNames;
final CalendarConstants constants = GWT.create(CalendarConstants.class);
final DateTimeConstants dtc = LocaleInfo.getCurrentLocale().getDateTimeConstants();
private Label closeButton;
int minYear = 1970;
int maxYear = 2999;
int firstDayOfWeek = 0; // 0 for sunday
boolean showOtherMonths;
HoverStyleHandler.Group hoverGroup = new HoverStyleHandler.Group();
HoverStyleHandler.Group rowHoverGroup = new HoverStyleHandler.Group();
public Calendar() {
setElement(DOM.createDiv());
setStylePrimaryName("calendar");
table = TableElement.as(DOM.createTable());
table.setCellPadding(0);
table.setCellSpacing(0);
thead = table.createTHead();
createTitleRow();
createNavRow();
createDayNameRow();
tbody = TableSectionElement.as(DOM.createTBody());
table.appendChild(tbody);
rows = new ArrayList();
for(int i=0; i < 6; i++) {
rows.add(new WeekRow());
}
setDate(new Date());
getElement().appendChild(table);
}
static native void disableSelection(Element elt) /*-{
elt.onselectstart = function(){return false;}
}-*/;
class NavButton extends Label {
public NavButton() {
setStyleName("button");
HoverStyleHandler hsh = new HoverStyleHandler(this, hoverGroup);
addMouseOutHandler(hsh);
addMouseOverHandler(hsh);
addMouseUpHandler(hsh);
addMouseDownHandler(hsh);
disableSelection(getElement());
}
public NavButton(String text) {
this();
setText(text);
}
// @Override
// public void onBrowserEvent(Event event) {
// if((event.getTypeInt() & (Event.ONMOUSEDOWN|Event.ONMOUSEUP|Event.ONCLICK|Event.ONDBLCLICK)) != 0) {
// event.cancelBubble(true);
// event.preventDefault();
// }
// super.onBrowserEvent(event);
// }
}
class DayLabel extends NavButton implements ClickHandler {
int row;
int col;
boolean otherMonth;
boolean disabled;
Date date;
public DayLabel(int row, int col) {
this.row = row;
this.col = col;
setStyleName("day");
addClickHandler(this);
}
public void onClick(ClickEvent event) {
if(!disabled)
cellClicked(this, event);
}
public void setOtherMonth(boolean otherMonth) {
this.otherMonth = otherMonth;
if(otherMonth) {
if(showOtherMonths) {
addStyleName("othermonth");
disabled = false;
} else {
addStyleName("emptycell");
setHTML(" ");
disabled = true;
}
} else {
disabled = false;
}
}
public void setDate(Date date) {
this.date = date;
}
public Date getDate() {
return date;
}
}
class WeekRow extends Widget {
ArrayList<DayLabel> days = new ArrayList();
TableRowElement row;
int rowNum;
public WeekRow() {
rowNum = tbody.getRows().getLength();
row = tbody.insertRow(-1);
setElement(row);
HoverStyleHandler hoverStyleHandler = new HoverStyleHandler(this, rowHoverGroup);
for(int j=7; j > 0; j--) {
TableCellElement cell = row.insertCell(-1);
DayLabel cellLabel = new DayLabel(rowNum,j);
cellLabel.addMouseDownHandler(hoverStyleHandler);
cellLabel.addMouseUpHandler(hoverStyleHandler);
cellLabel.addMouseOverHandler(hoverStyleHandler);
cellLabel.addMouseOutHandler(hoverStyleHandler);
add(cellLabel, cell);
days.add(cellLabel);
}
}
}
ArrayList<WeekRow> rows;
private Label title;
public void cellClicked(DayLabel cellLabel, ClickEvent click) {
//GWT.log("Calendar cell clicked - "+cellLabel.col+","+cellLabel.row+" date "+cellLabel.getDate(), null);
// If they clicked a new date, fire a change event. Otherwise,
// it's a double-click so close the pop-up.
if(setDate(cellLabel.getDate())) {
DomEvent.fireNativeEvent(Document.get().createChangeEvent(), this);
} else if(closable) {
CloseEvent.fire(this, this);
}
fireEvent(click);
}
private void createDayNameRow() {
dayNames = thead.insertRow(-1);
setStyleName(dayNames, "daynames");
for(String wd : dtc.shortWeekdays()) {
TableCellElement td = dayNames.insertCell(-1);
td.setInnerText(wd);
setStyleName(td, "name");
boolean weekend = wd.equals(dtc.shortWeekdays()[0]) || wd.equals(dtc.shortWeekdays()[6]);
if(weekend) setStyleName(td, "weekend", true);
}
}
private void createTitleRow() {
titleRow = thead.insertRow(-1);
/*
NavButton help = new NavButton("?");
help.setStyleName("button");
help.addStyleName("nav");
help.addClickHandler(new ClickHandler() {
public void onClick(Widget sender) {
showHelpPopup();
}
});
add(help, titleRow.insertCell(-1));
help.addMouseListener(new HoverStyleHandler(help, hoverGroup));
*/
addPlaceholder();
title = new Label();
title.setStyleName("title");
TableCellElement titleCell = titleRow.insertCell(-1);
titleCell.setColSpan(5);
add(title, titleCell);
if(closable) {
closeButton = new NavButton("\u00d7");
closeButton.setTitle(constants.getClose());
closeButton.addStyleName("title");
closeButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
CloseEvent.fire(Calendar.this, Calendar.this);
}
});
add(closeButton, titleRow.insertCell(-1));
} else {
addPlaceholder();
}
//if(weekNumbers) titleLength++;
}
private void addPlaceholder() {
Label placeHolder = new Label(" ");
placeHolder.setStyleName("title");
add(placeHolder, titleRow.insertCell(-1));
}
protected void showHelpPopup() {
Window.alert(constants.getAbout());
}
private void createNavRow() {
navRow = thead.insertRow(-1);
setStyleName(navRow, "headrow");
Label prevYear = new NavButton("\u00ab");
prevYear.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
gotoPrevYear();
}
});
add(prevYear, navRow.insertCell(-1));
Label prevMonth = new NavButton("\u2039");
prevMonth.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
gotoPrevMonth();
}
});
add(prevMonth, navRow.insertCell(-1));
Label today = new NavButton(constants.getToday());
today.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
gotoToday();
}
});
today.setTitle(constants.getGoToday());
TableCellElement todayCell = navRow.insertCell(-1);
todayCell.setColSpan(3);
add(today, todayCell);
Label nextMonth = new NavButton("\u203a");
nextMonth.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
gotoNextMonth();
}
});
add(nextMonth, navRow.insertCell(-1));
Label nextYear = new NavButton("\u00bb");
nextYear.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
gotoNextYear();
}
});
add(nextYear, navRow.insertCell(-1));
}
private void add(Label child, com.google.gwt.dom.client.Element container) {
// Detach new child.
child.removeFromParent();
// Logical attach.
getChildren().add(child);
// Physical attach.
container.appendChild(child.getElement());
// Adopt.
adopt(child);
}
public void gotoPrevYear() {
year -= 1;
dateChanged();
}
public void gotoNextYear() {
year += 1;
dateChanged();
}
public void gotoPrevMonth() {
endOfNextMonth(-1);
if(month <= 1) {
year--;
month = 12;
} else {
month--;
}
dateChanged();
}
public void gotoNextMonth() {
endOfNextMonth(1);
if(month == 12) {
month = 1;
year++;
} else {
month++;
}
dateChanged();
}
private boolean isLastDayOfLongStartMonth;
private boolean isSpecialCaseForFebruary;
/**
* A little helper function created to resolve the issue when the user is on the last
* day of a month with 31 days and jumps forward/backward a month to a month with
* 30 days in it.
*/
private void endOfNextMonth(int dir){
int[] shortMonths = {4,6,9,11};//months with 30 days
int[] longMonths = {1,3,5,7,8,10,12};//months with 31 days
int nextMonth = month + dir;
switch(nextMonth){
case 0: nextMonth = 12; break;
case 13: nextMonth = 1; break;
default: break;
}
if(nextMonth == 2 && isSpecialCaseForFebruary){//month = 2 is February (28/29 days)
dayOfMonth = lastDayOfFeb(year);
}else if(nextMonth == 2 && isLastDayOfLongStartMonth){
dayOfMonth = lastDayOfFeb(year);
} else {
if(dayOfMonth == 31){
for(int shortMonth : shortMonths){
if(nextMonth == shortMonth){
dayOfMonth = 30;
break;
}
}
} else if (isLastDayOfLongStartMonth ){
for(int longMonth : longMonths){
if(nextMonth == longMonth){
dayOfMonth = 31;
break;
}
}
}
}
}
private int lastDayOfFeb(int year){
if(isLeapYear(year))
return 29;
else
return 28;
}
/**
* I had to write this little function becasue whenever I tried to use the GregorianCalendar.isLeapYear(int)
* the application would not load... very strange bug.
* @param year
* @return
*/
private boolean isLeapYear(int year){
if (year % 400 == 0)
return true;
else if (year % 100 == 0)
return false;
else if (year % 4 == 0)
return true;
else
return false;
}
public void gotoToday() {
setDate(new Date());
}
public Date getDate() {
return createDate();
}
private Date createDate() {
return new Date(year-1900, month-1, dayOfMonth);
}
/**
* Set the date given.
*
* Returns true if the date passed in differs from the date
* already set.
*/
@SuppressWarnings("deprecation")
public boolean setDate(Date selectedDate) {
if(selectedDate == null)
throw new NullPointerException("Null dates not allowed for Calendar display");
int newYear = selectedDate.getYear()+1900;
int newMonth = selectedDate.getMonth()+1;
int newDayOfMonth = selectedDate.getDate();
//two booleans used to help navigate the last day of the month, month-to-month across various months
if(newDayOfMonth == 31)
isLastDayOfLongStartMonth = true;
else
isLastDayOfLongStartMonth = false;
if(newDayOfMonth == 29 || newDayOfMonth == 30)
isSpecialCaseForFebruary = true;
else
isSpecialCaseForFebruary = false;
if(newYear != this.year || newMonth != this.month || newDayOfMonth != this.dayOfMonth) {
this.year = newYear;
this.month = newMonth;
this.dayOfMonth = newDayOfMonth;
dateChanged();
return true;
}
return false;
}
@SuppressWarnings("deprecation")
private void dateChanged() {
Date today = new Date();
int thisYear = today.getYear()+1900;
int thisMonth = today.getMonth();
int thisDay = today.getDate();
//setVisible(table, false);
try {
if(year < minYear) {
year = minYear;
} else if(year > maxYear) {
year = maxYear;
}
// calendar voodoo for computing the first day that would actually be
// displayed in the calendar, even if it's from the previous month.
// WARNING: this is magic. ;-)
Date tempDate = createDate();
tempDate.setDate(1);
int day1 = (tempDate.getDay() - firstDayOfWeek) % 7;
if(day1 < 0)
day1 += 7;
tempDate.setDate(-day1 + 1);
//tempDate.setDate(tempDate.getDate()+1);
for(WeekRow row : rows) { // up to 6 rows
setStyleName(row.row, "daysrow");
boolean hasDays = false;
for(DayLabel dayLabel : row.days) {
int iday = tempDate.getDate();
dayLabel.setStyleName("day");
dayLabel.setText(String.valueOf(iday));
dayLabel.setDate(new Date(tempDate.getTime()));
boolean currentMonth = (tempDate.getMonth() == (month-1));
dayLabel.setOtherMonth(!currentMonth);
if(!dayLabel.disabled) {
hasDays = true;
if(currentMonth && iday == dayOfMonth) {
dayLabel.addStyleName("selected");
}
if(tempDate.getYear() == thisYear
&& tempDate.getMonth() == thisMonth
&& tempDate.getDate() == thisDay) {
dayLabel.addStyleName("today");
}
int wday = tempDate.getDay();
boolean isWeekend = wday == 0 || wday == 6;
if(isWeekend)
dayLabel.addStyleName(currentMonth?"weekend":"oweekend");
// } else {
// GWT.log("Cell date "+tempDate+" disabled; not in the same month as "+date, null);
}
// Advance to the next day
tempDate.setDate(iday+1);
}
if(!(hasDays || showOtherMonths)) {
setStyleName(row.row, "emptyrow");
}
}
title.setText(dtc.months()[month-1]+", "+year);
} catch(Exception e) {
Log.error("Error updating calendar for year="+thisYear+" month="+thisMonth+" day="+thisDay+": "+e, e);
} finally {
//setVisible(table, true);
}
}
public boolean isClosable() {
return closable;
}
public void setClosable(boolean closable) {
this.closable = closable;
}
public HandlerRegistration addCloseHandler(CloseHandler<Calendar> handler) {
return addHandler(handler, CloseEvent.getType());
}
public HandlerRegistration addChangeHandler(ChangeHandler handler) {
return addHandler(handler, ChangeEvent.getType());
}
public HandlerRegistration addClickHandler(ClickHandler handler) {
return addHandler(handler, ClickEvent.getType());
}
@SuppressWarnings("deprecation")
public void gotoNextWeek() {
Date d = createDate();
d.setDate(d.getDate()+7);
setDate(d);
}
@SuppressWarnings("deprecation")
public void gotoPrevWeek() {
Date d = createDate();
d.setDate(d.getDate()-7);
setDate(d);
}
@SuppressWarnings("deprecation")
public void gotoPrevDay() {
Date d = createDate();
d.setDate(d.getDate()-1);
setDate(d);
}
@SuppressWarnings("deprecation")
public void gotoNextDay() {
Date d = createDate();
d.setDate(d.getDate()+1);
setDate(d);
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDayOfMonth() {
return dayOfMonth;
}
public void setDayOfMonth(int dayOfMonth) {
this.dayOfMonth = dayOfMonth;
}
}