Skip to content

Commit

Permalink
Merge pull request #192 from dlsc-software-consulting-gmbh/resources-…
Browse files Browse the repository at this point in the history
…infinite-scrolling

Resources infinite scrolling
  • Loading branch information
dlemmermann authored Oct 11, 2022
2 parents 9172778 + ac31aa8 commit b451cb7
Show file tree
Hide file tree
Showing 26 changed files with 850 additions and 598 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,10 +122,38 @@ 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());

CheckBox infiniteScrolling = new CheckBox("Infinite scrolling");
infiniteScrolling.selectedProperty().bindBidirectional(resourcesView.scrollingEnabledProperty());
infiniteScrolling.setDisable(true);

CheckBox adjustBox = new CheckBox("Adjust first day of week");
adjustBox.selectedProperty().bindBidirectional(resourcesView.adjustToFirstDayOfWeekProperty());

Expand All @@ -148,13 +177,14 @@ public Type fromString(String string) {
slider.setMax(1);
slider.valueProperty().bindBidirectional(resourcesView.entryViewAvailabilityEditingOpacityProperty());

return new VBox(10, availabilityButton, new Label("View type"), typeBox, datePicker, 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);
}

@Override
protected DateControl createControl() {
resourcesView = new ResourcesView();
resourcesView.setScrollingEnabled(false);
resourcesView.setType(Type.DATES_OVER_RESOURCES);
resourcesView.setNumberOfDays(5);
resourcesView.setCreateEntryClickCount(1);
Expand Down Expand Up @@ -192,7 +222,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
3 changes: 1 addition & 2 deletions CalendarFXView/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,8 @@
</executions>

<configuration>
<sourceHighlighter>coderay</sourceHighlighter>
<backend>html</backend>
<imagesDir>manual-images</imagesDir>
<outputFile>index.html</outputFile>
</configuration>
</plugin>

Expand Down
29 changes: 7 additions & 22 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 @@ -870,7 +856,7 @@ public final void removeEventHandler(EventHandler<CalendarEvent> l) {
public final void fireEvent(CalendarEvent evt) {
if (fireEvents && !batchUpdates) {
if (MODEL.isLoggable(FINER)) {
MODEL.finer(getName() + ": fireing event: " + evt);
MODEL.finer(getName() + ": firing event: " + evt);
}

requireNonNull(evt);
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
Loading

0 comments on commit b451cb7

Please sign in to comment.