diff --git a/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/DayOfWeekPickerApp.java b/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/DayOfWeekPickerApp.java new file mode 100644 index 00000000..ec81c461 --- /dev/null +++ b/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/DayOfWeekPickerApp.java @@ -0,0 +1,66 @@ +package com.dlsc.gemsfx.demo; + +import com.dlsc.gemsfx.DayOfWeekPicker; +import com.dlsc.gemsfx.demo.fake.SimpleControlPane; +import javafx.application.Application; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ComboBox; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.SplitPane; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +public class DayOfWeekPickerApp extends Application { + + private final DayOfWeekPicker dayOfWeekPicker = new DayOfWeekPicker(); + + @Override + public void start(Stage primaryStage) throws Exception { + + dayOfWeekPicker.prefWidthProperty().bind(dayOfWeekPicker.getSelectionModel().selectionModeProperty().map(sm -> sm == SelectionMode.SINGLE ? 135 : 160)); + StackPane wrapper = new StackPane(dayOfWeekPicker); + wrapper.setStyle("-fx-padding: 30px; -fx-background-color: white;"); + + SplitPane splitPane = new SplitPane(wrapper,getControlPanel()); + splitPane.setDividerPositions(0.68); + + primaryStage.setScene(new Scene(splitPane, 800, 600)); + primaryStage.setTitle("Hello DayOfWeekPicker"); + primaryStage.show(); + } + + public Node getControlPanel() { + ComboBox selectionModeComboBox = new ComboBox<>(); + selectionModeComboBox.getItems().addAll(SelectionMode.values()); + selectionModeComboBox.valueProperty().bindBidirectional(dayOfWeekPicker.getSelectionModel().selectionModeProperty()); + + CheckBox autoHideOnSelection = new CheckBox("Hide On Selection"); + autoHideOnSelection.selectedProperty().bindBidirectional(dayOfWeekPicker.autoHideOnSelectionProperty()); + + Button selectAll = new Button("Select All"); + selectAll.setMaxWidth(Double.MAX_VALUE); + selectAll.managedProperty().bind(selectAll.visibleProperty()); + selectAll.visibleProperty().bind(dayOfWeekPicker.getSelectionModel().selectionModeProperty().isEqualTo(SelectionMode.MULTIPLE)); + selectAll.setOnAction(evt -> dayOfWeekPicker.getSelectionModel().selectAll()); + + Button clearSelection = new Button("Clear Selection"); + clearSelection.setMaxWidth(Double.MAX_VALUE); + clearSelection.setOnAction(evt -> dayOfWeekPicker.getSelectionModel().clearSelection()); + + VBox selectionButtons = new VBox(10, selectAll, clearSelection); + selectionButtons.setAlignment(Pos.CENTER); + selectionButtons.setFillWidth(true); + + return new SimpleControlPane( + "DayOfWeekPicker", + new SimpleControlPane.ControlItem("SelectionMode", selectionModeComboBox), + new SimpleControlPane.ControlItem("Auto Hide", autoHideOnSelection), + new SimpleControlPane.ControlItem("Test Select", selectionButtons) + ); + } +} diff --git a/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/HelloTimeRangePicker.java b/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/TimeRangePickerApp.java similarity index 96% rename from gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/HelloTimeRangePicker.java rename to gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/TimeRangePickerApp.java index de77196b..b11a3b5d 100644 --- a/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/HelloTimeRangePicker.java +++ b/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/TimeRangePickerApp.java @@ -11,7 +11,7 @@ import javafx.scene.layout.StackPane; import javafx.stage.Stage; -public class HelloTimeRangePicker extends Application { +public class TimeRangePickerApp extends Application { private TimeRangePicker timeRangePicker; diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/DayOfWeekPicker.java b/gemsfx/src/main/java/com/dlsc/gemsfx/DayOfWeekPicker.java new file mode 100644 index 00000000..2322973a --- /dev/null +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/DayOfWeekPicker.java @@ -0,0 +1,210 @@ +package com.dlsc.gemsfx; + +import com.dlsc.gemsfx.util.SimpleStringConverter; +import javafx.scene.control.Button; +import javafx.scene.control.SelectionMode; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.format.TextStyle; +import java.time.temporal.WeekFields; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +/** + * A custom control that allows users to select days of the week. + * It provides support for two {@link SelectionMode}: single and multiple . + * + * + * @see SelectionBox + */ +public class DayOfWeekPicker extends SelectionBox { + + private static final String DEFAULT_STYLE_CLASS = "day-of-week-picker"; + + public DayOfWeekPicker() { + getStyleClass().add(DEFAULT_STYLE_CLASS); + + // Set Items + getItems().setAll(getLocalizedDayOrder()); + + // Set Extra Buttons Provider + setExtraButtonsProvider(model -> switch (model.getSelectionMode()) { + case SINGLE -> { + // Button tomorrowButton = createExtraButton("Tomorrow", () -> model.clearAndSelect(getItems().indexOf(LocalDate.now().getDayOfWeek().plus(1)))); + // Button yesterdayButton = createExtraButton("Yesterday", () -> model.clearAndSelect(getItems().indexOf(LocalDate.now().getDayOfWeek().minus(1)))); + Button todayButton = createExtraButton("Today", () -> model.clearAndSelect(getItems().indexOf(LocalDate.now().getDayOfWeek()))); + Button clearButton = createExtraButton("Clear", model::clearSelection); + yield List.of(clearButton, todayButton); + } + case MULTIPLE -> { + // When clicking on the button, it will clear the current selection and select all weekdays + Button weekdaysButton = createExtraButton("Weekdays", () -> { + getSelectionModel().clearSelection(); + for (DayOfWeek day : getWeekdays()) { + getSelectionModel().select(day); + } + }); + // When clicking on the button, it will clear the current selection and select all weekend days + Button weekendsButton = createExtraButton("Weekends", () -> { + getSelectionModel().clearSelection(); + for (DayOfWeek day : getWeekendDays()) { + getSelectionModel().select(day); + } + }); + Button clearButton = createExtraButton("Clear", model::clearSelection); + Button anyDayButton = createExtraButton("Any Day", model::selectAll); + yield List.of(clearButton, anyDayButton, weekdaysButton, weekendsButton); + } + }); + + // set item converter + setItemConverter(new SimpleStringConverter<>(dayOfWeek -> dayOfWeek.getDisplayName(TextStyle.FULL, Locale.getDefault()))); + + // Set selected items converter + setSelectedItemsConverter(new SimpleStringConverter<>(selectedDays -> { + if (selectedDays == null || selectedDays.isEmpty()) { + return ""; + } else if (selectedDays.size() == 1) { + return selectedDays.get(0).getDisplayName(TextStyle.FULL, Locale.getDefault()); + } else if (isSelectedAll()) { + return "All Days"; + } else if (isOnlyWeekdaysSelected()) { + return "Weekdays"; + } else if (isOnlyWeekendsSelected()) { + return "Weekends"; + } else { + // Group selected days into consecutive ranges + List> ranges = mergeConsecutiveItem(selectedDays); + + // Build the display text + List rangeStrings = new ArrayList<>(); + for (List range : ranges) { + if (range.size() > 1) { + String startDay = range.get(0).getDisplayName(TextStyle.SHORT, Locale.getDefault()); + String endDay = range.get(range.size() - 1).getDisplayName(TextStyle.SHORT, Locale.getDefault()); + rangeStrings.add(startDay + " ~ " + endDay); + } else { + String dayName = range.get(0).getDisplayName(TextStyle.SHORT, Locale.getDefault()); + rangeStrings.add(dayName); + } + } + return String.join(", ", rangeStrings); + } + })); + } + + private List> mergeConsecutiveItem(List selectedDays) { + List days = new ArrayList<>(selectedDays); + if (days.isEmpty()) { + return Collections.emptyList(); + } + + // Get localized day order + List dayOrder = getLocalizedDayOrder(); + + // Sort days according to localized order + days.sort(Comparator.comparingInt(dayOrder::indexOf)); + + List> ranges = new ArrayList<>(); + List currentRange = new ArrayList<>(); + currentRange.add(days.get(0)); + + for (int i = 1; i < days.size(); i++) { + DayOfWeek previousDay = days.get(i - 1); + DayOfWeek currentDay = days.get(i); + + int prevIndex = dayOrder.indexOf(previousDay); + int currIndex = dayOrder.indexOf(currentDay); + + if ((prevIndex + 1) % 7 == currIndex % 7) { + currentRange.add(currentDay); + } else { + ranges.add(new ArrayList<>(currentRange)); + currentRange.clear(); + currentRange.add(currentDay); + } + } + ranges.add(currentRange); + return ranges; + } + + /** + * Get the localized order of DayOfWeek. + */ + public List getLocalizedDayOrder() { + WeekFields weekFields = WeekFields.of(Locale.getDefault()); + DayOfWeek firstDayOfWeek = weekFields.getFirstDayOfWeek(); + List dayOrderList = new ArrayList<>(); + DayOfWeek day = firstDayOfWeek; + for (int i = 0; i < 7; i++) { + dayOrderList.add(day); + day = day.plus(1); + } + return dayOrderList; + } + + /** + * Checks if only weekdays are selected in the current selection model. + * + * @return true if the selected days match exactly with the weekdays, otherwise false + */ + public boolean isOnlyWeekdaysSelected() { + Set selectedDays = new HashSet<>(getSelectionModel().getSelectedItems()); + Set weekdays = new HashSet<>(getWeekdays()); + return selectedDays.equals(weekdays); + } + + /** + * Checks if only weekend days are selected in the current selection model. + * + * @return true if the selected days match exactly with the weekend days, otherwise false + */ + public boolean isOnlyWeekendsSelected() { + Set selectedDays = new HashSet<>(getSelectionModel().getSelectedItems()); + Set weekends = new HashSet<>(getWeekendDays()); + return selectedDays.equals(weekends); + } + + /** + * Checks if all days are selected in the current selection model. + * + * @return true if all days are selected, otherwise false + */ + public boolean isSelectedAll() { + return getSelectionModel().getSelectedItems().size() == getItems().size(); + } + + /** + * Retrieves a list of the weekend days based on the current locale. + * + * @return a list of DayOfWeek objects representing the weekend days. + */ + public List getWeekendDays() { + WeekFields weekFields = WeekFields.of(Locale.getDefault()); + DayOfWeek weekendStart = weekFields.getFirstDayOfWeek().plus(5 % 7); + DayOfWeek weekendEnd = weekendStart.plus(1); + + return List.of(weekendStart, weekendEnd); + } + + /** + * Retrieves a list of the weekdays by excluding weekend days. + * + * @return a list of DayOfWeek objects representing the weekdays. + */ + public List getWeekdays() { + List weekdays = new ArrayList<>(List.of(DayOfWeek.values())); + weekdays.removeAll(getWeekendDays()); + return weekdays; + } + +} diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/TimeRangePicker.java b/gemsfx/src/main/java/com/dlsc/gemsfx/TimeRangePicker.java index eb2446a8..c0bb3aa0 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/TimeRangePicker.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/TimeRangePicker.java @@ -56,7 +56,7 @@ public TimeRangePicker(TimeRange... ranges) { int selectedCount = selectedRanges.size(); if (selectedCount == 0) { - return "No Data"; + return ""; } else if (selectedCount == 1) { return convertRangeToText(selectedRanges.get(0)); } else { diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/skins/SelectionBoxSkin.java b/gemsfx/src/main/java/com/dlsc/gemsfx/skins/SelectionBoxSkin.java index da0176d3..3e3712f6 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/skins/SelectionBoxSkin.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/skins/SelectionBoxSkin.java @@ -227,7 +227,7 @@ private String getDefaultDisplayText(List selectedItems) { int selectedCount = selectedItems.size(); if (selectedCount == 0) { - return "No Data"; + return ""; } else if (selectedCount == 1) { return convertItemToText(selectedItems.get(0)); } else { diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/util/CustomMultipleSelectionModel.java b/gemsfx/src/main/java/com/dlsc/gemsfx/util/CustomMultipleSelectionModel.java index eb3b8618..71dfcb46 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/util/CustomMultipleSelectionModel.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/util/CustomMultipleSelectionModel.java @@ -325,9 +325,6 @@ public void select(T obj) { int index = items.indexOf(obj); if (index >= 0) { select(index); - } else { - // Object not in the list, clear selection - clearSelection(); } }