Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.calendarfx.view.DateControl Maven / Gradle / Ivy
/*
* Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com)
*
* 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 com.calendarfx.view;
import com.calendarfx.model.Calendar;
import com.calendarfx.model.CalendarSource;
import com.calendarfx.model.Entry;
import com.calendarfx.model.Interval;
import com.calendarfx.util.LoggingDomain;
import com.calendarfx.view.page.DayPage;
import com.calendarfx.view.popover.DatePopOver;
import com.calendarfx.view.popover.EntryPopOverContentPane;
import impl.com.calendarfx.view.ViewHelper;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyListProperty;
import javafx.beans.property.ReadOnlyListWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.SelectionMode;
import javafx.scene.effect.Light.Point;
import javafx.scene.input.ContextMenuEvent;
import javafx.scene.input.InputEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.util.Callback;
import org.controlsfx.control.PopOver;
import org.controlsfx.control.PopOver.ArrowLocation;
import org.controlsfx.control.PropertySheet.Item;
import java.text.MessageFormat;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import static java.time.DayOfWeek.SATURDAY;
import static java.time.DayOfWeek.SUNDAY;
import static java.util.Objects.requireNonNull;
import static javafx.scene.control.SelectionMode.MULTIPLE;
import static javafx.scene.input.ContextMenuEvent.CONTEXT_MENU_REQUESTED;
import static javafx.scene.input.MouseButton.PRIMARY;
/**
* The superclass for all controls that are showing calendar information. This
* class is responsible for:
*
*
* Binding to other date controls
* Providing the current date, "today", first day of week
* Creating sources, calendars, entries
* Context menu
* Showing details for a given date or entry
* Providing a virtual grid for editing
* Selection handling
* Printing
*
* Binding Date controls are bound to each other to create complex date
* controls like the {@link CalendarView}. When date controls are bound to each
* other via the {@link #bind(DateControl, boolean)} method then most of their
* properties will be bound to each other. This not only includes date and time
* zone properties but also all the factory and detail callbacks. This allows an
* application to create a complex calendar control and to configure only that
* control without worrying about the date controls that are nested inside of
* it. The children will all "inherit" their settings from the parent control.
*
*
Current Date, Today, First Day of Week The {@link #dateProperty()}
* is used to store the date that the control has to display. For the
* {@link DayView} this would mean that it has to show exactly that date. The
* {@link DetailedWeekView} would only have to guarantee that it shows the week that
* contains this date. For this the {@link DetailedWeekView} uses the
* {@link #getFirstDayOfWeek()} method that looks up its value from the week
* fields stored in the {@link #weekFieldsProperty()}. The
* {@link #todayProperty()} is mainly used for highlighting today's date in the
* view (e.g. a red background).
*
*
Creating Sources, Calendars, Entries The date control uses various
* factories to create new sources, calendars, and entries. Each factory has to
* implement the {@link Callback} interface. The factories will be invoked when
* the application calls {@link #createCalendarSource()} or
* {@link #createEntryAt(ZonedDateTime)}.
*
*
Context Menu Date controls can either set a context menu explicitly
* via {@link #setContextMenu(ContextMenu)} or by providing a callback that gets
* invoked every time the context menu event is received (see
* {@link #setContextMenuCallback(Callback)}). If a context menu has been set
* explicitly then the callback will never be called again.
*
*
Details for Entries and Dates When clicking on an entry or a date
* the user wants to see details regarding the entry or the date. Callbacks for
* this can be registered via {@link #setEntryDetailsCallback(Callback)} and
* {@link #setDateDetailsCallback(Callback)}. The callbacks can decide which
* kind of user interface they want to show to the user. The default
* implementation for both callbacks is a {@link PopOver} control from the
* ControlsFX project.
*
*
Selection Handling Date controls use a very simple selection
* concept. All selected entries are stored inside an observable list (see
* {@link #getSelections()}). The controls support single and multiple
* selections (see {@link #setSelectionMode(SelectionMode)}). Due to the binding
* approach it does not matter in which child date control an entry gets
* selected. All controls will always know which entries are selected.
*
*
Virtual Grid A virtual grid is used for editing. It allows the start
* and end times of entries to snap to "virtual" grid lines. The grid can be
* used to make the times always snap to 5, 10, 15, 30 minutes for example. This
* makes it easier to align entries to each other and covers the most common use
* cases. More precise times can always be set in the details.
*/
public abstract class DateControl extends CalendarFXControl {
private int entryCounter = 1;
private Boolean usesOwnContextMenu;
private final InvalidationListener updateCalendarListListener = (Observable it) -> updateCalendarList();
private final WeakInvalidationListener weakUpdateCalendarListListener = new WeakInvalidationListener(updateCalendarListListener);
/**
* Constructs a new date control and initializes all factories and callbacks
* with default implementations.
*/
protected DateControl() {
setFocusTraversable(false);
setUsagePolicy(count -> {
if (count < 0) {
throw new IllegalArgumentException("usage count can not be smaller than zero, but was " + count);
}
switch (count) {
case 0:
return Usage.NONE;
case 1:
return Usage.VERY_LOW;
case 2:
return Usage.LOW;
case 3:
return Usage.MEDIUM;
case 4:
return Usage.HIGH;
case 5:
default:
return Usage.VERY_HIGH;
}
});
getWeekendDays().add(SATURDAY);
getWeekendDays().add(SUNDAY);
/*
* Every date control is initially populated with a default source and
* calendar.
*/
CalendarSource defaultCalendarSource = new CalendarSource(Messages.getString("DateControl.DEFAULT_CALENDAR_SOURCE_NAME")); //$NON-NLS-1$
Calendar defaultCalendar = new Calendar(Messages.getString("DateControl.DEFAULT_CALENDAR_NAME")); //$NON-NLS-1$
defaultCalendarSource.getCalendars().add(defaultCalendar);
getCalendarSources().add(defaultCalendarSource);
InvalidationListener hidePopOverListener = it -> maybeHidePopOvers();
sceneProperty().addListener(hidePopOverListener);
visibleProperty().addListener(hidePopOverListener);
getCalendarSources().addListener(weakUpdateCalendarListListener);
/*
* The popover content callback creates a content node that will make
* out the content of the popover used to display entry details.
*/
setEntryDetailsPopOverContentCallback(param -> new EntryPopOverContentPane(param.getPopOver(), param.getDateControl(), param.getEntry()));
/*
* The default calendar provider returns the first calendar from the
* first source.
*/
setDefaultCalendarProvider(control -> {
List sources = getCalendarSources();
if (sources != null && !sources.isEmpty()) {
CalendarSource s = sources.get(0);
List extends Calendar> calendars = s.getCalendars();
if (calendars != null && !calendars.isEmpty()) {
for (Calendar c : calendars) {
if (!c.isReadOnly() && isCalendarVisible(c)) {
return c;
}
}
Alert alert = new Alert(AlertType.WARNING);
alert.setTitle(Messages.getString("DateControl.TITLE_CALENDAR_PROBLEM")); //$NON-NLS-1$
alert.setHeaderText(Messages.getString("DateControl.HEADER_TEXT_UNABLE_TO_CREATE_NEW_ENTRY")); //$NON-NLS-1$
String newLine = System.getProperty("line.separator"); //$NON-NLS-1$
alert.setContentText(MessageFormat.format(Messages.getString("DateControl.CONTENT_TEXT_UNABLE_TO_CREATE_NEW_ENTRY"), //$NON-NLS-1$
newLine));
alert.show();
} else {
Alert alert = new Alert(AlertType.WARNING);
alert.setTitle(Messages.getString("DateControl.TITLE_CALENDAR_PROBLEM")); //$NON-NLS-1$
alert.setHeaderText(Messages.getString("DateControl.HEADER_TEXT_NO_CALENDARS_DEFINED")); //$NON-NLS-1$
alert.setContentText(Messages.getString("DateControl.CONTENT_TEXT_NO_CALENDARS_DEFINED")); //$NON-NLS-1$
alert.show();
}
}
return null;
});
setEntryFactory(param -> {
DateControl control = param.getDateControl();
VirtualGrid grid = control.getVirtualGrid();
ZonedDateTime time = param.getZonedDateTime();
DayOfWeek firstDayOfWeek = getFirstDayOfWeek();
ZonedDateTime lowerTime = grid.adjustTime(time, false, firstDayOfWeek);
ZonedDateTime upperTime = grid.adjustTime(time, true, firstDayOfWeek);
if (Duration.between(time, lowerTime).abs().minus(Duration.between(time, upperTime).abs()).isNegative()) {
time = lowerTime;
} else {
time = upperTime;
}
Entry entry = new Entry<>(MessageFormat.format(Messages.getString("DateControl.DEFAULT_ENTRY_TITLE"), entryCounter++)); //$NON-NLS-1$
Interval interval = new Interval(time.toLocalDateTime(), time.toLocalDateTime().plusHours(1));
entry.setInterval(interval);
if (control instanceof AllDayView) {
entry.setFullDay(true);
}
return entry;
});
setEntryDetailsCallback(param -> {
InputEvent evt = param.getInputEvent();
if (evt instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) evt;
if (mouseEvent.getClickCount() == 2) {
showEntryDetails(param.getEntry(), param.getOwner(), param.getScreenY());
return true;
}
} else {
showEntryDetails(param.getEntry(), param.getOwner(), param.getScreenY());
return true;
}
return false;
});
setDateDetailsCallback(param -> {
InputEvent evt = param.getInputEvent();
if (evt == null) {
showDateDetails(param.getOwner(), param.getLocalDate());
return true;
} else if (evt instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) evt;
if (mouseEvent.getClickCount() == 1) {
showDateDetails(param.getOwner(), param.getLocalDate());
return true;
}
}
return false;
});
setContextMenuCallback(new ContextMenuProvider());
setEntryContextMenuCallback(param -> {
EntryViewBase> entryView = param.getEntryView();
Entry> entry = entryView.getEntry();
ContextMenu contextMenu = new ContextMenu();
/*
* Show dialog / popover with entry details.
*/
MenuItem informationItem = new MenuItem(Messages.getString("DateControl.MENU_ITEM_INFORMATION")); //$NON-NLS-1$
informationItem.setOnAction(evt -> {
Callback detailsCallback = getEntryDetailsCallback();
if (detailsCallback != null) {
ContextMenuEvent ctxEvent = param.getContextMenuEvent();
EntryDetailsParameter entryDetailsParam = new EntryDetailsParameter(ctxEvent, DateControl.this, entryView.getEntry(), this, ctxEvent.getScreenX(), ctxEvent.getScreenY());
detailsCallback.call(entryDetailsParam);
}
});
contextMenu.getItems().add(informationItem);
String stylesheet = CalendarView.class.getResource("calendar.css") //$NON-NLS-1$
.toExternalForm();
/*
* Assign entry to different calendars.
*/
Menu calendarMenu = new Menu(Messages.getString("DateControl.MENU_CALENDAR")); //$NON-NLS-1$
for (Calendar calendar : getCalendars()) {
RadioMenuItem calendarItem = new RadioMenuItem(calendar.getName());
calendarItem.setOnAction(evt -> entry.setCalendar(calendar));
calendarItem.setDisable(calendar.isReadOnly());
calendarItem.setSelected(calendar.equals(param.getCalendar()));
calendarMenu.getItems().add(calendarItem);
StackPane graphic = new StackPane();
graphic.getStylesheets().add(stylesheet);
/*
* Icon has to be wrapped in a stackpane so that a stylesheet
* can be added to it.
*/
Rectangle icon = new Rectangle(10, 10);
icon.setArcHeight(2);
icon.setArcWidth(2);
icon.getStyleClass().setAll(calendar.getStyle() + "-icon"); //$NON-NLS-1$
graphic.getChildren().add(icon);
calendarItem.setGraphic(graphic);
}
calendarMenu.setDisable(param.getCalendar().isReadOnly());
contextMenu.getItems().add(calendarMenu);
if (getEntryEditPolicy().call(new EntryEditParameter(this, entry, EditOperation.DELETE))) {
/*
* Delete calendar entry.
*/
MenuItem delete = new MenuItem(Messages.getString("DateControl.MENU_ITEM_DELETE")); //$NON-NLS-1$
contextMenu.getItems().add(delete);
delete.setDisable(param.getCalendar().isReadOnly());
delete.setOnAction(evt -> {
Calendar calendar = entry.getCalendar();
if (!calendar.isReadOnly()) {
if (entry.isRecurrence()) {
entry.getRecurrenceSourceEntry().removeFromCalendar();
} else {
entry.removeFromCalendar();
}
}
});
}
return contextMenu;
});
setCalendarSourceFactory(param -> {
CalendarSource source = new CalendarSource(Messages.getString("DateControl.DEFAULT_NEW_CALENDAR_SOURCE")); //$NON-NLS-1$
Calendar calendar = new Calendar(Messages.getString("DateControl.DEFAULT_NEW_CALENDAR")); //$NON-NLS-1$
calendar.setShortName(Messages.getString("DateControl.DEFAULT_NEW_CALENDAR").substring(0, 1));
source.getCalendars().add(calendar);
return source;
});
addEventHandler(CONTEXT_MENU_REQUESTED, evt -> {
/*
* If a context menu was specified by calling setContextMenu() then
* we will not use the callback to produce one.
*/
if (null == usesOwnContextMenu) {
usesOwnContextMenu = getContextMenu() != null;
}
if (!usesOwnContextMenu) {
evt.consume();
Callback callback = getContextMenuCallback();
if (callback != null) {
Callback calendarProvider = getDefaultCalendarProvider();
Calendar calendar = calendarProvider.call(DateControl.this);
ZonedDateTime time = ZonedDateTime.now();
if (DateControl.this instanceof ZonedDateTimeProvider) {
ZonedDateTimeProvider provider = (ZonedDateTimeProvider) DateControl.this;
time = provider.getZonedDateTimeAt(evt.getX(), evt.getY());
}
ContextMenuParameter param = new ContextMenuParameter(evt, DateControl.this, calendar, time);
ContextMenu menu = callback.call(param);
if (menu != null) {
setContextMenu(menu);
menu.show(DateControl.this, evt.getScreenX(), evt.getScreenY());
}
}
}
});
addEventFilter(MouseEvent.MOUSE_PRESSED, evt -> {
maybeHidePopOvers();
performSelection(evt);
});
}
private final ObservableMap calendarVisibilityMap = FXCollections.observableHashMap();
public final ObservableMap getCalendarVisibilityMap() {
return calendarVisibilityMap;
}
public final BooleanProperty getCalendarVisibilityProperty(Calendar calendar) {
return calendarVisibilityMap.computeIfAbsent(calendar, cal -> new SimpleBooleanProperty(DateControl.this, "visible", true));
}
public final boolean isCalendarVisible(Calendar calendar) {
BooleanProperty prop = getCalendarVisibilityProperty(calendar);
return prop.get();
}
public final void setCalendarVisibility(Calendar calendar, boolean visible) {
BooleanProperty prop = getCalendarVisibilityProperty(calendar);
prop.set(visible);
}
/**
* Requests that the date control should reload its data and recreate its
* entry views. Normally applications do not have to call this method. It is
* more like a backdoor for client / server applications where the server is
* unable to push changes to the client. In this case the client must
* frequently trigger an explicit refresh.
*/
public final void refreshData() {
getProperties().put("refresh.data", true); //$NON-NLS-1$
getBoundDateControls().forEach(DateControl::refreshData);
}
private void performSelection(MouseEvent evt) {
if ((evt.getButton().equals(PRIMARY) || evt.isPopupTrigger()) && evt.getClickCount() == 1) {
Entry> entry;
EntryViewBase> view = null;
if (evt.getTarget() instanceof EntryViewBase) {
view = (EntryViewBase>) evt.getTarget();
}
if (view == null) {
return;
}
String disableFocusHandlingKey = "disable-focus-handling"; //$NON-NLS-1$
view.getProperties().put(disableFocusHandlingKey, true);
view.requestFocus();
entry = view.getEntry();
if (entry != null) {
if (!isMultiSelect(evt) && !getSelections().contains(entry)) {
clearSelection();
}
if (isMultiSelect(evt) && getSelections().contains(entry)) {
getSelections().remove(entry);
} else if (!getSelections().contains(entry)) {
getSelections().add(entry);
}
}
view.getProperties().remove(disableFocusHandlingKey);
}
}
private boolean isMultiSelect(MouseEvent evt) {
return (evt.isShiftDown() || evt.isShortcutDown()) && getSelectionMode().equals(MULTIPLE);
}
/**
* Creates a new calendar source that will be added to the list of calendar
* sources of this date control. The method delegates the actual creation of
* the calendar source to a factory, which can be specified by calling
* {@link #setCalendarSourceFactory(Callback)}.
*
* @see #setCalendarSourceFactory(Callback)
*/
public final void createCalendarSource() {
Callback factory = getCalendarSourceFactory();
if (factory != null) {
CreateCalendarSourceParameter param = new CreateCalendarSourceParameter(this);
CalendarSource calendarSource = factory.call(param);
if (calendarSource != null && !getCalendarSources().contains(calendarSource)) {
getCalendarSources().add(calendarSource);
}
}
}
// dragged entry support
private final ObjectProperty draggedEntry = new SimpleObjectProperty<>(this, "draggedEntry"); //$NON-NLS-1$
/**
* Stores a {@link DraggedEntry} instance, which serves as a wrapper around
* the actual entry that is currently being edited by the user. The
* framework creates this wrapper when the user starts a drag and adds it to
* the date control. This allows the framework to show the entry at its old
* and new location at the same time. It also ensures that the calendar does
* not fire any events before the user has committed the entry to a new
* location.
*
* @return the dragged entry
*/
public final ObjectProperty draggedEntryProperty() {
return draggedEntry;
}
/**
* Returns the value of {@link #draggedEntryProperty()}.
*
* @return the dragged entry
*/
public final DraggedEntry getDraggedEntry() {
return draggedEntry.get();
}
/**
* Sets the value of {@link #draggedEntryProperty()}.
*
* @param entry the dragged entry
*/
public final void setDraggedEntry(DraggedEntry entry) {
draggedEntryProperty().set(entry);
}
/**
* Creates a new entry at the given time. The method delegates the actual
* instance creation to the entry factory (see
* {@link #entryFactoryProperty()}). The factory receives a parameter object
* that contains the default calendar where the entry can be added, however
* the factory can choose to add the entry to any calendar it likes. Please
* note that the time passed to the factory will be adjusted based on the
* current virtual grid settings (see {@link #virtualGridProperty()}).
*
* @param time the time where the entry will be created (the entry start
* time)
* @return the new calendar entry
* @see #setEntryFactory(Callback)
* @see #setVirtualGrid(VirtualGrid)
*/
public final Entry> createEntryAt(ZonedDateTime time) {
return createEntryAt(time, null);
}
/**
* Creates a new entry at the given time. The method delegates the actual
* instance creation to the entry factory (see
* {@link #entryFactoryProperty()}). The factory receives a parameter object
* that contains the calendar where the entry can be added, however the
* factory can choose to add the entry to any calendar it likes. Please note
* that the time passed to the factory will be adjusted based on the current
* virtual grid settings (see {@link #virtualGridProperty()}).
*
* @param time the time where the entry will be created (the entry start
* time)
* @param calendar the calendar to which the new entry will be added (if null the
* default calendar provider will be invoked)
* @return the new calendar entry
* @see #setEntryFactory(Callback)
* @see #setVirtualGrid(VirtualGrid)
*/
public final Entry> createEntryAt(ZonedDateTime time, Calendar calendar) {
requireNonNull(time);
VirtualGrid grid = getVirtualGrid();
if (grid != null) {
ZonedDateTime timeA = grid.adjustTime(time, false, getFirstDayOfWeek());
ZonedDateTime timeB = grid.adjustTime(time, true, getFirstDayOfWeek());
if (Duration.between(time, timeA).abs().minus(Duration.between(time, timeB).abs()).isNegative()) {
time = timeA;
} else {
time = timeB;
}
}
if (calendar == null) {
Callback defaultCalendarProvider = getDefaultCalendarProvider();
calendar = defaultCalendarProvider.call(this);
}
if (calendar != null) {
/*
* We have to ensure that the calendar is visible, otherwise the new
* entry would not be shown to the user.
*/
setCalendarVisibility(calendar, true);
CreateEntryParameter param = new CreateEntryParameter(this, calendar, time);
Callback> factory = getEntryFactory();
Entry> entry = factory.call(param);
if (entry != null) {
/*
* This is OK. The factory can return NULL. In this case we
* assume that the application does not allow to create an entry
* at the given location.
*/
entry.setCalendar(calendar);
}
return entry;
} else {
LoggingDomain.EDITING.warning("No calendar found for adding a new entry."); //$NON-NLS-1$
}
return null;
}
/**
* Returns the calendar shown at the given location. This method returns an
* optional value. Calling this method might or might not make sense,
* depending on the type of control and the current layout (see
* {@link #layoutProperty()}).
*
* @param x the x-coordinate to check
* @param y the y-coordinate to check
* @return the calendar at the given location
*/
public Optional getCalendarAt(double x, double y) {
return Optional.empty();
}
/**
* Adjusts the current view / page in such a way that the given entry
* becomes visible.
*
* @param entry the entry to show
*/
public final void showEntry(Entry> entry) {
requireNonNull(entry);
doShowEntry(entry, false);
}
/**
* Adjusts the current view / page in such a way that the given entry
* becomes visible and brings up the details editor / UI for the entry
* (default is a popover).
*
* @param entry the entry to show
*/
public final void editEntry(Entry> entry) {
requireNonNull(entry);
doShowEntry(entry, true);
}
private void doShowEntry(Entry> entry, boolean startEditing) {
setDate(entry.getStartDate());
if (!entry.isFullDay()) {
setRequestedTime(entry.getStartTime());
}
if (startEditing) {
/*
* The UI first needs to update itself so that the matching entry
* view can be found.
*/
Platform.runLater(() -> doEditEntry(entry));
} else {
Platform.runLater(() -> doBounceEntry(entry));
}
}
private void doEditEntry(Entry> entry) {
EntryViewBase> entryView = findEntryView(entry);
if (entryView != null) {
entryView.bounce();
Point2D localToScreen = entryView.localToScreen(0, 0);
Callback callback = getEntryDetailsCallback();
EntryDetailsParameter param = new EntryDetailsParameter(null, this, entry, entryView, localToScreen.getX(), localToScreen.getY());
callback.call(param);
}
}
private void doBounceEntry(Entry> entry) {
EntryViewBase> entryView = findEntryView(entry);
if (entryView != null) {
entryView.bounce();
}
}
private static PopOver entryPopOver;
private void showEntryDetails(Entry> entry, Node owner, double screenY) {
maybeHidePopOvers();
Callback contentCallback = getEntryDetailsPopOverContentCallback();
if (contentCallback == null) {
throw new IllegalStateException("No content callback found for entry popover"); //$NON-NLS-1$
}
entryPopOver = new PopOver();
EntryDetailsPopOverContentParameter param = new EntryDetailsPopOverContentParameter(entryPopOver, this, owner, entry);
Node content = contentCallback.call(param);
if (content == null) {
content = new Label(Messages.getString("DateControl.NO_CONTENT")); //$NON-NLS-1$
}
entryPopOver.setContentNode(content);
ArrowLocation location = ViewHelper.findPopOverArrowLocation(owner);
entryPopOver.setArrowLocation(location);
Point position = ViewHelper.findPopOverArrowPosition(owner, screenY, entryPopOver.getArrowSize(), location);
entryPopOver.show(owner, position.getX(), position.getY());
}
private static DatePopOver datePopOver;
/**
* Creates a new {@link DatePopOver} and shows it attached to the given
* owner node.
*
* @param owner the owner node
* @param date the date for which to display more detail
*/
public void showDateDetails(Node owner, LocalDate date) {
maybeHidePopOvers();
datePopOver = new DatePopOver(this, date);
datePopOver.show(owner);
}
private void maybeHidePopOvers() {
if (entryPopOver != null && entryPopOver.isShowing() && !entryPopOver.isDetached()) {
entryPopOver.hide();
}
if (datePopOver != null && datePopOver.isShowing() && !datePopOver.isDetached()) {
datePopOver.hide();
}
}
private abstract static class ContextMenuParameterBase {
private DateControl dateControl;
private ContextMenuEvent contextMenuEvent;
public ContextMenuParameterBase(ContextMenuEvent contextMenuEvent, DateControl dateControl) {
this.contextMenuEvent = requireNonNull(contextMenuEvent);
this.dateControl = requireNonNull(dateControl);
}
public ContextMenuEvent getContextMenuEvent() {
return contextMenuEvent;
}
public DateControl getDateControl() {
return dateControl;
}
}
/**
* The parameter object passed to the entry factory. It contains the most
* important parameters for creating a new entry: the requesting date
* control, the time where the user performed a double click and the default
* calendar.
*
* @see DateControl#entryFactoryProperty()
* @see DateControl#defaultCalendarProviderProperty()
* @see DateControl#createEntryAt(ZonedDateTime)
*/
public static final class CreateEntryParameter {
private final Calendar calendar;
private final ZonedDateTime zonedDateTime;
private final DateControl control;
/**
* Constructs a new parameter object.
*
* @param control the control where the user / the application wants to
* create a new entry
* @param calendar the default calendar
* @param time the time selected by the user in the date control
*/
public CreateEntryParameter(DateControl control, Calendar calendar, ZonedDateTime time) {
this.control = requireNonNull(control);
this.calendar = requireNonNull(calendar);
this.zonedDateTime = requireNonNull(time);
}
/**
* Returns the default calendar. Applications can add the new entry to
* this calendar by calling {@link Entry#setCalendar(Calendar)} or the
* can choose any other calendar.
*
* @return the default calendar
*/
public Calendar getDefaultCalendar() {
return calendar;
}
/**
* The time selected by the user.
*
* @return the start time for the new entry
*/
public ZonedDateTime getZonedDateTime() {
return zonedDateTime;
}
/**
* The date control where the user performed the double click.
*
* @return the date control where the event happened
*/
public DateControl getDateControl() {
return control;
}
@Override
public String toString() {
return "CreateEntryParameter [calendar=" + calendar //$NON-NLS-1$
+ ", zonedDateTime=" + zonedDateTime + "]"; //$NON-NLS-1$ //$NON-NLS-2$
}
}
private final ObjectProperty>> entryFactory = new SimpleObjectProperty<>(this, "entryFactory"); //$NON-NLS-1$
/**
* A factory for creating new entries when the user double clicks inside the
* date control or when the application calls
* {@link #createEntryAt(ZonedDateTime)}. The factory can return NULL to
* indicate that no entry can be created at the given location.
*
*
Code Example
*
* The code below shows the default entry factory that is set on every date
* control.
*
*
*
* setEntryFactory(param -> {
* DateControl control = param.getControl();
* VirtualGrid grid = control.getVirtualGrid();
* ZonedDateTime time = param.getZonedDateTime();
* DayOfWeek firstDayOfWeek = getFirstDayOfWeek();
*
* ZonedDateTime lowerTime = grid.adjustTime(time, false, firstDayOfWeek);
* ZonedDateTime upperTime = grid.adjustTime(time, true, firstDayOfWeek);
*
* if (Duration.between(time, lowerTime).abs().minus(Duration.between(time, upperTime).abs()).isNegative()) {
* time = lowerTime;
* } else {
* time = upperTime;
* }
*
* Entry<Object> entry = new Entry<>("New Entry");
* entry.changeStartDate(time.toLocalDate());
* entry.changeStartTime(time.toLocalTime());
* entry.changeEndDate(entry.getStartDate());
* entry.changeEndTime(entry.getStartTime().plusHours(1));
*
* if (control instanceof AllDayView) {
* entry.setFullDay(true);
* }
*
* return entry;
* });
*
*
* @return the entry factory callback
*/
public final ObjectProperty>> entryFactoryProperty() {
return entryFactory;
}
/**
* Returns the value of {@link #entryFactoryProperty()}.
*
* @return the factory used for creating a new entry
*/
public final Callback> getEntryFactory() {
return entryFactoryProperty().get();
}
/**
* Sets the value of {@link #entryFactoryProperty()}.
*
* @param factory the factory used for creating a new entry
*/
public final void setEntryFactory(Callback> factory) {
Objects.requireNonNull(factory);
entryFactoryProperty().set(factory);
}
/*
* Calendar source callback.
*/
/**
* The parameter object passed to the calendar source factory.
*
* @see DateControl#setCalendarSourceFactory(Callback)
*/
public static final class CreateCalendarSourceParameter {
private DateControl dateControl;
/**
* Constructs a new parameter object.
*
* @param dateControl the control where the source will be added
*/
public CreateCalendarSourceParameter(DateControl dateControl) {
this.dateControl = requireNonNull(dateControl);
}
/**
* The control where the source will be added.
*
* @return the date control
*/
public DateControl getDateControl() {
return dateControl;
}
}
private final ObjectProperty> calendarSourceFactory = new SimpleObjectProperty<>(this, "calendarSourceFactory"); //$NON-NLS-1$
/**
* A factory for creating a new calendar source, e.g. a new Google calendar
* account.
*
*
Code Example The code below shows the default implementation of
* this factory. Applications can choose to bring up a full featured user
* interface / dialog to specify the exact location of the source (either
* locally or over a network). A local calendar source might read its data
* from an XML file while a remote source could load data from a web
* service.
*
*
* setCalendarSourceFactory(param -> {
* CalendarSource source = new CalendarSource("Calendar Source");
* Calendar calendar = new Calendar("Calendar");
* source.getCalendars().add(calendar);
* return source;
* });
*
*
* The factory can be invoked by calling {@link #createCalendarSource()}.
*
* @return the calendar source factory
* @see #createCalendarSource()
*/
public final ObjectProperty> calendarSourceFactoryProperty() {
return calendarSourceFactory;
}
/**
* Returns the value of {@link #calendarSourceFactoryProperty()}.
*
* @return the calendar source factory
*/
public final Callback getCalendarSourceFactory() {
return calendarSourceFactoryProperty().get();
}
/**
* Sets the value of {@link #calendarSourceFactoryProperty()}.
*
* @param callback the callback used for creating a new calendar source
*/
public final void setCalendarSourceFactory(Callback callback) {
calendarSourceFactoryProperty().set(callback);
}
/*
* Context menu callback for entries.
*/
/**
* The parameter object passed to the context menu callback for entries.
*
* @see DateControl#entryContextMenuCallbackProperty()
*/
public static final class EntryContextMenuParameter extends ContextMenuParameterBase {
private EntryViewBase> entryView;
/**
* Constructs a new context menu parameter object.
*
* @param evt the event that triggered the context menu
* @param control the date control where the event occurred
* @param entryView the entry view for which the context menu will be created
*/
public EntryContextMenuParameter(ContextMenuEvent evt, DateControl control, EntryViewBase> entryView) {
super(evt, control);
this.entryView = requireNonNull(entryView);
}
/**
* The entry view for which the context menu will be shown.
*
* @return the entry view
*/
public EntryViewBase> getEntryView() {
return entryView;
}
/**
* Convenience method to easily lookup the entry for which the view was
* created.
*
* @return the calendar entry
*/
public Entry> getEntry() {
return entryView.getEntry();
}
/**
* Convenience method to easily lookup the calendar of the entry for
* which the view was created.
*
* @return the calendar
*/
public Calendar getCalendar() {
return getEntry().getCalendar();
}
@Override
public String toString() {
return "EntryContextMenuParameter [entry=" + entryView //$NON-NLS-1$
+ ", dateControl =" + getDateControl() + "]"; //$NON-NLS-1$ //$NON-NLS-2$
}
}
// entry edit support
/**
* Possible edit operations on an entry. This enum will be used as parameter of the
* callback set with {@link DateControl#setEntryEditPolicy}.
*
* @see #setEntryEditPolicy(Callback)
*/
public enum EditOperation {
/**
* Checked if the start of an entry can be changed.
*/
CHANGE_START,
/**
* Checked if the end of an entry can be changed.
*/
CHANGE_END,
/**
* Checked if entry can be moved around, hence changing start and end time at
* the same time.
*/
MOVE,
/**
* Checked if an entry can be deleted.
*/
DELETE
}
/**
* Class used for parameter of {@link DateControl#entryEditPolicy}
* functional interface.
*/
public static final class EntryEditParameter {
/**
* The date control the entity is associated with.
*/
private final DateControl dateControl;
/**
* The entity the operation is operated on.
*/
private final Entry> entry;
/**
* The operation.
*/
private final DateControl.EditOperation editOperation;
public EntryEditParameter(DateControl dateControl, Entry> entry, EditOperation editOperation) {
this.dateControl = Objects.requireNonNull(dateControl);
this.entry = Objects.requireNonNull(entry);
this.editOperation = Objects.requireNonNull(editOperation);
}
/**
* The {@link DateControl} which is asking for a specific {@link DateControl.EditOperation} permission.
* @return The date control.
*/
public DateControl getDateControl() {
return dateControl;
}
/**
* The entry where the {@link com.calendarfx.view.DateControl.EditOperation} should be applied.
*
* @return The entry.
*/
public Entry> getEntry() {
return entry;
}
/**
* The actual edit operation.
*
* @return The edit operation.
*/
public EditOperation getEditOperation() {
return editOperation;
}
@Override
public String toString() {
return "EntryEditParameter{" +
"dateControl=" + dateControl +
", entry=" + entry +
", editOperation=" + editOperation +
'}';
}
}
private final ObjectProperty> entryEditPolicy = new SimpleObjectProperty<>(action -> true);
/**
* A property that stores a callback used for editing entries. If an edit operation will be executed
* on an entry then the callback will be invoked to determine if the operation is allowed. By default
* all operations listed inside {@link EditOperation} are allowed.
*
* @see EditOperation
*/
public final ObjectProperty> entryEditPolicyProperty() {
return entryEditPolicy;
}
/**
* Returns the value of {@link #entryEditPolicy}.
*
* @return The entry edit policy callback
*
* @see EditOperation
*/
public final Callback getEntryEditPolicy() {
return entryEditPolicy.get();
}
/**
* Sets the value of {@link #entryEditPolicy}.
*
* @param policy the entry edit policy callback
*
* @see EditOperation
*/
public final void setEntryEditPolicy(Callback policy) {
Objects.requireNonNull(policy, "The edit entry policy can not be null");
this.entryEditPolicy.set(policy);
}
private final ObjectProperty> entryContextMenuCallback = new SimpleObjectProperty<>(this, "entryFactory"); //$NON-NLS-1$
/**
* A callback used for dynamically creating a context menu for a given
* entry view.
*
*
Code Example The code below shows the default implementation of
* this callback.
*
*
*
* setEntryContextMenuCallback(param -> {
* EntryViewBase<?> entryView = param.getEntryView();
* Entry<?> entry = entryView.getEntry();
*
* ContextMenu contextMenu = new ContextMenu();
*
* MenuItem informationItem = new MenuItem("Information");
* informationItem.setOnAction(evt -> {
* Callback<EntryDetailsParameter, Boolean> detailsCallback = getEntryDetailsCallback();
* if (detailsCallback != null) {
* ContextMenuEvent ctxEvent = param.getContextMenuEvent();
* EntryDetailsParameter entryDetailsParam = new EntryDetailsParameter(ctxEvent, DateControl.this, entryView, this, ctxEvent.getScreenX(), ctxEvent.getScreenY());
* detailsCallback.call(entryDetailsParam);
* }
* });
* contextMenu.getItems().add(informationItem);
*
* Menu calendarMenu = new Menu("Calendar");
* for (Calendar calendar : getCalendars()) {
* MenuItem calendarItem = new MenuItem(calendar.getName());
* calendarItem.setOnAction(evt -> entry.setCalendar(calendar));
* calendarMenu.getItems().add(calendarItem);
* }
* contextMenu.getItems().add(calendarMenu);
*
* return contextMenu;
* });
*
*
* @return the property used for storing the callback
*/
public final ObjectProperty> entryContextMenuCallbackProperty() {
return entryContextMenuCallback;
}
/**
* Returns the value of {@link #entryContextMenuCallbackProperty()}.
*
* @return the callback for creating a context menu for a given calendar
* entry
*/
public final Callback getEntryContextMenuCallback() {
return entryContextMenuCallbackProperty().get();
}
/**
* Sets the value of {@link #entryContextMenuCallbackProperty()}.
*
* @param callback the callback used for creating a context menu for a calendar
* entry
*/
public final void setEntryContextMenuCallback(Callback callback) {
entryContextMenuCallbackProperty().set(callback);
}
/*
* Context menu callback.
*/
/**
* The parameter object passed to the context menu callback.
*
* @see DateControl#contextMenuCallbackProperty()
*/
public static final class ContextMenuParameter extends ContextMenuParameterBase {
private Calendar calendar;
private ZonedDateTime zonedDateTime;
/**
* Constructs a new parameter object.
*
* @param evt the event that triggered the context menu
* @param dateControl the date control where the event occurred
* @param calendar the (default) calendar where newly created entries should
* be added
* @param time the time where the mouse click occurred
*/
public ContextMenuParameter(ContextMenuEvent evt, DateControl dateControl, Calendar calendar, ZonedDateTime time) {
super(evt, dateControl);
this.calendar = requireNonNull(calendar);
this.zonedDateTime = time;
}
/**
* The (default) calendar where newly created entries should be added.
* Only relevant if the context menu is actually used for creating new
* entries. This can be different from application to application.
*
* @return the (default) calendar for adding new entries
*/
public Calendar getCalendar() {
return calendar;
}
/**
* The time where the mouse click occurred.
*
* @return the time shown at the mouse click location
*/
public ZonedDateTime getZonedDateTime() {
return zonedDateTime;
}
@Override
public String toString() {
return "ContextMenuParameter [calendar=" + calendar //$NON-NLS-1$
+ ", zonedDateTime=" + zonedDateTime + "]"; //$NON-NLS-1$ //$NON-NLS-2$
}
}
private final ObjectProperty> contextMenuCallback = new SimpleObjectProperty<>(this, "contextMenuCallback"); //$NON-NLS-1$
/**
* The context menu callback that will be invoked when the user triggers the
* context menu by clicking in an area without an entry view. Using a
* callback allows the application to create context menus with different
* content, depending on the current state of the application and the
* location of the click.
*
*
Code Example
*
* The code below shows a part of the default implementation:
*
*
*
* setContextMenuCallback(param -> {
* ContextMenu menu = new ContextMenu();
* MenuItem newEntryItem = new MenuItem("Add New Event");
* newEntryItem.setOnAction(evt -> {
* createEntryAt(param.getZonedDateTime());
* });
* menu.getItems().add(newEntry);
* return menu;
* });
*
*
* @return the context menu callback
*/
public final ObjectProperty> contextMenuCallbackProperty() {
return contextMenuCallback;
}
/**
* Returns the value of {@link #contextMenuCallbackProperty()}.
*
* @return the context menu callback
*/
public final Callback getContextMenuCallback() {
return contextMenuCallbackProperty().get();
}
/**
* Sets the value of {@link #contextMenuCallbackProperty()}.
*
* @param callback the context menu callback
*/
public final void setContextMenuCallback(Callback callback) {
contextMenuCallbackProperty().set(callback);
}
/*
* Default calendar provider callback.
*/
private final ObjectProperty> defaultCalendarProvider = new SimpleObjectProperty<>(this, "defaultCalendarProvider"); //$NON-NLS-1$
/**
* The default calendar provider is responsible for returning a calendar
* that can be used to add a new entry. This way the user can add new
* entries by simply double clicking inside the view without the need of
* first showing a calendar selection UI. This can be changed by setting a
* callback that prompts the user with a dialog.
*
*
Code Example
*
* The code shown below is the default implementation of this provider. It
* returns the first calendar of the first source. If no source is available
* it will return null.
*
*
*
* setDefaultCalendarProvider(control -> {
* List<CalendarSource> sources = getCalendarSources();
* if (sources != null && !sources.isEmpty()) {
* CalendarSource s = sources.get(0);
* List<? extends Calendar> calendars = s.getCalendars();
* if (calendars != null && !calendars.isEmpty()) {
* return calendars.get(0);
* }
* }
*
* return null;
* });
*
*
* @return the default calendar provider callback
*/
public final ObjectProperty> defaultCalendarProviderProperty() {
return defaultCalendarProvider;
}
/**
* Returns the value of {@link #defaultCalendarProviderProperty()}.
*
* @return the default calendar provider
*/
public final Callback getDefaultCalendarProvider() {
return defaultCalendarProviderProperty().get();
}
/**
* Sets the value of {@link #defaultCalendarProviderProperty()}.
*
* @param provider the default calendar provider
*/
public final void setDefaultCalendarProvider(Callback provider) {
requireNonNull(provider);
defaultCalendarProviderProperty().set(provider);
}
private void updateCalendarList() {
List removedCalendars = new ArrayList<>(calendars);
List newCalendars = new ArrayList<>();
for (CalendarSource source : getCalendarSources()) {
for (Calendar calendar : source.getCalendars()) {
if (calendars.contains(calendar)) {
removedCalendars.remove(calendar);
} else {
newCalendars.add(calendar);
}
}
source.getCalendars().removeListener(weakUpdateCalendarListListener);
source.getCalendars().addListener(weakUpdateCalendarListListener);
}
calendars.addAll(newCalendars);
calendars.removeAll(removedCalendars);
}
private abstract static class DetailsParameter {
private InputEvent inputEvent;
private DateControl dateControl;
private Node owner;
private double screenX;
private double screenY;
/**
* Constructs a new parameter object.
*
* @param inputEvent the input event that triggered the need for showing entry
* details (e.g. a mouse double click, or a context menu item
* selection)
* @param control the control where the event occurred
* @param owner a node that can be used as an owner for the dialog or
* popover
* @param screenX the screen location where the event occurred
* @param screenY the screen location where the event occurred
*/
public DetailsParameter(InputEvent inputEvent, DateControl control, Node owner, double screenX, double screenY) {
this.inputEvent = inputEvent;
this.dateControl = requireNonNull(control);
this.owner = requireNonNull(owner);
this.screenX = screenX;
this.screenY = screenY;
}
/**
* Returns the node that should be used as the owner of a dialog /
* popover. We should not use the entry view as the owner of a dialog /
* popover because views come and go. We need something that lives
* longer.
*
* @return an owner node for the details dialog / popover
*/
public Node getOwner() {
return owner;
}
/**
* The screen X location where the event occurred.
*
* @return the screen x location of the event
*/
public double getScreenX() {
return screenX;
}
/**
* The screen Y location where the event occurred.
*
* @return the screen y location of the event
*/
public double getScreenY() {
return screenY;
}
/**
* The input event that triggered the need for showing entry details
* (e.g. a mouse double click or a context menu item selection).
*
* @return the input event
*/
public InputEvent getInputEvent() {
return inputEvent;
}
/**
* The date control where the event occurred.
*
* @return the date control
*/
public DateControl getDateControl() {
return dateControl;
}
}
/**
* The parameter object passed to the entry details callback.
*
* @see DateControl#entryDetailsCallbackProperty()
*/
public final static class EntryDetailsParameter extends DetailsParameter {
private Entry> entry;
/**
* Constructs a new parameter object.
*
* @param inputEvent the input event that triggered the need for showing entry
* details (e.g. a mouse double click, or a context menu item
* selection)
* @param control the control where the event occurred
* @param entry the entry for which details are requested
* @param owner a node that can be used as an owner for the dialog or
* popover
* @param screenX the screen location where the event occurred
* @param screenY the screen location where the event occurred
*/
public EntryDetailsParameter(InputEvent inputEvent, DateControl control, Entry> entry, Node owner, double screenX, double screenY) {
super(inputEvent, control, owner, screenX, screenY);
this.entry = entry;
}
/**
* The entry for which details are requested.
*
* @return the entry view
*/
public Entry> getEntry() {
return entry;
}
}
/**
* The parameter object passed to the date details callback.
*
* @see DateControl#dateDetailsCallbackProperty()
*/
public final static class DateDetailsParameter extends DetailsParameter {
private LocalDate localDate;
/**
* Constructs a new parameter object.
*
* @param inputEvent the input event that triggered the need for showing entry
* details (e.g. a mouse double click, or a context menu item
* selection)
* @param control the control where the event occurred
* @param date the date for which details are required
* @param owner a node that can be used as an owner for the dialog or
* popover
* @param screenX the screen location where the event occurred
* @param screenY the screen location where the event occurred
*/
public DateDetailsParameter(InputEvent inputEvent, DateControl control, Node owner, LocalDate date, double screenX, double screenY) {
super(inputEvent, control, owner, screenX, screenY);
this.localDate = requireNonNull(date);
}
/**
* The date for which details are required.
*
* @return the date
*/
public LocalDate getLocalDate() {
return localDate;
}
}
private final ObjectProperty> dateDetailsCallback = new SimpleObjectProperty<>(this, "dateDetailsCallback"); //$NON-NLS-1$
/**
* A callback used for showing the details of a given date. The default
* implementation of this callback displays a small {@link PopOver} but
* applications might as well display a large dialog where the user can
* freely edit the date.
*
*
Code Example The code below shows the default implementation
* used by all date controls. It delegates to a private method that shows
* the popover.
*
*
* setDateDetailsCallback(param -> {
* InputEvent evt = param.getInputEvent();
* if (evt instanceof MouseEvent) {
* MouseEvent mouseEvent = (MouseEvent) evt;
* if (mouseEvent.getClickCount() == 1) {
* showDateDetails(param.getOwner(), param.getLocalDate());
* return true;
* }
* }
*
* return false;
* });
*
*
* @return the callback for showing details for a given date
*/
public final ObjectProperty> dateDetailsCallbackProperty() {
return dateDetailsCallback;
}
/**
* Sets the value of {@link #dateDetailsCallbackProperty()}.
*
* @param callback the date details callback
*/
public final void setDateDetailsCallback(Callback callback) {
requireNonNull(callback);
dateDetailsCallbackProperty().set(callback);
}
/**
* Returns the value of {@link #dateDetailsCallbackProperty()}.
*
* @return the date details callback
*/
public final Callback getDateDetailsCallback() {
return dateDetailsCallbackProperty().get();
}
private final ObjectProperty> entryDetailsCallback = new SimpleObjectProperty<>(this, "entryDetailsCallback"); //$NON-NLS-1$
/**
* A callback used for showing the details of a given entry. The default
* implementation of this callback displays a small {@link PopOver} but
* applications might as well display a large dialog where the user can
* freely edit the entry.
*
*
Code Example The code below shows the default implementation
* used by all date controls. It delegates to a private method that shows
* the popover.
*
*
* setEntryDetailsCallback(param -> {
* InputEvent evt = param.getInputEvent();
* if (evt instanceof MouseEvent) {
* MouseEvent mouseEvent = (MouseEvent) evt;
* if (mouseEvent.getClickCount() == 2) {
* showEntryDetails(param.getEntryView(), param.getOwner(), param.getScreenX(), param.getScreenY());
* return true;
* }
* } else {
* showEntryDetails(param.getEntryView(), param.getOwner(), param.getScreenX(), param.getScreenY());
* return true;
* }
*
* return false;
* });
*
*
* @return the callback used for showing details for a given entry
*/
public final ObjectProperty> entryDetailsCallbackProperty() {
return entryDetailsCallback;
}
/**
* Sets the value of {@link #entryDetailsCallbackProperty()}.
*
* @param callback the entry details callback
*/
public final void setEntryDetailsCallback(Callback callback) {
requireNonNull(callback);
entryDetailsCallbackProperty().set(callback);
}
/**
* Returns the value of {@link #entryDetailsCallbackProperty()}.
*
* @return the entry details callback
*/
public final Callback getEntryDetailsCallback() {
return entryDetailsCallbackProperty().get();
}
////////////
/**
* The parameter object passed to the entry details popover content
* callback.
*
* @see DateControl#entryDetailsPopOverContentCallbackProperty()
*/
public final static class EntryDetailsPopOverContentParameter {
private DateControl dateControl;
private Node node;
private Entry> entry;
private PopOver popOver;
/**
* Constructs a new parameter object.
*
* @param popOver the pop over for which details will be created
* @param control the control where the event occurred
* @param node the node where the event occurred
* @param entry the entry for which details will be shown
*/
public EntryDetailsPopOverContentParameter(PopOver popOver, DateControl control, Node node, Entry> entry) {
this.popOver = requireNonNull(popOver);
this.dateControl = requireNonNull(control);
this.node = requireNonNull(node);
this.entry = requireNonNull(entry);
}
/**
* Returns the popover in which the content will be shown.
*
* @return the popover
*/
public PopOver getPopOver() {
return popOver;
}
/**
* The date control where the popover was requested.
*
* @return the date control
*/
public DateControl getDateControl() {
return dateControl;
}
/**
* The node for which the popover was requested.
*
* @return the node
*/
public Node getNode() {
return node;
}
/**
* The entry for which the popover was requested.
*
* @return the entry
*/
public Entry> getEntry() {
return entry;
}
}
private final ObjectProperty> entryDetailsPopoverContentCallback = new SimpleObjectProperty<>(this, "entryDetailsPopoverContentCallback"); //$NON-NLS-1$
/**
* Stores a callback for creating the content of the popover.
*
* @return the popover content callback
*/
public final ObjectProperty> entryDetailsPopOverContentCallbackProperty() {
return entryDetailsPopoverContentCallback;
}
/**
* Sets the value of {@link #entryDetailsPopOverContentCallbackProperty()}.
*
* @param callback the entry details popover content callback
*/
public final void setEntryDetailsPopOverContentCallback(Callback callback) {
requireNonNull(callback);
entryDetailsPopOverContentCallbackProperty().set(callback);
}
/**
* Returns the value of
* {@link #entryDetailsPopOverContentCallbackProperty()}.
*
* @return the entry details popover content callback
*/
public final Callback getEntryDetailsPopOverContentCallback() {
return entryDetailsPopOverContentCallbackProperty().get();
}
///////////
private final ObjectProperty today = new SimpleObjectProperty<>(this, "today", LocalDate.now()); //$NON-NLS-1$
/**
* Stores the date that is considered to represent "today". This property is
* initialized with {@link LocalDate#now()} but can be any date.
*
* @return the date representing "today"
*/
public final ObjectProperty todayProperty() {
return today;
}
/**
* Sets the value of {@link #todayProperty()}.
*
* @param date the date representing "today"
*/
public final void setToday(LocalDate date) {
requireNonNull(date);
todayProperty().set(date);
}
/**
* Returns the value of {@link #todayProperty()}.
*
* @return the date representing "today"
*/
public final LocalDate getToday() {
return todayProperty().get();
}
private final BooleanProperty showToday = new SimpleBooleanProperty(
this, "showToday", true); //$NON-NLS-1$
/**
* A flag used to indicate that the view will mark the area that represents
* the value of {@link #todayProperty()}. By default this area will be
* filled with a different color (red) than the rest (white).
*
*
*
* @return true if today will be shown differently
*/
public final BooleanProperty showTodayProperty() {
return showToday;
}
/**
* Returns the value of {@link #showTodayProperty()}.
*
* @return true if today will be highlighted visually
*/
public final boolean isShowToday() {
return showTodayProperty().get();
}
/**
* Sets the value of {@link #showTodayProperty()}.
*
* @param show if true today will be highlighted visually
*/
public final void setShowToday(boolean show) {
showTodayProperty().set(show);
}
private final ObjectProperty date = new SimpleObjectProperty<>(this, "date", LocalDate.now()); //$NON-NLS-1$
/**
* The date that needs to be shown by the date control. This property is
* initialized with {@link LocalDate#now()}.
*
* @return the date shown by the control
*/
public final ObjectProperty dateProperty() {
return date;
}
/**
* Sets the value of {@link #dateProperty()}.
*
* @param date the date shown by the control
*/
public final void setDate(LocalDate date) {
requireNonNull(date);
dateProperty().set(date);
}
/**
* Returns the value of {@link #dateProperty()}.
*
* @return the date shown by the control
*/
public final LocalDate getDate() {
return dateProperty().get();
}
private final ObjectProperty zoneId = new SimpleObjectProperty<>(this, "zoneId", ZoneId.systemDefault()); //$NON-NLS-1$
/**
* The time zone used by the date control. Entries and date controls might
* use different time zones resulting in different layout of entry views.
*
* #see {@link Entry#zoneIdProperty()}
*
* @return the time zone used by the date control for calculating entry view
* layouts
*/
public final ObjectProperty zoneIdProperty() {
return zoneId;
}
/**
* Sets the value of {@link #zoneIdProperty()}.
*
* @param zoneId the time zone
*/
public final void setZoneId(ZoneId zoneId) {
requireNonNull(zoneId);
zoneIdProperty().set(zoneId);
}
/**
* Returns the value of {@link #zoneIdProperty()}.
*
* @return the time zone
*/
public final ZoneId getZoneId() {
return zoneIdProperty().get();
}
private final ObjectProperty time = new SimpleObjectProperty<>(this, "time", LocalTime.now()); //$NON-NLS-1$
/**
* Stores a time that can be visualized, e.g. the thin line in
* {@link DayView} representing the current time.
*
* @return the current time
*/
public final ObjectProperty timeProperty() {
return time;
}
/**
* Sets the value of {@link #timeProperty()}.
*
* @param time the current time
*/
public final void setTime(LocalTime time) {
requireNonNull(time);
timeProperty().set(time);
}
/**
* Returns the value of {@link #timeProperty()}.
*
* @return the current time
*/
public final LocalTime getTime() {
return timeProperty().get();
}
private final ObjectProperty startTime = new SimpleObjectProperty<>(this, "startTime", LocalTime.of(6, 0)); //$NON-NLS-1$
/**
* A start time used to limit the time interval shown by the control. The
* {@link DayView} uses this property and the {@link #endTimeProperty()} to
* support the concept of "early" and "late" hours. These hours can be
* hidden if required.
*
* @return the start time
*/
public final ObjectProperty startTimeProperty() {
return startTime;
}
/**
* Returns the value of {@link #startTimeProperty()}.
*
* @return the start time
*/
public final LocalTime getStartTime() {
return startTimeProperty().get();
}
/**
* Sets the value of {@link #startTimeProperty()}.
*
* @param time the start time
*/
public final void setStartTime(LocalTime time) {
startTimeProperty().set(time);
}
private final ObjectProperty endTime = new SimpleObjectProperty<>(this, "endTime", LocalTime.of(22, 0)); //$NON-NLS-1$
/**
* An end time used to limit the time interval shown by the control. The
* {@link DayView} uses this property and the {@link #startTimeProperty()}
* to support the concept of "early" and "late" hours. These hours can be
* hidden if required.
*
* @return the end time
*/
public final ObjectProperty endTimeProperty() {
return endTime;
}
/**
* Returns the value of {@link #endTimeProperty()}.
*
* @return the end time
*/
public final LocalTime getEndTime() {
return endTimeProperty().get();
}
/**
* Sets the value of {@link #endTimeProperty()}.
*
* @param time the end time
*/
public final void setEndTime(LocalTime time) {
endTimeProperty().set(time);
}
private final ObjectProperty weekFields = new SimpleObjectProperty<>(this, "weekFields", WeekFields.of(Locale.getDefault())); //$NON-NLS-1$
/**
* Week fields are used to determine the first day of a week (e.g. "Monday"
* in Germany or "Sunday" in the US). It is also used to calculate the week
* number as the week fields determine how many days are needed in the first
* week of a year. This property is initialized with {@link WeekFields#ISO}.
*
* @return the week fields
*/
public final ObjectProperty weekFieldsProperty() {
return weekFields;
}
/**
* Sets the value of {@link #weekFieldsProperty()}.
*
* @param weekFields the new week fields
*/
public final void setWeekFields(WeekFields weekFields) {
requireNonNull(weekFields);
weekFieldsProperty().set(weekFields);
}
/**
* Returns the value of {@link #weekFieldsProperty()}.
*
* @return the week fields
*/
public final WeekFields getWeekFields() {
return weekFieldsProperty().get();
}
/**
* A convenience method to lookup the first day of the week ("Monday" in
* Germany, "Sunday" in the US). This method delegates to
* {@link WeekFields#getFirstDayOfWeek()}.
*
* @return the first day of the week
* @see #weekFieldsProperty()
*/
public final DayOfWeek getFirstDayOfWeek() {
return getWeekFields().getFirstDayOfWeek();
}
private final ReadOnlyListWrapper calendars = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
/**
* A list that contains all calendars found in all calendar sources
* currently attached to this date control. This is a convenience list that
* "flattens" the two level structure of sources and their calendars. It is
* a read-only list because calendars can not be added directly to a date
* control. Instead they are added to calendar sources and those sources are
* then added to the control.
*
* @return the list of all calendars shown by this control
* @see #getCalendarSources()
*/
public final ReadOnlyListProperty calendarsProperty() {
return calendars.getReadOnlyProperty();
}
private final ObservableList unmodifiableCalendars = FXCollections.unmodifiableObservableList(calendars.get());
/**
* Returns the value of {@link #calendarsProperty()}.
*
* @return the list of all calendars shown by this control
*/
public final ObservableList getCalendars() {
return unmodifiableCalendars;
}
private final ObservableList calendarSources = FXCollections.observableArrayList();
/**
* The list of all calendar sources attached to this control. The calendars
* found in all sources are also added to the read-only list that can be
* retrieved by calling {@link #getCalendars()}.
*
* @return the calendar sources
* @see #calendarSourceFactoryProperty()
*/
public final ObservableList getCalendarSources() {
return calendarSources;
}
private final ObjectProperty selectionMode = new SimpleObjectProperty<>(this, "selectionMode", SelectionMode.MULTIPLE); //$NON-NLS-1$
/**
* Stores the selection mode. All date controls support single and multiple
* selections.
*
* @return the selection mode
* @see SelectionMode
*/
public final ObjectProperty selectionModeProperty() {
return selectionMode;
}
/**
* Sets the value of {@link #selectionModeProperty()}.
*
* @param mode the selection mode (single, multiple)
*/
public final void setSelectionMode(SelectionMode mode) {
requireNonNull(mode);
selectionModeProperty().set(mode);
}
/**
* Returns the value of {@link #selectionModeProperty()}.
*
* @return the selection mode (single, multiple)
*/
public final SelectionMode getSelectionMode() {
return selectionModeProperty().get();
}
private final ObservableSet> selections = FXCollections.observableSet();
/**
* Stores the currently selected entries.
*
* @return the set of currently selected entries
*/
public final ObservableSet> getSelections() {
return selections;
}
/**
* Adds the given entry to the set of currently selected entries.
*
* @param entry the selected entries
* @see #deselect(Entry)
* @see #getSelections()
*/
public final void select(Entry> entry) {
requireNonNull(entry);
selections.add(entry);
}
/**
* Removes the given entry from the set of currently selected entries.
*
* @param entry the selected entries
* @see #select(Entry)
* @see #getSelections()
*/
public final void deselect(Entry> entry) {
requireNonNull(entry);
selections.remove(entry);
}
/**
* Clears the current selection of entries.
*/
public final void clearSelection() {
getSelections().clear();
}
private final ObjectProperty virtualGrid = new SimpleObjectProperty<>(this, "virtualGrid", //$NON-NLS-1$
new VirtualGrid(Messages.getString("DateControl.DEFAULT_VIRTUAL_GRID_NAME"), Messages.getString("DateControl.DEFAULT_VIRTUAL_GRID_SHORT_NAME"), ChronoUnit.MINUTES, 15)); //$NON-NLS-1$ //$NON-NLS-2$
/**
* A virtual grid used for snapping to invisible grid lines while editing
* calendar entries. Using a virtual grid makes it easier to edit entries so
* that they all start at exactly the same time. The default grid is set to
* "5 Minutes". {@link VirtualGrid#OFF} can be used to completely disable
* the grid.
*
* @return the virtual grid
*/
public final ObjectProperty virtualGridProperty() {
return virtualGrid;
}
/**
* Returns the value of {@link #virtualGridProperty()}.
*
* @return the currently active grid
*/
public final VirtualGrid getVirtualGrid() {
return virtualGridProperty().get();
}
/**
* Sets the value of {@link #virtualGridProperty()}.
*
* @param grid the grid
*/
public final void setVirtualGrid(VirtualGrid grid) {
requireNonNull(grid);
virtualGridProperty().set(grid);
}
private final ObjectProperty requestedTime = new SimpleObjectProperty<>(this, "requestedTime", LocalTime.now()); //$NON-NLS-1$
/**
* Stores the time that the application wants to show in its date controls
* when the UI opens up. Most applications will normally set this time to
* {@link LocalTime#now()}.
*
* @return the requested time
*/
public final ObjectProperty requestedTimeProperty() {
return requestedTime;
}
/**
* Sets the value of {@link #requestedTimeProperty()}.
*
* @param time the requested time
*/
public final void setRequestedTime(LocalTime time) {
requestedTimeProperty().set(time);
}
/**
* Returns the value of {@link #requestedTimeProperty()}.
*
* @return the requested time
*/
public final LocalTime getRequestedTime() {
return requestedTimeProperty().get();
}
/**
* Supported layout strategies by the {@link DayView}.
*/
public enum Layout {
/**
* The standard layout lays out calendar entries in the most efficient
* way without distinguishing between different calendars. This is the
* layout found in most calendar software.
*/
STANDARD,
/**
* The swimlane layout creates virtual columns, one for each calendar.
* The entries of the calendars are shown in their own column. This
* layout strategy is often found in resource booking systems (e.g. one
* calendar per room / per person / per truck).
*/
SWIMLANE
}
private final ObjectProperty layout = new SimpleObjectProperty<>(this, "layout", Layout.STANDARD); //$NON-NLS-1$
/**
* Stores the strategy used by the view to layout the entries of several
* calendars at once. The standard layout ignores the source calendar of an
* entry and finds the next available place in the UI that satisfies the
* time bounds of the entry. The {@link Layout#SWIMLANE} strategy allocates
* a separate column for each calendar and resolves overlapping entry
* conflicts within that column. Swim lanes are especially useful for
* resource booking systems (rooms, people, trucks).
*
* @return the layout strategy of the view
*/
public final ObjectProperty layoutProperty() {
return layout;
}
/**
* Sets the value of {@link #layoutProperty()}.
*
* @param layout the layout
*/
public final void setLayout(Layout layout) {
requireNonNull(layout);
layoutProperty().set(layout);
}
/**
* Returns the value of {@link #layoutProperty()}.
*
* @return the layout strategy
*/
public final Layout getLayout() {
return layoutProperty().get();
}
private ObservableSet weekendDays = FXCollections.observableSet();
/**
* Returns the days of the week that are considered to be weekend days, for
* example Saturday and Sunday, or Friday and Saturday.
*
* @return the weekend days
*/
public ObservableSet getWeekendDays() {
return weekendDays;
}
/**
* Makes the control go forward in time by adding one or more days to the
* current date. Subclasses override this method to adjust it to their
* needs, e.g. the {@link DetailedWeekView} adds the number of days found in
* {@link DetailedWeekView#getNumberOfDays()}.
*
* @see #dateProperty()
*/
public void goForward() {
setDate(getDate().plusDays(1));
}
/**
* Makes the control go forward in time by removing one or more days from
* the current date. Subclasses override this method to adjust it to their
* needs, e.g. the {@link DetailedWeekView} removes the number of days found in
* {@link DetailedWeekView#getNumberOfDays()}.
*
* @see #dateProperty()
*/
public void goBack() {
setDate(getDate().minusDays(1));
}
/**
* Makes the control go to "today".
*
* @see #dateProperty()
* @see #todayProperty()
*/
public void goToday() {
setDate(getToday());
}
/**
* Finds the first view that represents the given entry.
*
* @param entry the entry
* @return the view
*/
public final EntryViewBase> findEntryView(Entry> entry) {
requireNonNull(entry);
return doFindEntryView(this, entry);
}
private EntryViewBase> doFindEntryView(Parent parent, Entry> entry) {
EntryViewBase> result = null;
for (Node node : parent.getChildrenUnmodifiable()) {
if (node instanceof EntryViewBase) {
EntryViewBase> base = (EntryViewBase>) node;
if (base.getEntry().equals(entry)) {
result = base;
break;
}
} else if (node instanceof Parent) {
result = doFindEntryView((Parent) node, entry);
if (result != null) {
break;
}
}
}
return result;
}
private List boundDateControls = FXCollections.observableArrayList();
/**
* Returns all data controls that are bound to this control.
*
* @return the bound date controls / sub controls / children controls
*/
public final List getBoundDateControls() {
return boundDateControls;
}
// hyperlink support
private final BooleanProperty enableHyperlinks = new SimpleBooleanProperty(this, "enableHyperlinks", true);
/**
* A property used to control whether the control allows the user to click on it or an element
* inside of it in order to "jump" to another screen with more detail. Example: in the {@link CalendarView}
* the user can click on the "day of month" label of a cell inside the {@link MonthSheetView} in
* order to switch to the {@link DayPage} where the user will see all entries scheduled for that day.
*
* @return true if the support for hyperlinks is enabled
*/
public final BooleanProperty enableHyperlinksProperty() {
return enableHyperlinks;
}
/**
* Sets the value of the {@link #enableHyperlinksProperty()}.
*
* @param enable if true the hyperlink support will be enabled
*/
public final void setEnableHyperlinks(boolean enable) {
this.enableHyperlinks.set(enable);
}
/**
* Returns the value of the {@link #enableHyperlinksProperty()}.
*
* @return true if the hyperlink support is enabled
*/
public final boolean isEnableHyperlinks() {
return enableHyperlinks.get();
}
/**
* Binds several properties of the given date control to the same properties
* of this control. This kind of binding is needed to create UIs with nested
* date controls. The {@link CalendarView} for example consists of several
* pages. Each page is a date control that consists of several other date
* controls. The {@link DayPage} consists of the {@link AgendaView}, a
* single {@link DayView}, and a {@link YearMonthView} . All of these
* controls are bound to each other so that the application can simply
* change properties on the {@link CalendarView} without worrying about the
* nested controls.
*
* @param otherControl the control that will be bound to this control
* @param bindDate determines if the date property will also be bound
*/
public final void bind(DateControl otherControl, boolean bindDate) {
requireNonNull(otherControl);
boundDateControls.add(otherControl);
// bind maps
Bindings.bindContentBidirectional(otherControl.getCalendarVisibilityMap(), getCalendarVisibilityMap());
// bind lists
Bindings.bindContentBidirectional(otherControl.getCalendarSources(), getCalendarSources());
Bindings.bindContentBidirectional(otherControl.getSelections(), getSelections());
Bindings.bindContentBidirectional(otherControl.getWeekendDays(), getWeekendDays());
// bind properties
Bindings.bindBidirectional(otherControl.suspendUpdatesProperty(), suspendUpdatesProperty());
Bindings.bindBidirectional(otherControl.entryFactoryProperty(), entryFactoryProperty());
Bindings.bindBidirectional(otherControl.defaultCalendarProviderProperty(), defaultCalendarProviderProperty());
Bindings.bindBidirectional(otherControl.virtualGridProperty(), virtualGridProperty());
Bindings.bindBidirectional(otherControl.draggedEntryProperty(), draggedEntryProperty());
Bindings.bindBidirectional(otherControl.requestedTimeProperty(), requestedTimeProperty());
Bindings.bindBidirectional(otherControl.selectionModeProperty(), selectionModeProperty());
Bindings.bindBidirectional(otherControl.selectionModeProperty(), selectionModeProperty());
Bindings.bindBidirectional(otherControl.weekFieldsProperty(), weekFieldsProperty());
Bindings.bindBidirectional(otherControl.layoutProperty(), layoutProperty());
Bindings.bindBidirectional(otherControl.startTimeProperty(), startTimeProperty());
Bindings.bindBidirectional(otherControl.endTimeProperty(), endTimeProperty());
Bindings.bindBidirectional(otherControl.timeProperty(), timeProperty());
Bindings.bindBidirectional(otherControl.usagePolicyProperty(), usagePolicyProperty());
if (bindDate) {
Bindings.bindBidirectional(otherControl.dateProperty(), dateProperty());
}
Bindings.bindBidirectional(otherControl.todayProperty(), todayProperty());
Bindings.bindBidirectional(otherControl.zoneIdProperty(), zoneIdProperty());
// edit callbacks
Bindings.bindBidirectional(otherControl.entryDetailsCallbackProperty(), entryDetailsCallbackProperty());
Bindings.bindBidirectional(otherControl.dateDetailsCallbackProperty(), dateDetailsCallbackProperty());
Bindings.bindBidirectional(otherControl.contextMenuCallbackProperty(), contextMenuCallbackProperty());
Bindings.bindBidirectional(otherControl.entryContextMenuCallbackProperty(), entryContextMenuCallbackProperty());
Bindings.bindBidirectional(otherControl.calendarSourceFactoryProperty(), calendarSourceFactoryProperty());
Bindings.bindBidirectional(otherControl.entryDetailsPopOverContentCallbackProperty(), entryDetailsPopOverContentCallbackProperty());
Bindings.bindBidirectional(otherControl.entryEditPolicyProperty(), entryEditPolicyProperty());
}
/**
* Unbinds the given control from this control. Unbinding is done for all
* properties and observable lists that have previously been bound by the
* {@link #bind(DateControl, boolean)} method.
*
* @param otherControl the control to unbind
*/
public final void unbind(DateControl otherControl) {
requireNonNull(otherControl);
boundDateControls.remove(otherControl);
// unbind maps
Bindings.unbindContentBidirectional(otherControl.getCalendarVisibilityMap(), getCalendarVisibilityMap());
// unbind lists
Bindings.unbindContentBidirectional(otherControl.getCalendarSources(), getCalendarSources());
Bindings.unbindContentBidirectional(otherControl.getSelections(), getSelections());
// unbind properties
Bindings.unbindBidirectional(otherControl.suspendUpdatesProperty(), suspendUpdatesProperty());
Bindings.unbindBidirectional(otherControl.entryFactoryProperty(), entryFactoryProperty());
Bindings.unbindBidirectional(otherControl.defaultCalendarProviderProperty(), defaultCalendarProviderProperty());
Bindings.unbindBidirectional(otherControl.virtualGridProperty(), virtualGridProperty());
Bindings.unbindBidirectional(otherControl.draggedEntryProperty(), draggedEntryProperty());
Bindings.unbindBidirectional(otherControl.requestedTimeProperty(), requestedTimeProperty());
Bindings.unbindBidirectional(otherControl.selectionModeProperty(), selectionModeProperty());
Bindings.unbindBidirectional(otherControl.selectionModeProperty(), selectionModeProperty());
Bindings.unbindBidirectional(otherControl.weekFieldsProperty(), weekFieldsProperty());
Bindings.unbindBidirectional(otherControl.dateProperty(), dateProperty());
Bindings.unbindBidirectional(otherControl.todayProperty(), todayProperty());
Bindings.unbindBidirectional(otherControl.zoneIdProperty(), zoneIdProperty());
Bindings.unbindBidirectional(otherControl.layoutProperty(), layoutProperty());
Bindings.unbindBidirectional(otherControl.startTimeProperty(), startTimeProperty());
Bindings.unbindBidirectional(otherControl.endTimeProperty(), endTimeProperty());
Bindings.unbindBidirectional(otherControl.timeProperty(), timeProperty());
Bindings.unbindBidirectional(otherControl.usagePolicyProperty(), usagePolicyProperty());
// unbind callbacks
Bindings.unbindBidirectional(otherControl.entryDetailsCallbackProperty(), entryDetailsCallbackProperty());
Bindings.unbindBidirectional(otherControl.dateDetailsCallbackProperty(), dateDetailsCallbackProperty());
Bindings.unbindBidirectional(otherControl.contextMenuCallbackProperty(), contextMenuCallbackProperty());
Bindings.unbindBidirectional(otherControl.entryContextMenuCallbackProperty(), entryContextMenuCallbackProperty());
Bindings.unbindBidirectional(otherControl.calendarSourceFactoryProperty(), calendarSourceFactoryProperty());
Bindings.unbindBidirectional(otherControl.entryEditPolicyProperty(), entryEditPolicyProperty());
}
private final BooleanProperty suspendUpdates = new SimpleBooleanProperty(this, "suspendUpdates", false);
/**
* A property that will suspend all updates to the view based on model changes. This feature
* comes in handy when performing large batch updates with many adds and / or removes of calendar
* entries. When this property is set to true the view will not add / remove / update any entry
* views. Once it is set back to false a single refresh will be executed.
*
* @return true if updates are suspended
*/
public final BooleanProperty suspendUpdatesProperty() {
return suspendUpdates;
}
/**
* Returns the value of {@link #suspendUpdatesProperty()}.
*
* @return true if updates are suspended
*/
public final boolean isSuspendUpdates() {
return suspendUpdates.get();
}
/**
* Sets the value of {@link #suspendUpdatesProperty()}.
*
* @param suspend if true updates are suspended
*/
public final void setSuspendUpdates(boolean suspend) {
this.suspendUpdates.set(suspend);
}
// usage policy support
public enum Usage {
NONE,
VERY_LOW,
LOW,
MEDIUM,
HIGH,
VERY_HIGH
}
public final ObjectProperty> usagePolicy = new SimpleObjectProperty<>(this, "usagePolicy");
/**
* A property used to store a policy that will be used to determine if a given number of entries
* indicates a low or high usage of that day. This policy is used by the {@link YearMonthView} to color
* the background of each day based on the "usage" of that day. The default implementation of this policy
* returns "none" for 0 entries, "very low" for 1 entry, "low" for 2 entries, "medium" for 3 entries,
* "high" for 4 entries, and "very high" for 5 entries or more.
*
* @return the usage policy
*/
public final ObjectProperty> usagePolicyProperty() {
return usagePolicy;
}
/**
* Sets the value of {@link #usagePolicyProperty()}.
*
* @param policy the new usage policy
*/
public final void setUsagePolicy(Callback policy) {
Objects.requireNonNull(policy);
this.usagePolicy.set(policy);
}
/**
* Returns the value of {@link #usagePolicyProperty()}.
*
* @return the new usage policy
*/
public final Callback getUsagePolicy() {
return usagePolicy.get();
}
private static final String DATE_CONTROL_CATEGORY = "Date Control"; //$NON-NLS-1$
@Override
public ObservableList- getPropertySheetItems() {
ObservableList
- items = super.getPropertySheetItems();
items.add(new Item() {
@Override
public Optional
> getObservableValue() {
return Optional.of(dateProperty());
}
@Override
public void setValue(Object value) {
setDate((LocalDate) value);
}
@Override
public Object getValue() {
return getDate();
}
@Override
public Class> getType() {
return LocalDate.class;
}
@Override
public String getName() {
return "Date"; //$NON-NLS-1$
}
@Override
public String getDescription() {
return "Date"; //$NON-NLS-1$
}
@Override
public String getCategory() {
return DATE_CONTROL_CATEGORY;
}
});
items.add(new Item() {
@Override
public Optional> getObservableValue() {
return Optional.of(layoutProperty());
}
@Override
public void setValue(Object value) {
setLayout((Layout) value);
}
@Override
public Object getValue() {
return getLayout();
}
@Override
public Class> getType() {
return Layout.class;
}
@Override
public String getName() {
return "Layout"; //$NON-NLS-1$
}
@Override
public String getDescription() {
return "Layout"; //$NON-NLS-1$
}
@Override
public String getCategory() {
return DATE_CONTROL_CATEGORY;
}
});
items.add(new Item() {
@Override
public Optional> getObservableValue() {
return Optional.of(selectionModeProperty());
}
@Override
public void setValue(Object value) {
setSelectionMode((SelectionMode) value);
}
@Override
public Object getValue() {
return getSelectionMode();
}
@Override
public Class> getType() {
return SelectionMode.class;
}
@Override
public String getName() {
return "Selection Mode"; //$NON-NLS-1$
}
@Override
public String getDescription() {
return "Selection Mode"; //$NON-NLS-1$
}
@Override
public String getCategory() {
return DATE_CONTROL_CATEGORY;
}
});
items.add(new Item() {
@Override
public Optional> getObservableValue() {
return Optional.of(todayProperty());
}
@Override
public void setValue(Object value) {
setToday((LocalDate) value);
}
@Override
public Object getValue() {
return getToday();
}
@Override
public Class> getType() {
return LocalDate.class;
}
@Override
public String getName() {
return "Today"; //$NON-NLS-1$
}
@Override
public String getDescription() {
return "Today"; //$NON-NLS-1$
}
@Override
public String getCategory() {
return DATE_CONTROL_CATEGORY;
}
});
items.add(new Item() {
@Override
public Optional> getObservableValue() {
return Optional.of(showTodayProperty());
}
@Override
public void setValue(Object value) {
setShowToday((boolean) value);
}
@Override
public Object getValue() {
return isShowToday();
}
@Override
public Class> getType() {
return Boolean.class;
}
@Override
public String getName() {
return "Show Today"; //$NON-NLS-1$
}
@Override
public String getDescription() {
return "Highlight today"; //$NON-NLS-1$
}
@Override
public String getCategory() {
return DATE_CONTROL_CATEGORY;
}
});
items.add(new Item() {
@Override
public Optional> getObservableValue() {
return Optional.of(zoneIdProperty());
}
@Override
public void setValue(Object value) {
setZoneId((ZoneId) value);
}
@Override
public Object getValue() {
return getZoneId();
}
@Override
public Class> getType() {
return ZoneId.class;
}
@Override
public String getName() {
return "Timezone"; //$NON-NLS-1$
}
@Override
public String getDescription() {
return "Timezone"; //$NON-NLS-1$
}
@Override
public String getCategory() {
return DATE_CONTROL_CATEGORY;
}
});
items.add(new Item() {
@Override
public Optional> getObservableValue() {
return Optional.of(weekFieldsProperty());
}
@Override
public void setValue(Object value) {
setWeekFields((WeekFields) value);
}
@Override
public Object getValue() {
return getWeekFields();
}
@Override
public Class> getType() {
return WeekFields.class;
}
@Override
public String getName() {
return "Week Fields"; //$NON-NLS-1$
}
@Override
public String getDescription() {
return "Week Fields (calendar standard)"; //$NON-NLS-1$
}
@Override
public String getCategory() {
return DATE_CONTROL_CATEGORY;
}
});
items.add(new Item() {
@Override
public Optional> getObservableValue() {
return Optional.of(timeProperty());
}
@Override
public void setValue(Object value) {
setTime((LocalTime) value);
}
@Override
public Object getValue() {
return getTime();
}
@Override
public Class> getType() {
return LocalTime.class;
}
@Override
public String getName() {
return "Time"; //$NON-NLS-1$
}
@Override
public String getDescription() {
return "Time"; //$NON-NLS-1$
}
@Override
public String getCategory() {
return DATE_CONTROL_CATEGORY;
}
});
items.add(new Item() {
@Override
public Optional> getObservableValue() {
return Optional.of(startTimeProperty());
}
@Override
public void setValue(Object value) {
setStartTime((LocalTime) value);
}
@Override
public Object getValue() {
return getStartTime();
}
@Override
public Class> getType() {
return LocalTime.class;
}
@Override
public String getName() {
return "Start Time"; //$NON-NLS-1$
}
@Override
public String getDescription() {
return "The first visible time at the top."; //$NON-NLS-1$
}
@Override
public String getCategory() {
return DATE_CONTROL_CATEGORY;
}
});
items.add(new Item() {
@Override
public Optional> getObservableValue() {
return Optional.of(endTimeProperty());
}
@Override
public void setValue(Object value) {
setEndTime((LocalTime) value);
}
@Override
public Object getValue() {
return getEndTime();
}
@Override
public Class> getType() {
return LocalTime.class;
}
@Override
public String getName() {
return "End Time"; //$NON-NLS-1$
}
@Override
public String getDescription() {
return "The last visible time at the bottom."; //$NON-NLS-1$
}
@Override
public String getCategory() {
return DATE_CONTROL_CATEGORY;
}
});
items.add(new Item() {
@Override
public Optional> getObservableValue() {
return Optional.of(enableHyperlinksProperty());
}
@Override
public void setValue(Object value) {
setEnableHyperlinks((boolean) value);
}
@Override
public Object getValue() {
return isEnableHyperlinks();
}
@Override
public Class> getType() {
return Boolean.class;
}
@Override
public String getName() {
return "Hyperlinks"; //$NON-NLS-1$
}
@Override
public String getDescription() {
return "Hyperlinks enabled / disabled"; //$NON-NLS-1$
}
@Override
public String getCategory() {
return DATE_CONTROL_CATEGORY;
}
});
return items;
}
}