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

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

There is a newer version: 11.12.7
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 com.calendarfx.util.LoggingDomain;
import impl.com.calendarfx.view.ViewHelper;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList;
import org.controlsfx.control.PropertySheet.Item;

import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Optional;

import static java.util.Objects.requireNonNull;

/**
 * The common superclass for all date controls that are used to display the 24
 * hours of a day: day view, week day view, time scale, week time scale, and
 * week view. Instances of this type can be configured to display hours at a
 * fixed height or alternatively a fixed number of hours for a given viewport
 * height (see {@link HoursLayoutStrategy}). This control also supports an early /
 * late hours concept where these hours can either be shown, or hidden, or shown
 * in a compressed way (see {@link EarlyLateHoursStrategy}). The idea behind
 * early / late hours is that often applications do not work with all 24 hours
 * of a day and hiding or compressing these hours allow the user to focus on
 * the relevant hours.
 */
public abstract class DayViewBase extends DateControl implements ZonedDateTimeProvider {

    /**
     * Constructs a new view.
     */
    public DayViewBase() {

        MapChangeListener propertiesListener = change -> {
            if (change.wasAdded()) {
                if (change.getKey().equals("show.current.time.marker")) { //$NON-NLS-1$
                    Boolean show = (Boolean) change.getValueAdded();
                    showCurrentTimeMarker.set(show);
                } else if (change.getKey().equals(
                        "show.current.time.today.marker")) { //$NON-NLS-1$
                    Boolean show = (Boolean) change.getValueAdded();
                    showCurrentTimeTodayMarker.set(show);
                } else if (change.getKey().equals("earliest.time.used")) {
                    LocalTime time = (LocalTime) change.getValueAdded();
                    earliestTimeUsed.set(time);
                } else if (change.getKey().equals("latest.time.used")) {
                    LocalTime time = (LocalTime) change.getValueAdded();
                    latestTimeUsed.set(time);
                }
            }
        };

        getProperties().addListener(propertiesListener);

        final InvalidationListener trimListener = it -> {
            if (isTrimTimeBounds()) {
                trimTimeBounds();
            }
        };

        earliestTimeUsedProperty().addListener(trimListener);
        latestTimeUsedProperty().addListener(trimListener);
        trimTimeBoundsProperty().addListener(trimListener);
    }

    private final DoubleProperty entryWidthPercentage = new SimpleDoubleProperty(this, "entryWidthPercentage", 100) {

        @Override
        public void set(double newValue) {
            if (newValue < 10 || newValue > 100) {
                throw new IllegalArgumentException("percentage widht must be between 10 and 100 but was " + newValue);
            }
            super.set(newValue);
        }
    };

    /**
     * A percentage value used to specify how much of the available width inside the
     * view will be utilized by the entry views. The default value is 100%, however
     * applications might want to set a smaller value to allow the user to click and
     * create new entries in already used time intervals.
     *
     * @return the entry percentage width
     */
    public final DoubleProperty entryWidthPercentageProperty() {
        return entryWidthPercentage;
    }

    /**
     * Sets the value of {@link #entryWidthPercentage}.
     *
     * @param percentage the new percentage width
     */
    public final void setEntryWidthPercentage(double percentage) {
        this.entryWidthPercentage.set(percentage);
    }

    /**
     * Returns the value of {@link #entryWidthPercentageProperty()}.
     *
     * @return the percentage width
     */
    public double getEntryWidthPercentage() {
        return entryWidthPercentage.get();
    }

    @Override
    public ZonedDateTime getZonedDateTimeAt(double x, double y) {
        return ZonedDateTime.of(ViewHelper.getLocationTime(this, y, false, true), getZoneId());
    }

    /**
     * An enumerator used for specifying how to deal with late and early hours.
     *
     * @see DayViewBase#earlyLateHoursStrategyProperty()
     * @see DayViewBase#startTimeProperty()
     * @see DayViewBase#endTimeProperty()
     */
    public enum EarlyLateHoursStrategy {

        /**
         * Show all hours with the same height.
         */
        SHOW,

        /**
         * Shows early / late hours with a compressed height and all other hours
         * with the standard height.
         *
         * @see DayViewBase#setHourHeight(double)
         * @see DayViewBase#setHourHeightCompressed(double)
         */
        SHOW_COMPRESSED,

        /**
         * Hide the early / late hours.
         */
        HIDE
    }

    private final ObjectProperty earlyLateHoursStrategy = new SimpleObjectProperty<>(
            this, "earlyLateHoursStrategy", EarlyLateHoursStrategy.SHOW); //$NON-NLS-1$

