dpfmanager.shell.interfaces.gui.component.common.TimeSpinner Maven / Gradle / Ivy
/**
* TimeSpinner.java
This program is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any later version; or,
* at your choice, under the terms of the Mozilla Public License, v. 2.0. SPDX GPL-3.0+ or MPL-2.0+.
*
This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License and the Mozilla Public License for more details.
* You should have received a copy of the GNU General Public License and the Mozilla Public
* License along with this program. If not, see http://www.gnu.org/licenses/
* and at http://mozilla.org/MPL/2.0 .
NB: for the
* © statement, include Easy Innova SL or other company/Person contributing the code.
©
* 2015 Easy Innova, SL
*
* @author Adrià Llorens
* @version 1.0
* @since 23/7/2015
*/
package dpfmanager.shell.interfaces.gui.component.common;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.InputEvent;
import javafx.util.StringConverter;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* Created by Adrià Llorens on 01/07/2016.
*/
public class TimeSpinner extends Spinner {
enum Mode {
HOURS {
@Override
LocalTime increment(LocalTime time, int steps) {
return time.plusHours(steps);
}
@Override
void select(TimeSpinner spinner) {
int index = spinner.getEditor().getText().indexOf(':');
spinner.getEditor().selectRange(0, index);
}
},
MINUTES {
@Override
LocalTime increment(LocalTime time, int steps) {
return time.plusMinutes(steps);
}
@Override
void select(TimeSpinner spinner) {
int index = spinner.getEditor().getText().lastIndexOf(':');
spinner.getEditor().selectRange(index + 1, spinner.getEditor().getText().length());
}
};
abstract LocalTime increment(LocalTime time, int steps);
abstract void select(TimeSpinner spinner);
LocalTime decrement(LocalTime time, int steps) {
return increment(time, -steps);
}
}
// Property containing the current editing mode:
private final ObjectProperty mode = new SimpleObjectProperty<>(Mode.HOURS);
public ObjectProperty modeProperty() {
return mode;
}
public final Mode getMode() {
return modeProperty().get();
}
public final void setMode(Mode mode) {
modeProperty().set(mode);
}
public TimeSpinner(LocalTime time) {
setPrefWidth(80.0);
setEditable(true);
// Create a StringConverter for converting between the text in the
// editor and the actual value:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
StringConverter localTimeConverter = new StringConverter() {
@Override
public String toString(LocalTime time) {
return formatter.format(time);
}
@Override
public LocalTime fromString(String string) {
String[] tokens = string.split(":");
int hours = getIntField(tokens, 0);
int minutes = getIntField(tokens, 1);
int totalSeconds = (hours * 60 + minutes) * 60;
return LocalTime.of((totalSeconds / 3600) % 24, (totalSeconds / 60) % 60);
}
private int getIntField(String[] tokens, int index) {
if (tokens.length <= index || tokens[index].isEmpty()) {
return 0;
}
return Integer.parseInt(tokens[index]);
}
};
// The textFormatter both manages the text <-> LocalTime conversion,
// and vetoes any edits that are not valid. We just make sure we have
// two colons and only digits in between:
TextFormatter textFormatter = new TextFormatter(localTimeConverter, time, c -> {
String newText = c.getControlNewText();
if (newText.matches("[0-9]{0,2}:[0-9]{0,2}")) {
return c;
}
return null;
});
// The spinner value factory defines increment and decrement by
// delegating to the current editing mode:
SpinnerValueFactory valueFactory = new SpinnerValueFactory() {
{
setConverter(localTimeConverter);
setValue(time);
}
@Override
public void decrement(int steps) {
setValue(mode.get().decrement(getValue(), steps));
mode.get().select(TimeSpinner.this);
}
@Override
public void increment(int steps) {
setValue(mode.get().increment(getValue(), steps));
mode.get().select(TimeSpinner.this);
}
};
this.setValueFactory(valueFactory);
this.getEditor().setTextFormatter(textFormatter);
// Update the mode when the user interacts with the editor.
// This is a bit of a hack, e.g. calling spinner.getEditor().positionCaret()
// could result in incorrect state. Directly observing the caretPostion
// didn't work well though; getting that to work properly might be
// a better approach in the long run.
this.getEditor().addEventHandler(InputEvent.ANY, e -> {
int caretPos = this.getEditor().getCaretPosition();
int hrIndex = this.getEditor().getText().indexOf(':');
if (caretPos <= hrIndex) {
mode.set(Mode.HOURS);
} else {
mode.set(Mode.MINUTES);
}
});
// When the mode changes, select the new portion:
mode.addListener((obs, oldMode, newMode) -> newMode.select(this));
}
public TimeSpinner() {
this(LocalTime.now());
}
}