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