    /**
     * Specifies a strategy for dealing with early / late hours. The idea behind
     * early / late hours is that often applications do not work with all 24
     * hours of a day and hideing or compressing these hours allow the user to
     * focus on the relevant hours.
     *
     * @return the early / late hours strategy
     * @see #startTimeProperty()
     * @see #endTimeProperty()
     */
    public final ObjectProperty earlyLateHoursStrategyProperty() {
        return earlyLateHoursStrategy;
    }

    /**
     * Sets the value of {@link #earlyLateHoursStrategyProperty()}.
     *
     * @param strategy the strategy to use for early / late hours
     */
    public final void setEarlyLateHoursStrategy(EarlyLateHoursStrategy strategy) {
        requireNonNull(strategy);
        earlyLateHoursStrategy.set(strategy);
    }

    /**
     * Returns the value of {@link #earlyLateHoursStrategyProperty()}.
     *
     * @return the early / late hours strategy
     */
    public final EarlyLateHoursStrategy getEarlyLateHoursStrategy() {
        return earlyLateHoursStrategy.get();
    }

    /**
     * An enumerator used for specifying how to lay out hours. The view can
     * either show a fixed number of hours in the available viewport or each
     * hour at a fixed height.
     *
     * @see DayViewBase#hoursLayoutStrategyProperty()
     * @see DayViewBase#visibleHoursProperty()
     */
    public enum HoursLayoutStrategy {

        /**
         * Keeps the height of each hour constant.
         */
        FIXED_HOUR_HEIGHT,

        /**
         * Keeps the number of hours shown in the viewport of the scrollpane
         * constant.
         */
        FIXED_HOUR_COUNT
    }

    private final ObjectProperty hoursLayoutStrategy = new SimpleObjectProperty<>(
            this, "hoursLayoutStrategy", HoursLayoutStrategy.FIXED_HOUR_COUNT); //$NON-NLS-1$

    /**
     * The layout strategy used by this view for showing hours. The view can
     * either show a fixed number of hours in the available viewport or each
     * hour at a fixed height.
     *
     * @return the hours layout strategy
     * @see DayViewBase#visibleHoursProperty()
     */
    public final ObjectProperty hoursLayoutStrategyProperty() {
        return hoursLayoutStrategy;
    }

    /**
     * Sets the value of {@link #hoursLayoutStrategyProperty()}.
     *
     * @param strategy the hours layout strategy
     */
    public final void setHoursLayoutStrategy(HoursLayoutStrategy strategy) {
        requireNonNull(strategy);
        hoursLayoutStrategy.set(strategy);
    }

    /**
     * Returns the value of {@link #hoursLayoutStrategyProperty()}.
     *
     * @return the hours layout strategy
     */
    public final HoursLayoutStrategy getHoursLayoutStrategy() {
        return hoursLayoutStrategy.get();
    }

    private final IntegerProperty visibleHours = new SimpleIntegerProperty(
            this, "visibleHours", 10); //$NON-NLS-1$

    /**
     * The number of visible hours that the application wants to present to the
     * user at any time.
     *
     * @return the number of visible hours
     * @see #hoursLayoutStrategyProperty()
     */
    public final IntegerProperty visibleHoursProperty() {
        return visibleHours;
    }

    /**
     * Sets the value of {@link #visibleHoursProperty()}.
     *
     * @param hours the number of visible hours
     */
    public final void setVisibleHours(int hours) {
        visibleHoursProperty().set(hours);
    }

    /**
     * Returns the value of {@link #visibleHoursProperty()}.
     *
     * @return the number of visible hours
     */
    public final int getVisibleHours() {
        return visibleHoursProperty().get();
    }

    private final DoubleProperty hourHeight = new SimpleDoubleProperty(this,
            "hourHeight", 70); //$NON-NLS-1$

    /**
     * The height used for each hour shown by the view.
     *
     * @return the hour height
     * @see #hoursLayoutStrategyProperty()
     */
    public final DoubleProperty hourHeightProperty() {
        return hourHeight;
    }

    /**
     * Sets the value of {@link #hourHeightProperty()}.
     *
     * @param height the hour height
     */
    public final void setHourHeight(double height) {
        if (height < 1) {
            throw new IllegalArgumentException(
                    "height must be larger than 0 but was " + height); //$NON-NLS-1$
        }
        hourHeightProperty().set(height);
    }

    /**
     * Returns the value of {@link #hourHeightProperty()}.
     *
     * @return the hour height
     */
    public final double getHourHeight() {
        return hourHeightProperty().get();
    }

