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.

VPopupCalendar.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.Date;
  6. import com.google.gwt.core.client.GWT;
  7. import com.google.gwt.event.dom.client.ClickEvent;
  8. import com.google.gwt.event.dom.client.ClickHandler;
  9. import com.google.gwt.event.dom.client.DomEvent;
  10. import com.google.gwt.event.dom.client.KeyCodes;
  11. import com.google.gwt.event.logical.shared.CloseEvent;
  12. import com.google.gwt.event.logical.shared.CloseHandler;
  13. import com.google.gwt.user.client.DOM;
  14. import com.google.gwt.user.client.Element;
  15. import com.google.gwt.user.client.Event;
  16. import com.google.gwt.user.client.Timer;
  17. import com.google.gwt.user.client.Window;
  18. import com.google.gwt.user.client.ui.Button;
  19. import com.google.gwt.user.client.ui.PopupPanel;
  20. import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
  21. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  22. import com.vaadin.terminal.gwt.client.BrowserInfo;
  23. import com.vaadin.terminal.gwt.client.DateTimeService;
  24. import com.vaadin.terminal.gwt.client.VPaintableWidget;
  25. import com.vaadin.terminal.gwt.client.UIDL;
  26. import com.vaadin.terminal.gwt.client.VConsole;
  27. import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.FocusChangeListener;
  28. import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.FocusOutListener;
  29. import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.SubmitListener;
  30. import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.TimeChangeListener;
  31. /**
  32. * Represents a date selection component with a text field and a popup date
  33. * selector.
  34. *
  35. * <b>Note:</b> To change the keyboard assignments used in the popup dialog you
  36. * should extend <code>com.vaadin.terminal.gwt.client.ui.VCalendarPanel</code>
  37. * and then pass set it by calling the
  38. * <code>setCalendarPanel(VCalendarPanel panel)</code> method.
  39. *
  40. */
  41. public class VPopupCalendar extends VTextualDate implements VPaintableWidget,
  42. Field, ClickHandler, CloseHandler<PopupPanel>, SubPartAware {
  43. private final Button calendarToggle;
  44. private VCalendarPanel calendar;
  45. private final VOverlay popup;
  46. private boolean open = false;
  47. private boolean parsable = true;
  48. public VPopupCalendar() {
  49. super();
  50. calendarToggle = new Button();
  51. calendarToggle.setStyleName(CLASSNAME + "-button");
  52. calendarToggle.setText("");
  53. calendarToggle.addClickHandler(this);
  54. // -2 instead of -1 to avoid FocusWidget.onAttach to reset it
  55. calendarToggle.getElement().setTabIndex(-2);
  56. add(calendarToggle);
  57. calendar = GWT.create(VCalendarPanel.class);
  58. calendar.setFocusOutListener(new FocusOutListener() {
  59. public boolean onFocusOut(DomEvent<?> event) {
  60. event.preventDefault();
  61. closeCalendarPanel();
  62. return true;
  63. }
  64. });
  65. calendar.setSubmitListener(new SubmitListener() {
  66. public void onSubmit() {
  67. // Update internal value and send valuechange event if immediate
  68. updateValue(calendar.getDate());
  69. // Update text field (a must when not immediate).
  70. buildDate(true);
  71. closeCalendarPanel();
  72. }
  73. public void onCancel() {
  74. closeCalendarPanel();
  75. }
  76. });
  77. popup = new VOverlay(true, true, true);
  78. popup.setStyleName(VDateField.CLASSNAME + "-popup");
  79. popup.setWidget(calendar);
  80. popup.addCloseHandler(this);
  81. DOM.setElementProperty(calendar.getElement(), "id",
  82. "PID_VAADIN_POPUPCAL");
  83. sinkEvents(Event.ONKEYDOWN);
  84. }
  85. @SuppressWarnings("deprecation")
  86. private void updateValue(Date newDate) {
  87. Date currentDate = getCurrentDate();
  88. if (currentDate == null || newDate.getTime() != currentDate.getTime()) {
  89. setCurrentDate((Date) newDate.clone());
  90. getClient().updateVariable(getId(), "year",
  91. newDate.getYear() + 1900, false);
  92. if (getCurrentResolution() > VDateField.RESOLUTION_YEAR) {
  93. getClient().updateVariable(getId(), "month",
  94. newDate.getMonth() + 1, false);
  95. if (getCurrentResolution() > RESOLUTION_MONTH) {
  96. getClient().updateVariable(getId(), "day",
  97. newDate.getDate(), false);
  98. if (getCurrentResolution() > RESOLUTION_DAY) {
  99. getClient().updateVariable(getId(), "hour",
  100. newDate.getHours(), false);
  101. if (getCurrentResolution() > RESOLUTION_HOUR) {
  102. getClient().updateVariable(getId(), "min",
  103. newDate.getMinutes(), false);
  104. if (getCurrentResolution() > RESOLUTION_MIN) {
  105. getClient().updateVariable(getId(), "sec",
  106. newDate.getSeconds(), false);
  107. }
  108. }
  109. }
  110. }
  111. }
  112. if (isImmediate()) {
  113. getClient().sendPendingVariableChanges();
  114. }
  115. }
  116. }
  117. /*
  118. * (non-Javadoc)
  119. *
  120. * @see
  121. * com.vaadin.terminal.gwt.client.ui.VTextualDate#updateFromUIDL(com.vaadin
  122. * .terminal.gwt.client.UIDL,
  123. * com.vaadin.terminal.gwt.client.ApplicationConnection)
  124. */
  125. @Override
  126. @SuppressWarnings("deprecation")
  127. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  128. boolean lastReadOnlyState = readonly;
  129. parsable = uidl.getBooleanAttribute("parsable");
  130. super.updateFromUIDL(uidl, client);
  131. popup.setStyleName(VDateField.CLASSNAME + "-popup "
  132. + VDateField.CLASSNAME + "-"
  133. + resolutionToString(currentResolution));
  134. calendar.setDateTimeService(getDateTimeService());
  135. calendar.setShowISOWeekNumbers(isShowISOWeekNumbers());
  136. if (calendar.getResolution() != currentResolution) {
  137. calendar.setResolution(currentResolution);
  138. if (calendar.getDate() != null) {
  139. calendar.setDate((Date) getCurrentDate().clone());
  140. // force re-render when changing resolution only
  141. calendar.renderCalendar();
  142. }
  143. }
  144. calendarToggle.setEnabled(enabled);
  145. if (currentResolution <= RESOLUTION_MONTH) {
  146. calendar.setFocusChangeListener(new FocusChangeListener() {
  147. public void focusChanged(Date date) {
  148. updateValue(date);
  149. buildDate();
  150. Date date2 = calendar.getDate();
  151. date2.setYear(date.getYear());
  152. date2.setMonth(date.getMonth());
  153. }
  154. });
  155. } else {
  156. calendar.setFocusChangeListener(null);
  157. }
  158. if (currentResolution > RESOLUTION_DAY) {
  159. calendar.setTimeChangeListener(new TimeChangeListener() {
  160. public void changed(int hour, int min, int sec, int msec) {
  161. Date d = getDate();
  162. if (d == null) {
  163. // date currently null, use the value from calendarPanel
  164. // (~ client time at the init of the widget)
  165. d = (Date) calendar.getDate().clone();
  166. }
  167. d.setHours(hour);
  168. d.setMinutes(min);
  169. d.setSeconds(sec);
  170. DateTimeService.setMilliseconds(d, msec);
  171. // Always update time changes to the server
  172. updateValue(d);
  173. // Update text field
  174. buildDate();
  175. }
  176. });
  177. }
  178. if (readonly) {
  179. calendarToggle.addStyleName(CLASSNAME + "-button-readonly");
  180. } else {
  181. calendarToggle.removeStyleName(CLASSNAME + "-button-readonly");
  182. }
  183. if (lastReadOnlyState != readonly) {
  184. updateWidth();
  185. }
  186. calendarToggle.setEnabled(true);
  187. }
  188. /*
  189. * (non-Javadoc)
  190. *
  191. * @see
  192. * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String)
  193. */
  194. @Override
  195. public void setStyleName(String style) {
  196. // make sure the style is there before size calculation
  197. super.setStyleName(style + " " + CLASSNAME + "-popupcalendar");
  198. }
  199. /**
  200. * Opens the calendar panel popup
  201. */
  202. public void openCalendarPanel() {
  203. if (!open && !readonly) {
  204. open = true;
  205. if (getCurrentDate() != null) {
  206. calendar.setDate((Date) getCurrentDate().clone());
  207. } else {
  208. calendar.setDate(new Date());
  209. }
  210. // clear previous values
  211. popup.setWidth("");
  212. popup.setHeight("");
  213. popup.setPopupPositionAndShow(new PositionCallback() {
  214. public void setPosition(int offsetWidth, int offsetHeight) {
  215. final int w = offsetWidth;
  216. final int h = offsetHeight;
  217. final int browserWindowWidth = Window.getClientWidth()
  218. + Window.getScrollLeft();
  219. final int browserWindowHeight = Window.getClientHeight()
  220. + Window.getScrollTop();
  221. int t = calendarToggle.getAbsoluteTop();
  222. int l = calendarToggle.getAbsoluteLeft();
  223. // Add a little extra space to the right to avoid
  224. // problems with IE7 scrollbars and to make it look
  225. // nicer.
  226. int extraSpace = 30;
  227. boolean overflowRight = false;
  228. if (l + +w + extraSpace > browserWindowWidth) {
  229. overflowRight = true;
  230. // Part of the popup is outside the browser window
  231. // (to the right)
  232. l = browserWindowWidth - w - extraSpace;
  233. }
  234. if (t + h + calendarToggle.getOffsetHeight() + 30 > browserWindowHeight) {
  235. // Part of the popup is outside the browser window
  236. // (below)
  237. t = browserWindowHeight - h
  238. - calendarToggle.getOffsetHeight() - 30;
  239. if (!overflowRight) {
  240. // Show to the right of the popup button unless we
  241. // are in the lower right corner of the screen
  242. l += calendarToggle.getOffsetWidth();
  243. }
  244. }
  245. // fix size
  246. popup.setWidth(w + "px");
  247. popup.setHeight(h + "px");
  248. popup.setPopupPosition(l,
  249. t + calendarToggle.getOffsetHeight() + 2);
  250. /*
  251. * We have to wait a while before focusing since the popup
  252. * needs to be opened before we can focus
  253. */
  254. Timer focusTimer = new Timer() {
  255. @Override
  256. public void run() {
  257. setFocus(true);
  258. }
  259. };
  260. focusTimer.schedule(100);
  261. }
  262. });
  263. } else {
  264. VConsole.error("Cannot reopen popup, it is already open!");
  265. }
  266. }
  267. /*
  268. * (non-Javadoc)
  269. *
  270. * @see
  271. * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
  272. * .dom.client.ClickEvent)
  273. */
  274. public void onClick(ClickEvent event) {
  275. if (event.getSource() == calendarToggle && isEnabled()) {
  276. openCalendarPanel();
  277. }
  278. }
  279. /*
  280. * (non-Javadoc)
  281. *
  282. * @see
  283. * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt
  284. * .event.logical.shared.CloseEvent)
  285. */
  286. public void onClose(CloseEvent<PopupPanel> event) {
  287. if (event.getSource() == popup) {
  288. buildDate();
  289. if (!BrowserInfo.get().isTouchDevice()) {
  290. /*
  291. * Move focus to textbox, unless on touch device (avoids opening
  292. * virtual keyboard).
  293. */
  294. focus();
  295. }
  296. // TODO resolve what the "Sigh." is all about and document it here
  297. // Sigh.
  298. Timer t = new Timer() {
  299. @Override
  300. public void run() {
  301. open = false;
  302. }
  303. };
  304. t.schedule(100);
  305. }
  306. }
  307. /**
  308. * Sets focus to Calendar panel.
  309. *
  310. * @param focus
  311. */
  312. public void setFocus(boolean focus) {
  313. calendar.setFocus(focus);
  314. }
  315. /*
  316. * (non-Javadoc)
  317. *
  318. * @see com.vaadin.terminal.gwt.client.ui.VTextualDate#getFieldExtraWidth()
  319. */
  320. @Override
  321. protected int getFieldExtraWidth() {
  322. if (fieldExtraWidth < 0) {
  323. fieldExtraWidth = super.getFieldExtraWidth();
  324. fieldExtraWidth += calendarToggle.getOffsetWidth();
  325. }
  326. return fieldExtraWidth;
  327. }
  328. /*
  329. * (non-Javadoc)
  330. *
  331. * @see com.vaadin.terminal.gwt.client.ui.VTextualDate#buildDate()
  332. */
  333. @Override
  334. protected void buildDate() {
  335. // Save previous value
  336. String previousValue = getText();
  337. super.buildDate();
  338. // Restore previous value if the input could not be parsed
  339. if (!parsable) {
  340. setText(previousValue);
  341. }
  342. }
  343. /**
  344. * Update the text field contents from the date. See {@link #buildDate()}.
  345. *
  346. * @param forceValid
  347. * true to force the text field to be updated, false to only
  348. * update if the parsable flag is true.
  349. */
  350. protected void buildDate(boolean forceValid) {
  351. if (forceValid) {
  352. parsable = true;
  353. }
  354. buildDate();
  355. }
  356. /*
  357. * (non-Javadoc)
  358. *
  359. * @see
  360. * com.vaadin.terminal.gwt.client.ui.VDateField#onBrowserEvent(com.google
  361. * .gwt.user.client.Event)
  362. */
  363. @Override
  364. public void onBrowserEvent(com.google.gwt.user.client.Event event) {
  365. super.onBrowserEvent(event);
  366. if (DOM.eventGetType(event) == Event.ONKEYDOWN
  367. && event.getKeyCode() == getOpenCalenderPanelKey()) {
  368. openCalendarPanel();
  369. event.preventDefault();
  370. }
  371. }
  372. /**
  373. * Get the key code that opens the calendar panel. By default it is the down
  374. * key but you can override this to be whatever you like
  375. *
  376. * @return
  377. */
  378. protected int getOpenCalenderPanelKey() {
  379. return KeyCodes.KEY_DOWN;
  380. }
  381. /**
  382. * Closes the open popup panel
  383. */
  384. public void closeCalendarPanel() {
  385. if (open) {
  386. popup.hide(true);
  387. }
  388. }
  389. private final String CALENDAR_TOGGLE_ID = "popupButton";
  390. @Override
  391. public Element getSubPartElement(String subPart) {
  392. if (subPart.equals(CALENDAR_TOGGLE_ID)) {
  393. return calendarToggle.getElement();
  394. }
  395. return super.getSubPartElement(subPart);
  396. }
  397. @Override
  398. public String getSubPartName(Element subElement) {
  399. if (calendarToggle.getElement().isOrHasChild(subElement)) {
  400. return CALENDAR_TOGGLE_ID;
  401. }
  402. return super.getSubPartName(subElement);
  403. }
  404. }