1 /* 2 * Copyright (C) 2008 ZXing authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.zxing.web.generator.client; 18 19 import com.google.gwt.dom.client.Element; 20 import com.google.gwt.event.dom.client.ChangeEvent; 21 import com.google.gwt.event.dom.client.ChangeHandler; 22 import com.google.gwt.event.dom.client.ClickEvent; 23 import com.google.gwt.event.dom.client.ClickHandler; 24 import com.google.gwt.event.dom.client.KeyPressHandler; 25 import com.google.gwt.event.logical.shared.ValueChangeEvent; 26 import com.google.gwt.event.logical.shared.ValueChangeHandler; 27 import com.google.gwt.i18n.client.DateTimeFormat; 28 import com.google.gwt.user.client.DOM; 29 import com.google.gwt.user.client.ui.CheckBox; 30 import com.google.gwt.user.client.ui.Grid; 31 import com.google.gwt.user.client.ui.HasText; 32 import com.google.gwt.user.client.ui.ListBox; 33 import com.google.gwt.user.client.ui.TextBox; 34 import com.google.gwt.user.client.ui.Widget; 35 import com.google.gwt.user.datepicker.client.DatePicker; 36 37 import java.util.Date; 38 39 /** 40 * A Generator for calendar events. 41 * 42 * @author Yohann Coppel 43 */ 44 public final class CalendarEventGenerator implements GeneratorSource { 45 46 private static final String[] FULL_DAY_ONLY_IDS = 47 { "fullDayOnlyInfo1", "fullDayOnlyInfo2", "fullDayOnlyInfo3", "fullDayOnlyInfo4" }; 48 private static final long ONE_HOUR = 60L * 60 * 1000; 49 50 private Grid table; 51 private final TextBox eventName = new TextBox(); 52 private final CheckBox fullDay = new CheckBox(); 53 private final DatePicker datePicker1 = new DatePicker(); 54 private final DatePicker datePicker2 = new DatePicker(); 55 private final TextBox timePicker1 = new TextBox(); 56 private final TextBox timePicker2 = new TextBox(); 57 private final CheckBox summerTime = new CheckBox(); 58 private final ListBox timeZones = new ListBox(); 59 private Date timePicker1PreviousDate; 60 private final TextBox location = new TextBox(); 61 private final TextBox description = new TextBox(); 62 CalendarEventGenerator(final ChangeHandler handler, KeyPressHandler keyListener)63 public CalendarEventGenerator(final ChangeHandler handler, KeyPressHandler keyListener) { 64 eventName.addStyleName(StylesDefs.INPUT_FIELD_REQUIRED); 65 eventName.addChangeHandler(handler); 66 eventName.addKeyPressHandler(keyListener); 67 setDateToTextBox(timePicker1, new Date()); 68 try { 69 setDateToTextBox(timePicker2, addMilliseconds(getDateFromTextBox(timePicker1), ONE_HOUR)); 70 timePicker1PreviousDate = getDateFromTextBox(timePicker1); 71 } catch (GeneratorException ge) { 72 throw new IllegalStateException(ge); 73 } 74 75 buildTimeZoneList(); 76 timeZones.setSelectedIndex(25); 77 timeZones.addKeyPressHandler(keyListener); 78 timePicker1.addValueChangeHandler(new ValueChangeHandler<String>() { 79 @Override 80 public void onValueChange(ValueChangeEvent<String> valueChangeEvent) { 81 Date time = timePicker1PreviousDate; 82 Date time1; 83 Date time2; 84 try { 85 time1 = getDateFromTextBox(timePicker1); 86 time2 = getDateFromTextBox(timePicker2); 87 } catch (GeneratorException e) { 88 return; 89 } 90 if (time2.after(time)) { 91 // keep the same time difference if the interval is valid. 92 long diff = time2.getTime() - time.getTime(); 93 setDateToTextBox(timePicker2, addMilliseconds(time1, diff)); 94 } else { 95 // otherwise erase the end date and set it to startdate + one hour. 96 setDateToTextBox(timePicker2, addMilliseconds(time1, ONE_HOUR)); 97 } 98 // no need to call onChange for timePicker1, since it will be called 99 // for timePicker2 when changes are made. 100 // listener.onChange(timePicker1); 101 timePicker1PreviousDate = time1; 102 } 103 }); 104 timePicker2.addValueChangeHandler(new ValueChangeHandler<String>() { 105 @Override 106 public void onValueChange(ValueChangeEvent<String> valueChangeEvent) { 107 // Hack to stitch together these old and new APIs 108 ChangeEvent event = new ChangeEvent() { 109 @Override 110 public Object getSource() { 111 return timePicker2; 112 } 113 }; 114 handler.onChange(event); 115 } 116 }); 117 } 118 buildTimeZoneList()119 private void buildTimeZoneList() { 120 for (TimeZoneInfo info : TimeZoneList.TIMEZONES) { 121 timeZones.addItem(info.getGmtRelative() + ' ' + info.getAbbreviation(), 122 String.valueOf(info.getGmtDiff())); 123 } 124 } 125 126 @Override getName()127 public String getName() { 128 return "Calendar event"; 129 } 130 131 @Override getWidget()132 public Grid getWidget() { 133 if (table != null) { 134 return table; 135 } 136 datePicker1.setValue(new Date()); 137 datePicker2.setValue(new Date()); 138 table = new Grid(10, 2); 139 140 table.setText(0, 0, "All day event"); 141 table.setWidget(0, 1, fullDay); 142 143 table.setText(1, 0, "Event title"); 144 table.setWidget(1, 1, eventName); 145 146 table.setText(2, 0, "Start date"); 147 table.setWidget(2, 1, datePicker1); 148 149 table.setText(3, 0, "Time"); 150 table.setWidget(3, 1, timePicker1); 151 152 table.setText(4, 0, "End date"); 153 table.setWidget(4, 1, datePicker2); 154 155 table.setText(5, 0, "Time"); 156 table.setWidget(5, 1, timePicker2); 157 158 table.setText(6, 0, "Time zone"); 159 table.setWidget(6, 1, timeZones); 160 161 table.setText(7, 0, "Daylight savings"); 162 table.setWidget(7, 1, summerTime); 163 164 table.setText(8, 0, "Location"); 165 table.setWidget(8, 1, location); 166 167 table.setText(9, 0, "Description"); 168 table.setWidget(9, 1, description); 169 170 table.getRowFormatter().getElement(3).setId(FULL_DAY_ONLY_IDS[0]); 171 table.getRowFormatter().getElement(5).setId(FULL_DAY_ONLY_IDS[1]); 172 table.getRowFormatter().getElement(6).setId(FULL_DAY_ONLY_IDS[2]); 173 table.getRowFormatter().getElement(7).setId(FULL_DAY_ONLY_IDS[3]); 174 175 fullDay.addClickHandler(new ClickHandler() { 176 @Override 177 public void onClick(ClickEvent event) { 178 CheckBox cb = (CheckBox) event.getSource(); 179 for (String s : FULL_DAY_ONLY_IDS) { 180 Element element = DOM.getElementById(s); 181 String style = cb.getValue() ? "none" : ""; 182 element.getStyle().setProperty("display", style); 183 } 184 } 185 }); 186 187 return table; 188 } 189 190 @Override getText()191 public String getText() throws GeneratorException { 192 String eventName = getEventNameField(); 193 String dates = getDateTimeFields(); 194 String location = getLocationField(); 195 String description = getDescriptionField(); 196 return "BEGIN:VEVENT\r\n" + eventName + dates + location + description + "END:VEVENT\r\n"; 197 } 198 getEventNameField()199 private String getEventNameField() throws GeneratorException { 200 String inputName = eventName.getText(); 201 if (inputName.isEmpty()) { 202 throw new GeneratorException("Event name must be at least 1 character."); 203 } 204 if (inputName.contains("\n")) { 205 throw new GeneratorException("Event name should not contain \\n characters."); 206 } 207 return "SUMMARY:" + inputName + "\r\n"; 208 } 209 getDateTimeFields()210 private String getDateTimeFields() throws GeneratorException { 211 if (fullDay.getValue()) { 212 return getFullDayDateFields(); 213 } 214 return getDateTimeValues(); 215 } 216 getLocationField()217 private String getLocationField() throws GeneratorException { 218 String locationString = location.getText(); 219 if (locationString.isEmpty()) { 220 return ""; 221 } 222 if (locationString.contains("\n")) { 223 throw new GeneratorException( 224 "Location should not contain \\n characters."); 225 } 226 return "LOCATION:" + locationString + "\r\n"; 227 } 228 getDescriptionField()229 private String getDescriptionField() throws GeneratorException { 230 String descriptionString = description.getText(); 231 if (descriptionString.isEmpty()) { 232 return ""; 233 } 234 if (descriptionString.contains("\n")) { 235 throw new GeneratorException( 236 "Description should not contain \\n characters."); 237 } 238 return "DESCRIPTION:" + descriptionString + "\r\n"; 239 } 240 getFullDayDateFields()241 private String getFullDayDateFields() throws GeneratorException { 242 Date date1 = datePicker1.getValue(); 243 Date date2 = datePicker2.getValue(); 244 if (date1 == null || date2 == null) { 245 throw new GeneratorException("Start and end dates must be set."); 246 } 247 if (date1.after(date2)) { 248 throw new GeneratorException("End date cannot be before start date."); 249 } 250 // Specify end date as +1 day since it's exclusive 251 Date date2PlusDay = new Date(date2.getTime() + 24 * 60 * 60 * 1000); 252 DateTimeFormat isoFormatter = DateTimeFormat.getFormat("yyyyMMdd"); 253 return "DTSTART;VALUE=DATE:" + isoFormatter.format(date1) + "\r\n" + 254 "DTEND;VALUE=DATE:" + isoFormatter.format(date2PlusDay) + "\r\n"; 255 } 256 getDateTimeValues()257 private String getDateTimeValues() throws GeneratorException { 258 Date date1 = datePicker1.getValue(); 259 Date date2 = datePicker2.getValue(); 260 Date time1 = getDateFromTextBox(timePicker1); 261 Date time2 = getDateFromTextBox(timePicker2); 262 if (date1 == null || date2 == null || time1 == null || time2 == null) { 263 throw new GeneratorException("Start and end dates/times must be set."); 264 } 265 String timezoneDelta = timeZones.getValue(timeZones.getSelectedIndex()); 266 long diffTimeZone = Long.parseLong(timezoneDelta); 267 if (summerTime.getValue()) { 268 diffTimeZone += ONE_HOUR; 269 } 270 Date dateTime1 = addMilliseconds(mergeDateAndTime(date1, time1), -diffTimeZone); 271 Date dateTime2 = addMilliseconds(mergeDateAndTime(date2, time2), -diffTimeZone); 272 if (dateTime1.after(dateTime2)) { 273 throw new GeneratorException("Ending date/time cannot be before starting date/time."); 274 } 275 DateTimeFormat isoFormatter = DateTimeFormat.getFormat("yyyyMMdd'T'HHmmss'Z'"); 276 return "DTSTART:" + isoFormatter.format(dateTime1) + "\r\n" + 277 "DTEND:" + isoFormatter.format(dateTime2) + "\r\n"; 278 } 279 mergeDateAndTime(Date date, Date time)280 private static Date mergeDateAndTime(Date date, Date time) { 281 // Is that the only ugly way to do with GWT ? given that we don't 282 // have java.util.Calendar for instance 283 DateTimeFormat extractDate = DateTimeFormat.getFormat("yyyyMMdd"); 284 DateTimeFormat extractTime = DateTimeFormat.getFormat("HHmm"); 285 DateTimeFormat merger = DateTimeFormat.getFormat("yyyyMMddHHmmss"); 286 String d = extractDate.format(date); 287 String t = extractTime.format(time) + "00"; 288 return merger.parse(d + t); 289 } 290 291 @Override validate(Widget widget)292 public void validate(Widget widget) throws GeneratorException { 293 if (widget == eventName) { 294 getEventNameField(); 295 } else if (widget == datePicker1 || widget == timePicker1 || widget == datePicker2 || widget == timePicker2) { 296 getDateTimeFields(); 297 } 298 } 299 addMilliseconds(Date time1, long milliseconds)300 private static Date addMilliseconds(Date time1, long milliseconds) { 301 return new Date(time1.getTime() + milliseconds); 302 } 303 getDateFromTextBox(HasText textBox)304 private static Date getDateFromTextBox(HasText textBox) throws GeneratorException { 305 DateTimeFormat extractTime = DateTimeFormat.getFormat("HHmm"); 306 try { 307 return extractTime.parseStrict(textBox.getText()); 308 } catch (IllegalArgumentException iae) { 309 throw new GeneratorException("Invalid time"); 310 } 311 } 312 setDateToTextBox(HasText textBox, Date date)313 private static void setDateToTextBox(HasText textBox, Date date) { 314 DateTimeFormat extractTime = DateTimeFormat.getFormat("HHmm"); 315 textBox.setText(extractTime.format(date)); 316 } 317 318 @Override setFocus()319 public void setFocus() { 320 eventName.setFocus(true); 321 } 322 } 323