    private final DoubleProperty hourHeightCompressed = new SimpleDoubleProperty(
            this, "hourHeightCompressed", 10); //$NON-NLS-1$

    /**
     * The height used for each early / late hour shown by the view when using
     * the {@link EarlyLateHoursStrategy#SHOW_COMPRESSED} strategy.
     *
     * @return the compressed hour height
     * @see #hoursLayoutStrategyProperty()
     */
    public final DoubleProperty hourHeightCompressedProperty() {
        return hourHeightCompressed;
    }

    /**
     * Sets the value of {@link #hourHeightCompressedProperty()}.
     *
     * @param height the compressed hour height
     */
    public final void setHourHeightCompressed(double height) {
        if (height < 1) {
            throw new IllegalArgumentException(
                    "height must be larger than 0 but was " + height); //$NON-NLS-1$
        }
        hourHeightCompressedProperty().set(height);
    }

    /**
     * Returns the value of {@link #hourHeightCompressedProperty()}.
     *
     * @return the compressed hour height
     */
    public final double getHourHeightCompressed() {
        return hourHeightCompressedProperty().get();
    }

    // Current time marker support.

    private final ReadOnlyBooleanWrapper showCurrentTimeMarker = new ReadOnlyBooleanWrapper(
            this, "showCurrentTimeMarker", false); //$NON-NLS-1$

