Skip to content

Commit

Permalink
Support for click and drag creation of entries, fixes to popover to s…
Browse files Browse the repository at this point in the history
…upport this.
  • Loading branch information
dlemmermann committed Sep 12, 2022
1 parent 992e9d9 commit 979e2de
Show file tree
Hide file tree
Showing 16 changed files with 332 additions and 178 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,15 @@ public class SchedulerApp extends Application {
public void start(Stage primaryStage) {
CalendarView calendarView = new CalendarView(Page.DAY, Page.WEEK);
calendarView.showWeekPage();
calendarView.setEnableTimeZoneSupport(false);
calendarView.setCreateEntryClickCount(1);

DetailedWeekView detailedWeekView = calendarView.getWeekPage().getDetailedWeekView();
WeekView weekView = detailedWeekView.getWeekView();
DayView dayView = calendarView.getDayPage().getDetailedDayView().getDayView();

detailedWeekView.setShowToday(false);
detailedWeekView.setEarlyLateHoursStrategy(EarlyLateHoursStrategy.HIDE);

// extra button for week page
ToggleButton editScheduleButton1 = new ToggleButton("Edit Schedule");
Expand All @@ -60,9 +63,6 @@ public void start(Stage primaryStage) {
editScheduleButton2.selectedProperty().bindBidirectional(dayView.editAvailabilityProperty());
((Pane) calendarView.getDayPage().getToolBarControls()).getChildren().add(editScheduleButton2);

calendarView.setEnableTimeZoneSupport(true);
calendarView.getWeekPage().getDetailedWeekView().setEarlyLateHoursStrategy(EarlyLateHoursStrategy.HIDE);

Calendar katja = new Calendar("Katja");
Calendar dirk = new Calendar("Dirk");
Calendar philip = new Calendar("Philip");
Expand Down
23 changes: 14 additions & 9 deletions CalendarFXView/src/main/java/com/calendarfx/util/ViewHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
import impl.com.calendarfx.view.DayViewScrollPane;
import javafx.collections.ObservableList;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.effect.Light.Point;
import javafx.stage.Screen;
import org.controlsfx.control.PopOver.ArrowLocation;

Expand Down Expand Up @@ -103,7 +103,7 @@ public static double getTimeLocation(DayViewBase view, ZonedDateTime zonedTime,
startTime = view.getZonedDateTimeStart();
endTime = view.getZonedDateTimeEnd();

long earlyHours = ChronoUnit.HOURS.between(minTime,startTime);
long earlyHours = ChronoUnit.HOURS.between(minTime, startTime);
long lateHours = ChronoUnit.HOURS.between(endTime, maxTime) + 1;

double hourHeightCompressed = view.getHourHeightCompressed();
Expand Down Expand Up @@ -180,7 +180,7 @@ public static Instant getInstantAt(DayViewBase view, double y) {
ZonedDateTime minTime = view.getZonedDateTimeMin();
ZonedDateTime maxTime = view.getZonedDateTimeMax();

long earlyHours = ChronoUnit.HOURS.between(minTime,startTime);
long earlyHours = ChronoUnit.HOURS.between(minTime, startTime);
long lateHours = ChronoUnit.HOURS.between(endTime, maxTime) + 1;

double hourHeightCompressed = view.getHourHeightCompressed();
Expand Down Expand Up @@ -236,6 +236,11 @@ public static ArrowLocation findPopOverArrowLocation(Node view) {
ObservableList<Screen> screens = Screen.getScreensForRectangle(
entryBounds.getMinX(), entryBounds.getMinY(),
entryBounds.getWidth(), entryBounds.getHeight());

if (screens.isEmpty()) {
return null;

}
Rectangle2D screenBounds = screens.get(0).getVisualBounds();

double spaceLeft = entryBounds.getMinX();
Expand All @@ -257,19 +262,19 @@ public static ArrowLocation findPopOverArrowLocation(Node view) {
return ArrowLocation.LEFT_TOP;
}

public static Point findPopOverArrowPosition(Node node, double screenY, double arrowSize, ArrowLocation arrowLocation) {
Point point = new Point();
point.setY(screenY);
public static Point2D findPopOverArrowPosition(Node node, double screenY, double arrowSize, ArrowLocation arrowLocation) {

Bounds entryBounds = node.localToScreen(node.getBoundsInLocal());

double screenX;

if (arrowLocation == ArrowLocation.LEFT_TOP || arrowLocation == ArrowLocation.LEFT_BOTTOM) {
point.setX(entryBounds.getMaxX());
screenX = entryBounds.getMaxX();
} else {
point.setX(entryBounds.getMinX() - arrowSize);
screenX = entryBounds.getMinX() - arrowSize;
}

return point;
return new Point2D(screenX, screenY);
}

public static void scrollToRequestedTime(DateControl control, DayViewScrollPane scrollPane) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ public CreateDeleteHandler(DateControl control) {
}

private void createEntry(MouseEvent evt) {
if (evt.getButton().equals(MouseButton.PRIMARY) && evt.getClickCount() == 2) {
if (!(dateControl instanceof DayView) && evt.getButton().equals(MouseButton.PRIMARY) && evt.getClickCount() == dateControl.getCreateEntryClickCount()) {

if (!evt.isStillSincePress()) {
return;
}

if (dateControl instanceof DayViewBase) {
DayViewBase dayViewBase = (DayViewBase) dateControl;
Expand Down Expand Up @@ -72,9 +76,14 @@ private void createEntry(MouseEvent evt) {

Optional<Calendar> calendar = dateControl.getCalendarAt(evt.getX(), evt.getY());
if (time != null) {
dateControl.createEntryAt(time, calendar.orElse(null));
Entry<?> entry = dateControl.createEntryAt(time, calendar.orElse(null));
if (dateControl.isShowDetailsUponCreation()) {
dateControl.fireEvent(new RequestEvent(dateControl, dateControl, entry));
}
}
}

evt.consume();
}
}

Expand Down
118 changes: 96 additions & 22 deletions CalendarFXView/src/main/java/com/calendarfx/view/DateControl.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@
import javafx.beans.WeakInvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyListProperty;
import javafx.beans.property.ReadOnlyListWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
Expand All @@ -53,7 +55,6 @@
import javafx.scene.control.MenuItem;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.SelectionMode;
import javafx.scene.effect.Light.Point;
import javafx.scene.input.ContextMenuEvent;
import javafx.scene.input.InputEvent;
import javafx.scene.input.MouseEvent;
Expand Down Expand Up @@ -162,6 +163,8 @@ public abstract class DateControl extends CalendarFXControl {

private final WeakInvalidationListener weakUpdateCalendarListListener = new WeakInvalidationListener(updateCalendarListListener);

private Entry<?> requestedDetailEntry;

/**
* Constructs a new date control and initializes all factories and callbacks
* with default implementations.
Expand Down Expand Up @@ -412,6 +415,13 @@ protected DateControl() {
getAvailableZoneIds().add(ZoneId.of("US/Eastern"));
getAvailableZoneIds().add(ZoneId.of("US/Central"));
getAvailableZoneIds().add(ZoneId.of("US/Pacific"));

createEntryClickCountProperty().addListener(it -> {
int createEntryClickCount = getCreateEntryClickCount();
if (createEntryClickCount <= 0 || createEntryClickCount > 3) {
throw new IllegalArgumentException("the click count for creating new entries must be between 1 and 3 but was " + createEntryClickCount);
}
});
}

private final ObservableMap<Calendar, BooleanProperty> calendarVisibilityMap = FXCollections.observableHashMap();
Expand Down Expand Up @@ -656,36 +666,50 @@ public final void editEntry(Entry<?> entry) {
}

private void doShowEntry(Entry<?> entry, boolean startEditing) {
layout(); // important so that entry view bounds can be found

setDate(entry.getStartDate());

if (!entry.isFullDay()) {
setRequestedTime(entry.getStartTime());
}
Platform.runLater(() -> {
// do not scroll time when a location is already given
// a location is usually given when the user created a new entry via dragging
if (!entry.isFullDay()) {
// wiggle the requested time property
setRequestedTime(null);
setRequestedTime(entry.getStartTime());
}

if (startEditing) {
Platform.runLater(() -> {
if (startEditing) {

/*
* The UI first needs to update itself so that the matching entry
* view can be found.
*/
Platform.runLater(() -> doEditEntry(entry));
} else {
Platform.runLater(() -> doBounceEntry(entry));
}
/*
* The UI first needs to update itself so that the matching entry
* view can be found.
*/
Platform.runLater(() -> doEditEntry(entry));
} else {
Platform.runLater(() -> doBounceEntry(entry));
}
});
});
}

private void doEditEntry(Entry<?> entry) {
EntryViewBase<?> entryView = findEntryView(entry);

if (entryView != null) {
entryView.bounce();
Platform.runLater(() -> {
if (entryView != null) {
entryView.bounce();

Point2D localToScreen = entryView.localToScreen(0, 0);
System.out.println(entryView.getLayoutBounds());
Point2D location = entryView.localToScreen(0, 0);

Callback<EntryDetailsParameter, Boolean> callback = getEntryDetailsCallback();
EntryDetailsParameter param = new EntryDetailsParameter(null, this, entry, entryView, localToScreen.getX(), localToScreen.getY());
callback.call(param);
}
System.out.println(location);
Callback<EntryDetailsParameter, Boolean> callback = getEntryDetailsCallback();
EntryDetailsParameter param = new EntryDetailsParameter(null, this, entry, entryView, location.getX(), location.getY());
callback.call(param);
}
});
}

private void doBounceEntry(Entry<?> entry) {
Expand All @@ -706,6 +730,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
}

EntryDetailsPopOverContentParameter param = new EntryDetailsPopOverContentParameter(entryPopOver, this, owner, entry);
Expand All @@ -718,8 +743,14 @@ private void showEntryDetails(Entry<?> entry, Node owner, double screenY) {
entryPopOver.setContentNode(content);

ArrowLocation location = ViewHelper.findPopOverArrowLocation(owner);
if (location == null) {
location = ArrowLocation.TOP_LEFT;
}

entryPopOver.setArrowLocation(location);
Point position = ViewHelper.findPopOverArrowPosition(owner, screenY, entryPopOver.getArrowSize(), location);

Point2D position = ViewHelper.findPopOverArrowPosition(owner, screenY, entryPopOver.getArrowSize(), location);

entryPopOver.show(owner, position.getX(), position.getY());
}

Expand Down Expand Up @@ -2496,7 +2527,7 @@ public final void setAvailabilityGrid(VirtualGrid availabilityGrid) {
this.availabilityGrid.set(availabilityGrid);
}

private final ObjectProperty<Paint> availabilityFill = new SimpleObjectProperty<>(this, "availabilityFill", Color.rgb(0, 0, 0, .2));
private final ObjectProperty<Paint> availabilityFill = new SimpleObjectProperty<>(this, "availabilityFill", Color.rgb(0, 0, 0, .1));

public final Paint getAvailabilityFill() {
return availabilityFill.get();
Expand All @@ -2516,6 +2547,26 @@ public final void setAvailabilityFill(Paint availabilityFill) {
this.availabilityFill.set(availabilityFill);
}

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

public final boolean isShowDetailsUponCreation() {
return showDetailsUponCreation.get();
}

/**
* Determines if the {@link #entryDetailsCallbackProperty()} will be used to display
* a dialog for a newly added calendar entry.
*
* @return true if the date control shows a dialog immediately after a new entry was added by the user
*/
public final BooleanProperty showDetailsUponCreationProperty() {
return showDetailsUponCreation;
}

public final void setShowDetailsUponCreation(boolean showDetailsUponCreation) {
this.showDetailsUponCreation.set(showDetailsUponCreation);
}

/**
* Binds several properties of the given date control to the same properties
* of this control. This kind of binding is needed to create UIs with nested
Expand Down Expand Up @@ -2564,6 +2615,7 @@ public final void bind(DateControl otherControl, boolean bindDate) {
Bindings.bindBidirectional(otherControl.usagePolicyProperty(), usagePolicyProperty());
Bindings.bindBidirectional(otherControl.availableZoneIdsProperty(), availableZoneIdsProperty());
Bindings.bindBidirectional(otherControl.enableTimeZoneSupportProperty(), enableTimeZoneSupportProperty());
Bindings.bindBidirectional(otherControl.showDetailsUponCreationProperty(), showDetailsUponCreationProperty());

if (bindDate) {
Bindings.bindBidirectional(otherControl.dateProperty(), dateProperty());
Expand All @@ -2573,6 +2625,7 @@ public final void bind(DateControl otherControl, boolean bindDate) {
Bindings.bindBidirectional(otherControl.availabilityCalendarProperty(), availabilityCalendarProperty());
Bindings.bindBidirectional(otherControl.availabilityGridProperty(), availabilityGridProperty());
Bindings.bindBidirectional(otherControl.availabilityFillProperty(), availabilityFillProperty());
Bindings.bindBidirectional(otherControl.createEntryClickCountProperty(), createEntryClickCountProperty());

// bind callbacks
Bindings.bindBidirectional(otherControl.entryDetailsCallbackProperty(), entryDetailsCallbackProperty());
Expand Down Expand Up @@ -2630,6 +2683,7 @@ public final void unbind(DateControl otherControl) {
Bindings.unbindBidirectional(otherControl.availabilityCalendarProperty(), availabilityCalendarProperty());
Bindings.unbindBidirectional(otherControl.availabilityGridProperty(), availabilityGridProperty());
Bindings.unbindBidirectional(otherControl.availabilityFillProperty(), availabilityFillProperty());
Bindings.unbindBidirectional(otherControl.showDetailsUponCreationProperty(), showDetailsUponCreationProperty());

// unbind callbacks
Bindings.unbindBidirectional(otherControl.entryDetailsCallbackProperty(), entryDetailsCallbackProperty());
Expand Down Expand Up @@ -2739,6 +2793,26 @@ public final void setAvailableZoneIds(ObservableList<ZoneId> availableZoneIds) {
this.availableZoneIds.set(availableZoneIds);
}

private final IntegerProperty createEntryClickCount = new SimpleIntegerProperty(this, "createEntryClickCount", 2);

public int getCreateEntryClickCount() {
return createEntryClickCount.get();
}

/**
* An integer that determines how many times the user has to perform a primary
* mouse button click in order to create a new entry.
*
* @return the number of mouse clicks required for creating a new entry
*/
public IntegerProperty createEntryClickCountProperty() {
return createEntryClickCount;
}

public void setCreateEntryClickCount(int createEntryClickCount) {
this.createEntryClickCount.set(createEntryClickCount);
}

private static final String DATE_CONTROL_CATEGORY = "Date Control";

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public class DayView extends DayViewBase {
public DayView() {
getStyleClass().add(DAY_VIEW);

showTodayProperty().addListener(it -> updateStyleClasses());
todayProperty().addListener(evt -> updateStyleClasses());
dateProperty().addListener(evt -> updateStyleClasses());
selectionModeProperty().addListener(evt -> getSelections().clear());
Expand All @@ -79,7 +80,7 @@ protected Skin<?> createDefaultSkin() {

private void updateStyleClasses() {
LocalDate date = getDate();
if (date.equals(getToday())) {
if (date.equals(getToday()) && isShowToday()) {
if (!getStyleClass().contains(DAY_VIEW_TODAY)) {
getStyleClass().add(DAY_VIEW_TODAY);
}
Expand Down
16 changes: 16 additions & 0 deletions CalendarFXView/src/main/java/com/calendarfx/view/DayViewBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import org.controlsfx.control.PropertySheet.Item;

import java.time.Instant;
Expand Down Expand Up @@ -863,6 +865,20 @@ public final void setOnLassoFinished(BiConsumer<Instant, Instant> onLassoFinishe
this.onLassoFinished.set(onLassoFinished);
}

private final ObjectProperty<Paint> lassoColor = new SimpleObjectProperty<>(this, "lassoColor", Color.ALICEBLUE);

public final Paint getLassoColor() {
return lassoColor.get();
}

public final ObjectProperty<Paint> lassoColorProperty() {
return lassoColor;
}

public final void setLassoColor(Paint lassoColor) {
this.lassoColor.set(lassoColor);
}

private final ObjectProperty<Instant> lassoStart = new SimpleObjectProperty<>(this, "lassoStart");

public final Instant getLassoStart() {
Expand Down
Loading

0 comments on commit 979e2de

Please sign in to comment.