Skip to content

Commit

Permalink
Add TimeRangePicker control and demo application
Browse files Browse the repository at this point in the history
Introduce the TimeRangePicker custom control for selecting time ranges, supporting both single and multiple selection modes. Additionally, include a demo application showcasing the usage of the new control with various example time ranges and interaction features.
  • Loading branch information
leewyatt committed Oct 18, 2024
1 parent 34e3b9e commit f063a35
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.dlsc.gemsfx.demo;

import com.dlsc.gemsfx.TimeRangePicker;
import com.dlsc.gemsfx.demo.fake.SimpleControlPane;
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class HelloTimeRangePicker extends Application {

private TimeRangePicker timeRangePicker;

@Override
public void start(Stage primaryStage) throws Exception {
timeRangePicker = new TimeRangePicker();
timeRangePicker.setPrefWidth(200);
timeRangePicker.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

timeRangePicker.getSelectionModel().selectIndices(1, 2, 3);

StackPane wrapper = new StackPane(timeRangePicker);
wrapper.setStyle("-fx-background-color: white; -fx-padding: 10;");

SplitPane splitPane = new SplitPane(wrapper, createControlPane());
splitPane.setDividerPositions(0.7);

primaryStage.setScene(new Scene(splitPane, 800, 600));
primaryStage.setTitle("Hello TimeRangePicker");
primaryStage.show();
}

private Node createControlPane() {
ComboBox<SelectionMode> controlPane = new ComboBox<>();
controlPane.getItems().addAll(SelectionMode.values());
controlPane.valueProperty().bindBidirectional(timeRangePicker.getSelectionModel().selectionModeProperty());

return new SimpleControlPane(
"Time Range Picker",
new SimpleControlPane.ControlItem("Selection Mode", controlPane)
);
}


public static void main(String[] args) {
launch(args);
}
}
142 changes: 142 additions & 0 deletions gemsfx/src/main/java/com/dlsc/gemsfx/TimeRangePicker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package com.dlsc.gemsfx;

import com.dlsc.gemsfx.util.SimpleStringConverter;
import javafx.scene.control.Button;
import javafx.util.StringConverter;

import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;

/**
* A custom control that allows users to select time ranges.
* It provides support for two selection modes: single range and multiple ranges.
* <ul>
* <li>SINGLE mode allows selection of only one time range at a time.</li>
* <li>MULTIPLE mode allows selection of multiple time ranges.</li>
* </ul>
*
* @see SelectionBox
*/
public class TimeRangePicker extends SelectionBox<TimeRangePicker.TimeRange> {

private static final String DEFAULT_STYLE_CLASS = "time-range-picker";

public TimeRangePicker() {
// add some default time ranges
this(new TimeRange(LocalTime.of(10, 0), LocalTime.of(12, 0)),
new TimeRange(LocalTime.of(12, 0), LocalTime.of(14, 0)),
new TimeRange(LocalTime.of(14, 0), LocalTime.of(16, 0)),
new TimeRange(LocalTime.of(16, 0), LocalTime.of(18, 0)));
}

public TimeRangePicker(TimeRange... ranges) {
getStyleClass().add(DEFAULT_STYLE_CLASS);

// Set time ranges
getItems().setAll(ranges);

// set extra buttons
setExtraButtonsProvider(model -> switch (model.getSelectionMode()) {
case SINGLE -> {
Button clearButton = createExtraButton("Clear", getSelectionModel()::clearSelection);
yield List.of(clearButton);
}
case MULTIPLE -> {
Button clearButton = createExtraButton("Clear", model::clearSelection);
Button selectAllButton = createExtraButton("Select All", model::selectAll);
yield List.of(clearButton, selectAllButton);
}
});

// set result converter
setSelectedItemsConverter(new SimpleStringConverter<>(selectedRanges -> {
int selectedCount = selectedRanges.size();

if (selectedCount == 0) {
return "No Data";
} else if (selectedCount == 1) {
return convertRangeToText(selectedRanges.get(0));
} else {
// Merge consecutive ranges
List<TimeRange> mergedRanges = mergeConsecutiveItem(selectedRanges);

// Build the display text
List<String> rangeStrings = new ArrayList<>();
for (TimeRange range : mergedRanges) {
rangeStrings.add(convertRangeToText(range));
}
return String.join(", ", rangeStrings);
}
}));
}

private List<TimeRange> mergeConsecutiveItem(List<TimeRange> ranges) {
List<TimeRange> mergedRanges = new ArrayList<>();

if (ranges.isEmpty()) {
return mergedRanges;
}

// Sort ranges by start time
ranges.sort(Comparator.comparing(TimeRange::startTime));

TimeRange currentRange = ranges.get(0);

for (int i = 1; i < ranges.size(); i++) {
TimeRange nextRange = ranges.get(i);

if (currentRange.endTime().equals(nextRange.startTime())) {
// Ranges are consecutive, merge them
currentRange = new TimeRange(currentRange.startTime(), nextRange.endTime());
} else {
// Ranges are not consecutive, add the current range and move to next
mergedRanges.add(currentRange);
currentRange = nextRange;
}
}

// Add the last range
mergedRanges.add(currentRange);

return mergedRanges;
}

private String convertRangeToText(TimeRange range) {
if (range == null) {
return "";
}
StringConverter<TimeRange> itemConverter = getItemConverter();
if (itemConverter != null) {
return itemConverter.toString(range);
}
return range.toString();
}

/**
* Represents a time range with a start time and an end time.
* Ensures that the start time is not after the end time.
*/
public record TimeRange(LocalTime startTime, LocalTime endTime) {

public TimeRange {
// Ensure startTime and endTime are not null
Objects.requireNonNull(startTime, "startTime cannot be null");
Objects.requireNonNull(endTime, "endTime cannot be null");

// If startTime is after endTime, swap them
if (startTime.isAfter(endTime)) {
LocalTime temp = startTime;
startTime = endTime;
endTime = temp;
}
}

@Override
public String toString() {
return startTime + " ~ " + endTime;
}
}
}

0 comments on commit f063a35

Please sign in to comment.