Java tutorial
/* * Copyright Miroslav Pokorny * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package rocket.widget.client; import java.util.Date; import java.util.HashMap; import java.util.Map; import rocket.util.client.Checker; import rocket.util.client.Utilities; import com.google.gwt.user.client.ui.ClickListener; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.HasHorizontalAlignment; import com.google.gwt.user.client.ui.HasVerticalAlignment; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.HTMLTable.CellFormatter; /** * A template class that provides most of the functionality to build a calendar * widget. THe widget itself is a grid containing optional headings and * potentially rows of weeks. A number of methods remain to be implemented by * sub classes allowing the picker to be customised. * <ul> * <li>{@link #hasHeadings()} Should be true if the date picker grid contains * headings</li> * <li>{@link #createHeading(int)} This method will be called if the grid has * headings.</li> * <li>{@link #createDayTile(int, int, int)} This method is called each time a * day tile is required during a redraw. Any type of widget may be created.</li> * </ul> * * The date picker itself does not contain any listener methods. If a developer * needs to add something along the lines of a {@link ClickListener} to each day * tile that is created to determine which day was selected. A Calendar is * typically decorated by other buttons that allow the user to move forward or * back a month. Moving the month forward by one may be achieved by the * following * * <pre> * datePicker.setMonth(datePicker.getMonth() + 1); * </pre> * * Each time the date value of the Calendar is changed the {@link #redraw()} * must be called after values are completed. The methods that change the date * are * <ul> * <li>{@link #setMonth(int)}</li> * <li>{@link #setYear(int)}</li> * </ul> * * The date picker is always positioned so that the start of the current month * is within the first week belonging to the calendar. */ abstract public class Calendar extends CompositeWidget { public Calendar() { super(); } @Override protected Widget createWidget() { this.setDate(this.createDate()); return this.createCalendarGrid(); } @Override protected String getInitialStyleName() { return WidgetConstants.CALENDAR_STYLE; } @Override protected int getSunkEventsBitMask() { return 0; } @Override public void onAttach() { super.onAttach(); this.redraw(); } /** * This method should be called whenever there is a need to repaint or * redraw all the day cells making up the calendar. */ public void redraw() { final CalendarGrid grid = this.getCalendarGrid(); final Date date = this.getDate(); long ticks = this.getDate().getTime(); ticks = ticks - date.getDay() * WidgetConstants.CALENDAR_MILLISECONDS_IN_A_DAY; date.setTime(ticks); final String dayStyle = this.getDayStyle(); final String currentMonthStyle = this.getCurrentMonthStyle(); final String nextMonthStyle = this.getNextMonthStyle(); String monthStyle = date.getDate() != 1 ? this.getPreviousMonthStyle() : currentMonthStyle; final int rowOffset = this.hasHeadings() ? 1 : 0; final int lastRow = grid.getRowCount(); final CellFormatter cellFormatter = grid.getCellFormatter(); for (int row = rowOffset; row < lastRow; row++) { for (int column = 0; column < WidgetConstants.CALENDAR_COLUMNS; column++) { final int year = date.getYear() + WidgetConstants.CALENDAR_YEAR_BIAS; final int month = date.getMonth(); final int dayOfMonth = date.getDate(); cellFormatter.setStyleName(row, column, dayStyle); cellFormatter.addStyleName(row, column, monthStyle); final Widget widget = this.createDayTile(year, month, dayOfMonth); grid.setWidget(row, column, widget, year, month, dayOfMonth); date.setDate(dayOfMonth + 1); if (date.getDate() < dayOfMonth) { monthStyle = monthStyle.equals(currentMonthStyle) ? nextMonthStyle : currentMonthStyle; } } } } protected String getDayStyle() { return WidgetConstants.CALENDAR_DAY_STYLE; } protected String getPreviousMonthStyle() { return WidgetConstants.CALENDAR_PREVIOUS_MONTH_STYLE; } protected String getCurrentMonthStyle() { return WidgetConstants.CALENDAR_CURRENT_MONTH_STYLE; } protected String getNextMonthStyle() { return WidgetConstants.CALENDAR_NEXT_MONTH_STYLE; } /** * A calendarGrid is used to hold all the cells that make up the calendar */ protected CalendarGrid getCalendarGrid() { return (CalendarGrid) this.getWidget(); } /** * Creates a calendarGrid widget with the necessary number of rows and * columns. * * @return The new calendarGrid. */ protected CalendarGrid createCalendarGrid() { final boolean hasHeadings = this.hasHeadings(); final int rows = hasHeadings ? WidgetConstants.CALENDAR_ROWS + 1 : WidgetConstants.CALENDAR_ROWS; final CalendarGrid grid = new CalendarGrid(rows, WidgetConstants.CALENDAR_COLUMNS); if (hasHeadings) { this.addHeadings(grid); } grid.setCellPadding(0); grid.setCellSpacing(0); final CellFormatter cellFormatter = grid.getCellFormatter(); final String dayStyle = this.getDayStyle(); for (int r = 0; r < rows; r++) { for (int c = 0; c < WidgetConstants.CALENDAR_COLUMNS; c++) { cellFormatter.setAlignment(r, c, HasHorizontalAlignment.ALIGN_CENTER, HasVerticalAlignment.ALIGN_MIDDLE); cellFormatter.setWidth(r, c, "100%"); cellFormatter.setHeight(r, c, "100%"); cellFormatter.setStyleName(r, c, dayStyle); } } return grid; } /** * A specialised Grid that maintains caches that map dates to widgets and * widgets back to date making it easy to find a widget by date and vice * versa. */ static class CalendarGrid extends Grid { CalendarGrid(final int rows, final int columns) { super(rows, columns); this.setWidgetsToDates(createWidgetsToDates()); this.setDatesToWidgets(createDatesToWidgets()); } /** * This mapping uses the year/month/dayOfMonth as the key with the value * being the widget itself. */ private Map<String, Widget> datesToWidgets; Map<String, Widget> getDatesToWidgets() { return this.datesToWidgets; } void setDatesToWidgets(final Map<String, Widget> datesToWidgets) { this.datesToWidgets = datesToWidgets; } Map<String, Widget> createDatesToWidgets() { return new HashMap<String, Widget>(); } /** * This mapping uses the widget as the key and the value is the * year/month/dayOfMonth */ private Map<Widget, String> widgetsToDates; Map<Widget, String> getWidgetsToDates() { return this.widgetsToDates; } void setWidgetsToDates(final Map<Widget, String> widgetsToDates) { this.widgetsToDates = widgetsToDates; } Map<Widget, String> createWidgetsToDates() { return new HashMap<Widget, String>(); } Widget getWidget(final int year, final int month, final int dayOfMonth) { final Object key = buildKey(year, month, dayOfMonth); return this.getDatesToWidgets().get(key); } void setWidget(final int row, final int column, final Widget widget, final int year, final int month, final int dayOfMonth) { final Map<Widget, String> widgetsToDates = this.getWidgetsToDates(); final Map<String, Widget> datesToWidgets = this.getDatesToWidgets(); // remove the previous widget the caches. final Widget previous = this.getWidget(row, column); if (null != previous) { final String previousKey = widgetsToDates.remove(widget); datesToWidgets.remove(previousKey); } // update the calendarGrid itself this.setWidget(row, column, widget); // update the caches. final String key = buildKey(year, month, dayOfMonth); widgetsToDates.put(widget, key); datesToWidgets.put(key, widget); } protected String buildKey(final int year, final int month, final int dayOfMonth) { return year + "/" + month + "/" + dayOfMonth; } } /** * Adds all headings for this calendar to the first row of the calendarGrid. * This method should only be invoked once usually as part of the * {@link #createWidget()} method. * * @param grid * The new grid */ protected void addHeadings(final Grid grid) { for (int dayOfWeek = 0; dayOfWeek < WidgetConstants.CALENDAR_COLUMNS; dayOfWeek++) { final Widget heading = this.createHeading(dayOfWeek); heading.addStyleName(WidgetConstants.CALENDAR_HEADING_STYLE); grid.setWidget(0, dayOfWeek, heading); } } /** * Sub classes should override this method to return true or false depending * on whether a heading row should be included when the calendarGrid is * built. * * @return A flag */ abstract protected boolean hasHeadings(); /** * Sub classes must implement this factory method to return a widget that * will be used as a heading. Typically this might include the day of the * week. * * @param dayOfWeek * 0 = Sunday, 1 = Monday etc. * @return The widget for the given heading */ abstract protected Widget createHeading(final int dayOfWeek); /** * Sub classes must implement this factory method to create and return a * widget that will be used to display an individual day within a cell * within the calendar. * * @param year * The year. * @param month * The month starting at 0 = January. * @param day * The day of the month starting at 1 * @return The widget for the given date. */ abstract protected Widget createDayTile(final int year, final int month, final int day); /** * Retrieves the widget at the given coordinates. * * @param column * @param row * @return The widget for the day. */ public Widget getDay(final int column, final int row) { final int row0 = this.hasHeadings() ? row + 1 : row; return this.getCalendarGrid().getWidget(row0, column); } /** * Getter that makes it possible to find the corresponding widget for a * particular date. If that date does not exist within the shown datepicker * null is returned. * * @param year * @param month * @param dayOfMonth * @return The found day. */ public Widget getDay(final int year, final int month, final int dayOfMonth) { return this.getCalendarGrid().getWidget(year, month, dayOfMonth); } public void setDay(final int column, final int row, final int year, final int month, final int dayOfMonth, final Widget widget) { this.getCalendarGrid().setWidget(row, column, widget, year, month, dayOfMonth); } /** * This Date object holds the date of the first day that appears in the * calendar. */ private Date date;; protected Date getDate() { Checker.notNull("field:date", date); return new Date(this.date.getTime()); } protected void setDate(final Date date) { Checker.notNull("parameter:date", date); this.date = new Date(date.getTime()); } /** * Factory which creates the date that will become the starting point for * this calendar. * * @return A new Date */ protected Date createDate() { final Date date = new Date(); date.setDate(1); return date; } public int getYear() { return this.getDate().getYear() + WidgetConstants.CALENDAR_YEAR_BIAS; } public void setYear(final int year) { final Date date = this.getDate(); date.setYear(year - WidgetConstants.CALENDAR_YEAR_BIAS); this.setDate(date); } public int getMonth() { return this.getDate().getMonth(); } public void setMonth(final int month) { final Date date = this.getDate(); date.setMonth(month); this.setDate(date); } public String toString() { return Utilities.defaultToString(this) + ", date: " + this.getDate(); } }