diff --git a/CalendarFXApp/src/main/java/com/calendarfx/app/CalendarApp.java b/CalendarFXApp/src/main/java/com/calendarfx/app/CalendarApp.java
index 657a0318..5150c412 100644
--- a/CalendarFXApp/src/main/java/com/calendarfx/app/CalendarApp.java
+++ b/CalendarFXApp/src/main/java/com/calendarfx/app/CalendarApp.java
@@ -95,6 +95,7 @@ public void run() {
updateTimeThread.start();
Scene scene = new Scene(stackPane);
+ scene.focusOwnerProperty().addListener(it -> System.out.println("focus owner: " + scene.getFocusOwner()));
CSSFX.start(scene);
primaryStage.setTitle("Calendar");
diff --git a/CalendarFXGoogle/src/main/java/com/calendarfx/google/model/GoogleEntry.java b/CalendarFXGoogle/src/main/java/com/calendarfx/google/model/GoogleEntry.java
index d5d954d4..68bb1a4a 100644
--- a/CalendarFXGoogle/src/main/java/com/calendarfx/google/model/GoogleEntry.java
+++ b/CalendarFXGoogle/src/main/java/com/calendarfx/google/model/GoogleEntry.java
@@ -19,7 +19,6 @@
import com.calendarfx.model.Entry;
import com.google.api.services.calendar.model.Event;
import com.google.api.services.calendar.model.EventAttendee;
-import impl.com.calendarfx.view.util.Util;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
@@ -30,6 +29,8 @@
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
+import java.util.Objects;
+
/**
* Custom entry representing a google event. This contains all the required
* information for a single event of google calendar.
@@ -83,7 +84,7 @@ public final void setStatus(Status status) {
public void set(boolean newValue) {
boolean oldValue = get();
- if (!Util.equals(oldValue, newValue)) {
+ if (!Objects.equals(oldValue, newValue)) {
super.set(newValue);
if (getCalendar() instanceof GoogleCalendar) {
@@ -119,7 +120,7 @@ public final void setAttendeesCanModify(boolean attendeesCanModify) {
public void set(boolean newValue) {
boolean oldValue = get();
- if (!Util.equals(oldValue, newValue)) {
+ if (!Objects.equals(oldValue, newValue)) {
super.set(newValue);
if (getCalendar() instanceof GoogleCalendar) {
@@ -151,7 +152,7 @@ public final void setAttendeesCanInviteOthers(
public void set(boolean newValue) {
boolean oldValue = get();
- if (!Util.equals(oldValue, newValue)) {
+ if (!Objects.equals(oldValue, newValue)) {
super.set(newValue);
if (getCalendar() instanceof GoogleCalendar) {
diff --git a/CalendarFXView/src/main/java/com/calendarfx/model/Entry.java b/CalendarFXView/src/main/java/com/calendarfx/model/Entry.java
index d27d1ead..6597520a 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/model/Entry.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/model/Entry.java
@@ -227,7 +227,7 @@ public void set(Interval newInterval) {
Interval oldInterval = getValue();
- if (!Util.equals(newInterval, oldInterval)) {
+ if (!Objects.equals(newInterval, oldInterval)) {
Calendar calendar = getCalendar();
@@ -672,7 +672,7 @@ public final StringProperty recurrenceRuleProperty() {
public void set(String newRecurrence) {
String oldRecurrence = get();
- if (!Util.equals(oldRecurrence, newRecurrence)) {
+ if (!Objects.equals(oldRecurrence, newRecurrence)) {
Calendar calendar = getCalendar();
@@ -839,7 +839,7 @@ public final String getId() {
public void set(Calendar newCalendar) {
Calendar oldCalendar = get();
- if (!Util.equals(oldCalendar, newCalendar)) {
+ if (!Objects.equals(oldCalendar, newCalendar)) {
if (oldCalendar != null) {
if (!isRecurrence()) {
@@ -1004,7 +1004,7 @@ public final ZoneId getZoneId() {
public void set(String newTitle) {
String oldTitle = get();
- if (!Util.equals(oldTitle, newTitle)) {
+ if (!Objects.equals(oldTitle, newTitle)) {
super.set(newTitle);
Calendar calendar = getCalendar();
@@ -1062,7 +1062,7 @@ public final StringProperty locationProperty() {
public void set(String newLocation) {
String oldLocation = get();
- if (!Util.equals(oldLocation, newLocation)) {
+ if (!Objects.equals(oldLocation, newLocation)) {
super.set(newLocation);
@@ -1215,7 +1215,7 @@ public final LocalTime getEndTime() {
public void set(boolean newFullDay) {
boolean oldFullDay = get();
- if (!Util.equals(oldFullDay, newFullDay)) {
+ if (!Objects.equals(oldFullDay, newFullDay)) {
super.set(newFullDay);
@@ -1508,7 +1508,7 @@ public final BooleanProperty hiddenProperty() {
}
/**
- * An entry can be made explicityl hidden.
+ * An entry can be made explicitly hidden.
*
* @param hidden true if the entry should not be visible in the calendar
*/
diff --git a/CalendarFXView/src/main/java/com/calendarfx/util/Util.java b/CalendarFXView/src/main/java/com/calendarfx/util/Util.java
deleted file mode 100644
index 6be47fa1..00000000
--- a/CalendarFXView/src/main/java/com/calendarfx/util/Util.java
+++ /dev/null
@@ -1,363 +0,0 @@
-/*
- * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.calendarfx.util;
-
-import com.calendarfx.view.DateControl;
-import com.calendarfx.view.Messages;
-import javafx.beans.InvalidationListener;
-import javafx.beans.Observable;
-import javafx.beans.WeakListener;
-import javafx.beans.property.Property;
-import javafx.geometry.Orientation;
-import javafx.scene.Node;
-import javafx.scene.Parent;
-import javafx.scene.control.ScrollBar;
-import net.fortuna.ical4j.model.Recur;
-import net.fortuna.ical4j.model.WeekDay;
-import net.fortuna.ical4j.transform.recurrence.Frequency;
-
-import java.lang.ref.WeakReference;
-import java.text.MessageFormat;
-import java.time.DayOfWeek;
-import java.time.LocalDate;
-import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeParseException;
-import java.time.format.FormatStyle;
-import java.util.List;
-import java.util.Objects;
-
-import static java.time.temporal.ChronoField.DAY_OF_WEEK;
-
-/**
- * Utility methods.
- */
-public class Util {
-
- /**
- * An interface used for converting an object of one type to an object
- * of another type.
- *
- * @param the first (left) type
- * @param the second (right) type
- */
- public interface Converter {
-
- L toLeft(R right);
-
- R toRight(L left);
- }
-
- /**
- * Converts the given recurrence rule (according to RFC 2445) into a human
- * readable text, e.g. "RRULE:FREQ=DAILY;" becomes "Every day".
- *
- * @param rrule the rule
- * @param startDate the start date for the rule
- * @return a nice text describing the rule
- */
- public static String convertRFC2445ToText(String rrule,
- LocalDate startDate) {
-
- try {
- Recur rule = new Recur<>(rrule.replaceFirst("^RRULE:", ""));
- StringBuilder sb = new StringBuilder();
-
- String granularity;
- String granularities;
-
- switch (rule.getFrequency()) {
- case DAILY:
- granularity = Messages.getString("Util.DAY");
- granularities = Messages.getString("Util.DAYS");
- break;
- case MONTHLY:
- granularity = Messages.getString("Util.MONTH");
- granularities = Messages.getString("Util.MONTHS");
- break;
- case WEEKLY:
- granularity = Messages.getString("Util.WEEK");
- granularities = Messages.getString("Util.WEEKS");
- break;
- case YEARLY:
- granularity = Messages.getString("Util.YEAR");
- granularities = Messages.getString("Util.YEARS");
- break;
- case HOURLY:
- granularity = Messages.getString("Util.HOUR");
- granularities = Messages.getString("Util.HOURS");
- break;
- case MINUTELY:
- granularity = Messages.getString("Util.MINUTE");
- granularities = Messages.getString("Util.MINUTES");
- break;
- case SECONDLY:
- granularity = Messages.getString("Util.SECOND");
- granularities = Messages.getString("Util.SECONDS");
- break;
- default:
- granularity = "";
- granularities = "";
- }
-
- int interval = rule.getInterval();
- if (interval > 1) {
- sb.append(MessageFormat.format(Messages.getString("Util.EVERY_PLURAL"), rule.getInterval(), granularities));
- } else {
- sb.append(MessageFormat.format(Messages.getString("Util.EVERY_SINGULAR"), granularity));
- }
-
- /*
- * Weekdays
- */
-
- if (rule.getFrequency().equals(Frequency.WEEKLY)) {
- List byDay = rule.getDayList();
- if (!byDay.isEmpty()) {
- sb.append(Messages.getString("Util.ON_WEEKDAY"));
- for (int i = 0; i < byDay.size(); i++) {
- WeekDay num = byDay.get(i);
- sb.append(makeHuman(num.getDay()));
- if (i < byDay.size() - 1) {
- sb.append(", ");
- }
- }
- }
- }
-
- if (rule.getFrequency().equals(Frequency.MONTHLY)) {
-
- if (!rule.getMonthDayList().isEmpty()) {
-
- int day = rule.getMonthDayList().get(0);
- sb.append(Messages.getString("Util.ON_MONTH_DAY"));
- sb.append(day);
-
- } else if (!rule.getDayList().isEmpty()) {
-
- /*
- * We only support one day.
- */
- WeekDay num = rule.getDayList().get(0);
- sb.append(MessageFormat.format(Messages.getString("Util.ON_MONTH_WEEKDAY"), makeHuman(num.getOffset()), makeHuman(num.getDay())));
- }
- }
-
- if (rule.getFrequency().equals(Frequency.YEARLY)) {
- sb.append(MessageFormat.format(Messages.getString("Util.ON_DATE"), DateTimeFormatter.ofPattern(Messages.getString("Util.MONTH_AND_DAY_FORMAT")).format(startDate)));
- }
-
- int count = rule.getCount();
- if (count > 0) {
- if (count == 1) {
- return Messages.getString("Util.ONCE");
- } else {
- sb.append(MessageFormat.format(Messages.getString("Util.TIMES"), count));
- }
- } else {
- LocalDate until = rule.getUntil();
- if (until != null) {
- sb.append(MessageFormat.format(Messages.getString("Util.UNTIL_DATE"), DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).format(until)));
- }
- }
-
- return sb.toString();
- } catch (IllegalArgumentException | DateTimeParseException e) {
- e.printStackTrace();
- return Messages.getString("Util.INVALID_RULE");
- }
- }
-
- private static String makeHuman(WeekDay.Day wday) {
- switch (wday) {
- case FR:
- return Messages.getString("Util.FRIDAY");
- case MO:
- return Messages.getString("Util.MONDAY");
- case SA:
- return Messages.getString("Util.SATURDAY");
- case SU:
- return Messages.getString("Util.SUNDAY");
- case TH:
- return Messages.getString("Util.THURSDAY");
- case TU:
- return Messages.getString("Util.TUESDAY");
- case WE:
- return Messages.getString("Util.WEDNESDAY");
- default:
- throw new IllegalArgumentException("unknown weekday: " + wday);
- }
- }
-
- private static String makeHuman(int num) {
- switch (num) {
- case 1:
- return Messages.getString("Util.FIRST");
- case 2:
- return Messages.getString("Util.SECOND");
- case 3:
- return Messages.getString("Util.THIRD");
- case 4:
- return Messages.getString("Util.FOURTH");
- case 5:
- return Messages.getString("Util.FIFTH");
- default:
- return Integer.toString(num);
- }
- }
-
- /**
- * Searches for a {@link ScrollBar} of the given orientation (vertical, horizontal)
- * somewhere in the containment hierarchy of the given parent node.
- *
- * @param parent the parent node
- * @param orientation the orientation (horizontal, vertical)
- * @return a scrollbar or null if none can be found
- */
- public static ScrollBar findScrollBar(Parent parent, Orientation orientation) {
- for (Node node : parent.getChildrenUnmodifiable()) {
- if (node instanceof ScrollBar) {
- ScrollBar b = (ScrollBar) node;
- if (b.getOrientation().equals(orientation)) {
- return b;
- }
- }
-
- if (node instanceof Parent) {
- ScrollBar b = findScrollBar((Parent) node, orientation);
- if (b != null) {
- return b;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Adjusts the given date to a new date that marks the beginning of the week where the
- * given date is located. If "Monday" is the first day of the week and the given date
- * is a "Wednesday" then this method will return a date that is two days earlier than the
- * given date.
- *
- * @param date the date to adjust
- * @param firstDayOfWeek the day of week that is considered the start of the week ("Monday" in Germany, "Sunday" in the US)
- * @return the date of the first day of the week
- * @see #adjustToLastDayOfWeek(LocalDate, DayOfWeek)
- * @see DateControl#getFirstDayOfWeek()
- */
- public static LocalDate adjustToFirstDayOfWeek(LocalDate date, DayOfWeek firstDayOfWeek) {
- LocalDate newDate = date.with(DAY_OF_WEEK, firstDayOfWeek.getValue());
- if (newDate.isAfter(date)) {
- newDate = newDate.minusWeeks(1);
- }
-
- return newDate;
- }
-
- /**
- * Adjusts the given date to a new date that marks the end of the week where the
- * given date is located. If "Monday" is the first day of the week and the given date
- * is a "Wednesday" then this method will return a date that is four days later than the
- * given date. This method calculates the first day of the week and then adds six days
- * to it.
- *
- * @param date the date to adjust
- * @param firstDayOfWeek the day of week that is considered the start of the week ("Monday" in Germany, "Sunday" in the US)
- * @return the date of the first day of the week
- * @see #adjustToFirstDayOfWeek(LocalDate, DayOfWeek)
- * @see DateControl#getFirstDayOfWeek()
- */
- public static LocalDate adjustToLastDayOfWeek(LocalDate date, DayOfWeek firstDayOfWeek) {
- LocalDate startOfWeek = adjustToFirstDayOfWeek(date, firstDayOfWeek);
- return startOfWeek.plusDays(6);
- }
-
- /**
- * Creates a bidirectional binding between the two given properties of different types via the
- * help of a {@link Converter}.
- *
- * @param leftProperty the left property
- * @param rightProperty the right property
- * @param converter the converter
- * @param the type of the left property
- * @param the type of the right property
- */
- public static void bindBidirectional(Property leftProperty, Property rightProperty, Converter converter) {
- BidirectionalConversionBinding binding = new BidirectionalConversionBinding<>(leftProperty, rightProperty, converter);
- leftProperty.addListener(binding);
- rightProperty.addListener(binding);
- leftProperty.setValue(converter.toLeft(rightProperty.getValue()));
- }
-
- private static class BidirectionalConversionBinding implements InvalidationListener, WeakListener {
-
- private final WeakReference> leftReference;
- private final WeakReference> rightReference;
- private final Converter converter;
- private boolean updating;
-
- private BidirectionalConversionBinding(Property leftProperty, Property rightProperty, Converter converter) {
- this.leftReference = new WeakReference<>(Objects.requireNonNull(leftProperty));
- this.rightReference = new WeakReference<>(Objects.requireNonNull(rightProperty));
- this.converter = Objects.requireNonNull(converter);
- }
-
- public Property getLeftProperty() {
- return leftReference.get();
- }
-
- public Property getRightProperty() {
- return rightReference.get();
- }
-
- @Override
- public boolean wasGarbageCollected() {
- return getLeftProperty() == null || getRightProperty() == null;
- }
-
- @Override
- public void invalidated(Observable observable) {
- if (updating) {
- return;
- }
-
- final Property leftProperty = getLeftProperty();
- final Property rightProperty = getRightProperty();
-
- if (wasGarbageCollected()) {
- if (leftProperty != null) {
- leftProperty.removeListener(this);
- }
- if (rightProperty != null) {
- rightProperty.removeListener(this);
- }
- } else {
- try {
- updating = true;
-
- if (observable == leftProperty) {
- rightProperty.setValue(converter.toRight(leftProperty.getValue()));
- } else {
- leftProperty.setValue(converter.toLeft(rightProperty.getValue()));
- }
- } finally {
- updating = false;
- }
- }
- }
- }
-}
diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/AllDayView.java b/CalendarFXView/src/main/java/com/calendarfx/view/AllDayView.java
index 21dbe4c2..583b63c3 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/view/AllDayView.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/AllDayView.java
@@ -17,8 +17,8 @@
package com.calendarfx.view;
import com.calendarfx.model.Entry;
-import com.calendarfx.util.Util;
import impl.com.calendarfx.view.AllDayViewSkin;
+import impl.com.calendarfx.view.util.Util;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
@@ -78,7 +78,7 @@ public AllDayView(int numberOfDays) {
getStyleClass().add(ALL_DAY_VIEW);
setNumberOfDays(numberOfDays);
- new CreateDeleteHandler(this);
+ new CreateAndDeleteHandler(this);
}
/**
diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/ContextMenuProvider.java b/CalendarFXView/src/main/java/com/calendarfx/view/ContextMenuProvider.java
index a70616fb..c690a8cd 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/view/ContextMenuProvider.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/ContextMenuProvider.java
@@ -46,8 +46,7 @@
*
* @see DateControl#setContextMenuCallback(Callback)
*/
-public class ContextMenuProvider
- implements Callback {
+public class ContextMenuProvider implements Callback {
@Override
public ContextMenu call(ContextMenuParameter param) {
diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/CreateDeleteHandler.java b/CalendarFXView/src/main/java/com/calendarfx/view/CreateAndDeleteHandler.java
similarity index 64%
rename from CalendarFXView/src/main/java/com/calendarfx/view/CreateDeleteHandler.java
rename to CalendarFXView/src/main/java/com/calendarfx/view/CreateAndDeleteHandler.java
index 84d83b12..04d696f8 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/view/CreateDeleteHandler.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/CreateAndDeleteHandler.java
@@ -21,28 +21,23 @@
import com.calendarfx.util.LoggingDomain;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
-import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import java.time.ZonedDateTime;
import java.util.Optional;
-import static java.util.Objects.requireNonNull;
+class CreateAndDeleteHandler extends DeleteHandler {
-class CreateDeleteHandler {
-
- private final DateControl dateControl;
-
- public CreateDeleteHandler(DateControl control) {
- this.dateControl = requireNonNull(control);
+ public CreateAndDeleteHandler(DateControl control) {
+ super(control);
dateControl.addEventHandler(MouseEvent.MOUSE_CLICKED, this::createEntry);
- dateControl.addEventHandler(KeyEvent.KEY_PRESSED, this::deleteEntries);
}
private void createEntry(MouseEvent evt) {
- if (!(dateControl instanceof DayView) && evt.getButton().equals(MouseButton.PRIMARY) && evt.getClickCount() == dateControl.getCreateEntryClickCount()) {
+ System.out.println("entry click count: " + dateControl.getCreateEntryClickCount());
+ if (evt.getButton().equals(MouseButton.PRIMARY) && evt.getClickCount() == dateControl.getCreateEntryClickCount()) {
if (!evt.isStillSincePress()) {
return;
@@ -86,33 +81,4 @@ private void createEntry(MouseEvent evt) {
evt.consume();
}
}
-
- private void deleteEntries(KeyEvent evt) {
- switch (evt.getCode()) {
- case DELETE:
- case BACK_SPACE:
- for (Entry> entry : dateControl.getSelections()) {
- if (!dateControl.getEntryEditPolicy().call(new DateControl.EntryEditParameter(dateControl, entry, DateControl.EditOperation.DELETE))) {
- continue;
- }
- if (entry.isRecurrence()) {
- entry = entry.getRecurrenceSourceEntry();
- }
- if (!dateControl.getEntryEditPolicy().call(new DateControl.EntryEditParameter(dateControl, entry, DateControl.EditOperation.DELETE))) {
- continue;
- }
-
- Calendar calendar = entry.getCalendar();
- if (calendar != null && !calendar.isReadOnly()) {
- entry.removeFromCalendar();
- }
- }
- dateControl.clearSelection();
- break;
- case F5:
- dateControl.refreshData();
- default:
- break;
- }
- }
}
\ No newline at end of file
diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/DateControl.java b/CalendarFXView/src/main/java/com/calendarfx/view/DateControl.java
index 26976906..02984bcc 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/view/DateControl.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/DateControl.java
@@ -61,6 +61,7 @@
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
+import javafx.stage.Modality;
import javafx.util.Callback;
import org.controlsfx.control.PopOver;
import org.controlsfx.control.PopOver.ArrowLocation;
@@ -76,7 +77,6 @@
import java.time.temporal.ChronoUnit;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -107,7 +107,7 @@
* properties will be bound to each other. This not only includes date and time
* zone properties but also all the factory and detail callbacks. This allows an
* application to create a complex calendar control and to configure only that
- * control without worrying about the date controls that are nested inside of
+ * control without worrying about the date controls that are nested inside
* it. The children will all "inherit" their settings from the parent control.
*
* Current Date, Today, First Day of Week
The {@link #dateProperty()}
@@ -168,8 +168,6 @@ public abstract class DateControl extends CalendarFXControl {
* with default implementations.
*/
protected DateControl() {
- setOnMouseClicked(evt -> requestFocus());
-
setUsagePolicy(count -> {
if (count < 0) {
throw new IllegalArgumentException("usage count can not be smaller than zero, but was " + count);
@@ -217,14 +215,12 @@ protected DateControl() {
*/
setDefaultCalendarProvider(control -> {
List sources = getCalendarSources();
- if (sources != null) {
- for (CalendarSource s : sources) {
- List extends Calendar> calendars = s.getCalendars();
- if (calendars != null && !calendars.isEmpty()) {
- for (Calendar c : calendars) {
- if (!c.isReadOnly() && isCalendarVisible(c)) {
- return c;
- }
+ for (CalendarSource s : sources) {
+ List extends Calendar> calendars = s.getCalendars();
+ if (calendars != null && !calendars.isEmpty()) {
+ for (Calendar c : calendars) {
+ if (!c.isReadOnly() && isCalendarVisible(c)) {
+ return c;
}
}
}
@@ -264,11 +260,11 @@ protected DateControl() {
if (evt instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) evt;
if (mouseEvent.getClickCount() == 2) {
- showEntryDetails(param.getEntry(), param.getOwner(), param.getScreenY());
+ showEntryDetails(param.getEntry(), param.getNode(), param.getOwner(), param.getScreenY());
return true;
}
} else {
- showEntryDetails(param.getEntry(), param.getOwner(), param.getScreenY());
+ showEntryDetails(param.getEntry(), param.getNode(), param.getOwner(), param.getScreenY());
return true;
}
@@ -307,7 +303,7 @@ protected DateControl() {
Callback detailsCallback = getEntryDetailsCallback();
if (detailsCallback != null) {
ContextMenuEvent ctxEvent = param.getContextMenuEvent();
- EntryDetailsParameter entryDetailsParam = new EntryDetailsParameter(ctxEvent, DateControl.this, entryView.getEntry(), entryView, ctxEvent.getScreenX(), ctxEvent.getScreenY());
+ EntryDetailsParameter entryDetailsParam = new EntryDetailsParameter(ctxEvent, DateControl.this, entryView.getEntry(), entryView, getScene().getRoot(), ctxEvent.getScreenX(), ctxEvent.getScreenY());
detailsCallback.call(entryDetailsParam);
}
});
@@ -356,7 +352,10 @@ protected DateControl() {
Calendar calendar = entry.getCalendar();
if (!calendar.isReadOnly()) {
if (entry.isRecurrence()) {
- entry.getRecurrenceSourceEntry().removeFromCalendar();
+ Entry> recurrenceSourceEntry = entry.getRecurrenceSourceEntry();
+ if (recurrenceSourceEntry != null) {
+ recurrenceSourceEntry.removeFromCalendar();
+ }
} else {
entry.removeFromCalendar();
}
@@ -386,7 +385,6 @@ protected DateControl() {
}
if (!usesOwnContextMenu) {
- evt.consume();
Callback callback = getContextMenuCallback();
if (callback != null) {
Callback calendarProvider = getDefaultCalendarProvider();
@@ -399,9 +397,10 @@ protected DateControl() {
ContextMenuParameter param = new ContextMenuParameter(evt, DateControl.this, calendar, time);
ContextMenu menu = callback.call(param);
if (menu != null) {
- setContextMenu(menu);
- menu.show(DateControl.this, evt.getScreenX(), evt.getScreenY());
+ menu.show(getScene().getWindow(), evt.getScreenX(), evt.getScreenY());
}
+
+ evt.consume();
}
}
});
@@ -542,14 +541,14 @@ public final void setDraggedEntry(DraggedEntry entry) {
* note that the time passed to the factory will be adjusted based on the
* current virtual grid settings (see {@link #virtualGridProperty()}).
*
- * @param time the time where the entry will be created (the entry start
+ * @param time the time point where the entry will be created (the entry start
* time)
* @return the new calendar entry or null if no entry could be created
* @see #setEntryFactory(Callback)
* @see #setVirtualGrid(VirtualGrid)
*/
public final Entry> createEntryAt(ZonedDateTime time) {
- return createEntryAt(time, null);
+ return createEntryAt(time, null, false);
}
/**
@@ -570,6 +569,28 @@ public final Entry> createEntryAt(ZonedDateTime time) {
* @see #setVirtualGrid(VirtualGrid)
*/
public final Entry> createEntryAt(ZonedDateTime time, Calendar calendar) {
+ return createEntryAt(time, calendar, false);
+ }
+
+ /**
+ * Creates a new entry at the given time. The method delegates the actual
+ * instance creation to the entry factory (see
+ * {@link #entryFactoryProperty()}). The factory receives a parameter object
+ * that contains the calendar where the entry can be added, however the
+ * factory can choose to add the entry to any calendar it likes. Please note
+ * that the time passed to the factory will be adjusted based on the current
+ * virtual grid settings (see {@link #virtualGridProperty()}).
+ *
+ * @param time the time point where the entry will be created (the entry start
+ * time)
+ * @param calendar the calendar to which the new entry will be added (if null the
+ * default calendar provider will be invoked)
+ * @param initiallyHidden entry will be invisible until the application calls {@link Entry#setHidden(boolean)}.
+ * @return the new calendar entry or null if no entry could be created
+ * @see #setEntryFactory(Callback)
+ * @see #setVirtualGrid(VirtualGrid)
+ */
+ public final Entry> createEntryAt(ZonedDateTime time, Calendar calendar, boolean initiallyHidden) {
requireNonNull(time);
VirtualGrid grid = getVirtualGrid();
if (grid != null) {
@@ -597,6 +618,7 @@ public final Entry> createEntryAt(ZonedDateTime time, Calendar calendar) {
* entry would not be shown to the user.
*/
setCalendarVisibility(calendar, true);
+
CreateEntryParameter param = new CreateEntryParameter(this, calendar, time);
Callback> factory = getEntryFactory();
Entry> entry = factory.call(param);
@@ -607,6 +629,7 @@ public final Entry> createEntryAt(ZonedDateTime time, Calendar calendar) {
* at the given location.
*/
if (entry != null) {
+ entry.setHidden(initiallyHidden);
entry.setCalendar(calendar);
}
@@ -615,6 +638,8 @@ public final Entry> createEntryAt(ZonedDateTime time, Calendar calendar) {
} else {
Alert alert = new Alert(AlertType.WARNING);
+ alert.initOwner(this.getScene().getWindow());
+ alert.initModality(Modality.WINDOW_MODAL);
alert.setTitle(Messages.getString("DateControl.TITLE_CALENDAR_PROBLEM"));
alert.setHeaderText(Messages.getString("DateControl.HEADER_TEXT_UNABLE_TO_CREATE_NEW_ENTRY"));
String newLine = System.getProperty("line.separator");
@@ -668,7 +693,7 @@ public final void editEntry(Entry> entry) {
* becomes visible and brings up the detail editor / UI for the entry
* (default is a popover).
*
- * @param entry the entry to show
+ * @param entry the entry to show
* @param changeDate change the date of the control to the entry's start date
*/
public final void editEntry(Entry> entry, boolean changeDate) {
@@ -717,7 +742,7 @@ private void doEditEntry(Entry> entry) {
Point2D location = entryView.localToScreen(0, 0);
Callback callback = getEntryDetailsCallback();
- EntryDetailsParameter param = new EntryDetailsParameter(null, this, entry, entryView, location.getX(), location.getY());
+ EntryDetailsParameter param = new EntryDetailsParameter(null, this, entry, entryView, getScene().getRoot(), location.getX(), location.getY());
callback.call(param);
}
});
@@ -733,7 +758,7 @@ private void doBounceEntry(Entry> entry) {
private PopOver entryPopOver;
- private void showEntryDetails(Entry> entry, Node owner, double screenY) {
+ private void showEntryDetails(Entry> entry, Node node, Node owner, double screenY) {
Callback contentCallback = getEntryDetailsPopOverContentCallback();
if (contentCallback == null) {
throw new IllegalStateException("No content callback found for entry popover");
@@ -741,7 +766,7 @@ private void showEntryDetails(Entry> entry, Node owner, double screenY) {
if (entryPopOver == null || entryPopOver.isDetached()) {
entryPopOver = new PopOver();
- entryPopOver.setAnimated(false); // important, otherwise too many side-effects
+ entryPopOver.setAnimated(false); // important, otherwise too many side effects
}
EntryDetailsPopOverContentParameter param = new EntryDetailsPopOverContentParameter(entryPopOver, this, owner, entry);
@@ -753,16 +778,16 @@ private void showEntryDetails(Entry> entry, Node owner, double screenY) {
entryPopOver.setContentNode(content);
- ArrowLocation location = ViewHelper.findPopOverArrowLocation(owner);
+ ArrowLocation location = ViewHelper.findPopOverArrowLocation(node);
if (location == null) {
location = ArrowLocation.TOP_LEFT;
}
entryPopOver.setArrowLocation(location);
- Point2D position = ViewHelper.findPopOverArrowPosition(owner, screenY, entryPopOver.getArrowSize(), location);
+ Point2D position = ViewHelper.findPopOverArrowPosition(node, screenY, entryPopOver.getArrowSize(), location);
- entryPopOver.show(owner, position.getX(), position.getY());
+ entryPopOver.show(node, position.getX(), position.getY());
}
/**
@@ -799,7 +824,7 @@ public DateControl getDateControl() {
/**
* The parameter object passed to the entry factory. It contains the most
* important parameters for creating a new entry: the requesting date
- * control, the time where the user performed a double click and the default
+ * control, the time point where the user performed a double click and the default
* calendar.
*
* @see DateControl#entryFactoryProperty()
@@ -873,8 +898,6 @@ public String toString() {
*
* The code below shows the default entry factory that is set on every date
* control.
- *
- *
*
* setEntryFactory(param -> {
* DateControl control = param.getControl();
@@ -969,7 +992,7 @@ public DateControl getDateControl() {
* account.
*
* Code Example
The code below shows the default implementation of
- * this factory. Applications can choose to bring up a full featured user
+ * this factory. Applications can choose to bring up a full-featured user
* interface / dialog to specify the exact location of the source (either
* locally or over a network). A local calendar source might read its data
* from an XML file while a remote source could load data from a web
@@ -1046,7 +1069,7 @@ public EntryViewBase> getEntryView() {
}
/**
- * Convenience method to easily lookup the entry for which the view was
+ * Convenience method to easily look up the entry for which the view was
* created.
*
* @return the calendar entry
@@ -1056,7 +1079,7 @@ public Entry> getEntry() {
}
/**
- * Convenience method to easily lookup the calendar of the entry for
+ * Convenience method to easily look up the calendar of the entry for
* which the view was created.
*
* @return the calendar
@@ -1172,7 +1195,7 @@ public String toString() {
/**
* A property that stores a callback used for editing entries. If an edit operation will be executed
- * on an entry then the callback will be invoked to determine if the operation is allowed. By default
+ * on an entry then the callback will be invoked to determine if the operation is allowed. By default,
* all operations listed inside {@link EditOperation} are allowed.
*
* @return the property
@@ -1211,8 +1234,6 @@ public final void setEntryEditPolicy(Callback polic
*
* Code Example
The code below shows the default implementation of
* this callback.
- *
- *
*
* setEntryContextMenuCallback(param -> {
* EntryViewBase<?> entryView = param.getEntryView();
@@ -1290,7 +1311,7 @@ public static final class ContextMenuParameter extends ContextMenuParameterBase
* @param dateControl the date control where the event occurred
* @param calendar the (default) calendar where newly created entries should
* be added (can be null if no editable calendar was found)
- * @param time the time where the mouse click occurred
+ * @param time the time point where the mouse click occurred
*/
public ContextMenuParameter(ContextMenuEvent evt, DateControl dateControl, Calendar calendar, ZonedDateTime time) {
super(evt, dateControl);
@@ -1310,7 +1331,7 @@ public Calendar getCalendar() {
}
/**
- * The time where the mouse click occurred.
+ * The time point where the mouse click occurred.
*
* @return the time shown at the mouse click location
*/
@@ -1336,8 +1357,6 @@ public String toString() {
* Code Example
*
* The code below shows a part of the default implementation:
- *
- *
*
* setContextMenuCallback(param -> {
* ContextMenu menu = new ContextMenu();
@@ -1382,7 +1401,7 @@ public final void setContextMenuCallback(Callback
* setDefaultCalendarProvider(control -> {
* List<CalendarSource> sources = getCalendarSources();
@@ -1459,6 +1476,7 @@ private abstract static class DetailsParameter {
private final Node owner;
private final double screenX;
private final double screenY;
+ private final Node node;
/**
* Constructs a new parameter object.
@@ -1472,9 +1490,10 @@ private abstract static class DetailsParameter {
* @param screenX the screen location where the event occurred
* @param screenY the screen location where the event occurred
*/
- public DetailsParameter(InputEvent inputEvent, DateControl control, Node owner, double screenX, double screenY) {
+ public DetailsParameter(InputEvent inputEvent, DateControl control, Node node, Node owner, double screenX, double screenY) {
this.inputEvent = inputEvent;
this.dateControl = requireNonNull(control);
+ this.node = requireNonNull(node);
this.owner = requireNonNull(owner);
this.screenX = screenX;
this.screenY = screenY;
@@ -1484,14 +1503,25 @@ public DetailsParameter(InputEvent inputEvent, DateControl control, Node owner,
* Returns the node that should be used as the owner of a dialog /
* popover. We should not use the entry view as the owner of a dialog /
* popover because views come and go. We need something that lives
- * longer.
+ * longer. A good candidate will be the root node of the scene.
*
- * @return an owner node for the details dialog / popover
+ * @return an owner node for the detail dialog / popover
*/
public Node getOwner() {
return owner;
}
+ /**
+ * Returns the node that will be used for calculating the position
+ * of the detail dialog / popover. Popovers will point an arrow at the
+ * given node.
+ *
+ * @return the annotated node
+ */
+ public Node getNode() {
+ return node;
+ }
+
/**
* The screen X location where the event occurred.
*
@@ -1547,13 +1577,14 @@ public final static class EntryDetailsParameter extends DetailsParameter {
* selection)
* @param control the control where the event occurred
* @param entry the entry for which details are requested
+ * @param node the node to which the popover will be placed relative too (when using popovers)
* @param owner a node that can be used as an owner for the dialog or
* popover
* @param screenX the screen location where the event occurred
* @param screenY the screen location where the event occurred
*/
- public EntryDetailsParameter(InputEvent inputEvent, DateControl control, Entry> entry, Node owner, double screenX, double screenY) {
- super(inputEvent, control, owner, screenX, screenY);
+ public EntryDetailsParameter(InputEvent inputEvent, DateControl control, Entry> entry, Node node, Node owner, double screenX, double screenY) {
+ super(inputEvent, control, node, owner, screenX, screenY);
this.entry = entry;
}
@@ -1584,13 +1615,13 @@ public final static class DateDetailsParameter extends DetailsParameter {
* selection)
* @param control the control where the event occurred
* @param date the date for which details are required
- * @param owner a node that can be used as an owner for the dialog or
- * popover
+ * @param node the annotated node (popover will point at it with an arrow)
+ * @param owner a node that can be used as an owner for the dialog or popover
* @param screenX the screen location where the event occurred
* @param screenY the screen location where the event occurred
*/
- public DateDetailsParameter(InputEvent inputEvent, DateControl control, Node owner, LocalDate date, double screenX, double screenY) {
- super(inputEvent, control, owner, screenX, screenY);
+ public DateDetailsParameter(InputEvent inputEvent, DateControl control, Node node, Node owner, LocalDate date, double screenX, double screenY) {
+ super(inputEvent, control, node, owner, screenX, screenY);
this.localDate = requireNonNull(date);
}
@@ -1732,7 +1763,7 @@ public final static class EntryDetailsPopOverContentParameter {
/**
* Constructs a new parameter object.
*
- * @param popOver the pop over for which details will be created
+ * @param popOver the popover for which details will be created
* @param control the control where the event occurred
* @param node the node where the event occurred
* @param entry the entry for which details will be shown
@@ -1849,9 +1880,8 @@ public final LocalDate getToday() {
/**
* A flag used to indicate that the view will mark the area that represents
- * the value of {@link #todayProperty()}. By default this area will be
+ * the value of {@link #todayProperty()}. By default, this area will be
* filled with a different color (red) than the rest (white).
- *
*
*
* @return true if today will be shown differently
@@ -2125,7 +2155,7 @@ public final WeekFields getWeekFields() {
}
/**
- * A convenience method to lookup the first day of the week ("Monday" in
+ * A convenience method to look up the first day of the week ("Monday" in
* Germany, "Sunday" in the US). This method delegates to
* {@link WeekFields#getFirstDayOfWeek()}.
*
@@ -2143,7 +2173,7 @@ public final DayOfWeek getFirstDayOfWeek() {
* currently attached to this date control. This is a convenience list that
* "flattens" the two level structure of sources and their calendars. It is
* a read-only list because calendars can not be added directly to a date
- * control. Instead they are added to calendar sources and those sources are
+ * control. Instead, they are added to calendar sources and those sources are
* then added to the control.
*
* @return the list of all calendars shown by this control
@@ -2341,7 +2371,7 @@ public enum Layout {
private final ObjectProperty layout = new SimpleObjectProperty<>(this, "layout", Layout.STANDARD);
/**
- * Stores the strategy used by the view to layout the entries of several
+ * Stores the strategy used by the view to lay out the entries of several
* calendars at once. The standard layout ignores the source calendar of an
* entry and finds the next available place in the UI that satisfies the
* time bounds of the entry. The {@link Layout#SWIMLANE} strategy allocates
@@ -2471,9 +2501,7 @@ public final WeakList getBoundDateControls() {
*/
public final void unbindAll() {
WeakList controls = getBoundDateControls();
- Iterator iterator = controls.iterator();
- while (iterator.hasNext()) {
- DateControl next = iterator.next();
+ for (DateControl next : controls) {
unbind(next);
}
}
@@ -2483,7 +2511,7 @@ public final void unbindAll() {
/**
* A property used to control whether the control allows the user to click on it or an element
- * inside of it in order to "jump" to another screen with more detail. Example: in the {@link CalendarView}
+ * inside it in order to "jump" to another screen with more detail. Example: in the {@link CalendarView}
* the user can click on the "day of month" label of a cell inside the {@link MonthSheetView} in
* order to switch to the {@link DayPage} where the user will see all entries scheduled for that day.
*
@@ -3272,7 +3300,7 @@ public String getCategory() {
items.add(new Item() {
@Override
- public Optional> getObservableValue() {
+ public Optional> getObservableValue() {
return Optional.empty();
}
@@ -3313,7 +3341,7 @@ public String getCategory() {
items.add(new Item() {
@Override
- public Optional> getObservableValue() {
+ public Optional> getObservableValue() {
return Optional.empty();
}
diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/DayView.java b/CalendarFXView/src/main/java/com/calendarfx/view/DayView.java
index fb057519..0136330e 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/view/DayView.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/DayView.java
@@ -70,7 +70,7 @@ public DayView() {
setEntryViewFactory(DayEntryView::new);
- new CreateDeleteHandler(this);
+ new DeleteHandler(this);
}
@Override
diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/DayViewBase.java b/CalendarFXView/src/main/java/com/calendarfx/view/DayViewBase.java
index e422664b..d065266b 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/view/DayViewBase.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/DayViewBase.java
@@ -1062,6 +1062,26 @@ public final void setLassoEnd(Instant lassoEnd) {
this.lassoEnd.set(lassoEnd);
}
+ private final BooleanProperty enableStartAndEndTimesFlip = new SimpleBooleanProperty(this, "enableStartAndEndTimesFlip", true);
+
+ public final boolean isEnableStartAndEndTimesFlip() {
+ return enableStartAndEndTimesFlip.get();
+ }
+
+ /**
+ * Determines whether the user can flip the start and the end time of an entry by
+ * dragging either one to the other side of the other one.
+ *
+ * @return whether start and end times can be flipped
+ */
+ public final BooleanProperty enableStartAndEndTimesFlipProperty() {
+ return enableStartAndEndTimesFlip;
+ }
+
+ public final void setEnableStartAndEndTimesFlip(boolean enableStartAndEndTimesFlip) {
+ this.enableStartAndEndTimesFlip.set(enableStartAndEndTimesFlip);
+ }
+
/**
* Invokes {@link DateControl#bind(DateControl, boolean)} and adds some more
* bindings between this control and the given control.
diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/DeleteHandler.java b/CalendarFXView/src/main/java/com/calendarfx/view/DeleteHandler.java
new file mode 100644
index 00000000..925855e0
--- /dev/null
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/DeleteHandler.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.calendarfx.view;
+
+import com.calendarfx.model.Calendar;
+import com.calendarfx.model.Entry;
+import javafx.scene.input.KeyEvent;
+
+import static java.util.Objects.requireNonNull;
+
+class DeleteHandler {
+
+ protected final DateControl dateControl;
+
+ public DeleteHandler(DateControl control) {
+ this.dateControl = requireNonNull(control);
+ dateControl.addEventHandler(KeyEvent.KEY_PRESSED, this::deleteEntries);
+ }
+
+ private void deleteEntries(KeyEvent evt) {
+ switch (evt.getCode()) {
+ case DELETE:
+ case BACK_SPACE:
+ for (Entry> entry : dateControl.getSelections()) {
+ if (!dateControl.getEntryEditPolicy().call(new DateControl.EntryEditParameter(dateControl, entry, DateControl.EditOperation.DELETE))) {
+ continue;
+ }
+ if (entry.isRecurrence()) {
+ entry = entry.getRecurrenceSourceEntry();
+ }
+ if (!dateControl.getEntryEditPolicy().call(new DateControl.EntryEditParameter(dateControl, entry, DateControl.EditOperation.DELETE))) {
+ continue;
+ }
+
+ Calendar calendar = entry.getCalendar();
+ if (calendar != null && !calendar.isReadOnly()) {
+ entry.removeFromCalendar();
+ }
+ }
+ dateControl.clearSelection();
+ break;
+ case F5:
+ dateControl.refreshData();
+ default:
+ break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/EntryViewBase.java b/CalendarFXView/src/main/java/com/calendarfx/view/EntryViewBase.java
index 5c5c77b2..891e84b7 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/view/EntryViewBase.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/EntryViewBase.java
@@ -116,6 +116,10 @@ public abstract class EntryViewBase extends CalendarFXCon
private final WeakListChangeListener super String> weakStyleListener = new WeakListChangeListener<>(styleListener);
+ private final InvalidationListener bindVisibilityListener = it -> bindVisibility();
+
+ private final WeakInvalidationListener weakBindVisibilityListener = new WeakInvalidationListener(bindVisibilityListener);
+
/**
* Constructs a new view for the given entry.
*
@@ -135,6 +139,7 @@ protected EntryViewBase(Entry> entry) {
if (evt.getButton().equals(PRIMARY) && evt.isStillSincePress() && evt.getClickCount() == getDetailsClickCount()) {
showDetails(evt, evt.getScreenX(), evt.getScreenY());
}
+ evt.consume();
});
addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, evt -> {
@@ -149,12 +154,11 @@ protected EntryViewBase(Entry> entry) {
EntryContextMenuParameter param = new EntryContextMenuParameter(evt, dateControl, EntryViewBase.this);
ContextMenu menu = callback.call(param);
if (menu != null) {
- setContextMenu(menu);
- menu.show(this, evt.getScreenX(), evt.getScreenY());
+ menu.show(getScene().getWindow(), evt.getScreenX(), evt.getScreenY());
+ evt.consume();
}
}
}
- evt.consume();
});
@SuppressWarnings("unchecked")
@@ -228,9 +232,12 @@ protected EntryViewBase(Entry> entry) {
addEventHandler(MouseEvent.MOUSE_PRESSED, this::performSelection);
- bindEntry(entry);
+ bindEntry();
+ bindVisibility();
- layerProperty().addListener(weakLayerListener);
+ layerProperty().addListener(weakBindVisibilityListener);
+
+ visibleProperty().addListener(it -> System.out.println("Entry: " + entry.getTitle() + ", visible = " + isVisible()));
}
private final IntegerProperty detailsClickCount = new SimpleIntegerProperty(this, "detailsClickCount", 2);
@@ -240,12 +247,11 @@ public final int getDetailsClickCount() {
}
/**
- * Determins the click count that is required to trigger the
+ * Determines the click count that is required to trigger the
* "show details" action.
*
- * @see DateControl#entryDetailsCallbackProperty()
- *
* @return the "show details" click count
+ * @see DateControl#entryDetailsCallbackProperty()
*/
public final IntegerProperty detailsClickCountProperty() {
return detailsClickCount;
@@ -264,15 +270,7 @@ public final Entry> getEntry() {
return entry;
}
- private final InvalidationListener calendarListener = it -> bindVisibility();
-
- private final WeakInvalidationListener weakCalendarListener = new WeakInvalidationListener(calendarListener);
-
- private final InvalidationListener layerListener = it -> bindVisibility();
-
- private final WeakInvalidationListener weakLayerListener = new WeakInvalidationListener(layerListener);
-
- private void bindEntry(Entry> entry) {
+ private void bindEntry() {
setStartDate(entry.getStartDate());
setEndDate(entry.getEndDate());
setStartTime(entry.getStartTime());
@@ -286,37 +284,44 @@ private void bindEntry(Entry> entry) {
getProperties().put("selected", true);
}
- entry.calendarProperty().addListener(weakCalendarListener);
+ entry.hiddenProperty().addListener(weakBindVisibilityListener);
+ entry.calendarProperty().addListener(weakBindVisibilityListener);
}
private void bindVisibility() {
Entry> entry = getEntry();
+
T dateControl = getDateControl();
if (entry != null && dateControl != null) {
Calendar calendar = entry.getCalendar();
- if (calendar != null) {
- BooleanBinding binding = Bindings.and(dateControl.getCalendarVisibilityProperty(calendar), Bindings.not(hiddenProperty()));
+ // the entry view can be hidden
+ BooleanBinding binding = Bindings.createBooleanBinding(() -> !isHidden(), hiddenProperty());
- binding = binding.and(entry.hiddenProperty().not());
+ if (calendar != null) {
+ // the calendar can be hidden
+ binding = binding.and(dateControl.getCalendarVisibilityProperty(calendar));
+ }
- if (getLayer() != null) {
- binding = binding.and(Bindings.createBooleanBinding(this::isAssignedLayerVisible, dateControl.visibleLayersProperty()));
- }
+ // the entry itself can also be hidden
+ binding = binding.and(entry.hiddenProperty().not());
- if (dateControl instanceof DayViewBase) {
- /*
- * Day views support editing of an availability calendar. During editing the
- * entries might be shown, hidden, or become somewhat transparent.
- */
- DayViewBase dayView = (DayViewBase) dateControl;
+ if (getLayer() != null) {
+ binding = binding.and(Bindings.createBooleanBinding(this::isAssignedLayerVisible, dateControl.visibleLayersProperty()));
+ }
- binding = binding.and(dayView.editAvailabilityProperty().not().or(dayView.entryViewAvailabilityEditingBehaviourProperty().isEqualTo(AvailabilityEditingEntryBehaviour.HIDE).not()));
- }
+ if (dateControl instanceof DayViewBase) {
+ /*
+ * Day views support editing of an availability calendar. During editing the
+ * entries might be shown, hidden, or become somewhat transparent.
+ */
+ DayViewBase dayView = (DayViewBase) dateControl;
- visibleProperty().bind(binding);
+ binding = binding.and(dayView.editAvailabilityProperty().not().or(dayView.entryViewAvailabilityEditingBehaviourProperty().isEqualTo(AvailabilityEditingEntryBehaviour.HIDE).not()));
}
+
+ visibleProperty().bind(binding);
}
}
@@ -478,7 +483,7 @@ private void showDetails(InputEvent evt, double x, double y) {
*/
if (control != null && getParent() != null) {
Callback callback = control.getEntryDetailsCallback();
- EntryDetailsParameter param = new EntryDetailsParameter(evt, control, getEntry(), this, x, y);
+ EntryDetailsParameter param = new EntryDetailsParameter(evt, control, getEntry(), this, getScene().getRoot(), x, y);
callback.call(param);
}
}
@@ -966,7 +971,8 @@ public final void setLayer(Layer layer) {
* Width percentage is only used for width computation when {@link #prefWidthProperty()}
* of view entry has no defined value and when {@link #alignmentStrategyProperty()}
* is not {@link AlignmentStrategy#FILL}.
- *
+ *
+ *
* @return the entry percentage width
*/
public final DoubleProperty widthPercentageProperty() {
@@ -1053,9 +1059,9 @@ public final void setHeightLayoutStrategy(HeightLayoutStrategy heightLayoutStrat
* require the entry to simply use the preferred width of the view and align the
* entry's view on the left, the center, or the middle.
*
- * If the time intervals of two entries are overlapping then the entries might
- * be placed in two columns. The alignment strategy would then determine the layout
- * of the entry within its column.
+ * If the time intervals of two entries are overlapping then the entries might
+ * be placed in two columns. The alignment strategy would then determine the layout
+ * of the entry within its column.
*
*
* @see #setAlignmentStrategy(AlignmentStrategy)
@@ -1238,6 +1244,8 @@ private void performSelection(MouseEvent evt) {
}
getProperties().remove(disableFocusHandlingKey);
+
+ evt.consume();
}
}
diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/MonthSheetView.java b/CalendarFXView/src/main/java/com/calendarfx/view/MonthSheetView.java
index 9bca9ba8..9dbde35d 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/view/MonthSheetView.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/MonthSheetView.java
@@ -138,15 +138,10 @@ private ContextMenu createContextMenu() {
ContextMenu contextMenu = new ContextMenu();
MenuItem newEntry = new MenuItem(Messages.getString("MonthSheetView.ADD_NEW_EVENT"));
newEntry.setOnAction(evt -> {
-
LocalDate date = getDateSelectionModel().getLastSelected();
- Entry> entry = createEntryAt(ZonedDateTime.of(date, LocalTime.of(12, 0), getZoneId()));
-
- Callback callback = getEntryDetailsCallback();
- EntryDetailsParameter param = new EntryDetailsParameter(null, this, entry, dateCell, ctxMenuScreenX, ctxMenuScreenY);
- callback.call(param);
-
+ createEntryAt(ZonedDateTime.of(date, LocalTime.of(12, 0), getZoneId()));
});
+
contextMenu.getItems().add(newEntry);
contextMenu.getItems().add(new SeparatorMenuItem());
diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/MonthView.java b/CalendarFXView/src/main/java/com/calendarfx/view/MonthView.java
index a70e143a..d2fe31ea 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/view/MonthView.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/MonthView.java
@@ -57,7 +57,7 @@ public MonthView() {
setEntryViewFactory(MonthEntryView::new);
- new CreateDeleteHandler(this);
+ new CreateAndDeleteHandler(this);
getSelectedDates().addListener((Observable it) -> {
if (getSelectedDates().size() == 1) {
diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/RecurrenceView.java b/CalendarFXView/src/main/java/com/calendarfx/view/RecurrenceView.java
index 2ddf1317..06b80b21 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/view/RecurrenceView.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/RecurrenceView.java
@@ -17,7 +17,7 @@
package com.calendarfx.view;
import com.calendarfx.model.Entry;
-import com.calendarfx.util.Util;
+
import impl.com.calendarfx.view.RecurrenceViewSkin;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/WeekDayHeaderView.java b/CalendarFXView/src/main/java/com/calendarfx/view/WeekDayHeaderView.java
index 635ef1e7..ef24ee75 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/view/WeekDayHeaderView.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/WeekDayHeaderView.java
@@ -16,8 +16,8 @@
package com.calendarfx.view;
-import com.calendarfx.util.Util;
import impl.com.calendarfx.view.WeekDayHeaderViewSkin;
+import impl.com.calendarfx.view.util.Util;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
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 8fd86551..6f2ee7b1 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/view/popover/EntryDetailsView.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/popover/EntryDetailsView.java
@@ -17,12 +17,12 @@
package com.calendarfx.view.popover;
import com.calendarfx.model.Entry;
-import com.calendarfx.util.Util;
import com.calendarfx.view.DateControl;
import com.calendarfx.view.Messages;
import com.calendarfx.view.RecurrenceView;
import com.calendarfx.view.TimeField;
import impl.com.calendarfx.view.ZoneIdStringConverter;
+import impl.com.calendarfx.view.util.Util;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.binding.Bindings;
diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/popover/EntryPopOverContentPane.java b/CalendarFXView/src/main/java/com/calendarfx/view/popover/EntryPopOverContentPane.java
index 164b0baf..0a2dedff 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/view/popover/EntryPopOverContentPane.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/popover/EntryPopOverContentPane.java
@@ -19,6 +19,7 @@
import com.calendarfx.model.Entry;
import com.calendarfx.view.CalendarView;
import com.calendarfx.view.DateControl;
+import com.calendarfx.view.DayViewBase;
import com.calendarfx.view.Messages;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
@@ -42,7 +43,7 @@ public class EntryPopOverContentPane extends PopOverContentPane {
private final WeakInvalidationListener weakHideListener = new WeakInvalidationListener(hideListener);
private final InvalidationListener fullDayListener = obs -> {
- if (getEntry().isFullDay() && !getPopOver().isDetached()) {
+ if (getEntry().isFullDay() && !getPopOver().isDetached() && getDateControl() instanceof DayViewBase) {
getPopOver().setDetached(true);
}
};
diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/print/PrintView.java b/CalendarFXView/src/main/java/com/calendarfx/view/print/PrintView.java
index 5e6e89b1..5f35b14b 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/view/print/PrintView.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/print/PrintView.java
@@ -18,12 +18,12 @@
import com.calendarfx.model.CalendarSource;
import com.calendarfx.util.LoggingDomain;
-import com.calendarfx.util.Util;
import com.calendarfx.view.CalendarView;
import com.calendarfx.view.DateControl;
import com.calendarfx.view.Messages;
import com.calendarfx.view.SourceView;
import impl.com.calendarfx.view.print.PrintViewSkin;
+import impl.com.calendarfx.view.util.Util;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
@@ -32,7 +32,13 @@
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
-import javafx.print.*;
+import javafx.print.JobSettings;
+import javafx.print.PageLayout;
+import javafx.print.PageOrientation;
+import javafx.print.Paper;
+import javafx.print.PrintColor;
+import javafx.print.Printer;
+import javafx.print.PrinterJob;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
diff --git a/CalendarFXView/src/main/java/com/calendarfx/view/print/TimeRangeView.java b/CalendarFXView/src/main/java/com/calendarfx/view/print/TimeRangeView.java
index a0907b16..6c965928 100644
--- a/CalendarFXView/src/main/java/com/calendarfx/view/print/TimeRangeView.java
+++ b/CalendarFXView/src/main/java/com/calendarfx/view/print/TimeRangeView.java
@@ -16,10 +16,11 @@
package com.calendarfx.view.print;
-import com.calendarfx.util.Util;
+
import com.calendarfx.view.CalendarView;
import com.calendarfx.view.print.TimeRangeField.TimeRangeFieldValue;
import impl.com.calendarfx.view.print.TimeRangeViewSkin;
+import impl.com.calendarfx.view.util.Util;
import javafx.beans.InvalidationListener;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
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 e2f7e6a4..8379b279 100644
--- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/AgendaViewSkin.java
+++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/AgendaViewSkin.java
@@ -154,13 +154,18 @@ private void updateList(String reason) {
Map>> dataMap = new HashMap<>();
dataLoader.loadEntries(dataMap);
+
List listEntries = new ArrayList<>();
for (LocalDate date : dataMap.keySet()) {
AgendaEntry listViewEntry = new AgendaEntry(date);
for (Entry> entry : dataMap.get(date)) {
- listViewEntry.getEntries().add(entry);
+ if (!entry.isHidden()) {
+ listViewEntry.getEntries().add(entry);
+ }
+ }
+ if (!listViewEntry.getEntries().isEmpty()) {
+ listEntries.add(listViewEntry);
}
- listEntries.add(listViewEntry);
}
Collections.sort(listEntries);
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 e39c0189..77643929 100644
--- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/AllDayViewSkin.java
+++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/AllDayViewSkin.java
@@ -21,13 +21,13 @@
import com.calendarfx.model.CalendarSource;
import com.calendarfx.model.Entry;
import com.calendarfx.util.LoggingDomain;
-import com.calendarfx.util.Util;
import com.calendarfx.view.AllDayEntryView;
import com.calendarfx.view.AllDayView;
import com.calendarfx.view.DraggedEntry;
import com.calendarfx.view.EntryViewBase;
import impl.com.calendarfx.view.util.Placement;
import impl.com.calendarfx.view.util.TimeBoundsResolver;
+import impl.com.calendarfx.view.util.Util;
import javafx.beans.InvalidationListener;
import javafx.geometry.Insets;
import javafx.scene.Node;
diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewEditController.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewEditController.java
index c6e32ae3..e68e647c 100644
--- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewEditController.java
+++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewEditController.java
@@ -19,8 +19,8 @@
import com.calendarfx.model.Calendar;
import com.calendarfx.model.Entry;
import com.calendarfx.util.LoggingDomain;
-import com.calendarfx.view.DateControl;
import com.calendarfx.view.DateControl.EditOperation;
+import com.calendarfx.view.DateControl.EntryEditParameter;
import com.calendarfx.view.DayEntryView;
import com.calendarfx.view.DayView;
import com.calendarfx.view.DayViewBase;
@@ -37,6 +37,7 @@
import javafx.scene.Cursor;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
+import javafx.util.Callback;
import java.time.DayOfWeek;
import java.time.Duration;
@@ -55,6 +56,7 @@ public class DayViewEditController {
private static final Logger LOGGER = LoggingDomain.EDITING;
private boolean dragging;
+ private boolean creating;
private final DayViewBase dayViewBase;
private DayEntryView dayEntryView;
private Entry> entry;
@@ -73,18 +75,18 @@ public DayViewEditController(DayViewBase dayView) {
// mouse released is very important for us. register with the scene, so we get that in any case.
if (dayView.getScene() != null) {
dayView.addEventFilter(MouseEvent.MOUSE_RELEASED, mouseReleasedHandler);
- dayView.addEventFilter(MouseEvent.MOUSE_EXITED, mouseReleasedHandler);
+// dayView.addEventFilter(MouseEvent.MOUSE_EXITED, mouseReleasedHandler);
}
// also register with the scene property. Mostly to remove our event filter if the component gets destroyed.
dayView.sceneProperty().addListener(((observable, oldValue, newValue) -> {
if (oldValue != null) {
oldValue.removeEventFilter(MouseEvent.MOUSE_RELEASED, mouseReleasedHandler);
- oldValue.removeEventFilter(MouseEvent.MOUSE_EXITED, mouseReleasedHandler);
+// oldValue.removeEventFilter(MouseEvent.MOUSE_EXITED, mouseReleasedHandler);
}
if (newValue != null) {
newValue.addEventFilter(MouseEvent.MOUSE_RELEASED, mouseReleasedHandler);
- newValue.addEventFilter(MouseEvent.MOUSE_EXITED, mouseReleasedHandler);
+// newValue.addEventFilter(MouseEvent.MOUSE_EXITED, mouseReleasedHandler);
}
}));
dayView.addEventFilter(MouseEvent.MOUSE_MOVED, this::mouseMoved);
@@ -123,17 +125,17 @@ private void initDragModeAndHandle(MouseEvent evt) {
LOGGER.finer("y-coordinate inside entry view: " + y);
if (y > dayEntryView.getHeight() - 5) {
- if (dayEntryView.getHeightLayoutStrategy().equals(HeightLayoutStrategy.USE_START_AND_END_TIME) && dayViewBase.getEntryEditPolicy().call(new DateControl.EntryEditParameter(dayViewBase, entry, EditOperation.CHANGE_END))) {
+ if (dayEntryView.getHeightLayoutStrategy().equals(HeightLayoutStrategy.USE_START_AND_END_TIME) && dayViewBase.getEntryEditPolicy().call(new EntryEditParameter(dayViewBase, entry, EditOperation.CHANGE_END))) {
dragMode = DraggedEntry.DragMode.END_TIME;
handle = Handle.BOTTOM;
}
} else if (y < 5) {
- if (dayEntryView.getHeightLayoutStrategy().equals(HeightLayoutStrategy.USE_START_AND_END_TIME) && dayViewBase.getEntryEditPolicy().call(new DateControl.EntryEditParameter(dayViewBase, entry, EditOperation.CHANGE_START))) {
+ if (dayEntryView.getHeightLayoutStrategy().equals(HeightLayoutStrategy.USE_START_AND_END_TIME) && dayViewBase.getEntryEditPolicy().call(new EntryEditParameter(dayViewBase, entry, EditOperation.CHANGE_START))) {
dragMode = DraggedEntry.DragMode.START_TIME;
handle = Handle.TOP;
}
} else {
- if (dayViewBase.getEntryEditPolicy().call(new DateControl.EntryEditParameter(dayViewBase, entry, EditOperation.MOVE))) {
+ if (dayViewBase.getEntryEditPolicy().call(new EntryEditParameter(dayViewBase, entry, EditOperation.MOVE))) {
dragMode = DraggedEntry.DragMode.START_AND_END_TIME;
handle = Handle.CENTER;
}
@@ -195,7 +197,12 @@ private void mousePressed(MouseEvent evt) {
LOGGER.finer("mouse event y-coordinate:" + evt.getY());
LOGGER.finer("time: " + dayViewBase.getZonedDateTimeAt(evt.getX(), evt.getY(), dayViewBase.getZoneId()));
+ boolean initiallyHideDraggedEntry = false;
+ creating = false;
+
if (!dayViewBase.isScrollingEnabled() && evt.getTarget() instanceof DayView) {
+ creating = true;
+
Optional calendar = dayViewBase.getCalendarAt(evt.getX(), evt.getY());
Instant instantAt = dayViewBase.getInstantAt(evt);
@@ -205,7 +212,7 @@ private void mousePressed(MouseEvent evt) {
}
ZonedDateTime time = ZonedDateTime.ofInstant(instantAt, dayViewBase.getZoneId());
- entry = dayViewBase.createEntryAt(time, calendar.orElse(null));
+ entry = dayViewBase.createEntryAt(time, calendar.orElse(null), true);
if (virtualGrid != null) {
entry.setInterval(entry.getInterval().withEndTime(entry.getInterval().getStartTime().plus(virtualGrid.getAmount(), virtualGrid.getUnit())));
@@ -228,6 +235,9 @@ private void mousePressed(MouseEvent evt) {
dragging = true;
showEntryDetails = true;
}
+
+ initiallyHideDraggedEntry = true;
+
} else if (evt.getTarget() instanceof EntryViewBase) {
initDragModeAndHandle(evt);
} else {
@@ -237,9 +247,11 @@ private void mousePressed(MouseEvent evt) {
LOGGER.finer("drag mode: " + dragMode);
LOGGER.finer("handle: " + handle);
+ Callback entryEditPolicy = dayViewBase.getEntryEditPolicy();
+
switch (dragMode) {
case START_AND_END_TIME:
- if (dayViewBase.getEntryEditPolicy().call(new DateControl.EntryEditParameter(dayViewBase, entry, EditOperation.MOVE))) {
+ if (entryEditPolicy.call(new EntryEditParameter(dayViewBase, entry, EditOperation.MOVE))) {
dragging = true;
if (dayEntryView != null) {
dayEntryView.getProperties().put("dragged", true);
@@ -257,7 +269,7 @@ private void mousePressed(MouseEvent evt) {
}
break;
case END_TIME:
- if (dayViewBase.getEntryEditPolicy().call(new DateControl.EntryEditParameter(dayViewBase, entry, EditOperation.CHANGE_END))) {
+ if (entryEditPolicy.call(new EntryEditParameter(dayViewBase, entry, EditOperation.CHANGE_END))) {
dragging = true;
if (dayEntryView != null) {
dayEntryView.getProperties().put("dragged-end", true);
@@ -265,7 +277,7 @@ private void mousePressed(MouseEvent evt) {
}
break;
case START_TIME:
- if (dayViewBase.getEntryEditPolicy().call(new DateControl.EntryEditParameter(dayViewBase, entry, EditOperation.CHANGE_START))) {
+ if (entryEditPolicy.call(new EntryEditParameter(dayViewBase, entry, EditOperation.CHANGE_START))) {
dragging = true;
if (dayEntryView != null) {
dayEntryView.getProperties().put("dragged-start", true);
@@ -283,6 +295,7 @@ private void mousePressed(MouseEvent evt) {
if (dayViewBase != null) {
DraggedEntry draggedEntry = new DraggedEntry(entry, dragMode);
draggedEntry.setOffsetDuration(offsetDuration);
+ draggedEntry.setHidden(initiallyHideDraggedEntry);
dayViewBase.setDraggedEntry(draggedEntry);
}
}
@@ -324,15 +337,23 @@ private void mouseReleased(MouseEvent evt) {
DraggedEntry draggedEntry = dayViewBase.getDraggedEntry();
if (draggedEntry != null) {
- entry.setInterval(draggedEntry.getInterval());
- dayViewBase.setDraggedEntry(null);
- if (dayViewBase.isShowDetailsUponEntryCreation() && showEntryDetails) {
- dayViewBase.fireEvent(new RequestEvent(dayViewBase, dayViewBase, entry));
+ if (!draggedEntry.isHidden()) {
+ entry.setInterval(draggedEntry.getInterval());
+ dayViewBase.setDraggedEntry(null);
+ if (dayViewBase.isShowDetailsUponEntryCreation() && showEntryDetails) {
+ dayViewBase.fireEvent(new RequestEvent(dayViewBase, dayViewBase, entry));
+ }
+ } else {
+ entry.removeFromCalendar();
}
}
}
private void mouseDragged(MouseEvent evt) {
+ if (evt.isStillSincePress()) {
+ return;
+ }
+
if (!dayViewBase.isScrollingEnabled()) {
setLassoEnd(snapToGrid(dayViewBase.getInstantAt(evt), dayViewBase.getAvailabilityGrid(), true));
}
@@ -346,6 +367,17 @@ private void mouseDragged(MouseEvent evt) {
return;
}
+ /*
+ * We might run in the sampler application. Then the entry view will not
+ * be inside a date control.
+ */
+ DraggedEntry draggedEntry = dayViewBase.getDraggedEntry();
+
+ if (draggedEntry != null) {
+ draggedEntry.getOriginalEntry().setHidden(false);
+ draggedEntry.setHidden(false);
+ }
+
switch (dragMode) {
case START_TIME:
switch (handle) {
@@ -544,7 +576,8 @@ public final void setLassoEnd(Instant lassoEnd) {
this.lassoEnd.set(lassoEnd);
}
- private final ObjectProperty> onLassoFinished = new SimpleObjectProperty<>(this, "onLassoFinished", (start, end) ->{});
+ private final ObjectProperty> onLassoFinished = new SimpleObjectProperty<>(this, "onLassoFinished", (start, end) -> {
+ });
public final BiConsumer getOnLassoFinished() {
return onLassoFinished.get();
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 49333d67..a96c7d13 100644
--- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewSkin.java
+++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/DayViewSkin.java
@@ -38,6 +38,7 @@
import com.calendarfx.view.VirtualGrid;
import impl.com.calendarfx.view.util.Placement;
import impl.com.calendarfx.view.util.TimeBoundsResolver;
+import impl.com.calendarfx.view.util.Util;
import impl.com.calendarfx.view.util.VisualBoundsResolver;
import javafx.animation.FadeTransition;
import javafx.application.Platform;
@@ -550,11 +551,13 @@ protected void layoutChildrenStatic(double contentX, double contentY, double con
line.setEndY(yy);
}
- // the dragged entry view
- if (draggedEntryView != null) {
- boolean showing = isRelevant(draggedEntryView.getEntry());
- draggedEntryView.setVisible(showing);
- }
+// // the dragged entry view
+// if (draggedEntryView != null) {
+// Entry> draggedEntry = draggedEntryView.getEntry();
+// if (!draggedEntry.isHidden()) {
+// //draggedEntry.setHidden(!isRelevant(draggedEntry));
+// }
+// }
layoutEntries(contentX, contentY, contentWidth, contentHeight);
layoutCurrentTime(contentX, contentY, contentWidth);
@@ -878,7 +881,7 @@ protected void entryIntervalChanged(CalendarEvent evt) {
}
private boolean removeEntryView(Entry> entry) {
- boolean removed = entryViewGroup.getChildren().removeIf(node -> {
+ boolean removed = Util.removeChildren(entryViewGroup, node -> {
DayEntryView view = (DayEntryView) node;
Entry> removedEntry = entry;
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 5d999804..b41b7f76 100644
--- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/MonthSheetViewSkin.java
+++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/MonthSheetViewSkin.java
@@ -21,7 +21,7 @@
import com.calendarfx.model.CalendarSource;
import com.calendarfx.model.Entry;
import com.calendarfx.util.LoggingDomain;
-import com.calendarfx.view.DateControl;
+import com.calendarfx.view.DateControl.DateDetailsParameter;
import com.calendarfx.view.DateSelectionModel;
import com.calendarfx.view.MonthSheetView;
import com.calendarfx.view.MonthSheetView.DateCell;
@@ -409,9 +409,10 @@ public int hashCode() {
private void showDateDetails(LocalDate date) {
DateCell cell = cellMap.get(date);
Bounds bounds = cell.localToScreen(cell.getLayoutBounds());
- Callback callback = getSkinnable().getDateDetailsCallback();
- DateControl.DateDetailsParameter param = new DateControl.DateDetailsParameter(null, getSkinnable(), cell, date, bounds.getMinX(), bounds.getMinY());
- callback.call(param);
+ Callback callback = getSkinnable().getDateDetailsCallback();
+ if (callback != null) {
+ callback.call(new DateDetailsParameter(null, getSkinnable(), cell, cell.getScene().getRoot(), date, bounds.getMinX(), bounds.getMinY()));
+ }
}
private final WeakEventHandler weakCellClickedHandler = new WeakEventHandler<>(cellClickedHandler);
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 44493040..e28640a7 100644
--- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/MonthViewSkin.java
+++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/MonthViewSkin.java
@@ -21,12 +21,13 @@
import com.calendarfx.model.CalendarSource;
import com.calendarfx.model.Entry;
import com.calendarfx.util.LoggingDomain;
-import com.calendarfx.util.Util;
import com.calendarfx.view.EntryViewBase.Position;
import com.calendarfx.view.Messages;
import com.calendarfx.view.MonthEntryView;
import com.calendarfx.view.MonthView;
import com.calendarfx.view.RequestEvent;
+import impl.com.calendarfx.view.util.Util;
+import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
@@ -624,7 +625,8 @@ class MonthDayEntriesPane extends Pane {
this.week = week;
this.day = day;
- entries.addListener((Observable evt) -> update());
+ // since JavaFX 19 this needs to be run later
+ entries.addListener((Observable evt) -> Platform.runLater(() -> update()));
setMinSize(0, 0);
setPrefSize(0, 0);
@@ -654,7 +656,7 @@ public final ObservableList> getEntries() {
}
private void update() {
- getChildren().removeIf(node -> node instanceof MonthEntryView);
+ Util.removeChildren(this, node -> node instanceof MonthEntryView);
if (!entries.isEmpty()) {
diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/RecurrenceViewSkin.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/RecurrenceViewSkin.java
index 8419b9a9..c4bde10a 100644
--- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/RecurrenceViewSkin.java
+++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/RecurrenceViewSkin.java
@@ -17,9 +17,9 @@
package impl.com.calendarfx.view;
import com.calendarfx.util.LoggingDomain;
-import com.calendarfx.util.Util;
import com.calendarfx.view.Messages;
import com.calendarfx.view.RecurrenceView;
+import impl.com.calendarfx.view.util.Util;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.geometry.HPos;
@@ -370,8 +370,7 @@ private void updateView() {
weekDaySaturdayButton.setSelected(isSelected(Day.SA, days));
weekDaySundayButton.setSelected(isSelected(Day.SU, days));
- summary.setText(Util.convertRFC2445ToText(rule,
- getSkinnable().getStartDate()));
+ summary.setText(Util.convertRFC2445ToText(rule, getSkinnable().getStartDate()));
} catch (IllegalArgumentException | DateTimeParseException e) {
e.printStackTrace();
}
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 fb98ed20..2c7bddb2 100644
--- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/YearMonthViewSkin.java
+++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/YearMonthViewSkin.java
@@ -21,12 +21,12 @@
import com.calendarfx.model.CalendarSource;
import com.calendarfx.model.Entry;
import com.calendarfx.util.LoggingDomain;
-import com.calendarfx.util.Util;
import com.calendarfx.view.DateControl;
import com.calendarfx.view.DateControl.DateDetailsParameter;
import com.calendarfx.view.Messages;
import com.calendarfx.view.RequestEvent;
import com.calendarfx.view.YearMonthView;
+import impl.com.calendarfx.view.util.Util;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.geometry.HPos;
@@ -507,11 +507,10 @@ private void handleSingleClick(MouseEvent evt, Node node, LocalDate date) {
case NONE:
break;
case SHOW_DETAILS:
- Callback callback = view
- .getDateDetailsCallback();
- DateDetailsParameter param = new DateDetailsParameter(evt, view,
- node, date, evt.getScreenX(), evt.getScreenY());
- callback.call(param);
+ Callback callback = view.getDateDetailsCallback();
+ if (callback != null) {
+ callback.call(new DateDetailsParameter(evt, view, node, node.getScene().getRoot(), date, evt.getScreenX(), evt.getScreenY()));
+ }
break;
case PERFORM_SELECTION:
boolean multiSelect = evt.isShiftDown() || evt.isShortcutDown();
diff --git a/CalendarFXView/src/main/java/impl/com/calendarfx/view/resources/ResourcesViewContainerSkin.java b/CalendarFXView/src/main/java/impl/com/calendarfx/view/resources/ResourcesViewContainerSkin.java
index 00e284d5..b584df05 100644
--- a/CalendarFXView/src/main/java/impl/com/calendarfx/view/resources/ResourcesViewContainerSkin.java
+++ b/CalendarFXView/src/main/java/impl/com/calendarfx/view/resources/ResourcesViewContainerSkin.java
@@ -185,7 +185,7 @@ private void updateViewResourcesOverDates() {
weekView.installDefaultLassoFinishedBehaviour();
- weekView.numberOfDaysProperty().bind(resourcesView.numberOfDaysProperty());
+ weekView.numberOfDaysProperty().bindBidirectional(resourcesView.numberOfDaysProperty());
CalendarSource calendarSource = createCalendarSource(resource);
weekView.getCalendarSources().setAll(calendarSource);
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 cb9f8301..cda1b239 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
@@ -16,18 +16,43 @@
package impl.com.calendarfx.view.util;
+import com.calendarfx.view.DateControl;
+import com.calendarfx.view.Messages;
import javafx.application.Platform;
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+import javafx.beans.WeakListener;
+import javafx.beans.property.Property;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
+import javafx.geometry.Orientation;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.Parent;
import javafx.scene.control.MultipleSelectionModel;
-
+import javafx.scene.control.ScrollBar;
+import javafx.scene.layout.Pane;
+import net.fortuna.ical4j.model.Recur;
+import net.fortuna.ical4j.model.WeekDay;
+import net.fortuna.ical4j.transform.recurrence.Frequency;
+
+import java.lang.ref.WeakReference;
+import java.text.MessageFormat;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.format.FormatStyle;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
import static java.time.temporal.ChronoField.DAY_OF_WEEK;
import static java.time.temporal.ChronoField.DAY_OF_YEAR;
@@ -40,37 +65,46 @@
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
import static java.time.temporal.ChronoUnit.DAYS;
-@SuppressWarnings("javadoc")
+/**
+ * A collection of useful static methods for easy re-use in various
+ * places of the framework.
+ */
public final class Util {
- public static boolean intersect(LocalDate aStart, LocalDate aEnd,
- LocalDate bStart, LocalDate bEnd) {
+ 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);
+ return childrenWereRemoved;
+ }
+
+ public static boolean removeChildren(Group group, Predicate predicate) {
+ List list = new ArrayList<>(group.getChildrenUnmodifiable());
+ boolean childrenWereRemoved = list.removeIf(predicate);
+ group.getChildren().setAll(list);
+ return childrenWereRemoved;
+ }
+ public static boolean intersect(LocalDate aStart, LocalDate aEnd, LocalDate bStart, LocalDate bEnd) {
// Same start time or same end time?
if (aStart.equals(bStart) || aEnd.equals(bEnd)) {
return true;
}
return aStart.isBefore(bEnd) && aEnd.isAfter(bStart);
-
}
- public static boolean intersect(LocalTime aStart, LocalTime aEnd,
- LocalTime bStart, LocalTime bEnd) {
-
+ public static boolean intersect(LocalTime aStart, LocalTime aEnd, LocalTime bStart, LocalTime bEnd) {
// Same start time or same end time?
if (aStart.equals(bStart) || aEnd.equals(bEnd)) {
return true;
}
return aStart.isBefore(bEnd) && aEnd.isAfter(bStart);
-
}
- public static boolean intersect(ZonedDateTime aStart, ZonedDateTime aEnd,
- ZonedDateTime bStart, ZonedDateTime bEnd) {
-
+ public static boolean intersect(ZonedDateTime aStart, ZonedDateTime aEnd, ZonedDateTime bStart, ZonedDateTime bEnd) {
// Same start time or same end time?
if (aStart.equals(bStart) || aEnd.equals(bEnd)) {
return true;
@@ -80,8 +114,7 @@ public static boolean intersect(ZonedDateTime aStart, ZonedDateTime aEnd,
}
- public static LocalDateTime truncate(LocalDateTime time, ChronoUnit unit,
- int stepRate, DayOfWeek firstDayOfWeek) {
+ public static LocalDateTime truncate(LocalDateTime time, ChronoUnit unit, int stepRate, DayOfWeek firstDayOfWeek) {
switch (unit) {
case DAYS:
return adjustField(time, DAY_OF_YEAR, stepRate).truncatedTo(unit);
@@ -90,55 +123,37 @@ public static LocalDateTime truncate(LocalDateTime time, ChronoUnit unit,
case HOURS:
return adjustField(time, HOUR_OF_DAY, stepRate).truncatedTo(unit);
case MINUTES:
- return adjustField(time, MINUTE_OF_HOUR, stepRate)
- .truncatedTo(unit);
+ return adjustField(time, MINUTE_OF_HOUR, stepRate).truncatedTo(unit);
case SECONDS:
- return adjustField(time, SECOND_OF_MINUTE, stepRate).truncatedTo(
- unit);
+ return adjustField(time, SECOND_OF_MINUTE, stepRate).truncatedTo(unit);
case MILLIS:
- return adjustField(time, MILLI_OF_SECOND, stepRate).truncatedTo(
- unit);
+ return adjustField(time, MILLI_OF_SECOND, stepRate).truncatedTo(unit);
case MICROS:
- return adjustField(time, MICRO_OF_SECOND, stepRate).truncatedTo(
- unit);
+ return adjustField(time, MICRO_OF_SECOND, stepRate).truncatedTo(unit);
case NANOS:
- return adjustField(time, NANO_OF_SECOND, stepRate)
- .truncatedTo(unit);
+ return adjustField(time, NANO_OF_SECOND, stepRate).truncatedTo(unit);
case MONTHS:
- return time
- .with(MONTH_OF_YEAR,
- Math.max(
- 1,
- time.get(MONTH_OF_YEAR)
- - time.get(MONTH_OF_YEAR)
- % stepRate)).withDayOfMonth(1)
- .truncatedTo(DAYS);
+ return time.with(MONTH_OF_YEAR, Math.max(1, time.get(MONTH_OF_YEAR) - time.get(MONTH_OF_YEAR) % stepRate)).withDayOfMonth(1).truncatedTo(DAYS);
case YEARS:
- return adjustField(time, ChronoField.YEAR, stepRate).withDayOfYear(
- 1).truncatedTo(DAYS);
+ return adjustField(time, ChronoField.YEAR, stepRate).withDayOfYear(1).truncatedTo(DAYS);
case WEEKS:
- return time.with(DAY_OF_WEEK, firstDayOfWeek.getValue()).truncatedTo(
- DAYS);
+ return time.with(DAY_OF_WEEK, firstDayOfWeek.getValue()).truncatedTo(DAYS);
case DECADES:
int decade = time.getYear() / 10 * 10;
- return time.with(ChronoField.YEAR, decade).withDayOfYear(1)
- .truncatedTo(DAYS);
+ return time.with(ChronoField.YEAR, decade).withDayOfYear(1).truncatedTo(DAYS);
case CENTURIES:
int century = time.getYear() / 100 * 100;
- return time.with(ChronoField.YEAR, century).withDayOfYear(1)
- .truncatedTo(DAYS);
+ return time.with(ChronoField.YEAR, century).withDayOfYear(1).truncatedTo(DAYS);
case MILLENNIA:
int millenium = time.getYear() / 1000 * 1000;
- return time.with(ChronoField.YEAR, millenium).withDayOfYear(1)
- .truncatedTo(DAYS);
+ return time.with(ChronoField.YEAR, millenium).withDayOfYear(1).truncatedTo(DAYS);
default:
}
return time;
}
- public static ZonedDateTime truncate(ZonedDateTime time, ChronoUnit unit,
- int stepRate, DayOfWeek firstDayOfWeek) {
+ public static ZonedDateTime truncate(ZonedDateTime time, ChronoUnit unit, int stepRate, DayOfWeek firstDayOfWeek) {
switch (unit) {
case DAYS:
return adjustField(time, DAY_OF_YEAR, stepRate).truncatedTo(unit);
@@ -147,92 +162,56 @@ public static ZonedDateTime truncate(ZonedDateTime time, ChronoUnit unit,
case HOURS:
return adjustField(time, HOUR_OF_DAY, stepRate).truncatedTo(unit);
case MINUTES:
- return adjustField(time, MINUTE_OF_HOUR, stepRate)
- .truncatedTo(unit);
+ return adjustField(time, MINUTE_OF_HOUR, stepRate).truncatedTo(unit);
case SECONDS:
- return adjustField(time, SECOND_OF_MINUTE, stepRate).truncatedTo(
- unit);
+ return adjustField(time, SECOND_OF_MINUTE, stepRate).truncatedTo(unit);
case MILLIS:
- return adjustField(time, MILLI_OF_SECOND, stepRate).truncatedTo(
- unit);
+ return adjustField(time, MILLI_OF_SECOND, stepRate).truncatedTo(unit);
case MICROS:
- return adjustField(time, MICRO_OF_SECOND, stepRate).truncatedTo(
- unit);
+ return adjustField(time, MICRO_OF_SECOND, stepRate).truncatedTo(unit);
case NANOS:
- return adjustField(time, NANO_OF_SECOND, stepRate)
- .truncatedTo(unit);
+ return adjustField(time, NANO_OF_SECOND, stepRate).truncatedTo(unit);
case MONTHS:
- return time
- .with(MONTH_OF_YEAR,
- Math.max(
- 1,
- time.get(MONTH_OF_YEAR)
- - time.get(MONTH_OF_YEAR)
- % stepRate)).withDayOfMonth(1)
- .truncatedTo(DAYS);
+ return time.with(MONTH_OF_YEAR, Math.max(1, time.get(MONTH_OF_YEAR) - time.get(MONTH_OF_YEAR) % stepRate)).withDayOfMonth(1).truncatedTo(DAYS);
case YEARS:
- return adjustField(time, ChronoField.YEAR, stepRate).withDayOfYear(
- 1).truncatedTo(DAYS);
+ return adjustField(time, ChronoField.YEAR, stepRate).withDayOfYear(1).truncatedTo(DAYS);
case WEEKS:
- return time.with(DAY_OF_WEEK, firstDayOfWeek.getValue()).truncatedTo(
- DAYS);
+ return time.with(DAY_OF_WEEK, firstDayOfWeek.getValue()).truncatedTo(DAYS);
case DECADES:
int decade = time.getYear() / 10 * 10;
- return time.with(ChronoField.YEAR, decade).withDayOfYear(1)
- .truncatedTo(DAYS);
+ return time.with(ChronoField.YEAR, decade).withDayOfYear(1).truncatedTo(DAYS);
case CENTURIES:
int century = time.getYear() / 100 * 100;
- return time.with(ChronoField.YEAR, century).withDayOfYear(1)
- .truncatedTo(DAYS);
+ return time.with(ChronoField.YEAR, century).withDayOfYear(1).truncatedTo(DAYS);
case MILLENNIA:
int millenium = time.getYear() / 1000 * 1000;
- return time.with(ChronoField.YEAR, millenium).withDayOfYear(1)
- .truncatedTo(DAYS);
+ return time.with(ChronoField.YEAR, millenium).withDayOfYear(1).truncatedTo(DAYS);
default:
}
return time;
}
- public static LocalTime truncate(LocalTime time, ChronoUnit unit,
- int stepRate) {
+ public static LocalTime truncate(LocalTime time, ChronoUnit unit, int stepRate) {
switch (unit) {
case HOURS:
return adjustField(time, HOUR_OF_DAY, stepRate).truncatedTo(unit);
case MINUTES:
- return adjustField(time, MINUTE_OF_HOUR, stepRate)
- .truncatedTo(unit);
+ return adjustField(time, MINUTE_OF_HOUR, stepRate).truncatedTo(unit);
case SECONDS:
- return adjustField(time, SECOND_OF_MINUTE, stepRate).truncatedTo(
- unit);
+ return adjustField(time, SECOND_OF_MINUTE, stepRate).truncatedTo(unit);
case MILLIS:
- return adjustField(time, MILLI_OF_SECOND, stepRate).truncatedTo(
- unit);
+ return adjustField(time, MILLI_OF_SECOND, stepRate).truncatedTo(unit);
case MICROS:
- return adjustField(time, MICRO_OF_SECOND, stepRate).truncatedTo(
- unit);
+ return adjustField(time, MICRO_OF_SECOND, stepRate).truncatedTo(unit);
case NANOS:
- return adjustField(time, NANO_OF_SECOND, stepRate)
- .truncatedTo(unit);
+ return adjustField(time, NANO_OF_SECOND, stepRate).truncatedTo(unit);
default:
}
return time;
}
- public static boolean equals(Object first, Object second) {
- if (first == null) {
- return second == null;
- }
-
- if (second == null) {
- // because we already know that first is not null (see above)
- return false;
- }
-
- return first.equals(second);
- }
-
public static void runInFXThread(Runnable runnable) {
if (Platform.isFxApplicationThread()) {
runnable.run();
@@ -241,10 +220,6 @@ public static void runInFXThread(Runnable runnable) {
}
}
- public static MultipleSelectionModel createEmptySelectionModel() {
- return new EmptySelectionModel<>();
- }
-
private static ZonedDateTime adjustField(ZonedDateTime time, ChronoField field, int stepRate) {
return time.with(field, time.get(field) - time.get(field) % stepRate);
}
@@ -257,6 +232,10 @@ private static LocalTime adjustField(LocalTime time, ChronoField field, int step
return time.with(field, time.get(field) - time.get(field) % stepRate);
}
+ public static MultipleSelectionModel createEmptySelectionModel() {
+ return new EmptySelectionModel<>();
+ }
+
private static class EmptySelectionModel extends MultipleSelectionModel {
@Override
public void selectPrevious() {
@@ -327,4 +306,315 @@ public ObservableList getSelectedIndices() {
}
}
+ /**
+ * An interface used for converting an object of one type to an object
+ * of another type.
+ *
+ * @param the first (left) type
+ * @param the second (right) type
+ */
+ public interface Converter {
+
+ L toLeft(R right);
+
+ R toRight(L left);
+ }
+
+ /**
+ * Converts the given recurrence rule (according to RFC 2445) into a human-readable text,
+ * e.g. "RRULE:FREQ=DAILY;" becomes "Every day".
+ *
+ * @param rrule the rule
+ * @param startDate the start date for the rule
+ * @return a nice text describing the rule
+ */
+ public static String convertRFC2445ToText(String rrule, LocalDate startDate) {
+ try {
+ Recur rule = new Recur<>(rrule.replaceFirst("^RRULE:", ""));
+ StringBuilder sb = new StringBuilder();
+
+ String granularity;
+ String granularities;
+
+ switch (rule.getFrequency()) {
+ case DAILY:
+ granularity = Messages.getString("Util.DAY");
+ granularities = Messages.getString("Util.DAYS");
+ break;
+ case MONTHLY:
+ granularity = Messages.getString("Util.MONTH");
+ granularities = Messages.getString("Util.MONTHS");
+ break;
+ case WEEKLY:
+ granularity = Messages.getString("Util.WEEK");
+ granularities = Messages.getString("Util.WEEKS");
+ break;
+ case YEARLY:
+ granularity = Messages.getString("Util.YEAR");
+ granularities = Messages.getString("Util.YEARS");
+ break;
+ case HOURLY:
+ granularity = Messages.getString("Util.HOUR");
+ granularities = Messages.getString("Util.HOURS");
+ break;
+ case MINUTELY:
+ granularity = Messages.getString("Util.MINUTE");
+ granularities = Messages.getString("Util.MINUTES");
+ break;
+ case SECONDLY:
+ granularity = Messages.getString("Util.SECOND");
+ granularities = Messages.getString("Util.SECONDS");
+ break;
+ default:
+ granularity = "";
+ granularities = "";
+ }
+
+ int interval = rule.getInterval();
+ if (interval > 1) {
+ sb.append(MessageFormat.format(Messages.getString("Util.EVERY_PLURAL"), rule.getInterval(), granularities));
+ } else {
+ sb.append(MessageFormat.format(Messages.getString("Util.EVERY_SINGULAR"), granularity));
+ }
+
+ /*
+ * Weekdays
+ */
+
+ if (rule.getFrequency().equals(Frequency.WEEKLY)) {
+ List byDay = rule.getDayList();
+ if (!byDay.isEmpty()) {
+ sb.append(Messages.getString("Util.ON_WEEKDAY"));
+ for (int i = 0; i < byDay.size(); i++) {
+ WeekDay num = byDay.get(i);
+ sb.append(makeHuman(num.getDay()));
+ if (i < byDay.size() - 1) {
+ sb.append(", ");
+ }
+ }
+ }
+ }
+
+ if (rule.getFrequency().equals(Frequency.MONTHLY)) {
+
+ if (!rule.getMonthDayList().isEmpty()) {
+
+ int day = rule.getMonthDayList().get(0);
+ sb.append(Messages.getString("Util.ON_MONTH_DAY"));
+ sb.append(day);
+
+ } else if (!rule.getDayList().isEmpty()) {
+
+ /*
+ * We only support one day.
+ */
+ WeekDay num = rule.getDayList().get(0);
+ sb.append(MessageFormat.format(Messages.getString("Util.ON_MONTH_WEEKDAY"), makeHuman(num.getOffset()), makeHuman(num.getDay())));
+ }
+ }
+
+ if (rule.getFrequency().equals(Frequency.YEARLY)) {
+ sb.append(MessageFormat.format(Messages.getString("Util.ON_DATE"), DateTimeFormatter.ofPattern(Messages.getString("Util.MONTH_AND_DAY_FORMAT")).format(startDate)));
+ }
+
+ int count = rule.getCount();
+ if (count > 0) {
+ if (count == 1) {
+ return Messages.getString("Util.ONCE");
+ } else {
+ sb.append(MessageFormat.format(Messages.getString("Util.TIMES"), count));
+ }
+ } else {
+ LocalDate until = rule.getUntil();
+ if (until != null) {
+ sb.append(MessageFormat.format(Messages.getString("Util.UNTIL_DATE"), DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).format(until)));
+ }
+ }
+
+ return sb.toString();
+ } catch (IllegalArgumentException | DateTimeParseException e) {
+ e.printStackTrace();
+ return Messages.getString("Util.INVALID_RULE");
+ }
+ }
+
+ private static String makeHuman(WeekDay.Day wday) {
+ switch (wday) {
+ case FR:
+ return Messages.getString("Util.FRIDAY");
+ case MO:
+ return Messages.getString("Util.MONDAY");
+ case SA:
+ return Messages.getString("Util.SATURDAY");
+ case SU:
+ return Messages.getString("Util.SUNDAY");
+ case TH:
+ return Messages.getString("Util.THURSDAY");
+ case TU:
+ return Messages.getString("Util.TUESDAY");
+ case WE:
+ return Messages.getString("Util.WEDNESDAY");
+ default:
+ throw new IllegalArgumentException("unknown weekday: " + wday);
+ }
+ }
+
+ private static String makeHuman(int num) {
+ switch (num) {
+ case 1:
+ return Messages.getString("Util.FIRST");
+ case 2:
+ return Messages.getString("Util.SECOND");
+ case 3:
+ return Messages.getString("Util.THIRD");
+ case 4:
+ return Messages.getString("Util.FOURTH");
+ case 5:
+ return Messages.getString("Util.FIFTH");
+ default:
+ return Integer.toString(num);
+ }
+ }
+
+ /**
+ * Searches for a {@link ScrollBar} of the given orientation (vertical, horizontal)
+ * somewhere in the containment hierarchy of the given parent node.
+ *
+ * @param parent the parent node
+ * @param orientation the orientation (horizontal, vertical)
+ * @return a scrollbar or null if none can be found
+ */
+ public static ScrollBar findScrollBar(Parent parent, Orientation orientation) {
+ for (Node node : parent.getChildrenUnmodifiable()) {
+ if (node instanceof ScrollBar) {
+ ScrollBar b = (ScrollBar) node;
+ if (b.getOrientation().equals(orientation)) {
+ return b;
+ }
+ }
+
+ if (node instanceof Parent) {
+ ScrollBar b = findScrollBar((Parent) node, orientation);
+ if (b != null) {
+ return b;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Adjusts the given date to a new date that marks the beginning of the week where the
+ * given date is located. If "Monday" is the first day of the week and the given date
+ * is a "Wednesday" then this method will return a date that is two days earlier than the
+ * given date.
+ *
+ * @param date the date to adjust
+ * @param firstDayOfWeek the day of week that is considered the start of the week ("Monday" in Germany, "Sunday" in the US)
+ * @return the date of the first day of the week
+ * @see #adjustToLastDayOfWeek(LocalDate, DayOfWeek)
+ * @see DateControl#getFirstDayOfWeek()
+ */
+ public static LocalDate adjustToFirstDayOfWeek(LocalDate date, DayOfWeek firstDayOfWeek) {
+ LocalDate newDate = date.with(DAY_OF_WEEK, firstDayOfWeek.getValue());
+ if (newDate.isAfter(date)) {
+ newDate = newDate.minusWeeks(1);
+ }
+
+ return newDate;
+ }
+
+ /**
+ * Adjusts the given date to a new date that marks the end of the week where the
+ * given date is located. If "Monday" is the first day of the week and the given date
+ * is a "Wednesday" then this method will return a date that is four days later than the
+ * given date. This method calculates the first day of the week and then adds six days
+ * to it.
+ *
+ * @param date the date to adjust
+ * @param firstDayOfWeek the day of week that is considered the start of the week ("Monday" in Germany, "Sunday" in the US)
+ * @return the date of the first day of the week
+ * @see #adjustToFirstDayOfWeek(LocalDate, DayOfWeek)
+ * @see DateControl#getFirstDayOfWeek()
+ */
+ public static LocalDate adjustToLastDayOfWeek(LocalDate date, DayOfWeek firstDayOfWeek) {
+ LocalDate startOfWeek = adjustToFirstDayOfWeek(date, firstDayOfWeek);
+ return startOfWeek.plusDays(6);
+ }
+
+ /**
+ * Creates a bidirectional binding between the two given properties of different types via the
+ * help of a {@link Converter}.
+ *
+ * @param leftProperty the left property
+ * @param rightProperty the right property
+ * @param converter the converter
+ * @param the type of the left property
+ * @param the type of the right property
+ */
+ public static void bindBidirectional(Property leftProperty, Property rightProperty, Converter converter) {
+ BidirectionalConversionBinding binding = new BidirectionalConversionBinding<>(leftProperty, rightProperty, converter);
+ leftProperty.addListener(binding);
+ rightProperty.addListener(binding);
+ leftProperty.setValue(converter.toLeft(rightProperty.getValue()));
+ }
+
+ private static class BidirectionalConversionBinding implements InvalidationListener, WeakListener {
+
+ private final WeakReference> leftReference;
+ private final WeakReference> rightReference;
+ private final Converter converter;
+ private boolean updating;
+
+ private BidirectionalConversionBinding(Property leftProperty, Property rightProperty, Converter converter) {
+ this.leftReference = new WeakReference<>(Objects.requireNonNull(leftProperty));
+ this.rightReference = new WeakReference<>(Objects.requireNonNull(rightProperty));
+ this.converter = Objects.requireNonNull(converter);
+ }
+
+ public Property getLeftProperty() {
+ return leftReference.get();
+ }
+
+ public Property getRightProperty() {
+ return rightReference.get();
+ }
+
+ @Override
+ public boolean wasGarbageCollected() {
+ return getLeftProperty() == null || getRightProperty() == null;
+ }
+
+ @Override
+ public void invalidated(Observable observable) {
+ if (updating) {
+ return;
+ }
+
+ final Property leftProperty = getLeftProperty();
+ final Property rightProperty = getRightProperty();
+
+ if (wasGarbageCollected()) {
+ if (leftProperty != null) {
+ leftProperty.removeListener(this);
+ }
+ if (rightProperty != null) {
+ rightProperty.removeListener(this);
+ }
+ } else {
+ try {
+ updating = true;
+
+ if (observable == leftProperty) {
+ rightProperty.setValue(converter.toRight(leftProperty.getValue()));
+ } else {
+ leftProperty.setValue(converter.toLeft(rightProperty.getValue()));
+ }
+ } finally {
+ updating = false;
+ }
+ }
+ }
+ }
}