All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jfxtras.labs.scene.control.scheduler.skin.EventAbstractTrackedPane Maven / Gradle / Ivy

There is a newer version: 9.0-r1
Show newest version
package jfxtras.labs.scene.control.scheduler.skin;


import jfxtras.labs.scene.control.scheduler.Scheduler;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * @author Tom Eugelink
 */
public class EventAbstractTrackedPane extends EventAbstractPane {

    EventAbstractTrackedPane(Scheduler.Event event, LayoutHelp layoutHelp) {
        super(event, layoutHelp);

        // we know start and end optionally are set
        startDateTime = event.getStartTime();

        endDateTime = event.getEndTime();
        durationInMS = startDateTime.until(endDateTime, ChronoUnit.MILLIS);

        firstPaneOfEvent = startDateTime.equals(event.getStartTime());
        lastPaneOfEvent = (endDateTime != null && endDateTime.equals(event.getEndTime()));
        intermediatePaneOfEvent = !firstPaneOfEvent && !lastPaneOfEvent;
    }

    protected final LocalDateTime startDateTime;
    protected final LocalDateTime endDateTime;
    protected final long durationInMS;
    protected final boolean firstPaneOfEvent;
    protected final boolean intermediatePaneOfEvent;
    protected final boolean lastPaneOfEvent;

    @Override
    protected boolean showStartTimeText() {
        return firstPaneOfEvent;
    }

    @Override
    protected boolean showEndTimeText() {
        return lastPaneOfEvent;
    }

    // for the role of cluster owner
    List clusterMembers = new ArrayList<>();
    List> clusterTracks = new ArrayList<>();

    // for the role of cluster member
    EventAbstractTrackedPane clusterOwner = this;
    int clusterTrackIdx = -1;

