All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jdesktop.swingx.JXMonthView Maven / Gradle / Ivy

There is a newer version: 1.7.2
Show newest version
/*
 * $Id: JXMonthView.java 4147 2012-02-01 17:13:24Z kschaefe $
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jdesktop.swingx;

import org.jdesktop.beans.JavaBean;
import org.jdesktop.swingx.calendar.CalendarUtils;
import org.jdesktop.swingx.calendar.DateSelectionModel;
import org.jdesktop.swingx.calendar.DateSelectionModel.SelectionMode;
import org.jdesktop.swingx.calendar.DaySelectionModel;
import org.jdesktop.swingx.event.DateSelectionEvent.EventType;
import org.jdesktop.swingx.event.DateSelectionListener;
import org.jdesktop.swingx.event.EventListenerMap;
import org.jdesktop.swingx.plaf.LookAndFeelAddons;
import org.jdesktop.swingx.plaf.MonthViewAddon;
import org.jdesktop.swingx.plaf.MonthViewUI;
import org.jdesktop.swingx.util.Contract;

import javax.swing.JComponent;
import javax.swing.Timer;
import javax.swing.UIManager;
import java.awt.Color;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.Array;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.EventListener;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeSet;

/**
 * Component that displays a month calendar which can be used to select a day
 * or range of days.  By default the JXMonthView will display a
 * single calendar using the current month and year, using
 * Calendar.SUNDAY as the first day of the week.
 * 

* The JXMonthView can be configured to display more than one * calendar at a time by calling * setPreferredCalCols/setPreferredCalRows. These * methods will set the preferred number of calendars to use in each * column/row. As these values change, the Dimension returned * from getMinimumSize and getPreferredSize will * be updated. The following example shows how to create a 2x2 view which is * contained within a JFrame: *

 *     JXMonthView monthView = new JXMonthView();
 *     monthView.setPreferredCols(2);
 *     monthView.setPreferredRows(2);
 *
 *     JFrame frame = new JFrame();
 *     frame.getContentPane().add(monthView);
 *     frame.pack();
 *     frame.setVisible(true);
 * 
*

* JXMonthView can be further configured to allow any day of the * week to be considered the first day of the week. Character * representation of those days may also be set by providing an array of * strings. *

 *    monthView.setFirstDayOfWeek(Calendar.MONDAY);
 *    monthView.setDaysOfTheWeek(
 *            new String[]{"S", "M", "T", "W", "Th", "F", "S"});
 * 
*

* This component supports flagging days. These flagged days are displayed * in a bold font. This can be used to inform the user of such things as * scheduled appointment. *


 *    // Create some dates that we want to flag as being important.
 *    Calendar cal1 = Calendar.getInstance();
 *    cal1.set(2004, 1, 1);
 *    Calendar cal2 = Calendar.getInstance();
 *    cal2.set(2004, 1, 5);
 *
 *    monthView.setFlaggedDates(cal1.getTime(), cal2.getTime(), new Date());
 * 
* Applications may have the need to allow users to select different ranges of * dates. There are three modes of selection that are supported, single, single interval * and multiple interval selection. Once a selection is made a DateSelectionEvent is * fired to inform listeners of the change. *
 *    // Change the selection mode to select full weeks.
 *    monthView.setSelectionMode(SelectionMode.SINGLE_INTERVAL_SELECTION);
 *
 *    // Register a date selection listener to get notified about
 *    // any changes in the date selection model.
 *    monthView.getSelectionModel().addDateSelectionListener(new DateSelectionListener {
 *        public void valueChanged(DateSelectionEvent e) {
 *            log.info(e.getSelection());
 *        }
 *    });
 * 
*

* NOTE (for users of earlier versions): as of version 1.19 control about selection * dates is moved completely into the model. The default model used is of type * DaySelectionModel, which handles dates in the same way the JXMonthView did earlier * (that is, normalize all to the start of the day, which means zeroing all time * fields).

* * @author Joshua Outwater * @author Jeanette Winzenburg * @version $Revision: 4147 $ */ @JavaBean public class JXMonthView extends JComponent { /* * moved from package calendar to swingx at version 1.51 */ /** * action command used for commit actionEvent. */ public static final String COMMIT_KEY = "monthViewCommit"; /** * action command used for cancel actionEvent. */ public static final String CANCEL_KEY = "monthViewCancel"; public static final String BOX_PADDING_X = "boxPaddingX"; public static final String BOX_PADDING_Y = "boxPaddingY"; public static final String DAYS_OF_THE_WEEK = "daysOfTheWeek"; public static final String SELECTION_MODEL = "selectionModel"; public static final String TRAVERSABLE = "traversable"; public static final String FLAGGED_DATES = "flaggedDates"; static { LookAndFeelAddons.contribute(new MonthViewAddon()); } /** * UI Class ID */ public static final String uiClassID = "MonthViewUI"; public static final int DAYS_IN_WEEK = 7; public static final int MONTHS_IN_YEAR = 12; /** * Keeps track of the first date we are displaying. We use this as a * restore point for the calendar. This is normalized to the start of the * first day of the month given in setFirstDisplayedDate. */ private Date firstDisplayedDay; /** * the calendar to base all selections, flagging upon. * NOTE: the time of this calendar is undefined - before using, internal * code must explicitly set it. * PENDING JW: as of version 1.26 all calendar/properties are controlled by the model. * We keep a clone of the model's calendar here for notification reasons: * model fires DateSelectionEvent of type CALENDAR_CHANGED which neiter carry the * oldvalue nor the property name needed to map into propertyChange notification. */ private Calendar cal; /** * calendar to store the real input of firstDisplayedDate. */ private Calendar anchor; /** * Start of the day which contains System.millis() in the current calendar. * Kept in synch via a timer started in addNotify. */ private Date today; /** * The timer used to keep today in synch with system time. */ private Timer todayTimer; // PENDING JW: why kept apart from cal? Why writable? - shouldn't the calendar have complete // control? private int firstDayOfWeek; //-------------- selection/flagging /** * The DateSelectionModel driving this component. This model's calendar * is the reference for all dates. */ private DateSelectionModel model; /** * Listener registered with the current model to keep Calendar dependent * state synched. */ private DateSelectionListener modelListener; /** * The manager of the flagged dates. Note * that the type of this is an implementation detail. */ private DaySelectionModel flaggedDates; /** * Storage of actionListeners registered with the monthView. */ private final EventListenerMap listenerMap; private boolean traversable; private boolean leadingDays; private boolean trailingDays; private boolean showWeekNumber; private boolean componentInputMapEnabled; //------------------- // PENDING JW: ?? protected Date modifiedStartDate; protected Date modifiedEndDate; //------------- visuals /** * Localizable day column headers. Default typically installed by the uidelegate. */ private String[] daysOfTheWeek; protected Insets _monthStringInsets = new Insets(0, 0, 0, 0); private int boxPaddingX; private int boxPaddingY; private int minCalCols = 1; private int minCalRows = 1; private Color todayBackgroundColor; private Color monthStringBackground; private Color monthStringForeground; private Color daysOfTheWeekForeground; private Color selectedBackground; private final Map dayToColorTable = new HashMap<>(); private Color flaggedDayForeground; private Color selectedForeground; private boolean zoomable; /** * Create a new instance of the JXMonthView class using the * default Locale and the current system time as the first date to * display. */ public JXMonthView() { this(null, null, null); } /** * Create a new instance of the JXMonthView class using the * default Locale and the current system time as the first date to * display. * * @param locale desired locale, if null the system default locale is used */ public JXMonthView(Locale locale) { this(null, null, locale); } /** * Create a new instance of the JXMonthView class using the * default Locale and the given time as the first date to * display. * * @param firstDisplayedDay a day of the first month to display; if null, the current * System time is used. */ public JXMonthView(Date firstDisplayedDay) { this(firstDisplayedDay, null, null); } /** * Create a new instance of the JXMonthView class using the * default Locale, the given time as the first date to * display and the given selection model. * * @param firstDisplayedDay a day of the first month to display; if null, the current * System time is used. * @param model the selection model to use, if null a DefaultSelectionModel is * created. */ public JXMonthView(Date firstDisplayedDay, DateSelectionModel model) { this(firstDisplayedDay, model, null); } /** * Create a new instance of the JXMonthView class using the * given Locale, the given time as the first date to * display and the given selection model. * * @param firstDisplayedDay a day of the first month to display; if null, the current * System time is used. * @param model the selection model to use, if null a DefaultSelectionModel is * created. * @param locale desired locale, if null the system default locale is used */ public JXMonthView(Date firstDisplayedDay, DateSelectionModel model, Locale locale) { listenerMap = new EventListenerMap(); initModel(model, locale); superSetLocale(locale); setFirstDisplayedDay(firstDisplayedDay != null ? firstDisplayedDay : getCurrentDate()); // Keep track of today updateTodayFromCurrentTime(); // install the controller updateUI(); setFocusable(true); todayBackgroundColor = getForeground(); } //------------------ Calendar related properties /** * Sets locale and resets text and format used to display months and days. * Also resets firstDayOfWeek.

*

* PENDING JW: the following warning should be obsolete (installCalendar * should take care) - check if it really is! * *

* Warning: Since this resets any string labels that are cached in UI * (month and day names) and firstDayofWeek, use setDaysOfTheWeek and/or * setFirstDayOfWeek after (re)setting locale. *

* * @param locale new Locale to be used for formatting * @see #setDaysOfTheWeek(String[]) * @see #setFirstDayOfWeek(int) */ @Override public void setLocale(Locale locale) { model.setLocale(locale); } /** * @param locale */ private void superSetLocale(Locale locale) { // PENDING JW: formally, a null value is allowed and must be passed on to super // I suspect this is not done here to keep the logic out off the constructor? // if (locale != null) { super.setLocale(locale); repaint(); } } /** * Returns a clone of the internal calendar, with it's time set to firstDisplayedDate. *

* PENDING: firstDisplayed useful as reference time? It's timezone dependent anyway. * Think: could return with monthView's today instead? * * @return a clone of internal calendar, configured to the current firstDisplayedDate * @throws IllegalStateException if called before instantitation is completed */ public Calendar getCalendar() { // JW: this is to guard against a regression of not-fully understood // problems in constructor (UI used to call back into this before we were ready) if (cal == null) throw new IllegalStateException("must not be called before instantiation is complete"); Calendar calendar = (Calendar) cal.clone(); calendar.setTime(firstDisplayedDay); return calendar; } /** * Gets the time zone. * * @return The TimeZone used by the JXMonthView. */ public TimeZone getTimeZone() { // PENDING JW: looks fishy (left-over?) .. why not ask the model? return cal.getTimeZone(); } /** * Sets the time zone with the given time zone value. *

* This is a bound property. * * @param tz The TimeZone. */ public void setTimeZone(TimeZone tz) { model.setTimeZone(tz); } /** * Gets what the first day of the week is; e.g., * Calendar.SUNDAY in the U.S., Calendar.MONDAY * in France. * * @return int The first day of the week. */ public int getFirstDayOfWeek() { return firstDayOfWeek; } /** * Sets what the first day of the week is; e.g., * Calendar.SUNDAY in US, Calendar.MONDAY * in France. * * @param firstDayOfWeek The first day of the week. * @see Calendar */ public void setFirstDayOfWeek(int firstDayOfWeek) { getSelectionModel().setFirstDayOfWeek(firstDayOfWeek); } //---------------------- synch to model's calendar /** * Initializes selection model related internals. If the Locale is * null, it falls back to JComponent.defaultLocale. If the model * is null it creates a default model with the locale. *

* PENDING JW: leave default locale fallback to model? * * @param model the DateSelectionModel which should drive the monthView. * If null, a default model is created and initialized with the given locale. * @param locale the Locale to use with the selectionModel. If null, * JComponent.getDefaultLocale is used. */ private void initModel(DateSelectionModel model, Locale locale) { if (locale == null) { locale = JComponent.getDefaultLocale(); } if (model == null) { model = new DaySelectionModel(locale); } this.model = model; // PENDING JW: do better to synchronize Calendar related // properties of flaggedDates to those of the selection model. // plus: should use the same normalization? this.flaggedDates = new DaySelectionModel(locale); flaggedDates.setSelectionMode(SelectionMode.MULTIPLE_INTERVAL_SELECTION); installCalendar(); model.addDateSelectionListener(getDateSelectionListener()); } /** * Lazily creates and returns the DateSelectionListener which listens * for model's calendar properties. * * @return a DateSelectionListener for model's CALENDAR_CHANGED notification. */ private DateSelectionListener getDateSelectionListener() { if (modelListener == null) { modelListener = ev -> { if (EventType.CALENDAR_CHANGED.equals(ev.getEventType())) { updateCalendar(); } }; } return modelListener; } /** * Installs the internal calendars from the selection model.

*

* PENDING JW: in fixing #11433, added update of firstDisplayedDay and * today here - check if correct place to do so. */ private void installCalendar() { cal = model.getCalendar(); firstDayOfWeek = cal.getFirstDayOfWeek(); Date anchorDate = getAnchorDate(); anchor = (Calendar) cal.clone(); if (anchorDate != null) { setFirstDisplayedDay(anchorDate); } updateTodayFromCurrentTime(); } /** * Returns the anchor date. Currently, this is the "uncleaned" input date * of setFirstDisplayedDate. This is a quick hack for Issue #618-swingx, to * have some invariant for testing. Do not use in client code, may change * without notice! * * @return the "uncleaned" first display date or null if the firstDisplayedDay * is not yet set. */ protected Date getAnchorDate() { return anchor != null ? anchor.getTime() : null; } /** * Callback from selection model calendar changes. */ private void updateCalendar() { if (!getLocale().equals(model.getLocale())) { installCalendar(); superSetLocale(model.getLocale()); } else { if (!model.getTimeZone().equals(getTimeZone())) { updateTimeZone(); } if (cal.getMinimalDaysInFirstWeek() != model.getMinimalDaysInFirstWeek()) { updateMinimalDaysOfFirstWeek(); } if (cal.getFirstDayOfWeek() != model.getFirstDayOfWeek()) { updateFirstDayOfWeek(); } } } /** * Callback from changing timezone in model. */ private void updateTimeZone() { TimeZone old = getTimeZone(); TimeZone tz = model.getTimeZone(); cal.setTimeZone(tz); anchor.setTimeZone(tz); setFirstDisplayedDay(anchor.getTime()); updateTodayFromCurrentTime(); updateDatesAfterTimeZoneChange(old); firePropertyChange("timeZone", old, getTimeZone()); } /** * All dates are "cleaned" relative to the timezone they had been set. * After changing the timezone, they need to be updated to the new. *

* Here: clear everything. * * @param oldTimeZone the timezone before the change */ protected void updateDatesAfterTimeZoneChange(TimeZone oldTimeZone) { SortedSet flagged = getFlaggedDates(); flaggedDates.setTimeZone(getTimeZone()); firePropertyChange("flaggedDates", flagged, getFlaggedDates()); } /** * Call back from listening to model firstDayOfWeek change. */ private void updateFirstDayOfWeek() { int oldFirstDayOfWeek = this.firstDayOfWeek; firstDayOfWeek = getSelectionModel().getFirstDayOfWeek(); cal.setFirstDayOfWeek(firstDayOfWeek); anchor.setFirstDayOfWeek(firstDayOfWeek); firePropertyChange("firstDayOfWeek", oldFirstDayOfWeek, firstDayOfWeek); } /** * Call back from listening to model minimalDaysOfFirstWeek change. *

* NOTE: this is not a property as we have no public api to change * it on JXMonthView. */ private void updateMinimalDaysOfFirstWeek() { cal.setMinimalDaysInFirstWeek(model.getMinimalDaysInFirstWeek()); anchor.setMinimalDaysInFirstWeek(model.getMinimalDaysInFirstWeek()); } //-------------------- scrolling /** * Returns the last date able to be displayed. For example, if the last * visible month was April the time returned would be April 30, 23:59:59. * * @return long The last displayed date. */ public Date getLastDisplayedDay() { return getUI().getLastDisplayedDay(); } /** * Returns the first displayed date. * * @return long The first displayed date. */ public Date getFirstDisplayedDay() { return firstDisplayedDay; } /** * Set the first displayed date. We only use the month and year of * this date. The Calendar.DAY_OF_MONTH field is reset to * 1 and all other fields, with exception of the year and month, * are reset to 0. * * @param date The first displayed date. */ public void setFirstDisplayedDay(Date date) { anchor.setTime(date); Date oldDate = getFirstDisplayedDay(); cal.setTime(anchor.getTime()); CalendarUtils.startOfMonth(cal); firstDisplayedDay = cal.getTime(); firePropertyChange("firstDisplayedDay", oldDate, getFirstDisplayedDay()); } /** * Moves the date into the visible region of the calendar. If * the date is greater than the last visible date it will become the last * visible date. While if it is less than the first visible date it will * become the first visible date.

*

* NOTE: this is the recommended method to scroll to a particular date, the * functionally equivalent method taking a long as parameter will most * probably be deprecated. * * @param date Date to make visible, must not be null. * @see #ensureDateVisible(Date) */ public void ensureDateVisible(Date date) { if (date.before(firstDisplayedDay)) { setFirstDisplayedDay(date); } else { Date lastDisplayedDate = getLastDisplayedDay(); if (date.after(lastDisplayedDate)) { // extract to CalendarUtils! cal.setTime(date); int month = cal.get(Calendar.MONTH); int year = cal.get(Calendar.YEAR); cal.setTime(lastDisplayedDate); int lastMonth = cal.get(Calendar.MONTH); int lastYear = cal.get(Calendar.YEAR); cal.setTime(firstDisplayedDay); int diffMonths = month - lastMonth + (year - lastYear) * MONTHS_IN_YEAR; cal.add(Calendar.MONTH, diffMonths); setFirstDisplayedDay(cal.getTime()); } } } /** * Returns the Date at the given location. May be null if the * coordinates don't map to a day in the month which contains the * coordinates. Specifically: hitting leading/trailing dates returns null. *

* Mapping pixel to calendar day. * * @param x the x position of the location in pixel * @param y the y position of the location in pixel * @return the day at the given location or null if the location * doesn't map to a day in the month which contains the coordinates. */ public Date getDayAtLocation(int x, int y) { return getUI().getDayAtLocation(x, y); } //------------------ today /** * Returns the current Date (whateverthatmeans). Internally always invoked when * the current default is needed. Introduced mainly for testing, don't override! *

* This implementation returns a Date instantiated with System.currentTimeInMillis. * * @return the date deemed as current. */ Date getCurrentDate() { return new Date(System.currentTimeMillis()); } /** * Sets today from the current system time. *

* temporary widened access for testing. */ protected void updateTodayFromCurrentTime() { setToday(getCurrentDate()); } /** * Increments today. This is used by the timer. *

* PENDING: is it safe? doesn't check if we are really tomorrow? * temporary widened access for testing. */ protected void incrementToday() { cal.setTime(getToday()); cal.add(Calendar.DAY_OF_MONTH, 1); setToday(cal.getTime()); } /** * Sets the date which represents today. Internally * modified to the start of the day which contains the * given date in this monthView's calendar coordinates. *

* temporary widened access for testing. * * @param date the date which should be used as today. */ protected void setToday(Date date) { Date oldToday = getToday(); // PENDING JW: do we really want the start of today? this.today = startOfDay(date); firePropertyChange("today", oldToday, getToday()); } /** * Returns the start of today in this monthviews calendar coordinates. * * @return the start of today as Date. */ public Date getToday() { // null only happens in the very first time ... return today != null ? (Date) today.clone() : null; } //---- internal date manipulation ("cleanup" == start of day in monthView's calendar) /** * Returns the start of the day as Date. * * @param date the Date. * @return start of the given day as Date, relative to this * monthView's calendar. */ private Date startOfDay(Date date) { return CalendarUtils.startOfDay(cal, date); } //------------------- ui delegate /** * @inheritDoc */ public MonthViewUI getUI() { return (MonthViewUI) ui; } /** * Sets the L&F object that renders this component. * * @param ui UI to use for this {@code JXMonthView} */ public void setUI(MonthViewUI ui) { super.setUI(ui); } /** * Resets the UI property with the value from the current look and feel. * * @see UIManager#getUI(JComponent) */ @Override public void updateUI() { setUI((MonthViewUI) LookAndFeelAddons.getUI(this, MonthViewUI.class)); invalidate(); } /** * @inheritDoc */ @Override public String getUIClassID() { return uiClassID; } //---------------- DateSelectionModel /** * Returns the date selection model which drives this * JXMonthView. * * @return the date selection model */ public DateSelectionModel getSelectionModel() { return model; } /** * Sets the date selection model to drive this monthView. * * @param model the selection model to use, must not be null. * @throws NullPointerException if model is null */ public void setSelectionModel(DateSelectionModel model) { Contract.asNotNull(model, "date selection model must not be null"); DateSelectionModel oldModel = getSelectionModel(); model.removeDateSelectionListener(getDateSelectionListener()); this.model = model; installCalendar(); if (!model.getLocale().equals(getLocale())) { super.setLocale(model.getLocale()); } model.addDateSelectionListener(getDateSelectionListener()); firePropertyChange(SELECTION_MODEL, oldModel, getSelectionModel()); } //-------------------- delegates to model /** * Clear any selection from the selection model */ public void clearSelection() { getSelectionModel().clearSelection(); } /** * Return true if the selection is empty, false otherwise * * @return true if the selection is empty, false otherwise */ public boolean isSelectionEmpty() { return getSelectionModel().isSelectionEmpty(); } /** * Get the current selection * * @return sorted set of selected dates */ public SortedSet getSelection() { return getSelectionModel().getSelection(); } /** * Adds the selection interval to the selection model. * * @param startDate Start of date range to add to the selection * @param endDate End of date range to add to the selection */ public void addSelectionInterval(Date startDate, Date endDate) { getSelectionModel().addSelectionInterval(startDate, endDate); } /** * Sets the selection interval to the selection model. * * @param startDate Start of date range to set the selection to * @param endDate End of date range to set the selection to */ public void setSelectionInterval(Date startDate, Date endDate) { getSelectionModel().setSelectionInterval(startDate, endDate); } /** * Removes the selection interval from the selection model. * * @param startDate Start of the date range to remove from the selection * @param endDate End of the date range to remove from the selection */ public void removeSelectionInterval(Date startDate, Date endDate) { getSelectionModel().removeSelectionInterval(startDate, endDate); } /** * Returns the current selection mode for this JXMonthView. * * @return int Selection mode. */ public SelectionMode getSelectionMode() { return getSelectionModel().getSelectionMode(); } /** * Set the selection mode for this JXMonthView. * * @param selectionMode The selection mode to use for this {@code JXMonthView} */ public void setSelectionMode(SelectionMode selectionMode) { getSelectionModel().setSelectionMode(selectionMode); } /** * Returns the earliest selected date. * * @return the first Date in the selection or null if empty. */ public Date getFirstSelectionDate() { return getSelectionModel().getFirstSelectionDate(); } /** * Returns the earliest selected date. * * @return the first Date in the selection or null if empty. */ public Date getLastSelectionDate() { return getSelectionModel().getLastSelectionDate(); } /** * Returns the earliest selected date. *

* PENDING JW: keep this? it was introduced before the first/last * in model. When delegating everything, we duplicate here. * * @return the first Date in the selection or null if empty. */ public Date getSelectionDate() { return getFirstSelectionDate(); } /** * Sets the model's selection to the given date or clears the selection if * null. * * @param newDate the selection date to set */ public void setSelectionDate(Date newDate) { if (newDate == null) { clearSelection(); } else { setSelectionInterval(newDate, newDate); } } /** * Returns true if the specified date falls within the _startSelectedDate * and _endSelectedDate range. * * @param date The date to check * @return true if the date is selected, false otherwise */ public boolean isSelected(Date date) { return getSelectionModel().isSelected(date); } /** * Set the lower bound date that is allowed to be selected.

* * @param lowerBound the lower bound, null means none. */ public void setLowerBound(Date lowerBound) { getSelectionModel().setLowerBound(lowerBound); } /** * Set the upper bound date that is allowed to be selected.

* * @param upperBound the upper bound, null means none. */ public void setUpperBound(Date upperBound) { getSelectionModel().setUpperBound(upperBound); } /** * Return the lower bound date that is allowed to be selected for this * model. * * @return lower bound date or null if not set */ public Date getLowerBound() { return getSelectionModel().getLowerBound(); } /** * Return the upper bound date that is allowed to be selected for this * model. * * @return upper bound date or null if not set */ public Date getUpperBound() { return getSelectionModel().getUpperBound(); } /** * Identifies whether or not the date passed is an unselectable date. *

* * @param date date which to test for unselectable status * @return true if the date is unselectable, false otherwise */ public boolean isUnselectableDate(Date date) { return getSelectionModel().isUnselectableDate(date); } /** * Sets the dates that should be unselectable. This will replace the model's * current set of unselectable dates. The implication is that calling with * zero dates will remove all unselectable dates. *

*

* NOTE: neither the given array nor any of its elements must be null. * * @param unselectableDates zero or more not-null dates that should be * unselectable. * @throws NullPointerException if either the array or any of the elements * are null */ public void setUnselectableDates(Date... unselectableDates) { Contract.asNotNull(unselectableDates, "unselectable dates must not be null"); SortedSet unselectableSet = new TreeSet<>(); Collections.addAll(unselectableSet, unselectableDates); getSelectionModel().setUnselectableDates(unselectableSet); // PENDING JW: check that ui does the repaint! repaint(); } // --------------------- flagged dates /** * Identifies whether or not the date passed is a flagged date. * * @param date date which to test for flagged status * @return true if the date is flagged, false otherwise */ public boolean isFlaggedDate(Date date) { if (date == null) return false; return flaggedDates.isSelected(date); } /** * Replace all flags with the given dates.

*

* NOTE: neither the given array nor any of its elements should be null. * Currently, a null array will be tolerated to ease migration. A null * has the same effect as clearFlaggedDates. * * @param flagged the dates to be flagged */ public void setFlaggedDates(Date... flagged) { SortedSet oldFlagged = getFlaggedDates(); flaggedDates.clearSelection(); if (flagged != null) { for (Date date : flagged) { flaggedDates.addSelectionInterval(date, date); } } firePropertyChange("flaggedDates", oldFlagged, getFlaggedDates()); } /** * Adds the dates to the flags. *

* NOTE: neither the given array nor any of its elements should be null. * Currently, a null array will be tolerated to ease migration. A null * does nothing. * * @param flagged the dates to be flagged */ public void addFlaggedDates(Date... flagged) { SortedSet oldFlagged = flaggedDates.getSelection(); if (flagged != null) { for (Date date : flagged) { flaggedDates.addSelectionInterval(date, date); } } firePropertyChange("flaggedDates", oldFlagged, flaggedDates.getSelection()); } /** * Unflags the given dates. *

* NOTE: neither the given array nor any of its elements should be null. * Currently, a null array will be tolerated to ease migration. * * @param flagged the dates to be unflagged */ public void removeFlaggedDates(Date... flagged) { SortedSet oldFlagged = flaggedDates.getSelection(); if (flagged != null) { for (Date date : flagged) { flaggedDates.removeSelectionInterval(date, date); } } firePropertyChange("flaggedDates", oldFlagged, flaggedDates.getSelection()); } /** * Clears all flagged dates. */ public void clearFlaggedDates() { SortedSet oldFlagged = flaggedDates.getSelection(); flaggedDates.clearSelection(); firePropertyChange("flaggedDates", oldFlagged, flaggedDates.getSelection()); } /** * Returns a sorted set of flagged Dates. The returned set is guaranteed to * be not null, but may be empty. * * @return a sorted set of flagged dates. */ public SortedSet getFlaggedDates() { return flaggedDates.getSelection(); } /** * Returns a boolean indicating if this monthView has flagged dates. * * @return a boolean indicating if this monthView has flagged dates. */ public boolean hasFlaggedDates() { return !flaggedDates.isSelectionEmpty(); } //------------------- visual properties /** * Sets a boolean property indicating whether or not to show leading dates * for a months displayed by this component.

*

* The default value is false. * * @param value true if leading dates should be displayed, false otherwise. */ public void setShowingLeadingDays(boolean value) { boolean old = isShowingLeadingDays(); leadingDays = value; firePropertyChange("showingLeadingDays", old, isShowingLeadingDays()); } /** * Returns a boolean indicating whether or not we're showing leading dates. * * @return true if leading dates are shown, false otherwise. */ public boolean isShowingLeadingDays() { return leadingDays; } /** * Sets a boolean property indicating whether or not to show * trailing dates for the months displayed by this component.

*

* The default value is false. * * @param value true if trailing dates should be displayed, false otherwise. */ public void setShowingTrailingDays(boolean value) { boolean old = isShowingTrailingDays(); trailingDays = value; firePropertyChange("showingTrailingDays", old, isShowingTrailingDays()); } /** * Returns a boolean indicating whether or not we're showing trailing dates. * * @return true if trailing dates are shown, false otherwise. */ public boolean isShowingTrailingDays() { return trailingDays; } /** * Returns whether or not the month view supports traversing months. * If zoomable is enabled, traversable is enabled as well. Otherwise * returns the traversable property as set by client code. * * @return true if month traversing is enabled. * @see #setZoomable(boolean) */ public boolean isTraversable() { if (isZoomable()) return true; return traversable; } /** * Set whether or not the month view will display buttons to allow the user * to traverse to previous or next months.

*

* The default value is false.

*

* PENDING JW: fire the "real" property or the compound with zoomable? * * @param traversable set to true to enable month traversing, false * otherwise. * @see #isTraversable() * @see #setZoomable(boolean) */ public void setTraversable(boolean traversable) { boolean old = isTraversable(); this.traversable = traversable; firePropertyChange(TRAVERSABLE, old, isTraversable()); } /** * Returns true if zoomable (through date ranges). * * @return true if zoomable is enabled. * @see #setZoomable(boolean) */ public boolean isZoomable() { return zoomable; } /** * Sets the zoomable property. If true, the calendar's date range can * be zoomed. This state implies that the calendar is traversable and * showing exactly one calendar box, effectively ignoring the properties.

* * Note: The actual zoomable behaviour is not yet implemented. * * @param zoomable a boolean indicating whether or not zooming date * ranges is enabled. * @see #setTraversable(boolean) */ public void setZoomable(boolean zoomable) { boolean old = isZoomable(); this.zoomable = zoomable; firePropertyChange("zoomable", old, isZoomable()); } /** * Returns whether or not this JXMonthView should display * week number. * * @return true if week numbers should be displayed */ public boolean isShowingWeekNumber() { return showWeekNumber; } /** * Set whether or not this JXMonthView will display week * numbers or not. * * @param showWeekNumber true if week numbers should be displayed, * false otherwise */ public void setShowingWeekNumber(boolean showWeekNumber) { boolean old = isShowingWeekNumber(); this.showWeekNumber = showWeekNumber; firePropertyChange("showingWeekNumber", old, isShowingWeekNumber()); } /** * Sets the String representation for each day of the week as used * in the header of the day's grid. For * this method the first days of the week days[0] is assumed to be * Calendar.SUNDAY. If null, the representation provided * by the MonthViewUI is used. *

* The default value is the representation as * returned from the MonthViewUI. * * @param days Array of characters that represents each day * @throws IllegalArgumentException if not null and days.length != * DAYS_IN_WEEK */ public void setDaysOfTheWeek(String[] days) { if (days != null && days.length != DAYS_IN_WEEK) { throw new IllegalArgumentException("Array of days is not of length " + DAYS_IN_WEEK + " as expected."); } String[] oldValue = getDaysOfTheWeek(); daysOfTheWeek = days; firePropertyChange(DAYS_OF_THE_WEEK, oldValue, days); } /** * Returns the String representation for each day of the * week. * * @return String representation for the days of the week, guaranteed to * never be null. * @see #setDaysOfTheWeek(String[]) * @see MonthViewUI */ public String[] getDaysOfTheWeek() { if (daysOfTheWeek != null) { String[] days = new String[DAYS_IN_WEEK]; System.arraycopy(daysOfTheWeek, 0, days, 0, DAYS_IN_WEEK); return days; } return getUI().getDaysOfTheWeek(); } /** * @param dayOfWeek * @return String representation of day of week. */ public String getDayOfTheWeek(int dayOfWeek) { return getDaysOfTheWeek()[dayOfWeek - 1]; } /** * Returns the padding used between days in the calendar. * * @return Padding used between days in the calendar */ public int getBoxPaddingX() { return boxPaddingX; } /** * Sets the number of pixels used to pad the left and right side of a day. * The padding is applied to both sides of the days. Therefore, if you * used the padding value of 3, the number of pixels between any two days * would be 6. * * @param boxPaddingX Number of pixels applied to both sides of a day */ public void setBoxPaddingX(int boxPaddingX) { int oldBoxPadding = getBoxPaddingX(); this.boxPaddingX = boxPaddingX; firePropertyChange(BOX_PADDING_X, oldBoxPadding, getBoxPaddingX()); } /** * Returns the padding used above and below days in the calendar. * * @return Padding used between dats in the calendar */ public int getBoxPaddingY() { return boxPaddingY; } /** * Sets the number of pixels used to pad the top and bottom of a day. * The padding is applied to both the top and bottom of a day. Therefore, * if you used the padding value of 3, the number of pixels between any * two days would be 6. * * @param boxPaddingY Number of pixels applied to top and bottom of a day */ public void setBoxPaddingY(int boxPaddingY) { int oldBoxPadding = getBoxPaddingY(); this.boxPaddingY = boxPaddingY; firePropertyChange(BOX_PADDING_Y, oldBoxPadding, getBoxPaddingY()); } /** * Returns the selected background color. * * @return the selected background color. */ public Color getSelectionBackground() { return selectedBackground; } /** * Sets the selected background color to c. The default color * is installed by the ui. * * @param c Selected background. */ public void setSelectionBackground(Color c) { Color old = getSelectionBackground(); selectedBackground = c; firePropertyChange("selectionBackground", old, getSelectionBackground()); } /** * Returns the selected foreground color. * * @return the selected foreground color. */ public Color getSelectionForeground() { return selectedForeground; } /** * Sets the selected foreground color to c. The default color * is installed by the ui. * * @param c Selected foreground. */ public void setSelectionForeground(Color c) { Color old = getSelectionForeground(); selectedForeground = c; firePropertyChange("selectionForeground", old, getSelectionForeground()); } /** * Returns the color used when painting the today background. * * @return Color Color */ public Color getTodayBackground() { return todayBackgroundColor; } /** * Sets the color used to draw the bounding box around today. The default * is the background of the JXMonthView component. * * @param c color to set */ public void setTodayBackground(Color c) { Color oldValue = getTodayBackground(); todayBackgroundColor = c; firePropertyChange("todayBackground", oldValue, getTodayBackground()); // PENDING JW: remove repaint, ui must take care of it repaint(); } /** * Returns the color used to paint the month string background. * * @return Color Color. */ public Color getMonthStringBackground() { return monthStringBackground; } /** * Sets the color used to draw the background of the month string. The * default is 138, 173, 209 (Blue-ish). * * @param c color to set */ public void setMonthStringBackground(Color c) { Color old = getMonthStringBackground(); monthStringBackground = c; firePropertyChange("monthStringBackground", old, getMonthStringBackground()); // PENDING JW: remove repaint, ui must take care of it repaint(); } /** * Returns the color used to paint the month string foreground. * * @return Color Color. */ public Color getMonthStringForeground() { return monthStringForeground; } /** * Sets the color used to draw the foreground of the month string. The * default is Color.WHITE. * * @param c color to set */ public void setMonthStringForeground(Color c) { Color old = getMonthStringForeground(); monthStringForeground = c; firePropertyChange("monthStringForeground", old, getMonthStringForeground()); // PENDING JW: remove repaint, ui must take care of it repaint(); } /** * Sets the color used to draw the foreground of each day of the week. These * are the titles * * @param c color to set */ public void setDaysOfTheWeekForeground(Color c) { Color old = getDaysOfTheWeekForeground(); daysOfTheWeekForeground = c; firePropertyChange("daysOfTheWeekForeground", old, getDaysOfTheWeekForeground()); } /** * @return Color Color */ public Color getDaysOfTheWeekForeground() { return daysOfTheWeekForeground; } /** * Set the color to be used for painting the specified day of the week. * Acceptable values are Calendar.SUNDAY - Calendar.SATURDAY.

*

* PENDING JW: this is not a property - should it be and * fire a change notification? If so, how? * * @param dayOfWeek constant value defining the day of the week. * @param c The color to be used for painting the numeric day of the week. */ public void setDayForeground(int dayOfWeek, Color c) { if (dayOfWeek < Calendar.SUNDAY || dayOfWeek > Calendar.SATURDAY) { throw new IllegalArgumentException("dayOfWeek must be in [Calendar.SUNDAY ... " + "Calendar.SATURDAY] but was " + dayOfWeek); } dayToColorTable.put(dayOfWeek, c); repaint(); } /** * Return the color that should be used for painting the numerical day of the week. * * @param dayOfWeek The day of week to get the color for. * @return The color to be used for painting the numeric day of the week. * If this was no color has yet been defined the component foreground color * will be returned. */ public Color getDayForeground(int dayOfWeek) { Color c = dayToColorTable.get(dayOfWeek); if (c == null) { c = getForeground(); } return c; } /** * Return the color that should be used for painting the numerical day of the week. * * @param dayOfWeek The day of week to get the color for. * @return The color to be used for painting the numeric day of the week or null * If no color has yet been defined. */ public Color getPerDayOfWeekForeground(int dayOfWeek) { return dayToColorTable.get(dayOfWeek); } /** * Set the color to be used for painting the foreground of a flagged day. * * @param c The color to be used for painting. */ public void setFlaggedDayForeground(Color c) { Color old = getFlaggedDayForeground(); flaggedDayForeground = c; firePropertyChange("flaggedDayForeground", old, getFlaggedDayForeground()); } /** * Return the color that should be used for painting the foreground of the flagged day. * * @return The color to be used for painting */ public Color getFlaggedDayForeground() { return flaggedDayForeground; } /** * Returns a copy of the insets used to paint the month string background. * * @return Insets Month string insets. */ public Insets getMonthStringInsets() { return (Insets) _monthStringInsets.clone(); } /** * Insets used to modify the width/height when painting the background * of the month string area. * * @param insets Insets */ public void setMonthStringInsets(Insets insets) { Insets old = getMonthStringInsets(); if (insets == null) { _monthStringInsets.top = 0; _monthStringInsets.left = 0; _monthStringInsets.bottom = 0; _monthStringInsets.right = 0; } else { _monthStringInsets.top = insets.top; _monthStringInsets.left = insets.left; _monthStringInsets.bottom = insets.bottom; _monthStringInsets.right = insets.right; } firePropertyChange("monthStringInsets", old, getMonthStringInsets()); // PENDING JW: remove repaint, ui must take care of it repaint(); } /** * Returns the preferred number of columns to paint calendars in. *

* * @return int preferred number of columns of calendars. * @see #setPreferredColumnCount(int) */ public int getPreferredColumnCount() { return minCalCols; } /** * Sets the preferred number of columns of calendars. Does nothing if cols * <= 0. The default value is 1. *

* * @param cols The number of columns of calendars. * @see #getPreferredColumnCount() */ public void setPreferredColumnCount(int cols) { if (cols <= 0) { return; } int old = getPreferredColumnCount(); minCalCols = cols; firePropertyChange("preferredColumnCount", old, getPreferredColumnCount()); // PENDING JW: remove revalidate/repaint, ui must take care of it revalidate(); repaint(); } /** * Returns the preferred number of rows to paint calendars in. *

* * @return int Rows of calendars. * @see #setPreferredRowCount(int) */ public int getPreferredRowCount() { return minCalRows; } /** * Sets the preferred number of rows to paint calendars.Does nothing if rows * <= 0. The default value is 1. *

* * @param rows The number of rows of calendars. * @see #getPreferredRowCount() */ public void setPreferredRowCount(int rows) { if (rows <= 0) { return; } int old = getPreferredRowCount(); minCalRows = rows; firePropertyChange("preferredRowCount", old, getPreferredRowCount()); // PENDING JW: remove revalidate/repaint, ui must take care of it revalidate(); repaint(); } /** * {@inheritDoc} */ @Override public void removeNotify() { if (todayTimer != null) { todayTimer.stop(); } super.removeNotify(); } /** * {@inheritDoc} */ @Override public void addNotify() { super.addNotify(); // partial fix for #1125: today updated in addNotify // partial, because still not in synch if not shown updateTodayFromCurrentTime(); // Setup timer to update the value of today. int secondsTillTomorrow = 86400; if (todayTimer == null) { todayTimer = new Timer(secondsTillTomorrow * 1000, e -> incrementToday()); } // Modify the initial delay by the current time. cal.setTime(getCurrentDate()); secondsTillTomorrow = secondsTillTomorrow - cal.get(Calendar.HOUR_OF_DAY) * 3600 - cal.get(Calendar.MINUTE) * 60 - cal.get(Calendar.SECOND); todayTimer.setInitialDelay(secondsTillTomorrow * 1000); todayTimer.start(); } //-------------------- action and listener /** * Commits the current selection.

*

* Resets the model's adjusting property to false * and fires an ActionEvent * with the COMMIT_KEY action command. * * @see #cancelSelection() * @see DateSelectionModel#setAdjusting(boolean) */ public void commitSelection() { getSelectionModel().setAdjusting(false); fireActionPerformed(COMMIT_KEY); } /** * Cancels the selection.

*

* Resets the model's adjusting property to * false and fires an ActionEvent with the CANCEL_KEY action command. * * @see #commitSelection * @see DateSelectionModel#setAdjusting(boolean) */ public void cancelSelection() { getSelectionModel().setAdjusting(false); fireActionPerformed(CANCEL_KEY); } /** * Sets the component input map enablement property.

*

* If enabled, the keybinding for WHEN_IN_FOCUSED_WINDOW are * installed, otherwise not. Changing this property will * install/clear the corresponding key bindings. Typically, clients * which want to use the monthview in a popup, should enable these.

*

* The default value is false. * * @param enabled boolean to indicate whether the component * input map should be enabled. * @see #isComponentInputMapEnabled() */ public void setComponentInputMapEnabled(boolean enabled) { boolean old = isComponentInputMapEnabled(); this.componentInputMapEnabled = enabled; firePropertyChange("componentInputMapEnabled", old, isComponentInputMapEnabled()); } /** * Returns the componentInputMapEnabled property. * * @return a boolean indicating whether the component input map is * enabled. * @see #setComponentInputMapEnabled(boolean) */ public boolean isComponentInputMapEnabled() { return componentInputMapEnabled; } /** * Adds an ActionListener. *

* The ActionListener will receive an ActionEvent with its actionCommand * set to COMMIT_KEY or CANCEL_KEY after the selection has been committed * or canceled, respectively. *

*

* Note that actionEvents are typically fired after a dedicated user gesture * to end an ongoing selectin (like ENTER, ESCAPE) or after explicit programmatic * commits/cancels. It is usually not fired after each change to the selection state. * Client code which wants to be notified about all selection changes should * register a DateSelectionListener to the DateSelectionModel. * * @param l The ActionListener that is to be notified * @see #commitSelection() * @see #cancelSelection() * @see #getSelectionModel() */ public void addActionListener(ActionListener l) { listenerMap.add(ActionListener.class, l); } /** * Removes an ActionListener. * * @param l The ActionListener to remove. */ public void removeActionListener(ActionListener l) { listenerMap.remove(ActionListener.class, l); } @Override @SuppressWarnings("unchecked") public T[] getListeners(Class listenerType) { List listeners = listenerMap.getListeners(listenerType); T[] result; if (!listeners.isEmpty()) { //noinspection unchecked result = (T[]) Array.newInstance(listenerType, listeners.size()); result = listeners.toArray(result); } else { result = super.getListeners(listenerType); } return result; } /** * Creates and fires an ActionEvent with the given action * command to all listeners. * * @param actionCommand the command for the created. */ protected void fireActionPerformed(String actionCommand) { ActionListener[] listeners = getListeners(ActionListener.class); ActionEvent e = null; for (ActionListener listener : listeners) { if (e == null) { e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, actionCommand); } listener.actionPerformed(e); } } //--- deprecated code - NOTE: these methods will be removed soon! /** * @deprecated pre-0.9.5 - this is kept as a reminder only, don't * use! we can make this private or comment it out after * next version */ @Deprecated protected void cleanupWeekSelectionDates(Date startDate, Date endDate) { cal.setTime(startDate); int count = 1; while (cal.getTimeInMillis() < endDate.getTime()) { cal.add(Calendar.DAY_OF_MONTH, 1); count++; } if (count > DAYS_IN_WEEK) { // Move the start date to the first day of the week. cal.setTime(startDate); int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); int firstDayOfWeek = getFirstDayOfWeek(); int daysFromStart = dayOfWeek - firstDayOfWeek; if (daysFromStart < 0) { daysFromStart += DAYS_IN_WEEK; } cal.add(Calendar.DAY_OF_MONTH, -daysFromStart); modifiedStartDate = cal.getTime(); // Move the end date to the last day of the week. cal.setTime(endDate); dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); int lastDayOfWeek = firstDayOfWeek - 1; if (lastDayOfWeek == 0) { lastDayOfWeek = Calendar.SATURDAY; } int daysTillEnd = lastDayOfWeek - dayOfWeek; if (daysTillEnd < 0) { daysTillEnd += DAYS_IN_WEEK; } cal.add(Calendar.DAY_OF_MONTH, daysTillEnd); modifiedEndDate = cal.getTime(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy