io.guise.framework.model.CalendarMonthTableModel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of guise-framework Show documentation
Show all versions of guise-framework Show documentation
Guise™ Internet application framework.
/*
* Copyright © 2005-2008 GlobalMentor, Inc.
*
* 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 io.guise.framework.model;
import java.text.DateFormat;
import java.util.*;
import static java.util.Objects.*;
import static com.globalmentor.java.Classes.*;
import static com.globalmentor.java.Conditions.*;
import static com.globalmentor.time.Calendars.*;
import com.globalmentor.beans.*;
import io.guise.framework.*;
import io.guise.framework.converter.*;
/**
* A table model representing the days of a calendar month. Each cell contains a {@link Date} value.
* @author Garret Wilson
*/
public class CalendarMonthTableModel extends AbstractTableModel //TODO set the model to read-only
{
//TODO switch to using calendar.getActualMaximum(calobject.DAY_OF_WEEK) or something to determine the days in the week, rather than a constant
/** The Guise session that owns this object. */
private final GuiseSession session;
/** @return The Guise session that owns this object. */
public GuiseSession getSession() {
return session;
}
/** The column style bound property. */
public static final String COLUMN_LABEL_DATE_STYLE_PROPERTY = getPropertyName(CalendarMonthTableModel.class, "columnLabelStyle");
/** The date bound property. */
public static final String DATE_PROPERTY = getPropertyName(CalendarMonthTableModel.class, "date");
/** The number of days this calendar should be offset left (negative) or right (positive) so that the days align with the correct day-of-the-week column. */
private int dayOffset = 0;
/**
* @return The number of days this calendar should be offset left (negative) or right (positive) so that the days align with the correct day-of-the-week
* column.
*/
protected int getDayOffset() {
return dayOffset;
}
/** The number of rows in this table. */
private int rowCount = 0;
@Override
public int getRowCount() {
return rowCount;
}
/** The calendar representing the first day of the month. */
private Calendar monthCalendar;
/** @return A clone of the calendar representing the first day of the month. */
protected Calendar getMonthCalendar() {
return (Calendar)monthCalendar.clone();
}
/** The date this calendar represents. */
private Date date;
/** @return The date this calendar represents. */
public Date getDate() {
return (Date)date.clone();
}
/**
* Sets the date this calendar represents. A copy will be made of the date before it is stored. This is a bound property.
* @param newDate The date this calendar is to represent.
* @throws NullPointerException if the given date is null
.
* @see #DATE_PROPERTY
*/
public void setDate(final Date newDate) {
if(!date.equals(requireNonNull(newDate, "Date cannot be null."))) { //if the value is really changing
final Date oldDate = date; //get the old value
date = (Date)newDate.clone(); //clone the new date and actually change the value
updateModel(); //update the model based upon the new value
firePropertyChange(DATE_PROPERTY, oldDate, newDate); //indicate that the value changed
}
}
/** The style of the column label. */
private DateStringLiteralStyle columnLabelDateStyle = DateStringLiteralStyle.DAY_OF_WEEK;
/** @return The style of the column label. */
public DateStringLiteralStyle getColumnLabelDateStyle() {
return columnLabelDateStyle;
}
/**
* Sets the style of the column label. Note that this property is experimental, and may eventually be replaced with a style specification in the table
* component rather than the table model. This is a bound property.
* @param newColumnLabelStyle The style of the column label.
* @throws NullPointerException if the given label style is null
.
* @see #COLUMN_LABEL_DATE_STYLE_PROPERTY
*/
public void setColumnLabelDateStyle(final DateStringLiteralStyle newColumnLabelStyle) {
if(columnLabelDateStyle != newColumnLabelStyle) { //if the value is really changing
final DateStringLiteralStyle oldColumnLabelStyle = columnLabelDateStyle; //get the old value
columnLabelDateStyle = requireNonNull(newColumnLabelStyle, "Column label style cannot be null."); //actually change the value
updateColumnLabelDateFormat(); //update the column label date format object based upon the new style
firePropertyChange(COLUMN_LABEL_DATE_STYLE_PROPERTY, oldColumnLabelStyle, newColumnLabelStyle); //indicate that the value changed
}
}
/** The date format object for formatting the column labels. */
private DateFormat columnLabelDateFormat;
/** @return The date format object for formatting the column labels. */
protected DateFormat getColumnLabelDateFormat() {
return columnLabelDateFormat;
}
/**
* Updates the model based upon the current calendar. The column label date format is also updated.
* @see #updateColumnLabelDateFormat()
*/
protected void updateModel() {
monthCalendar = Calendar.getInstance(getSession().getLocale()); //create a month calendar for the current locale
//TODO del when works monthCalendar=(Calendar)calendar.clone(); //make a copy of the calendar
monthCalendar.setTime(getDate()); //set the calendar to the correct time
monthCalendar.set(Calendar.DAY_OF_MONTH, 1); //set the month calendar to the first day of the month
monthCalendar.set(Calendar.HOUR_OF_DAY, 0); //set the hour to midnight
monthCalendar.set(Calendar.MINUTE, 0); //set the minute to zero
monthCalendar.set(Calendar.SECOND, 0); //set the second to zero
monthCalendar.set(Calendar.MILLISECOND, 0); //set the millisecond to zero
//TODO fix Log.trace("updating calendar model in locale", getSession().getLocale());
// TODO fix Log.trace("ready to update model with calendar", monthCalendar);
final int firstDayOfWeek = monthCalendar.getFirstDayOfWeek(); //get the first day of the week for this calendar
//TODO del Log.trace("first day of week", firstDayOfWeek);
final int dayOfWeek = monthCalendar.get(Calendar.DAY_OF_WEEK); //get the day of the week of the first day of the month
//TODO del Log.trace("day of week", dayOfWeek);
dayOffset = firstDayOfWeek - dayOfWeek; //calculate the offset for the first day of the month, and all days
if(dayOfWeek < firstDayOfWeek) { //if the day of the week is before the first day of the week
dayOffset -= WEEK_DAY_COUNT; //keep from going backwards too far TODO there should be a better way to do this using modulus
}
//TODO del Log.trace("day offset", dayOffset);
rowCount = (int)Math.ceil((monthCalendar.getActualMaximum(Calendar.DAY_OF_MONTH) - dayOffset) / (double)WEEK_DAY_COUNT); //find out how many partial rows are used, taking into account the day offset
//TODO del Log.trace("row count", rowCount);
updateColumnLabelDateFormat(); //update the date format object for formatting column labels
}
/**
* Updates the column label date format based upon the column label date style and current locale.
* @see #getColumnLabelDateStyle()
*/
protected void updateColumnLabelDateFormat() {
columnLabelDateFormat = AbstractDateStringLiteralConverter.createDateFormat(getColumnLabelDateStyle(), null, getSession().getLocale(),
getSession().getTimeZone()); //create a new date format based upon the style, locale, and time zone
}
/** Default constructor for current month using the current date. */
public CalendarMonthTableModel() {
this(new Date()); //construct the class using the current date
}
/**
* Date constructor.
* @param date The date this calendar is to represent.
* @throws NullPointerException if the given date is null
.
*/
public CalendarMonthTableModel(final Date date) { //TODO decide if we want to allow a calendar with another locale to be set, because right now we change calendars automatically
this.session = Guise.getInstance().getGuiseSession(); //store a reference to the current Guise session
for(int i = 0; i < WEEK_DAY_COUNT; ++i) { //for each week day index (the indices are constant, regardless of with which day of the week the locale starts)
addColumn(new WeekDayTableColumnModel(i)); //add a new week day table column
}
this.date = requireNonNull(date, "Date cannot be null");
updateModel(); //update the model to match the initial month calendar
//TODO important: this is a memory leak---make sure we uninstall the listener when the session goes away
getSession().addPropertyChangeListener(GuiseSession.LOCALE_PROPERTY, new AbstractGenericPropertyChangeListener() { //listen for the session locale changing
@Override
public void propertyChange(final GenericPropertyChangeEvent propertyChangeEvent) { //if the locale changes
updateModel(); //update the model based upon the new locale
}
});
}
@Override
public C getCellValue(final int rowIndex, final TableColumnModel column) {
final int columnIndex = getColumnIndex(column); //get the index of this column
if(columnIndex < 0) { //if this column isn't in this table
throw new IllegalArgumentException("Table column " + column + " not in table.");
}
final int offset = rowIndex * WEEK_DAY_COUNT + columnIndex + getDayOffset(); //find the absolute offset from the beginning, and then compensate for the first day of the month
final Calendar cellCalendar = getMonthCalendar(); //get a clone of the month calendar
cellCalendar.add(Calendar.DAY_OF_MONTH, offset); //move the calendar day to the requested cell
return column.getValueClass().cast(cellCalendar.getTime()); //return the calendar time representing the date of the cell
}
@Override
public void setCellValue(final int rowIndex, final TableColumnModel column, final C newCellValue) {
throw new UnsupportedOperationException("Calendar days are read-only.");
}
/**
* A day-of-week column in a calendar month table. Each cell contains a {@link Date} value.
* @author Garret Wilson
*/
public class WeekDayTableColumnModel extends DefaultTableColumnModel {
/** The physical index of the day of the week relative to the first day of the week. */
private final int index;
/** @return The physical index of the day of the week relative to the first day of the week. */
public int getIndex() {
return index;
}
/**
* {@inheritDoc}
*
* This version returns a representation of the day of the week if no label is specified.
*
*/
@Override
public String getLabel() {
String label = super.getLabel(); //get the specified label
if(label == null) { //if no label is specified
final Calendar columnCalendar = getMonthCalendar(); //get a clone of the month calendar
final int dayOfWeek = ((columnCalendar.getFirstDayOfWeek() + getIndex() - 1) % WEEK_DAY_COUNT) + 1; //find out which day of the week this column represents
columnCalendar.set(Calendar.DAY_OF_WEEK, dayOfWeek); //set the calendar to the correct day of the week, without caring the actual date
label = getColumnLabelDateFormat().format(columnCalendar.getTime()); //format the day of the week for the label
}
return label; //return the label for this column
}
/**
* Day-of-week constructor.
* @param index The physical index of the day of the week relative to the first day of the week.
* @throws IllegalArgumentException if the given index is less than zero, or greater than or equal to the number of days in a week.
*/
public WeekDayTableColumnModel(final int index) {
super(Date.class); //construct the parent class
this.index = checkIndexBounds(index, WEEK_DAY_COUNT); //make sure the index is within bounds
}
}
}