    /**
     * This method prepares a day for being drawn.
     * The appointments within one day might overlap, this method will create a data structure so it is clear how these overlapping appointments should be drawn.
     * All appointments in one day are process based on their start time; earliest first, and if there are more with the same start time, longest duration first.
     * The appointments are then place onto (parallel) tracks; an appointment initially is placed in track 0.
     * But if there is already an (partially overlapping) appointment there, then the appointment is moved to track 1.
     * Unless there also is an appointment already in that track 1, then the next track is tried, and so forth, until a free track is found.
     * For example (the letters are not the sequence in which the appointments are processed, they're just for identifying them):
     * 

* tracks * 0 1 2 3 * ------- * . . . . * . . . . * A . . . * A B C . * A B C D * A B . D * A . . D * A E . D * A . . D * . . . D * . . . D * F . . D * F H . D * . . . . * G . . . * . . . . *

* Appointment A was rendered first and put into track 0 and its start time. * Then appointment B was added, initially it was put in track 0, but appointment A already uses the that slot, so B was moved into track 1. * C moved from track 0, conflicting with A, to track 1, conflicting with B, and ended up in track 2. And so forth. * F and H show that even though D overlaps them, they could perfectly be placed in lower tracks. *

* A cluster of appointments always starts with a free standing appointment in track 0, for example A or G, such appointment is called the cluster owner. * When the next appointment is added to the tracks, and finds that it cannot be put in track 0, it will be added as a member to the cluster represented by the appointment in track 0. * Special attention must be paid to an appointment that is placed in track 0, but is linked to a cluster by a earlier appointment in a higher track; such an appointment is not the cluster owner. * In the example above, F is linked through D to the cluster owned by A. So F is not a cluster owner, but a member of the cluster owned by A. * And appointment H through F is also part of the cluster owned by A. * G finally starts a new cluster. * The cluster owner knows all members and how many tracks there are, each member knows in what track it is and has a direct link to the cluster owner. *

* When rendering the appointments above, parallel appointments are rendered narrower & indented, so appointments partially overlap and the left side of an appointment is always visible to the user. * In the example above the single appointment G is rendered full width, while for example A, B, C and D are overlapping. * F and H are drawn in the same dimensions as A and B in order to allow D to overlap then. * The size and amount of indentation depends on the number of appointments that are rendered next to each other. * In order to compute its location and size, each appointment needs to know: * - its start and ending time, * - its track number, * - its total number of tracks, * - and naturally the total width and height available to draw the day. */ static List determineTracks(List eventAbstractTrackedPanes) { // sort on start time and then decreasing duration Collections.sort(eventAbstractTrackedPanes, new Comparator() { @Override public int compare(EventAbstractTrackedPane o1, EventAbstractTrackedPane o2) { // if not same start, then compare on starttime if (!o1.startDateTime.isEqual(o2.startDateTime)) { return o1.startDateTime.compareTo(o2.startDateTime); } /* // task after appointment if (o1 instanceof EventRegularBodyPane *//*&& o2 instanceof AppointmentTaskBodyPane*//*) { return -1; } if (o1 instanceof AppointmentTaskBodyPane && o2 instanceof AppointmentRegularBodyPane) { return 1; }*/ // longest last return o1.durationInMS == o2.durationInMS ? 0 : (o1.durationInMS > o2.durationInMS ? -1 : 1); } }); // start placing appointments in the tracks EventAbstractTrackedPane lClusterOwner = null; for (EventAbstractTrackedPane lAppointmentPane : eventAbstractTrackedPanes) { // if there is no cluster owner if (lClusterOwner == null) { // than the current becomes an owner // only create a minimal cluster, because it will be setup fully in the code below lClusterOwner = lAppointmentPane; lClusterOwner.clusterTracks = new ArrayList>(); } // in which track should it be added int lTrackNr = determineTrackWhereAppointmentCanBeAdded(lClusterOwner.clusterTracks, lAppointmentPane); // if it can be added to track 0, then we have a "situation". Track 0 could mean // - we must start a new cluster // - the appointment is still linked to the running cluster by means of a linking appointment in the higher tracks if (lTrackNr == 0) { // So let's see if there is a linking appointment higher up boolean lOverlaps = false; for (int i = 1; i < lClusterOwner.clusterTracks.size() && lOverlaps == false; i++) { lOverlaps = checkIfTheAppointmentOverlapsAnAppointmentAlreadyInThisTrack(lClusterOwner.clusterTracks, i, lAppointmentPane); } // if it does not overlap, we start a new cluster if (lOverlaps == false) { lClusterOwner = lAppointmentPane; lClusterOwner.clusterMembers = new ArrayList(); lClusterOwner.clusterTracks = new ArrayList>(); lClusterOwner.clusterTracks.add(new ArrayList()); } } // add it to the track (and setup all other cluster data) lClusterOwner.clusterMembers.add(lAppointmentPane); lClusterOwner.clusterTracks.get(lTrackNr).add(lAppointmentPane); lAppointmentPane.clusterOwner = lClusterOwner; lAppointmentPane.clusterTrackIdx = lTrackNr; // for debug System.out.println("----"); for (int i = 0; i < lClusterOwner.clusterTracks.size(); i++) { System.out.println(i + ": " + lClusterOwner.clusterTracks.get(i) ); } System.out.println("----"); } // done return eventAbstractTrackedPanes; } /** * */ static private int determineTrackWhereAppointmentCanBeAdded(List> tracks, EventAbstractTrackedPane eventPane) { int lTrackNr = 0; while (true) { // make sure there is a arraylist for this track if (lTrackNr == tracks.size()) { tracks.add(new ArrayList()); } // scan all existing appointments in this track and see if there is an overlap if (checkIfTheAppointmentOverlapsAnAppointmentAlreadyInThisTrack(tracks, lTrackNr, eventPane) == false) { // no overlap, it can be added here return lTrackNr; } // overlap, try next track lTrackNr++; } } /** * */ static private boolean checkIfTheAppointmentOverlapsAnAppointmentAlreadyInThisTrack(List> tracks, int tracknr, EventAbstractTrackedPane newEventPane) { // get the track List lTrack = tracks.get(tracknr); // scan all existing appointments in this track for (EventAbstractTrackedPane lEventPane : lTrack) { // There is an overlap: // if the start time of the already placed appointment is before or equals the new appointment's end time // and the end time of the already placed appointment is after the new appointment's start time (equals will put two consequative appointments into separate tracks) // ...PPPPPPPPP... // .NNNN.......... -> Ps <= Ne & Pe >= Ns -> overlap // .....NNNNN..... -> Ps <= Ne & Pe >= Ns -> overlap // ..........NNN.. -> Ps <= Ne & Pe >= Ns -> overlap // .NNNNNNNNNNNNN. -> Ps <= Ne & Pe >= Ns -> overlap // .N............. -> false & Pe >= Ns -> no overlap // .............N. -> Ps <= Ne & false -> no overlap LocalDateTime lPlacedStart = lEventPane.startDateTime; LocalDateTime lPlacedEnd = (lEventPane.endDateTime != null ? lEventPane.endDateTime : lEventPane.startDateTime.plusMinutes(10)); LocalDateTime lNewStart = newEventPane.startDateTime; LocalDateTime lNewEnd = (newEventPane.endDateTime != null ? newEventPane.endDateTime : newEventPane.startDateTime.plusMinutes(10)); if ((lPlacedStart.isEqual(lNewStart) || lNewEnd == null || lPlacedStart.isBefore(lNewEnd)) && lPlacedEnd != null && lPlacedEnd.isAfter(lNewStart) ) { // overlap return true; } } // no overlap return false; } /** * */ // static private boolean checkIfTheAppointmentOverlapsAnAppointmentAlreadyInThisTrack(List> tracks, int tracknr, AppointmentAbstractTrackedPane newAppointmentPane) // { // // get the track // List lTrack = tracks.get(tracknr); // if (lTrack.size() == 0) { // return false; // no overlap // } // // // scan all existing appointments in this track // for (AppointmentAbstractTrackedPane lPlacedAppointmentPane : lTrack) // { // // There is an overlap: // // if the start time of the already placed appointment is before or equals the new appointment's end time // // and the end time of the already placed appointment is after or equals the new appointment's start time // // // // ...PPPPPPPPP... already placed appointment with end date // // // // .NNNN.......... -> overlap // // .....NNNNN..... -> overlap // // ..........NNN.. -> overlap // // .NNNNNNNNNNNNN. -> overlap // // .N............. -> no overlap -> N-end <= P-start [1] // // .............N. -> no overlap -> N-start >= p-end [2] // // .NNNNNNNNNNNNNN -> overlap (new without end date) // // ......NNNNNNNNN -> overlap (new without end date) // // .............NN -> no overlap (new without end date) -> N-start >= p-end [3] // // // // ...PPPPPPPPPPPP already placed appointment without end date // // .NNNN.......... -> overlap // // .....NNNNN..... -> overlap // // ..........NNN.. -> overlap // // .NNNNNNNNNNNNN. -> overlap // // .N............. -> no overlap -> N-end <= P-start [4] // // .............N. -> overlap // // .NNNNNNNNNNNNNN -> overlap (new without end date) // // ......NNNNNNNNN -> overlap (new without end date) // // .............NN -> overlap (new without end date) // if ( (lPlacedAppointmentPane.endDateTime != null && newAppointmentPane.endDateTime != null && isBeforeOrEqual(newAppointmentPane.endDateTime, lPlacedAppointmentPane.startDateTime)) // [1] // || (lPlacedAppointmentPane.endDateTime != null && newAppointmentPane.endDateTime != null && isAfterOrEqual(newAppointmentPane.startDateTime, lPlacedAppointmentPane.endDateTime)) // [2] // || (lPlacedAppointmentPane.endDateTime != null && newAppointmentPane.endDateTime == null && isAfterOrEqual(newAppointmentPane.startDateTime, lPlacedAppointmentPane.endDateTime)) // [3] // || (lPlacedAppointmentPane.endDateTime == null && newAppointmentPane.endDateTime != null && isBeforeOrEqual(newAppointmentPane.endDateTime, lPlacedAppointmentPane.startDateTime)) // [4] // ) // { // // no overlap // return false; // } // } // // overlap // return true; // } // // static private boolean isBeforeOrEqual(LocalDateTime ldt1, LocalDateTime ldt2) { // return ldt1.isBefore(ldt2) || ldt1.isEqual(ldt2); // } // // static private boolean isAfterOrEqual(LocalDateTime ldt1, LocalDateTime ldt2) { // return ldt1.isAfter(ldt2) || ldt1.isEqual(ldt2); // } /** * */ public String toString() { return "pane=" + startDateTime + "-" + endDateTime + ";" + super.toString() ; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy