Commit | Line | Data |
---|---|---|
051e2913 KL |
1 | /* |
2 | * Jexer - Java Text User Interface | |
3 | * | |
4 | * The MIT License (MIT) | |
5 | * | |
a69ed767 | 6 | * Copyright (C) 2019 Kevin Lamonte |
051e2913 KL |
7 | * |
8 | * Permission is hereby granted, free of charge, to any person obtaining a | |
9 | * copy of this software and associated documentation files (the "Software"), | |
10 | * to deal in the Software without restriction, including without limitation | |
11 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
12 | * and/or sell copies of the Software, and to permit persons to whom the | |
13 | * Software is furnished to do so, subject to the following conditions: | |
14 | * | |
15 | * The above copyright notice and this permission notice shall be included in | |
16 | * all copies or substantial portions of the Software. | |
17 | * | |
18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
21 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
24 | * DEALINGS IN THE SOFTWARE. | |
25 | * | |
26 | * @author Kevin Lamonte [kevin.lamonte@gmail.com] | |
27 | * @version 1 | |
28 | */ | |
29 | package jexer; | |
30 | ||
31 | import java.util.Calendar; | |
32 | import java.util.GregorianCalendar; | |
33 | ||
34 | import jexer.bits.CellAttributes; | |
35 | import jexer.bits.GraphicsChars; | |
e820d5dd | 36 | import jexer.bits.StringUtils; |
051e2913 KL |
37 | import jexer.event.TKeypressEvent; |
38 | import jexer.event.TMouseEvent; | |
39 | import static jexer.TKeypress.*; | |
40 | ||
41 | /** | |
42 | * TCalendar is a date picker widget. | |
43 | */ | |
44 | public class TCalendar extends TWidget { | |
45 | ||
46 | // ------------------------------------------------------------------------ | |
47 | // Variables -------------------------------------------------------------- | |
48 | // ------------------------------------------------------------------------ | |
49 | ||
50 | /** | |
51 | * The calendar being displayed. | |
52 | */ | |
53 | private GregorianCalendar displayCalendar = new GregorianCalendar(); | |
54 | ||
55 | /** | |
56 | * The calendar with the selected day. | |
57 | */ | |
58 | private GregorianCalendar calendar = new GregorianCalendar(); | |
59 | ||
60 | /** | |
61 | * The action to perform when the user changes the value of the calendar. | |
62 | */ | |
63 | private TAction updateAction = null; | |
64 | ||
65 | // ------------------------------------------------------------------------ | |
66 | // Constructors ----------------------------------------------------------- | |
67 | // ------------------------------------------------------------------------ | |
68 | ||
69 | /** | |
70 | * Public constructor. | |
71 | * | |
72 | * @param parent parent widget | |
73 | * @param x column relative to parent | |
74 | * @param y row relative to parent | |
75 | * @param updateAction action to call when the user changes the value of | |
76 | * the calendar | |
77 | */ | |
78 | public TCalendar(final TWidget parent, final int x, final int y, | |
79 | final TAction updateAction) { | |
80 | ||
81 | // Set parent and window | |
82 | super(parent, x, y, 28, 8); | |
83 | ||
84 | this.updateAction = updateAction; | |
85 | } | |
86 | ||
87 | // ------------------------------------------------------------------------ | |
88 | // Event handlers --------------------------------------------------------- | |
89 | // ------------------------------------------------------------------------ | |
90 | ||
91 | /** | |
92 | * Returns true if the mouse is currently on the left arrow. | |
93 | * | |
94 | * @param mouse mouse event | |
95 | * @return true if the mouse is currently on the left arrow | |
96 | */ | |
97 | private boolean mouseOnLeftArrow(final TMouseEvent mouse) { | |
98 | if ((mouse.getY() == 0) | |
99 | && (mouse.getX() == 1) | |
100 | ) { | |
101 | return true; | |
102 | } | |
103 | return false; | |
104 | } | |
105 | ||
106 | /** | |
107 | * Returns true if the mouse is currently on the right arrow. | |
108 | * | |
109 | * @param mouse mouse event | |
110 | * @return true if the mouse is currently on the right arrow | |
111 | */ | |
112 | private boolean mouseOnRightArrow(final TMouseEvent mouse) { | |
113 | if ((mouse.getY() == 0) | |
114 | && (mouse.getX() == getWidth() - 2) | |
115 | ) { | |
116 | return true; | |
117 | } | |
118 | return false; | |
119 | } | |
120 | ||
121 | /** | |
122 | * Handle mouse down clicks. | |
123 | * | |
124 | * @param mouse mouse button down event | |
125 | */ | |
126 | @Override | |
127 | public void onMouseDown(final TMouseEvent mouse) { | |
128 | if ((mouseOnLeftArrow(mouse)) && (mouse.isMouse1())) { | |
129 | displayCalendar.add(Calendar.MONTH, -1); | |
130 | } else if ((mouseOnRightArrow(mouse)) && (mouse.isMouse1())) { | |
131 | displayCalendar.add(Calendar.MONTH, 1); | |
132 | } else if (mouse.isMouse1()) { | |
133 | // Find the day this might correspond to, and set it. | |
134 | int index = (mouse.getY() - 2) * 7 + (mouse.getX() / 4) + 1; | |
135 | // System.err.println("index: " + index); | |
136 | ||
137 | int lastDayNumber = displayCalendar.getActualMaximum( | |
138 | Calendar.DAY_OF_MONTH); | |
139 | GregorianCalendar firstOfMonth = new GregorianCalendar(); | |
140 | firstOfMonth.setTimeInMillis(displayCalendar.getTimeInMillis()); | |
141 | firstOfMonth.set(Calendar.DAY_OF_MONTH, 1); | |
142 | int dayOf1st = firstOfMonth.get(Calendar.DAY_OF_WEEK) - 1; | |
143 | // System.err.println("dayOf1st: " + dayOf1st); | |
144 | ||
145 | int day = index - dayOf1st; | |
146 | // System.err.println("day: " + day); | |
147 | ||
148 | if ((day < 1) || (day > lastDayNumber)) { | |
149 | return; | |
150 | } | |
151 | calendar.setTimeInMillis(displayCalendar.getTimeInMillis()); | |
152 | calendar.set(Calendar.DAY_OF_MONTH, day); | |
153 | } | |
154 | } | |
155 | ||
156 | /** | |
157 | * Handle mouse double click. | |
158 | * | |
159 | * @param mouse mouse double click event | |
160 | */ | |
161 | @Override | |
162 | public void onMouseDoubleClick(final TMouseEvent mouse) { | |
163 | if (updateAction != null) { | |
a524aa2e | 164 | updateAction.DO(this); |
051e2913 KL |
165 | } |
166 | } | |
167 | ||
168 | /** | |
169 | * Handle keystrokes. | |
170 | * | |
171 | * @param keypress keystroke event | |
172 | */ | |
173 | @Override | |
174 | public void onKeypress(final TKeypressEvent keypress) { | |
175 | int increment = 0; | |
176 | ||
177 | if (keypress.equals(kbUp)) { | |
178 | increment = -7; | |
179 | } else if (keypress.equals(kbDown)) { | |
180 | increment = 7; | |
181 | } else if (keypress.equals(kbLeft)) { | |
182 | increment = -1; | |
183 | } else if (keypress.equals(kbRight)) { | |
184 | increment = 1; | |
185 | } else if (keypress.equals(kbEnter)) { | |
186 | if (updateAction != null) { | |
a524aa2e | 187 | updateAction.DO(this); |
051e2913 KL |
188 | } |
189 | return; | |
190 | } else { | |
191 | // Pass to parent for the things we don't care about. | |
192 | super.onKeypress(keypress); | |
193 | return; | |
194 | } | |
195 | ||
196 | if (increment != 0) { | |
197 | calendar.add(Calendar.DAY_OF_YEAR, increment); | |
198 | ||
199 | if ((displayCalendar.get(Calendar.MONTH) != calendar.get( | |
200 | Calendar.MONTH)) | |
201 | || (displayCalendar.get(Calendar.YEAR) != calendar.get( | |
202 | Calendar.YEAR)) | |
203 | ) { | |
204 | if (increment < 0) { | |
205 | displayCalendar.add(Calendar.MONTH, -1); | |
206 | } else { | |
207 | displayCalendar.add(Calendar.MONTH, 1); | |
208 | } | |
209 | } | |
210 | } | |
211 | ||
212 | } | |
213 | ||
214 | // ------------------------------------------------------------------------ | |
215 | // TWidget ---------------------------------------------------------------- | |
216 | // ------------------------------------------------------------------------ | |
217 | ||
218 | /** | |
219 | * Draw the combobox down arrow. | |
220 | */ | |
221 | @Override | |
222 | public void draw() { | |
223 | CellAttributes backgroundColor = getTheme().getColor( | |
224 | "tcalendar.background"); | |
225 | CellAttributes dayColor = getTheme().getColor( | |
226 | "tcalendar.day"); | |
227 | CellAttributes selectedDayColor = getTheme().getColor( | |
228 | "tcalendar.day.selected"); | |
229 | CellAttributes arrowColor = getTheme().getColor( | |
230 | "tcalendar.arrow"); | |
231 | CellAttributes titleColor = getTheme().getColor( | |
232 | "tcalendar.title"); | |
233 | ||
234 | // Fill in the interior background | |
235 | for (int i = 0; i < getHeight(); i++) { | |
a69ed767 | 236 | hLineXY(0, i, getWidth(), ' ', backgroundColor); |
051e2913 KL |
237 | } |
238 | ||
239 | // Draw the title | |
240 | String title = String.format("%tB %tY", displayCalendar, | |
241 | displayCalendar); | |
e820d5dd KL |
242 | // This particular title is always single-width (see format string |
243 | // above), but for completeness let's treat it the same as every | |
244 | // other window title string. | |
245 | int titleLeft = (getWidth() - StringUtils.width(title) - 2) / 2; | |
a69ed767 KL |
246 | putCharXY(titleLeft, 0, ' ', titleColor); |
247 | putStringXY(titleLeft + 1, 0, title, titleColor); | |
e820d5dd | 248 | putCharXY(titleLeft + StringUtils.width(title) + 1, 0, ' ', |
051e2913 KL |
249 | titleColor); |
250 | ||
251 | // Arrows | |
a69ed767 KL |
252 | putCharXY(1, 0, GraphicsChars.LEFTARROW, arrowColor); |
253 | putCharXY(getWidth() - 2, 0, GraphicsChars.RIGHTARROW, | |
051e2913 KL |
254 | arrowColor); |
255 | ||
256 | /* | |
257 | * Now draw out the days. | |
258 | */ | |
a69ed767 | 259 | putStringXY(0, 1, " S M T W T F S ", dayColor); |
051e2913 KL |
260 | int lastDayNumber = displayCalendar.getActualMaximum( |
261 | Calendar.DAY_OF_MONTH); | |
262 | GregorianCalendar firstOfMonth = new GregorianCalendar(); | |
263 | firstOfMonth.setTimeInMillis(displayCalendar.getTimeInMillis()); | |
264 | firstOfMonth.set(Calendar.DAY_OF_MONTH, 1); | |
265 | int dayOf1st = firstOfMonth.get(Calendar.DAY_OF_WEEK) - 1; | |
266 | int dayColumn = dayOf1st * 4; | |
267 | int row = 2; | |
268 | ||
269 | int dayOfMonth = 1; | |
270 | while (dayOfMonth <= lastDayNumber) { | |
271 | if (dayColumn == 4 * 7) { | |
272 | dayColumn = 0; | |
273 | row++; | |
274 | } | |
275 | if ((dayOfMonth == calendar.get(Calendar.DAY_OF_MONTH)) | |
276 | && (displayCalendar.get(Calendar.MONTH) == calendar.get( | |
277 | Calendar.MONTH)) | |
278 | && (displayCalendar.get(Calendar.YEAR) == calendar.get( | |
279 | Calendar.YEAR)) | |
280 | ) { | |
a69ed767 | 281 | putStringXY(dayColumn, row, |
051e2913 KL |
282 | String.format(" %2d ", dayOfMonth), selectedDayColor); |
283 | } else { | |
a69ed767 | 284 | putStringXY(dayColumn, row, |
051e2913 KL |
285 | String.format(" %2d ", dayOfMonth), dayColor); |
286 | } | |
287 | dayColumn += 4; | |
288 | dayOfMonth++; | |
289 | } | |
290 | ||
291 | } | |
292 | ||
293 | // ------------------------------------------------------------------------ | |
294 | // TCalendar -------------------------------------------------------------- | |
295 | // ------------------------------------------------------------------------ | |
296 | ||
297 | /** | |
298 | * Get calendar value. | |
299 | * | |
300 | * @return the current calendar value (clone instance) | |
301 | */ | |
302 | public Calendar getValue() { | |
303 | return (Calendar) calendar.clone(); | |
304 | } | |
305 | ||
306 | /** | |
307 | * Set calendar value. | |
308 | * | |
309 | * @param calendar the new value to use | |
310 | */ | |
311 | public final void setValue(final Calendar calendar) { | |
312 | this.calendar.setTimeInMillis(calendar.getTimeInMillis()); | |
313 | } | |
314 | ||
315 | /** | |
316 | * Set calendar value. | |
317 | * | |
318 | * @param millis the millis to set to | |
319 | */ | |
320 | public final void setValue(final long millis) { | |
321 | this.calendar.setTimeInMillis(millis); | |
322 | } | |
323 | ||
324 | } |