Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

VCalendarPanel.java 72KB

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