From 1ee09532fca9cdd1017a2339de7be9775dabb905 Mon Sep 17 00:00:00 2001 From: Dirk Lemmermann Date: Thu, 6 Oct 2022 18:05:51 +0200 Subject: [PATCH 1/7] Implemented infinite scrolling for ResourcesView. --- .../views/resources/HelloResourcesView.java | 6 +- .../com/calendarfx/view/DayViewBaseSkin.java | 2 + .../impl/com/calendarfx/view/DayViewSkin.java | 171 ++++++++++-------- .../view/ResourcesViewContainerSkin.java | 1 + .../calendarfx/view/ResourcesViewSkin.java | 144 +++++++++++---- .../calendarfx/view/TimeScaleViewSkin.java | 8 +- 6 files changed, 221 insertions(+), 111 deletions(-) diff --git a/CalendarFXSampler/src/main/java/com/calendarfx/demo/views/resources/HelloResourcesView.java b/CalendarFXSampler/src/main/java/com/calendarfx/demo/views/resources/HelloResourcesView.java index bdf6f034..f1601be3 100644 --- a/CalendarFXSampler/src/main/java/com/calendarfx/demo/views/resources/HelloResourcesView.java +++ b/CalendarFXSampler/src/main/java/com/calendarfx/demo/views/resources/HelloResourcesView.java @@ -125,6 +125,9 @@ public Type fromString(String string) { gridTypeBox.getItems().setAll(GridType.values()); gridTypeBox.valueProperty().bindBidirectional(resourcesView.gridTypeProperty()); + CheckBox infiniteScrolling = new CheckBox("Infinite scrolling"); + infiniteScrolling.selectedProperty().bindBidirectional(resourcesView.scrollingEnabledProperty()); + CheckBox adjustBox = new CheckBox("Adjust first day of week"); adjustBox.selectedProperty().bindBidirectional(resourcesView.adjustToFirstDayOfWeekProperty()); @@ -148,13 +151,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, 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(true); resourcesView.setType(Type.DATES_OVER_RESOURCES); resourcesView.setNumberOfDays(5); resourcesView.setCreateEntryClickCount(1); diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewBaseSkin.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewBaseSkin.java index c449824f..6de8b322 100644 --- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewBaseSkin.java +++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewBaseSkin.java @@ -47,6 +47,8 @@ public DayViewBaseSkin(T view) { registerLayoutListener(view.showTodayProperty()); registerLayoutListener(view.zoneIdProperty()); registerLayoutListener(view.editAvailabilityProperty()); + registerLayoutListener(view.scrollingEnabledProperty()); + registerLayoutListener(view.gridTypeProperty()); view.setOnScroll(evt -> { final double oldLocation = evt.getY(); diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewSkin.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewSkin.java index c298bcdc..de5debf4 100644 --- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewSkin.java +++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewSkin.java @@ -158,6 +158,7 @@ public DayViewSkin(T view) { currentTimeLine.visibleProperty().bind(view.enableCurrentTimeMarkerProperty().and(view.editAvailabilityProperty().not())); getChildren().add(currentTimeLine); + view.scrollTimeProperty().addListener(drawBackgroundCanvasListener); view.lassoStartProperty().addListener(drawBackgroundCanvasListener); view.lassoEndProperty().addListener(drawBackgroundCanvasListener); view.editAvailabilityProperty().addListener(drawBackgroundCanvasListener); @@ -263,6 +264,8 @@ private void listenToAvailabilityCalendar(Calendar oldCalendar, Calendar newCale } private void createStaticLines() { + lines.clear(); + for (int i = 1; i < 24; i++) { createLine(HALF_HOUR_LINE_STYLE_CLASS); createLine(FULL_HOUR_LINE_STYLE_CLASS); @@ -448,63 +451,65 @@ protected void layoutChildren(double contentX, double contentY, double contentWi protected void layoutChildrenInfiniteScrolling(double contentX, double contentY, double contentWidth, double contentHeight) { final T view = getSkinnable(); - final ZonedDateTime scrollTime = view.getScrollTime(); - Instant time = scrollTime.toInstant().truncatedTo(ChronoUnit.HOURS); + if (view.getGridType().equals(GridType.STANDARD)) { + final ZonedDateTime scrollTime = view.getScrollTime(); + Instant time = scrollTime.toInstant().truncatedTo(ChronoUnit.HOURS); - double y = view.getLocation(time); + double y = view.getLocation(time); - int lineIndex = 0; + int lineIndex = 0; - do { + do { - LocalTime localTime = LocalTime.ofInstant(time, view.getZoneId()); + LocalTime localTime = LocalTime.ofInstant(time, view.getZoneId()); + + if (lineIndex >= lines.size()) { + createLine(); + Line line = lines.get(lineIndex); + line.toBack(); + } - if (lineIndex >= lines.size()) { - createLine(); Line line = lines.get(lineIndex); line.toBack(); - } - - Line line = lines.get(lineIndex); - line.toBack(); - line.setVisible(true); + line.setVisible(true); - line.getStyleClass().removeAll(HALF_HOUR_LINE_STYLE_CLASS, FULL_HOUR_LINE_STYLE_CLASS, MIDNIGHT_LINE_STYLE_CLASS, NOON_LINE_STYLE_CLASS); + line.getStyleClass().removeAll(HALF_HOUR_LINE_STYLE_CLASS, FULL_HOUR_LINE_STYLE_CLASS, MIDNIGHT_LINE_STYLE_CLASS, NOON_LINE_STYLE_CLASS); - if (localTime.getMinute() == 30) { - line.getStyleClass().add(HALF_HOUR_LINE_STYLE_CLASS); - } else { - line.getStyleClass().add(FULL_HOUR_LINE_STYLE_CLASS); - } + if (localTime.getMinute() == 30) { + line.getStyleClass().add(HALF_HOUR_LINE_STYLE_CLASS); + } else { + line.getStyleClass().add(FULL_HOUR_LINE_STYLE_CLASS); + } - if (localTime.equals(LocalTime.MIDNIGHT)) { - line.getStyleClass().add(MIDNIGHT_LINE_STYLE_CLASS); - line.setStartX(snapPositionX(contentX)); - line.setEndX(snapPositionX(contentX + contentWidth)); - } else if (localTime.equals(LocalTime.NOON)) { - line.setStartX(snapPositionX(contentX)); - line.setEndX(snapPositionX(contentX + contentWidth)); - if (view.isShowNoonMarker()) { - line.getStyleClass().add(NOON_LINE_STYLE_CLASS); + if (localTime.equals(LocalTime.MIDNIGHT)) { + line.getStyleClass().add(MIDNIGHT_LINE_STYLE_CLASS); + line.setStartX(snapPositionX(contentX)); + line.setEndX(snapPositionX(contentX + contentWidth)); + } else if (localTime.equals(LocalTime.NOON)) { + line.setStartX(snapPositionX(contentX)); + line.setEndX(snapPositionX(contentX + contentWidth)); + if (view.isShowNoonMarker()) { + line.getStyleClass().add(NOON_LINE_STYLE_CLASS); + } + } else { + line.setStartX(snapPositionX(contentX + 4)); + line.setEndX(snapPositionX(contentX + contentWidth - 4)); } - } else { - line.setStartX(snapPositionX(contentX + 4)); - line.setEndX(snapPositionX(contentX + contentWidth - 4)); - } - line.setStartY(snapPositionY(y)); - line.setEndY(snapPositionY(y)); + line.setStartY(snapPositionY(y)); + line.setEndY(snapPositionY(y)); - lineIndex++; + lineIndex++; - time = time.plus(30, ChronoUnit.MINUTES); - y = view.getLocation(time); + time = time.plus(30, ChronoUnit.MINUTES); + y = view.getLocation(time); - } while (y < contentY + contentHeight); + } while (y < contentY + contentHeight); - for (int i = lineIndex; i < lines.size(); i++) { - Line line = lines.get(i); - line.setVisible(false); + for (int i = lineIndex; i < lines.size(); i++) { + Line line = lines.get(i); + line.setVisible(false); + } } layoutEntries(contentX, contentY, contentWidth, contentHeight); @@ -1120,67 +1125,85 @@ public void draw() { GraphicsContext gc = backgroundCanvas.getGraphicsContext2D(); gc.clearRect(0, 0, getWidth(), getHeight()); - T dayView = getSkinnable(); - Calendar availabilityCalendar = dayView.getAvailabilityCalendar(); + T view = getSkinnable(); + Calendar availabilityCalendar = view.getAvailabilityCalendar(); if (availabilityCalendar != null) { - gc.setFill(dayView.getAvailabilityFill()); - LocalDate date = dayView.getDate(); - Map>> entries = availabilityCalendar.findEntries(date, date, dayView.getZoneId()); + gc.setFill(view.getAvailabilityFill()); + LocalDate date = view.getDate(); + Map>> entries = availabilityCalendar.findEntries(date, date, view.getZoneId()); List> entriesOnDate = entries.get(date); if (entriesOnDate != null) { entriesOnDate.forEach(entry -> { ZonedDateTime startAsZonedDateTime = entry.getStartAsZonedDateTime(); ZonedDateTime endAsZonedDateTime = entry.getEndAsZonedDateTime(); - double y1 = ViewHelper.getTimeLocation(dayView, startAsZonedDateTime); - double y2 = ViewHelper.getTimeLocation(dayView, endAsZonedDateTime); + double y1 = ViewHelper.getTimeLocation(view, startAsZonedDateTime); + double y2 = ViewHelper.getTimeLocation(view, endAsZonedDateTime); gc.fillRect(0, y1, getWidth(), y2 - y1); }); } } - if (dayView.isEditAvailability()) { - Instant start = dayView.getLassoStart(); - Instant end = dayView.getLassoEnd(); + if (view.isEditAvailability()) { + Instant start = view.getLassoStart(); + Instant end = view.getLassoEnd(); if (start != null && end != null) { - double y1 = ViewHelper.getTimeLocation(dayView, start); - double y2 = ViewHelper.getTimeLocation(dayView, end); + double y1 = ViewHelper.getTimeLocation(view, start); + double y2 = ViewHelper.getTimeLocation(view, end); double minY = Math.min(y1, y2); double maxY = Math.max(y1, y2); - gc.setFill(dayView.getLassoColor()); + gc.setFill(view.getLassoColor()); gc.fillRect(0, minY, getWidth(), maxY - minY); } } - if (dayView.getGridType().equals(GridType.CUSTOM)) { - gc.setStroke(dayView.getGridLineColor()); + VirtualGrid virtualGrid = view.getGridLines(); - ZonedDateTime startTime = dayView.getZonedDateTimeMin(); - ZonedDateTime endTime = dayView.getZonedDateTimeMax(); + if (view.getGridType().equals(GridType.CUSTOM)) { + gc.setStroke(view.getGridLineColor()); - if (dayView.getEarlyLateHoursStrategy().equals(EarlyLateHoursStrategy.HIDE)) { - startTime = dayView.getZonedDateTimeStart(); - endTime = dayView.getZonedDateTimeEnd(); - } + if (view.isScrollingEnabled()) { + ZonedDateTime time = view.getScrollTime(); + time = virtualGrid.adjustTime(time, false, view.getFirstDayOfWeek()); - VirtualGrid virtualGrid = dayView.getGridLines(); + double y = view.getLocation(time); - do { - double y = ViewHelper.getTimeLocation(dayView, startTime); - if (startTime.toLocalTime().getMinute() == 0) { - gc.setLineDashes(null); - } else { - gc.setLineDashes(2, 2); + do { + if (time.toLocalTime().getMinute() == 0) { + gc.setLineDashes(null); + } else { + gc.setLineDashes(2, 2); + } + gc.strokeLine(0, y, getWidth(), y); + time = time.plus(virtualGrid.getAmount(), virtualGrid.getUnit()); + y = view.getLocation(time); + } while (y < getHeight()); + + gc.setLineDashes(null); + } else { + ZonedDateTime startTime = view.getZonedDateTimeMin(); + ZonedDateTime endTime = view.getZonedDateTimeMax(); + + if (view.getEarlyLateHoursStrategy().equals(EarlyLateHoursStrategy.HIDE)) { + startTime = view.getZonedDateTimeStart(); + endTime = view.getZonedDateTimeEnd(); } - gc.strokeLine(0, y, getWidth(), y); - startTime = startTime.plus(virtualGrid.getAmount(), virtualGrid.getUnit()); - } while (startTime.isBefore(endTime)); - } - gc.setLineDashes(null); + do { + double y = ViewHelper.getTimeLocation(view, startTime); + if (startTime.toLocalTime().getMinute() == 0) { + gc.setLineDashes(null); + } else { + gc.setLineDashes(2, 2); + } + gc.strokeLine(0, y, getWidth(), y); + startTime = startTime.plus(virtualGrid.getAmount(), virtualGrid.getUnit()); + } while (startTime.isBefore(endTime)); + } + } } } } diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewContainerSkin.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewContainerSkin.java index 8b9be990..4cb38a55 100644 --- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewContainerSkin.java +++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewContainerSkin.java @@ -109,6 +109,7 @@ private void updateViewDatesOverResources() { dayView.setDefaultCalendarProvider(control -> calendarSource.getCalendars().get(0)); dayView.setPrefWidth(0); // so they all end up with the same percentage width + dayView.setMinHeight(0); HBox.setHgrow(dayView, Priority.ALWAYS); resourcesBox.getChildren().add(dayView); diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewSkin.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewSkin.java index 149918f6..b4bc1380 100644 --- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewSkin.java +++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewSkin.java @@ -17,13 +17,12 @@ package impl.com.calendarfx.view; import com.calendarfx.model.CalendarSource; -import com.calendarfx.view.AllDayView; -import com.calendarfx.view.TimeScaleView; -import com.calendarfx.view.WeekDayHeaderView; import com.calendarfx.model.Resource; +import com.calendarfx.view.AllDayView; import com.calendarfx.view.ResourcesView; import com.calendarfx.view.ResourcesView.Type; -import javafx.application.Platform; +import com.calendarfx.view.TimeScaleView; +import com.calendarfx.view.WeekDayHeaderView; import javafx.beans.InvalidationListener; import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; @@ -40,32 +39,34 @@ import javafx.scene.layout.RowConstraints; import javafx.scene.layout.VBox; import javafx.util.Callback; +import org.controlsfx.control.PlusMinusSlider; import java.time.LocalDate; -import static com.calendarfx.util.ViewHelper.scrollToRequestedTime; - public class ResourcesViewSkin> extends DateControlSkin> { private final GridPane gridPane; - private final ResourcesViewContainer resourcesContainer; - private final DayViewScrollPane timeScaleScrollPane; - private final DayViewScrollPane dayViewsScrollPane; private final ScrollBar scrollBar; + private final PlusMinusSlider plusMinusSlider; public ResourcesViewSkin(ResourcesView view) { super(view); scrollBar = new ScrollBar(); scrollBar.setOrientation(Orientation.VERTICAL); + scrollBar.visibleProperty().bind(view.scrollingEnabledProperty().not()); - TimeScaleView timeScale = new TimeScaleView(); - view.bind(timeScale, true); + plusMinusSlider = new PlusMinusSlider(); + plusMinusSlider.setOrientation(Orientation.VERTICAL); + plusMinusSlider.visibleProperty().bind(view.scrollingEnabledProperty()); - // time scale scroll pane - timeScaleScrollPane = new DayViewScrollPane(timeScale, scrollBar); - timeScaleScrollPane.getStyleClass().addAll("calendar-scroll-pane", "day-view-timescale-scroll-pane"); - timeScaleScrollPane.setMinWidth(Region.USE_PREF_SIZE); + plusMinusSlider.setOnValueChanged(evt -> { + // exponential function to increase scrolling speed when reaching ends of slider + final double base = plusMinusSlider.getValue(); + final double pow = Math.signum(plusMinusSlider.getValue()) * Math.pow(base, 2); + final double pixel = pow * -100; + view.setScrollTime(view.getZonedDateTimeAt(0, pixel, view.getZoneId())); + }); InvalidationListener updateViewListener = it -> updateView(); view.showAllDayViewProperty().addListener(updateViewListener); @@ -76,6 +77,7 @@ public ResourcesViewSkin(ResourcesView view) { view.numberOfDaysProperty().addListener(updateViewListener); view.typeProperty().addListener(updateViewListener); view.getResources().addListener(updateViewListener); + view.scrollingEnabledProperty().addListener(updateViewListener); RowConstraints row0 = new RowConstraints(); row0.setFillHeight(true); @@ -91,20 +93,15 @@ public ResourcesViewSkin(ResourcesView view) { gridPane.getRowConstraints().setAll(row0, row1); gridPane.getStyleClass().add("container"); - resourcesContainer = new ResourcesViewContainer<>(view); - - view.bind(resourcesContainer, true); - getChildren().add(gridPane); - dayViewsScrollPane = new DayViewScrollPane(resourcesContainer, scrollBar); - - /* - * Run later when the control has become visible. - */ - Platform.runLater(() -> scrollToRequestedTime(view, dayViewsScrollPane)); - view.requestedTimeProperty().addListener(it -> scrollToRequestedTime(view, dayViewsScrollPane)); +// /* +// * Run later when the control has become visible. +// */ +// Platform.runLater(() -> scrollToRequestedTime(view, dayViewsScrollPane)); +// +// view.requestedTimeProperty().addListener(it -> scrollToRequestedTime(view, dayViewsScrollPane)); updateView(); } @@ -130,7 +127,17 @@ private void updateViewDatesOverResources() { timeScaleColumn.setFillWidth(true); timeScaleColumn.setHgrow(Priority.NEVER); gridPane.getColumnConstraints().add(timeScaleColumn); - gridPane.add(timeScaleScrollPane, 0, 1); + + TimeScaleView timeScale = createTimeScale(); + + if (view.isScrollingEnabled()) { + gridPane.add(timeScale, 0, 1); + } else { + DayViewScrollPane timeScaleScrollPane = new DayViewScrollPane(timeScale, scrollBar); + timeScaleScrollPane.getStyleClass().addAll("calendar-scroll-pane", "day-view-timescale-scroll-pane"); + timeScaleScrollPane.setMinWidth(Region.USE_PREF_SIZE); + gridPane.add(timeScaleScrollPane, 0, 1); + } Node upperLeftCorner = view.getUpperLeftCorner(); upperLeftCorner.getStyleClass().add("upper-left-corner"); @@ -285,23 +292,71 @@ private void updateViewDatesOverResources() { } } - ColumnConstraints dayViewsConstraints = new ColumnConstraints(); - dayViewsConstraints.setFillWidth(true); - dayViewsConstraints.setHgrow(Priority.ALWAYS); - gridPane.getColumnConstraints().add(dayViewsConstraints); - gridPane.add(dayViewsScrollPane, 1, 1); + ColumnConstraints containerOrScrollPaneConstraints = new ColumnConstraints(); + containerOrScrollPaneConstraints.setFillWidth(true); + containerOrScrollPaneConstraints.setHgrow(Priority.ALWAYS); + gridPane.getColumnConstraints().add(containerOrScrollPaneConstraints); + + ResourcesViewContainer resourcesContainer = createContainer(); + if (view.isScrollingEnabled()) { + gridPane.add(resourcesContainer, 1, 1); + } else { + gridPane.add(new DayViewScrollPane(resourcesContainer, scrollBar), 1, 1); + } if (view.isShowScrollBar()) { ColumnConstraints scrollbarConstraint = new ColumnConstraints(); scrollbarConstraint.setFillWidth(true); scrollbarConstraint.setHgrow(Priority.NEVER); scrollbarConstraint.setPrefWidth(Region.USE_COMPUTED_SIZE); + gridPane.getColumnConstraints().add(scrollbarConstraint); - gridPane.add(scrollBar, 2, 1); + if (view.isScrollingEnabled()) { + gridPane.add(plusMinusSlider, 2, 1); + } else { + gridPane.add(scrollBar, 2, 1); + } } } + private ResourcesViewContainer resourcesContainer; + + private ResourcesViewContainer createContainer() { + ResourcesView view = getSkinnable(); + + // first tear down the existing one + if (resourcesContainer != null) { + view.unbind(resourcesContainer); + } + + // then create the new one + resourcesContainer = new ResourcesViewContainer<>(view); + resourcesContainer.setMinHeight(0); + view.bind(resourcesContainer, true); + + // return it + return resourcesContainer; + } + + private TimeScaleView timeScaleView; + + private TimeScaleView createTimeScale(){ + ResourcesView view = getSkinnable(); + + // first tear down the existing one + if (timeScaleView != null) { + view.unbind(timeScaleView); + } + + // then create the new one + timeScaleView = new TimeScaleView(); + view.bind(timeScaleView, true); + + // return it + return timeScaleView; + } + private void updateViewResourcesOverDates() { final ResourcesView view = getSkinnable(); @@ -311,7 +366,17 @@ private void updateViewResourcesOverDates() { timeScaleColumn.setHgrow(Priority.NEVER); gridPane.getColumnConstraints().add(timeScaleColumn); - gridPane.add(timeScaleScrollPane, 0, 1); + TimeScaleView timeScale = createTimeScale(); + + if (view.isScrollingEnabled()) { + gridPane.add(timeScale, 0, 1); + } else { + // time scale scroll pane + DayViewScrollPane timeScaleScrollPane = new DayViewScrollPane(timeScale, scrollBar); + timeScaleScrollPane.getStyleClass().addAll("calendar-scroll-pane", "day-view-timescale-scroll-pane"); + timeScaleScrollPane.setMinWidth(Region.USE_PREF_SIZE); + gridPane.add(timeScaleScrollPane, 0, 1); + } Node upperLeftCorner = view.getUpperLeftCorner(); upperLeftCorner.getStyleClass().add("upper-left-corner"); @@ -404,7 +469,16 @@ private void updateViewResourcesOverDates() { dayViewsConstraints.setFillWidth(true); dayViewsConstraints.setHgrow(Priority.ALWAYS); gridPane.getColumnConstraints().add(dayViewsConstraints); - gridPane.add(dayViewsScrollPane, 1, 1); + + ResourcesViewContainer resourcesContainer = createContainer(); + if (view.isScrollingEnabled()) { + resourcesContainer.setTranslateY(0); + resourcesContainer.setManaged(true); + gridPane.add(resourcesContainer, 1, 1); + } else { + DayViewScrollPane dayViewsScrollPane = new DayViewScrollPane(resourcesContainer, scrollBar); + gridPane.add(dayViewsScrollPane, 1, 1); + } if (view.isShowScrollBar()) { ColumnConstraints scrollbarConstraint = new ColumnConstraints(); diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/TimeScaleViewSkin.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/TimeScaleViewSkin.java index e3aa5374..fa51bb8c 100644 --- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/TimeScaleViewSkin.java +++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/TimeScaleViewSkin.java @@ -63,9 +63,15 @@ public TimeScaleViewSkin(T view) { setupCurrentTimeMarkerSupport(); updateShowMarkers(); + view.scrollingEnabledProperty().addListener(it -> { + timeLabels.clear(); + getChildren().clear(); + view.requestLayout(); + }); + view.scrollTimeProperty().addListener(it -> { if (view.isScrollingEnabled()) { - getSkinnable().requestLayout(); + view.requestLayout(); } }); From 6db2c30756fbf157269b86c0837115663cc8df83 Mon Sep 17 00:00:00 2001 From: Dirk Lemmermann Date: Tue, 11 Oct 2022 15:00:31 +0200 Subject: [PATCH 2/7] Fixes for recurrence support in the AllDayView. Fixes regarding time zone support, fixes in the entry popover that were needed for JavaFX 19. --- .../views/resources/HelloResourcesView.java | 35 ++- .../java/com/calendarfx/model/Calendar.java | 27 +- .../java/com/calendarfx/model/Resource.java | 87 ++++++- .../java/com/calendarfx/view/DateControl.java | 2 + .../com/calendarfx/view/ResourcesView.java | 10 +- .../view/popover/EntryDetailsView.java | 24 +- .../com/calendarfx/view/AgendaViewSkin.java | 2 +- .../com/calendarfx/view/AllDayViewSkin.java | 243 ++++++++++-------- .../impl/com/calendarfx/view/DayViewSkin.java | 4 +- .../calendarfx/view/MonthSheetViewSkin.java | 2 +- .../com/calendarfx/view/MonthViewSkin.java | 2 +- .../view/ResourcesViewContainerSkin.java | 22 +- .../calendarfx/view/ResourcesViewSkin.java | 52 ++-- .../calendarfx/view/YearMonthViewSkin.java | 2 +- .../impl/com/calendarfx/view/util/Util.java | 16 +- .../com/calendarfx/view/calendar.css | 1 - 16 files changed, 319 insertions(+), 212 deletions(-) diff --git a/CalendarFXSampler/src/main/java/com/calendarfx/demo/views/resources/HelloResourcesView.java b/CalendarFXSampler/src/main/java/com/calendarfx/demo/views/resources/HelloResourcesView.java index f1601be3..ca8c81c7 100644 --- a/CalendarFXSampler/src/main/java/com/calendarfx/demo/views/resources/HelloResourcesView.java +++ b/CalendarFXSampler/src/main/java/com/calendarfx/demo/views/resources/HelloResourcesView.java @@ -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; @@ -100,7 +101,7 @@ public Node getControlPanel() { ChoiceBox typeBox = new ChoiceBox<>(); typeBox.getItems().setAll(Type.values()); typeBox.valueProperty().bindBidirectional(resourcesView.typeProperty()); - typeBox.setConverter(new StringConverter() { + typeBox.setConverter(new StringConverter<>() { @Override public String toString(Type object) { if (object != null) { @@ -121,6 +122,30 @@ public Type fromString(String string) { } }); + ChoiceBox 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 gridTypeBox = new ChoiceBox<>(); gridTypeBox.getItems().setAll(GridType.values()); gridTypeBox.valueProperty().bindBidirectional(resourcesView.gridTypeProperty()); @@ -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); } @@ -196,7 +221,9 @@ private List> createResources(int count) { private Resource create(String name, Style style) { Resource 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; } diff --git a/CalendarFXView/src/main/java/com/calendarfx/model/Calendar.java b/CalendarFXView/src/main/java/com/calendarfx/model/Calendar.java index f6006a2e..9b31932f 100644 --- a/CalendarFXView/src/main/java/com/calendarfx/model/Calendar.java +++ b/CalendarFXView/src/main/java/com/calendarfx/model/Calendar.java @@ -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()); } /** @@ -297,8 +297,7 @@ public final Map>> findEntries(LocalDate startDate, Loc @SuppressWarnings({"rawtypes", "unchecked"}) private Map>> 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); @@ -314,8 +313,7 @@ private Map>> doGetEntries(LocalDate startDate, LocalDa } if (MODEL.isLoggable(FINE)) { - MODEL.fine(getName() + ": found " + intersectingEntries.size() - + " entries"); + MODEL.fine(getName() + ": found " + intersectingEntries.size() + " entries"); } Map>> result = new HashMap<>(); @@ -329,17 +327,6 @@ private Map>> 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 dateList = new Recur(recurrenceRule).getDates(utilStartDate, utilEndDate); for (LocalDate repeatingDate : dateList) { @@ -351,12 +338,11 @@ private Map>> 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()); @@ -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") + "]"; } } diff --git a/CalendarFXView/src/main/java/com/calendarfx/model/Resource.java b/CalendarFXView/src/main/java/com/calendarfx/model/Resource.java index d62f68a7..f5e79ea7 100644 --- a/CalendarFXView/src/main/java/com/calendarfx/model/Resource.java +++ b/CalendarFXView/src/main/java/com/calendarfx/model/Resource.java @@ -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 @@ -20,10 +31,27 @@ */ public class Resource { + 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); } @@ -46,24 +74,44 @@ public final void setUserObject(T userObject) { this.userObject.set(userObject); } - public final ObjectProperty calendar = new SimpleObjectProperty<>(this, "calendar", new Calendar()); + private final ObservableList 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 getCalendarSources() { + return calendarSources; } + private final ReadOnlyListWrapper 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 calendarProperty() { - return calendar; + public final ReadOnlyListProperty calendarsProperty() { + return calendars.getReadOnlyProperty(); } - public final void setCalendar(Calendar calendar) { - this.calendar.set(calendar); + private final ObservableList unmodifiableCalendars = FXCollections.unmodifiableObservableList(calendars.get()); + + /** + * Returns the value of {@link #calendarsProperty()}. + * + * @return the list of all calendars available for this control + */ + public final ObservableList getCalendars() { + return unmodifiableCalendars; } public final ObjectProperty availabilityCalendar = new SimpleObjectProperty<>(this, "availabilityCalendar", new Calendar()); @@ -90,6 +138,25 @@ public void setAvailabilityCalendar(Calendar availabilityCalendar) { this.availabilityCalendar.set(availabilityCalendar); } + 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); + } + @Override public String toString() { if (getUserObject() != null) { diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/DateControl.java b/CalendarFXView/src/main/java/com/calendarfx/view/DateControl.java index 2592865d..3c424478 100644 --- a/CalendarFXView/src/main/java/com/calendarfx/view/DateControl.java +++ b/CalendarFXView/src/main/java/com/calendarfx/view/DateControl.java @@ -625,6 +625,8 @@ public final Entry createEntryAt(ZonedDateTime time, Calendar calendar, boole Callback> 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 diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/ResourcesView.java b/CalendarFXView/src/main/java/com/calendarfx/view/ResourcesView.java index 85ab500c..559b475c 100644 --- a/CalendarFXView/src/main/java/com/calendarfx/view/ResourcesView.java +++ b/CalendarFXView/src/main/java/com/calendarfx/view/ResourcesView.java @@ -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()))); @@ -137,11 +142,6 @@ private void maybeRunAndConsume(RequestEvent evt, Consumer consume } } - @Override - protected Skin createDefaultSkin() { - return new ResourcesViewSkin(this); - } - private final BooleanProperty adjustToFirstDayOfWeek = new SimpleBooleanProperty(this, "adjustToFirstDayOfWeek", true); /** diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/popover/EntryDetailsView.java b/CalendarFXView/src/main/java/com/calendarfx/view/popover/EntryDetailsView.java index 6f2ee7b1..929abea9 100644 --- a/CalendarFXView/src/main/java/com/calendarfx/view/popover/EntryDetailsView.java +++ b/CalendarFXView/src/main/java/com/calendarfx/view/popover/EntryDetailsView.java @@ -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); } }); diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/AgendaViewSkin.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/AgendaViewSkin.java index 5b45ea06..81111ab2 100644 --- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/AgendaViewSkin.java +++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/AgendaViewSkin.java @@ -198,7 +198,7 @@ public LocalDate getLoadEndDate() { @Override public ZoneId getZoneId() { - return ZoneId.systemDefault(); + return getSkinnable().getZoneId(); } @Override diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/AllDayViewSkin.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/AllDayViewSkin.java index 790d352e..744ae6a1 100644 --- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/AllDayViewSkin.java +++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/AllDayViewSkin.java @@ -30,6 +30,7 @@ import impl.com.calendarfx.view.util.Util; import javafx.beans.InvalidationListener; import javafx.geometry.Insets; +import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.control.Control; import javafx.scene.layout.HBox; @@ -45,6 +46,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; @@ -58,6 +60,8 @@ public class AllDayViewSkin extends DateControlSkin implements LoadD private final DataLoader dataLoader; private final HBox container; + private final Group entryViewGroup = new Group(); + public AllDayViewSkin(AllDayView view) { super(view); @@ -67,6 +71,10 @@ public AllDayViewSkin(AllDayView view) { container.getStyleClass().add("container"); getChildren().add(container); + entryViewGroup.setMouseTransparent(false); + entryViewGroup.setManaged(false); + getChildren().add(entryViewGroup); + // update backgrounds InvalidationListener updateBackgroundsListener = evt -> updateBackgrounds(); view.numberOfDaysProperty().addListener(updateBackgroundsListener); @@ -100,7 +108,7 @@ protected void refreshData() { private void updateEntries(String reason) { LoggingDomain.PERFORMANCE.fine("updating entries, reason: " + reason); - getChildren().removeIf(child -> child instanceof AllDayEntryView); + entryViewGroup.getChildren().clear(); Map>> dataMap = new HashMap<>(); dataLoader.loadEntries(dataMap); @@ -119,27 +127,40 @@ private void updateEntries(String reason) { } getSkinnable().autosize(); + + getSkinnable().requestLayout(); } - private boolean removeEntryView(Entry entry) { - boolean removed = getChildren().removeIf(node -> { - if (node instanceof AllDayEntryView) { - AllDayEntryView view = (AllDayEntryView) node; + private List findEntryViews(Entry entry) { + return entryViewGroup.getChildren().stream() + .map(node -> (EntryViewBase) node) + .filter(e -> e.getEntry().getId().equals(entry.getId())) + .collect(Collectors.toList()); + } - Entry removedEntry = entry; - if (removedEntry.getRecurrenceSourceEntry() != null) { - removedEntry = removedEntry.getRecurrenceSourceEntry(); - } + private Optional findRecurrenceEntryView(Entry entry) { + List collect = entryViewGroup.getChildren().stream() + .map(node -> (EntryViewBase) node) + .filter(e -> e.getEntry().isRecurrence()) + .filter(e -> e.getEntry().getRecurrenceId().equals(entry.getRecurrenceId())) + .collect(Collectors.toList()); - Entry viewEntry = view.getEntry(); - if (viewEntry.getRecurrenceSourceEntry() != null) { - viewEntry = viewEntry.getRecurrenceSourceEntry(); - } + if (collect.isEmpty()) { + return Optional.empty(); + } - return viewEntry.getId().equals(removedEntry.getId()); - } + return Optional.of(collect.get(0)); + } - return false; + private boolean removeEntryViews(Entry entry, String reason) { + if (reason != null) { + LoggingDomain.VIEW.fine("removing entry, reason = " + reason + ", date = " + getSkinnable().getDate()); + } + + boolean removed = Util.removeChildren(entryViewGroup, node -> { + AllDayEntryView view = (AllDayEntryView) node; + Entry viewEntry = view.getEntry(); + return viewEntry.getId().equals(entry.getId()); }); if (removed && !(entry instanceof DraggedEntry) && LoggingDomain.VIEW.isLoggable(Level.FINE)) { @@ -149,31 +170,45 @@ private boolean removeEntryView(Entry entry) { return removed; } - private void addEntryView(Entry entry) { + private void addEntryViews(Entry entry, String reason) { + LoggingDomain.VIEW.fine("adding entry, reason = " + reason + ", date = " + getSkinnable().getDate()); if (entry.isRecurring()) { - Calendar calendar = entry.getCalendar(); - final Map>> entries = calendar.findEntries(getLoadStartDate(), getLoadEndDate(), getZoneId()); - for (LocalDate date : entries.keySet()) { - List> entriesOnDate = entries.get(date); - if (entriesOnDate != null) { - entriesOnDate.forEach(this::doAddEntryView); - } - } + Map> recurrenceEntries = findRecurrenceEntries(entry); + recurrenceEntries.forEach((date, recurrence) -> doAddEntryView(recurrence)); } else { doAddEntryView(entry); } + + getSkinnable().requestLayout(); + } + + private Map> findRecurrenceEntries(Entry entry) { + Calendar calendar = entry.getCalendar(); + LocalDate startDate = getLoadStartDate(); + LocalDate endDate = getLoadEndDate(); + Map>> entries = calendar.findEntries(startDate, endDate, getZoneId()); + Map> result = new HashMap<>(); + + entries.forEach((date, list) -> { + if (!list.isEmpty()) { + Optional> first = list.stream().filter(e -> e.getStartDate().equals(date)).findFirst(); + if (first.isPresent()) { + result.put(date, first.get()); + } + } + }); + + return result; } private AllDayEntryView doAddEntryView(Entry entry) { Callback, AllDayEntryView> factory = getSkinnable().getEntryViewFactory(); AllDayEntryView view = factory.call(entry); - view.applyCss(); // TODO: really needed view.getProperties().put("control", getSkinnable()); view.setManaged(false); int index = findIndex(entry); - - getChildren().add(index, view); + entryViewGroup.getChildren().add(index, view); if (!(entry instanceof DraggedEntry) && LoggingDomain.VIEW.isLoggable(Level.FINE)) { LoggingDomain.VIEW.fine("added entry view " + entry.getTitle() + ", day = " + getSkinnable().getDate()); @@ -187,16 +222,14 @@ private AllDayEntryView doAddEntryView(Entry entry) { * view. The right order is important for TAB traversal to work properly. */ private int findIndex(Entry entry) { - int childrenSize = getChildren().size(); + int childrenSize = entryViewGroup.getChildren().size(); for (int i = 0; i < childrenSize; i++) { - Node node = getChildren().get(i); - if (node instanceof AllDayEntryView) { - AllDayEntryView view = (AllDayEntryView) node; - Entry viewEntry = view.getEntry(); - if (viewEntry.getStartAsZonedDateTime().isAfter(entry.getStartAsZonedDateTime())) { - return i; - } + Node node = entryViewGroup.getChildren().get(i); + AllDayEntryView view = (AllDayEntryView) node; + Entry viewEntry = view.getEntry(); + if (viewEntry.getStartAsZonedDateTime().isAfter(entry.getStartAsZonedDateTime())) { + return i; } } @@ -210,95 +243,81 @@ protected void calendarChanged(Calendar calendar) { @Override protected void entryCalendarChanged(CalendarEvent evt) { - Entry entry = evt.getEntry(); + LoggingDomain.VIEW.fine("handle entry calendar changed, date = " + getSkinnable().getDate()); - /* - * We only care about full day entries in this view. - */ - if (!entry.isFullDay()) { - return; - } - - if (evt.isEntryRemoved()) { - removeEntryView(entry); - getSkinnable().requestLayout(); + Entry entry = evt.getEntry(); + if (evt.getCalendar() == null) { + removeEntryViews(entry, "entry was deleted"); + } else { + if (entry.isFullDay() && isRelevant(entry)) { + List entryView = findEntryViews(entry); + if (!entryView.isEmpty()) { + entryView.forEach(view -> view.getEntry().setCalendar(evt.getCalendar())); + } else { + addEntryViews(entry, "entry calendar changed"); + } + } } - if (evt.isEntryAdded() && isRelevant(entry)) { - addEntryView(entry); - getSkinnable().requestLayout(); - } + getSkinnable().requestLayout(); } @Override protected void entryFullDayChanged(CalendarEvent evt) { + LoggingDomain.VIEW.fine("handle entry full day flag changed, date = " + getSkinnable().getDate()); Entry entry = evt.getEntry(); if (isRelevant(entry)) { + removeEntryViews(entry, "full day flag changed to false"); if (entry.isFullDay()) { - addEntryView(entry); - } else { - removeEntryView(entry); + addEntryViews(entry, "full day flag changed to true, no entry view can be present"); } - getSkinnable().requestLayout(); } + getSkinnable().requestLayout(); } @Override protected void entryRecurrenceRuleChanged(CalendarEvent evt) { + LoggingDomain.VIEW.fine("handle entry recurrence rule changed, date = " + getSkinnable().getDate()); Entry entry = evt.getEntry(); /* * We only care about full day entries in this view. */ - if (!entry.isFullDay()) { - return; + if (entry.isFullDay()) { + // remove all entry views + removeEntryViews(entry, "recurrence rule changed"); + if (isRelevant(entry)) { + addEntryViews(entry, "recurrence rule changed"); + } } - removeEntryView(entry); - addEntryView(entry); + getSkinnable().requestLayout(); } @Override protected void entryIntervalChanged(CalendarEvent evt) { + LoggingDomain.VIEW.fine("handle entry interval changed, date = " + getSkinnable().getDate()); + Entry entry = evt.getEntry(); /* * We only care about full day entries in this view. */ - if (!entry.isFullDay()) { - return; - } - - removeEntryView(entry); - - if (isRelevant(entry)) { - if (entry.isRecurring()) { - Calendar calendar = entry.getCalendar(); - final Map>> entriesMap = calendar.findEntries(getLoadStartDate(), getLoadEndDate(), getZoneId()); - List> entries = entriesMap.get(getSkinnable().getDate()); - if (entries != null) { - for (Entry e : entries) { - if (e.getId().equals(entry.getId())) { - addEntryView(e); - - /* - * We only support recurrence for temporal units larger than days, so there can only - * be one entry. - */ - break; - } - } - } - } else { - addEntryView(entry); + if (entry.isFullDay()) { + // remove all entry views + removeEntryViews(entry, "interval changed"); + if (isRelevant(entry)) { + addEntryViews(entry, "interval changed"); } } + + getSkinnable().requestLayout(); } @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { - List entryViews = getChildren().stream().filter(node -> node instanceof AllDayEntryView).map(node -> (AllDayEntryView) node).collect(Collectors.toList()); + List entryViews = entryViewGroup.getChildren().stream().filter(node -> node instanceof AllDayEntryView).map(node -> (AllDayEntryView) node).collect(Collectors.toList()); List placements = TimeBoundsResolver.resolve(entryViews); @@ -307,11 +326,13 @@ protected double computePrefHeight(double width, double topInset, double rightIn maxPosition = Math.max(maxPosition, p.getColumnIndex()); } - Insets insets = getSkinnable().getInsets(); - Insets extraPadding = getSkinnable().getExtraPadding(); + AllDayView view = getSkinnable(); + + Insets insets = view.getInsets(); + Insets extraPadding = view.getExtraPadding(); - double rowHeight = getSkinnable().getRowHeight(); - double rowSpacing = getSkinnable().getRowSpacing(); + double rowHeight = view.getRowHeight(); + double rowSpacing = view.getRowSpacing(); return (maxPosition + 1) * rowHeight + (maxPosition * rowSpacing) + insets.getTop() + insets.getBottom() * extraPadding.getTop() + extraPadding.getBottom(); } @@ -324,27 +345,29 @@ protected double computeMinHeight(double width, double topInset, double rightIns protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) { super.layoutChildren(contentX, contentY, contentWidth, contentHeight); - double rowHeight = getSkinnable().getRowHeight(); - double rowSpacing = getSkinnable().getRowSpacing(); + AllDayView view = getSkinnable(); + + double rowHeight = view.getRowHeight(); + double rowSpacing = view.getRowSpacing(); double height = 0; - Insets extraPadding = getSkinnable().getExtraPadding(); + Insets extraPadding = view.getExtraPadding(); - List entryViews = getChildren().stream().filter(node -> node instanceof AllDayEntryView).map(node -> (AllDayEntryView) node).collect(Collectors.toList()); + List entryViews = entryViewGroup.getChildren().stream().map(node -> (AllDayEntryView) node).collect(Collectors.toList()); List placements = TimeBoundsResolver.resolve(entryViews); for (Placement placement : placements) { - EntryViewBase view = placement.getEntryView(); - Entry entry = view.getEntry(); + EntryViewBase entryView = placement.getEntryView(); + Entry entry = entryView.getEntry(); - LocalDate startDate = getSkinnable().getDate(); - if (getSkinnable().isAdjustToFirstDayOfWeek()) { - startDate = Util.adjustToFirstDayOfWeek(getSkinnable().getDate(), getSkinnable().getFirstDayOfWeek()); + LocalDate startDate = view.getDate(); + if (view.isAdjustToFirstDayOfWeek()) { + startDate = Util.adjustToFirstDayOfWeek(view.getDate(), view.getFirstDayOfWeek()); } - LocalDate endDate = startDate.plusDays(getSkinnable().getNumberOfDays() - 1); + LocalDate endDate = startDate.plusDays(view.getNumberOfDays() - 1); long deltaDays = ChronoUnit.DAYS.between(startDate, entry.getStartDate()); @@ -355,34 +378,34 @@ protected void layoutChildren(double contentX, double contentY, double contentWi } if (entry.getStartDate().isBefore(startDate)) { - view.getProperties().put("startDate", startDate); + entryView.getProperties().put("startDate", startDate); } else { - view.getProperties().put("startDate", entry.getStartDate()); + entryView.getProperties().put("startDate", entry.getStartDate()); } if (entry.getEndDate().isAfter(endDate)) { - view.getProperties().put("endDate", endDate); + entryView.getProperties().put("endDate", endDate); } else { - view.getProperties().put("endDate", entry.getEndDate()); + entryView.getProperties().put("endDate", entry.getEndDate()); } entryDurationInDays = Math.max(entryDurationInDays, 1); - double dayWidth = contentWidth / getSkinnable().getNumberOfDays(); + double dayWidth = contentWidth / view.getNumberOfDays(); double x = Math.max(0, contentX + (deltaDays * dayWidth)); double y = contentY + placement.getColumnIndex() * (rowHeight + rowSpacing) + extraPadding.getTop(); double w; - if (getSkinnable().getNumberOfDays() == 1) { + if (view.getNumberOfDays() == 1) { w = contentWidth + 1; } else { - w = Math.min(entryDurationInDays * dayWidth - getSkinnable().getColumnSpacing(), contentWidth - x); + w = Math.min(entryDurationInDays * dayWidth - view.getColumnSpacing(), contentWidth - x); } - view.setMaxHeight(rowHeight); + entryView.setMaxHeight(rowHeight); - view.resizeRelocate(snapPositionX(x), snapPositionY(y), snapSizeX(w), snapSizeY(rowHeight)); + entryView.resizeRelocate(snapPositionX(x), snapPositionY(y), snapSizeX(w), snapSizeY(rowHeight)); height = Math.max(height, y + rowHeight); } @@ -481,7 +504,7 @@ public LocalDate getLoadEndDate() { @Override public ZoneId getZoneId() { - return ZoneId.systemDefault(); + return getSkinnable().getZoneId(); } @Override diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewSkin.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewSkin.java index de5debf4..f921b13e 100644 --- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewSkin.java +++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewSkin.java @@ -826,7 +826,7 @@ protected void entryCalendarChanged(CalendarEvent evt) { removeEntryView(entry, "entry was deleted"); } } else { - if (evt.getOldCalendar() == null && isRelevant(entry)) { + if (!entry.isFullDay() && evt.getOldCalendar() == null && isRelevant(entry)) { addEntryView(entry, "entry calendar changed"); } } @@ -1089,7 +1089,7 @@ public LocalDate getLoadEndDate() { @Override public ZoneId getZoneId() { - return ZoneId.systemDefault(); + return getSkinnable().getZoneId(); } @Override diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/MonthSheetViewSkin.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/MonthSheetViewSkin.java index 5e24e6ce..09e37dd9 100644 --- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/MonthSheetViewSkin.java +++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/MonthSheetViewSkin.java @@ -486,7 +486,7 @@ public LocalDate getLoadEndDate() { @Override public ZoneId getZoneId() { - return ZoneId.systemDefault(); + return getSkinnable().getZoneId(); } @Override diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/MonthViewSkin.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/MonthViewSkin.java index c9aaa230..cebb7b53 100644 --- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/MonthViewSkin.java +++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/MonthViewSkin.java @@ -448,7 +448,7 @@ public LocalDate getLoadEndDate() { @Override public ZoneId getZoneId() { - return ZoneId.systemDefault(); + return getSkinnable().getZoneId(); } @Override diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewContainerSkin.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewContainerSkin.java index 4cb38a55..8736b762 100644 --- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewContainerSkin.java +++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewContainerSkin.java @@ -1,11 +1,10 @@ package impl.com.calendarfx.view; -import com.calendarfx.model.CalendarSource; -import com.calendarfx.view.DayView; -import com.calendarfx.view.WeekView; import com.calendarfx.model.Resource; +import com.calendarfx.view.DayView; import com.calendarfx.view.ResourcesView; import com.calendarfx.view.ResourcesView.Type; +import com.calendarfx.view.WeekView; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.binding.Bindings; @@ -104,9 +103,8 @@ private void updateViewDatesOverResources() { dayView.setAvailabilityCalendar(resource.getAvailabilityCalendar()); dayView.installDefaultLassoFinishedBehaviour(); - CalendarSource calendarSource = createCalendarSource(resource); - dayView.getCalendarSources().setAll(calendarSource); - dayView.setDefaultCalendarProvider(control -> calendarSource.getCalendars().get(0)); + Bindings.bindContent(dayView.getCalendarSources(), resource.getCalendarSources()); + dayView.setDefaultCalendarProvider(dateControl -> resource.getCalendars().get(0)); dayView.setPrefWidth(0); // so they all end up with the same percentage width dayView.setMinHeight(0); @@ -187,9 +185,8 @@ private void updateViewResourcesOverDates() { weekView.numberOfDaysProperty().bindBidirectional(resourcesView.numberOfDaysProperty()); - CalendarSource calendarSource = createCalendarSource(resource); - weekView.getCalendarSources().setAll(calendarSource); - weekView.setDefaultCalendarProvider(control -> calendarSource.getCalendars().get(0)); + Bindings.bindContent(weekView.getCalendarSources(), resource.getCalendarSources()); + weekView.setDefaultCalendarProvider(dateControl -> resource.getCalendars().get(0)); this.box.getChildren().add(weekView); @@ -206,11 +203,4 @@ private void updateViewResourcesOverDates() { HBox.setHgrow(weekView, Priority.ALWAYS); } } - - private CalendarSource createCalendarSource(T resource) { - CalendarSource source = new CalendarSource(resource.getUserObject().toString()); - source.setName(resource.getUserObject().toString()); - source.getCalendars().setAll(resource.getCalendar()); - return source; - } } \ No newline at end of file diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewSkin.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewSkin.java index b4bc1380..84fd4cf9 100644 --- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewSkin.java +++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/ResourcesViewSkin.java @@ -16,13 +16,14 @@ package impl.com.calendarfx.view; -import com.calendarfx.model.CalendarSource; import com.calendarfx.model.Resource; +import com.calendarfx.util.ViewHelper; import com.calendarfx.view.AllDayView; import com.calendarfx.view.ResourcesView; import com.calendarfx.view.ResourcesView.Type; import com.calendarfx.view.TimeScaleView; import com.calendarfx.view.WeekDayHeaderView; +import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; @@ -48,6 +49,7 @@ public class ResourcesViewSkin> extends DateControlSkin view) { super(view); @@ -95,22 +97,30 @@ public ResourcesViewSkin(ResourcesView view) { getChildren().add(gridPane); + updateView(); -// /* -// * Run later when the control has become visible. -// */ -// Platform.runLater(() -> scrollToRequestedTime(view, dayViewsScrollPane)); -// -// view.requestedTimeProperty().addListener(it -> scrollToRequestedTime(view, dayViewsScrollPane)); + /* + * Run later when the control has become visible. + */ + Platform.runLater(() -> { + if (dayViewScrollPane != null) { + ViewHelper.scrollToRequestedTime(view, dayViewScrollPane); + } + }); - updateView(); + view.requestedTimeProperty().addListener(it -> { + if (dayViewScrollPane != null) { + ViewHelper.scrollToRequestedTime(view, dayViewScrollPane); + } + }); } - private void updateView() { gridPane.getChildren().clear(); gridPane.getColumnConstraints().clear(); + dayViewScrollPane = null; + ResourcesView view = getSkinnable(); if (view.getType().equals(Type.RESOURCES_OVER_DATES)) { updateViewResourcesOverDates(); @@ -262,14 +272,12 @@ private void updateViewDatesOverResources() { allDayView.setAdjustToFirstDayOfWeek(false); allDayView.setNumberOfDays(1); - // some unbindings for AllDayView + // some un-bindings for AllDayView Bindings.unbindBidirectional(view.defaultCalendarProviderProperty(), allDayView.defaultCalendarProviderProperty()); Bindings.unbindBidirectional(view.draggedEntryProperty(), allDayView.draggedEntryProperty()); Bindings.unbindContentBidirectional(view.getCalendarSources(), allDayView.getCalendarSources()); - CalendarSource calendarSource = createCalendarSource(resource); - allDayView.getCalendarSources().setAll(calendarSource); - allDayView.setDefaultCalendarProvider(control -> calendarSource.getCalendars().get(0)); + Bindings.bindContent(allDayView.getCalendarSources(), resource.getCalendarSources()); VBox.setVgrow(allDayView, Priority.ALWAYS); singleResourceBox.getChildren().add(allDayView); @@ -301,7 +309,8 @@ private void updateViewDatesOverResources() { if (view.isScrollingEnabled()) { gridPane.add(resourcesContainer, 1, 1); } else { - gridPane.add(new DayViewScrollPane(resourcesContainer, scrollBar), 1, 1); + dayViewScrollPane = new DayViewScrollPane(resourcesContainer, scrollBar); + gridPane.add(dayViewScrollPane, 1, 1); } if (view.isShowScrollBar()) { @@ -433,10 +442,7 @@ private void updateViewResourcesOverDates() { Bindings.unbindBidirectional(view.draggedEntryProperty(), allDayView.draggedEntryProperty()); Bindings.unbindContentBidirectional(view.getCalendarSources(), allDayView.getCalendarSources()); - CalendarSource calendarSource = createCalendarSource(resource); - allDayView.getCalendarSources().setAll(calendarSource); - allDayView.setDefaultCalendarProvider(control -> calendarSource.getCalendars().get(0)); - + Bindings.bindContent(allDayView.getCalendarSources(), resource.getCalendarSources()); resourceHeader.getChildren().add(allDayView); } @@ -476,8 +482,8 @@ private void updateViewResourcesOverDates() { resourcesContainer.setManaged(true); gridPane.add(resourcesContainer, 1, 1); } else { - DayViewScrollPane dayViewsScrollPane = new DayViewScrollPane(resourcesContainer, scrollBar); - gridPane.add(dayViewsScrollPane, 1, 1); + dayViewScrollPane = new DayViewScrollPane(resourcesContainer, scrollBar); + gridPane.add(dayViewScrollPane, 1, 1); } if (view.isShowScrollBar()) { @@ -490,10 +496,4 @@ private void updateViewResourcesOverDates() { gridPane.add(scrollBar, 2, 1); } } - - private CalendarSource createCalendarSource(T resource) { - CalendarSource source = new CalendarSource(resource.getUserObject().toString()); - source.getCalendars().add(resource.getCalendar()); - return source; - } } diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/YearMonthViewSkin.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/YearMonthViewSkin.java index 2c7bddb2..b2319709 100644 --- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/YearMonthViewSkin.java +++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/YearMonthViewSkin.java @@ -645,7 +645,7 @@ public LocalDate getLoadEndDate() { @Override public ZoneId getZoneId() { - return ZoneId.systemDefault(); + return getSkinnable().getZoneId(); } @Override diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/util/Util.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/util/Util.java index cda1b239..db178914 100644 --- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/util/Util.java +++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/util/Util.java @@ -74,14 +74,24 @@ public final class Util { public static boolean removeChildren(Pane parent, Predicate predicate) { List list = new ArrayList<>(parent.getChildrenUnmodifiable().stream().filter(predicate.negate()).collect(Collectors.toList())); boolean childrenWereRemoved = list.removeIf(predicate); - parent.getChildren().setAll(list); + if (list.isEmpty()) { + parent.getChildren().clear(); + } else { + parent.getChildren().setAll(list); + } return childrenWereRemoved; } public static boolean removeChildren(Group group, Predicate predicate) { - List list = new ArrayList<>(group.getChildrenUnmodifiable()); + List list = new ArrayList<>(group.getChildren()); boolean childrenWereRemoved = list.removeIf(predicate); - group.getChildren().setAll(list); + if (list.isEmpty()) { + if (!group.getChildren().isEmpty()) { + group.getChildren().clear(); + } + } else { + group.getChildren().setAll(list); + } return childrenWereRemoved; } diff --git a/CalendarFXView/src/main/resources/com/calendarfx/view/calendar.css b/CalendarFXView/src/main/resources/com/calendarfx/view/calendar.css index 050b2aa4..3536a60d 100644 --- a/CalendarFXView/src/main/resources/com/calendarfx/view/calendar.css +++ b/CalendarFXView/src/main/resources/com/calendarfx/view/calendar.css @@ -888,7 +888,6 @@ */ .month-entry-view { - -fx-font-size: .9em; } /* From 8afaf6f882b71d6189be67ea5c46c5ec3e264962 Mon Sep 17 00:00:00 2001 From: Dirk Lemmermann Date: Tue, 11 Oct 2022 15:11:14 +0200 Subject: [PATCH 3/7] Fixed spelling error. --- CalendarFXView/src/main/java/com/calendarfx/model/Calendar.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CalendarFXView/src/main/java/com/calendarfx/model/Calendar.java b/CalendarFXView/src/main/java/com/calendarfx/model/Calendar.java index 9b31932f..b2709354 100644 --- a/CalendarFXView/src/main/java/com/calendarfx/model/Calendar.java +++ b/CalendarFXView/src/main/java/com/calendarfx/model/Calendar.java @@ -856,7 +856,7 @@ public final void removeEventHandler(EventHandler 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); From 723b4ca51e279af7da90316c4fa5526a569e3b9b Mon Sep 17 00:00:00 2001 From: Dirk Lemmermann Date: Tue, 11 Oct 2022 15:18:55 +0200 Subject: [PATCH 4/7] Updated the documentation. Included section on ResourcesView. Applied different styling to code fragments in docs. --- CalendarFXView/pom.xml | 3 +- docs/index.html | 525 ++++++++++-------- .../resources-view-availability.png | Bin 0 -> 479083 bytes .../resources-view-dates-over-resources.png | Bin 0 -> 513918 bytes .../resources-view-resources-over-dates.png | Bin 0 -> 520227 bytes 5 files changed, 285 insertions(+), 243 deletions(-) create mode 100644 docs/manual-images/resources-view-availability.png create mode 100644 docs/manual-images/resources-view-dates-over-resources.png create mode 100644 docs/manual-images/resources-view-resources-over-dates.png diff --git a/CalendarFXView/pom.xml b/CalendarFXView/pom.xml index 38f02b8f..055bd5c7 100644 --- a/CalendarFXView/pom.xml +++ b/CalendarFXView/pom.xml @@ -73,9 +73,8 @@ - coderay html - manual-images + index.html diff --git a/docs/index.html b/docs/index.html index e5d624fa..ed5ac16e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,29 +2,26 @@ - + - + CalendarFX Developer Manual