-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #84 from gldiazcardenas/issue-75-year-picker
Issue 75 - Year Picker
- Loading branch information
Showing
8 changed files
with
593 additions
and
0 deletions.
There are no files selected for viewing
48 changes: 48 additions & 0 deletions
48
gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/YearPickerApp.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package com.dlsc.gemsfx.demo; | ||
|
||
import com.dlsc.gemsfx.YearPicker; | ||
import fr.brouillard.oss.cssfx.CSSFX; | ||
import javafx.application.Application; | ||
import javafx.beans.binding.Bindings; | ||
import javafx.geometry.Insets; | ||
import javafx.geometry.Pos; | ||
import javafx.scene.Scene; | ||
import javafx.scene.control.CheckBox; | ||
import javafx.scene.control.Label; | ||
import javafx.scene.layout.VBox; | ||
import javafx.stage.Stage; | ||
|
||
public class YearPickerApp extends Application { | ||
|
||
@Override | ||
public void start(Stage stage) { | ||
YearPicker yearPicker = new YearPicker(); | ||
|
||
Label valueLabel = new Label(); | ||
valueLabel.textProperty().bind(Bindings.convert(yearPicker.yearProperty())); | ||
|
||
CheckBox editable = new CheckBox("Editable"); | ||
editable.selectedProperty().bindBidirectional(yearPicker.editableProperty()); | ||
|
||
CheckBox disable = new CheckBox("Disable"); | ||
disable.selectedProperty().bindBidirectional(yearPicker.disableProperty()); | ||
|
||
VBox vBox = new VBox(10, yearPicker, valueLabel, editable, disable); | ||
|
||
vBox.setPadding(new Insets(20)); | ||
vBox.setAlignment(Pos.TOP_LEFT); | ||
|
||
Scene scene = new Scene(vBox); | ||
CSSFX.start(); | ||
|
||
stage.setTitle("YearPicker"); | ||
stage.setScene(scene); | ||
stage.sizeToScene(); | ||
stage.centerOnScreen(); | ||
stage.show(); | ||
} | ||
|
||
public static void main(String[] args) { | ||
launch(); | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/YearViewApp.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.dlsc.gemsfx.demo; | ||
|
||
import com.dlsc.gemsfx.YearView; | ||
import fr.brouillard.oss.cssfx.CSSFX; | ||
import javafx.application.Application; | ||
import javafx.geometry.Insets; | ||
import javafx.geometry.Pos; | ||
import javafx.scene.Scene; | ||
import javafx.scene.layout.Region; | ||
import javafx.scene.layout.VBox; | ||
import javafx.stage.Stage; | ||
|
||
public class YearViewApp extends Application { | ||
|
||
@Override | ||
public void start(Stage stage) { | ||
YearView view = new YearView(); | ||
view.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); | ||
|
||
VBox vBox = new VBox(view); | ||
|
||
vBox.setPadding(new Insets(20)); | ||
vBox.setAlignment(Pos.CENTER); | ||
|
||
Scene scene = new Scene(vBox); | ||
CSSFX.start(); | ||
|
||
stage.setTitle("YearView"); | ||
stage.setScene(scene); | ||
stage.sizeToScene(); | ||
stage.centerOnScreen(); | ||
stage.show(); | ||
} | ||
|
||
public static void main(String[] args) { | ||
launch(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package com.dlsc.gemsfx; | ||
|
||
import com.dlsc.gemsfx.skins.YearPickerSkin; | ||
import javafx.beans.property.ReadOnlyObjectProperty; | ||
import javafx.beans.property.ReadOnlyObjectWrapper; | ||
import javafx.css.PseudoClass; | ||
import javafx.scene.control.ComboBoxBase; | ||
import javafx.scene.control.Skin; | ||
import javafx.scene.control.TextField; | ||
import javafx.scene.control.TextFormatter; | ||
import javafx.scene.input.KeyCode; | ||
import javafx.scene.input.KeyEvent; | ||
import javafx.scene.layout.Region; | ||
import javafx.util.converter.NumberStringConverter; | ||
import org.apache.commons.lang3.StringUtils; | ||
|
||
import java.text.DecimalFormat; | ||
import java.text.ParsePosition; | ||
import java.time.Year; | ||
import java.util.function.UnaryOperator; | ||
|
||
public class YearPicker extends ComboBoxBase<Year> { | ||
|
||
private final TextField editor = new TextField(); | ||
private final NumberStringFilteredConverter converter = new NumberStringFilteredConverter(); | ||
|
||
public YearPicker() { | ||
getStyleClass().setAll("year-picker", "text-input"); | ||
|
||
setFocusTraversable(false); | ||
|
||
valueProperty().addListener((obs, oldV, newV) -> { | ||
updateText(newV); | ||
year.set(newV == null ? null : newV.getValue()); | ||
}); | ||
|
||
editor.setTextFormatter(new TextFormatter<>(converter, null, converter.getFilter())); | ||
editor.editableProperty().bind(editableProperty()); | ||
editor.setOnAction(evt -> commit()); | ||
editor.focusedProperty().addListener(it -> { | ||
if (!editor.isFocused()) { | ||
commit(); | ||
} | ||
pseudoClassStateChanged(PseudoClass.getPseudoClass("focused"), editor.isFocused()); | ||
}); | ||
|
||
editor.addEventHandler(KeyEvent.KEY_PRESSED, evt -> { | ||
Year value = getValue(); | ||
if (value != null) { | ||
if (evt.getCode().equals(KeyCode.DOWN)) { | ||
setValue(value.plusYears(1)); | ||
} else if (evt.getCode().equals(KeyCode.UP)) { | ||
setValue(value.minusYears(1)); | ||
} | ||
} | ||
}); | ||
|
||
setMaxWidth(Region.USE_PREF_SIZE); | ||
updateText(null); | ||
} | ||
|
||
/** | ||
* Returns the text field control used for manual input. | ||
* | ||
* @return the editor / text field | ||
*/ | ||
public final TextField getEditor() { | ||
return editor; | ||
} | ||
|
||
@Override | ||
protected Skin<?> createDefaultSkin() { | ||
return new YearPickerSkin(this); | ||
} | ||
|
||
@Override | ||
public String getUserAgentStylesheet() { | ||
return YearMonthView.class.getResource("year-picker.css").toExternalForm(); | ||
} | ||
|
||
private final ReadOnlyObjectWrapper<Integer> year = new ReadOnlyObjectWrapper<>(this, "wrapper"); | ||
|
||
public final ReadOnlyObjectProperty<Integer> yearProperty() { | ||
return year.getReadOnlyProperty(); | ||
} | ||
|
||
public final Integer getYear() { | ||
return year.get(); | ||
} | ||
|
||
private void commit() { | ||
String text = editor.getText(); | ||
if (StringUtils.isNotBlank(text)) { | ||
Number value = converter.fromString(text); | ||
if (value != null) { | ||
setValue(Year.of(value.intValue())); | ||
} else { | ||
setValue(null); | ||
} | ||
} | ||
} | ||
|
||
private void updateText(Year value) { | ||
if (value != null) { | ||
editor.setText(String.valueOf(value.getValue())); | ||
} else { | ||
editor.setText(""); | ||
} | ||
editor.positionCaret(editor.getText().length()); | ||
} | ||
|
||
static class NumberStringFilteredConverter extends NumberStringConverter { | ||
|
||
public NumberStringFilteredConverter() { | ||
super(new DecimalFormat("####")); | ||
} | ||
|
||
UnaryOperator<TextFormatter.Change> getFilter() { | ||
return change -> { | ||
String newText = change.getControlNewText(); | ||
|
||
if (!newText.isEmpty()) { | ||
// Convert to number | ||
ParsePosition parsePosition = new ParsePosition(0); | ||
Number value = getNumberFormat().parse(newText, parsePosition); | ||
if (value == null || parsePosition.getIndex() < newText.length()) { | ||
return null; | ||
} | ||
|
||
// Validate max length | ||
if (newText.length() > 4) { | ||
String head = change.getControlNewText().substring(0, 4); | ||
change.setText(head); | ||
int oldLength = change.getControlText().length(); | ||
change.setRange(0, oldLength); | ||
} | ||
} | ||
|
||
return change; | ||
}; | ||
} | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package com.dlsc.gemsfx; | ||
|
||
import com.dlsc.gemsfx.skins.YearViewSkin; | ||
import javafx.beans.property.ObjectProperty; | ||
import javafx.beans.property.SimpleObjectProperty; | ||
import javafx.scene.control.Control; | ||
import javafx.scene.control.Skin; | ||
|
||
import java.time.Year; | ||
|
||
public class YearView extends Control { | ||
|
||
public YearView() { | ||
getStyleClass().add("year-view"); | ||
} | ||
|
||
@Override | ||
protected Skin<?> createDefaultSkin() { | ||
return new YearViewSkin(this); | ||
} | ||
|
||
@Override | ||
public String getUserAgentStylesheet() { | ||
return YearView.class.getResource("year-view.css").toExternalForm(); | ||
} | ||
|
||
private final ObjectProperty<Year> value = new SimpleObjectProperty<>(this, "value"); | ||
|
||
public Year getValue() { | ||
return value.get(); | ||
} | ||
|
||
public ObjectProperty<Year> valueProperty() { | ||
return value; | ||
} | ||
|
||
public void setValue(Year value) { | ||
this.value.set(value); | ||
} | ||
|
||
} |
54 changes: 54 additions & 0 deletions
54
gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearPickerSkin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package com.dlsc.gemsfx.skins; | ||
|
||
import com.dlsc.gemsfx.YearPicker; | ||
import com.dlsc.gemsfx.YearView; | ||
import javafx.scene.Node; | ||
import javafx.scene.layout.HBox; | ||
import javafx.scene.layout.Priority; | ||
import javafx.scene.layout.Region; | ||
import javafx.scene.layout.StackPane; | ||
import org.kordamp.ikonli.javafx.FontIcon; | ||
|
||
import java.util.Objects; | ||
|
||
public class YearPickerSkin extends CustomComboBoxSkinBase<YearPicker> { | ||
|
||
private YearView yearView; | ||
|
||
public YearPickerSkin(YearPicker picker) { | ||
super(picker); | ||
|
||
picker.setOnMouseClicked(evt -> picker.show()); | ||
|
||
FontIcon calendarIcon = new FontIcon(); | ||
calendarIcon.getStyleClass().add("edit-icon"); // using styles similar to combobox, for consistency | ||
|
||
StackPane editButton = new StackPane(calendarIcon); | ||
editButton.setFocusTraversable(false); | ||
editButton.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); | ||
editButton.getStyleClass().add("edit-button"); // using styles similar to combobox, for consistency | ||
editButton.setOnMouseClicked(evt -> picker.show()); | ||
|
||
HBox.setHgrow(picker.getEditor(), Priority.ALWAYS); | ||
|
||
HBox box = new HBox(picker.getEditor(), editButton); | ||
box.getStyleClass().add("box"); | ||
|
||
getChildren().add(box); | ||
} | ||
|
||
@Override | ||
protected Node getPopupContent() { | ||
if (yearView == null) { | ||
yearView = new YearView(); | ||
yearView.valueProperty().bindBidirectional(getSkinnable().valueProperty()); | ||
yearView.valueProperty().addListener((obs, oldValue, newValue) -> { | ||
if (!Objects.equals(oldValue, newValue)) { | ||
getSkinnable().hide(); | ||
} | ||
}); | ||
} | ||
return yearView; | ||
} | ||
|
||
} |
Oops, something went wrong.