    /**
     * A read-only property used to indicate whether the view should show the
     * "current time" marker. Normally a day view will show this marker if the
     * date shown by the view is equal to the value of "today". Depending on the
     * subclass the marker can be a red label (see {@link TimeScaleView}) or a
     * thin red line (see red time label and line in image below).
     * 

*

*

* * @return true if the current time marker should be shown * @see #showCurrentTimeTodayMarkerProperty() * @see #dateProperty() * @see #todayProperty() */ public final ReadOnlyBooleanProperty showCurrentTimeMarkerProperty() { return showCurrentTimeMarker; } /** * Returns the value of {@link #showCurrentTimeMarkerProperty()}. * * @return true if the view should display the marker */ public final boolean isShowCurrentTimeMarker() { return showCurrentTimeMarker.get(); } private final ReadOnlyBooleanWrapper showCurrentTimeTodayMarker = new ReadOnlyBooleanWrapper( this, "showCurrentTimeTodayMarker", false); //$NON-NLS-1$ /** * A read-only property used to indicate whether the view should show the * "current time today" marker. Normally a day view will show this marker if * the date shown by the view is equal to the value of "today". The default * marker is a red circle (see circle in the image below). *

*

*

* * @return true if the current time marker should be shown * @see #showCurrentTimeMarkerProperty() * @see #dateProperty() * @see #todayProperty() */ public final ReadOnlyBooleanProperty showCurrentTimeTodayMarkerProperty() { return showCurrentTimeTodayMarker; } /** * Returns the value of {@link #showCurrentTimeTodayMarkerProperty()}. * * @return true if the view should show the marker */ public final boolean isShowCurrentTimeTodayMarker() { return showCurrentTimeTodayMarker.get(); } private final BooleanProperty enableCurrentTimeMarker = new SimpleBooleanProperty(this, "enableCurrentTimeMarker", true); /** * A property used to signal whether the application wants to use the red (default) line * used for marking the current system time. The default value is true, so the line will be * shown. * * @return true if the current time will be marked with a red line / text */ public final BooleanProperty enableCurrentTimeMarkerProperty() { return enableCurrentTimeMarker; } /** * Returns the value of {@link #enableCurrentTimeMarkerProperty}. * * @return true if the current time will be marked with a red line / text */ public final boolean isEnableCurrentTimeMarker() { return enableCurrentTimeMarker.get(); } /** * Sets the value of {@link #enableCurrentTimeMarkerProperty}. * * @param enable if true the current time will be marked with a red line / text */ public final void setEnableCurrentTimeMarker(boolean enable) { enableCurrentTimeMarker.set(enable); } private final ReadOnlyObjectWrapper earliestTimeUsed = new ReadOnlyObjectWrapper<>(this, "earliestTimeUsed"); /** * A read-only property used for informing the application about the earliest time point used * by any of the calendar entries currently shown by the view. * * @return the earliest time used by the view */ public final ReadOnlyObjectProperty earliestTimeUsedProperty() { return earliestTimeUsed.getReadOnlyProperty(); } /** * Returns the value of {@link #earliestTimeUsedProperty()}. * * @return the earliest time used */ public final LocalTime getEarliestTimeUsed() { return earliestTimeUsed.get(); } private final ReadOnlyObjectWrapper latestTimeUsed = new ReadOnlyObjectWrapper<>(this, "latestTimeUsed"); /** * A read-only property used for informing the application about the latest time point used * by any of the calendar entries currently shown by the view. * * @return the latest time used by the view */ public final ReadOnlyObjectProperty latestTimeUsedProperty() { return latestTimeUsed.getReadOnlyProperty(); } /** * Returns the value of {@link #latestTimeUsedProperty()}. * * @return the latest time used */ public final LocalTime getLatestTimeUsed() { return latestTimeUsed.get(); } private final BooleanProperty trimTimeBounds = new SimpleBooleanProperty(this, "timTimeBounds", false); public final BooleanProperty trimTimeBoundsProperty() { return trimTimeBounds; } public final boolean isTrimTimeBounds() { return trimTimeBounds.get(); } public final void setTrimTimeBounds(boolean trimTimeBounds) { this.trimTimeBounds.set(trimTimeBounds); } private void trimTimeBounds() { if (this instanceof WeekDayView) { return; } LoggingDomain.PRINTING.fine("trimming hours"); LocalTime st = LocalTime.of(8, 0); LocalTime et = LocalTime.of(19, 0); LocalTime etu = getEarliestTimeUsed(); LocalTime ltu = getLatestTimeUsed(); LoggingDomain.PRINTING.fine("earliest time: " + etu + ", latest time: " + ltu); setEarlyLateHoursStrategy(EarlyLateHoursStrategy.HIDE); if (etu != null && ltu != null && ltu.isAfter(etu)) { // some padding before the first entry if (!etu.isBefore(LocalTime.of(1, 0))) { etu = etu.minusHours(1); } else { etu = LocalTime.MIN; } // some padding after the last entry if (!ltu.isAfter(LocalTime.of(23, 0))) { ltu = ltu.plusHours(1); } else { ltu = LocalTime.MAX; } // only adjust start time if it is too late if (etu.isBefore(st.plusHours(1))) { setStartTime(etu); } else { setStartTime(st); } // only adjust end time if it is too early if (ltu.isAfter(et.minusHours(1))) { setEndTime(ltu); } else { setEndTime(et); } } else { setStartTime(st); setEndTime(et); } setVisibleHours(Math.min(24, (int) getStartTime().until(getEndTime(), ChronoUnit.HOURS))); } /** * Invokes {@link DateControl#bind(DateControl, boolean)} and adds some more * bindings between this control and the given control. * * @param otherControl the control that will be bound to this control * @param bindDate if true will also bind the date property */ public final void bind(DayViewBase otherControl, boolean bindDate) { super.bind(otherControl, bindDate); Bindings.bindBidirectional( otherControl.earlyLateHoursStrategyProperty(), earlyLateHoursStrategy); Bindings.bindBidirectional(otherControl.hoursLayoutStrategyProperty(), hoursLayoutStrategyProperty()); Bindings.bindBidirectional(otherControl.hourHeightProperty(), hourHeightProperty()); Bindings.bindBidirectional(otherControl.hourHeightCompressedProperty(), hourHeightCompressedProperty()); Bindings.bindBidirectional(otherControl.visibleHoursProperty(), visibleHoursProperty()); Bindings.bindBidirectional(otherControl.enableCurrentTimeMarkerProperty(), enableCurrentTimeMarkerProperty()); Bindings.bindBidirectional(otherControl.trimTimeBoundsProperty(), trimTimeBoundsProperty()); } public final void unbind(DayViewBase otherControl) { super.unbind(otherControl); Bindings.unbindBidirectional( otherControl.earlyLateHoursStrategyProperty(), earlyLateHoursStrategy); Bindings.unbindBidirectional(otherControl.hoursLayoutStrategyProperty(), hoursLayoutStrategyProperty()); Bindings.unbindBidirectional(otherControl.hourHeightProperty(), hourHeightProperty()); Bindings.unbindBidirectional( otherControl.hourHeightCompressedProperty(), hourHeightCompressedProperty()); Bindings.unbindBidirectional(otherControl.visibleHoursProperty(), visibleHoursProperty()); Bindings.unbindBidirectional(otherControl.enableCurrentTimeMarkerProperty(), enableCurrentTimeMarkerProperty()); Bindings.unbindBidirectional(otherControl.trimTimeBoundsProperty(), trimTimeBoundsProperty()); } private static final String DAY_VIEW_BASE_CATEGORY = "Date View Base"; //$NON-NLS-1$ @Override public ObservableList getPropertySheetItems() { ObservableList items = super.getPropertySheetItems(); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(enableCurrentTimeMarkerProperty()); } @Override public void setValue(Object value) { setEnableCurrentTimeMarker((boolean) value); } @Override public Object getValue() { return isEnableCurrentTimeMarker(); } @Override public Class getType() { return boolean.class; } @Override public String getName() { return "Current time marker"; //$NON-NLS-1$ } @Override public String getDescription() { return "Early / Late Hours Layout Strategy"; //$NON-NLS-1$ } @Override public String getCategory() { return DAY_VIEW_BASE_CATEGORY; } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(earlyLateHoursStrategyProperty()); } @Override public void setValue(Object value) { setEarlyLateHoursStrategy((EarlyLateHoursStrategy) value); } @Override public Object getValue() { return getEarlyLateHoursStrategy(); } @Override public Class getType() { return EarlyLateHoursStrategy.class; } @Override public String getName() { return "Early / Late Hours"; //$NON-NLS-1$ } @Override public String getDescription() { return "Early / Late Hours Layout Strategy"; //$NON-NLS-1$ } @Override public String getCategory() { return DAY_VIEW_BASE_CATEGORY; } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(visibleHoursProperty()); } @Override public void setValue(Object value) { setVisibleHours((int) value); } @Override public Object getValue() { return getVisibleHours(); } @Override public Class getType() { return Integer.class; } @Override public String getName() { return "Visible Hours"; //$NON-NLS-1$ } @Override public String getDescription() { return "Number of visible hours"; //$NON-NLS-1$ } @Override public String getCategory() { return DAY_VIEW_BASE_CATEGORY; } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(hourHeightProperty()); } @Override public void setValue(Object value) { setHourHeight((double) value); } @Override public Object getValue() { return getHourHeight(); } @Override public Class getType() { return Double.class; } @Override public String getName() { return "Hour Height"; //$NON-NLS-1$ } @Override public String getDescription() { return "Height of one hour"; //$NON-NLS-1$ } @Override public String getCategory() { return DAY_VIEW_BASE_CATEGORY; } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(hourHeightCompressedProperty()); } @Override public void setValue(Object value) { setHourHeightCompressed((double) value); } @Override public Object getValue() { return getHourHeightCompressed(); } @Override public Class getType() { return Double.class; } @Override public String getName() { return "Hour Height Compressed"; //$NON-NLS-1$ } @Override public String getDescription() { return "Height of one hour when shown compressed (early / late hours)."; //$NON-NLS-1$ } @Override public String getCategory() { return DAY_VIEW_BASE_CATEGORY; } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(hoursLayoutStrategyProperty()); } @Override public void setValue(Object value) { setHoursLayoutStrategy((HoursLayoutStrategy) value); } @Override public Object getValue() { return getHoursLayoutStrategy(); } @Override public Class getType() { return HoursLayoutStrategy.class; } @Override public String getName() { return "Layout Strategy"; //$NON-NLS-1$ } @Override public String getDescription() { return "Layout Strategy"; //$NON-NLS-1$ } @Override public String getCategory() { return DAY_VIEW_BASE_CATEGORY; } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(trimTimeBoundsProperty()); } @Override public void setValue(Object value) { setTrimTimeBounds((boolean) value); } @Override public Object getValue() { return isTrimTimeBounds(); } @Override public Class getType() { return boolean.class; } @Override public String getName() { return "Trim Time Bounds"; //$NON-NLS-1$ } @Override public String getDescription() { return "Adjust earliest / latest times shown"; //$NON-NLS-1$ } @Override public String getCategory() { return DAY_VIEW_BASE_CATEGORY; } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(earliestTimeUsedProperty()); } @Override public void setValue(Object value) { } @Override public Object getValue() { return getEarliestTimeUsed(); } @Override public Class getType() { return LocalTime.class; } @Override public String getName() { return "Earliest Time Used"; //$NON-NLS-1$ } @Override public String getDescription() { return "Earliest start time of any entry view"; //$NON-NLS-1$ } @Override public String getCategory() { return DAY_VIEW_BASE_CATEGORY; } @Override public boolean isEditable() { return true; } }); items.add(new Item() { @Override public Optional> getObservableValue() { return Optional.of(latestTimeUsedProperty()); } @Override public void setValue(Object value) { } @Override public Object getValue() { return getLatestTimeUsed(); } @Override public Class getType() { return LocalTime.class; } @Override public String getName() { return "Latest Time Used"; //$NON-NLS-1$ } @Override public String getDescription() { return "Latest end time of any entry view"; //$NON-NLS-1$ } @Override public String getCategory() { return DAY_VIEW_BASE_CATEGORY; } @Override public boolean isEditable() { return true; } }); return items; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy