├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── pom.xml
├── screenshot
├── datepicker-dark.png
├── datepicker-light.png
├── timepicker-dark.png
└── timepicker-light.png
└── src
├── main
├── java
│ └── raven
│ │ └── datetime
│ │ ├── DatePicker.java
│ │ ├── DateSelectionAble.java
│ │ ├── PanelDateOption.java
│ │ ├── PanelDateOptionLabel.java
│ │ ├── TimePicker.java
│ │ ├── TimeSelectionAble.java
│ │ ├── component
│ │ ├── PanelPopupEditor.java
│ │ ├── date
│ │ │ ├── ButtonDate.java
│ │ │ ├── ButtonMonthYear.java
│ │ │ ├── DateSelectionModel.java
│ │ │ ├── DefaultDateCellRenderer.java
│ │ │ ├── Header.java
│ │ │ ├── PanelDate.java
│ │ │ ├── PanelMonth.java
│ │ │ ├── PanelYear.java
│ │ │ ├── SingleDate.java
│ │ │ └── event
│ │ │ │ ├── DateControlEvent.java
│ │ │ │ ├── DateControlListener.java
│ │ │ │ ├── DateSelectionModelEvent.java
│ │ │ │ └── DateSelectionModelListener.java
│ │ └── time
│ │ │ ├── AnimationChange.java
│ │ │ ├── Header.java
│ │ │ ├── PanelClock.java
│ │ │ ├── TimeSelectionModel.java
│ │ │ └── event
│ │ │ ├── TimeActionListener.java
│ │ │ ├── TimeSelectionModelEvent.java
│ │ │ └── TimeSelectionModelListener.java
│ │ ├── event
│ │ ├── DateSelectionEvent.java
│ │ ├── DateSelectionListener.java
│ │ ├── TimeSelectionEvent.java
│ │ └── TimeSelectionListener.java
│ │ ├── swing
│ │ └── slider
│ │ │ ├── PanelSlider.java
│ │ │ ├── SimpleTransition.java
│ │ │ └── SliderTransition.java
│ │ └── util
│ │ ├── InputUtils.java
│ │ ├── InputValidationListener.java
│ │ └── Utils.java
└── resources
│ └── raven
│ └── datetime
│ └── icon
│ ├── back.svg
│ ├── calendar.svg
│ ├── clock.svg
│ └── forward.svg
└── test
└── java
└── test
├── TestDate.java
├── TestFrame.java
├── TestTime.java
└── renderer
├── CustomDateCellRenderer.java
├── LabelDate.java
└── TestDateCellRenderer.java
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build and Release
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | tags:
7 | - 'v[0-9]*'
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-24.04
12 | name: Build JAR and Release
13 | steps:
14 | - uses: actions/checkout@v2
15 |
16 | - name: Set up JDK 11
17 | uses: actions/setup-java@v2
18 | with:
19 | java-version: '11'
20 | distribution: 'adopt'
21 |
22 | - name: Cache local Maven repository
23 | uses: actions/cache@v3
24 | with:
25 | path: ~/.m2/repository
26 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
27 | restore-keys: |
28 | ${{ runner.os }}-maven-
29 |
30 | - name: Build JAR with Maven
31 | run: mvn clean package -DskipTests --file pom.xml
32 |
33 | - name: Get Changelog Entry
34 | id: changelog_reader
35 | uses: mindsers/changelog-reader-action@v2
36 | with:
37 | validation_level: warn
38 | version: ${{ steps.tag_name.outputs.current_version }}
39 | path: ./CHANGELOG.md
40 |
41 | - name: Upload Release Asset
42 | uses: softprops/action-gh-release@v2
43 | with:
44 | files: ./target/*.jar
45 | tag_name: ${{ github.ref_name }}
46 | name: ${{ github.ref_name }}
47 | body: ${{ steps.changelog_reader.outputs.changes }}
48 | generate_release_notes: true
49 | env:
50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | !.mvn/wrapper/maven-wrapper.jar
3 | !**/src/main/**/target/
4 | !**/src/test/**/target/
5 |
6 | ### IntelliJ IDEA ###
7 | .idea/
8 | *.iws
9 | *.iml
10 | *.ipr
11 |
12 | ### Eclipse ###
13 | .apt_generated
14 | .classpath
15 | .factorypath
16 | .project
17 | .settings
18 | .springBeans
19 | .sts4-cache
20 |
21 | ### NetBeans ###
22 | /nbproject/private/
23 | /nbbuild/
24 | /dist/
25 | /nbdist/
26 | /.nb-gradle/
27 | build/
28 | !**/src/main/**/build/
29 | !**/src/test/**/build/
30 |
31 | ### VS Code ###
32 | .vscode/
33 |
34 | ### Mac OS ###
35 | .DS_Store
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [2.1.2] - 2025-06-08
4 |
5 | ### New features and improvements
6 |
7 | - DatePicker:
8 | - Add new option `defaultDateCellRenderer` to custom date cell renderer (issue #18) and (PR #19)
9 | - Add global `DatePicker.defaultWeekdays` for change the default label weekdays (issue #20)
10 | - Add new method `getEditor()` return as `JFormattedTextField` is editor has set
11 |
12 | ### Changed
13 |
14 | - TimePicker:
15 | - Time header button AM and PM now use `Locale.ENGLISH` as string symbols
16 |
17 | ### Fixed bugs
18 |
19 | - DatePicker:
20 | - Fixed incorrect start day of week when `startWeekOnMonday` is true (issue #21)
21 | - TimePicker:
22 | - Fixed incorrect selection location in 24 hour view.
23 |
24 | ## [2.1.1] - 2025-05-05
25 |
26 | ### Changed
27 |
28 | - DatePicker:
29 | - Removed component fixed value size. (previous width:260, height:250)
30 | - Add new option `selectionArc` default `999f`
31 |
32 | ## [2.1.0] - 2025-04-25
33 |
34 | ### New features and improvements
35 |
36 | - DatePicker:
37 | - Add new option `startWeekOnMonday` for show the monday is the start of the week (default `false`)
38 | - Add new method `toDateSelectionView()` for show date selection state (show as current selected date. if selected
39 | date `null` show as system current date) and this method call by default when show with popup
40 | - TimePicker:
41 | - Add new method `setHourSelectionView(boolean hourSelectionView)` if `true` show hour selection state. if `false`
42 | show minute selection state. and this method call by default when show with popup
43 | - Date and Time Popup:
44 | - Add new method:
45 | - `showPopup(Component component)` to show datepicker or timePicker with popup without editor
46 | - `setPopupSpace(Point popupSpace)` to set the popup space with component or editor
47 |
48 | ### Changed
49 |
50 | - Add popup frame insets default `(5,5,5,5)`
51 | - FlatLaf: update to `v3.6`
52 | - Test: removed `flatlaf-fonts-roboto` and style properties in resources
53 |
54 | ## [2.0.0] - 2024-12-24
55 |
56 | ### New features and improvements
57 |
58 | - DatePicker:
59 | - When disabled, users can no longer change the date selection
60 | - TimePicker:
61 | - When disabled, users can no longer change the time selection
62 | - Add time selection able and editor input validation check (issue #14) and (PR #15)
63 |
64 | ### Changed
65 |
66 | - Rewrote and refactored existing code for improved readability and performance
67 | - Move `raven.datetime.component.date.DatePicker` to `raven.datetime.DatePicker`
68 | - Move `raven.datetime.component.time.TimePicker` to `raven.datetime.TimePicker`
69 |
70 | ## [1.4.1] - 2024-11-29
71 |
72 | ### New features and improvements
73 |
74 | - DatePicker:
75 | - Add new option `animationEnabled`
76 | - Date popup in `SINGLE_DATE_SELECTED` mode, now auto close when double click
77 | - Add `PanelDateOptionLabel` to custom panel date option (PR #10)
78 |
79 | ### Changed
80 |
81 | - FlatLaf: update to v3.5.2
82 |
83 | ### Fixed bugs
84 |
85 | - DatePicker:
86 | - Fixed editor input validation
87 | - Miglayout use `novisualpadding` and set slider fix size as `260,250`
88 | - TimePicker:
89 | - Fixed `editor` not changed value when switch between `24h` and `12h` view
90 |
91 | ## [1.4.0] - 2024-09-14
92 |
93 | ### New features and improvements
94 |
95 | - DatePicker:
96 | - Add editor input validation (PR #9)
97 |
98 | ### Changed
99 |
100 | - Miglayout: back to v5.3 to support java 8
101 |
102 | ## [1.3.0] - 2024-07-05
103 |
104 | ### New features and improvements
105 |
106 | - DatePicker:
107 | - Add new method:
108 | - `setDateFormat(String format)` to format date (issues #5)
109 | - `setColor(Color color)` (issues #6)
110 |
111 | ### Fixed bugs
112 |
113 | - PanelSlider `flush` image after end of animation
114 | - Fixed editor popup error when using JDialog
115 |
116 | ## [1.2.0] - 2024-05-06
117 |
118 | ### New features and improvements
119 |
120 | - DatePicker:
121 | - Add new method `selectCurrentMonth()` (PR #2)
122 | - TimePicker:
123 | - Add new method `setColor(Color color)` (issues #3)
124 | - Popup menu will show inside the windows frame
125 | - Add new method `setEditorIcon(Icon icon)`
126 |
127 | ### Fixed bugs
128 |
129 | - DatePicker:
130 | - Invert between date selected (when `date` is after `toDate`) (PR #2)
131 | - Fixed `between date` resend value when editor value changed
132 | - TimePicker:
133 | - Fixed event time changed not work while `am` or `pm` changed in editor
134 | - Fixed time format by use `Locale.ENGLISH` (issues #4)
135 | - Fixed reset old editor to default after changed new editor
136 | - Fixed event `SelectionListener` invoke only value changed
137 |
138 | ## [1.1.0] - 2024-04-13
139 |
140 | ### New features and improvements
141 |
142 | - Add new datepicker (PR #1)
143 | - Update style background in timepicker
144 |
145 | ## [1.0.0] - 2023-12-17
146 |
147 | - Initial release
148 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Raven Laing
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Swing Datetime Picker
2 | A simple datetime picker implementation using Java Swing, built with the `flatlaf` UI library and `miglayout` for layout management.
3 |
4 | This project provides a datetime picker component that can be easily integrated into Java Swing applications. It leverages flatlaf for a modern look and miglayout for flexible and easy-to-use layout management.
5 |
6 |
7 |
8 |
9 |
10 |
11 | ## Installation
12 |
13 | [](https://central.sonatype.com/artifact/io.github.dj-raven/swing-datetime-picker/versions)
14 |
15 | Add the dependency
16 | ``` xml
17 |
18 | io.github.dj-raven
19 | swing-datetime-picker
20 | 2.1.2
21 |
22 | ```
23 |
24 | ### Snapshots
25 | To get the latest updates before the release, you can use the snapshot version from [Sonatype Central](https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/io/github/dj-raven/swing-datetime-picker/)
26 |
27 | ``` xml
28 |
29 |
30 | Central Portal Snapshots
31 | central-portal-snapshots
32 | https://central.sonatype.com/repository/maven-snapshots/
33 |
34 |
35 | ```
36 | Add the snapshot version
37 | ``` xml
38 |
39 | io.github.dj-raven
40 | swing-datetime-picker
41 | 2.1.2-SNAPSHOT
42 |
43 | ```
44 |
45 | ## Usage TimePicker
46 | | Method | Return Value | Description |
47 | | ------------ | ------------ |----------------------------------------------------------------|
48 | | now() | | set the time to current local time |
49 | | setSelectedTime(LocalTime time) | | set the time to a specific value |
50 | | clearSelectedTime() | | clear the selected time |
51 | | isTimeSelected() | `boolean` | check time is selected |
52 | | getSelectedTime() | `LocalTime` | return the selected time |
53 | | getSelectedTimeAsString() | `String` | return selected time as string |
54 | | addTimeSelectionListener(TimeSelectionListener event) | | add event time selection |
55 | | removeTimeSelectionListener(TimeSelectionListener event) | | remove event time selection |
56 | | setOrientation(int orientation) | | `SwingConstants.VERTICAL` or `SwingConstants.HORIZONTAL` |
57 | | setEditor(JFormattedTextField editor) | | display the selected time on the editor and allow to edit time |
58 | | setTimeSelectionAble(TimeSelectionAble dsb) | | set time selection able |
59 | | set24HourView(boolean hour24) | | set time to 24h selection view |
60 | | is24HourView() | `boolean` | return `ture` is 24h selection view |
61 | | showPopup() | | if time have editor, timepicker will show up with popup menu |
62 | | closePopup() | | close editor popup |
63 | | setColor(Color color) | | change base color |
64 | | setEditorIcon(Icon icon) | | change icon to editor |
65 | | setEditorValidation(boolean validation) | | validation editor |
66 | | void setValidationOnNull(boolean validationOnNull) | | validation editor on null selection |
67 | | void showPopup(Component component) | | show timePicker with popup without editor |
68 | | void setPopupSpace(Point popupSpace) | | set the popup space with component or editor |
69 | | void toDateSelectionView() | | show date selection state |
70 |
71 | ## Usage DatePicker
72 | | Method | Return Value | Description |
73 | |--------------------------------------------------------| ----------- | ------------ |
74 | | now() | | set the date to current local date |
75 | | setToBack() | | slide panel to back with animation |
76 | | setToForward() | | slide panel to forward with animation |
77 | | selectMonth() | | show panel month slide with animation |
78 | | selectYear() | | show panel year slide with animation |
79 | | slideTo(LocalDate date) | | slide panel to specific date |
80 | | getDateSelectionMode() | `DateSelectionMode` | return the date selection mode |
81 | | setDateSelectionMode(DateSelectionMode mode) | | set mode `SINGLE_DATE_SELECTED` or `BETWEEN_DATE_SELECTED` |
82 | | setSelectedDate(LocalDate date) | | set the date to a specific value |
83 | | setSelectedDateRange(LocalDate from, LocalDate to) | | set the date range to a specific value |
84 | | setEditor(JFormattedTextField editor) | | display the selected date on the editor and allow to edit date |
85 | | setDateSelectionAble(DateSelectionAble dsb) | | set date selection able |
86 | | showPopup() | | if date have editor, datepicker will show up with popup menu |
87 | | closePopup() | | close editor popup |
88 | | setSeparator(String separator) | | set separator to between date |
89 | | setUsePanelOption(boolean usePanelOption) | | set datepicker use panel option |
90 | | setCloseAfterSelected(boolean closeAfterSelected) | | if true popup will close after selected date |
91 | | clearSelectedDate() | | clear the selected date |
92 | | isDateSelected() | `boolean` | check date is selected |
93 | | getSelectedDate() | `LocalDate` | return the selected date |
94 | | getSelectedDateRange() | `LocalDate[]` | return the selected date range |
95 | | getSelectedDateAsString() | `String` | return selected date as string |
96 | | addDateSelectionListener(DateSelectionListener evt) | | add event date selection |
97 | | removeDateSelectionListener(DateSelectionListener evt) | | remove event date selection |
98 | | selectCurrentMonth() | | select from first day to current day in current month |
99 | | setColor(Color color) | | change base color |
100 | | setEditorIcon(Icon icon) | | change icon to editor |
101 | | setDateFormat(String format) | | change date format |
102 | | setEditorValidation(boolean validation) | | validation editor |
103 | | void setValidationOnNull(boolean validationOnNull) | | validation editor on null selection |
104 | | void setAnimationEnabled(boolean animationEnabled) | | enable or disabled the animation |
105 | | void setPanelDateOptionLabel(PanelDateOptionLabel opt) | | set new panel date option label |
106 | | void setStartWeekOnMonday(boolean startWeekOnMonday) | | show the monday is the start of the week |
107 | | void showPopup(Component component) | | show datePicker with popup without editor |
108 | | void setPopupSpace(Point popupSpace) | | set the popup space with component or editor |
109 | | void setHourSelectionView(boolean hourSelectionView) | | show hour or minute selection state |
110 | | void setSelectionArc(float selectionArc) | | set date selection border arc |
111 | | void setDefaultDateCellRenderer(DefaultDateCellRenderer renderer) | | set date cell renderer to paint custom graphics |
112 | | public static void setDefaultWeekdays(String[] defaultWeekdays) | | `static` method for set default label week days |
113 |
114 | ## Library Resources
115 | - [FlatLaf](https://github.com/JFormDesigner/FlatLaf) - FlatLaf library for the modern UI design theme
116 | - [MigLayout](https://github.com/mikaelgrev/miglayout) - MigLayout library for flexible layout management
117 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | io.github.dj-raven
8 | swing-datetime-picker
9 | 2.1.2
10 |
11 |
12 | 1.8
13 | 1.8
14 | UTF-8
15 |
16 |
17 | Java Swing Datetime Picker
18 | Swing library build with flatlaf
19 | https://github.com/DJ-Raven/swing-datetime-picker
20 |
21 |
22 |
23 | MIT License
24 | http://www.opensource.org/licenses/mit-license.php
25 |
26 |
27 |
28 |
29 |
30 | Raven Laing
31 | com.github.dj-raven
32 | https://github.com/dj-raven
33 |
34 |
35 |
36 |
37 | scm:git:git://github.com/dj-raven/swing-datetime-picker.git
38 | scm:git:ssh://github.com:dj-raven/swing-datetime-picker.git
39 | https://github.com/dj-raven/swing-datetime-picker
40 |
41 |
42 |
43 |
44 | com.formdev
45 | flatlaf
46 | 3.6
47 |
48 |
49 | com.formdev
50 | flatlaf-extras
51 | 3.6
52 |
53 |
54 | com.miglayout
55 | miglayout-swing
56 | 5.3
57 |
58 |
59 |
60 | com.formdev
61 | flatlaf-intellij-themes
62 | 3.6
63 | test
64 |
65 |
66 |
67 |
68 |
69 |
70 | org.sonatype.central
71 | central-publishing-maven-plugin
72 | 0.7.0
73 | true
74 |
75 | central
76 |
77 |
78 |
79 |
80 | org.apache.maven.plugins
81 | maven-source-plugin
82 | 2.2.1
83 |
84 |
85 | attach-sources
86 |
87 | jar-no-fork
88 |
89 |
90 |
91 |
92 |
93 | org.apache.maven.plugins
94 | maven-javadoc-plugin
95 | 2.9.1
96 |
97 |
98 | attach-javadocs
99 |
100 | jar
101 |
102 |
103 |
104 |
105 |
106 |
107 | org.apache.maven.plugins
108 | maven-gpg-plugin
109 | 1.5
110 |
111 |
112 | sign-artifacts
113 | verify
114 |
115 | sign
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/screenshot/datepicker-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DJ-Raven/swing-datetime-picker/f8b61c4c61b63cbd189d72019f0956377dc6b981/screenshot/datepicker-dark.png
--------------------------------------------------------------------------------
/screenshot/datepicker-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DJ-Raven/swing-datetime-picker/f8b61c4c61b63cbd189d72019f0956377dc6b981/screenshot/datepicker-light.png
--------------------------------------------------------------------------------
/screenshot/timepicker-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DJ-Raven/swing-datetime-picker/f8b61c4c61b63cbd189d72019f0956377dc6b981/screenshot/timepicker-dark.png
--------------------------------------------------------------------------------
/screenshot/timepicker-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DJ-Raven/swing-datetime-picker/f8b61c4c61b63cbd189d72019f0956377dc6b981/screenshot/timepicker-light.png
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/DatePicker.java:
--------------------------------------------------------------------------------
1 | package raven.datetime;
2 |
3 | import com.formdev.flatlaf.FlatClientProperties;
4 | import com.formdev.flatlaf.extras.FlatSVGIcon;
5 | import net.miginfocom.swing.MigLayout;
6 | import raven.datetime.component.PanelPopupEditor;
7 | import raven.datetime.component.date.*;
8 | import raven.datetime.component.date.event.DateControlEvent;
9 | import raven.datetime.component.date.event.DateControlListener;
10 | import raven.datetime.component.date.event.DateSelectionModelEvent;
11 | import raven.datetime.component.date.event.DateSelectionModelListener;
12 | import raven.datetime.event.DateSelectionEvent;
13 | import raven.datetime.event.DateSelectionListener;
14 | import raven.datetime.swing.slider.PanelSlider;
15 | import raven.datetime.swing.slider.SimpleTransition;
16 | import raven.datetime.swing.slider.SliderTransition;
17 | import raven.datetime.util.InputUtils;
18 | import raven.datetime.util.InputValidationListener;
19 |
20 | import javax.swing.*;
21 | import javax.swing.event.ChangeEvent;
22 | import javax.swing.event.ChangeListener;
23 | import java.awt.*;
24 | import java.time.LocalDate;
25 | import java.time.format.DateTimeFormatter;
26 | import java.util.Arrays;
27 |
28 | public class DatePicker extends PanelPopupEditor implements DateSelectionModelListener, DateControlListener, ChangeListener {
29 |
30 | private static String[] defaultWeekdays = null;
31 |
32 | private DateTimeFormatter format;
33 | private String dateFormatPattern = "dd/MM/yyyy";
34 | private DateSelectionListener dateSelectionListener;
35 | private InputValidationListener inputValidationListener;
36 | private DateSelectionModel dateSelectionModel;
37 | private PanelDateOption panelDateOption;
38 | private PanelDateOptionLabel panelDateOptionLabel;
39 | private InputUtils.ValueCallback valueCallback;
40 | private Icon editorIcon;
41 | private String separator = " to ";
42 | private boolean usePanelOption;
43 | private boolean closeAfterSelected;
44 | private boolean animationEnabled = true;
45 | private boolean startWeekOnMonday;
46 | private float selectionArc = 999;
47 | private int month = 10;
48 | private int year = 2023;
49 | private Color color;
50 | private JButton editorButton;
51 | private SelectionState selectionState = SelectionState.DATE;
52 | private PanelDate panelDate;
53 | private PanelMonth panelMonth;
54 | private PanelYear panelYear;
55 |
56 | private DefaultDateCellRenderer defaultDateCellRenderer = new DefaultDateCellRenderer();
57 | private final Header header = new Header();
58 | private final PanelSlider panelSlider = new PanelSlider();
59 |
60 | public DatePicker() {
61 | this(null);
62 | }
63 |
64 | public DatePicker(DateSelectionModel dateSelectionModel) {
65 | init(dateSelectionModel);
66 | }
67 |
68 | private void init(DateSelectionModel dateSelectionModel) {
69 | putClientProperty(FlatClientProperties.STYLE, "" +
70 | "[light]background:darken($Panel.background,2%);" +
71 | "[dark]background:lighten($Panel.background,2%);");
72 | setLayout(new MigLayout(
73 | "wrap,insets 10,fill",
74 | "[fill]",
75 | "[top,grow 0][center,fill]"));
76 |
77 | format = DateTimeFormatter.ofPattern(dateFormatPattern);
78 | header.addDateControlListener(this);
79 |
80 | if (dateSelectionModel == null) {
81 | dateSelectionModel = createDefaultDateSelection();
82 | }
83 | setDateSelectionModel(dateSelectionModel);
84 |
85 | add(header);
86 | add(panelSlider);
87 | initDate();
88 | }
89 |
90 | private void initDate() {
91 | LocalDate date = LocalDate.now();
92 | int month = date.getMonthValue() - 1;
93 | int year = date.getYear();
94 | this.month = month;
95 | this.year = year;
96 | header.setDate(month, year);
97 | panelSlider.addSlide(createPanelDate(month, year), null);
98 | }
99 |
100 | public void setToBack() {
101 | if (selectionState == SelectionState.DATE) {
102 | if (month == 0) {
103 | month = 11;
104 | year--;
105 | } else {
106 | month--;
107 | }
108 | header.setDate(month, year);
109 | panelSlider.addSlide(createPanelDate(month, year), getSliderTransition(SimpleTransition.SliderType.BACK));
110 | } else if (selectionState == SelectionState.MONTH) {
111 | year--;
112 | header.setDate(month, year);
113 | panelSlider.addSlide(createPanelMonth(year), getSliderTransition(SimpleTransition.SliderType.BACK));
114 | } else {
115 | int oldYear = this.panelYear.getYear();
116 | panelSlider.addSlide(createPanelYear(oldYear - PanelYear.YEAR_CELL), getSliderTransition(SimpleTransition.SliderType.BACK));
117 | }
118 | }
119 |
120 | public void setToForward() {
121 | if (selectionState == SelectionState.DATE) {
122 | if (month == 11) {
123 | month = 0;
124 | year++;
125 | } else {
126 | month++;
127 | }
128 | header.setDate(month, year);
129 | panelSlider.addSlide(createPanelDate(month, year), getSliderTransition(SimpleTransition.SliderType.FORWARD));
130 | } else if (selectionState == SelectionState.MONTH) {
131 | year++;
132 | header.setDate(month, year);
133 | panelSlider.addSlide(createPanelMonth(year), getSliderTransition(SimpleTransition.SliderType.FORWARD));
134 | } else {
135 | int oldYear = this.panelYear.getYear();
136 | panelSlider.addSlide(createPanelYear(oldYear + PanelYear.YEAR_CELL), getSliderTransition(SimpleTransition.SliderType.FORWARD));
137 | }
138 | }
139 |
140 | public void selectMonth() {
141 | if (selectionState != SelectionState.MONTH) {
142 | panelSlider.addSlide(createPanelMonth(year), getSliderTransition(selectionState == SelectionState.DATE ? SimpleTransition.SliderType.TOP_DOWN : SimpleTransition.SliderType.DOWN_TOP));
143 | selectionState = SelectionState.MONTH;
144 | } else {
145 | panelSlider.addSlide(createPanelDate(month, year), getSliderTransition(SimpleTransition.SliderType.DOWN_TOP));
146 | selectionState = SelectionState.DATE;
147 | }
148 | }
149 |
150 | public void selectYear() {
151 | if (selectionState != SelectionState.YEAR) {
152 | panelSlider.addSlide(createPanelYear(year), getSliderTransition(SimpleTransition.SliderType.TOP_DOWN));
153 | selectionState = SelectionState.YEAR;
154 | } else {
155 | panelSlider.addSlide(createPanelDate(month, year), getSliderTransition(SimpleTransition.SliderType.DOWN_TOP));
156 | selectionState = SelectionState.DATE;
157 | }
158 | }
159 |
160 | public DateSelectionMode getDateSelectionMode() {
161 | return dateSelectionModel.getDateSelectionMode();
162 | }
163 |
164 | public void setDateSelectionMode(DateSelectionMode dateSelectionMode) {
165 | if (getDateSelectionMode() != dateSelectionMode) {
166 | this.dateSelectionModel.setDateSelectionMode(dateSelectionMode);
167 | if (editor != null) {
168 | InputUtils.changeDateFormatted(editor, dateFormatPattern, getDateSelectionMode() == DateSelectionMode.BETWEEN_DATE_SELECTED, separator, getInputValidationListener());
169 | this.defaultPlaceholder = null;
170 | clearSelectedDate();
171 | commitEdit();
172 | }
173 | repaint();
174 | }
175 | }
176 |
177 | public void now() {
178 | LocalDate date = LocalDate.now();
179 | if (getDateSelectionMode() == DateSelectionMode.BETWEEN_DATE_SELECTED) {
180 | setSelectedDateRange(date, date);
181 | } else {
182 | setSelectedDate(date);
183 | }
184 | }
185 |
186 | public void toDateSelectionView() {
187 | LocalDate date = getSelectedDate();
188 | if (date == null) {
189 | date = LocalDate.now();
190 | }
191 | int m = date.getMonthValue() - 1;
192 | int y = date.getYear();
193 | if (selectionState != SelectionState.DATE || y != year || m != month) {
194 | panelSlider.addSlide(createPanelDate(m, y), getSliderTransition(SimpleTransition.SliderType.DEFAULT));
195 | month = m;
196 | year = y;
197 | selectionState = SelectionState.DATE;
198 | header.setDate(month, year);
199 | updateSelected();
200 | }
201 | }
202 |
203 | public void selectCurrentMonth() {
204 | LocalDate date = LocalDate.now();
205 | if (getDateSelectionMode() == DateSelectionMode.BETWEEN_DATE_SELECTED) {
206 | setSelectedDateRange(date.withDayOfMonth(1), date);
207 | } else {
208 | setSelectedDate(date);
209 | }
210 | }
211 |
212 | public void setSelectedDate(LocalDate date) {
213 | dateSelectionModel.setDate(new SingleDate(date));
214 | if (getDateSelectionMode() == DateSelectionMode.BETWEEN_DATE_SELECTED) {
215 | dateSelectionModel.setToDate(new SingleDate(date));
216 | }
217 | slideTo(date);
218 | }
219 |
220 | public void setSelectedDateRange(LocalDate from, LocalDate to) {
221 | if (getDateSelectionMode() == DateSelectionMode.SINGLE_DATE_SELECTED) {
222 | throw new IllegalArgumentException("Single date mode can't accept the range date");
223 | }
224 | dateSelectionModel.setSelectDate(new SingleDate(from), new SingleDate(to));
225 | slideTo(from);
226 | }
227 |
228 | public void setEditor(JFormattedTextField editor) {
229 | if (editor != this.editor) {
230 | JFormattedTextField old = this.editor;
231 | if (old != null) {
232 | uninstallEditor(old);
233 | }
234 | this.editor = editor;
235 | if (this.editor != null) {
236 | installEditor(editor);
237 | if (editorValidation) {
238 | validChanged(editor, isValid);
239 | } else {
240 | validChanged(editor, true);
241 | }
242 | }
243 | }
244 | }
245 |
246 | public Icon getEditorIcon() {
247 | return editorIcon;
248 | }
249 |
250 | public void setEditorIcon(Icon editorIcon) {
251 | this.editorIcon = editorIcon;
252 | editorButton.setIcon(editorIcon);
253 | }
254 |
255 | public DateSelectionAble getDateSelectionAble() {
256 | return dateSelectionModel.getDateSelectionAble();
257 | }
258 |
259 | public void setDateSelectionAble(DateSelectionAble dateSelectionAble) {
260 | this.dateSelectionModel.setDateSelectionAble(dateSelectionAble);
261 | if (selectionState == SelectionState.DATE) {
262 | panelDate.load();
263 | }
264 | commitEdit();
265 | }
266 |
267 | public Color getColor() {
268 | return color;
269 | }
270 |
271 | public void setColor(Color color) {
272 | this.color = color;
273 | repaint();
274 | }
275 |
276 | public String getSeparator() {
277 | return separator;
278 | }
279 |
280 | public void setSeparator(String separator) {
281 | if (separator == null) {
282 | throw new IllegalArgumentException("separator can't be null");
283 | }
284 | if (!this.separator.equals(separator)) {
285 | this.separator = separator;
286 | if (editor != null) {
287 | InputUtils.changeDateFormatted(editor, dateFormatPattern, getDateSelectionMode() == DateSelectionMode.BETWEEN_DATE_SELECTED, separator, getInputValidationListener());
288 | this.defaultPlaceholder = null;
289 | setEditorValue();
290 | }
291 | }
292 | }
293 |
294 | public String getDateFormat() {
295 | return this.dateFormatPattern;
296 | }
297 |
298 | public void setDateFormat(String format) {
299 | if (format == null) {
300 | throw new IllegalArgumentException("format can't be null");
301 | }
302 | if (!this.dateFormatPattern.equals(format)) {
303 | this.format = DateTimeFormatter.ofPattern(format);
304 | if (editor != null) {
305 | InputUtils.changeDateFormatted(editor, format, getDateSelectionMode() == DateSelectionMode.BETWEEN_DATE_SELECTED, separator, getInputValidationListener());
306 | this.defaultPlaceholder = null;
307 | }
308 | this.dateFormatPattern = format;
309 | }
310 | }
311 |
312 | public boolean isUsePanelOption() {
313 | return usePanelOption;
314 | }
315 |
316 | public void setUsePanelOption(boolean usePanelOption) {
317 | if (this.usePanelOption != usePanelOption) {
318 | this.usePanelOption = usePanelOption;
319 | if (usePanelOption) {
320 | if (panelDateOption == null) {
321 | panelDateOption = new PanelDateOption(this);
322 | panelDateOption.installDateOptionLabel();
323 | }
324 | add(panelDateOption, "dock east,gap 0 10 10 10");
325 | repaint();
326 | revalidate();
327 | } else {
328 | if (panelDateOption != null) {
329 | remove(panelDateOption);
330 | panelDateOption = null;
331 | repaint();
332 | revalidate();
333 | }
334 | }
335 | }
336 | }
337 |
338 | public PanelDateOptionLabel getPanelDateOptionLabel() {
339 | return panelDateOptionLabel;
340 | }
341 |
342 | public void setPanelDateOptionLabel(PanelDateOptionLabel panelDateOptionLabel) {
343 | if (panelDateOptionLabel == null) {
344 | throw new IllegalArgumentException("panelDateOptionLabel can't be null");
345 | }
346 | this.panelDateOptionLabel = panelDateOptionLabel;
347 | if (panelDateOption != null) {
348 | panelDateOption.installDateOptionLabel();
349 | }
350 | }
351 |
352 | public boolean isCloseAfterSelected() {
353 | return closeAfterSelected;
354 | }
355 |
356 | public void setCloseAfterSelected(boolean closeAfterSelected) {
357 | this.closeAfterSelected = closeAfterSelected;
358 | }
359 |
360 | public boolean isAnimationEnabled() {
361 | return animationEnabled;
362 | }
363 |
364 | public void setAnimationEnabled(boolean animationEnabled) {
365 | this.animationEnabled = animationEnabled;
366 | }
367 |
368 | public boolean isStartWeekOnMonday() {
369 | return startWeekOnMonday;
370 | }
371 |
372 | public void setStartWeekOnMonday(boolean startWeekOnMonday) {
373 | if (this.startWeekOnMonday != startWeekOnMonday) {
374 | this.startWeekOnMonday = startWeekOnMonday;
375 | if (selectionState == SelectionState.DATE && panelDate != null) {
376 | // update the panel date
377 | panelDate.load();
378 | panelDate.repaint();
379 | panelDate.revalidate();
380 | }
381 | }
382 | }
383 |
384 | public float getSelectionArc() {
385 | return selectionArc;
386 | }
387 |
388 | public void setSelectionArc(float selectionArc) {
389 | if (this.selectionArc != selectionArc) {
390 | this.selectionArc = selectionArc;
391 | updateSelected();
392 | }
393 | }
394 |
395 | public void clearSelectedDate() {
396 | dateSelectionModel.setSelectDate(null, null);
397 | updateSelected();
398 | }
399 |
400 | public boolean isDateSelected() {
401 | if (getDateSelectionMode() == DateSelectionMode.SINGLE_DATE_SELECTED) {
402 | return dateSelectionModel.getDate() != null;
403 | } else {
404 | return dateSelectionModel.getDate() != null && dateSelectionModel.getToDate() != null;
405 | }
406 | }
407 |
408 | public LocalDate getSelectedDate() {
409 | SingleDate date = dateSelectionModel.getDate();
410 | if (date != null) {
411 | return date.toLocalDate();
412 | }
413 | return null;
414 | }
415 |
416 | public LocalDate[] getSelectedDateRange() {
417 | SingleDate from = dateSelectionModel.getDate();
418 | if (from != null) {
419 | LocalDate[] dates = new LocalDate[2];
420 | dates[0] = from.toLocalDate();
421 | SingleDate to = dateSelectionModel.getToDate();
422 | if (to != null) {
423 | dates[1] = to.toLocalDate();
424 | return dates;
425 | }
426 | }
427 | return null;
428 | }
429 |
430 | public String getSelectedDateAsString() {
431 | if (isDateSelected()) {
432 | if (getDateSelectionMode() == DateSelectionMode.SINGLE_DATE_SELECTED) {
433 | return format.format(getSelectedDate());
434 | } else {
435 | LocalDate[] dates = getSelectedDateRange();
436 | return format.format(dates[0]) + separator + format.format(dates[1]);
437 | }
438 | } else {
439 | return null;
440 | }
441 | }
442 |
443 | public void slideTo(LocalDate date) {
444 | int m = date.getMonthValue() - 1;
445 | int y = date.getYear();
446 | if (year != y || month != m) {
447 | if (year < y || (year <= y && month < m)) {
448 | panelSlider.addSlide(createPanelDate(m, y), getSliderTransition(SimpleTransition.SliderType.FORWARD));
449 | } else {
450 | panelSlider.addSlide(createPanelDate(m, y), getSliderTransition(SimpleTransition.SliderType.BACK));
451 | }
452 | month = m;
453 | year = y;
454 | selectionState = SelectionState.DATE;
455 | header.setDate(month, year);
456 | } else {
457 | if (selectionState != SelectionState.DATE) {
458 | panelSlider.addSlide(createPanelDate(m, y), getSliderTransition(SimpleTransition.SliderType.DOWN_TOP));
459 | selectionState = SelectionState.DATE;
460 | }
461 | }
462 | updateSelected();
463 | }
464 |
465 | public void addDateSelectionListener(DateSelectionListener listener) {
466 | listenerList.add(DateSelectionListener.class, listener);
467 | }
468 |
469 | public void removeDateSelectionListener(DateSelectionListener listener) {
470 | listenerList.remove(DateSelectionListener.class, listener);
471 | }
472 |
473 | public DefaultDateCellRenderer getDefaultDateCellRenderer() {
474 | return defaultDateCellRenderer;
475 | }
476 |
477 | public void setDefaultDateCellRenderer(DefaultDateCellRenderer defaultDateCellRenderer) {
478 | this.defaultDateCellRenderer = defaultDateCellRenderer;
479 | repaint();
480 | }
481 |
482 | public Header getHeader() {
483 | return header;
484 | }
485 |
486 | public DateSelectionModel getDateSelectionModel() {
487 | return dateSelectionModel;
488 | }
489 |
490 | public void setDateSelectionModel(DateSelectionModel dateSelectionModel) {
491 | if (dateSelectionModel == null) {
492 | throw new IllegalArgumentException("dateSelectionModel can't be null");
493 | }
494 | if (this.dateSelectionModel != dateSelectionModel) {
495 | DateSelectionModel old = this.dateSelectionModel;
496 | if (old != null) {
497 | old.removeDatePickerSelectionListener(this);
498 | }
499 | this.dateSelectionModel = dateSelectionModel;
500 | this.dateSelectionModel.addDatePickerSelectionListener(this);
501 | }
502 | }
503 |
504 | @Override
505 | public void dateSelectionModelChanged(DateSelectionModelEvent e) {
506 | if (e.getAction() == DateSelectionModelEvent.DATE) {
507 | verifyDateSelection();
508 | }
509 | repaint();
510 | }
511 |
512 | @Override
513 | public void dateControlChanged(DateControlEvent e) {
514 | if (e.getType() == DateControlEvent.BACK) {
515 | setToBack();
516 | } else if (e.getType() == DateControlEvent.FORWARD) {
517 | setToForward();
518 | } else if (e.getType() == DateControlEvent.MONTH) {
519 | selectMonth();
520 | } else if (e.getType() == DateControlEvent.YEAR) {
521 | selectYear();
522 | }
523 | }
524 |
525 | @Override
526 | public void stateChanged(ChangeEvent e) {
527 | if (e.getSource() == panelMonth) {
528 | this.month = panelMonth.getSelectedMonth();
529 | header.setDate(month, year);
530 | panelSlider.addSlide(createPanelDate(month, year), getSliderTransition(SimpleTransition.SliderType.DOWN_TOP));
531 | selectionState = SelectionState.DATE;
532 | } else if (e.getSource() == panelYear) {
533 | this.year = panelYear.getSelectedYear();
534 | header.setDate(month, year);
535 | panelSlider.addSlide(createPanelMonth(year), getSliderTransition(SimpleTransition.SliderType.DOWN_TOP));
536 | selectionState = SelectionState.MONTH;
537 | }
538 | }
539 |
540 | protected DateSelectionModel createDefaultDateSelection() {
541 | return new DateSelectionModel();
542 | }
543 |
544 | protected SliderTransition getSliderTransition(SimpleTransition.SliderType type) {
545 | if (!animationEnabled) {
546 | return null;
547 | }
548 | return SimpleTransition.get(type);
549 | }
550 |
551 | private void updateSelected() {
552 | if (selectionState == SelectionState.DATE) {
553 | panelDate.checkSelection();
554 | } else if (selectionState == SelectionState.MONTH) {
555 | panelMonth.checkSelection();
556 | } else if (selectionState == SelectionState.YEAR) {
557 | panelYear.checkSelection();
558 | }
559 | }
560 |
561 | private void installEditor(JFormattedTextField editor) {
562 | if (editor != null) {
563 | JToolBar toolBar = new JToolBar();
564 | editorButton = new JButton(editorIcon != null ? editorIcon : new FlatSVGIcon("raven/datetime/icon/calendar.svg", 0.8f));
565 | toolBar.add(editorButton);
566 | editorButton.addActionListener(e -> {
567 | if (editor.isEnabled()) {
568 | editor.grabFocus();
569 | showPopup();
570 | }
571 | });
572 | InputUtils.useDateInput(editor, dateFormatPattern, getDateSelectionMode() == DateSelectionMode.BETWEEN_DATE_SELECTED, separator, getValueCallback(), getInputValidationListener());
573 | setEditorValue();
574 | editor.putClientProperty(FlatClientProperties.TEXT_FIELD_TRAILING_COMPONENT, toolBar);
575 | addDateSelectionListener(getDateSelectionListener());
576 | }
577 | }
578 |
579 | private void uninstallEditor(JFormattedTextField editor) {
580 | if (editor != null) {
581 | editorButton = null;
582 | InputUtils.removePropertyChange(editor);
583 | if (dateSelectionListener != null) {
584 | removeDateSelectionListener(dateSelectionListener);
585 | }
586 | }
587 | }
588 |
589 | private InputUtils.ValueCallback getValueCallback() {
590 | if (valueCallback == null) {
591 | valueCallback = value -> {
592 | if (value == null && isDateSelected()) {
593 | clearSelectedDate();
594 | } else {
595 | if (value != null && !value.equals(getSelectedDateAsString())) {
596 | if (getDateSelectionMode() == DateSelectionMode.SINGLE_DATE_SELECTED) {
597 | LocalDate date = InputUtils.stringToDate(format, value.toString());
598 | if (date != null) {
599 | setSelectedDate(date);
600 | }
601 | } else {
602 | LocalDate[] dates = InputUtils.stringToDate(format, separator, value.toString());
603 | if (dates != null) {
604 | setSelectedDateRange(dates[0], dates[1]);
605 | }
606 | }
607 | }
608 | }
609 | };
610 | }
611 | return valueCallback;
612 | }
613 |
614 | private DateSelectionListener getDateSelectionListener() {
615 | if (dateSelectionListener == null) {
616 | dateSelectionListener = dateSelectionEvent -> setEditorValue();
617 | }
618 | return dateSelectionListener;
619 | }
620 |
621 | private void setEditorValue() {
622 | String value = getSelectedDateAsString();
623 | if (value != null) {
624 | if (!editor.getText().equalsIgnoreCase(value)) {
625 | editor.setValue(value);
626 | }
627 | } else {
628 | editor.setValue(null);
629 | }
630 | }
631 |
632 | private InputValidationListener getInputValidationListener() {
633 | if (inputValidationListener == null) {
634 | inputValidationListener = new InputValidationListener() {
635 |
636 | @Override
637 | public boolean isValidation() {
638 | return dateSelectionModel.getDateSelectionAble() != null;
639 | }
640 |
641 | @Override
642 | public void inputChanged(boolean status) {
643 | checkValidation(status);
644 | }
645 |
646 | @Override
647 | public boolean checkSelectionAble(LocalDate date) {
648 | if (dateSelectionModel.getDateSelectionAble() == null) return true;
649 | return dateSelectionModel.getDateSelectionAble().isDateSelectedAble(date);
650 | }
651 | };
652 | }
653 | return inputValidationListener;
654 | }
655 |
656 | @Override
657 | protected String getDefaultPlaceholder() {
658 | if (defaultPlaceholder == null) {
659 | if (getDateSelectionMode() == DateSelectionMode.BETWEEN_DATE_SELECTED) {
660 | String d = InputUtils.datePatternToInputFormat(dateFormatPattern, "-");
661 | defaultPlaceholder = d + separator + d;
662 | } else {
663 | defaultPlaceholder = InputUtils.datePatternToInputFormat(dateFormatPattern, "-");
664 | }
665 | }
666 | return defaultPlaceholder;
667 | }
668 |
669 | @Override
670 | protected void popupOpen() {
671 | toDateSelectionView();
672 | }
673 |
674 | private void verifyDateSelection() {
675 | if (getDateSelectionMode() == DateSelectionMode.BETWEEN_DATE_SELECTED) {
676 | SingleDate fromDate = dateSelectionModel.getDate();
677 | SingleDate toDate = dateSelectionModel.getToDate();
678 | if ((fromDate == null && toDate != null) || (fromDate != null && toDate == null)) {
679 | return;
680 | }
681 | }
682 | if (isCloseAfterSelected()) {
683 | closePopup();
684 | }
685 | fireDateSelectionChanged(new DateSelectionEvent(this));
686 | if (panelDateOption != null) {
687 | panelDateOption.setSelectedCustom();
688 | }
689 | }
690 |
691 | public void fireDateSelectionChanged(DateSelectionEvent event) {
692 | Object[] listeners = listenerList.getListenerList();
693 | for (int i = listeners.length - 2; i >= 0; i -= 2) {
694 | if (listeners[i] == DateSelectionListener.class) {
695 | ((DateSelectionListener) listeners[i + 1]).dateSelected(event);
696 | }
697 | }
698 | }
699 |
700 | private void setPanelDate(PanelDate panelDate) {
701 | this.panelDate = panelDate;
702 | }
703 |
704 | private void setPanelMonth(PanelMonth panelMonth) {
705 | PanelMonth old = this.panelMonth;
706 | if (old != null) {
707 | old.removeChangeListener(this);
708 | }
709 | this.panelMonth = panelMonth;
710 | this.panelMonth.addChangeListener(this);
711 | }
712 |
713 | private void setPanelYear(PanelYear panelYear) {
714 | PanelYear old = this.panelYear;
715 | if (old != null) {
716 | old.removeChangeListener(this);
717 | }
718 | this.panelYear = panelYear;
719 | this.panelYear.addChangeListener(this);
720 | }
721 |
722 | private PanelDate createPanelDate(int month, int year) {
723 | PanelDate panelDate = new PanelDate(this, month, year);
724 | setPanelDate(panelDate);
725 | return panelDate;
726 | }
727 |
728 | private PanelMonth createPanelMonth(int year) {
729 | PanelMonth panelMonth = new PanelMonth(this, year);
730 | setPanelMonth(panelMonth);
731 | return panelMonth;
732 | }
733 |
734 | private PanelYear createPanelYear(int year) {
735 | PanelYear panelYear = new PanelYear(this, year);
736 | setPanelYear(panelYear);
737 | return panelYear;
738 | }
739 |
740 | public static void setDefaultWeekdays(String[] defaultWeekdays) {
741 | if (defaultWeekdays == null) {
742 | DatePicker.defaultWeekdays = null;
743 | } else {
744 | DatePicker.defaultWeekdays = Arrays.copyOf(defaultWeekdays, defaultWeekdays.length);
745 | }
746 | }
747 |
748 | public static String[] getDefaultWeekdays() {
749 | if (defaultWeekdays == null) {
750 | return null;
751 | }
752 | return Arrays.copyOf(defaultWeekdays, defaultWeekdays.length);
753 | }
754 |
755 | public enum DateSelectionMode {
756 | SINGLE_DATE_SELECTED, BETWEEN_DATE_SELECTED
757 | }
758 |
759 | private enum SelectionState {
760 | DATE, MONTH, YEAR
761 | }
762 | }
763 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/DateSelectionAble.java:
--------------------------------------------------------------------------------
1 | package raven.datetime;
2 |
3 | import java.time.LocalDate;
4 |
5 | public interface DateSelectionAble {
6 |
7 | boolean isDateSelectedAble(LocalDate date);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/PanelDateOption.java:
--------------------------------------------------------------------------------
1 | package raven.datetime;
2 |
3 | import com.formdev.flatlaf.FlatClientProperties;
4 | import net.miginfocom.swing.MigLayout;
5 |
6 | import javax.swing.*;
7 | import java.awt.*;
8 | import java.time.LocalDate;
9 | import java.util.List;
10 |
11 | public class PanelDateOption extends JPanel {
12 |
13 | private final DatePicker datePicker;
14 | private boolean disableChange;
15 |
16 | public PanelDateOption(DatePicker datePicker) {
17 | this.datePicker = datePicker;
18 | init();
19 | }
20 |
21 | private void init() {
22 | putClientProperty(FlatClientProperties.STYLE, "" +
23 | "background:null");
24 | buttonGroup = new ButtonGroup();
25 | }
26 |
27 | public void installDateOptionLabel() {
28 | removeAll();
29 | PanelDateOptionLabel panelDateOptionLabel = datePicker.getPanelDateOptionLabel();
30 | if (panelDateOptionLabel == null) {
31 | panelDateOptionLabel = createDefaultPanelDateOptionLabel();
32 | }
33 | String layoutRowConstraints = "";
34 | List items = panelDateOptionLabel.getListItems();
35 | for (int i = 0; i < items.size(); i++) {
36 | PanelDateOptionLabel.Item item = items.get(i);
37 | add(createButton(item.getLabel(), item.getCallback()));
38 | if (item.getCallback() == null) {
39 | layoutRowConstraints += "push";
40 | } else {
41 | layoutRowConstraints += "[]";
42 | }
43 | }
44 | layoutRowConstraints += "[]";
45 | setLayout(new MigLayout("wrap,insets 5,fillx", "[fill]", layoutRowConstraints));
46 | add(new JSeparator(SwingConstants.VERTICAL), "dock west,height 100%", 0);
47 | repaint();
48 | revalidate();
49 | }
50 |
51 | private JToggleButton createButton(String name, PanelDateOptionLabel.LabelCallback callback) {
52 | JToggleButton button = new JToggleButton(name);
53 | button.setHorizontalAlignment(SwingConstants.LEADING);
54 | if (callback == null) {
55 | button.setName("custom");
56 | }
57 | button.addActionListener(e -> {
58 | disableChange = true;
59 | boolean isEnable = datePicker.isEnabled();
60 | if (callback == null) {
61 | if (isEnable) {
62 | datePicker.clearSelectedDate();
63 | }
64 | } else {
65 | LocalDate[] dates = callback.getDate();
66 | if (dates.length == 0) {
67 | throw new IllegalArgumentException("Date option is empty so can't be select");
68 | }
69 | boolean singleDate = dates.length == 1 || datePicker.getDateSelectionMode() == DatePicker.DateSelectionMode.SINGLE_DATE_SELECTED;
70 | if (isEnable) {
71 | if (singleDate) {
72 | datePicker.setSelectedDate(dates[0]);
73 | } else {
74 | datePicker.setSelectedDateRange(dates[0], dates[1]);
75 | }
76 | } else {
77 | datePicker.slideTo(dates[0]);
78 | }
79 | }
80 | });
81 | button.putClientProperty(FlatClientProperties.STYLE, "" +
82 | "arc:10;" +
83 | "borderWidth:0;" +
84 | "focusWidth:0;" +
85 | "innerFocusWidth:0;" +
86 | "margin:4,10,4,10;" +
87 | "background:null");
88 | buttonGroup.add(button);
89 | return button;
90 | }
91 |
92 | public void setSelectedCustom() {
93 | if (!disableChange) {
94 | JToggleButton customButton = null;
95 | for (Component com : getComponents()) {
96 | String name = com.getName();
97 | if (name != null && name.equals("custom")) {
98 | customButton = (JToggleButton) com;
99 | break;
100 | }
101 | }
102 | if (customButton != null) {
103 | customButton.setSelected(true);
104 | }
105 | }
106 | disableChange = false;
107 | }
108 |
109 | private PanelDateOptionLabel createDefaultPanelDateOptionLabel() {
110 | PanelDateOptionLabel defaultPanelDateOptionLabel = new PanelDateOptionLabel();
111 | defaultPanelDateOptionLabel.add("Today", PanelDateOptionLabel.LabelCallback.TODAY);
112 | defaultPanelDateOptionLabel.add("Yesterday", PanelDateOptionLabel.LabelCallback.YESTERDAY);
113 | defaultPanelDateOptionLabel.add("Last 7 Days", PanelDateOptionLabel.LabelCallback.LAST_7_DAYS);
114 | defaultPanelDateOptionLabel.add("Last 30 Days", PanelDateOptionLabel.LabelCallback.LAST_30_DAYS);
115 | defaultPanelDateOptionLabel.add("This Month", PanelDateOptionLabel.LabelCallback.THIS_MONTH);
116 | defaultPanelDateOptionLabel.add("Last Month", PanelDateOptionLabel.LabelCallback.LAST_MONTH);
117 | defaultPanelDateOptionLabel.add("Last Year", PanelDateOptionLabel.LabelCallback.LAST_YEAR);
118 | defaultPanelDateOptionLabel.add("Custom", PanelDateOptionLabel.LabelCallback.CUSTOM);
119 |
120 | return defaultPanelDateOptionLabel;
121 | }
122 |
123 | private ButtonGroup buttonGroup;
124 | }
125 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/PanelDateOptionLabel.java:
--------------------------------------------------------------------------------
1 | package raven.datetime;
2 |
3 | import java.time.LocalDate;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | public class PanelDateOptionLabel {
8 |
9 | private final List- listItems = new ArrayList<>();
10 |
11 | public PanelDateOptionLabel() {
12 | }
13 |
14 | public void add(String label, LabelCallback callback) {
15 | listItems.add(new Item(label, callback));
16 | }
17 |
18 | public List
- getListItems() {
19 | return listItems;
20 | }
21 |
22 | private void remove(int index) {
23 | listItems.remove(index);
24 | }
25 |
26 | private void clear() {
27 | listItems.clear();
28 | }
29 |
30 | public static class Item {
31 |
32 | public String getLabel() {
33 | return label;
34 | }
35 |
36 | public LabelCallback getCallback() {
37 | return callback;
38 | }
39 |
40 | public Item(String label, LabelCallback callback) {
41 | this.label = label;
42 | this.callback = callback;
43 | }
44 |
45 | private final String label;
46 | private final LabelCallback callback;
47 | }
48 |
49 | public interface LabelCallback {
50 |
51 | LabelCallback TODAY = () -> new LocalDate[]{LocalDate.now()};
52 | LabelCallback YESTERDAY = () -> new LocalDate[]{LocalDate.now().minusDays(1)};
53 | LabelCallback LAST_7_DAYS = () -> new LocalDate[]{LocalDate.now().minusDays(7), LocalDate.now()};
54 | LabelCallback LAST_30_DAYS = () -> new LocalDate[]{LocalDate.now().minusDays(30), LocalDate.now()};
55 | LabelCallback THIS_MONTH = () -> {
56 | LocalDate now = LocalDate.now();
57 | return new LocalDate[]{now.withDayOfMonth(1), now.withDayOfMonth(now.lengthOfMonth())};
58 | };
59 | LabelCallback LAST_MONTH = () -> {
60 | LocalDate lastMonth = LocalDate.now().minusMonths(1);
61 | return new LocalDate[]{lastMonth.withDayOfMonth(1), lastMonth.withDayOfMonth(lastMonth.lengthOfMonth())};
62 | };
63 | LabelCallback LAST_YEAR = () -> {
64 | LocalDate lastYear = LocalDate.now().minusYears(1).withDayOfYear(1);
65 | return new LocalDate[]{lastYear, lastYear.withDayOfYear(lastYear.lengthOfYear())};
66 | };
67 |
68 | LabelCallback CUSTOM = null;
69 |
70 | LocalDate[] getDate();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/TimePicker.java:
--------------------------------------------------------------------------------
1 | package raven.datetime;
2 |
3 | import com.formdev.flatlaf.FlatClientProperties;
4 | import com.formdev.flatlaf.extras.FlatSVGIcon;
5 | import net.miginfocom.swing.MigLayout;
6 | import raven.datetime.component.PanelPopupEditor;
7 | import raven.datetime.component.time.Header;
8 | import raven.datetime.component.time.PanelClock;
9 | import raven.datetime.component.time.TimeSelectionModel;
10 | import raven.datetime.component.time.event.TimeActionListener;
11 | import raven.datetime.component.time.event.TimeSelectionModelEvent;
12 | import raven.datetime.component.time.event.TimeSelectionModelListener;
13 | import raven.datetime.event.TimeSelectionEvent;
14 | import raven.datetime.event.TimeSelectionListener;
15 | import raven.datetime.util.InputUtils;
16 | import raven.datetime.util.InputValidationListener;
17 |
18 | import javax.swing.*;
19 | import java.awt.*;
20 | import java.time.LocalTime;
21 | import java.time.format.DateTimeFormatter;
22 | import java.util.Locale;
23 |
24 | public class TimePicker extends PanelPopupEditor implements TimeSelectionModelListener {
25 |
26 | private final DateTimeFormatter format12h = DateTimeFormatter.ofPattern("hh:mm a", Locale.ENGLISH);
27 | private final DateTimeFormatter format24h = DateTimeFormatter.ofPattern("HH:mm", Locale.ENGLISH);
28 | private TimeSelectionModel timeSelectionModel;
29 | private TimeSelectionListener timeSelectionListener;
30 | private InputValidationListener inputValidationListener;
31 | private InputUtils.ValueCallback valueCallback;
32 | private Icon editorIcon;
33 | private MigLayout layout;
34 | private int orientation = SwingConstants.VERTICAL;
35 | private Color color;
36 | private JButton editorButton;
37 | private LocalTime oldSelectedTime;
38 |
39 | private Header header;
40 | private PanelClock panelClock;
41 |
42 | public TimePicker() {
43 | this(null);
44 | }
45 |
46 | public TimePicker(TimeSelectionModel model) {
47 | init(model);
48 | }
49 |
50 | private void init(TimeSelectionModel model) {
51 | putClientProperty(FlatClientProperties.STYLE, "" +
52 | "[light]background:darken($Panel.background,2%);" +
53 | "[dark]background:lighten($Panel.background,2%);");
54 | layout = new MigLayout(
55 | "wrap,fill,insets 3",
56 | "fill",
57 | "fill");
58 | setLayout(layout);
59 |
60 | if (model == null) {
61 | model = createDefaultTimeSelection();
62 | }
63 | setTimeSelectionModel(model);
64 |
65 | header = new Header(this, new HeaderActionListener());
66 | panelClock = new PanelClock(this, new ClockActionListener());
67 | add(header, "width 120:120");
68 | add(panelClock, "width 230:230,height 230:230");
69 | }
70 |
71 | public int getOrientation() {
72 | return orientation;
73 | }
74 |
75 | public void setOrientation(int orientation) {
76 | if (this.orientation != orientation) {
77 | String c = orientation == SwingConstants.VERTICAL ? "wrap," : "";
78 | layout.setLayoutConstraints(c + "fill,insets 3");
79 | this.orientation = orientation;
80 | header.setOrientation(orientation);
81 | revalidate();
82 | }
83 | }
84 |
85 | public void setEditor(JFormattedTextField editor) {
86 | if (editor != this.editor) {
87 | if (this.editor != null) {
88 | uninstallEditor(this.editor);
89 | }
90 | this.editor = editor;
91 | if (editor != null) {
92 | installEditor(editor);
93 | if (editorValidation) {
94 | validChanged(editor, isValid);
95 | } else {
96 | validChanged(editor, true);
97 | }
98 | }
99 | }
100 | }
101 |
102 | public boolean is24HourView() {
103 | return panelClock.isUse24hour();
104 | }
105 |
106 | public void set24HourView(boolean hour24) {
107 | if (panelClock.isUse24hour() != hour24) {
108 | panelClock.setUse24hour(hour24);
109 | header.setUse24hour(hour24);
110 | panelClock.updateClock();
111 | header.updateHeader();
112 | repaint();
113 | if (editor != null) {
114 | InputUtils.changeTimeFormatted(editor, hour24, getInputValidationListener());
115 | this.defaultPlaceholder = null;
116 | setEditorValue();
117 | }
118 | }
119 | }
120 |
121 | public Color getColor() {
122 | return color;
123 | }
124 |
125 | public void setColor(Color color) {
126 | this.color = color;
127 | header.setColor(color);
128 | panelClock.setColor(color);
129 | }
130 |
131 | public Icon getEditorIcon() {
132 | return editorIcon;
133 | }
134 |
135 | public void setEditorIcon(Icon editorIcon) {
136 | this.editorIcon = editorIcon;
137 | if (editorButton != null) {
138 | editorButton.setIcon(editorIcon);
139 | }
140 | }
141 |
142 | /**
143 | * Set time to current local time
144 | */
145 | public void now() {
146 | setSelectedTime(LocalTime.now());
147 | }
148 |
149 | public void setSelectedTime(LocalTime time) {
150 | if (time == null) {
151 | clearSelectedTime();
152 | } else {
153 | int hour = time.getHour();
154 | int minute = time.getMinute();
155 | timeSelectionModel.set(hour, minute);
156 | }
157 | }
158 |
159 | public void clearSelectedTime() {
160 | timeSelectionModel.set(-1, -1);
161 | }
162 |
163 | public boolean isTimeSelected() {
164 | return timeSelectionModel.isSelected();
165 | }
166 |
167 | public LocalTime getSelectedTime() {
168 | return timeSelectionModel.getTime();
169 | }
170 |
171 | public String getSelectedTimeAsString() {
172 | if (isTimeSelected()) {
173 | if (panelClock.isUse24hour()) {
174 | return format24h.format(getSelectedTime());
175 | } else {
176 | return format12h.format(getSelectedTime());
177 | }
178 | } else {
179 | return null;
180 | }
181 | }
182 |
183 | protected TimeSelectionModel createDefaultTimeSelection() {
184 | return new TimeSelectionModel();
185 | }
186 |
187 | public TimeSelectionAble getTimeSelectionAble() {
188 | return timeSelectionModel.getTimeSelectionAble();
189 | }
190 |
191 | public void setTimeSelectionAble(TimeSelectionAble timeSelectionAble) {
192 | timeSelectionModel.setTimeSelectionAble(timeSelectionAble);
193 | commitEdit();
194 | }
195 |
196 | public TimeSelectionModel getTimeSelectionModel() {
197 | return timeSelectionModel;
198 | }
199 |
200 | public void setTimeSelectionModel(TimeSelectionModel timeSelectionModel) {
201 | if (timeSelectionModel == null) {
202 | throw new IllegalArgumentException("timeSelectionModel can't be null");
203 | }
204 | if (this.timeSelectionModel != timeSelectionModel) {
205 | TimeSelectionModel old = this.timeSelectionModel;
206 | if (old != null) {
207 | old.removeTimePickerSelectionListener(this);
208 | }
209 | this.timeSelectionModel = timeSelectionModel;
210 | this.timeSelectionModel.addTimePickerSelectionListener(this);
211 | }
212 | }
213 |
214 | public boolean isHourSelectionView() {
215 | return panelClock.isHourSelectionView();
216 | }
217 |
218 | public void setHourSelectionView(boolean hourSelectionView) {
219 | header.setHourSelectionView(hourSelectionView);
220 | panelClock.setHourSelectionViewImmediately(hourSelectionView);
221 | }
222 |
223 | public void addTimeSelectionListener(TimeSelectionListener listener) {
224 | listenerList.add(TimeSelectionListener.class, listener);
225 | }
226 |
227 | public void removeTimeSelectionListener(TimeSelectionListener listener) {
228 | listenerList.remove(TimeSelectionListener.class, listener);
229 | }
230 |
231 | private void installEditor(JFormattedTextField editor) {
232 | JToolBar toolBar = new JToolBar();
233 | editorButton = new JButton(editorIcon != null ? editorIcon : new FlatSVGIcon("raven/datetime/icon/clock.svg", 0.8f));
234 | toolBar.add(editorButton);
235 | editorButton.addActionListener(e -> {
236 | if (editor.isEnabled()) {
237 | editor.grabFocus();
238 | showPopup();
239 | }
240 | });
241 | InputUtils.useTimeInput(editor, panelClock.isUse24hour(), getValueCallback(), getInputValidationListener());
242 | setEditorValue();
243 | editor.putClientProperty(FlatClientProperties.TEXT_FIELD_TRAILING_COMPONENT, toolBar);
244 | addTimeSelectionListener(getTimeSelectionListener());
245 | }
246 |
247 | private void uninstallEditor(JFormattedTextField editor) {
248 | if (editor != null) {
249 | editorButton = null;
250 | InputUtils.removePropertyChange(editor);
251 | if (timeSelectionListener != null) {
252 | removeTimeSelectionListener(timeSelectionListener);
253 | }
254 | }
255 | }
256 |
257 | private InputUtils.ValueCallback getValueCallback() {
258 | if (valueCallback == null) {
259 | valueCallback = value -> {
260 | if (value == null && isTimeSelected()) {
261 | clearSelectedTime();
262 | } else {
263 | if (value != null && !value.equals(getSelectedTimeAsString())) {
264 | LocalTime time = InputUtils.stringToTime(panelClock.isUse24hour(), value.toString());
265 | if (time != null) {
266 | setSelectedTime(time);
267 | }
268 | }
269 | }
270 | };
271 | }
272 | return valueCallback;
273 | }
274 |
275 | private TimeSelectionListener getTimeSelectionListener() {
276 | if (timeSelectionListener == null) {
277 | timeSelectionListener = timeSelectionEvent -> setEditorValue();
278 | }
279 | return timeSelectionListener;
280 | }
281 |
282 | private void setEditorValue() {
283 | String value = getSelectedTimeAsString();
284 | if (value != null) {
285 | if (!editor.getText().equalsIgnoreCase(value)) {
286 | editor.setValue(value);
287 | }
288 | } else {
289 | editor.setValue(null);
290 | }
291 | }
292 |
293 | private InputValidationListener getInputValidationListener() {
294 | if (inputValidationListener == null) {
295 | inputValidationListener = new InputValidationListener() {
296 |
297 | @Override
298 | public boolean isValidation() {
299 | return timeSelectionModel.getTimeSelectionAble() != null;
300 | }
301 |
302 | @Override
303 | public void inputChanged(boolean status) {
304 | checkValidation(status);
305 | }
306 |
307 | @Override
308 | public boolean checkSelectionAble(LocalTime time) {
309 | int hour = time.getHour();
310 | int minute = time.getMinute();
311 | return timeSelectionModel.checkSelection(hour, minute);
312 | }
313 | };
314 | }
315 | return inputValidationListener;
316 | }
317 |
318 | @Override
319 | protected String getDefaultPlaceholder() {
320 | if (defaultPlaceholder == null) {
321 | String pattern;
322 | if (is24HourView()) {
323 | pattern = "--:--";
324 | } else {
325 | pattern = "--:-- --";
326 | }
327 | defaultPlaceholder = pattern;
328 | }
329 | return defaultPlaceholder;
330 | }
331 |
332 | @Override
333 | protected void popupOpen() {
334 | setHourSelectionView(true);
335 | }
336 |
337 | private void verifyTimeSelection() {
338 | LocalTime time = getSelectedTime();
339 | if ((time == null && oldSelectedTime == null)) {
340 | return;
341 | } else if (time != null && oldSelectedTime != null) {
342 | if (time.equals(oldSelectedTime)) {
343 | return;
344 | }
345 | }
346 | oldSelectedTime = time;
347 | fireTimeSelectionChanged(new TimeSelectionEvent(this));
348 | }
349 |
350 | public void fireTimeSelectionChanged(TimeSelectionEvent event) {
351 | Object[] listeners = listenerList.getListenerList();
352 | for (int i = listeners.length - 2; i >= 0; i -= 2) {
353 | if (listeners[i] == TimeSelectionListener.class) {
354 | ((TimeSelectionListener) listeners[i + 1]).timeSelected(event);
355 | }
356 | }
357 | }
358 |
359 | @Override
360 | public void setEnabled(boolean enabled) {
361 | super.setEnabled(enabled);
362 | header.setEnabled(enabled);
363 | panelClock.setEnabled(enabled);
364 | }
365 |
366 | public Header getHeader() {
367 | return header;
368 | }
369 |
370 | @Override
371 | public void timeSelectionModelChanged(TimeSelectionModelEvent e) {
372 | // check if user clear the time selection. so we set to the hour selection view
373 | if (!timeSelectionModel.isSelected() && e.getAction() == TimeSelectionModelEvent.HOUR_MINUTE) {
374 | panelClock.setHourSelectionView(true);
375 | header.setHourSelectionView(true);
376 | }
377 | panelClock.updateClock();
378 | header.updateHeader();
379 | repaint();
380 | verifyTimeSelection();
381 | }
382 |
383 | private class ClockActionListener implements TimeActionListener {
384 |
385 | @Override
386 | public void selectionViewChanged(boolean isHourSelectionView) {
387 | if (isHourSelectionView) {
388 | panelClock.setHourSelectionView(false);
389 | header.setHourSelectionView(false);
390 | }
391 | }
392 | }
393 |
394 | private class HeaderActionListener implements TimeActionListener {
395 |
396 | @Override
397 | public void selectionViewChanged(boolean isHourSelectionView) {
398 | panelClock.setHourSelectionView(isHourSelectionView);
399 | }
400 | }
401 | }
402 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/TimeSelectionAble.java:
--------------------------------------------------------------------------------
1 | package raven.datetime;
2 |
3 | import java.time.LocalTime;
4 |
5 | public interface TimeSelectionAble {
6 |
7 | boolean isTimeSelectedAble(LocalTime time, boolean hourView);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/PanelPopupEditor.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component;
2 |
3 | import com.formdev.flatlaf.FlatClientProperties;
4 | import raven.datetime.util.Utils;
5 |
6 | import javax.swing.*;
7 | import java.awt.*;
8 | import java.text.ParseException;
9 |
10 | public abstract class PanelPopupEditor extends JPanel {
11 |
12 | protected JFormattedTextField editor;
13 | protected JPopupMenu popupMenu;
14 |
15 | protected boolean editorValidation = true;
16 | protected boolean isValid;
17 | protected boolean validationOnNull;
18 | protected String defaultPlaceholder;
19 | protected Point popupSpace = new Point(1, 1);
20 |
21 | protected LookAndFeel oldThemes = UIManager.getLookAndFeel();
22 |
23 | public PanelPopupEditor() {
24 | }
25 |
26 | public void showPopup() {
27 | showPopup(editor);
28 | }
29 |
30 | public void showPopup(Component component) {
31 | if (popupMenu == null) {
32 | popupMenu = new JPopupMenu();
33 | popupMenu.putClientProperty(FlatClientProperties.STYLE, "" +
34 | "borderInsets:1,1,1,1");
35 | popupMenu.add(this);
36 | }
37 | if (UIManager.getLookAndFeel() != oldThemes) {
38 | // component in popup not update UI when change themes
39 | // so need to update when popup show
40 | SwingUtilities.updateComponentTreeUI(popupMenu);
41 | oldThemes = UIManager.getLookAndFeel();
42 | }
43 | Point point = Utils.adjustPopupLocation(popupMenu, component, popupSpace);
44 | popupOpen();
45 | popupMenu.show(component, point.x, point.y);
46 | }
47 |
48 | public void closePopup() {
49 | if (popupMenu != null) {
50 | popupMenu.setVisible(false);
51 | repaint();
52 | }
53 | }
54 |
55 | public boolean isEditorValidation() {
56 | return editorValidation;
57 | }
58 |
59 | public void setEditorValidation(boolean editorValidation) {
60 | if (this.editorValidation != editorValidation) {
61 | this.editorValidation = editorValidation;
62 | if (editor != null) {
63 | if (editorValidation) {
64 | validChanged(editor, isValid);
65 | } else {
66 | validChanged(editor, true);
67 | }
68 | }
69 | }
70 | }
71 |
72 | public boolean isValidationOnNull() {
73 | return validationOnNull;
74 | }
75 |
76 | public void setValidationOnNull(boolean validationOnNull) {
77 | if (this.validationOnNull != validationOnNull) {
78 | this.validationOnNull = validationOnNull;
79 | commitEdit();
80 | }
81 | }
82 |
83 | protected void checkValidation(boolean status) {
84 | boolean valid = status || isEditorValidationOnNull();
85 | if (isValid != valid) {
86 | isValid = valid;
87 | if (editor != null) {
88 | if (editorValidation) {
89 | validChanged(editor, valid);
90 | }
91 | }
92 | }
93 | }
94 |
95 | protected void validChanged(JFormattedTextField editor, boolean isValid) {
96 | String style = isValid ? null : FlatClientProperties.OUTLINE_ERROR;
97 | editor.putClientProperty(FlatClientProperties.OUTLINE, style);
98 | }
99 |
100 | protected boolean isEditorValidationOnNull() {
101 | if (validationOnNull) {
102 | return false;
103 | }
104 | return editor != null && editor.getText().equals(getDefaultPlaceholder());
105 | }
106 |
107 | protected void commitEdit() {
108 | if (editor != null && editorValidation) {
109 | try {
110 | editor.commitEdit();
111 | } catch (ParseException ignored) {
112 | }
113 | }
114 | }
115 |
116 | public JFormattedTextField getEditor() {
117 | return editor;
118 | }
119 |
120 | public Point getPopupSpace() {
121 | return popupSpace;
122 | }
123 |
124 | public void setPopupSpace(Point popupSpace) {
125 | this.popupSpace = popupSpace;
126 | }
127 |
128 | protected abstract String getDefaultPlaceholder();
129 |
130 | protected abstract void popupOpen();
131 | }
132 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/date/ButtonDate.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.date;
2 |
3 | import com.formdev.flatlaf.FlatClientProperties;
4 | import com.formdev.flatlaf.ui.FlatUIUtils;
5 | import raven.datetime.DatePicker;
6 |
7 | import javax.swing.*;
8 | import java.awt.*;
9 | import java.awt.event.MouseAdapter;
10 | import java.awt.event.MouseEvent;
11 |
12 | public class ButtonDate extends JButton {
13 |
14 | private final DatePicker datePicker;
15 | private final SingleDate date;
16 | private final int rowIndex;
17 | private boolean press;
18 | private boolean hover;
19 |
20 | public ButtonDate(DatePicker datePicker, SingleDate date, boolean enable, int rowIndex) {
21 | this.datePicker = datePicker;
22 | this.date = date;
23 | this.rowIndex = rowIndex;
24 | setText(date.getDay() + "");
25 | init(enable);
26 | }
27 |
28 | private void init(boolean enable) {
29 | setContentAreaFilled(false);
30 | addActionListener(e -> {
31 | if (datePicker.isEnabled()) {
32 | datePicker.getDateSelectionModel().selectDate(date);
33 | hover = false;
34 | PanelDate panelDate = (PanelDate) getParent();
35 | panelDate.checkSelection();
36 | }
37 | });
38 | addMouseListener(new MouseAdapter() {
39 | @Override
40 | public void mouseClicked(MouseEvent e) {
41 | if (datePicker.isEnabled() && isEnabled() && datePicker.getDateSelectionMode() == DatePicker.DateSelectionMode.SINGLE_DATE_SELECTED) {
42 | if (e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(e)) {
43 | datePicker.closePopup();
44 | }
45 | }
46 | }
47 |
48 | @Override
49 | public void mousePressed(MouseEvent e) {
50 | if (datePicker.isEnabled() && SwingUtilities.isLeftMouseButton(e)) {
51 | press = true;
52 | }
53 | }
54 |
55 | @Override
56 | public void mouseReleased(MouseEvent e) {
57 | if (datePicker.isEnabled() && SwingUtilities.isLeftMouseButton(e)) {
58 | press = false;
59 | }
60 | }
61 |
62 | @Override
63 | public void mouseEntered(MouseEvent e) {
64 | if (datePicker.isEnabled()) {
65 | hover = true;
66 | if (datePicker.getDateSelectionModel().getDateSelectionMode() == DatePicker.DateSelectionMode.BETWEEN_DATE_SELECTED && datePicker.getDateSelectionModel().getDate() != null) {
67 | datePicker.getDateSelectionModel().setHoverDate(date);
68 | }
69 | }
70 | }
71 |
72 | @Override
73 | public void mouseExited(MouseEvent e) {
74 | if (datePicker.isEnabled()) {
75 | hover = false;
76 | }
77 | }
78 | });
79 | if (enable) {
80 | putClientProperty(FlatClientProperties.STYLE, "" +
81 | "margin:7,7,7,7;" +
82 | "focusWidth:2;" +
83 | "selectedForeground:contrast($Component.accentColor,$Button.background,#fff)");
84 | } else {
85 | putClientProperty(FlatClientProperties.STYLE, "" +
86 | "margin:7,7,7,7;" +
87 | "focusWidth:2;" +
88 | "selectedForeground:contrast($Component.accentColor,$Button.background,#fff);" +
89 | "foreground:$Button.disabledText");
90 | }
91 | }
92 |
93 | @Override
94 | protected void paintComponent(Graphics g) {
95 | DefaultDateCellRenderer dateCellRenderer = datePicker.getDefaultDateCellRenderer();
96 | if (dateCellRenderer != null) {
97 | Graphics2D g2 = (Graphics2D) g.create();
98 | try {
99 | FlatUIUtils.setRenderingHints(g2);
100 | dateCellRenderer.paint(g2, datePicker, this, date, getWidth(), getHeight());
101 | } finally {
102 | g2.dispose();
103 | }
104 | }
105 | super.paintComponent(g);
106 | }
107 |
108 | public boolean isDateSelected() {
109 | DateSelectionModel dateSelectionModel = datePicker.getDateSelectionModel();
110 | if (dateSelectionModel.getDateSelectionMode() == DatePicker.DateSelectionMode.SINGLE_DATE_SELECTED) {
111 | return date.same(dateSelectionModel.getDate());
112 | } else {
113 | return date.same(dateSelectionModel.getDate()) || date.same(dateSelectionModel.getToDate());
114 | }
115 | }
116 |
117 | public SingleDate getDate() {
118 | return date;
119 | }
120 |
121 | public boolean isPress() {
122 | return press;
123 | }
124 |
125 | public boolean isHover() {
126 | return hover;
127 | }
128 |
129 | public int getRowIndex() {
130 | return rowIndex;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/date/ButtonMonthYear.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.date;
2 |
3 | import com.formdev.flatlaf.FlatClientProperties;
4 | import com.formdev.flatlaf.ui.FlatUIUtils;
5 | import com.formdev.flatlaf.util.UIScale;
6 | import raven.datetime.DatePicker;
7 | import raven.datetime.util.Utils;
8 |
9 | import javax.swing.*;
10 | import java.awt.*;
11 | import java.awt.event.MouseAdapter;
12 | import java.awt.event.MouseEvent;
13 |
14 | public class ButtonMonthYear extends JButton {
15 |
16 | private final DatePicker datePicker;
17 | private final int value;
18 | private boolean press;
19 | private boolean hover;
20 |
21 | public ButtonMonthYear(DatePicker datePicker, int value) {
22 | this.datePicker = datePicker;
23 | this.value = value;
24 | init();
25 | }
26 |
27 | private void init() {
28 | setContentAreaFilled(false);
29 | addMouseListener(new MouseAdapter() {
30 | @Override
31 | public void mousePressed(MouseEvent e) {
32 | if (SwingUtilities.isLeftMouseButton(e)) {
33 | press = true;
34 | }
35 | }
36 |
37 | @Override
38 | public void mouseReleased(MouseEvent e) {
39 | if (SwingUtilities.isLeftMouseButton(e)) {
40 | press = false;
41 | }
42 | }
43 |
44 | @Override
45 | public void mouseEntered(MouseEvent e) {
46 | hover = true;
47 | }
48 |
49 | @Override
50 | public void mouseExited(MouseEvent e) {
51 | hover = false;
52 | }
53 | });
54 | putClientProperty(FlatClientProperties.STYLE, "" +
55 | "margin:6,6,6,6;" +
56 | "selectedForeground:contrast($Component.accentColor,$Button.background,#fff);");
57 | }
58 |
59 | public int getValue() {
60 | return value;
61 | }
62 |
63 | @Override
64 | protected void paintComponent(Graphics g) {
65 | Graphics2D g2 = (Graphics2D) g.create();
66 | FlatUIUtils.setRenderingHints(g2);
67 | int border = UIScale.scale(6);
68 | float arc = UIScale.scale(datePicker.getSelectionArc());
69 | int width = getWidth() - border;
70 | int height = getHeight() - border;
71 | int x = (getWidth() - width) / 2;
72 | int y = (getHeight() - height) / 2;
73 | g2.setColor(getColor());
74 | FlatUIUtils.paintComponentBackground(g2, x, y, width, height, 0, arc);
75 | g2.dispose();
76 | super.paintComponent(g);
77 | }
78 |
79 | protected Color getColor() {
80 | Color color = isSelected() ? getAccentColor() : FlatUIUtils.getParentBackground(this);
81 | return Utils.getColor(color, press, hover);
82 | }
83 |
84 | protected Color getAccentColor() {
85 | if (datePicker.getColor() != null) {
86 | return datePicker.getColor();
87 | }
88 | return UIManager.getColor("Component.accentColor");
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/date/DateSelectionModel.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.date;
2 |
3 | import raven.datetime.DatePicker;
4 | import raven.datetime.DateSelectionAble;
5 | import raven.datetime.component.date.event.DateSelectionModelEvent;
6 | import raven.datetime.component.date.event.DateSelectionModelListener;
7 |
8 | import javax.swing.event.EventListenerList;
9 |
10 | public class DateSelectionModel {
11 |
12 | protected EventListenerList listenerList = new EventListenerList();
13 | private DatePicker.DateSelectionMode dateSelectionMode;
14 | private DateSelectionAble dateSelectionAble;
15 | private SingleDate date;
16 | private SingleDate toDate;
17 | private SingleDate hoverDate;
18 |
19 | public DateSelectionModel() {
20 | this(DatePicker.DateSelectionMode.SINGLE_DATE_SELECTED);
21 | }
22 |
23 | public DateSelectionModel(DatePicker.DateSelectionMode dateSelectionMode) {
24 | this(dateSelectionMode, null);
25 | }
26 |
27 | public DateSelectionModel(DateSelectionAble dateSelectionAble) {
28 | this(DatePicker.DateSelectionMode.SINGLE_DATE_SELECTED, dateSelectionAble);
29 | }
30 |
31 | public DateSelectionModel(DatePicker.DateSelectionMode dateSelectionMode, DateSelectionAble dateSelectionAble) {
32 | this.dateSelectionMode = dateSelectionMode;
33 | this.dateSelectionAble = dateSelectionAble;
34 | }
35 |
36 | public SingleDate getDate() {
37 | return date;
38 | }
39 |
40 | public void setDate(SingleDate date) {
41 | if (equalsDate(this.date, date)) {
42 | return;
43 | }
44 | if (!checkSelection(date)) {
45 | return;
46 | }
47 | this.date = date;
48 | fireDatePickerChanged(new DateSelectionModelEvent(this, DateSelectionModelEvent.DATE));
49 | }
50 |
51 | public SingleDate getToDate() {
52 | return toDate;
53 | }
54 |
55 | public void setToDate(SingleDate toDate) {
56 | if (equalsDate(this.toDate, toDate)) {
57 | return;
58 | }
59 | if (!checkSelection(toDate)) {
60 | return;
61 | }
62 | this.toDate = toDate;
63 | }
64 |
65 | public SingleDate getHoverDate() {
66 | return hoverDate;
67 | }
68 |
69 | public void setHoverDate(SingleDate hoverDate) {
70 | if (equalsDate(this.hoverDate, hoverDate)) {
71 | return;
72 | }
73 | this.hoverDate = hoverDate;
74 | fireDatePickerChanged(new DateSelectionModelEvent(this, DateSelectionModelEvent.BETWEEN_DATE_HOVER));
75 | }
76 |
77 | public void setSelectDate(SingleDate from, SingleDate to) {
78 | if (equalsDate(this.date, from) && equalsDate(this.toDate, to)) {
79 | return;
80 | }
81 | if (!checkSelection(from) || !checkSelection(to)) {
82 | return;
83 | }
84 | date = from;
85 | toDate = to;
86 | fireDatePickerChanged(new DateSelectionModelEvent(this, DateSelectionModelEvent.DATE));
87 | }
88 |
89 | protected void selectDate(SingleDate date) {
90 | if (!checkSelection(date)) {
91 | return;
92 | }
93 | if (dateSelectionMode == DatePicker.DateSelectionMode.SINGLE_DATE_SELECTED) {
94 | setDate(date);
95 | } else {
96 | if (getDate() == null || getToDate() != null) {
97 | this.date = date;
98 | hoverDate = date;
99 | if (getToDate() != null) {
100 | this.toDate = null;
101 | }
102 | fireDatePickerChanged(new DateSelectionModelEvent(this, DateSelectionModelEvent.DATE));
103 | } else {
104 | this.toDate = date;
105 | invertIfNecessaryToDate(date);
106 | fireDatePickerChanged(new DateSelectionModelEvent(this, DateSelectionModelEvent.DATE));
107 | }
108 | }
109 | }
110 |
111 | public void setDateSelectionAble(DateSelectionAble dateSelectionAble) {
112 | this.dateSelectionAble = dateSelectionAble;
113 | }
114 |
115 | public DateSelectionAble getDateSelectionAble() {
116 | return dateSelectionAble;
117 | }
118 |
119 | public void addDatePickerSelectionListener(DateSelectionModelListener listener) {
120 | listenerList.add(DateSelectionModelListener.class, listener);
121 | }
122 |
123 | public void removeDatePickerSelectionListener(DateSelectionModelListener listener) {
124 | listenerList.remove(DateSelectionModelListener.class, listener);
125 | }
126 |
127 | public void fireDatePickerChanged(DateSelectionModelEvent event) {
128 | Object[] listeners = listenerList.getListenerList();
129 | for (int i = listeners.length - 2; i >= 0; i -= 2) {
130 | if (listeners[i] == DateSelectionModelListener.class) {
131 | ((DateSelectionModelListener) listeners[i + 1]).dateSelectionModelChanged(event);
132 | }
133 | }
134 | }
135 |
136 | public boolean equalsDate(SingleDate date1, SingleDate date2) {
137 | // both are null
138 | if (date1 == null && date2 == null) {
139 | return true;
140 | }
141 | // only one is null
142 | if (date1 == null || date2 == null) {
143 | return false;
144 | }
145 | return date1.same(date2);
146 | }
147 |
148 | public DatePicker.DateSelectionMode getDateSelectionMode() {
149 | return dateSelectionMode;
150 | }
151 |
152 | public void setDateSelectionMode(DatePicker.DateSelectionMode dateSelectionMode) {
153 | this.dateSelectionMode = dateSelectionMode;
154 | }
155 |
156 | private boolean checkSelection(SingleDate date) {
157 | if (dateSelectionAble != null) {
158 | return date == null || dateSelectionAble.isDateSelectedAble(date.toLocalDate());
159 | }
160 | return true;
161 | }
162 |
163 | private void invertIfNecessaryToDate(SingleDate date) {
164 | if (this.date.toLocalDate().isAfter(date.toLocalDate())) {
165 | this.toDate = this.date;
166 | this.date = date;
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/date/DefaultDateCellRenderer.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.date;
2 |
3 | import com.formdev.flatlaf.FlatLaf;
4 | import com.formdev.flatlaf.ui.FlatUIUtils;
5 | import com.formdev.flatlaf.util.ColorFunctions;
6 | import com.formdev.flatlaf.util.UIScale;
7 | import raven.datetime.DatePicker;
8 | import raven.datetime.util.Utils;
9 |
10 | import javax.swing.*;
11 | import java.awt.*;
12 | import java.awt.geom.Area;
13 | import java.awt.geom.Rectangle2D;
14 |
15 | public class DefaultDateCellRenderer {
16 |
17 | public void paint(Graphics2D g2, DatePicker datePicker, ButtonDate component, SingleDate date, float width, float height) {
18 |
19 | // paint selection
20 | float arc = getArc(datePicker);
21 | boolean isSelected = component.isDateSelected();
22 | paintDateSelection(g2, datePicker, component, isSelected, width, height, arc);
23 |
24 | // paint date between selected
25 | DateSelectionModel dateSelectionModel = datePicker.getDateSelectionModel();
26 | if (dateSelectionModel.getDateSelectionMode() == DatePicker.DateSelectionMode.BETWEEN_DATE_SELECTED && dateSelectionModel.getDate() != null) {
27 | paintBetweenDateSelection(g2, datePicker, component, date, width, height, arc);
28 | }
29 | if (date.same(new SingleDate())) {
30 | paintCurrentDate(g2, datePicker, component, date, isSelected, width, height, arc);
31 | }
32 | }
33 |
34 | /**
35 | * Paint cell selection or cell focused
36 | */
37 | protected void paintDateSelection(Graphics2D g2, DatePicker datePicker, ButtonDate component, boolean isSelected, float width, float height, float arc) {
38 | Rectangle2D.Float rec = getRectangle(width, height);
39 | g2.setColor(getSelectionColor(datePicker, component, isSelected));
40 | g2.fill(FlatUIUtils.createComponentRectangle(rec.x, rec.y, rec.width, rec.height, arc));
41 | }
42 |
43 | protected void paintBetweenDateSelection(Graphics2D g2, DatePicker datePicker, ButtonDate component, SingleDate date, float width, float height, float arc) {
44 | g2.setColor(getBetweenDateColor(datePicker, component));
45 | DateSelectionModel dateSelectionModel = datePicker.getDateSelectionModel();
46 | Rectangle2D.Float rec = getRectangle(width, height);
47 | int rowIndex = component.getRowIndex();
48 | if (date.between(dateSelectionModel.getDate(), getToDate(datePicker))) {
49 | if (rowIndex == 0) {
50 | g2.fill(createShape(rec.x, rec.y, width, rec.height, arc, true, true));
51 | } else if (rowIndex == 6) {
52 | g2.fill(createShape(rec.x, rec.y, width, rec.height, arc, false, true));
53 | } else {
54 | g2.fill(new Rectangle2D.Float(0, rec.y, width, rec.height));
55 | }
56 | }
57 | if (!dateSelectionModel.getDate().same(getToDate(datePicker))) {
58 | boolean right = dateSelectionModel.getDate().before(getToDate(datePicker));
59 | if (date.same(dateSelectionModel.getDate())) {
60 | if ((right && rowIndex != 6) || !right && rowIndex != 0) {
61 | g2.fill(createShape(rec.x, rec.y, width, rec.height, arc, right, false));
62 | }
63 | }
64 | if (date.same(getToDate(datePicker))) {
65 | if ((right && rowIndex != 0) || (!right && rowIndex != 6)) {
66 | g2.fill(createShape(rec.x, rec.y, width, rec.height, arc, !right, !component.isHover() && dateSelectionModel.getToDate() == null));
67 | }
68 | }
69 | }
70 | }
71 |
72 | protected void paintCurrentDate(Graphics2D g2, DatePicker datePicker, ButtonDate component, SingleDate date, boolean isSelected, float width, float height, float arc) {
73 | Rectangle2D.Float rec = getRectangle(width, height);
74 | float fw = getSelectedFocusWidth();
75 | float space = UIScale.scale(isSelected ? fw : 0);
76 | if (space > 0) {
77 | rec.x -= space;
78 | rec.y -= space;
79 | rec.width += space * 2f;
80 | rec.height += space * 2f;
81 | }
82 | Area area = new Area(FlatUIUtils.createComponentRectangle(rec.x, rec.y, rec.width, rec.height, arc));
83 | if (isSelected) {
84 | float s = UIScale.scale(fw + 1f);
85 | area.subtract(new Area(FlatUIUtils.createComponentRectangle(rec.x + s, rec.x + s, rec.width - s * 2f, rec.height - s * 2f, arc - s)));
86 | } else {
87 | float s = UIScale.scale(fw);
88 | area.subtract(new Area(FlatUIUtils.createComponentRectangle(rec.x + s, rec.y + s, rec.width - s * 2f, rec.height - s * 2f, arc - s)));
89 | }
90 | Color accentColor = getAccentColor(datePicker);
91 | g2.setColor(isSelected ? getBorderColor(component, accentColor) : accentColor);
92 | g2.fill(area);
93 | }
94 |
95 | private SingleDate getToDate(DatePicker datePicker) {
96 | DateSelectionModel dateSelectionModel = datePicker.getDateSelectionModel();
97 | return dateSelectionModel.getToDate() != null ? dateSelectionModel.getToDate() : dateSelectionModel.getHoverDate();
98 | }
99 |
100 | protected Shape createShape(float x, float y, float width, float size, float arc, boolean right, boolean add) {
101 | Area area;
102 | if (right) {
103 | area = new Area(new Rectangle2D.Float(width / 2, y, width / 2, size));
104 | area.subtract(new Area(FlatUIUtils.createComponentRectangle(x, y, size, size, arc)));
105 | } else {
106 | area = new Area(new Rectangle2D.Float(0, y, width / 2, size));
107 | }
108 | if (add) {
109 | area.add(new Area(FlatUIUtils.createComponentRectangle(x, y, size, size, arc)));
110 | } else {
111 | area.subtract(new Area(FlatUIUtils.createComponentRectangle(x, y, size, size, arc)));
112 | }
113 | return area;
114 | }
115 |
116 | protected Color getBorderColor(ButtonDate component, Color color) {
117 | return ColorFunctions.mix(color, component.getParent().getBackground(), 0.45f);
118 | }
119 |
120 | protected Color getBetweenDateColor(DatePicker datePicker, ButtonDate component) {
121 | Color color = FlatUIUtils.getParentBackground(component);
122 | if (datePicker.getDateSelectionModel().getToDate() != null) {
123 | return ColorFunctions.mix(color, getAccentColor(datePicker), 0.9f);
124 | }
125 | return FlatLaf.isLafDark() ? ColorFunctions.lighten(color, 0.03f) : ColorFunctions.darken(color, 0.03f);
126 | }
127 |
128 | /**
129 | * Get selection color, focused color or isPress and isHover color
130 | */
131 | protected Color getSelectionColor(DatePicker datePicker, ButtonDate component, boolean isSelected) {
132 | // use component parent background as the focused color
133 | Color color = FlatUIUtils.getParentBackground(component);
134 | if (isSelected) {
135 | // use accent color as selection color
136 | color = getAccentColor(datePicker);
137 | }
138 | return Utils.getColor(color, component.isPress(), component.isHover());
139 | }
140 |
141 | protected Color getAccentColor(DatePicker datePicker) {
142 | if (datePicker.getColor() != null) {
143 | return datePicker.getColor();
144 | }
145 | return UIManager.getColor("Component.accentColor");
146 | }
147 |
148 | protected float getCellPadding() {
149 | return 7f;
150 | }
151 |
152 | protected float getSelectedFocusWidth() {
153 | return 2f;
154 | }
155 |
156 | protected float getArc(DatePicker datePicker) {
157 | return datePicker.getSelectionArc();
158 | }
159 |
160 | protected Rectangle2D.Float getRectangle(float width, float height) {
161 | float size = Math.min(width, height) - UIScale.scale(getCellPadding());
162 | float x = (width - size) / 2f;
163 | float y = (height - size) / 2f;
164 | return new Rectangle2D.Float(x, y, size, size);
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/date/Header.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.date;
2 |
3 | import com.formdev.flatlaf.FlatClientProperties;
4 | import com.formdev.flatlaf.extras.FlatSVGIcon;
5 | import net.miginfocom.swing.MigLayout;
6 | import raven.datetime.component.date.event.DateControlEvent;
7 | import raven.datetime.component.date.event.DateControlListener;
8 |
9 | import javax.swing.*;
10 | import java.text.DateFormatSymbols;
11 |
12 | public class Header extends JPanel {
13 |
14 | protected JButton buttonMonth;
15 | protected JButton buttonYear;
16 |
17 | protected Icon backIcon;
18 |
19 | protected Icon forwardIcon;
20 |
21 | public Header() {
22 | this(10, 2023);
23 | }
24 |
25 | public Header(int month, int year) {
26 | init(month, year);
27 | }
28 |
29 | private void init(int month, int year) {
30 | putClientProperty(FlatClientProperties.STYLE, "" +
31 | "background:null");
32 | setLayout(new MigLayout("fill,insets 3", "[]push[][]push[]", "fill"));
33 |
34 | JButton cmdBack = createButton();
35 | JButton cmdNext = createButton();
36 |
37 | backIcon = createDefaultBackIcon();
38 | forwardIcon = createDefaultForwardIcon();
39 | cmdBack.setIcon(backIcon);
40 | cmdNext.setIcon(forwardIcon);
41 |
42 | buttonMonth = createButton();
43 | buttonYear = createButton();
44 |
45 | cmdBack.addActionListener(e -> fireDateControlChanged(new DateControlEvent(this, DateControlEvent.DAY_STATE, DateControlEvent.BACK)));
46 | cmdNext.addActionListener(e -> fireDateControlChanged(new DateControlEvent(this, DateControlEvent.DAY_STATE, DateControlEvent.FORWARD)));
47 | buttonMonth.addActionListener(e -> fireDateControlChanged(new DateControlEvent(this, DateControlEvent.DAY_STATE, DateControlEvent.MONTH)));
48 | buttonYear.addActionListener(e -> fireDateControlChanged(new DateControlEvent(this, DateControlEvent.DAY_STATE, DateControlEvent.YEAR)));
49 |
50 | add(cmdBack);
51 | add(buttonMonth);
52 | add(buttonYear);
53 | add(cmdNext);
54 | setDate(month, year);
55 | }
56 |
57 | protected JButton createButton() {
58 | JButton button = new JButton();
59 | button.putClientProperty(FlatClientProperties.STYLE, "" +
60 | "background:null;" +
61 | "arc:10;" +
62 | "borderWidth:0;" +
63 | "focusWidth:0;" +
64 | "innerFocusWidth:0;" +
65 | "margin:0,5,0,5");
66 | return button;
67 | }
68 |
69 | protected Icon createDefaultBackIcon() {
70 | return new FlatSVGIcon("raven/datetime/icon/back.svg");
71 | }
72 |
73 | protected Icon createDefaultForwardIcon() {
74 | return new FlatSVGIcon("raven/datetime/icon/forward.svg");
75 | }
76 |
77 | public void addDateControlListener(DateControlListener listener) {
78 | listenerList.add(DateControlListener.class, listener);
79 | }
80 |
81 | public void removeDateControlListener(DateControlListener listener) {
82 | listenerList.remove(DateControlListener.class, listener);
83 | }
84 |
85 | public void fireDateControlChanged(DateControlEvent event) {
86 | Object[] listeners = listenerList.getListenerList();
87 | for (int i = listeners.length - 2; i >= 0; i -= 2) {
88 | if (listeners[i] == DateControlListener.class) {
89 | ((DateControlListener) listeners[i + 1]).dateControlChanged(event);
90 | }
91 | }
92 | }
93 |
94 | public void setDate(int month, int year) {
95 | buttonMonth.setText(DateFormatSymbols.getInstance().getMonths()[month]);
96 | buttonYear.setText(year + "");
97 | }
98 |
99 | public Icon getBackIcon() {
100 | return backIcon;
101 | }
102 |
103 | public void setBackIcon(Icon backIcon) {
104 | this.backIcon = backIcon;
105 | }
106 |
107 | public Icon getForwardIcon() {
108 | return forwardIcon;
109 | }
110 |
111 | public void setForwardIcon(Icon forwardIcon) {
112 | this.forwardIcon = forwardIcon;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/date/PanelDate.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.date;
2 |
3 | import com.formdev.flatlaf.FlatClientProperties;
4 | import net.miginfocom.swing.MigLayout;
5 | import raven.datetime.DatePicker;
6 |
7 | import javax.swing.*;
8 | import java.awt.*;
9 | import java.text.DateFormatSymbols;
10 | import java.util.Calendar;
11 |
12 | public class PanelDate extends JPanel {
13 |
14 | private final DatePicker datePicker;
15 | private final int month;
16 | private final int year;
17 |
18 | public PanelDate(DatePicker datePicker, int month, int year) {
19 | this.datePicker = datePicker;
20 | this.month = month;
21 | this.year = year;
22 | init();
23 | }
24 |
25 | private void init() {
26 | putClientProperty(FlatClientProperties.STYLE, "" +
27 | "background:null;");
28 | setLayout(new MigLayout(
29 | "novisualpadding,wrap 7,insets 3,gap 0,al center center",
30 | "[fill]",
31 | "[fill]10[fill][fill]"));
32 | load();
33 | }
34 |
35 | public void load() {
36 | removeAll();
37 | createDateHeader();
38 | final int col = 7;
39 | final int row = 6;
40 | final int t = col * row;
41 | Calendar calendar = Calendar.getInstance();
42 | calendar.set(Calendar.YEAR, year);
43 | calendar.set(Calendar.MONTH, month);
44 | calendar.set(Calendar.DATE, 1);
45 | int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
46 | if (datePicker.isStartWeekOnMonday() && dayOfWeek == Calendar.SUNDAY) {
47 | dayOfWeek = 8;
48 | }
49 | int startDay = dayOfWeek - (datePicker.isStartWeekOnMonday() ? 2 : 1);
50 | calendar.add(Calendar.DATE, -startDay);
51 | int rowIndex = 0;
52 | for (int i = 1; i <= t; i++) {
53 | SingleDate singleDate = new SingleDate(calendar);
54 | boolean selectable = datePicker.getDateSelectionModel().getDateSelectionAble() == null || datePicker.getDateSelectionModel().getDateSelectionAble().isDateSelectedAble(singleDate.toLocalDate());
55 | boolean enable = calendar.get(Calendar.MONTH) == month && calendar.get(Calendar.YEAR) == year;
56 | JButton button = createButton(new SingleDate(calendar), enable, rowIndex);
57 | if (!selectable) {
58 | button.setEnabled(false);
59 | }
60 | add(button);
61 | calendar.add(Calendar.DATE, 1);
62 | if (rowIndex == 6) {
63 | rowIndex = 0;
64 | } else {
65 | rowIndex++;
66 | }
67 | }
68 | checkSelection();
69 | }
70 |
71 | protected void createDateHeader() {
72 | String[] weekdays = DatePicker.getDefaultWeekdays();
73 | if (weekdays == null) {
74 | weekdays = DateFormatSymbols.getInstance().getShortWeekdays();
75 | }
76 | // swap monday to the start day of week
77 | if (datePicker.isStartWeekOnMonday()) {
78 | String sunday = weekdays[1];
79 | for (int i = 2; i < weekdays.length; i++) {
80 | weekdays[i - 1] = weekdays[i];
81 | }
82 | weekdays[weekdays.length - 1] = sunday;
83 | }
84 | for (String week : weekdays) {
85 | if (!week.isEmpty()) {
86 | add(createLabel(week));
87 | }
88 | }
89 | }
90 |
91 | private JLabel createLabel(String text) {
92 | JLabel label = new JLabel(text, JLabel.CENTER);
93 | label.putClientProperty(FlatClientProperties.STYLE, "" +
94 | "[light]foreground:lighten($Label.foreground,30%);" +
95 | "[dark]foreground:darken($Label.foreground,30%)");
96 | return label;
97 | }
98 |
99 | protected JButton createButton(SingleDate date, boolean enable, int rowIndex) {
100 | ButtonDate button = new ButtonDate(datePicker, date, enable, rowIndex);
101 | if (button.isDateSelected()) {
102 | button.setSelected(true);
103 | }
104 | return button;
105 | }
106 |
107 | public void checkSelection() {
108 | for (int i = 0; i < getComponentCount(); i++) {
109 | Component com = getComponent(i);
110 | if (com instanceof ButtonDate) {
111 | ButtonDate buttonDate = (ButtonDate) com;
112 | if (datePicker.getDateSelectionModel().getDateSelectionMode() == DatePicker.DateSelectionMode.SINGLE_DATE_SELECTED) {
113 | buttonDate.setSelected(buttonDate.getDate().same(datePicker.getDateSelectionModel().getDate()));
114 | } else {
115 | buttonDate.setSelected(buttonDate.getDate().same(datePicker.getDateSelectionModel().getDate()) || buttonDate.getDate().same(datePicker.getDateSelectionModel().getToDate()));
116 | }
117 | }
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/date/PanelMonth.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.date;
2 |
3 | import com.formdev.flatlaf.FlatClientProperties;
4 | import net.miginfocom.swing.MigLayout;
5 | import raven.datetime.DatePicker;
6 |
7 | import javax.swing.*;
8 | import javax.swing.event.ChangeEvent;
9 | import javax.swing.event.ChangeListener;
10 | import java.awt.*;
11 | import java.text.DateFormatSymbols;
12 |
13 | public class PanelMonth extends JPanel {
14 |
15 | private final DatePicker datePicker;
16 | private final int year;
17 | private int selectedMonth = -1;
18 |
19 | public PanelMonth(DatePicker datePicker, int year) {
20 | this.datePicker = datePicker;
21 | this.year = year;
22 | init();
23 | }
24 |
25 | private void init() {
26 | putClientProperty(FlatClientProperties.STYLE, "" +
27 | "background:null");
28 | setLayout(new MigLayout(
29 | "novisualpadding,wrap 3,insets 0,fillx,gap 0,al center center",
30 | "fill,sg main",
31 | "fill"));
32 |
33 | final int count = 12;
34 | for (int i = 0; i < count; i++) {
35 | final int month = i;
36 | ButtonMonthYear button = new ButtonMonthYear(datePicker, i);
37 | button.setText(DateFormatSymbols.getInstance().getMonths()[i]);
38 | if (checkSelected(month + 1)) {
39 | button.setSelected(true);
40 | }
41 | button.addActionListener(e -> {
42 | this.selectedMonth = month;
43 | fireMonthChanged(new ChangeEvent(this));
44 | });
45 | add(button);
46 | }
47 | }
48 |
49 | public boolean checkSelected(int month) {
50 | DateSelectionModel dateSelectionModel = datePicker.getDateSelectionModel();
51 | if (dateSelectionModel.getDateSelectionMode() == DatePicker.DateSelectionMode.SINGLE_DATE_SELECTED) {
52 | return dateSelectionModel.getDate() != null && year == dateSelectionModel.getDate().getYear() && month == dateSelectionModel.getDate().getMonth();
53 | } else {
54 | return (dateSelectionModel.getDate() != null && year == dateSelectionModel.getDate().getYear() && month == dateSelectionModel.getDate().getMonth()) ||
55 | (dateSelectionModel.getToDate() != null && year == dateSelectionModel.getToDate().getYear() && month == dateSelectionModel.getToDate().getMonth());
56 | }
57 | }
58 |
59 | public void checkSelection() {
60 | for (int i = 0; i < getComponentCount(); i++) {
61 | Component com = getComponent(i);
62 | if (com instanceof ButtonMonthYear) {
63 | ButtonMonthYear button = (ButtonMonthYear) com;
64 | button.setSelected(checkSelected(button.getValue() + 1));
65 | }
66 | }
67 | }
68 |
69 | public void addChangeListener(ChangeListener listener) {
70 | listenerList.add(ChangeListener.class, listener);
71 | }
72 |
73 | public void removeChangeListener(ChangeListener listener) {
74 | listenerList.remove(ChangeListener.class, listener);
75 | }
76 |
77 | public void fireMonthChanged(ChangeEvent event) {
78 | Object[] listeners = listenerList.getListenerList();
79 | for (int i = listeners.length - 2; i >= 0; i -= 2) {
80 | if (listeners[i] == ChangeListener.class) {
81 | ((ChangeListener) listeners[i + 1]).stateChanged(event);
82 | }
83 | }
84 | }
85 |
86 | public int getSelectedMonth() {
87 | return selectedMonth;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/date/PanelYear.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.date;
2 |
3 | import com.formdev.flatlaf.FlatClientProperties;
4 | import net.miginfocom.swing.MigLayout;
5 | import raven.datetime.DatePicker;
6 |
7 | import javax.swing.*;
8 | import javax.swing.event.ChangeEvent;
9 | import javax.swing.event.ChangeListener;
10 | import java.awt.*;
11 |
12 | public class PanelYear extends JPanel {
13 |
14 | public static final int YEAR_CELL = 28;
15 | private final DatePicker datePicker;
16 | private final int year;
17 | private int selectedYear = -1;
18 |
19 | public PanelYear(DatePicker datePicker, int year) {
20 | this.datePicker = datePicker;
21 | this.year = year;
22 | init();
23 | }
24 |
25 | private void init() {
26 | putClientProperty(FlatClientProperties.STYLE, "" +
27 | "background:null");
28 | setLayout(new MigLayout(
29 | "novisualpadding,wrap 4,insets 0,fillx,gap 0,al center center",
30 | "fill,sg main",
31 | "fill"));
32 |
33 | final int count = YEAR_CELL;
34 | for (int i = 0; i < count; i++) {
35 | final int y = getStartYear(year) + i;
36 | ButtonMonthYear button = new ButtonMonthYear(datePicker, y);
37 | button.setText(y + "");
38 | if (checkSelected(y)) {
39 | button.setSelected(true);
40 | }
41 | button.addActionListener(e -> {
42 | this.selectedYear = y;
43 | fireYearChanged(new ChangeEvent(this));
44 | });
45 | add(button);
46 | }
47 | checkSelection();
48 | }
49 |
50 | private int getStartYear(int year) {
51 | int initYear = 1900;
52 | int currentYear = year;
53 | int yearsPerPage = YEAR_CELL;
54 | int yearsPassed = currentYear - initYear;
55 | int pages = yearsPassed / yearsPerPage;
56 | int startingYearOnPage = initYear + (pages * yearsPerPage);
57 | return startingYearOnPage;
58 | }
59 |
60 | protected boolean checkSelected(int year) {
61 | DateSelectionModel dateSelectionModel = datePicker.getDateSelectionModel();
62 | if (dateSelectionModel.getDateSelectionMode() == DatePicker.DateSelectionMode.SINGLE_DATE_SELECTED) {
63 | return dateSelectionModel.getDate() != null && year == dateSelectionModel.getDate().getYear();
64 | } else {
65 | return (dateSelectionModel.getDate() != null && year == dateSelectionModel.getDate().getYear()) ||
66 | (dateSelectionModel.getToDate() != null && year == dateSelectionModel.getToDate().getYear());
67 | }
68 | }
69 |
70 | public void checkSelection() {
71 | for (int i = 0; i < getComponentCount(); i++) {
72 | Component com = getComponent(i);
73 | if (com instanceof ButtonMonthYear) {
74 | ButtonMonthYear button = (ButtonMonthYear) com;
75 | button.setSelected(checkSelected(button.getValue()));
76 | }
77 | }
78 | }
79 |
80 | public int getYear() {
81 | return year;
82 | }
83 |
84 | public void addChangeListener(ChangeListener listener) {
85 | listenerList.add(ChangeListener.class, listener);
86 | }
87 |
88 | public void removeChangeListener(ChangeListener listener) {
89 | listenerList.remove(ChangeListener.class, listener);
90 | }
91 |
92 | public void fireYearChanged(ChangeEvent event) {
93 | Object[] listeners = listenerList.getListenerList();
94 | for (int i = listeners.length - 2; i >= 0; i -= 2) {
95 | if (listeners[i] == ChangeListener.class) {
96 | ((ChangeListener) listeners[i + 1]).stateChanged(event);
97 | }
98 | }
99 | }
100 |
101 | public int getSelectedYear() {
102 | return selectedYear;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/date/SingleDate.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.date;
2 |
3 | import java.time.LocalDate;
4 | import java.util.Calendar;
5 |
6 | public class SingleDate {
7 |
8 | private int day;
9 | private int month;
10 | private int year;
11 |
12 | public SingleDate() {
13 | this(LocalDate.now());
14 | }
15 |
16 | public SingleDate(LocalDate date) {
17 | this.day = date.getDayOfMonth();
18 | this.month = date.getMonthValue();
19 | this.year = date.getYear();
20 | }
21 |
22 | public SingleDate(Calendar calendar) {
23 | this(calendar.get(Calendar.DATE), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.YEAR));
24 | }
25 |
26 | public SingleDate(int day, int month, int year) {
27 | this.day = day;
28 | this.month = month;
29 | this.year = year;
30 | }
31 |
32 | public int getDay() {
33 | return day;
34 | }
35 |
36 | public void setDay(int day) {
37 | this.day = day;
38 | }
39 |
40 | public int getMonth() {
41 | return month;
42 | }
43 |
44 | public void setMonth(int month) {
45 | this.month = month;
46 | }
47 |
48 | public int getYear() {
49 | return year;
50 | }
51 |
52 | public void setYear(int year) {
53 | this.year = year;
54 | }
55 |
56 | public boolean same(SingleDate date) {
57 | return date != null && year == date.year && month == date.month && day == date.day;
58 | }
59 |
60 | public boolean after(SingleDate date) {
61 | if (date == null) {
62 | return false;
63 | }
64 | if (year > date.year) {
65 | return true;
66 | } else if (year < date.year) {
67 | return false;
68 | } else {
69 | if (month > date.month) {
70 | return true;
71 | } else if (month < date.month) {
72 | return false;
73 | } else {
74 | return day > date.day;
75 | }
76 | }
77 | }
78 |
79 | public boolean before(SingleDate date) {
80 | if (date == null) {
81 | return false;
82 | }
83 | if (year < date.year) {
84 | return true;
85 | } else if (year > date.year) {
86 | return false;
87 | } else {
88 | if (month < date.month) {
89 | return true;
90 | } else if (month > date.month) {
91 | return false;
92 | } else {
93 | return day < date.day;
94 | }
95 | }
96 | }
97 |
98 | public boolean between(SingleDate from, SingleDate to) {
99 | if (from.before(to)) {
100 | return after(from) && before(to);
101 | } else {
102 | return after(to) && before(from);
103 | }
104 | }
105 |
106 | public LocalDate toLocalDate() {
107 | return LocalDate.of(year, month, day);
108 | }
109 |
110 | @Override
111 | public String toString() {
112 | return day + "/" + month + "/" + year;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/date/event/DateControlEvent.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.date.event;
2 |
3 | import java.util.EventObject;
4 |
5 | public class DateControlEvent extends EventObject {
6 |
7 | public static final int DAY_STATE = 1;
8 | public static final int MONTH_STATE = 2;
9 | public static final int YEAR_STATE = 3;
10 |
11 | public static final int BACK = 10;
12 | public static final int FORWARD = 11;
13 | public static final int MONTH = 12;
14 | public static final int YEAR = 13;
15 |
16 | protected int state;
17 | protected int type;
18 |
19 | public DateControlEvent(Object source, int state, int type) {
20 | super(source);
21 | this.state = state;
22 | this.type = type;
23 | }
24 |
25 | public int getState() {
26 | return state;
27 | }
28 |
29 | public int getType() {
30 | return type;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/date/event/DateControlListener.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.date.event;
2 |
3 | import java.util.EventListener;
4 |
5 | public interface DateControlListener extends EventListener {
6 |
7 | void dateControlChanged(DateControlEvent e);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/date/event/DateSelectionModelEvent.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.date.event;
2 |
3 | import java.util.EventObject;
4 |
5 | public class DateSelectionModelEvent extends EventObject {
6 |
7 | public static final int BETWEEN_DATE_HOVER = 1;
8 | public static final int DATE = 2;
9 |
10 | protected int action;
11 |
12 | public DateSelectionModelEvent(Object source, int action) {
13 | super(source);
14 | this.action = action;
15 | }
16 |
17 | public int getAction() {
18 | return action;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/date/event/DateSelectionModelListener.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.date.event;
2 |
3 | import java.util.EventListener;
4 |
5 | public interface DateSelectionModelListener extends EventListener {
6 |
7 | void dateSelectionModelChanged(DateSelectionModelEvent e);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/time/AnimationChange.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.time;
2 |
3 | import com.formdev.flatlaf.util.Animator;
4 | import com.formdev.flatlaf.util.CubicBezierEasing;
5 |
6 | import java.awt.*;
7 |
8 | public class AnimationChange {
9 |
10 | public final Animator animator;
11 |
12 | private final AnimationValue angleValue = new AnimationValue();
13 | private final AnimationValue marginValue = new AnimationValue();
14 |
15 | private float angle;
16 |
17 | private float margin;
18 |
19 | public AnimationChange(Component component) {
20 | animator = new Animator(350, v -> {
21 | angle = angleValue.interpolate(v);
22 | margin = marginValue.interpolate(v);
23 | component.repaint();
24 | });
25 | animator.setInterpolator(CubicBezierEasing.EASE);
26 | }
27 |
28 | public void start(float angleTarget, float marginTarget, boolean animated) {
29 | if (angle != angleTarget || margin != marginTarget) {
30 | angleValue.set(angle, angleTarget);
31 | marginValue.set(margin, marginTarget);
32 | if (animator.isRunning()) {
33 | animator.stop();
34 | }
35 | if (animated) {
36 | animator.start();
37 | } else {
38 | angle = angleValue.interpolate(1f);
39 | margin = marginValue.interpolate(1f);
40 | }
41 | }
42 | }
43 |
44 | public float getAngle() {
45 | return angle;
46 | }
47 |
48 | public float getMargin() {
49 | return margin;
50 | }
51 |
52 | public void set(float angle, float margin) {
53 | if (animator.isRunning()) {
54 | animator.stop();
55 | }
56 | this.angle = angle;
57 | this.margin = margin;
58 | }
59 |
60 | public boolean isRunning() {
61 | return animator.isRunning();
62 | }
63 |
64 | private static class AnimationValue {
65 |
66 | private float from;
67 | private float target;
68 |
69 | public void set(float from, float target) {
70 | this.from = from;
71 | this.target = target;
72 | }
73 |
74 | private float interpolate(float fraction) {
75 | return from + ((target - from) * fraction);
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/time/Header.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.time;
2 |
3 | import com.formdev.flatlaf.FlatClientProperties;
4 | import net.miginfocom.swing.MigLayout;
5 | import raven.datetime.TimePicker;
6 | import raven.datetime.component.time.event.TimeActionListener;
7 |
8 | import javax.swing.*;
9 | import java.awt.*;
10 | import java.text.DateFormatSymbols;
11 | import java.text.DecimalFormat;
12 | import java.util.Locale;
13 |
14 | public class Header extends JPanel {
15 |
16 | private final TimePicker timePicker;
17 | private final TimeActionListener timeActionListener;
18 | private final DecimalFormat format = new DecimalFormat("00");
19 | private MigLayout layout;
20 | private Color color;
21 |
22 | public Header(TimePicker timePicker, TimeActionListener timeActionListener) {
23 | this.timePicker = timePicker;
24 | this.timeActionListener = timeActionListener;
25 | init();
26 | }
27 |
28 | private void init() {
29 | layout = new MigLayout("fill,insets 10", "center");
30 | setLayout(layout);
31 | add(createToolBar(), "id b1");
32 | add(createAmPm(), "pos b1.x2+rel 0.5al n n");
33 | }
34 |
35 | public void updateHeader() {
36 | int hour = timePicker.getTimeSelectionModel().getHour();
37 | int minute = timePicker.getTimeSelectionModel().getMinute();
38 |
39 | if (hour == -1 && minute == -1) {
40 | buttonAm.setSelected(false);
41 | buttonPm.setSelected(false);
42 | } else {
43 | if (hour >= 12) {
44 | setSelectedAm(false);
45 | } else {
46 | setSelectedAm(true);
47 | }
48 | }
49 |
50 | if (!timePicker.is24HourView()) {
51 | if (hour >= 12) {
52 | hour -= 12;
53 | }
54 | if (hour == 0) {
55 | hour = 12;
56 | }
57 | }
58 |
59 | String hourText = hour == -1 ? "--" : format.format(hour);
60 | String minuteText = minute == -1 ? "--" : format.format(minute);
61 | buttonHour.setText(hourText);
62 | buttonMinute.setText(minuteText);
63 | }
64 |
65 | public void setOrientation(int orientation) {
66 | String c = orientation == SwingConstants.VERTICAL ? "pos b1.x2+rel 0.5al n n" : "pos 0.5al b1.y2+rel n n";
67 | amPmToolBar.setOrientation(orientation);
68 | layout.setComponentConstraints(amPmToolBar, c);
69 | }
70 |
71 | public boolean isAm() {
72 | return !buttonPm.isSelected();
73 | }
74 |
75 | public void setHourSelectionView(boolean hourSelectionView) {
76 | if (hourSelectionView) {
77 | buttonHour.setSelected(true);
78 | } else {
79 | buttonMinute.setSelected(true);
80 | }
81 | }
82 |
83 | public void setUse24hour(boolean use24hour) {
84 | amPmToolBar.setVisible(!use24hour);
85 | }
86 |
87 | protected JToolBar createToolBar() {
88 | JToolBar toolBar = new JToolBar();
89 | toolBar.putClientProperty(FlatClientProperties.STYLE, "" +
90 | "background:null;" +
91 | "hoverButtonGroupBackground:null;");
92 | buttonHour = createButton();
93 | buttonMinute = createButton();
94 | ButtonGroup group = new ButtonGroup();
95 | group.add(buttonHour);
96 | group.add(buttonMinute);
97 | buttonHour.setSelected(true);
98 | buttonHour.addActionListener(e -> timeActionListener.selectionViewChanged(true));
99 | buttonMinute.addActionListener(e -> timeActionListener.selectionViewChanged(false));
100 | toolBar.add(buttonHour);
101 | toolBar.add(createSplit());
102 | toolBar.add(buttonMinute);
103 | return toolBar;
104 | }
105 |
106 | protected JToggleButton createButton() {
107 | JToggleButton button = new JToggleButton("--");
108 | button.putClientProperty(FlatClientProperties.STYLE, "" +
109 | "font:+15;" +
110 | "toolbar.margin:3,5,3,5;" +
111 | "foreground:contrast($Component.accentColor,$ToggleButton.background,#fff);" +
112 | "background:null;" +
113 | "toolbar.hoverBackground:null");
114 | return button;
115 | }
116 |
117 | protected JButton createAmPmButton(boolean isAm) {
118 | String[] amPM = DateFormatSymbols.getInstance(Locale.ENGLISH).getAmPmStrings();
119 | String amOrPm = isAm ? amPM[0] : amPM[1];
120 | JButton button = new JButton(amOrPm);
121 | button.addActionListener(e -> actionAmPmChanged(isAm));
122 | button.putClientProperty(FlatClientProperties.STYLE, "" +
123 | "font:+1;" +
124 | "foreground:contrast($Component.accentColor,$ToggleButton.background,#fff);" +
125 | "background:null;" +
126 | "toolbar.hoverBackground:null");
127 | return button;
128 | }
129 |
130 | private void actionAmPmChanged(boolean isAm) {
131 | TimeSelectionModel timeSelectionModel = timePicker.getTimeSelectionModel();
132 | int hour = timeSelectionModel.getHour();
133 | int minute = timeSelectionModel.getMinute();
134 | if (hour == -1 && minute == -1) {
135 | setSelectedAm(isAm);
136 |
137 | // need to repaint the panel clock to update the paint text selection able
138 | timePicker.repaint();
139 | } else {
140 | if (isAm) {
141 | if (hour >= 12) {
142 | hour -= 12;
143 | }
144 | } else {
145 | if (hour < 12) {
146 | hour += 12;
147 | }
148 | }
149 | timeSelectionModel.set(hour, minute);
150 | }
151 | }
152 |
153 | private void setSelectedAm(boolean isAm) {
154 | buttonAm.setSelected(isAm);
155 | buttonPm.setSelected(!isAm);
156 | }
157 |
158 | protected JLabel createSplit() {
159 | JLabel label = new JLabel(":");
160 | label.putClientProperty(FlatClientProperties.STYLE, "" +
161 | "font:+10;" +
162 | "foreground:contrast($Component.accentColor,$Label.background,#fff)");
163 | return label;
164 | }
165 |
166 | protected JToolBar createAmPm() {
167 | amPmToolBar = new JToolBar();
168 | amPmToolBar.setOrientation(SwingConstants.VERTICAL);
169 | amPmToolBar.putClientProperty(FlatClientProperties.STYLE, "" +
170 | "background:null;" +
171 | "hoverButtonGroupBackground:null");
172 | buttonAm = createAmPmButton(true);
173 | buttonPm = createAmPmButton(false);
174 | amPmToolBar.add(buttonAm);
175 | amPmToolBar.add(buttonPm);
176 | return amPmToolBar;
177 | }
178 |
179 | public void setColor(Color color) {
180 | this.color = color;
181 | }
182 |
183 | /**
184 | * Override this method to return the background color to the JToolBar
185 | * When JToolBar use null background, so it will paint the parent background.
186 | */
187 | @Override
188 | public Color getBackground() {
189 | if (color != null) {
190 | return color;
191 | }
192 | return UIManager.getColor("Component.accentColor");
193 | }
194 |
195 | @Override
196 | public void setEnabled(boolean enabled) {
197 | super.setEnabled(enabled);
198 | buttonAm.setEnabled(enabled);
199 | buttonPm.setEnabled(enabled);
200 | }
201 |
202 | private JToggleButton buttonHour;
203 | private JToggleButton buttonMinute;
204 |
205 | private JToolBar amPmToolBar;
206 | private JButton buttonAm;
207 | private JButton buttonPm;
208 | }
209 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/time/PanelClock.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.time;
2 |
3 | import com.formdev.flatlaf.FlatClientProperties;
4 | import com.formdev.flatlaf.FlatLaf;
5 | import com.formdev.flatlaf.ui.FlatUIUtils;
6 | import com.formdev.flatlaf.util.ColorFunctions;
7 | import com.formdev.flatlaf.util.UIScale;
8 | import raven.datetime.TimePicker;
9 | import raven.datetime.component.time.event.TimeActionListener;
10 |
11 | import javax.swing.*;
12 | import java.awt.*;
13 | import java.awt.event.MouseAdapter;
14 | import java.awt.event.MouseEvent;
15 | import java.awt.geom.*;
16 |
17 | public class PanelClock extends JPanel {
18 |
19 | private final TimePicker timePicker;
20 | private final TimeActionListener timeActionListener;
21 | private boolean use24hour;
22 | private boolean hourSelectionView = true;
23 | private AnimationChange animationChange;
24 | private final int margin12h = 20;
25 | private final int margin24h = 50;
26 | private Color color;
27 |
28 | public void setHourSelectionView(boolean hourSelectionView) {
29 | if (this.hourSelectionView != hourSelectionView) {
30 | this.hourSelectionView = hourSelectionView;
31 | repaint();
32 | runAnimation(true);
33 | }
34 | }
35 |
36 | public void setHourSelectionViewImmediately(boolean hourSelectionView) {
37 | if (this.hourSelectionView != hourSelectionView) {
38 | this.hourSelectionView = hourSelectionView;
39 | repaint();
40 | runAnimation(false);
41 | }
42 | }
43 |
44 | public boolean isHourSelectionView() {
45 | return hourSelectionView;
46 | }
47 |
48 | public void setUse24hour(boolean use24hour) {
49 | if (this.use24hour != use24hour) {
50 | this.use24hour = use24hour;
51 | }
52 | }
53 |
54 | public boolean isUse24hour() {
55 | return use24hour;
56 | }
57 |
58 | public PanelClock(TimePicker timePicker, TimeActionListener timeActionListener) {
59 | this.timePicker = timePicker;
60 | this.timeActionListener = timeActionListener;
61 | init();
62 | }
63 |
64 | private void init() {
65 | animationChange = new AnimationChange(this);
66 | putClientProperty(FlatClientProperties.STYLE, "" +
67 | "border:5,15,5,15;" +
68 | "background:null;" +
69 | "foreground:contrast($Component.accentColor,$Panel.background,#fff)");
70 | MouseAdapter mouseAdapter = new MouseAdapter() {
71 | @Override
72 | public void mousePressed(MouseEvent e) {
73 | if (isEnabled()) {
74 | mouseChanged(e);
75 | }
76 | }
77 |
78 | @Override
79 | public void mouseReleased(MouseEvent e) {
80 | if (isEnabled()) {
81 | timeActionListener.selectionViewChanged(hourSelectionView);
82 | }
83 | }
84 |
85 | @Override
86 | public void mouseDragged(MouseEvent e) {
87 | if (isEnabled()) {
88 | mouseChanged(e);
89 | }
90 | }
91 |
92 | private void mouseChanged(MouseEvent e) {
93 | int value = getValueOf(e.getPoint(), hourSelectionView);
94 | if (hourSelectionView) {
95 | timePicker.getTimeSelectionModel().setHour(value);
96 | } else {
97 | timePicker.getTimeSelectionModel().setMinute(value);
98 | }
99 | }
100 | };
101 | addMouseListener(mouseAdapter);
102 | addMouseMotionListener(mouseAdapter);
103 | }
104 |
105 | public void updateClock() {
106 | TimeSelectionModel timeSelectionModel = timePicker.getTimeSelectionModel();
107 | if (hourSelectionView) {
108 | animationChange.set(getAngleOf(timeSelectionModel.getHour(), true), getTargetMargin());
109 | } else {
110 | animationChange.set(getAngleOf(timeSelectionModel.getMinute(), false), getTargetMargin());
111 | }
112 | }
113 |
114 | @Override
115 | protected void paintComponent(Graphics g) {
116 | super.paintComponent(g);
117 | Graphics2D g2 = (Graphics2D) g.create();
118 | FlatUIUtils.setRenderingHints(g2);
119 | Insets insets = getInsets();
120 | int width = getWidth() - (insets.left + insets.right);
121 | int height = getHeight() - (insets.top + insets.bottom);
122 |
123 | int size = Math.min(width, height);
124 | g2.translate(insets.left, insets.top);
125 | int x = (width - size) / 2;
126 | int y = (height - size) / 2;
127 |
128 | // create clock background
129 | g2.setColor(getClockBackground());
130 | g2.fill(new Ellipse2D.Double(x, y, size, size));
131 |
132 | // create selection
133 | paintSelection(g2, x, y, size);
134 |
135 | // create clock number
136 | paintClockNumber(g2, x, y, size);
137 | g2.dispose();
138 | }
139 |
140 | protected void paintSelection(Graphics2D g2, int x, int y, int size) {
141 | TimeSelectionModel timeSelectionModel = timePicker.getTimeSelectionModel();
142 | AffineTransform tran = g2.getTransform();
143 | size = size / 2;
144 | final float margin = UIScale.scale(animationChange.getMargin());
145 | float centerSize = UIScale.scale(8f);
146 | float lineSize = UIScale.scale(3);
147 | float selectSize = UIScale.scale(25f);
148 | float unselectSize = UIScale.scale(4);
149 | float lineHeight = size - margin;
150 | Area area = new Area(new Ellipse2D.Float(x + size - (centerSize / 2), y + size - (centerSize / 2), centerSize, centerSize));
151 | if ((hourSelectionView && timeSelectionModel.getHour() != -1) || (!hourSelectionView && timeSelectionModel.getMinute() != -1)) {
152 | area.add(new Area(new RoundRectangle2D.Float(x + size - (lineSize / 2), y + margin, lineSize, lineHeight, lineSize, lineSize)));
153 | area.add(new Area(new Ellipse2D.Float(x + size - (selectSize / 2), y + margin - selectSize / 2, selectSize, selectSize)));
154 | if (!hourSelectionView && !animationChange.isRunning() && (timeSelectionModel.getMinute() % 5 != 0)) {
155 | area.subtract(new Area(new Ellipse2D.Float(x + size - (unselectSize / 2), y + margin - unselectSize / 2, unselectSize, unselectSize)));
156 | }
157 | }
158 | g2.setColor(getSelectedColor());
159 | float angle = animationChange.getAngle();
160 | g2.rotate(Math.toRadians(angle), x + size, y + size);
161 | g2.fill(area);
162 | g2.setTransform(tran);
163 | }
164 |
165 | protected void paintClockNumber(Graphics2D g2, int x, int y, int size) {
166 | paintClockNumber(g2, x, y, size, margin12h, 0, hourSelectionView ? 1 : 5);
167 | if (hourSelectionView && use24hour) {
168 | paintClockNumber(g2, x, y, size, margin24h, 12, 1);
169 | }
170 | }
171 |
172 | protected void paintClockNumber(Graphics2D g2, int x, int y, int size, int margin, int start, int add) {
173 | TimeSelectionModel timeSelectionModel = timePicker.getTimeSelectionModel();
174 | final int mg = UIScale.scale(margin);
175 | float center = size / 2f;
176 | float angle = 360 / 12f;
177 | for (int i = 1; i <= 12; i++) {
178 | float ag = angle * i - 90;
179 | int value = fixHour((start + i * add), hourSelectionView);
180 | float nx = (float) (center + (Math.cos(Math.toRadians(ag)) * (center - mg)));
181 | float ny = (float) (center + (Math.sin(Math.toRadians(ag)) * (center - mg)));
182 |
183 | int hour;
184 | int minute;
185 | if (hourSelectionView) {
186 | hour = valueToTime(value, true);
187 | minute = timeSelectionModel.getMinute();
188 | } else {
189 | hour = timeSelectionModel.getHour();
190 | minute = value;
191 | }
192 | boolean isSelectedAble = timeSelectionModel.checkSelection(hour, minute);
193 | paintNumber(g2, x + nx, y + ny, fixNumberAndToString(value), isSelected(value), isSelectedAble);
194 | }
195 | }
196 |
197 | protected void paintNumber(Graphics2D g2, float x, float y, String num, boolean isSelected, boolean isSelectedAble) {
198 | FontMetrics fm = g2.getFontMetrics();
199 | Rectangle2D rec = fm.getStringBounds(num, g2);
200 | float x1 = (float) (x - rec.getWidth() / 2f);
201 | float y1 = (float) (y - rec.getHeight() / 2f);
202 | if (!isSelectedAble) {
203 | g2.setColor(UIManager.getColor("Label.disabledForeground"));
204 | } else if (isSelected) {
205 | g2.setColor(getSelectedForeground());
206 | } else {
207 | g2.setColor(UIManager.getColor("Panel.foreground"));
208 | }
209 | g2.drawString(num, x1, y1 + fm.getAscent());
210 | }
211 |
212 | protected Color getClockBackground() {
213 | if (FlatLaf.isLafDark()) {
214 | return ColorFunctions.lighten(getBackground(), 0.03f);
215 | } else {
216 | return ColorFunctions.darken(getBackground(), 0.03f);
217 | }
218 | }
219 |
220 | protected boolean isSelected(int value) {
221 | if (hourSelectionView) {
222 | int hour = getHourValue(timePicker.getTimeSelectionModel().getHour());
223 | return value == hour;
224 | } else {
225 | return value == timePicker.getTimeSelectionModel().getMinute();
226 | }
227 | }
228 |
229 | protected Color getSelectedColor() {
230 | if (color != null) {
231 | return color;
232 | }
233 | return UIManager.getColor("Component.accentColor");
234 | }
235 |
236 | protected Color getSelectedForeground() {
237 | return getForeground();
238 | }
239 |
240 | /**
241 | * Convert angle to hour or minute base on the hourView
242 | * Return value hour or minute
243 | */
244 | private int getValueOf(float angle, boolean hourView) {
245 | float ag = angle / 360;
246 | int value = (int) (ag * (hourView ? 12 : 60));
247 | if (hourView) {
248 | if (isUse24hour()) {
249 | return value == 0 ? 12 : value;
250 | } else {
251 | return value == 12 ? 0 : value;
252 | }
253 | } else {
254 | return value == 60 ? 0 : value;
255 | }
256 | }
257 |
258 | /**
259 | * Convert point location to the value hour or minute base on the hourView
260 | * Return value hour or minute
261 | */
262 | private int getValueOf(Point point, boolean hourView) {
263 | float angle = getAngleOf(point) + (hourView ? 360 / 12f / 2f : 360 / 60f / 2f);
264 | int value = getValueOf(angle, hourView);
265 | if (hourView) {
266 | boolean isAdd12Hour = (!use24hour && !timePicker.getHeader().isAm())
267 | || (use24hour && is24hourSelect(point));
268 | return fixHour(value + (isAdd12Hour ? 12 : 0), true);
269 | } else {
270 | return value;
271 | }
272 | }
273 |
274 | private boolean is24hourSelect(Point point) {
275 | Insets insets = getInsets();
276 | int width = getWidth() - (insets.left + insets.right);
277 | int height = getHeight() - (insets.top + insets.bottom);
278 | int size = Math.min(width, height) / 2;
279 | int distanceTarget = (size - UIScale.scale(margin12h + 15));
280 | float centerX = insets.left + width / 2f;
281 | float centerY = insets.top + height / 2f;
282 | double distance = Math.sqrt(Math.pow((point.x - centerX), 2) + Math.pow((point.y - centerY), 2));
283 | return distance < distanceTarget;
284 | }
285 |
286 | /**
287 | * Convert hour or minute to the angle base on the hourView
288 | * Return angle vales
289 | */
290 | private float getAngleOf(int number, boolean hourView) {
291 | float ag = 360 / (hourView ? 12f : 60f);
292 | return fixAngle(ag * number);
293 | }
294 |
295 | /**
296 | * Convert point location to angle
297 | * Return angle
298 | */
299 | private float getAngleOf(Point point) {
300 | Insets insets = getInsets();
301 | int width = getWidth() - (insets.left + insets.right);
302 | int height = getHeight() - (insets.top + insets.bottom);
303 | float centerX = insets.left + width / 2f;
304 | float centerY = insets.top + height / 2f;
305 | float x = point.x - centerX;
306 | float y = point.y - centerY;
307 | double angle = Math.toDegrees(Math.atan2(y, x)) + 90;
308 | if (angle < 0) {
309 | angle += 360;
310 | }
311 | return (float) angle;
312 | }
313 |
314 | /**
315 | * Make the angle is between 0 and 360-1
316 | */
317 | private float fixAngle(float angle) {
318 | if (angle > 360) {
319 | angle -= 360;
320 | }
321 | if (angle == 360) {
322 | return 0;
323 | }
324 | return angle;
325 | }
326 |
327 | /**
328 | * Fix hour or minute base on the hourView
329 | * If 24h ( return 0 to 23 )
330 | * If 12h ( return 1 to 12 )
331 | * If minute ( return 0 to 59 )
332 | */
333 | private int fixHour(int value, boolean hourView) {
334 | if (hourView) {
335 | if (use24hour) {
336 | if (value == 24) {
337 | return 0;
338 | }
339 | }
340 | } else {
341 | if (value == 60) {
342 | return 0;
343 | }
344 | }
345 | return value;
346 | }
347 |
348 | private String fixNumberAndToString(int num) {
349 | if (num == 0) {
350 | return "00";
351 | }
352 | return num + "";
353 | }
354 |
355 | private int getHourValue(int hour) {
356 | if (isUse24hour()) {
357 | return hour;
358 | } else {
359 | hour = (timePicker.getHeader().isAm() ? hour : hour - 12);
360 | return hour == 0 ? 12 : hour;
361 | }
362 | }
363 |
364 | private int valueToTime(int value, boolean hourView) {
365 | if (!hourView) {
366 | return value;
367 | }
368 | if (!isUse24hour()) {
369 | boolean isAm = timePicker.getHeader().isAm();
370 | value += isAm ? 0 : 12;
371 | if (isAm && value == 12) {
372 | return 0;
373 | }
374 | if (!isAm && value == 24) {
375 | return 12;
376 | }
377 | }
378 | return value;
379 | }
380 |
381 | private boolean is24hour() {
382 | int hour = timePicker.getTimeSelectionModel().getHour();
383 | return use24hour && (hour == 0 || hour > 12);
384 | }
385 |
386 | private int getTargetMargin() {
387 | return is24hour() && hourSelectionView ? margin24h : margin12h;
388 | }
389 |
390 | /**
391 | * Start animation selection change
392 | */
393 | private void runAnimation(boolean animate) {
394 | int value = hourSelectionView ? timePicker.getTimeSelectionModel().getHour() : timePicker.getTimeSelectionModel().getMinute();
395 | float angleTarget = getAngleOf(value, hourSelectionView);
396 | float marginTarget = getTargetMargin();
397 | animationChange.start(angleTarget, marginTarget, animate);
398 | }
399 |
400 | public void setColor(Color color) {
401 | this.color = color;
402 | }
403 | }
404 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/time/TimeSelectionModel.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.time;
2 |
3 | import raven.datetime.TimeSelectionAble;
4 | import raven.datetime.component.time.event.TimeSelectionModelEvent;
5 | import raven.datetime.component.time.event.TimeSelectionModelListener;
6 |
7 | import javax.swing.event.EventListenerList;
8 | import java.time.LocalTime;
9 |
10 | public class TimeSelectionModel {
11 |
12 | protected EventListenerList listenerList = new EventListenerList();
13 | private TimeSelectionAble timeSelectionAble;
14 | private int hour = -1;
15 | private int minute = -1;
16 |
17 | public TimeSelectionModel() {
18 | }
19 |
20 | public TimeSelectionAble getTimeSelectionAble() {
21 | return timeSelectionAble;
22 | }
23 |
24 | public void setTimeSelectionAble(TimeSelectionAble timeSelectionAble) {
25 | this.timeSelectionAble = timeSelectionAble;
26 | }
27 |
28 | public int getHour() {
29 | return hour;
30 | }
31 |
32 | public void setHour(int hour) {
33 | if (!checkSelection(hour, minute)) {
34 | return;
35 | }
36 | if (this.hour != hour) {
37 | this.hour = hour;
38 | fireTimePickerChanged(new TimeSelectionModelEvent(this, TimeSelectionModelEvent.HOUR));
39 | }
40 | }
41 |
42 | public int getMinute() {
43 | return minute;
44 | }
45 |
46 | public void setMinute(int minute) {
47 | if (hour == -1) {
48 | set(getDefaultSelectionHour(minute), minute);
49 | } else {
50 | if (this.minute != minute) {
51 | if (!checkSelection(hour, minute)) {
52 | return;
53 | }
54 | this.minute = minute;
55 | fireTimePickerChanged(new TimeSelectionModelEvent(this, TimeSelectionModelEvent.MINUTE));
56 | }
57 | }
58 | }
59 |
60 | public boolean isSelected() {
61 | return hour != -1 && minute != -1;
62 | }
63 |
64 | public LocalTime getTime() {
65 | if (isSelected()) {
66 | return LocalTime.of(hour, minute);
67 | }
68 | return null;
69 | }
70 |
71 | public void set(int hour, int minute) {
72 | int action = 0;
73 | if (this.hour != hour && this.minute != minute) {
74 | action = TimeSelectionModelEvent.HOUR_MINUTE;
75 | } else if (this.hour != hour) {
76 | action = TimeSelectionModelEvent.HOUR;
77 | } else if (this.minute != minute) {
78 | action = TimeSelectionModelEvent.MINUTE;
79 | }
80 | if (action != 0) {
81 | if (!checkSelection(hour, minute)) {
82 | return;
83 | }
84 | this.hour = hour;
85 | this.minute = minute;
86 | fireTimePickerChanged(new TimeSelectionModelEvent(this, action));
87 | }
88 | }
89 |
90 | /**
91 | * When hour unselected and user select the minute
92 | * So we need set the default hour
93 | */
94 | public int getDefaultSelectionHour(int minute) {
95 | int defaultHour = 0;
96 | if (timeSelectionAble != null) {
97 | defaultHour = getAvailableDefaultHour(defaultHour, minute);
98 | }
99 | return defaultHour;
100 | }
101 |
102 | /**
103 | * Default hour can be disabled by timeSelectionAble
104 | * Use this method to get the available hour
105 | */
106 | private int getAvailableDefaultHour(int startHour, int minute) {
107 | int hour = startHour;
108 | for (int i = 0; i < 23; i++) {
109 | if (checkSelection(hour, minute)) {
110 | return hour;
111 | }
112 | hour++;
113 | if (hour == 24) {
114 | hour = 0;
115 | }
116 | }
117 | return startHour;
118 | }
119 |
120 | /**
121 | * When check time selection able but minute not set
122 | * Use this for default minute to check the available hour
123 | */
124 | private int getDefaultMinuteCheck() {
125 | return 0;
126 | }
127 |
128 | public boolean checkSelection(int hour, int minute) {
129 | if (timeSelectionAble == null || hour == -1 || (minute == -1 && hour == -1)) {
130 | return true;
131 | }
132 | // hourView true if only hour are set. use to display the hour on the PanelClock
133 | boolean hourView = false;
134 | if (minute == -1) {
135 | minute = getDefaultMinuteCheck();
136 | hourView = true;
137 | }
138 | return timeSelectionAble.isTimeSelectedAble(LocalTime.of(hour, minute), hourView);
139 | }
140 |
141 | public void addTimePickerSelectionListener(TimeSelectionModelListener listener) {
142 | listenerList.add(TimeSelectionModelListener.class, listener);
143 | }
144 |
145 | public void removeTimePickerSelectionListener(TimeSelectionModelListener listener) {
146 | listenerList.remove(TimeSelectionModelListener.class, listener);
147 | }
148 |
149 | public void fireTimePickerChanged(TimeSelectionModelEvent event) {
150 | Object[] listeners = listenerList.getListenerList();
151 | for (int i = listeners.length - 2; i >= 0; i -= 2) {
152 | if (listeners[i] == TimeSelectionModelListener.class) {
153 | ((TimeSelectionModelListener) listeners[i + 1]).timeSelectionModelChanged(event);
154 | }
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/time/event/TimeActionListener.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.time.event;
2 |
3 | public interface TimeActionListener {
4 |
5 | void selectionViewChanged(boolean isHourSelectionView);
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/time/event/TimeSelectionModelEvent.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.time.event;
2 |
3 | import java.util.EventObject;
4 |
5 | public class TimeSelectionModelEvent extends EventObject {
6 |
7 | public static final int HOUR = 1;
8 | public static final int MINUTE = 2;
9 | public static final int HOUR_MINUTE = 3;
10 |
11 | protected int action;
12 |
13 | public TimeSelectionModelEvent(Object source, int action) {
14 | super(source);
15 | this.action = action;
16 | }
17 |
18 | public int getAction() {
19 | return action;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/component/time/event/TimeSelectionModelListener.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.component.time.event;
2 |
3 | import java.util.EventListener;
4 |
5 | public interface TimeSelectionModelListener extends EventListener {
6 |
7 | void timeSelectionModelChanged(TimeSelectionModelEvent e);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/event/DateSelectionEvent.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.event;
2 |
3 | import java.util.EventObject;
4 |
5 | public class DateSelectionEvent extends EventObject {
6 |
7 | public DateSelectionEvent(Object source) {
8 | super(source);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/event/DateSelectionListener.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.event;
2 |
3 | import java.util.EventListener;
4 |
5 | public interface DateSelectionListener extends EventListener {
6 |
7 | void dateSelected(DateSelectionEvent dateSelectionEvent);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/event/TimeSelectionEvent.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.event;
2 |
3 | import java.util.EventObject;
4 |
5 | public class TimeSelectionEvent extends EventObject {
6 |
7 | public TimeSelectionEvent(Object source) {
8 | super(source);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/event/TimeSelectionListener.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.event;
2 |
3 | import java.util.EventListener;
4 |
5 | public interface TimeSelectionListener extends EventListener {
6 |
7 | void timeSelected(TimeSelectionEvent timeSelectionEvent);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/swing/slider/PanelSlider.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.swing.slider;
2 |
3 | import com.formdev.flatlaf.util.Animator;
4 | import com.formdev.flatlaf.util.CubicBezierEasing;
5 |
6 | import javax.swing.*;
7 | import java.awt.*;
8 | import java.awt.image.VolatileImage;
9 |
10 | public class PanelSlider extends JLayeredPane {
11 |
12 | public Component getSlideComponent() {
13 | return slideComponent;
14 | }
15 |
16 | private PanelSnapshot panelSnapshot;
17 | private Component slideComponent;
18 |
19 | public PanelSlider() {
20 | init();
21 | }
22 |
23 | private void init() {
24 | panelSnapshot = new PanelSnapshot();
25 | setLayout(new CardLayout());
26 | setLayer(panelSnapshot, JLayeredPane.DRAG_LAYER);
27 | add(panelSnapshot);
28 | panelSnapshot.setVisible(false);
29 | }
30 |
31 | public void addSlide(Component component, SliderTransition transition) {
32 | this.slideComponent = component;
33 | if (getComponentCount() == 1) {
34 | add(component);
35 | repaint();
36 | revalidate();
37 | component.setVisible(true);
38 | } else {
39 | Component oldComponent = getComponent(1);
40 | add(component);
41 | if (transition != null) {
42 | doLayout();
43 | component.doLayout();
44 | Image oldImage = createImage(oldComponent);
45 | Image newImage = createImage(component);
46 | remove(oldComponent);
47 | panelSnapshot.animate(component, transition, oldImage, newImage);
48 | } else {
49 | component.setVisible(true);
50 | remove(oldComponent);
51 | revalidate();
52 | repaint();
53 | }
54 | }
55 | }
56 |
57 | private Image createImage(Component component) {
58 | VolatileImage snapshot = component.createVolatileImage(getWidth(), getHeight());
59 | if (snapshot != null) {
60 | component.paint(snapshot.getGraphics());
61 | }
62 | return snapshot;
63 | }
64 |
65 | private static class PanelSnapshot extends JComponent {
66 |
67 | private final Animator animator;
68 | private Component component;
69 | private float animate;
70 |
71 | private SliderTransition sliderTransition;
72 | private Image oldImage;
73 | private Image newImage;
74 |
75 | public PanelSnapshot() {
76 | animator = new Animator(400, new Animator.TimingTarget() {
77 | @Override
78 | public void timingEvent(float v) {
79 | animate = v;
80 | repaint();
81 | }
82 |
83 | @Override
84 | public void end() {
85 | setVisible(false);
86 | component.setVisible(true);
87 | if (newImage != null) {
88 | newImage.flush();
89 | }
90 | if (oldImage != null) {
91 | oldImage.flush();
92 | }
93 | }
94 | });
95 | animator.setInterpolator(CubicBezierEasing.EASE);
96 | }
97 |
98 | protected void animate(Component component, SliderTransition sliderTransition, Image oldImage, Image newImage) {
99 | if (animator.isRunning()) {
100 | animator.stop();
101 | }
102 | this.component = component;
103 | this.oldImage = oldImage;
104 | this.newImage = newImage;
105 | this.sliderTransition = sliderTransition;
106 | this.animate = 0f;
107 | repaint();
108 | setVisible(true);
109 | animator.start();
110 | }
111 |
112 | @Override
113 | public void paint(Graphics g) {
114 | if (sliderTransition != null) {
115 | int width = getWidth();
116 | int height = getHeight();
117 | sliderTransition.render(g, oldImage, newImage, width, height, animate);
118 | }
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/swing/slider/SimpleTransition.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.swing.slider;
2 |
3 | import java.awt.*;
4 |
5 | public class SimpleTransition {
6 |
7 | public static SliderTransition get(SliderType sliderType) {
8 | SliderTransition transition = null;
9 | if (sliderType == SliderType.BACK) {
10 | transition = new SliderTransition() {
11 |
12 | @Override
13 | public void renderImageNew(Graphics g, Image image, int width, int height, float animate) {
14 | int x = -(int) (width * (1f - animate));
15 | g.drawImage(image, x, 0, null);
16 | g.dispose();
17 | }
18 |
19 | @Override
20 | public void renderImageOld(Graphics g, Image image, int width, int height, float animate) {
21 | int x = (int) (width * animate);
22 | g.drawImage(image, x, 0, null);
23 | g.dispose();
24 | }
25 | };
26 | } else if (sliderType == SliderType.FORWARD) {
27 | transition = new SliderTransition() {
28 |
29 | @Override
30 | public void renderImageNew(Graphics g, Image image, int width, int height, float animate) {
31 | int x = (int) (width - width * animate);
32 | g.drawImage(image, x, 0, null);
33 | g.dispose();
34 | }
35 |
36 | @Override
37 | public void renderImageOld(Graphics g, Image image, int width, int height, float animate) {
38 | int x = -(int) (width * animate);
39 | g.drawImage(image, x, 0, null);
40 | g.dispose();
41 | }
42 | };
43 | } else if (sliderType == SliderType.ZOOM_OUT) {
44 | transition = new SliderTransition() {
45 |
46 | @Override
47 | public void renderImageNew(Graphics g, Image image, int width, int height, float animate) {
48 | Graphics2D g2 = (Graphics2D) g;
49 | g2.setComposite(AlphaComposite.SrcOver.derive(animate));
50 | g.drawImage(image, 0, 0, null);
51 | g2.dispose();
52 | }
53 |
54 | @Override
55 | public void renderImageOld(Graphics g, Image image, int width, int height, float animate) {
56 | Graphics2D g2 = (Graphics2D) g;
57 | float sx = 1 + animate;
58 | float sy = 1 + animate;
59 | int x = width / 2;
60 | int y = height / 2;
61 | g2.translate(x, y);
62 | g2.scale(sx, sy);
63 | g2.translate(-x, -y);
64 | g2.setComposite(AlphaComposite.SrcOver.derive(1f - animate));
65 | g.drawImage(image, 0, 0, null);
66 | g2.dispose();
67 | }
68 | };
69 | } else if (sliderType == SliderType.TOP_DOWN) {
70 | transition = new SliderTransition() {
71 |
72 | @Override
73 | public void renderImageNew(Graphics g, Image image, int width, int height, float animate) {
74 | Graphics2D g2 = (Graphics2D) g;
75 | int y = (int) (-height + height * animate);
76 | g.drawImage(image, 0, y, null);
77 | g2.dispose();
78 | }
79 |
80 | @Override
81 | public void renderImageOld(Graphics g, Image image, int width, int height, float animate) {
82 | Graphics2D g2 = (Graphics2D) g;
83 | g.drawImage(image, 0, 0, null);
84 | g2.dispose();
85 | }
86 | };
87 | } else if (sliderType == SliderType.DOWN_TOP) {
88 | transition = new SliderTransition() {
89 |
90 | @Override
91 | public void renderImageNew(Graphics g, Image image, int width, int height, float animate) {
92 | Graphics2D g2 = (Graphics2D) g;
93 | g.drawImage(image, 0, 0, null);
94 | g2.dispose();
95 | }
96 |
97 | @Override
98 | public void renderImageOld(Graphics g, Image image, int width, int height, float animate) {
99 | Graphics2D g2 = (Graphics2D) g;
100 | int y = (int) ( - height * animate);
101 | g.drawImage(image, 0, y, null);
102 | g2.dispose();
103 | }
104 |
105 | @Override
106 | public void render(Graphics g, Image imageOld, Image imageNew, int width, int height, float animate) {
107 | renderImageNew(g.create(), imageNew, width, height, animate);
108 | renderImageOld(g.create(), imageOld, width, height, animate);
109 | }
110 | };
111 | }
112 | return transition;
113 | }
114 |
115 | public enum SliderType {
116 | DEFAULT, BACK, FORWARD, ZOOM_IN, ZOOM_OUT, TOP_DOWN, DOWN_TOP
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/swing/slider/SliderTransition.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.swing.slider;
2 |
3 | import java.awt.*;
4 |
5 | public abstract class SliderTransition {
6 |
7 | public abstract void renderImageOld(Graphics g, Image image, int width, int height, float animate);
8 |
9 | public abstract void renderImageNew(Graphics g, Image image, int width, int height, float animate);
10 |
11 | public void render(Graphics g, Image imageOld, Image imageNew, int width, int height, float animate) {
12 | renderImageOld(g.create(), imageOld, width, height, animate);
13 | renderImageNew(g.create(), imageNew, width, height, animate);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/util/InputUtils.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.util;
2 |
3 | import com.formdev.flatlaf.FlatClientProperties;
4 |
5 | import javax.swing.*;
6 | import javax.swing.text.DefaultFormatterFactory;
7 | import javax.swing.text.MaskFormatter;
8 | import java.awt.*;
9 | import java.beans.PropertyChangeListener;
10 | import java.text.DateFormat;
11 | import java.text.ParseException;
12 | import java.text.SimpleDateFormat;
13 | import java.time.DateTimeException;
14 | import java.time.LocalDate;
15 | import java.time.LocalTime;
16 | import java.time.ZoneId;
17 | import java.time.format.DateTimeFormatter;
18 | import java.time.format.DateTimeFormatterBuilder;
19 | import java.util.Date;
20 | import java.util.HashMap;
21 | import java.util.Locale;
22 | import java.util.Map;
23 | import java.util.function.Consumer;
24 |
25 | public class InputUtils extends MaskFormatter {
26 |
27 | private static Map inputMap;
28 |
29 | public static LocalDate dateToLocalDate(Date date) {
30 | return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
31 | }
32 |
33 | public static LocalTime stringToTime(boolean use24h, String value) {
34 | try {
35 | if (use24h) {
36 | final DateTimeFormatter format24h = DateTimeFormatter.ofPattern("HH:mm", Locale.ENGLISH);
37 | return LocalTime.from(format24h.parse(value));
38 | } else {
39 | final DateTimeFormatter format12h = DateTimeFormatter.ofPattern("hh:mm a", Locale.ENGLISH);
40 | return LocalTime.from(format12h.parse(value.toUpperCase()));
41 | }
42 | } catch (Exception e) {
43 | System.err.println(e.getMessage());
44 | return null;
45 | }
46 | }
47 |
48 | public static LocalDate stringToDate(DateTimeFormatter format, String value) {
49 | try {
50 | return LocalDate.from(format.parse(value));
51 | } catch (Exception e) {
52 | return null;
53 | }
54 | }
55 |
56 | public static LocalDate[] stringToDate(DateTimeFormatter format, String separator, String value) {
57 | try {
58 | String[] dates = value.split(separator);
59 | LocalDate from = LocalDate.from(format.parse(dates[0]));
60 | LocalDate to = LocalDate.from(format.parse(dates[1]));
61 | return new LocalDate[]{from, to};
62 | } catch (Exception e) {
63 | return null;
64 | }
65 | }
66 |
67 | public static void useTimeInput(JFormattedTextField txt, boolean use24h, ValueCallback callback, InputValidationListener inputValidationListener) {
68 | try {
69 | TimeInputFormat mask = new TimeInputFormat(use24h ? "##:##" : "##:## ??", use24h, inputValidationListener);
70 | OldEditorProperty oldEditorProperty = initEditor(txt, mask, callback);
71 |
72 | PropertyChangeListener propertyChangeListener = evt -> callback.valueChanged(txt.getValue());
73 | txt.addPropertyChangeListener("value", propertyChangeListener);
74 | oldEditorProperty.propertyChangeListener = propertyChangeListener;
75 | putPropertyChange(txt, oldEditorProperty);
76 | } catch (ParseException e) {
77 | System.err.println(e.getMessage());
78 | }
79 | }
80 |
81 | public static void useDateInput(JFormattedTextField txt, String pattern, boolean between, String separator, ValueCallback callback, InputValidationListener inputValidationListener) {
82 | try {
83 | String format = datePatternToInputFormat(pattern, "#");
84 | DateInputFormat mask = new DateInputFormat(between ? format + separator + format : format, between, separator, pattern, inputValidationListener);
85 | OldEditorProperty oldEditorProperty = initEditor(txt, mask, callback);
86 |
87 | if (callback != null) {
88 | PropertyChangeListener propertyChangeListener = evt -> callback.valueChanged(txt.getValue());
89 | txt.addPropertyChangeListener("value", propertyChangeListener);
90 | oldEditorProperty.propertyChangeListener = propertyChangeListener;
91 | }
92 | putPropertyChange(txt, oldEditorProperty);
93 | } catch (ParseException e) {
94 | System.err.println(e.getMessage());
95 | }
96 | }
97 |
98 | public static void changeTimeFormatted(JFormattedTextField txt, boolean use24h, InputValidationListener inputValidationListener) {
99 | try {
100 | TimeInputFormat mask = new TimeInputFormat(use24h ? "##:##" : "##:## ??", use24h, inputValidationListener);
101 | mask.setCommitsOnValidEdit(true);
102 | mask.setPlaceholderCharacter('-');
103 | DefaultFormatterFactory df = new DefaultFormatterFactory(mask);
104 | txt.setFormatterFactory(df);
105 | } catch (ParseException e) {
106 | System.err.println(e.getMessage());
107 | }
108 | }
109 |
110 | public static void changeDateFormatted(JFormattedTextField txt, String pattern, boolean between, String separator, InputValidationListener inputValidationListener) {
111 | try {
112 | String format = datePatternToInputFormat(pattern, "#");
113 | DateInputFormat mask = new DateInputFormat(between ? format + separator + format : format, between, separator, pattern, inputValidationListener);
114 | mask.setCommitsOnValidEdit(true);
115 | mask.setPlaceholderCharacter('-');
116 | DefaultFormatterFactory df = new DefaultFormatterFactory(mask);
117 | txt.setFormatterFactory(df);
118 | } catch (ParseException e) {
119 | System.err.println(e.getMessage());
120 | }
121 | }
122 |
123 | public static String datePatternToInputFormat(String pattern, String rpm) {
124 | String regex = "[dmy]";
125 | return pattern.toLowerCase().replaceAll(regex, rpm);
126 | }
127 |
128 | private static OldEditorProperty initEditor(JFormattedTextField txt, MaskFormatter format, ValueCallback callback) {
129 | removePropertyChange(txt);
130 | OldEditorProperty oldEditorProperty = OldEditorProperty.getFromOldEditor(txt);
131 | format.setCommitsOnValidEdit(true);
132 | format.setPlaceholderCharacter('-');
133 | DefaultFormatterFactory df = new DefaultFormatterFactory(format);
134 | txt.setFormatterFactory(df);
135 |
136 | txt.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true);
137 | txt.putClientProperty(FlatClientProperties.TEXT_FIELD_CLEAR_CALLBACK, (Consumer>) o -> {
138 | txt.setValue(null);
139 | if (callback != null) {
140 | callback.valueChanged(null);
141 | }
142 | });
143 | return oldEditorProperty;
144 | }
145 |
146 | private static void putPropertyChange(JFormattedTextField txt, OldEditorProperty oldEditorProperty) {
147 | if (inputMap == null) {
148 | inputMap = new HashMap<>();
149 | }
150 | inputMap.put(txt, oldEditorProperty);
151 | }
152 |
153 | public static void removePropertyChange(JFormattedTextField txt) {
154 | if (inputMap == null) {
155 | return;
156 | }
157 | OldEditorProperty oldEditorProperty = inputMap.get(txt);
158 | if (oldEditorProperty != null) {
159 | oldEditorProperty.removeFromEditor(txt);
160 | inputMap.remove(txt);
161 | }
162 | }
163 |
164 | private static class TimeInputFormat extends MaskFormatter {
165 |
166 | private final InputValidationListener inputValidationListener;
167 | private final DateTimeFormatter timeFormat;
168 |
169 | public TimeInputFormat(String mark, boolean use24h, InputValidationListener inputValidationListener) throws ParseException {
170 | super(mark);
171 | this.inputValidationListener = inputValidationListener;
172 | this.timeFormat = new DateTimeFormatterBuilder()
173 | .parseCaseInsensitive()
174 | .appendPattern(use24h ? "HH:mm" : "hh:mm a")
175 | .toFormatter(Locale.ENGLISH);
176 | }
177 |
178 | @Override
179 | public Object stringToValue(String value) throws ParseException {
180 | checkTime(value);
181 | return super.stringToValue(value);
182 | }
183 |
184 | public void checkTime(String value) throws ParseException {
185 | try {
186 | LocalTime time = LocalTime.parse(value, timeFormat);
187 |
188 | if (inputValidationListener == null) return;
189 |
190 | // validate time selection able
191 | if (inputValidationListener.isValidation()) {
192 | if (!inputValidationListener.checkSelectionAble(time)) {
193 | throw new DateTimeException("error selection able");
194 | }
195 | }
196 | inputValidationListener.inputChanged(true);
197 | } catch (DateTimeException e) {
198 | if (inputValidationListener != null) {
199 | inputValidationListener.inputChanged(false);
200 | }
201 | throw new ParseException(e.getMessage(), 0);
202 | }
203 | }
204 | }
205 |
206 | private static class DateInputFormat extends MaskFormatter {
207 |
208 | private final boolean between;
209 | private final String separator;
210 | private final DateFormat dateFormat;
211 | private final InputValidationListener inputValidationListener;
212 |
213 | public DateInputFormat(String mark, boolean between, String separator, String pattern, InputValidationListener inputValidationListener) throws ParseException {
214 | super(mark);
215 | this.between = between;
216 | this.separator = separator;
217 | this.dateFormat = new SimpleDateFormat(pattern);
218 | this.inputValidationListener = inputValidationListener;
219 | this.dateFormat.setLenient(false);
220 | }
221 |
222 | @Override
223 | public Object stringToValue(String value) throws ParseException {
224 | checkTime(value);
225 | return super.stringToValue(value);
226 | }
227 |
228 | public void checkTime(String value) throws ParseException {
229 | try {
230 | if (between) {
231 | String[] values = value.split(separator);
232 | Date fromDate = dateFormat.parse(values[0]);
233 | Date toDate = dateFormat.parse(values[1]);
234 |
235 | if (inputValidationListener == null) return;
236 |
237 | // validate date selection able
238 | if (inputValidationListener.isValidation()) {
239 | LocalDate date1 = dateToLocalDate(fromDate);
240 | LocalDate date2 = dateToLocalDate(toDate);
241 | if (!inputValidationListener.checkSelectionAble(date1) ||
242 | !inputValidationListener.checkSelectionAble(date2)) {
243 | throw new ParseException("error selection able", 0);
244 | }
245 | }
246 | } else {
247 | Date date = dateFormat.parse(value);
248 |
249 | if (inputValidationListener == null) return;
250 |
251 | // validate date selection able
252 | if (inputValidationListener.isValidation()) {
253 | LocalDate d = dateToLocalDate(date);
254 | if (!inputValidationListener.checkSelectionAble(d)) {
255 | throw new ParseException("error selection able", 0);
256 | }
257 | }
258 | }
259 | inputValidationListener.inputChanged(true);
260 | } catch (ParseException e) {
261 | if (inputValidationListener != null) {
262 | inputValidationListener.inputChanged(false);
263 | }
264 | throw e;
265 | }
266 | }
267 | }
268 |
269 | public interface ValueCallback {
270 | void valueChanged(Object value);
271 | }
272 |
273 | private static class OldEditorProperty {
274 |
275 | protected PropertyChangeListener propertyChangeListener;
276 | protected JFormattedTextField.AbstractFormatterFactory formatter;
277 | protected Component oldTrailingComponent;
278 | private boolean isShowClearButton;
279 | private Consumer> clearButtonCallback;
280 | private String outline;
281 | protected Object value;
282 | protected String text;
283 |
284 | protected static OldEditorProperty getFromOldEditor(JFormattedTextField editor) {
285 | OldEditorProperty oldEditorProperty = new OldEditorProperty();
286 | oldEditorProperty.formatter = editor.getFormatterFactory();
287 | oldEditorProperty.oldTrailingComponent = FlatClientProperties.clientProperty(editor, FlatClientProperties.TEXT_FIELD_TRAILING_COMPONENT, null, Component.class);
288 | oldEditorProperty.isShowClearButton = FlatClientProperties.clientPropertyBoolean(editor, FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, false);
289 | oldEditorProperty.clearButtonCallback = FlatClientProperties.clientProperty(editor, FlatClientProperties.TEXT_FIELD_CLEAR_CALLBACK, null, Consumer.class);
290 | oldEditorProperty.outline = FlatClientProperties.clientProperty(editor, FlatClientProperties.OUTLINE, null, String.class);
291 | oldEditorProperty.value = editor.getValue();
292 | oldEditorProperty.text = editor.getText();
293 | return oldEditorProperty;
294 | }
295 |
296 | protected void removeFromEditor(JFormattedTextField editor) {
297 | editor.removePropertyChangeListener("value", propertyChangeListener);
298 | editor.setFormatterFactory(formatter);
299 | editor.putClientProperty(FlatClientProperties.TEXT_FIELD_TRAILING_COMPONENT, oldTrailingComponent);
300 | editor.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, isShowClearButton);
301 | editor.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, clearButtonCallback);
302 | editor.putClientProperty(FlatClientProperties.OUTLINE, outline);
303 | editor.setValue(value);
304 | editor.setText(text);
305 | }
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/util/InputValidationListener.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.util;
2 |
3 | public interface InputValidationListener {
4 |
5 | boolean isValidation();
6 |
7 | void inputChanged(boolean isValid);
8 |
9 | boolean checkSelectionAble(T data);
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/raven/datetime/util/Utils.java:
--------------------------------------------------------------------------------
1 | package raven.datetime.util;
2 |
3 | import com.formdev.flatlaf.FlatLaf;
4 | import com.formdev.flatlaf.ui.FlatUIUtils;
5 | import com.formdev.flatlaf.util.ColorFunctions;
6 | import com.formdev.flatlaf.util.UIScale;
7 |
8 | import javax.swing.*;
9 | import java.awt.*;
10 |
11 | public class Utils {
12 |
13 | public static Point adjustPopupLocation(JPopupMenu popupMenu, Component component, Point space) {
14 | Window window = SwingUtilities.getWindowAncestor(component);
15 | if (window == null) {
16 | return new Point(0, component.getHeight());
17 | }
18 | Insets frameInsets = window.getInsets();
19 | frameInsets = FlatUIUtils.addInsets(frameInsets, UIScale.scale(new Insets(5, 5, 5, 5)));
20 | Dimension popupSize = popupMenu.getComponent().getPreferredSize();
21 | int frameWidth = window.getWidth() - (frameInsets.left + frameInsets.right);
22 | int frameHeight = window.getHeight() - (frameInsets.top + frameInsets.bottom);
23 | Point locationOnFrame = SwingUtilities.convertPoint(component, new Point(0, component.getHeight()), window);
24 | int bottomSpace = frameHeight - locationOnFrame.y - popupSize.height - space.y;
25 | int rightSpace = frameWidth - locationOnFrame.x - popupSize.width - space.x;
26 | int x = frameInsets.left + rightSpace > 0 ? locationOnFrame.x + space.x : Math.max(locationOnFrame.x + component.getWidth() - popupSize.width - space.x, frameInsets.left);
27 | int y = frameInsets.top + bottomSpace > 0 ? locationOnFrame.y + space.y : Math.max(locationOnFrame.y - component.getHeight() - popupSize.height - space.y, frameInsets.top);
28 | return SwingUtilities.convertPoint(window, new Point(x, y), component);
29 | }
30 |
31 | public static Color getColor(Color color, boolean press, boolean hover) {
32 | if (press) {
33 | return FlatLaf.isLafDark() ? ColorFunctions.lighten(color, 0.1f) : ColorFunctions.darken(color, 0.1f);
34 | } else if (hover) {
35 | return FlatLaf.isLafDark() ? ColorFunctions.lighten(color, 0.03f) : ColorFunctions.darken(color, 0.03f);
36 | }
37 | return color;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/resources/raven/datetime/icon/back.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/raven/datetime/icon/calendar.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/main/resources/raven/datetime/icon/clock.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/main/resources/raven/datetime/icon/forward.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/test/java/test/TestDate.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import com.formdev.flatlaf.themes.FlatMacDarkLaf;
4 | import net.miginfocom.swing.MigLayout;
5 | import raven.datetime.DatePicker;
6 |
7 | import javax.swing.*;
8 | import javax.swing.border.TitledBorder;
9 | import java.awt.*;
10 | import java.time.LocalDate;
11 | import java.time.format.DateTimeFormatter;
12 |
13 | public class TestDate extends TestFrame {
14 |
15 | private DatePicker datePicker;
16 |
17 | public TestDate() {
18 | setLayout(new MigLayout("wrap"));
19 | datePicker = new DatePicker();
20 | datePicker.addDateSelectionListener(dateEvent -> {
21 | DateTimeFormatter df = DateTimeFormatter.ofPattern("dd-MM-yyyy");
22 | if (datePicker.getDateSelectionMode() == DatePicker.DateSelectionMode.SINGLE_DATE_SELECTED) {
23 | LocalDate date = datePicker.getSelectedDate();
24 | if (date != null) {
25 | System.out.println("date change " + df.format(datePicker.getSelectedDate()));
26 | } else {
27 | System.out.println("date change to null");
28 | }
29 | } else {
30 | LocalDate dates[] = datePicker.getSelectedDateRange();
31 | if (dates != null) {
32 | System.out.println("date change " + df.format(dates[0]) + " to " + df.format(dates[1]));
33 | } else {
34 | System.out.println("date change to null");
35 | }
36 | }
37 | });
38 |
39 | datePicker.setDateSelectionAble((date) -> !date.isAfter(LocalDate.now()));
40 |
41 | datePicker.now();
42 | JFormattedTextField editor = new JFormattedTextField();
43 | datePicker.setEditor(editor);
44 | add(editor, "width 250");
45 | createDateOption();
46 | }
47 |
48 | private void createDateOption() {
49 | JPanel panel = new JPanel(new MigLayout("wrap"));
50 | panel.setBorder(new TitledBorder("Option"));
51 | JCheckBox chBtw = new JCheckBox("Use date between");
52 | chBtw.addActionListener(e -> {
53 | if (chBtw.isSelected()) {
54 | datePicker.setDateSelectionMode(DatePicker.DateSelectionMode.BETWEEN_DATE_SELECTED);
55 | } else {
56 | datePicker.setDateSelectionMode(DatePicker.DateSelectionMode.SINGLE_DATE_SELECTED);
57 | }
58 | });
59 | JCheckBox chDateOpt = new JCheckBox("Use panel option");
60 | chDateOpt.addActionListener(e -> datePicker.setUsePanelOption(chDateOpt.isSelected()));
61 | JCheckBox chClose = new JCheckBox("Close after selected");
62 | chClose.addActionListener(e -> datePicker.setCloseAfterSelected(chClose.isSelected()));
63 |
64 | JCheckBox chEditorValidation = new JCheckBox("Editor validation", datePicker.isEditorValidation());
65 | JCheckBox chValidationOnNull = new JCheckBox("Validation on null");
66 | JCheckBox chAnimationEnabled = new JCheckBox("Animation enabled", datePicker.isAnimationEnabled());
67 | JCheckBox chStartWeekOnMonday = new JCheckBox("Start week on monday", datePicker.isStartWeekOnMonday());
68 |
69 | chEditorValidation.addActionListener(e -> {
70 | datePicker.setEditorValidation(chEditorValidation.isSelected());
71 | chValidationOnNull.setEnabled(chEditorValidation.isSelected());
72 | });
73 |
74 | chValidationOnNull.addActionListener(e -> datePicker.setValidationOnNull(chValidationOnNull.isSelected()));
75 | chAnimationEnabled.addActionListener(e -> datePicker.setAnimationEnabled(chAnimationEnabled.isSelected()));
76 | chStartWeekOnMonday.addActionListener(e -> datePicker.setStartWeekOnMonday(chStartWeekOnMonday.isSelected()));
77 |
78 | panel.add(chBtw);
79 | panel.add(chDateOpt);
80 | panel.add(chClose);
81 | panel.add(chEditorValidation);
82 | panel.add(chValidationOnNull);
83 | panel.add(chAnimationEnabled);
84 | panel.add(chStartWeekOnMonday);
85 | add(panel);
86 | }
87 |
88 | public static void main(String[] args) {
89 | FlatMacDarkLaf.setup();
90 | EventQueue.invokeLater(() -> new TestDate().setVisible(true));
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/test/java/test/TestFrame.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import com.formdev.flatlaf.FlatLaf;
4 | import com.formdev.flatlaf.extras.FlatAnimatedLafChange;
5 | import com.formdev.flatlaf.intellijthemes.FlatAllIJThemes;
6 |
7 | import javax.swing.*;
8 | import java.awt.*;
9 |
10 | public class TestFrame extends JFrame {
11 |
12 | public TestFrame() {
13 | setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
14 | setSize(new Dimension(800, 600));
15 | setLocationRelativeTo(null);
16 | createMenuBar();
17 | }
18 |
19 | private void createMenuBar() {
20 | JMenuBar menuBar = new JMenuBar();
21 | JMenu menuFile = new JMenu("File");
22 | JMenu menuThemes = new JMenu("Themes");
23 | JMenuItem menuExit = new JMenuItem("Exit");
24 | ButtonGroup group = new ButtonGroup();
25 | JCheckBoxMenuItem menuMacDark = new JCheckBoxMenuItem("Mac Dark");
26 | JCheckBoxMenuItem menuMacLight = new JCheckBoxMenuItem("Mac Light");
27 | group.add(menuMacDark);
28 | group.add(menuMacLight);
29 |
30 | menuMacDark.setSelected(true);
31 |
32 | menuExit.addActionListener(e -> System.exit(0));
33 |
34 | menuFile.add(menuExit);
35 | for (FlatAllIJThemes.FlatIJLookAndFeelInfo themeInfo : FlatAllIJThemes.INFOS) {
36 | menuThemes.add(createThemeButton(group, themeInfo));
37 | }
38 | menuBar.add(menuFile);
39 | menuBar.add(menuThemes);
40 | setJMenuBar(menuBar);
41 | }
42 |
43 | private JMenuItem createThemeButton(ButtonGroup group, FlatAllIJThemes.FlatIJLookAndFeelInfo themeInfo) {
44 | JCheckBoxMenuItem menu = new JCheckBoxMenuItem(themeInfo.getName());
45 | menu.addActionListener(e -> changeTheme(themeInfo));
46 | group.add(menu);
47 | return menu;
48 | }
49 |
50 | private void changeTheme(FlatAllIJThemes.FlatIJLookAndFeelInfo themeInfo) {
51 | EventQueue.invokeLater(() -> {
52 | FlatAnimatedLafChange.showSnapshot();
53 | try {
54 | UIManager.setLookAndFeel(themeInfo.getClassName());
55 | } catch (Exception e) {
56 | e.printStackTrace();
57 | }
58 | FlatLaf.updateUI();
59 | FlatAnimatedLafChange.hideSnapshotWithAnimation();
60 | });
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/test/java/test/TestTime.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import com.formdev.flatlaf.themes.FlatMacLightLaf;
4 | import net.miginfocom.swing.MigLayout;
5 | import raven.datetime.TimePicker;
6 |
7 | import javax.swing.*;
8 | import javax.swing.border.TitledBorder;
9 | import java.awt.*;
10 | import java.time.LocalTime;
11 | import java.time.format.DateTimeFormatter;
12 |
13 | public class TestTime extends TestFrame {
14 |
15 | private TimePicker timePicker;
16 |
17 | public TestTime() {
18 | setLayout(new MigLayout("wrap"));
19 | timePicker = new TimePicker();
20 | timePicker.addTimeSelectionListener(timeEvent -> {
21 | if (timePicker.isTimeSelected()) {
22 | DateTimeFormatter df = DateTimeFormatter.ofPattern("hh:mm a");
23 | System.out.println("event selected : " + timePicker.getSelectedTime().format(df));
24 | } else {
25 | System.out.println("event selected : null");
26 | }
27 | });
28 |
29 | // set enable selection time from
30 | // 0 to 19:30 or
31 | // 12:00 am to 07:30 pm
32 | // timePicker.setTimeSelectionAble((time, hourView) -> !time.isAfter(LocalTime.of(19, 30)));
33 |
34 | timePicker.now();
35 |
36 | JFormattedTextField editor = new JFormattedTextField();
37 | timePicker.setEditor(editor);
38 | add(editor, "width 200");
39 | createTimeOption();
40 | }
41 |
42 | private void createTimeOption() {
43 | JPanel panel = new JPanel(new MigLayout("wrap", "[150]"));
44 | panel.setBorder(new TitledBorder("Option"));
45 | JCheckBox ch24hourView = new JCheckBox("24 hour view");
46 | JCheckBox chHorizontal = new JCheckBox("Horizontal");
47 | JCheckBox chDisablePast = new JCheckBox("Disable past");
48 | JCheckBox chEditorValidation = new JCheckBox("Editor validation", timePicker.isEditorValidation());
49 | JCheckBox chValidationOnNull = new JCheckBox("Validation on null");
50 | ch24hourView.addActionListener(e -> {
51 | timePicker.set24HourView(ch24hourView.isSelected());
52 | });
53 | chHorizontal.addActionListener(e -> timePicker.setOrientation(chHorizontal.isSelected() ? SwingConstants.HORIZONTAL : SwingConstants.VERTICAL));
54 | chDisablePast.addActionListener(e -> {
55 | boolean disable = chDisablePast.isSelected();
56 | if (disable) {
57 | disablePast();
58 | } else {
59 | timePicker.setTimeSelectionAble(null);
60 | }
61 | });
62 | chEditorValidation.addActionListener(e -> {
63 | timePicker.setEditorValidation(chEditorValidation.isSelected());
64 | chValidationOnNull.setEnabled(chEditorValidation.isSelected());
65 | });
66 |
67 | chValidationOnNull.addActionListener(e -> timePicker.setValidationOnNull(chValidationOnNull.isSelected()));
68 |
69 | panel.add(ch24hourView);
70 | panel.add(chHorizontal);
71 | panel.add(chDisablePast);
72 | panel.add(chEditorValidation);
73 | panel.add(chValidationOnNull);
74 | add(panel);
75 | }
76 |
77 | private void disablePast() {
78 | timePicker.setTimeSelectionAble((time, hourView) -> {
79 | LocalTime now = LocalTime.now().withSecond(0).withNano(0);
80 | if (hourView) {
81 | // use this to enable the hour selection on the PanelClock
82 | return time.getHour() >= now.getHour();
83 | }
84 | return !time.isBefore(now);
85 | });
86 | }
87 |
88 | public static void main(String[] args) {
89 | FlatMacLightLaf.setup();
90 | EventQueue.invokeLater(() -> new TestTime().setVisible(true));
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/test/java/test/renderer/CustomDateCellRenderer.java:
--------------------------------------------------------------------------------
1 | package test.renderer;
2 |
3 | import com.formdev.flatlaf.util.UIScale;
4 | import raven.datetime.DatePicker;
5 | import raven.datetime.component.date.ButtonDate;
6 | import raven.datetime.component.date.DefaultDateCellRenderer;
7 | import raven.datetime.component.date.SingleDate;
8 |
9 | import java.awt.*;
10 | import java.awt.geom.Arc2D;
11 | import java.awt.geom.Rectangle2D;
12 | import java.time.LocalDate;
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | public class CustomDateCellRenderer extends DefaultDateCellRenderer {
17 |
18 | private List list;
19 |
20 | public CustomDateCellRenderer() {
21 | // init list
22 | list = new ArrayList<>();
23 | list.add(new DateStatus(LocalDate.of(2025, 5, 12), LabelDate.MOSTLY_BOOKED));
24 | list.add(new DateStatus(LocalDate.of(2025, 5, 13), LabelDate.FREE));
25 | list.add(new DateStatus(LocalDate.of(2025, 5, 16), LabelDate.FULLY_BOOKED));
26 | list.add(new DateStatus(LocalDate.of(2025, 5, 20), LabelDate.SOME_SLOTS));
27 | list.add(new DateStatus(LocalDate.of(2025, 5, 23), LabelDate.MOSTLY_BOOKED));
28 | list.add(new DateStatus(LocalDate.of(2025, 5, 28), LabelDate.MOSTLY_FREE));
29 |
30 | list.add(new DateStatus(LocalDate.of(2025, 6, 26), LabelDate.SOME_SLOTS));
31 | }
32 |
33 | @Override
34 | public void paint(Graphics2D g2, DatePicker datePicker, ButtonDate component, SingleDate date, float width, float height) {
35 | super.paint(g2, datePicker, component, date, width, height);
36 | LabelDate labelDate = getLabelDate(date.toLocalDate());
37 | if (labelDate != null) {
38 | g2.setColor(labelDate.getColor());
39 |
40 | // use rendering hint to control draw strokes line
41 | g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
42 | g2.setStroke(new BasicStroke(UIScale.scale(getSelectedFocusWidth()), BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
43 | Rectangle2D.Float rec = getRectangle(width, height);
44 | g2.draw(new Arc2D.Float(rec, 90, 360 * labelDate.getValue(), Arc2D.OPEN));
45 | }
46 | }
47 |
48 | private LabelDate getLabelDate(LocalDate date) {
49 | for (DateStatus d : list) {
50 | if (d.getDate().equals(date)) {
51 | return d.getLabel();
52 | }
53 | }
54 | return null;
55 | }
56 |
57 | private class DateStatus {
58 |
59 | public LocalDate getDate() {
60 | return date;
61 | }
62 |
63 | public void setDate(LocalDate date) {
64 | this.date = date;
65 | }
66 |
67 | public LabelDate getLabel() {
68 | return label;
69 | }
70 |
71 | public void setLabel(LabelDate label) {
72 | this.label = label;
73 | }
74 |
75 | public DateStatus(LocalDate date, LabelDate label) {
76 | this.date = date;
77 | this.label = label;
78 | }
79 |
80 | private LocalDate date;
81 | private LabelDate label;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/test/java/test/renderer/LabelDate.java:
--------------------------------------------------------------------------------
1 | package test.renderer;
2 |
3 | import java.awt.*;
4 |
5 | public enum LabelDate {
6 |
7 | FREE("Free", new Color(59, 155, 60), 1f),
8 | MOSTLY_BOOKED("Mostly Booked", new Color(239, 138, 138), 0.75f),
9 | SOME_SLOTS("Some Slots", new Color(213, 189, 44), 0.5f),
10 | FULLY_BOOKED("Fully Booked", new Color(231, 41, 41), 1f),
11 | MOSTLY_FREE("Mostly Free", new Color(140, 225, 141), 0.75f);
12 |
13 | private final String name;
14 | private final Color color;
15 | private final float value;
16 |
17 | LabelDate(String name, Color color, float value) {
18 | this.name = name;
19 | this.color = color;
20 | this.value = value;
21 | }
22 |
23 | public String getName() {
24 | return name;
25 | }
26 |
27 | public Color getColor() {
28 | return color;
29 | }
30 |
31 | public float getValue() {
32 | return value;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/test/java/test/renderer/TestDateCellRenderer.java:
--------------------------------------------------------------------------------
1 | package test.renderer;
2 |
3 | import com.formdev.flatlaf.themes.FlatMacDarkLaf;
4 | import net.miginfocom.swing.MigLayout;
5 | import raven.datetime.DatePicker;
6 | import test.TestFrame;
7 |
8 | import java.awt.*;
9 | import java.time.LocalDate;
10 | import java.time.format.DateTimeFormatter;
11 |
12 | public class TestDateCellRenderer extends TestFrame {
13 |
14 | public TestDateCellRenderer() {
15 | setLayout(new MigLayout(""));
16 | DatePicker datePicker = new DatePicker();
17 | datePicker.setDateSelectionMode(DatePicker.DateSelectionMode.BETWEEN_DATE_SELECTED);
18 | datePicker.addDateSelectionListener(dateEvent -> {
19 | DateTimeFormatter df = DateTimeFormatter.ofPattern("dd-MM-yyyy");
20 | if (datePicker.getDateSelectionMode() == DatePicker.DateSelectionMode.SINGLE_DATE_SELECTED) {
21 | LocalDate date = datePicker.getSelectedDate();
22 | if (date != null) {
23 | System.out.println("date change " + df.format(datePicker.getSelectedDate()));
24 | } else {
25 | System.out.println("date change to null");
26 | }
27 | } else {
28 | LocalDate dates[] = datePicker.getSelectedDateRange();
29 | if (dates != null) {
30 | System.out.println("date change " + df.format(dates[0]) + " to " + df.format(dates[1]));
31 | } else {
32 | System.out.println("date change to null");
33 | }
34 | }
35 | });
36 |
37 | datePicker.setDefaultDateCellRenderer(new CustomDateCellRenderer());
38 | datePicker.setSelectedDate(LocalDate.of(2025, 5, 10));
39 | // date picker minimum size 300px
40 | add(datePicker, "width 300::,height 330::");
41 | }
42 |
43 |
44 | public static void main(String[] args) {
45 | // System.setProperty("flatlaf.uiScale", "150%");
46 | FlatMacDarkLaf.setup();
47 | EventQueue.invokeLater(() -> new TestDateCellRenderer().setVisible(true));
48 | }
49 | }
50 |
--------------------------------------------------------------------------------