Skip to content

Commit

Permalink
Fixes for recurrence support in the AllDayView. Fixes regarding time …
Browse files Browse the repository at this point in the history
…zone support, fixes in the entry popover that were needed for JavaFX 19.
  • Loading branch information
dlemmermann committed Oct 11, 2022
1 parent 1ee0953 commit 6db2c30
Show file tree
Hide file tree
Showing 16 changed files with 319 additions and 212 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
import com.calendarfx.model.Calendar;
import com.calendarfx.model.Calendar.Style;
import com.calendarfx.model.Entry;
import com.calendarfx.model.Resource;
import com.calendarfx.view.DateControl;
import com.calendarfx.view.DateControl.Layout;
import com.calendarfx.view.DayViewBase.AvailabilityEditingEntryBehaviour;
import com.calendarfx.view.DayViewBase.EarlyLateHoursStrategy;
import com.calendarfx.view.DayViewBase.GridType;
import com.calendarfx.model.Resource;
import com.calendarfx.view.ResourcesView;
import com.calendarfx.view.ResourcesView.Type;
import javafx.collections.FXCollections;
Expand Down Expand Up @@ -100,7 +101,7 @@ public Node getControlPanel() {
ChoiceBox<Type> typeBox = new ChoiceBox<>();
typeBox.getItems().setAll(Type.values());
typeBox.valueProperty().bindBidirectional(resourcesView.typeProperty());
typeBox.setConverter(new StringConverter<Type>() {
typeBox.setConverter(new StringConverter<>() {
@Override
public String toString(Type object) {
if (object != null) {
Expand All @@ -121,6 +122,30 @@ public Type fromString(String string) {
}
});

ChoiceBox<Layout> layoutBox = new ChoiceBox<>();
layoutBox.getItems().setAll(Layout.values());
layoutBox.valueProperty().bindBidirectional(resourcesView.layoutProperty());
layoutBox.setConverter(new StringConverter<>() {
@Override
public String toString(Layout object) {
if (object != null) {
if (object.equals(Layout.SWIMLANE)) {
return "Swim Lanes";
} else if (object.equals(Layout.STANDARD)) {
return "Standard";
} else {
return "unknown layout type: " + object.name();
}
}
return "";
}

@Override
public Layout fromString(String string) {
return null;
}
});

ChoiceBox<GridType> gridTypeBox = new ChoiceBox<>();
gridTypeBox.getItems().setAll(GridType.values());
gridTypeBox.valueProperty().bindBidirectional(resourcesView.gridTypeProperty());
Expand Down Expand Up @@ -151,7 +176,7 @@ public Type fromString(String string) {
slider.setMax(1);
slider.valueProperty().bindBidirectional(resourcesView.entryViewAvailabilityEditingOpacityProperty());

return new VBox(10, availabilityButton, new Label("View type"), typeBox, datePicker, infiniteScrolling, adjustBox, new Label("Number of resources"), numberOfResourcesBox, new Label("Number of days"), daysBox, new Label("Clicks to create"), clicksBox,
return new VBox(10, availabilityButton, new Label("View type"), typeBox, layoutBox, datePicker, infiniteScrolling, adjustBox, new Label("Number of resources"), numberOfResourcesBox, new Label("Number of days"), daysBox, new Label("Clicks to create"), clicksBox,
new Label("Availability Behaviour"), behaviourBox, new Label("Availability Opacity"), slider, new Label("Grid Type"), gridTypeBox, scrollbarBox, timescaleBox, allDayBox, detailsBox, flipBox);
}

Expand Down Expand Up @@ -196,7 +221,9 @@ private List<Resource<String>> createResources(int count) {
private Resource<String> create(String name, Style style) {
Resource<String> resource = new Resource(name);
resource.getAvailabilityCalendar().setName("Availability of " + name);
resource.getCalendar().setStyle(style);
resource.getCalendars().get(0).setStyle(style);
resource.getCalendars().get(0).setUserObject(resource);
resource.getCalendarSources().get(0).getCalendars().add(new Calendar("Second", resource));
fillAvailabilities(resource.getAvailabilityCalendar());
return resource;
}
Expand Down
27 changes: 6 additions & 21 deletions CalendarFXView/src/main/java/com/calendarfx/model/Calendar.java
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ private Interval calculateSourceBoundsFromRecurrenceBounds(Entry<?> source, Entr
sourceStart = sourceStart.plus(startDelta);
sourceEnd = sourceEnd.plus(endDelta);

return new Interval(sourceStart.toLocalDate(), sourceStart.toLocalTime(), sourceEnd.toLocalDate(), sourceEnd.toLocalTime(), source.getZoneId());
return new Interval(sourceStart.toLocalDate(), sourceStart.toLocalTime(), sourceEnd.toLocalDate(), sourceEnd.toLocalTime(), recurrence.getZoneId());
}

/**
Expand Down Expand Up @@ -297,8 +297,7 @@ public final Map<LocalDate, List<Entry<?>>> findEntries(LocalDate startDate, Loc
@SuppressWarnings({"rawtypes", "unchecked"})
private Map<LocalDate, List<Entry<?>>> doGetEntries(LocalDate startDate, LocalDate endDate, ZoneId zoneId) {
if (MODEL.isLoggable(FINE)) {
MODEL.fine(getName() + ": getting entries from " + startDate
+ " until " + endDate + ", zone = " + zoneId);
MODEL.fine(getName() + ": getting entries from " + startDate + " until " + endDate + ", zone = " + zoneId);
}

ZonedDateTime st = ZonedDateTime.of(startDate, LocalTime.MIN, zoneId);
Expand All @@ -314,8 +313,7 @@ private Map<LocalDate, List<Entry<?>>> doGetEntries(LocalDate startDate, LocalDa
}

if (MODEL.isLoggable(FINE)) {
MODEL.fine(getName() + ": found " + intersectingEntries.size()
+ " entries");
MODEL.fine(getName() + ": found " + intersectingEntries.size() + " entries");
}

Map<LocalDate, List<Entry<?>>> result = new HashMap<>();
Expand All @@ -329,17 +327,6 @@ private Map<LocalDate, List<Entry<?>>> doGetEntries(LocalDate startDate, LocalDa
try {
LocalDate utilEndDate = et.toLocalDate();

/*
* TODO: for performance reasons we should definitely
* use the advanceTo() call, but unfortunately this
* collides with the fact that e.g. the DetailedWeekView loads
* data day by day. So a given day would not show
* entries that start on the day before but intersect
* with the given day. We have to find a solution for
* this.
*/
// iterator.advanceTo(st.toLocalDate());

List<LocalDate> dateList = new Recur(recurrenceRule).getDates(utilStartDate, utilEndDate);

for (LocalDate repeatingDate : dateList) {
Expand All @@ -351,12 +338,11 @@ private Map<LocalDate, List<Entry<?>>> doGetEntries(LocalDate startDate, LocalDa
recurrence.getProperties().put("com.calendarfx.recurrence.id", zonedDateTime.toString());
recurrence.setRecurrenceRule(entry.getRecurrenceRule());

// update the recurrence interval
LocalDate recurrenceStartDate = zonedDateTime.toLocalDate();
LocalDate recurrenceEndDate = recurrenceStartDate.plus(entry.getStartDate().until(entry.getEndDate()));
recurrence.setInterval(entry.getInterval().withDates(recurrenceStartDate, recurrenceEndDate));

Interval recurrenceInterval = entry.getInterval().withDates(recurrenceStartDate, recurrenceEndDate);

recurrence.setInterval(recurrenceInterval);
recurrence.setUserObject(entry.getUserObject());
recurrence.setTitle(entry.getTitle());
recurrence.setMinimumDuration(entry.getMinimumDuration());
Expand Down Expand Up @@ -913,7 +899,6 @@ public final void setUserObject(T userObject) {

@Override
public String toString() {
return "Calendar [name=" + getName() + ", style=" + getStyle()
+ ", readOnly=" + isReadOnly() + "]";
return "Calendar [name=" + getName() + ", style=" + getStyle() + ", readOnly=" + isReadOnly() + ", " + (getUserObject() != null ? getUserObject().toString() : "null") + "]";
}
}
87 changes: 77 additions & 10 deletions CalendarFXView/src/main/java/com/calendarfx/model/Resource.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
package com.calendarfx.model;

import com.calendarfx.view.DateControl;
import com.calendarfx.view.Messages;
import com.calendarfx.view.ResourcesView;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyListProperty;
import javafx.beans.property.ReadOnlyListWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import java.util.ArrayList;
import java.util.List;

/**
* A resource represents a person or a machine. Resources can be edited via
Expand All @@ -20,10 +31,27 @@
*/
public class Resource<T> {

private final InvalidationListener updateCalendarListListener = (Observable it) -> updateCalendarList();

private final WeakInvalidationListener weakUpdateCalendarListListener = new WeakInvalidationListener(updateCalendarListListener);

public Resource() {
getCalendarSources().addListener(weakUpdateCalendarListListener);

/*
* Every resource is initially populated with a default source and calendar.
* We borrow the i18n strings from DateControl.
*/
Calendar defaultCalendar = new Calendar(Messages.getString("DateControl.DEFAULT_CALENDAR_NAME"));
defaultCalendar.setUserObject(this);

CalendarSource defaultCalendarSource = new CalendarSource(Messages.getString("DateControl.DEFAULT_CALENDAR_SOURCE_NAME"));
defaultCalendarSource.getCalendars().add(defaultCalendar);
getCalendarSources().add(defaultCalendarSource);
}

public Resource(T userObject) {
this();
setUserObject(userObject);
}

Expand All @@ -46,24 +74,44 @@ public final void setUserObject(T userObject) {
this.userObject.set(userObject);
}

public final ObjectProperty<Calendar> calendar = new SimpleObjectProperty<>(this, "calendar", new Calendar());
private final ObservableList<CalendarSource> calendarSources = FXCollections.observableArrayList();

public final Calendar getCalendar() {
return calendar.get();
/**
* The list of all calendar sources attached to this resource.
*
* @return the calendar sources
* @see DateControl#getCalendarSources()
*/
public final ObservableList<CalendarSource> getCalendarSources() {
return calendarSources;
}

private final ReadOnlyListWrapper<Calendar> calendars = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());

/**
* A resource can be "booked" or "allocated to tasks". Those bookings / allocations are stored
* in this calendar.
* A list that contains all calendars found in all calendar sources
* currently attached to this resource. 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 resource.
* Instead, they are added to calendar sources and those sources are
* then added to the control.
*
* @return the resource calendar with the resource's bookings
* @return the list of all calendars available for this resource
* @see #getCalendarSources()
*/
public final ObjectProperty<Calendar> calendarProperty() {
return calendar;
public final ReadOnlyListProperty<Calendar> calendarsProperty() {
return calendars.getReadOnlyProperty();
}

public final void setCalendar(Calendar calendar) {
this.calendar.set(calendar);
private final ObservableList<Calendar> unmodifiableCalendars = FXCollections.unmodifiableObservableList(calendars.get());

/**
* Returns the value of {@link #calendarsProperty()}.
*
* @return the list of all calendars available for this control
*/
public final ObservableList<Calendar> getCalendars() {
return unmodifiableCalendars;
}

public final ObjectProperty<Calendar> availabilityCalendar = new SimpleObjectProperty<>(this, "availabilityCalendar", new Calendar());
Expand All @@ -90,6 +138,25 @@ public void setAvailabilityCalendar(Calendar availabilityCalendar) {
this.availabilityCalendar.set(availabilityCalendar);
}

private void updateCalendarList() {
List<Calendar> removedCalendars = new ArrayList<>(calendars);
List<Calendar> 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);
}

@Override
public String toString() {
if (getUserObject() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,8 @@ public final Entry<?> createEntryAt(ZonedDateTime time, Calendar calendar, boole
Callback<CreateEntryParameter, Entry<?>> factory = getEntryFactory();
Entry<?> entry = factory.call(param);

System.out.println(calendar);

/*
* This is OK. The factory can return NULL. In this case we
* assume that the application does not allow to create an entry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ public ResourcesView() {
maybeAdjustToFirstDayOfWeek();
}

@Override
protected Skin<?> createDefaultSkin() {
return new ResourcesViewSkin(this);
}

private void maybeAdjustToFirstDayOfWeek() {
if (isAdjustToFirstDayOfWeek()) {
setDate(getDate().with(TemporalAdjusters.previousOrSame(getFirstDayOfWeek())));
Expand Down Expand Up @@ -137,11 +142,6 @@ private void maybeRunAndConsume(RequestEvent evt, Consumer<RequestEvent> consume
}
}

@Override
protected Skin<?> createDefaultSkin() {
return new ResourcesViewSkin(this);
}

private final BooleanProperty adjustToFirstDayOfWeek = new SimpleBooleanProperty(this, "adjustToFirstDayOfWeek", true);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,34 +195,38 @@ public EntryDetailsView(Entry<?> entry, DateControl dateControl) {
endTimeField.visibleProperty().bind(Bindings.not(entry.fullDayProperty()));

// start date and time
startDatePicker.valueProperty().addListener(evt -> {
startDatePicker.valueProperty().addListener((obs, oldValue, newValue) -> {
if (!updatingFields) {
entry.changeStartDate(startDatePicker.getValue(), true);
// Work-Around for DatePicker bug introduced with 18+9 ("commit on focus lost").
startDatePicker.getEditor().setText(startDatePicker.getConverter().toString(newValue));
entry.changeStartDate(newValue, true);
}
});

startTimeField.valueProperty().addListener(evt -> {
startTimeField.valueProperty().addListener((obs, oldValue, newValue) -> {
if (!updatingFields) {
entry.changeStartTime(startTimeField.getValue(), true);
entry.changeStartTime(newValue, true);
}
});

// end date and time
endDatePicker.valueProperty().addListener(evt -> {
endDatePicker.valueProperty().addListener((obs, oldValue, newValue) -> {
if (!updatingFields) {
entry.changeEndDate(endDatePicker.getValue(), false);
// Work-Around for DatePicker bug introduced with 18+9 ("commit on focus lost").
endDatePicker.getEditor().setText(endDatePicker.getConverter().toString(newValue));
entry.changeEndDate(newValue, false);
}
});

endTimeField.valueProperty().addListener(evt -> {
endTimeField.valueProperty().addListener((obs, oldValue, newValue) -> {
if (!updatingFields) {
entry.changeEndTime(endTimeField.getValue(), false);
entry.changeEndTime(newValue, false);
}
});

zoneBox.valueProperty().addListener(evt -> {
zoneBox.valueProperty().addListener((obs, oldValue, newValue) -> {
if (!updatingFields && zoneBox.getValue() != null) {
entry.changeZoneId(zoneBox.getValue());
entry.changeZoneId(newValue);
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ public LocalDate getLoadEndDate() {

@Override
public ZoneId getZoneId() {
return ZoneId.systemDefault();
return getSkinnable().getZoneId();
}

@Override
Expand Down
Loading

0 comments on commit 6db2c30

Please sign in to comment.