Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

VAbstractCalendarPanel.java 72KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223
  1. /*
  2. * Copyright 2000-2021 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.ui;
  17. import static com.vaadin.client.DateTimeService.asTwoDigits;
  18. import java.util.Date;
  19. import java.util.HashMap;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.function.Predicate;
  23. import java.util.logging.Logger;
  24. import java.util.stream.Collectors;
  25. import java.util.stream.Stream;
  26. import com.google.gwt.aria.client.Id;
  27. import com.google.gwt.aria.client.Roles;
  28. import com.google.gwt.aria.client.SelectedValue;
  29. import com.google.gwt.dom.client.Element;
  30. import com.google.gwt.dom.client.NativeEvent;
  31. import com.google.gwt.event.dom.client.BlurEvent;
  32. import com.google.gwt.event.dom.client.BlurHandler;
  33. import com.google.gwt.event.dom.client.ClickHandler;
  34. import com.google.gwt.event.dom.client.DomEvent;
  35. import com.google.gwt.event.dom.client.FocusEvent;
  36. import com.google.gwt.event.dom.client.FocusHandler;
  37. import com.google.gwt.event.dom.client.KeyCodes;
  38. import com.google.gwt.event.dom.client.KeyDownEvent;
  39. import com.google.gwt.event.dom.client.KeyDownHandler;
  40. import com.google.gwt.event.dom.client.KeyPressEvent;
  41. import com.google.gwt.event.dom.client.KeyPressHandler;
  42. import com.google.gwt.event.dom.client.MouseDownEvent;
  43. import com.google.gwt.event.dom.client.MouseDownHandler;
  44. import com.google.gwt.event.dom.client.MouseOutEvent;
  45. import com.google.gwt.event.dom.client.MouseOutHandler;
  46. import com.google.gwt.event.dom.client.MouseUpEvent;
  47. import com.google.gwt.event.dom.client.MouseUpHandler;
  48. import com.google.gwt.i18n.client.DateTimeFormat;
  49. import com.google.gwt.user.client.DOM;
  50. import com.google.gwt.user.client.Event;
  51. import com.google.gwt.user.client.Timer;
  52. import com.google.gwt.user.client.ui.Button;
  53. import com.google.gwt.user.client.ui.FlexTable;
  54. import com.google.gwt.user.client.ui.InlineHTML;
  55. import com.google.gwt.user.client.ui.Widget;
  56. import com.vaadin.client.DateTimeService;
  57. import com.vaadin.client.WidgetUtil;
  58. import com.vaadin.client.ui.aria.AriaHelper;
  59. import com.vaadin.shared.util.SharedUtil;
  60. /**
  61. * Abstract calendar panel to show and select a date using a resolution. The
  62. * class is parameterized by the date resolution enumeration type.
  63. *
  64. * @author Vaadin Ltd
  65. *
  66. * @param <R>
  67. * the resolution type which this field is based on (day, month, ...)
  68. * @since 8.0
  69. */
  70. @SuppressWarnings("deprecation")
  71. public abstract class VAbstractCalendarPanel<R extends Enum<R>>
  72. extends FocusableFlexTable implements KeyDownHandler, KeyPressHandler,
  73. MouseOutHandler, MouseDownHandler, MouseUpHandler, BlurHandler,
  74. FocusHandler, SubPartAware {
  75. public interface SubmitListener {
  76. /**
  77. * Called when calendar user triggers a submitting operation in calendar
  78. * panel. E.g. clicking on day or hitting enter.
  79. */
  80. void onSubmit();
  81. /**
  82. * On e.g. ESC key.
  83. */
  84. void onCancel();
  85. }
  86. /**
  87. * Blur listener that listens to blur event from the panel.
  88. */
  89. public interface FocusOutListener {
  90. /**
  91. * @param event
  92. * dom event
  93. * @return true if the calendar panel is not used after focus moves out
  94. */
  95. boolean onFocusOut(DomEvent<?> event);
  96. }
  97. /**
  98. * FocusChangeListener is notified when the panel changes its
  99. * {@code focused} value.
  100. */
  101. public interface FocusChangeListener {
  102. void focusChanged(Date focusedDate);
  103. }
  104. /**
  105. * Represents a Date button in the calendar
  106. */
  107. private class VEventButton extends Button {
  108. public VEventButton() {
  109. addMouseDownHandler(VAbstractCalendarPanel.this);
  110. addMouseOutHandler(VAbstractCalendarPanel.this);
  111. addMouseUpHandler(VAbstractCalendarPanel.this);
  112. }
  113. }
  114. private static final String CN_FOCUSED = "focused";
  115. private static final String CN_TODAY = "today";
  116. private static final String CN_SELECTED = "selected";
  117. private static final String CN_OFFMONTH = "offmonth";
  118. private static final String CN_OUTSIDE_RANGE = "outside-range";
  119. private VEventButton prevYear;
  120. private VEventButton nextYear;
  121. private VEventButton prevMonth;
  122. private VEventButton nextMonth;
  123. private FlexTable days = new FlexTable();
  124. private R resolution;
  125. private Timer mouseTimer;
  126. private Date value;
  127. private DateTimeService dateTimeService;
  128. private boolean showISOWeekNumbers;
  129. private FocusedDate displayedMonth;
  130. private FocusedDate focusedDate;
  131. private Day selectedDay;
  132. private Day focusedDay;
  133. private FocusOutListener focusOutListener;
  134. private SubmitListener submitListener;
  135. private FocusChangeListener focusChangeListener;
  136. private boolean hasFocus = false;
  137. private VDateField<R> parent;
  138. private boolean initialRenderDone = false;
  139. private String prevMonthAssistiveLabel;
  140. private String nextMonthAssistiveLabel;
  141. private String prevYearAssistiveLabel;
  142. private String nextYearAssistiveLabel;
  143. /**
  144. * Represents a click handler for when a user selects a value by using the
  145. * mouse
  146. */
  147. @SuppressWarnings("unchecked")
  148. private ClickHandler dayClickHandler = event -> {
  149. if (!isEnabled() || isReadonly()) {
  150. return;
  151. }
  152. Date newDate = ((Day) event.getSource()).getDate();
  153. if (!isDateInsideRange(newDate,
  154. getResolution(VAbstractCalendarPanel.this::isDay))) {
  155. return;
  156. }
  157. if (newDate.getMonth() != displayedMonth.getMonth()
  158. || newDate.getYear() != displayedMonth.getYear()) {
  159. // If an off-month date was clicked, we must change the
  160. // displayed month and re-render the calendar (#8931)
  161. displayedMonth.setMonth(newDate.getMonth());
  162. displayedMonth.setYear(newDate.getYear());
  163. renderCalendar();
  164. }
  165. focusDay(newDate);
  166. selectFocused();
  167. onSubmit();
  168. };
  169. private Map<String, String> dateStyles = new HashMap<String, String>();
  170. private DateTimeFormat df = DateTimeFormat.getFormat("yyyy-MM-dd");
  171. public VAbstractCalendarPanel() {
  172. getElement().setId(DOM.createUniqueId());
  173. setStyleName(VDateField.CLASSNAME + "-calendarpanel");
  174. Roles.getGridRole().set(getElement());
  175. addKeyDownHandler(this);
  176. addFocusHandler(this);
  177. addBlurHandler(this);
  178. }
  179. public void setParentField(VDateField<R> parent) {
  180. this.parent = parent;
  181. }
  182. /**
  183. * Sets the focus to given date in the current view. Used when moving in the
  184. * calendar with the keyboard.
  185. *
  186. * @param date
  187. * A Date representing the day of month to be focused. Must be
  188. * one of the days currently visible.
  189. */
  190. @SuppressWarnings("unchecked")
  191. private void focusDay(Date date) {
  192. // Only used when calendar body is present
  193. if (acceptDayFocus()) {
  194. if (focusedDay != null) {
  195. focusedDay.removeStyleDependentName(CN_FOCUSED);
  196. }
  197. if (date != null && focusedDate != null) {
  198. focusedDate.setTime(date.getTime());
  199. int rowCount = days.getRowCount();
  200. for (int i = 0; i < rowCount; i++) {
  201. int cellCount = days.getCellCount(i);
  202. for (int j = 0; j < cellCount; j++) {
  203. Widget widget = days.getWidget(i, j);
  204. if (widget instanceof VAbstractCalendarPanel.Day) {
  205. Day curday = (Day) widget;
  206. if (curday.getDate().equals(date)) {
  207. curday.addStyleDependentName(CN_FOCUSED);
  208. focusedDay = curday;
  209. // Reference focused day from calendar panel
  210. Roles.getGridRole()
  211. .setAriaActivedescendantProperty(
  212. getElement(),
  213. Id.of(curday.getElement()));
  214. return;
  215. }
  216. }
  217. }
  218. }
  219. }
  220. }
  221. }
  222. /**
  223. * Returns {@code true} if current resolution assumes handling focus event
  224. * for day UI component.
  225. *
  226. * @return {@code true} if day focus events should be handled, {@code false}
  227. * otherwise
  228. */
  229. protected abstract boolean acceptDayFocus();
  230. /**
  231. * Returns {@code true} if the provided {@code resolution} represents a day.
  232. *
  233. * @param resolution
  234. * the given resolution
  235. * @return {@code true} if the {@code resolution} represents a day
  236. */
  237. protected abstract boolean isDay(R resolution);
  238. /**
  239. * Returns {@code true} if the provided {@code resolution} represents a
  240. * month.
  241. *
  242. * @param resolution
  243. * the given resolution
  244. * @return {@code true} if the {@code resolution} represents a month
  245. */
  246. protected abstract boolean isMonth(R resolution);
  247. /**
  248. * Returns {@code true} if the provided {@code resolution} represents an
  249. * year.
  250. *
  251. * @param resolution
  252. * the given resolution
  253. * @return {@code true} if the {@code resolution} represents a year
  254. */
  255. protected boolean isYear(R resolution) {
  256. return parent.isYear(resolution);
  257. }
  258. /**
  259. * Returns {@code true} if the {@code resolution} representation is strictly
  260. * below month (day, hour, etc..).
  261. *
  262. * @param resolution
  263. * the given resolution
  264. * @return whether the {@code resolution} is below the month resolution
  265. */
  266. protected abstract boolean isBelowMonth(R resolution);
  267. /**
  268. * Returns all available resolutions for the widget.
  269. *
  270. * @return all available resolutions
  271. */
  272. protected Stream<R> getResolutions() {
  273. return parent.getResolutions();
  274. }
  275. /**
  276. * Finds the resolution by the {@code filter}.
  277. *
  278. * @param filter
  279. * predicate to filter resolutions
  280. * @return the resolution accepted by the {@code filter}
  281. */
  282. protected R getResolution(Predicate<R> filter) {
  283. List<R> resolutions = getResolutions().filter(filter)
  284. .collect(Collectors.toList());
  285. assert resolutions
  286. .size() == 1 : "The result of filtering by the predicate "
  287. + "contains unexpected number of resolution items :"
  288. + resolutions.size();
  289. return resolutions.get(0);
  290. }
  291. /**
  292. * Sets the selection highlight to a given day in the current view
  293. *
  294. * @param date
  295. * A Date representing the day of month to be selected. Must be
  296. * one of the days currently visible.
  297. *
  298. */
  299. @SuppressWarnings("unchecked")
  300. private void selectDate(Date date) {
  301. if (selectedDay != null) {
  302. selectedDay.removeStyleDependentName(CN_SELECTED);
  303. Roles.getGridcellRole()
  304. .removeAriaSelectedState(selectedDay.getElement());
  305. }
  306. int rowCount = days.getRowCount();
  307. for (int i = 0; i < rowCount; i++) {
  308. int cellCount = days.getCellCount(i);
  309. for (int j = 0; j < cellCount; j++) {
  310. Widget widget = days.getWidget(i, j);
  311. if (widget instanceof VAbstractCalendarPanel.Day) {
  312. Day curday = (Day) widget;
  313. if (curday.getDate().equals(date)) {
  314. curday.addStyleDependentName(CN_SELECTED);
  315. selectedDay = curday;
  316. Roles.getGridcellRole().setAriaSelectedState(
  317. selectedDay.getElement(), SelectedValue.TRUE);
  318. return;
  319. }
  320. }
  321. }
  322. }
  323. }
  324. /**
  325. * Updates year, month, day from focusedDate to value
  326. */
  327. private void selectFocused() {
  328. if (focusedDate != null
  329. && isDateInsideRange(focusedDate, getResolution())) {
  330. if (value == null) {
  331. // No previously selected value (set to null on server side).
  332. // Create a new date using current date and time
  333. value = new Date();
  334. }
  335. /*
  336. * #5594 set Date (day) to 1 in order to prevent any kind of
  337. * wrapping of months when later setting the month. (e.g. 31 ->
  338. * month with 30 days -> wraps to the 1st of the following month,
  339. * e.g. 31st of May -> 31st of April = 1st of May)
  340. */
  341. value.setDate(1);
  342. if (value.getYear() != focusedDate.getYear()) {
  343. value.setYear(focusedDate.getYear());
  344. }
  345. if (value.getMonth() != focusedDate.getMonth()) {
  346. value.setMonth(focusedDate.getMonth());
  347. }
  348. if (value.getDate() != focusedDate.getDate()) {
  349. }
  350. // We always need to set the date, even if it hasn't changed, since
  351. // it was forced to 1 above.
  352. value.setDate(focusedDate.getDate());
  353. selectDate(focusedDate);
  354. } else {
  355. getLogger()
  356. .info("Trying to select the focused date which is NULL!");
  357. }
  358. }
  359. protected boolean onValueChange() {
  360. return false;
  361. }
  362. public R getResolution() {
  363. return resolution;
  364. }
  365. public void setResolution(R resolution) {
  366. this.resolution = resolution;
  367. }
  368. /**
  369. * Checks whether the widget is not editable (read-only).
  370. *
  371. * @return {@code true} if the widget is read-only
  372. */
  373. protected boolean isReadonly() {
  374. return parent.isReadonly();
  375. }
  376. /**
  377. * Checks whether the widget is enabled.
  378. *
  379. * @return {@code true} is the widget is enabled
  380. */
  381. protected boolean isEnabled() {
  382. return parent.isEnabled();
  383. }
  384. @Override
  385. public void setStyleName(String style) {
  386. super.setStyleName(style);
  387. if (initialRenderDone) {
  388. // Dynamic updates to the stylename needs to render the calendar to
  389. // update the inner element stylenames
  390. renderCalendar();
  391. }
  392. }
  393. @Override
  394. public void setStylePrimaryName(String style) {
  395. super.setStylePrimaryName(style);
  396. if (initialRenderDone) {
  397. // Dynamic updates to the stylename needs to render the calendar to
  398. // update the inner element stylenames
  399. renderCalendar();
  400. }
  401. }
  402. /**
  403. * Sets the style names for dates.
  404. *
  405. * @param dateStyles
  406. * the map of date string to style name
  407. *
  408. * @since 8.3
  409. */
  410. public void setDateStyles(Map<String, String> dateStyles) {
  411. this.dateStyles.clear();
  412. if (dateStyles != null) {
  413. this.dateStyles.putAll(dateStyles);
  414. }
  415. }
  416. private void clearCalendarBody(boolean remove) {
  417. if (!remove) {
  418. // Leave the cells in place but clear their contents
  419. // This has the side effect of ensuring that the calendar always
  420. // contain 7 rows.
  421. for (int row = 1; row < 7; row++) {
  422. for (int col = 0; col < 8; col++) {
  423. days.setHTML(row, col, "&nbsp;");
  424. }
  425. }
  426. } else if (getRowCount() > 1) {
  427. removeRow(1);
  428. days.clear();
  429. }
  430. }
  431. /**
  432. * Builds the top buttons and current month and year header.
  433. *
  434. * @param needsMonth
  435. * Should the month buttons be visible?
  436. * @param needsBody
  437. * indicates whether the calendar body is drawn
  438. */
  439. private void buildCalendarHeader(boolean needsMonth, boolean needsBody) {
  440. getRowFormatter().addStyleName(0,
  441. parent.getStylePrimaryName() + "-calendarpanel-header");
  442. if (prevMonth == null && needsMonth) {
  443. prevMonth = new VEventButton();
  444. prevMonth.setHTML("&lsaquo;");
  445. prevMonth.setStyleName("v-button-prevmonth");
  446. nextMonth = new VEventButton();
  447. nextMonth.setHTML("&rsaquo;");
  448. nextMonth.setStyleName("v-button-nextmonth");
  449. setWidget(0, 3, nextMonth);
  450. setWidget(0, 1, prevMonth);
  451. Roles.getButtonRole().set(prevMonth.getElement());
  452. Roles.getButtonRole()
  453. .setTabindexExtraAttribute(prevMonth.getElement(), -1);
  454. Roles.getButtonRole().set(nextMonth.getElement());
  455. Roles.getButtonRole()
  456. .setTabindexExtraAttribute(nextMonth.getElement(), -1);
  457. } else if (prevMonth != null && !needsMonth) {
  458. // Remove month traverse buttons
  459. remove(prevMonth);
  460. remove(nextMonth);
  461. prevMonth = null;
  462. nextMonth = null;
  463. }
  464. if (prevYear == null) {
  465. prevYear = new VEventButton();
  466. prevYear.setHTML("&laquo;");
  467. prevYear.setStyleName("v-button-prevyear");
  468. nextYear = new VEventButton();
  469. nextYear.setHTML("&raquo;");
  470. nextYear.setStyleName("v-button-nextyear");
  471. setWidget(0, 0, prevYear);
  472. setWidget(0, 4, nextYear);
  473. Roles.getButtonRole().set(prevYear.getElement());
  474. Roles.getButtonRole()
  475. .setTabindexExtraAttribute(prevYear.getElement(), -1);
  476. Roles.getButtonRole().set(nextYear.getElement());
  477. Roles.getButtonRole()
  478. .setTabindexExtraAttribute(nextYear.getElement(), -1);
  479. }
  480. updateControlButtonRangeStyles(needsMonth);
  481. updateAssistiveLabels();
  482. final String monthName = needsMonth
  483. ? getDateTimeService().getMonth(displayedMonth.getMonth())
  484. : "";
  485. final int year = displayedMonth.getYear() + 1900;
  486. getFlexCellFormatter().setStyleName(0, 2,
  487. parent.getStylePrimaryName() + "-calendarpanel-month");
  488. getFlexCellFormatter().setStyleName(0, 0,
  489. parent.getStylePrimaryName() + "-calendarpanel-prevyear");
  490. getFlexCellFormatter().setStyleName(0, 4,
  491. parent.getStylePrimaryName() + "-calendarpanel-nextyear");
  492. getFlexCellFormatter().setStyleName(0, 3,
  493. parent.getStylePrimaryName() + "-calendarpanel-nextmonth");
  494. getFlexCellFormatter().setStyleName(0, 1,
  495. parent.getStylePrimaryName() + "-calendarpanel-prevmonth");
  496. // Set ID to be referenced from focused date or calendar panel
  497. Element monthYearElement = getFlexCellFormatter().getElement(0, 2);
  498. AriaHelper.ensureHasId(monthYearElement);
  499. Event.sinkEvents(monthYearElement, Event.ONCLICK);
  500. Event.setEventListener(monthYearElement, event -> {
  501. // Don't handle header clicks if resolution in below month
  502. if (!isEnabled() || isReadonly() || isBelowMonth(getResolution())) {
  503. return;
  504. }
  505. selectFocused();
  506. onSubmit();
  507. });
  508. if (!needsBody) {
  509. Roles.getGridRole().setAriaLabelledbyProperty(getElement(),
  510. Id.of(monthYearElement));
  511. } else {
  512. Roles.getGridRole().removeAriaLabelledbyProperty(getElement());
  513. }
  514. setHTML(0, 2,
  515. "<span class=\"" + parent.getStylePrimaryName()
  516. + "-calendarpanel-month\">" + monthName + " " + year
  517. + "</span>");
  518. if (!isBelowMonth(getResolution())) {
  519. monthYearElement.addClassName("header-month-year");
  520. }
  521. }
  522. private void updateControlButtonRangeStyles(boolean needsMonth) {
  523. if (focusedDate == null) {
  524. return;
  525. }
  526. if (needsMonth) {
  527. Date prevMonthDate = (Date) focusedDate.clone();
  528. removeOneMonth(prevMonthDate);
  529. R month = getResolution(VAbstractCalendarPanel.this::isMonth);
  530. if (!isDateInsideRange(prevMonthDate, month)) {
  531. prevMonth.addStyleName(CN_OUTSIDE_RANGE);
  532. } else {
  533. prevMonth.removeStyleName(CN_OUTSIDE_RANGE);
  534. }
  535. Date nextMonthDate = (Date) focusedDate.clone();
  536. addOneMonth(nextMonthDate);
  537. if (!isDateInsideRange(nextMonthDate, month)) {
  538. nextMonth.addStyleName(CN_OUTSIDE_RANGE);
  539. } else {
  540. nextMonth.removeStyleName(CN_OUTSIDE_RANGE);
  541. }
  542. }
  543. Date prevYearDate = (Date) focusedDate.clone();
  544. prevYearDate.setYear(prevYearDate.getYear() - 1);
  545. R year = getResolution(VAbstractCalendarPanel.this::isYear);
  546. if (!isDateInsideRange(prevYearDate, year)) {
  547. prevYear.addStyleName(CN_OUTSIDE_RANGE);
  548. } else {
  549. prevYear.removeStyleName(CN_OUTSIDE_RANGE);
  550. }
  551. Date nextYearDate = (Date) focusedDate.clone();
  552. nextYearDate.setYear(nextYearDate.getYear() + 1);
  553. if (!isDateInsideRange(nextYearDate, year)) {
  554. nextYear.addStyleName(CN_OUTSIDE_RANGE);
  555. } else {
  556. nextYear.removeStyleName(CN_OUTSIDE_RANGE);
  557. }
  558. }
  559. /**
  560. * Returns date time service for the widget.
  561. *
  562. * @see #setDateTimeService(DateTimeService)
  563. *
  564. * @return date time service
  565. */
  566. protected DateTimeService getDateTimeService() {
  567. return dateTimeService;
  568. }
  569. /**
  570. * Returns the date field which this panel is attached to.
  571. *
  572. * @return the "parent" date field
  573. */
  574. protected VDateField<R> getDateField() {
  575. return parent;
  576. }
  577. public void setDateTimeService(DateTimeService dateTimeService) {
  578. this.dateTimeService = dateTimeService;
  579. }
  580. /**
  581. * Returns whether ISO 8601 week numbers should be shown in the value
  582. * selector or not. ISO 8601 defines that a week always starts with a Monday
  583. * so the week numbers are only shown if this is the case.
  584. *
  585. * @return true if week number should be shown, false otherwise
  586. */
  587. public boolean isShowISOWeekNumbers() {
  588. return showISOWeekNumbers;
  589. }
  590. public void setShowISOWeekNumbers(boolean showISOWeekNumbers) {
  591. this.showISOWeekNumbers = showISOWeekNumbers;
  592. if (initialRenderDone && isBelowMonth(resolution)) {
  593. clearCalendarBody(false);
  594. buildCalendarBody();
  595. }
  596. }
  597. /**
  598. * Checks inclusively whether a date is inside a range of dates or not.
  599. *
  600. * @param date
  601. * @return
  602. */
  603. private boolean isDateInsideRange(Date date, R minResolution) {
  604. assert (date != null);
  605. return isAcceptedByRangeEnd(date, minResolution)
  606. && isAcceptedByRangeStart(date, minResolution);
  607. }
  608. /**
  609. * Accepts dates greater than or equal to rangeStart, depending on the
  610. * resolution. If the resolution is set to DAY, the range will compare on a
  611. * day-basis. If the resolution is set to YEAR, only years are compared. So
  612. * even if the range is set to one millisecond in next year, also next year
  613. * will be included.
  614. *
  615. * @param date
  616. * @param minResolution
  617. * @return
  618. */
  619. private boolean isAcceptedByRangeStart(Date date, R minResolution) {
  620. assert (date != null);
  621. // rangeStart == null means that we accept all values below rangeEnd
  622. if (rangeStart == null) {
  623. return true;
  624. }
  625. String dateStrResolution = dateStrResolution(date, minResolution);
  626. return rangeStart.substring(0, dateStrResolution.length())
  627. .compareTo(dateStrResolution) <= 0;
  628. }
  629. private String dateStrResolution(Date date, R minResolution) {
  630. String dateStrResolution = (1900 + date.getYear()) + "";
  631. while (dateStrResolution.length() < 4) {
  632. dateStrResolution = "0" + dateStrResolution;
  633. }
  634. if (!isYear(minResolution)) {
  635. dateStrResolution += "-" + asTwoDigits(1 + date.getMonth());
  636. if (!isMonth(minResolution)) {
  637. dateStrResolution += "-" + asTwoDigits(date.getDate());
  638. }
  639. }
  640. return dateStrResolution;
  641. }
  642. /**
  643. * Accepts dates earlier than or equal to rangeStart, depending on the
  644. * resolution. If the resolution is set to DAY, the range will compare on a
  645. * day-basis. If the resolution is set to YEAR, only years are compared. So
  646. * even if the range is set to one millisecond in next year, also next year
  647. * will be included.
  648. *
  649. * @param date
  650. * @param minResolution
  651. * @return
  652. */
  653. private boolean isAcceptedByRangeEnd(Date date, R minResolution) {
  654. assert (date != null);
  655. // rangeEnd == null means that we accept all values above rangeStart
  656. if (rangeEnd == null) {
  657. return true;
  658. }
  659. // If dateStrResolution has more year digits than rangeEnd, we need
  660. // to pad it in order to be lexicographically compatible
  661. String dateStrResolution = dateStrResolution(date, minResolution);
  662. String paddedEnd = rangeEnd;
  663. int yearDigits = dateStrResolution.indexOf("-");
  664. if (yearDigits == -1) {
  665. yearDigits = dateStrResolution.length();
  666. }
  667. while (paddedEnd.indexOf("-") < yearDigits) {
  668. paddedEnd = "0" + paddedEnd;
  669. }
  670. return paddedEnd.substring(0, dateStrResolution.length())
  671. .compareTo(dateStrResolution) >= 0;
  672. }
  673. /**
  674. * Builds the day and time selectors of the calendar.
  675. */
  676. private void buildCalendarBody() {
  677. final int weekColumn = 0;
  678. final int firstWeekdayColumn = 1;
  679. final int headerRow = 0;
  680. setWidget(1, 0, days);
  681. setCellPadding(0);
  682. setCellSpacing(0);
  683. getFlexCellFormatter().setColSpan(1, 0, 5);
  684. getFlexCellFormatter().setStyleName(1, 0,
  685. getDateField().getStylePrimaryName() + "-calendarpanel-body");
  686. days.getFlexCellFormatter().setStyleName(headerRow, weekColumn,
  687. "v-week");
  688. days.setHTML(headerRow, weekColumn, "<strong></strong>");
  689. // Hide the week column if week numbers are not to be displayed.
  690. days.getFlexCellFormatter().setVisible(headerRow, weekColumn,
  691. isShowISOWeekNumbers());
  692. days.getRowFormatter().setStyleName(headerRow,
  693. getDateField().getStylePrimaryName()
  694. + "-calendarpanel-weekdays");
  695. if (isShowISOWeekNumbers()) {
  696. days.getFlexCellFormatter().setStyleName(headerRow, weekColumn,
  697. "v-first");
  698. days.getFlexCellFormatter().setStyleName(headerRow,
  699. firstWeekdayColumn, "");
  700. days.getRowFormatter().addStyleName(headerRow,
  701. getDateField().getStylePrimaryName()
  702. + "-calendarpanel-weeknumbers");
  703. } else {
  704. days.getFlexCellFormatter().setStyleName(headerRow, weekColumn, "");
  705. days.getFlexCellFormatter().setStyleName(headerRow,
  706. firstWeekdayColumn, "v-first");
  707. }
  708. days.getFlexCellFormatter().setStyleName(headerRow,
  709. firstWeekdayColumn + 6, "v-last");
  710. // Print weekday names
  711. final int firstDay = getDateTimeService().getFirstDayOfWeek();
  712. for (int i = 0; i < 7; i++) {
  713. int day = (i + firstDay) % 7;
  714. if (isBelowMonth(getResolution())) {
  715. days.setHTML(headerRow, firstWeekdayColumn + i, "<strong>"
  716. + getDateTimeService().getShortDay(day) + "</strong>");
  717. } else {
  718. days.setHTML(headerRow, firstWeekdayColumn + i, "");
  719. }
  720. Roles.getColumnheaderRole().set(days.getCellFormatter()
  721. .getElement(headerRow, firstWeekdayColumn + i));
  722. }
  723. // Zero out hours, minutes, seconds, and milliseconds to compare dates
  724. // without time part
  725. final Date tmp = new Date();
  726. final Date today = new Date(tmp.getYear(), tmp.getMonth(),
  727. tmp.getDate());
  728. final Date selectedDate = value == null ? null
  729. : new Date(value.getYear(), value.getMonth(), value.getDate());
  730. final int startWeekDay = getDateTimeService()
  731. .getStartWeekDay(displayedMonth);
  732. final Date curr = (Date) displayedMonth.clone();
  733. // Start from the first day of the week that at least partially belongs
  734. // to the current month
  735. curr.setDate(1 - startWeekDay);
  736. // No month has more than 6 weeks so 6 is a safe maximum for rows.
  737. for (int weekOfMonth = 1; weekOfMonth < 7; weekOfMonth++) {
  738. for (int dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
  739. // Actually write the day of month
  740. Date dayDate = (Date) curr.clone();
  741. Day day = new Day(dayDate);
  742. // Set ID with prefix of the calendar panel's ID
  743. day.getElement().setId(getElement().getId() + "-" + weekOfMonth
  744. + "-" + dayOfWeek);
  745. // Set assistive label to read focused date and month/year
  746. Roles.getButtonRole().set(day.getElement());
  747. Roles.getButtonRole().setAriaLabelledbyProperty(
  748. day.getElement(), Id.of(day.getElement()),
  749. Id.of(getFlexCellFormatter().getElement(0, 2)));
  750. day.setStyleName(getDateField().getStylePrimaryName()
  751. + "-calendarpanel-day");
  752. if (!isDateInsideRange(dayDate, getResolution(this::isDay))) {
  753. day.addStyleDependentName(CN_OUTSIDE_RANGE);
  754. }
  755. if (curr.equals(selectedDate)) {
  756. day.addStyleDependentName(CN_SELECTED);
  757. Roles.getGridcellRole().setAriaSelectedState(
  758. day.getElement(), SelectedValue.TRUE);
  759. selectedDay = day;
  760. }
  761. if (curr.equals(today)) {
  762. day.addStyleDependentName(CN_TODAY);
  763. }
  764. if (curr.equals(focusedDate)) {
  765. focusedDay = day;
  766. if (hasFocus) {
  767. day.addStyleDependentName(CN_FOCUSED);
  768. // Reference focused day from calendar panel
  769. Roles.getGridRole().setAriaActivedescendantProperty(
  770. getElement(), Id.of(day.getElement()));
  771. }
  772. }
  773. if (curr.getMonth() != displayedMonth.getMonth()) {
  774. day.addStyleDependentName(CN_OFFMONTH);
  775. }
  776. String dayDateString = df.format(dayDate);
  777. if (dateStyles.containsKey(dayDateString)) {
  778. day.addStyleName(dateStyles.get(dayDateString));
  779. }
  780. days.setWidget(weekOfMonth, firstWeekdayColumn + dayOfWeek,
  781. day);
  782. Roles.getGridcellRole().set(days.getCellFormatter().getElement(
  783. weekOfMonth, firstWeekdayColumn + dayOfWeek));
  784. // ISO week numbers if requested
  785. days.getCellFormatter().setVisible(weekOfMonth, weekColumn,
  786. isShowISOWeekNumbers());
  787. if (isShowISOWeekNumbers()) {
  788. final String baseCssClass = getDateField()
  789. .getStylePrimaryName()
  790. + "-calendarpanel-weeknumber";
  791. String weekCssClass = baseCssClass;
  792. int weekNumber = DateTimeService.getISOWeekNumber(curr);
  793. days.setHTML(weekOfMonth, 0, "<span class=\"" + weekCssClass
  794. + "\"" + ">" + weekNumber + "</span>");
  795. }
  796. curr.setDate(curr.getDate() + 1);
  797. }
  798. }
  799. }
  800. /**
  801. * Updates the calendar and text field with the selected dates.
  802. */
  803. public void renderCalendar() {
  804. renderCalendar(true);
  805. }
  806. /**
  807. * Returns the value of initialRenderDone.
  808. *
  809. * @since 8.7
  810. *
  811. * @return {@code true} if the initial render has been marked as done,
  812. * {@code false} otherwise
  813. */
  814. public boolean isInitialRenderDone() {
  815. return initialRenderDone;
  816. }
  817. /**
  818. * For internal use only. May be removed or replaced in the future.
  819. *
  820. * Updates the calendar and text field with the selected dates.
  821. *
  822. * @param updateDate
  823. * The value false prevents setting the selected date of the
  824. * calendar based on focusedDate. That can be used when only the
  825. * resolution of the calendar is changed and no date has been
  826. * selected.
  827. */
  828. @SuppressWarnings("rawtypes")
  829. public void renderCalendar(boolean updateDate) {
  830. if (parent instanceof VAbstractPopupCalendar
  831. && !((VAbstractPopupCalendar) parent).popup.isShowing()) {
  832. // a popup that isn't open cannot possibly need a focus change event
  833. updateDate = false;
  834. }
  835. doRenderCalendar(updateDate);
  836. initialRenderDone = true;
  837. }
  838. /**
  839. * Performs the rendering required by the {@link #renderCalendar(boolean)}.
  840. * Subclasses may override this method to provide a custom implementation
  841. * avoiding {@link #renderCalendar(boolean)} override. The latter method
  842. * contains a common logic which should not be overridden.
  843. *
  844. * @param updateDate
  845. * The value false prevents setting the selected date of the
  846. * calendar based on focusedDate. That can be used when only the
  847. * resolution of the calendar is changed and no date has been
  848. * selected.
  849. */
  850. protected void doRenderCalendar(boolean updateDate) {
  851. super.setStylePrimaryName(
  852. getDateField().getStylePrimaryName() + "-calendarpanel");
  853. if (focusedDate == null) {
  854. Date date = getDate();
  855. if (date == null) {
  856. date = new Date();
  857. }
  858. // focusedDate must have zero hours, mins, secs, millisecs
  859. focusedDate = new FocusedDate(date.getYear(), date.getMonth(),
  860. date.getDate());
  861. displayedMonth = new FocusedDate(date.getYear(), date.getMonth(),
  862. 1);
  863. }
  864. if (updateDate && !isDay(getResolution())
  865. && focusChangeListener != null) {
  866. focusChangeListener.focusChanged(new Date(focusedDate.getTime()));
  867. }
  868. final boolean needsMonth = !isYear(getResolution());
  869. boolean needsBody = isBelowMonth(resolution);
  870. buildCalendarHeader(needsMonth, needsBody);
  871. clearCalendarBody(!needsBody);
  872. if (needsBody) {
  873. buildCalendarBody();
  874. }
  875. }
  876. /**
  877. * Moves the focus forward the given number of days.
  878. */
  879. private void focusNextDay(int days) {
  880. if (focusedDate == null) {
  881. return;
  882. }
  883. Date focusCopy = ((Date) focusedDate.clone());
  884. focusCopy.setDate(focusedDate.getDate() + days);
  885. if (!isDateInsideRange(focusCopy, getResolution())) {
  886. // If not inside allowed range, then do not move anything
  887. return;
  888. }
  889. int oldMonth = focusedDate.getMonth();
  890. int oldYear = focusedDate.getYear();
  891. focusedDate.setDate(focusedDate.getDate() + days);
  892. if (focusedDate.getMonth() == oldMonth
  893. && focusedDate.getYear() == oldYear) {
  894. // Month did not change, only move the selection
  895. focusDay(focusedDate);
  896. } else {
  897. // If the month changed we need to re-render the calendar
  898. displayedMonth.setMonth(focusedDate.getMonth());
  899. displayedMonth.setYear(focusedDate.getYear());
  900. renderCalendar();
  901. }
  902. }
  903. /**
  904. * Moves the focus backward the given number of days.
  905. */
  906. private void focusPreviousDay(int days) {
  907. focusNextDay(-days);
  908. }
  909. /**
  910. * Selects the next month
  911. */
  912. private void focusNextMonth() {
  913. if (focusedDate == null) {
  914. return;
  915. }
  916. // Trying to request next month
  917. Date requestedNextMonthDate = (Date) focusedDate.clone();
  918. addOneMonth(requestedNextMonthDate);
  919. if (!isDateInsideRange(requestedNextMonthDate,
  920. getResolution(this::isMonth))) {
  921. return;
  922. }
  923. // Now also checking whether the day is inside the range or not. If not
  924. // inside,
  925. // correct it
  926. if (!isDateInsideRange(requestedNextMonthDate,
  927. getResolution(this::isDay))) {
  928. requestedNextMonthDate = adjustDateToFitInsideRange(
  929. requestedNextMonthDate);
  930. }
  931. focusedDate.setTime(requestedNextMonthDate.getTime());
  932. displayedMonth.setMonth(displayedMonth.getMonth() + 1);
  933. renderCalendar();
  934. }
  935. private static void addOneMonth(Date date) {
  936. int currentMonth = date.getMonth();
  937. int requestedMonth = (currentMonth + 1) % 12;
  938. date.setMonth(date.getMonth() + 1);
  939. /*
  940. * If the selected value was e.g. 31.3 the new value would be 31.4 but
  941. * this value is invalid so the new value will be 1.5. This is taken
  942. * care of by decreasing the value until we have the correct month.
  943. */
  944. while (date.getMonth() != requestedMonth) {
  945. date.setDate(date.getDate() - 1);
  946. }
  947. }
  948. private static void removeOneMonth(Date date) {
  949. int currentMonth = date.getMonth();
  950. date.setMonth(date.getMonth() - 1);
  951. /*
  952. * If the selected value was e.g. 31.12 the new value would be 31.11 but
  953. * this value is invalid so the new value will be 1.12. This is taken
  954. * care of by decreasing the value until we have the correct month.
  955. */
  956. while (date.getMonth() == currentMonth) {
  957. date.setDate(date.getDate() - 1);
  958. }
  959. }
  960. /**
  961. * Selects the previous month
  962. */
  963. private void focusPreviousMonth() {
  964. if (focusedDate == null) {
  965. return;
  966. }
  967. Date requestedPreviousMonthDate = (Date) focusedDate.clone();
  968. removeOneMonth(requestedPreviousMonthDate);
  969. if (!isDateInsideRange(requestedPreviousMonthDate,
  970. getResolution(this::isMonth))) {
  971. return;
  972. }
  973. if (!isDateInsideRange(requestedPreviousMonthDate,
  974. getResolution(this::isDay))) {
  975. requestedPreviousMonthDate = adjustDateToFitInsideRange(
  976. requestedPreviousMonthDate);
  977. }
  978. focusedDate.setTime(requestedPreviousMonthDate.getTime());
  979. displayedMonth.setMonth(displayedMonth.getMonth() - 1);
  980. renderCalendar();
  981. }
  982. /**
  983. * Selects the previous year
  984. */
  985. private void focusPreviousYear(int years) {
  986. if (focusedDate == null) {
  987. return;
  988. }
  989. Date previousYearDate = (Date) focusedDate.clone();
  990. previousYearDate.setYear(previousYearDate.getYear() - years);
  991. // Do not focus if not inside range
  992. if (!isDateInsideRange(previousYearDate, getResolution(this::isYear))) {
  993. return;
  994. }
  995. // If we remove one year, but have to roll back a bit, fit it
  996. // into the calendar. Also the months have to be changed
  997. if (!isDateInsideRange(previousYearDate, getResolution(this::isDay))) {
  998. previousYearDate = adjustDateToFitInsideRange(previousYearDate);
  999. focusedDate.setYear(previousYearDate.getYear());
  1000. focusedDate.setMonth(previousYearDate.getMonth());
  1001. focusedDate.setDate(previousYearDate.getDate());
  1002. displayedMonth.setYear(previousYearDate.getYear());
  1003. displayedMonth.setMonth(previousYearDate.getMonth());
  1004. } else {
  1005. int currentMonth = focusedDate.getMonth();
  1006. focusedDate.setYear(focusedDate.getYear() - years);
  1007. displayedMonth.setYear(displayedMonth.getYear() - years);
  1008. /*
  1009. * If the focused date was a leap day (Feb 29), the new date becomes
  1010. * Mar 1 if the new year is not also a leap year. Set it to Feb 28
  1011. * instead.
  1012. */
  1013. if (focusedDate.getMonth() != currentMonth) {
  1014. focusedDate.setDate(0);
  1015. }
  1016. }
  1017. renderCalendar();
  1018. }
  1019. /**
  1020. * Selects the next year
  1021. */
  1022. private void focusNextYear(int years) {
  1023. if (focusedDate == null) {
  1024. return;
  1025. }
  1026. Date nextYearDate = (Date) focusedDate.clone();
  1027. nextYearDate.setYear(nextYearDate.getYear() + years);
  1028. // Do not focus if not inside range
  1029. if (!isDateInsideRange(nextYearDate, getResolution(this::isYear))) {
  1030. return;
  1031. }
  1032. // If we add one year, but have to roll back a bit, fit it
  1033. // into the calendar. Also the months have to be changed
  1034. if (!isDateInsideRange(nextYearDate, getResolution(this::isDay))) {
  1035. nextYearDate = adjustDateToFitInsideRange(nextYearDate);
  1036. focusedDate.setYear(nextYearDate.getYear());
  1037. focusedDate.setMonth(nextYearDate.getMonth());
  1038. focusedDate.setDate(nextYearDate.getDate());
  1039. displayedMonth.setYear(nextYearDate.getYear());
  1040. displayedMonth.setMonth(nextYearDate.getMonth());
  1041. } else {
  1042. int currentMonth = focusedDate.getMonth();
  1043. focusedDate.setYear(focusedDate.getYear() + years);
  1044. displayedMonth.setYear(displayedMonth.getYear() + years);
  1045. /*
  1046. * If the focused date was a leap day (Feb 29), the new date becomes
  1047. * Mar 1 if the new year is not also a leap year. Set it to Feb 28
  1048. * instead.
  1049. */
  1050. if (focusedDate.getMonth() != currentMonth) {
  1051. focusedDate.setDate(0);
  1052. }
  1053. }
  1054. renderCalendar();
  1055. }
  1056. /**
  1057. * Handles a user click on the component
  1058. *
  1059. * @param sender
  1060. * The component that was clicked
  1061. */
  1062. private void processClickEvent(Widget sender) {
  1063. if (!isEnabled() || isReadonly()) {
  1064. return;
  1065. }
  1066. if (sender == prevYear) {
  1067. focusPreviousYear(1);
  1068. } else if (sender == nextYear) {
  1069. focusNextYear(1);
  1070. } else if (sender == prevMonth) {
  1071. focusPreviousMonth();
  1072. } else if (sender == nextMonth) {
  1073. focusNextMonth();
  1074. }
  1075. }
  1076. /*
  1077. * (non-Javadoc)
  1078. *
  1079. * @see
  1080. * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
  1081. * .event.dom.client.KeyDownEvent)
  1082. */
  1083. @Override
  1084. public void onKeyDown(KeyDownEvent event) {
  1085. handleKeyPress(event);
  1086. }
  1087. /*
  1088. * (non-Javadoc)
  1089. *
  1090. * @see
  1091. * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
  1092. * .gwt.event.dom.client.KeyPressEvent)
  1093. */
  1094. @Override
  1095. public void onKeyPress(KeyPressEvent event) {
  1096. handleKeyPress(event);
  1097. }
  1098. /**
  1099. * Handles the keypress from both the onKeyPress event and the onKeyDown
  1100. * event
  1101. *
  1102. * @param event
  1103. * The keydown/keypress event
  1104. */
  1105. private void handleKeyPress(DomEvent<?> event) {
  1106. // Check tabs
  1107. int keycode = event.getNativeEvent().getKeyCode();
  1108. if (keycode == KeyCodes.KEY_TAB
  1109. && event.getNativeEvent().getShiftKey()) {
  1110. if (onTabOut(event)) {
  1111. return;
  1112. }
  1113. }
  1114. // Handle the navigation
  1115. if (handleNavigation(keycode,
  1116. event.getNativeEvent().getCtrlKey()
  1117. || event.getNativeEvent().getMetaKey(),
  1118. event.getNativeEvent().getShiftKey())) {
  1119. event.preventDefault();
  1120. }
  1121. }
  1122. /**
  1123. * Notifies submit-listeners of a submit event
  1124. */
  1125. private void onSubmit() {
  1126. if (getSubmitListener() != null) {
  1127. getSubmitListener().onSubmit();
  1128. }
  1129. }
  1130. /**
  1131. * Notifies submit-listeners of a cancel event
  1132. */
  1133. private void onCancel() {
  1134. if (getSubmitListener() != null) {
  1135. getSubmitListener().onCancel();
  1136. }
  1137. }
  1138. /**
  1139. * Handles the keyboard navigation when the resolution is set to years.
  1140. *
  1141. * @param keycode
  1142. * The keycode to process
  1143. * @param ctrl
  1144. * Is ctrl pressed?
  1145. * @param shift
  1146. * is shift pressed
  1147. * @return Returns true if the keycode was processed, else false
  1148. */
  1149. protected boolean handleNavigationYearMode(int keycode, boolean ctrl,
  1150. boolean shift) {
  1151. // Ctrl and Shift selection not supported
  1152. if (ctrl || shift) {
  1153. return false;
  1154. } else if (keycode == getPreviousKey()) {
  1155. focusNextYear(10); // Add 10 years
  1156. return true;
  1157. } else if (keycode == getForwardKey()) {
  1158. focusNextYear(1); // Add 1 year
  1159. return true;
  1160. } else if (keycode == getNextKey()) {
  1161. focusPreviousYear(10); // Subtract 10 years
  1162. return true;
  1163. } else if (keycode == getBackwardKey()) {
  1164. focusPreviousYear(1); // Subtract 1 year
  1165. return true;
  1166. } else if (keycode == getSelectKey()) {
  1167. value = (Date) focusedDate.clone();
  1168. onSubmit();
  1169. return true;
  1170. } else if (keycode == getResetKey()) {
  1171. // Restore showing value the selected value
  1172. focusedDate.setTime(value.getTime());
  1173. renderCalendar();
  1174. return true;
  1175. } else if (keycode == getCloseKey()) {
  1176. // TODO fire listener, on users responsibility??
  1177. onCancel();
  1178. return true;
  1179. }
  1180. return false;
  1181. }
  1182. /**
  1183. * Handle the keyboard navigation when the resolution is set to MONTH.
  1184. *
  1185. * @param keycode
  1186. * The keycode to handle
  1187. * @param ctrl
  1188. * Was the ctrl key pressed?
  1189. * @param shift
  1190. * Was the shift key pressed?
  1191. * @return {@code true} if the navigation was handled successfully,
  1192. * {@code false} otherwise
  1193. */
  1194. protected boolean handleNavigationMonthMode(int keycode, boolean ctrl,
  1195. boolean shift) {
  1196. // Ctrl selection not supported
  1197. if (ctrl) {
  1198. return false;
  1199. } else if (keycode == getPreviousKey()) {
  1200. focusNextYear(1); // Add 1 year
  1201. return true;
  1202. } else if (keycode == getForwardKey()) {
  1203. focusNextMonth(); // Add 1 month
  1204. return true;
  1205. } else if (keycode == getNextKey()) {
  1206. focusPreviousYear(1); // Subtract 1 year
  1207. return true;
  1208. } else if (keycode == getBackwardKey()) {
  1209. focusPreviousMonth(); // Subtract 1 month
  1210. return true;
  1211. } else if (keycode == getSelectKey()) {
  1212. value = (Date) focusedDate.clone();
  1213. onSubmit();
  1214. return true;
  1215. } else if (keycode == getResetKey()) {
  1216. // Restore showing value the selected value
  1217. focusedDate.setTime(value.getTime());
  1218. renderCalendar();
  1219. return true;
  1220. } else if (keycode == getCloseKey()) {
  1221. onCancel();
  1222. // TODO fire close event
  1223. return true;
  1224. }
  1225. return false;
  1226. }
  1227. /**
  1228. * Handle keyboard navigation what the resolution is set to DAY.
  1229. *
  1230. * @param keycode
  1231. * The keycode to handle
  1232. * @param ctrl
  1233. * Was the ctrl key pressed?
  1234. * @param shift
  1235. * Was the shift key pressed?
  1236. * @return Return true if the key press was handled by the method, else
  1237. * return false.
  1238. */
  1239. protected boolean handleNavigationDayMode(int keycode, boolean ctrl,
  1240. boolean shift) {
  1241. // Ctrl key is not in use
  1242. if (ctrl) {
  1243. return false;
  1244. }
  1245. /*
  1246. * Jumps to the next day.
  1247. */
  1248. if (keycode == getForwardKey() && !shift) {
  1249. focusNextDay(1);
  1250. return true;
  1251. /*
  1252. * Jumps to the previous day
  1253. */
  1254. } else if (keycode == getBackwardKey() && !shift) {
  1255. focusPreviousDay(1);
  1256. return true;
  1257. /*
  1258. * Jumps one week forward in the calendar
  1259. */
  1260. } else if (keycode == getNextKey() && !shift) {
  1261. focusNextDay(7);
  1262. return true;
  1263. /*
  1264. * Jumps one week back in the calendar
  1265. */
  1266. } else if (keycode == getPreviousKey() && !shift) {
  1267. focusPreviousDay(7);
  1268. return true;
  1269. /*
  1270. * Selects the value that is chosen
  1271. */
  1272. } else if (keycode == getSelectKey() && !shift) {
  1273. selectFocused();
  1274. onSubmit(); // submit
  1275. return true;
  1276. } else if (keycode == getCloseKey()) {
  1277. onCancel();
  1278. // TODO close event
  1279. return true;
  1280. /*
  1281. * Jumps to the next month
  1282. */
  1283. } else if (shift && keycode == getForwardKey()) {
  1284. focusNextMonth();
  1285. return true;
  1286. /*
  1287. * Jumps to the previous month
  1288. */
  1289. } else if (shift && keycode == getBackwardKey()) {
  1290. focusPreviousMonth();
  1291. return true;
  1292. /*
  1293. * Jumps to the next year
  1294. */
  1295. } else if (shift && keycode == getPreviousKey()) {
  1296. focusNextYear(1);
  1297. return true;
  1298. /*
  1299. * Jumps to the previous year
  1300. */
  1301. } else if (shift && keycode == getNextKey()) {
  1302. focusPreviousYear(1);
  1303. return true;
  1304. /*
  1305. * Resets the selection
  1306. */
  1307. } else if (keycode == getResetKey() && !shift) {
  1308. // Restore showing value the selected value
  1309. focusedDate = new FocusedDate(value.getYear(), value.getMonth(),
  1310. value.getDate());
  1311. displayedMonth = new FocusedDate(value.getYear(), value.getMonth(),
  1312. 1);
  1313. renderCalendar();
  1314. return true;
  1315. }
  1316. return false;
  1317. }
  1318. /**
  1319. * Handles the keyboard navigation.
  1320. *
  1321. * @param keycode
  1322. * The key code that was pressed
  1323. * @param ctrl
  1324. * Was the ctrl key pressed
  1325. * @param shift
  1326. * Was the shift key pressed
  1327. * @return Return true if key press was handled by the component, else
  1328. * return false
  1329. */
  1330. protected boolean handleNavigation(int keycode, boolean ctrl,
  1331. boolean shift) {
  1332. if (!isEnabled() || isReadonly()) {
  1333. return false;
  1334. } else if (isYear(getResolution())) {
  1335. return handleNavigationYearMode(keycode, ctrl, shift);
  1336. } else if (isMonth(getResolution())) {
  1337. return handleNavigationMonthMode(keycode, ctrl, shift);
  1338. } else if (isDay(getResolution())) {
  1339. return handleNavigationDayMode(keycode, ctrl, shift);
  1340. } else {
  1341. return handleNavigationDayMode(keycode, ctrl, shift);
  1342. }
  1343. }
  1344. /**
  1345. * Returns the reset key which will reset the calendar to the previous
  1346. * selection. By default this is backspace but it can be overridden to
  1347. * change the key to whatever you want.
  1348. *
  1349. * @return the reset key
  1350. */
  1351. protected int getResetKey() {
  1352. return KeyCodes.KEY_BACKSPACE;
  1353. }
  1354. /**
  1355. * Returns the select key which selects the value. By default this is the
  1356. * enter key but it can be changed to whatever you like by overriding this
  1357. * method.
  1358. *
  1359. * @return the select key
  1360. */
  1361. protected int getSelectKey() {
  1362. return KeyCodes.KEY_ENTER;
  1363. }
  1364. /**
  1365. * Returns the key that closes the popup window if this is a VPopopCalendar.
  1366. * Else this does nothing. By default this is the Escape key but you can
  1367. * change the key to whatever you want by overriding this method.
  1368. *
  1369. * @return the closing key
  1370. */
  1371. protected int getCloseKey() {
  1372. return KeyCodes.KEY_ESCAPE;
  1373. }
  1374. /**
  1375. * The key that selects the next day in the calendar. By default this is the
  1376. * right arrow key but by overriding this method it can be changed to
  1377. * whatever you like.
  1378. *
  1379. * @return the forward key
  1380. */
  1381. protected int getForwardKey() {
  1382. return KeyCodes.KEY_RIGHT;
  1383. }
  1384. /**
  1385. * The key that selects the previous day in the calendar. By default this is
  1386. * the left arrow key but by overriding this method it can be changed to
  1387. * whatever you like.
  1388. *
  1389. * @return the backward key
  1390. */
  1391. protected int getBackwardKey() {
  1392. return KeyCodes.KEY_LEFT;
  1393. }
  1394. /**
  1395. * The key that selects the next week in the calendar. By default this is
  1396. * the down arrow key but by overriding this method it can be changed to
  1397. * whatever you like.
  1398. *
  1399. * @return the next week key
  1400. */
  1401. protected int getNextKey() {
  1402. return KeyCodes.KEY_DOWN;
  1403. }
  1404. /**
  1405. * The key that selects the previous week in the calendar. By default this
  1406. * is the up arrow key but by overriding this method it can be changed to
  1407. * whatever you like.
  1408. *
  1409. * @return the previous week key
  1410. */
  1411. protected int getPreviousKey() {
  1412. return KeyCodes.KEY_UP;
  1413. }
  1414. /*
  1415. * (non-Javadoc)
  1416. *
  1417. * @see
  1418. * com.google.gwt.event.dom.client.MouseOutHandler#onMouseOut(com.google
  1419. * .gwt.event.dom.client.MouseOutEvent)
  1420. */
  1421. @Override
  1422. public void onMouseOut(MouseOutEvent event) {
  1423. if (mouseTimer != null) {
  1424. mouseTimer.cancel();
  1425. }
  1426. }
  1427. /*
  1428. * (non-Javadoc)
  1429. *
  1430. * @see
  1431. * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google
  1432. * .gwt.event.dom.client.MouseDownEvent)
  1433. */
  1434. @SuppressWarnings("unchecked")
  1435. @Override
  1436. public void onMouseDown(MouseDownEvent event) {
  1437. // Click-n-hold the left mouse button for fast-forward or fast-rewind.
  1438. // Timer is first used for a 500ms delay after mousedown. After that has
  1439. // elapsed, another timer is triggered to go off every 150ms. Both
  1440. // timers are cancelled on mouseup or mouseout.
  1441. if (event.getNativeButton() == NativeEvent.BUTTON_LEFT && event
  1442. .getSource() instanceof VAbstractCalendarPanel.VEventButton) {
  1443. final VEventButton sender = (VEventButton) event.getSource();
  1444. processClickEvent(sender);
  1445. mouseTimer = new Timer() {
  1446. @Override
  1447. public void run() {
  1448. mouseTimer = new Timer() {
  1449. @Override
  1450. public void run() {
  1451. processClickEvent(sender);
  1452. }
  1453. };
  1454. mouseTimer.scheduleRepeating(150);
  1455. }
  1456. };
  1457. mouseTimer.schedule(500);
  1458. }
  1459. }
  1460. /*
  1461. * (non-Javadoc)
  1462. *
  1463. * @see
  1464. * com.google.gwt.event.dom.client.MouseUpHandler#onMouseUp(com.google.gwt
  1465. * .event.dom.client.MouseUpEvent)
  1466. */
  1467. @Override
  1468. public void onMouseUp(MouseUpEvent event) {
  1469. if (mouseTimer != null) {
  1470. mouseTimer.cancel();
  1471. }
  1472. }
  1473. /**
  1474. * Adjusts a date to fit inside the range, only if outside
  1475. *
  1476. * @param date
  1477. */
  1478. private Date adjustDateToFitInsideRange(Date date) {
  1479. if (!isAcceptedByRangeStart(date, resolution)) {
  1480. date = parseRangeString(rangeStart);
  1481. } else if (!isAcceptedByRangeEnd(date, resolution)) {
  1482. date = parseRangeString(rangeEnd);
  1483. }
  1484. return date;
  1485. }
  1486. private Date parseRangeString(String dateStr) {
  1487. if (dateStr == null || "".equals(dateStr)) {
  1488. return null;
  1489. }
  1490. int year = Integer.parseInt(dateStr.substring(0, 4)) - 1900;
  1491. int month = parsePart(dateStr, 5, 2, 1) - 1;
  1492. int day = parsePart(dateStr, 8, 2, 1);
  1493. int hrs = parsePart(dateStr, 11, 2, 0);
  1494. int min = parsePart(dateStr, 14, 2, 0);
  1495. int sec = parsePart(dateStr, 17, 2, 0);
  1496. return new Date(year, month, day, hrs, min, sec);
  1497. }
  1498. private int parsePart(String dateStr, int beginIndex, int length,
  1499. int defValue) {
  1500. if (dateStr.length() < beginIndex + length) {
  1501. return defValue;
  1502. }
  1503. return Integer
  1504. .parseInt(dateStr.substring(beginIndex, beginIndex + length));
  1505. }
  1506. /**
  1507. * Sets the data of the Panel.
  1508. *
  1509. * @param currentDate
  1510. * The date to set
  1511. */
  1512. public void setDate(Date currentDate) {
  1513. doSetDate(currentDate, false, () -> {
  1514. });
  1515. }
  1516. /**
  1517. * The actual implementation of the logic which sets the data of the Panel.
  1518. * The method {@link #setDate(Date)} just delegate a call to this method
  1519. * providing additional config parameters.
  1520. *
  1521. * @param currentDate
  1522. * currentDate The date to set
  1523. * @param needRerender
  1524. * if {@code true} then calendar will be rerendered regardless of
  1525. * internal logic, otherwise the decision will be made on the
  1526. * internal state inside the method
  1527. * @param focusAction
  1528. * an additional action which will be executed in case
  1529. * rerendering is not required
  1530. */
  1531. protected void doSetDate(Date currentDate, boolean needRerender,
  1532. Runnable focusAction) {
  1533. // Check that we are not re-rendering an already active date
  1534. if (currentDate == value && currentDate != null) {
  1535. return;
  1536. }
  1537. boolean currentDateWasAdjusted = false;
  1538. // Check that selected date is inside the allowed range
  1539. if (currentDate != null
  1540. && !isDateInsideRange(currentDate, getResolution())) {
  1541. currentDate = adjustDateToFitInsideRange(currentDate);
  1542. currentDateWasAdjusted = true;
  1543. }
  1544. Date oldDisplayedMonth = displayedMonth;
  1545. value = currentDate;
  1546. // If current date was adjusted, we will not select any date,
  1547. // since that will look like a date is selected. Instead we
  1548. // only focus on the adjusted value
  1549. if (value == null || currentDateWasAdjusted) {
  1550. // If ranges enabled, we may need to focus on a different view to
  1551. // potentially not get stuck
  1552. if (rangeStart != null || rangeEnd != null) {
  1553. Date dateThatFitsInsideRange = adjustDateToFitInsideRange(
  1554. new Date());
  1555. focusedDate = new FocusedDate(dateThatFitsInsideRange.getYear(),
  1556. dateThatFitsInsideRange.getMonth(),
  1557. dateThatFitsInsideRange.getDate());
  1558. displayedMonth = new FocusedDate(
  1559. dateThatFitsInsideRange.getYear(),
  1560. dateThatFitsInsideRange.getMonth(), 1);
  1561. // value was adjusted. Set selected to null to not cause
  1562. // confusion, but this is only needed (and allowed) when we have
  1563. // a day
  1564. // resolution
  1565. if (isDay(getResolution())) {
  1566. value = null;
  1567. }
  1568. } else {
  1569. displayedMonth = null;
  1570. focusedDate = null;
  1571. }
  1572. } else {
  1573. focusedDate = new FocusedDate(value.getYear(), value.getMonth(),
  1574. value.getDate());
  1575. displayedMonth = new FocusedDate(value.getYear(), value.getMonth(),
  1576. 1);
  1577. }
  1578. // Re-render calendar if the displayed month is changed.
  1579. if (needRerender || oldDisplayedMonth == null || value == null
  1580. || oldDisplayedMonth.getYear() != value.getYear()
  1581. || oldDisplayedMonth.getMonth() != value.getMonth()) {
  1582. renderCalendar();
  1583. } else {
  1584. focusDay(focusedDate);
  1585. selectFocused();
  1586. focusAction.run();
  1587. }
  1588. if (!hasFocus) {
  1589. focusDay(null);
  1590. }
  1591. }
  1592. /**
  1593. * A widget representing a single day in the calendar panel.
  1594. */
  1595. private class Day extends InlineHTML {
  1596. private final Date date;
  1597. Day(Date date) {
  1598. super("" + date.getDate());
  1599. this.date = date;
  1600. addClickHandler(dayClickHandler);
  1601. }
  1602. public Date getDate() {
  1603. return date;
  1604. }
  1605. }
  1606. public Date getDate() {
  1607. return value;
  1608. }
  1609. /**
  1610. * True should be returned if the panel will not be used after this event.
  1611. *
  1612. * @param event
  1613. * dom event
  1614. * @return {@code true} if the panel will not be used after this event,
  1615. * {@code false} otherwise
  1616. */
  1617. protected boolean onTabOut(DomEvent<?> event) {
  1618. if (focusOutListener != null) {
  1619. return focusOutListener.onFocusOut(event);
  1620. }
  1621. return false;
  1622. }
  1623. /**
  1624. * A focus out listener is triggered when the panel loosed focus. This can
  1625. * happen either after a user clicks outside the panel or tabs out.
  1626. *
  1627. * @param listener
  1628. * The listener to trigger
  1629. */
  1630. public void setFocusOutListener(FocusOutListener listener) {
  1631. focusOutListener = listener;
  1632. }
  1633. /**
  1634. * The submit listener is called when the user selects a value from the
  1635. * calendar either by clicking the day or selects it by keyboard.
  1636. *
  1637. * @param submitListener
  1638. * The listener to trigger
  1639. */
  1640. public void setSubmitListener(SubmitListener submitListener) {
  1641. this.submitListener = submitListener;
  1642. }
  1643. /**
  1644. * The given FocusChangeListener is notified when the focused date changes
  1645. * by user either clicking on a new date or by using the keyboard.
  1646. *
  1647. * @param listener
  1648. * The FocusChangeListener to be notified
  1649. */
  1650. public void setFocusChangeListener(FocusChangeListener listener) {
  1651. focusChangeListener = listener;
  1652. }
  1653. /**
  1654. * Returns the submit listener that listens to selection made from the
  1655. * panel.
  1656. *
  1657. * @return The listener or NULL if no listener has been set
  1658. */
  1659. public SubmitListener getSubmitListener() {
  1660. return submitListener;
  1661. }
  1662. /*
  1663. * (non-Javadoc)
  1664. *
  1665. * @see
  1666. * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
  1667. * .dom.client.BlurEvent)
  1668. */
  1669. @Override
  1670. public void onBlur(final BlurEvent event) {
  1671. if (event.getSource() instanceof VAbstractCalendarPanel) {
  1672. hasFocus = false;
  1673. focusDay(null);
  1674. }
  1675. }
  1676. /*
  1677. * (non-Javadoc)
  1678. *
  1679. * @see
  1680. * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
  1681. * .dom.client.FocusEvent)
  1682. */
  1683. @Override
  1684. public void onFocus(FocusEvent event) {
  1685. if (event.getSource() instanceof VAbstractCalendarPanel) {
  1686. hasFocus = true;
  1687. // Focuses the current day if the calendar shows the days
  1688. if (focusedDay != null) {
  1689. focusDay(focusedDate);
  1690. }
  1691. }
  1692. }
  1693. private static final String SUBPART_NEXT_MONTH = "nextmon";
  1694. private static final String SUBPART_PREV_MONTH = "prevmon";
  1695. private static final String SUBPART_NEXT_YEAR = "nexty";
  1696. private static final String SUBPART_PREV_YEAR = "prevy";
  1697. private static final String SUBPART_DAY = "day";
  1698. private static final String SUBPART_MONTH_YEAR_HEADER = "header";
  1699. private String rangeStart;
  1700. private String rangeEnd;
  1701. @Override
  1702. public String getSubPartName(
  1703. com.google.gwt.user.client.Element subElement) {
  1704. if (contains(nextMonth, subElement)) {
  1705. return SUBPART_NEXT_MONTH;
  1706. } else if (contains(prevMonth, subElement)) {
  1707. return SUBPART_PREV_MONTH;
  1708. } else if (contains(nextYear, subElement)) {
  1709. return SUBPART_NEXT_YEAR;
  1710. } else if (contains(prevYear, subElement)) {
  1711. return SUBPART_PREV_YEAR;
  1712. } else if (contains(days, subElement)) {
  1713. // Day, find out which dayOfMonth and use that as the identifier
  1714. Day day = WidgetUtil.findWidget(subElement, Day.class);
  1715. if (day != null) {
  1716. Date date = day.getDate();
  1717. int id = date.getDate();
  1718. // Zero or negative ids map to days of the preceding month,
  1719. // past-the-end-of-month ids to days of the following month
  1720. if (date.getMonth() < displayedMonth.getMonth()) {
  1721. id -= DateTimeService.getNumberOfDaysInMonth(date);
  1722. } else if (date.getMonth() > displayedMonth.getMonth()) {
  1723. id += DateTimeService
  1724. .getNumberOfDaysInMonth(displayedMonth);
  1725. }
  1726. return SUBPART_DAY + id;
  1727. }
  1728. } else if (getCellFormatter().getElement(0, 2)
  1729. .isOrHasChild(subElement)) {
  1730. return SUBPART_MONTH_YEAR_HEADER;
  1731. }
  1732. return null;
  1733. }
  1734. /**
  1735. * Checks if subElement is inside the widget DOM hierarchy.
  1736. *
  1737. * @param w
  1738. * the widget to investigate
  1739. * @param subElement
  1740. * the element to search for
  1741. * @return {@code true} if the given widget is a parent of the given
  1742. * element, {@code false} otherwise.
  1743. */
  1744. protected boolean contains(Widget w, Element subElement) {
  1745. if (w == null || w.getElement() == null) {
  1746. return false;
  1747. }
  1748. return w.getElement().isOrHasChild(subElement);
  1749. }
  1750. @SuppressWarnings("unchecked")
  1751. @Override
  1752. public com.google.gwt.user.client.Element getSubPartElement(
  1753. String subPart) {
  1754. if (SUBPART_NEXT_MONTH.equals(subPart)) {
  1755. return nextMonth.getElement();
  1756. }
  1757. if (SUBPART_PREV_MONTH.equals(subPart)) {
  1758. return prevMonth.getElement();
  1759. }
  1760. if (SUBPART_NEXT_YEAR.equals(subPart)) {
  1761. return nextYear.getElement();
  1762. }
  1763. if (SUBPART_PREV_YEAR.equals(subPart)) {
  1764. return prevYear.getElement();
  1765. }
  1766. if (subPart.startsWith(SUBPART_DAY)) {
  1767. // Zero or negative ids map to days in the preceding month,
  1768. // past-the-end-of-month ids to days in the following month
  1769. int dayOfMonth = Integer
  1770. .parseInt(subPart.substring(SUBPART_DAY.length()));
  1771. Date date = new Date(displayedMonth.getYear(),
  1772. displayedMonth.getMonth(), dayOfMonth);
  1773. for (Widget w : days) {
  1774. if (w instanceof VAbstractCalendarPanel.Day) {
  1775. Day day = (Day) w;
  1776. if (day.getDate().equals(date)) {
  1777. return day.getElement();
  1778. }
  1779. }
  1780. }
  1781. }
  1782. if (SUBPART_MONTH_YEAR_HEADER.equals(subPart)) {
  1783. return DOM.asOld(
  1784. (Element) getCellFormatter().getElement(0, 2).getChild(0));
  1785. }
  1786. return null;
  1787. }
  1788. @Override
  1789. protected void onDetach() {
  1790. super.onDetach();
  1791. if (mouseTimer != null) {
  1792. mouseTimer.cancel();
  1793. }
  1794. }
  1795. /**
  1796. * Helper class to inform the screen reader that the user changed the
  1797. * selected date. It sets the value of a field that is outside the view, and
  1798. * is defined as a live area. That way the screen reader recognizes the
  1799. * change and reads it to the user.
  1800. */
  1801. public class FocusedDate extends Date {
  1802. public FocusedDate(int year, int month, int date) {
  1803. super(year, month, date);
  1804. }
  1805. @Override
  1806. public void setTime(long time) {
  1807. super.setTime(time);
  1808. setLabel();
  1809. }
  1810. @Override
  1811. @Deprecated
  1812. public void setDate(int date) {
  1813. super.setDate(date);
  1814. setLabel();
  1815. }
  1816. @Override
  1817. @Deprecated
  1818. public void setMonth(int month) {
  1819. super.setMonth(month);
  1820. setLabel();
  1821. }
  1822. @Override
  1823. @Deprecated
  1824. public void setYear(int year) {
  1825. super.setYear(year);
  1826. setLabel();
  1827. }
  1828. private void setLabel() {
  1829. if (getDateField() instanceof VAbstractPopupCalendar) {
  1830. ((VAbstractPopupCalendar<?, ?>) getDateField())
  1831. .setFocusedDate(this);
  1832. }
  1833. }
  1834. }
  1835. /**
  1836. * Sets the start range for this component. The start range is inclusive,
  1837. * and it depends on the current resolution, what is considered inside the
  1838. * range.
  1839. *
  1840. * @param newRangeStart
  1841. * - the allowed range's start date
  1842. */
  1843. public void setRangeStart(String newRangeStart) {
  1844. if (!SharedUtil.equals(rangeStart, newRangeStart)) {
  1845. rangeStart = newRangeStart;
  1846. if (initialRenderDone) {
  1847. // Dynamic updates to the range needs to render the calendar to
  1848. // update the element stylenames
  1849. renderCalendar();
  1850. }
  1851. }
  1852. }
  1853. /**
  1854. * Sets the end range for this component. The end range is inclusive, and it
  1855. * depends on the current resolution, what is considered inside the range.
  1856. *
  1857. * @param newRangeEnd
  1858. * - the allowed range's end date
  1859. */
  1860. public void setRangeEnd(String newRangeEnd) {
  1861. if (!SharedUtil.equals(rangeEnd, newRangeEnd)) {
  1862. // Dates with year 10000 or more has + prefix, which is not
  1863. // compatible with format returned by dateStrResolution method
  1864. if (newRangeEnd.startsWith("+")) {
  1865. rangeEnd = newRangeEnd.substring(1);
  1866. } else {
  1867. rangeEnd = newRangeEnd;
  1868. }
  1869. if (initialRenderDone) {
  1870. // Dynamic updates to the range needs to render the calendar to
  1871. // update the element stylenames
  1872. renderCalendar();
  1873. }
  1874. }
  1875. }
  1876. /**
  1877. * Set assistive label for the previous year element.
  1878. *
  1879. * @param label
  1880. * the label to set
  1881. * @since 8.4
  1882. */
  1883. public void setAssistiveLabelPreviousYear(String label) {
  1884. prevYearAssistiveLabel = label;
  1885. }
  1886. /**
  1887. * Set assistive label for the next year element.
  1888. *
  1889. * @param label
  1890. * the label to set
  1891. * @since 8.4
  1892. */
  1893. public void setAssistiveLabelNextYear(String label) {
  1894. nextYearAssistiveLabel = label;
  1895. }
  1896. /**
  1897. * Set assistive label for the previous month element.
  1898. *
  1899. * @param label
  1900. * the label to set
  1901. * @since 8.4
  1902. */
  1903. public void setAssistiveLabelPreviousMonth(String label) {
  1904. prevMonthAssistiveLabel = label;
  1905. }
  1906. /**
  1907. * Set assistive label for the next month element.
  1908. *
  1909. * @param label
  1910. * the label to set
  1911. * @since 8.4
  1912. */
  1913. public void setAssistiveLabelNextMonth(String label) {
  1914. nextMonthAssistiveLabel = label;
  1915. }
  1916. /**
  1917. * Updates assistive labels of the navigation elements.
  1918. *
  1919. * @since 8.4
  1920. */
  1921. public void updateAssistiveLabels() {
  1922. if (prevMonth != null) {
  1923. Roles.getButtonRole().setAriaLabelProperty(prevMonth.getElement(),
  1924. prevMonthAssistiveLabel);
  1925. }
  1926. if (nextMonth != null) {
  1927. Roles.getButtonRole().setAriaLabelProperty(nextMonth.getElement(),
  1928. nextMonthAssistiveLabel);
  1929. }
  1930. if (prevYear != null) {
  1931. Roles.getButtonRole().setAriaLabelProperty(prevYear.getElement(),
  1932. prevYearAssistiveLabel);
  1933. }
  1934. if (nextYear != null) {
  1935. Roles.getButtonRole().setAriaLabelProperty(nextYear.getElement(),
  1936. nextYearAssistiveLabel);
  1937. }
  1938. }
  1939. private static Logger getLogger() {
  1940. return Logger.getLogger(VAbstractCalendarPanel.class.getName());
  1941. }
  1942. }