You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VCalendarPanel.java 72KB

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