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

com.calendarfx.view.DateSelectionModel Maven / Gradle / Ivy

There is a newer version: 8.5.0
Show newest version
/*
 *  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 javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

public class DateSelectionModel {

    private LocalDate lastSelected;
    private DateSelector selector;
    private final ObservableList selectedDates = FXCollections.observableArrayList();
    private final ObservableList unmodifiable = FXCollections.unmodifiableObservableList(selectedDates);
    private final ObjectProperty selectionMode = new SimpleObjectProperty<>(this, "selectionMode");

    public DateSelectionModel() {
        super();
        setSelectionMode(SelectionMode.MULTIPLE_DATES);
    }

    public final void select(LocalDate date) {
        selector.select(date);
    }

    public final void selectRange(LocalDate start, LocalDate end) {
        selector.selectRange(start, end);
    }

    public final void selectUntil(LocalDate date) {
        selector.selectUntil(date);
    }

    public final void deselect(LocalDate date) {
        selector.deselect(date);
    }

    public final void clearAndSelect(LocalDate date) {
        selector.clearAndSelect(date);
    }

    public final void clear() {
        selector.clear();
    }

    public final boolean isSelected(LocalDate date) {
        return selectedDates.contains(date);
    }

    public final boolean isEmpty() {
        return selectedDates.isEmpty();
    }

    public final ObservableList getSelectedDates() {
        return unmodifiable;
    }

    public final LocalDate getLastSelected() {
        return lastSelected;
    }

    public final ObjectProperty selectionModeProperty() {
        return selectionMode;
    }

    public final SelectionMode getSelectionMode() {
        return selectionModeProperty().get();
    }

    public final void setSelectionMode(SelectionMode selectionMode) {
        SelectionMode oldMode = getSelectionMode();
        selectionModeProperty().set(Objects.requireNonNull(selectionMode));
        if (oldMode != selectionMode) {
            selector = DateSelector.createInstance(this);
        }
    }

    public enum SelectionMode {

        SINGLE_DATE,

        SINGLE_DATE_RANGE,

        MULTIPLE_DATES

    }

    private static abstract class DateSelector {

        protected final DateSelectionModel model;

        public DateSelector(DateSelectionModel model) {
            this.model = model;
        }

        public abstract void select(LocalDate date);

        public abstract void selectRange(LocalDate start, LocalDate end);

        public abstract void selectUntil(LocalDate date);

        public void clear() {
            model.selectedDates.clear();
            model.lastSelected = null;
        }

        public void clearAndSelect(LocalDate date) {
            if (date != null) {
                model.selectedDates.setAll(date);
                model.lastSelected = date;
            } else {
                clear();
            }
        }

        public void deselect(LocalDate date) {
            model.selectedDates.remove(date);
            if (model.lastSelected != null && model.lastSelected.equals(date)) {
                model.lastSelected = null;
            }
        }

        public static DateSelector createInstance(DateSelectionModel model) {
            switch (model.getSelectionMode()) {
                case SINGLE_DATE:
                    return new SingleDateSelector(model);

                case SINGLE_DATE_RANGE:
                    return new SingleDateRangeSelector(model);

                case MULTIPLE_DATES:
                    return new MultipleDatesSelector(model);

                default:
                    throw new UnsupportedOperationException();
            }
        }
    }

    private static class SingleDateSelector extends DateSelector {

        public SingleDateSelector(DateSelectionModel model) {
            super(model);
            if (model.selectedDates.size() > 1) {
                if (model.lastSelected != null) {
                    select(model.lastSelected);
                } else {
                    select(model.selectedDates.get(0));
                }
            }
        }

        @Override
        public void select(LocalDate date) {
            if (date != null) {
                model.selectedDates.setAll(date);
                model.lastSelected = date;
            } else {
                model.clear();
            }
        }

        @Override
        public void selectRange(LocalDate start, LocalDate end) {
            if (start == null && end == null) {
                model.clear();
            } else if (end != null) {
                select(end);
            } else {
                select(start);
            }
        }

        @Override
        public void selectUntil(LocalDate date) {
            select(date);
        }

    }

    private static final class SingleDateRangeSelector extends DateSelector {

        private LocalDate rangeStart;
        private LocalDate rangeEnd;

        public SingleDateRangeSelector(DateSelectionModel model) {
            super(model);

            List selection = new ArrayList<>();
            selection.addAll(model.selectedDates);

            if (!selection.isEmpty()) {
                Collections.sort(selection);

                boolean valid = true;
                for (int i = 0; i < selection.size() - 1; i++) {
                    if (i == selection.size() - 1) {
                        break;
                    }

                    LocalDate pivot = selection.get(i);
                    LocalDate next = selection.get(i + 1);

                    if (!next.equals(pivot.plusDays(1))) {
                        valid = false;
                        break;
                    }
                }

                if (!valid) {
                    clear();
                } else {
                    rangeStart = selection.get(0);
                    rangeEnd = selection.get(selection.size() - 1);
                    model.lastSelected = rangeEnd;
                }
            }
        }

        @Override
        public void select(LocalDate date) {
            if (date != null) {
                if (rangeStart == null) {
                    model.selectedDates.setAll(date);
                    model.lastSelected = date;
                    rangeStart = date;
                    rangeEnd = date;
                } else {
                    if (date.equals(rangeStart.plusDays(-1)) || date.equals(rangeEnd.plusDays(1))) {
                        model.selectedDates.add(date);
                        model.lastSelected = date;

                        if (date.isAfter(rangeEnd)) {
                            rangeEnd = date;
                        }

                        if (date.isBefore(rangeStart)) {
                            rangeStart = date;
                        }
                    } else {
                        rangeStart = null;
                        rangeEnd = null;
                        select(date);
                    }
                }
            }
        }

        @Override
        public void selectRange(LocalDate start, LocalDate end) {
            if (start != null && end != null && !start.isAfter(end)) {
                if (model.isEmpty() || isIntersecting(start, end)) {
                    List toSelect = new ArrayList<>();
                    while (start.isBefore(end) || start.isEqual(end)) {
                        if (!model.selectedDates.contains(start)) {
                            toSelect.add(start);
                        }
                        start = start.plusDays(1);
                    }

                    if (!toSelect.isEmpty()) {
                        LocalDate first = toSelect.get(0);
                        LocalDate last = toSelect.get(toSelect.size() - 1);
                        model.selectedDates.addAll(toSelect);
                        model.lastSelected = last;

                        if (rangeStart == null || rangeStart.isAfter(first)) {
                            rangeStart = first;
                        }

                        if (rangeEnd == null || rangeEnd.isBefore(last)) {
                            rangeEnd = last;
                        }
                    }
                } else if (start.equals(rangeEnd.plusDays(1)) || end.equals(rangeStart.plusDays(-1))) {
                    List toSelect = new ArrayList<>();
                    while (start.isBefore(end) || start.isEqual(end)) {
                        toSelect.add(start);
                        start = start.plusDays(1);
                    }

                    LocalDate first = toSelect.get(0);
                    LocalDate last = toSelect.get(toSelect.size() - 1);
                    model.selectedDates.addAll(toSelect);
                    model.lastSelected = last;

                    if (rangeStart == null || rangeStart.isAfter(first)) {
                        rangeStart = first;
                    }

                    if (rangeEnd == null || rangeEnd.isBefore(last)) {
                        rangeEnd = last;
                    }
                } else {
                    List toSelect = new ArrayList<>();
                    while (start.isBefore(end) || start.isEqual(end)) {
                        toSelect.add(start);
                        start = start.plusDays(1);
                    }

                    LocalDate first = toSelect.get(0);
                    LocalDate last = toSelect.get(toSelect.size() - 1);
                    model.selectedDates.setAll(toSelect);
                    model.lastSelected = last;

                    if (rangeStart == null || rangeStart.isAfter(first)) {
                        rangeStart = first;
                    }

                    if (rangeEnd == null || rangeEnd.isBefore(last)) {
                        rangeEnd = last;
                    }
                }
            }
        }

        @Override
        public void selectUntil(LocalDate date) {
            if (date != null && model.lastSelected != null) {
                LocalDate start = model.lastSelected.isBefore(date) ? model.lastSelected : date;
                LocalDate end = model.lastSelected.isBefore(date) ? date : model.lastSelected;
                selectRange(start, end);
                model.lastSelected = date;
            }
        }

        @Override
        public void clear() {
            super.clear();
            rangeStart = null;
            rangeEnd = null;
        }

        private boolean isIntersecting(LocalDate start, LocalDate end) {
            if ((start.isBefore(rangeStart) || start.isEqual(rangeStart)) && (end.isAfter(rangeEnd) || end.isEqual(rangeEnd))) {
                return true;
            }

            if ((start.isEqual(rangeStart) || start.isAfter(rangeStart)) && (start.isBefore(rangeEnd) || start.isEqual(rangeEnd))) {
                return true;
            }

            return (end.isEqual(rangeStart) || end.isAfter(rangeStart)) && (end.isBefore(rangeEnd) || end.isEqual(rangeEnd));

        }

        @Override
        public void clearAndSelect(LocalDate date) {
            super.clearAndSelect(date);
            rangeStart = date;
            rangeEnd = date;
        }

    }

    private static final class MultipleDatesSelector extends DateSelector {

        public MultipleDatesSelector(DateSelectionModel model) {
            super(model);
        }

        @Override
        public void select(LocalDate date) {
            if (date != null) {
                if (!model.selectedDates.contains(date)) {
                    model.selectedDates.add(date);
                }
                model.lastSelected = date;
            }
        }

        @Override
        public void selectRange(LocalDate start, LocalDate end) {
            if (start == null || end == null || start.isAfter(end)) {
                return;
            }

            List toSelect = new ArrayList<>();
            while (start.isBefore(end) || start.equals(end)) {
                if (!model.selectedDates.contains(start)) {
                    toSelect.add(start);
                }
                start = start.plusDays(1);
            }

            model.selectedDates.addAll(toSelect);
            model.lastSelected = toSelect.get(toSelect.size() - 1);
        }

        @Override
        public void selectUntil(LocalDate date) {
            if (date != null && model.lastSelected != null) {
                LocalDate start = model.lastSelected.isBefore(date) ? model.lastSelected : date;
                LocalDate end = model.lastSelected.isBefore(date) ? date : model.lastSelected;
                selectRange(start, end);
            }
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy