]> code.delx.au - comingnext/blob - comingNext/index.html
Workaround for Nokia firmware bug
[comingnext] / comingNext / index.html
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 <html xmlns="http://www.w3.org/1999/xhtml">
4 <head>
5
6 <title>Coming Next</title>
7
8 <style type="text/css">
9 /* The following classes can be modified by widget settings */
10 .background { color:#ffffff; background-color:#000000 }
11 .backgroundFullscreen { color:#ffffff; background-color:#000000 }
12 .weekDay { }
13 .date { }
14 .today { color:#ff0000 }
15 .tomorrow { color:#0000ff }
16 .time { }
17 .now { color:#ff00ff }
18 .description { }
19 .icon { width:15px; height:15px }
20 .overdue { color:#ffff00 }
21 .calendar1 { background-color:#0757cf }
22 .calendar2 { background-color:#579f37 }
23 .calendar3 { background-color:#ff9f07 }
24 .calendar4 { background-color:#af8fef }
25 .calendar5 { background-color:#57afbf }
26 .calendar6 { background-color:#9fdf57 }
27 </style>
28
29 <script type="text/javascript" src="localizedTextStrings.js" charset="utf-8"></script>
30 <script type="text/javascript" src="../debug.js" charset="utf-8"></script>
31 <script type="text/javascript">
32 // valid types for the config object are 'Int', 'Bool', 'String', 'Enum', 'UID', 'Array'
33 var config = {
34 fontsize: { Type: 'Enum', Default: 'auto', Value: 'auto', ValidValues: ['auto', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28'],},
35 eventsPerWidget: { Type: 'Int', Default: 4, Value: 4,},
36 monthRange: { Type: 'Int', Default: 2, Value: 2,},
37 maxNumberOfEventsOnFullscreen: { Type: 'Int', Default: 30, Value: 30,},
38 includeTodos: { Type: 'Bool', Default: true, Value: true,},
39 useBackgroundImage: { Type: 'Bool', Default: true, Value: true,},
40 backgroundImageLocation: { Type: 'Enum', Default: 'internal', Value: 'internal', ValidValues: ['internal', 'external']},
41 showCombinedDateTime: { Type: 'Bool', Default: false, Value: false,},
42 showLocation: { Type: 'Bool', Default: true, Value: true,},
43 showIcons: { Type: 'Bool', Default: true, Value: true,},
44 showTodayAsText: { Type: 'Bool', Default: true, Value: true,},
45 todayText: { Type: 'String', Default: getLocalizedText('settings.default.todayText'), Value: getLocalizedText('settings.default.todayText'),},
46 tomorrowText: { Type: 'String', Default: getLocalizedText('settings.default.tomorrowText'), Value: getLocalizedText('settings.default.tomorrowText'),},
47 showNowAsText: { Type: 'Bool', Default: true, Value: true,},
48 nowText: { Type: 'String', Default: getLocalizedText('settings.default.nowText'), Value: getLocalizedText('settings.default.nowText'),},
49 markOverdueTodos: { Type: 'Bool', Default: true, Value: true,},
50 overdueText: {Type: 'String', Default: getLocalizedText('settings.default.overdueText'), Value: getLocalizedText('settings.default.overdueText'),},
51 dateSeparator: { Type: 'String', Default: getLocalizedText('settings.default.dateSeparator'), Value: getLocalizedText('settings.default.dateSeparator'),},
52 dateFormat: { Type: 'Enum', Default: 'auto', Value: 'auto', ValidValues: ['auto', 'DDMM', 'MMDD'],},
53 weekDayLength: { Type: 'Int', Default: 2, Value: 2,},
54 updateDataInterval: { Type: 'Int', Default: 5, Value: 5,},
55 calendarApp: { Type: 'UID', Default: 0x10005901, Value: 0x10005901,},
56 showNothingText: { Type: 'Bool', Default: true, Value: true,},
57 nothingText: { Type: 'String', Default: getLocalizedText('settings.default.nothingText'), Value: getLocalizedText('settings.default.nothingText'),},
58 enableDaylightSaving: { Type: 'Bool', Default: true, Value: true,},
59 daylightSavingOffset: { Type: 'Int', Default: 1, Value: 1,},
60 hideWidgetOnCalendarOpen: { Type: 'Bool', Default: false, Value: false,},
61 showCalendarIndicator: { Type: 'Bool', Default: true, Value: true,},
62 excludedCalendars: { Type: 'Array', Default: [], Value: [],},
63 enableLogging: { Type: 'Bool', Default: false, Value: false,},
64 anonymizeLogging: { Type: 'Bool', Default: false, Value: false,},
65 cssStyle_background: { Type: 'String', Default: 'color:#ffffff; background-color:#000000', Value: 'color:#ffffff; background-color:#000000',},
66 cssStyle_backgroundFullscreen: { Type: 'String', Default: 'color:#ffffff; background-color:#000000', Value: 'color:#ffffff; background-color:#000000',},
67 cssStyle_weekDay: { Type: 'String', Default: '', Value: '',},
68 cssStyle_date: { Type: 'String', Default: '', Value: '',},
69 cssStyle_today: { Type: 'String', Default: 'color:#ff0000', Value: 'color:#ff0000',},
70 cssStyle_tomorrow: { Type: 'String', Default: 'color:#0000ff', Value: 'color:#0000ff',},
71 cssStyle_time: { Type: 'String', Default: '', Value: '',},
72 cssStyle_now: { Type: 'String', Default: 'color:#ff00ff', Value: 'color:#ff00ff',},
73 cssStyle_description: { Type: 'String', Default: '', Value: '',},
74 cssStyle_icon: { Type: 'String', Default: '', Value: '',},
75 cssStyle_overdue: { Type: 'String', Default: 'color:#ffff00', Value: 'color:#ffff00',},
76 cssStyle_calendar1: { Type: 'String', Default: 'background-color:#0757cf', Value: 'background-color:#0757cf',},
77 cssStyle_calendar2: { Type: 'String', Default: 'background-color:#579f37', Value: 'background-color:#579f37',},
78 cssStyle_calendar3: { Type: 'String', Default: 'background-color:#ff9f07', Value: 'background-color:#ff9f07',},
79 cssStyle_calendar4: { Type: 'String', Default: 'background-color:#af8fef', Value: 'background-color:#af8fef',},
80 cssStyle_calendar5: { Type: 'String', Default: 'background-color:#57afbf', Value: 'background-color:#57afbf',},
81 cssStyle_calendar6: { Type: 'String', Default: 'background-color:#9fdf57', Value: 'background-color:#9fdf57',},
82 }
83
84
85
86 //-------------------------------------------------------
87 // Nothing of interest from here on...
88 //-------------------------------------------------------
89 var panelNum = 0; // use 1 for second panel
90 var version = "1.37";
91 var versionURL = "http://comingnext.sourceforge.net/version.xml";
92 var calendarService = null;
93 var cacheEntriesHtml = [];
94 var months_translated = [];
95 var weekdays_translated = [];
96 var orientation = '';
97 var now = new Date();
98 var mode = 0; // 0 = homescreen, 1 = fullscreen, 2 = settings, 3 = about, 4 = check for update
99 var reqV = null;
100 var settingsCalEntryId = null;
101 var settingsCache = null;
102 var notificationRequests = new Array();
103 var calendarList = [];
104 var calendarColors = [];
105 var updateTimer = null;
106 var screenRotationTimer = null;
107 var lastUpdateTime = now; // last time we updated the display
108 var lastReloadTime = null; // last time we fetched calendar data
109 var reloadInterval = 6 * 60 * 60 * 1000; // = 6 hours; time interval for reloading calendar data
110 var errorOccured = false;
111 var entryLists = null; // stores all fetched calendar entries until data is refreshed
112 var statupSuccessful = false; // indicates if everything started up wihtout errors. If we detect an error after that, it might just be a temporary problem e.g. by a backup process.
113 var use12hoursTimeFormat = false; // defines how time should be formated: 19:00 or 07:00 pm
114 var timeFormatSeparator = ":"; // format time 19:00 or 19.00 depending on system setting
115 var defaultFontSize = null; // default browser font size will be set by init
116 var displayOffset = 0;
117
118 // vars for daylight saving time
119 var summertime = false; // true, if current date is in summer, false if in winter
120 var daylightSavingDates = new Object(); // caches calculated DST winter and summer time shift dates
121
122 // this is a list of data fields a calendar event can have
123 var entryFields = [
124 "id",
125 "Type",
126 "CalendarName",
127 "Summary",
128 "Location",
129 "Status",
130 "StartTime",
131 "EndTime",
132 "InstanceStartTime",
133 "InstanceEndTime"
134 ];
135
136 function isLeapYear( year ) {
137 if (( year % 4 == 0 && year % 100 != 0 ) || year % 400 == 0 )
138 return true;
139 else
140 return false;
141 }
142
143 function calcLeapYear(year, days)
144 {
145 if (isLeapYear(year))
146 return ++days;
147 else
148 return days;
149 }
150
151 function subToSunday(myDate, year, days, prevMonthDays)
152 {
153 for (i = myDate.getDay(); i > 0 ;i--)
154 days--;
155 days -= prevMonthDays;
156 days = isLeapYear(year) ? --days : days;
157 return days;
158 }
159
160 function isSummertime(curDate)
161 {
162 var summer = false;
163 var winter = false;
164
165 // if we already calculated DST summer and winter time dates for this year, use cached values
166 var dst = daylightSavingDates[curDate.getFullYear()];
167 if (!dst) {
168 var thisYearS = new Date(curDate.getFullYear(), 3, 0, 0, 0, 0 );
169 var thisYearW = new Date(curDate.getFullYear(), 10, 0, 0, 0, 0 );
170 var nextYearS = new Date(curDate.getFullYear() + 1, 3, 0, 0, 0, 0 );
171 var nextYearW = new Date(curDate.getFullYear() + 1, 10, 0, 0, 0, 0 );
172
173 thisYearSDays = nextYearSDays = 90;
174 thisYearWDays = nextYearWDays = 304;
175
176 thisYearSDays = calcLeapYear(curDate.getFullYear(), thisYearSDays);
177 thisYearWDays = calcLeapYear(curDate.getFullYear(), thisYearWDays);
178 nextYearSDays = calcLeapYear(curDate.getFullYear() + 1, nextYearSDays);
179 nextYearWDays = calcLeapYear(curDate.getFullYear() + 1, nextYearWDays);
180
181 thisYearSDays = subToSunday(thisYearS, curDate.getFullYear(), thisYearSDays, 59);
182 thisYearWDays = subToSunday(thisYearW, curDate.getFullYear(), thisYearWDays, 273);
183 nextYearSDays = subToSunday(nextYearS, curDate.getFullYear() + 1, nextYearSDays, 59);
184 nextYearWDays = subToSunday(nextYearW, curDate.getFullYear() + 1, nextYearWDays, 273);
185
186 dst = {
187 Summer: new Date (curDate.getFullYear(), 03-1, thisYearSDays, 2, 0, 0),
188 Winter: new Date (curDate.getFullYear(), 10-1, thisYearWDays, 2, 0, 0),
189 }
190 daylightSavingDates[curDate.getFullYear()] = dst;
191 }
192
193 if (dst.Summer < curDate)
194 summer = true;
195 if (dst.Winter < curDate)
196 winter = true;
197 if (summer && !winter)
198 return true;
199 else
200 return false;
201 }
202
203 function error(message)
204 {
205 console.info('Error: ' + message);
206 document.getElementById("calendarList").innerHTML = 'Error: ' + message;
207 document.getElementById("fullscreenCalendarList").innerHTML = 'Error: ' + message;
208 errorOccured = true;
209 document.onclick = null;
210 }
211
212 function areDatesEqual(date1, date2)
213 {
214 return (date1.getFullYear() == date2.getFullYear() &&
215 date1.getMonth() == date2.getMonth() &&
216 date1.getDate() == date2.getDate());
217 }
218
219 function isTomorrow(date)
220 {
221 // tommorow = now + 1 day
222 // ToDo: some days can be shorter as 24 hours(daylight saving change day)
223 return areDatesEqual(date, new Date (now.getTime() + 24*60*60*1000));
224 }
225
226 function isToday(date)
227 {
228 return areDatesEqual(date, now);
229 }
230
231 function collectLocales()
232 {
233 var tmpyear = 2000 + panelNum;
234 var month = 0;
235
236 if (months_translated.length > 0)
237 return;
238 for (month = 0; month < 12; month++) {
239 var startDate = new Date(tmpyear, month, 15);
240
241 var item = new Object();
242 item.Type = "DayEvent";
243 item.StartTime = startDate;
244 item.Summary = "__temp" + month;
245
246 var criteria = new Object();
247 criteria.Type = "CalendarEntry";
248 criteria.Item = item;
249
250 try {
251 var result = calendarService.IDataSource.Add(criteria);
252 if (result.ErrorCode)
253 throw(result.ErrorMessage);
254 } catch (e) {
255 error("collectLocales: " + e + ', line ' + e.line);
256 }
257 }
258 for (weekday = 0; weekday < 7; weekday++) {
259 var startDate = new Date(2000, 0, 2 + weekday); // date that we know for sure is a sunday
260
261 var item = new Object();
262 item.Type = "DayEvent";
263 item.StartTime = startDate;
264 item.Summary = "__weekday_temp" + weekday;
265
266 var criteria = new Object();
267 criteria.Type = "CalendarEntry";
268 criteria.Item = item;
269
270 try {
271 var result = calendarService.IDataSource.Add(criteria);
272 if (result.ErrorCode)
273 throw(result.ErrorMessage);
274 } catch (e) {
275 error("collectLocales: " + e + ', line ' + e.line);
276 }
277 }
278 try {
279 var startTime = new Date(tmpyear,0,1);
280 var endTime = new Date(tmpyear,11,31);
281 var listFiltering = {
282 Type:'CalendarEntry',
283 Filter:{
284 StartRange: startTime,
285 EndRange: endTime,
286 SearchText: '__temp',
287 Type: 'DayEvent'
288 }
289 }
290 var result = calendarService.IDataSource.GetList(listFiltering);
291 if (result.ErrorCode)
292 throw(result.ErrorMessage);
293 var list = result.ReturnValue;
294 } catch(e) {
295 error("collectLocales: " + e + ', line ' + e.line);
296 return;
297 }
298 var ids = new Array();
299 try {
300 var entry;
301 var counter = 0;
302 var dateArr = [];
303
304 while (list && (entry = list.getNext()) != undefined) {
305 dateArr = (entry.StartTime + '').replace(/,/g,'').replace(/\./g,':').replace(/ /g,' ').split(' ');
306 var day = dateArr[1];
307 var month = dateArr[2];
308 var year = dateArr[3];
309
310 // make sure month is set properly
311 if (isNaN(parseInt(day))) {
312 var tmp = day;
313 day = month;
314 month = tmp;
315 } else if (isNaN(parseInt(year))) {
316 var tmp = year;
317 year = month;
318 month = tmp;
319 }
320
321 log(entry.StartTime + ' -> ' + month + ' ' + counter);
322 ids[counter] = entry.id;
323 months_translated[month] = counter + 1;
324 counter++;
325 }
326 } catch(e) {
327 error("collectLocales: " + e + ', line ' + e.line);
328 return;
329 }
330 try {
331 var startTime = new Date(2000,0,2);
332 var endTime = new Date(2000,0,9);
333 var listFiltering = {
334 Type:'CalendarEntry',
335 Filter:{
336 StartRange: startTime,
337 EndRange: endTime,
338 SearchText: '__weekday_temp',
339 Type: 'DayEvent'
340 }
341 }
342 var result = calendarService.IDataSource.GetList(listFiltering);
343 if (result.ErrorCode)
344 throw(result.ErrorMessage);
345 var weekdaylist = result.ReturnValue;
346 } catch(e) {
347 error("collectLocales: " + e + ', line ' + e.line);
348 return;
349 }
350 try {
351 var entry;
352 var counter2 = 0;
353 var curWeekday = "";
354
355 while (weekdaylist && (entry = weekdaylist.getNext()) != undefined) {
356 detectTimeFormat(entry.StartTime + '');
357 curWeekday = (entry.StartTime + '').split(',')[0];
358 log(entry.StartTime + ' -> ' + curWeekday + ' ' + counter2);
359 ids[counter + counter2] = entry.id;
360 weekdays_translated[counter2] = curWeekday;
361 counter2++;
362 }
363 } catch(e) {
364 error("collectLocales: " + e + ', line ' + e.line);
365 return;
366 }
367 log(ids);
368 try {
369 var criteria = new Object();
370 criteria.Type = "CalendarEntry";
371 criteria.Data = {
372 IdList: ids
373 }
374
375 var result = calendarService.IDataSource.Delete(criteria);
376 if (result.ErrorCode)
377 throw(result.ErrorMessage);
378 } catch(e) {
379 error('deleting temp calendar entries:' + e + ', line ' + e.line);
380 return;
381 }
382 }
383
384 function stringEndsWith(str, suffix)
385 {
386 return str.indexOf(suffix, str.length - suffix.length) !== -1;
387 }
388
389 // detects the system's current time format by parsing a native calendar timestamp (this is the only reliable formating across all devices and firmwares)
390 function detectTimeFormat(localeTimeString)
391 {
392 localeTimeString = localeTimeString.toLowerCase();
393 use12hoursTimeFormat = stringEndsWith(localeTimeString, "am") || stringEndsWith(localeTimeString, "pm");
394 timeFormatSeparator = localeTimeString.indexOf(":") != -1 ? ":" : ".";
395 }
396
397 function requestNotification()
398 {
399 var criteria = new Object();
400 criteria.Type = "CalendarEntry";
401 criteria.Filter = new Object();
402 for(var i=0; i < calendarList.length; i++) {
403 criteria.Filter.CalendarName = calendarList[i];
404 try {
405 var notificationRequest = calendarService.IDataSource.RequestNotification(criteria, callback);
406 if (notificationRequest.ErrorCode)
407 error('requestNotification failed with error code ' + notificationRequest.ErrorCode);
408 notificationRequests.push(notificationRequest);
409 } catch (e) {
410 error("requestNotification: " + e + ', line ' + e.line);
411 }
412 }
413
414 var criteria2 = new Object();
415 criteria2.Type = "CalendarEntry";
416 criteria2.Filter = new Object();
417 criteria2.Filter.LocalIdList = new Array();
418 criteria2.Filter.LocalIdList[0] = settingsCalEntryId;
419 try {
420 var notificationRequest = calendarService.IDataSource.RequestNotification(criteria2, settingsCallback);
421 if (notificationRequest.ErrorCode)
422 error('requestNotification failed with error code ' + notificationRequest.ErrorCode);
423 notificationRequests.push(notificationRequest);
424 } catch (e) {
425 error("requestNotification: " + e + ', line ' + e.line);
426 }
427 }
428
429 function cancelNotification()
430 {
431 for(var i=0; i < notificationRequests.length; i++) {
432 try {
433 var result = calendarService.IDataSource.Cancel(notificationRequests[i]);
434 if (result.ErrorCode)
435 error('cancelNotification failed with error code ' + result.ErrorCode);
436 } catch (e) {
437 error("cancelNotification: " + e + ', line ' + e.line);
438 }
439 }
440 }
441
442 function callback(transId, eventCode, result)
443 {
444 log("callback(): panelNum: " + panelNum + " transId: " + transId + " eventCode: " + eventCode + " result.ErrorCode: " + result.ErrorCode);
445 lastReloadTime = null; // force calendar data reload on next update
446 updateData();
447 }
448
449 function settingsCallback(transId, eventCode, result)
450 {
451 log("settingsCallback(): panelNum: " + panelNum + " transId: " + transId + " eventCode: " + eventCode + " result.ErrorCode: " + result.ErrorCode);
452 loadSettings();
453 }
454
455 function parseDate(dateString)
456 {
457 /*
458 Dates my look very differently. Also keep in mind that the names are localized!!! These are the possibilities depending on the users date format setting:
459 Wednesday, 26 August, 2009 24:00:00
460 Wednesday, 26 August, 2009 12:00:00 am
461 Wednesday, August 26, 2009 12:00:00 am
462 Wednesday, 2009 August, 26 12:00:00 am
463 Wednesday, 2009 August, 28 8.00.00 pm
464 Wednesday, 2009 August, 28 08:00:00 PM
465 */
466 var result = null;
467
468 if (dateString == "" || dateString == null || dateString == undefined)
469 return result;
470 if (dateString instanceof Date) {
471 // we already have a date object, no need to parse string here
472 result = dateString;
473 }
474 else {
475 var dateArr = (dateString + '').replace(/,/g, '').replace(/\./g, ':').replace(/ /g, ' ').split(' ');
476 if (dateArr.length != 5 && dateArr.length != 6)
477 return null;
478
479 // parse date
480 var weekDay = dateArr[0];
481 var day = dateArr[1];
482 var month = dateArr[2];
483 var year = dateArr[3];
484 // make sure month is set properly
485 if (isNaN(parseInt(day))) {
486 var tmp = day;
487 day = month;
488 month = tmp;
489 }
490 else
491 if (isNaN(parseInt(year))) {
492 var tmp = year;
493 year = month;
494 month = tmp;
495 }
496 // make sure day and year are set properly
497 if (Number(day) > Number(year)) {
498 var tmp = year;
499 year = day;
500 day = tmp;
501 }
502 month = months_translated[month];
503
504 // parse time
505 var timeArr = dateArr[4].split(':');
506 if (timeArr.length != 3)
507 return null;
508 var hours = Number(timeArr[0]);
509 var minutes = Number(timeArr[1]);
510 var seconds = Number(timeArr[2]);
511 if (dateArr.length == 6 && dateArr[5].toLowerCase() == 'pm' && hours < 12)
512 hours += 12;
513 if (dateArr.length == 6 && dateArr[5].toLowerCase() == 'am' && hours == 12)
514 hours = 0;
515
516 result = new Date(year, month - 1, day, hours, minutes, seconds);
517 }
518
519 // take care of daylight saving time
520 if (config['enableDaylightSaving'].Value) {
521
522 // determine if date is in summer or winter time
523 var dateSummerTime = isSummertime(result);
524
525 // work around bug in Nokias calendar api resulting in dates within a different DST to be off by 1 hour
526 if (summertime && !dateSummerTime) {
527 result = new Date(result.getTime() - 1000 * 60 * 60 * config['daylightSavingOffset'].Value); // -1 hour
528 log('parseDate(): fixing time -1h: ' + result);
529 }
530 else if (!summertime && dateSummerTime) {
531 result = new Date(result.getTime() + 1000 * 60 * 60 * config['daylightSavingOffset'].Value); // +1 hour
532 log('parseDate(): fixing time +1h: ' + result);
533 }
534 }
535 if (displayOffset != 0) {
536 result = new Date(result.getTime() + displayOffset);
537 }
538
539 return result;
540 }
541
542 function getWeekdayLocalized(date) {
543 var localizedString = date.toLocaleDateString();
544 if (localizedString.indexOf(",") == -1) {
545 return weekdays_translated[date.getDay()];
546 } else
547 return localizedString.split(',')[0];
548 }
549
550 // returns a short date as string ("31.12" or "12.31") based on the format string which should look like "Wednesday, 26 August, 2009 12:00:00 am"
551 function formatDate(date, format)
552 {
553 var day = date.getDate().toString();
554 var month = (date.getMonth() + 1).toString();
555 while (day.length < 2) { day = '0' + day; }
556 while (month.length < 2) { month = '0' + month; }
557
558 if (config['showTodayAsText'].Value && isToday(date))
559 return '<span class="today">' + config['todayText'].Value + '</span>';
560 if (config['showTodayAsText'].Value && isTomorrow(date))
561 return '<span class="tomorrow">' + config['tomorrowText'].Value + '</span>';
562
563 if (format instanceof Date) {
564 // we don't know how to format this
565 if (config['dateFormat'].Value == 'auto' || config['dateFormat'].Value == 'DDMM')
566 return day + config['dateSeparator'].Value + month;
567 else
568 return month + config['dateSeparator'].Value + day;
569 }
570 var dateArr = format.replace(/,/g,'').replace(/\./g,':').replace(/ /g,' ').split(' ');
571 if (dateArr.length != 5 && dateArr.length != 6) {
572 // we don't know how to format this
573 if (config['dateFormat'].Value == 'auto' || config['dateFormat'].Value == 'DDMM')
574 return day + config['dateSeparator'].Value + month;
575 else
576 return month + config['dateSeparator'].Value + day;
577 }
578
579 var dayFirst = true;
580 if (config['dateFormat'].Value == 'MMDD')
581 dayFirst = false;
582 else if (config['dateFormat'].Value == 'DDMM')
583 dayFirst = true;
584 else {
585 // config['dateFormat'].Value == 'auto', try to detect system setting
586 // parse date
587 var day_ = dateArr[1];
588 var month_ = dateArr[2];
589 var year_ = dateArr[3];
590 // make sure month is set properly
591 if (isNaN(parseInt(day_))) {
592 var tmp = day_;
593 day_ = month_;
594 month_ = tmp;
595 dayFirst = false;
596 } else if (isNaN(parseInt(year_))) {
597 var tmp = year_;
598 year_ = month_;
599 month_ = tmp;
600 dayFirst = true;
601 }
602 // make sure day and year are set properly
603 if (Number(day_) > Number(year_))
604 dayFirst = false;
605 }
606
607 if (dayFirst)
608 return day + config['dateSeparator'].Value + month;
609 else
610 return month + config['dateSeparator'].Value + day;
611 }
612
613 function formatTime(date)
614 {
615 // date is a Date() object
616 var hour = date.getHours();
617 var minute = date.getMinutes();
618
619 // don't use Date().toLocaleTimeString() as it is utterly broken on newer firmwares
620 if (use12hoursTimeFormat) {
621 var ap = "AM";
622 if (hour > 11)
623 ap = "PM";
624 if (hour > 12)
625 hour = hour - 12;
626 if (hour == 0)
627 hour = 12;
628 if (hour < 10)
629 hour = "0" + hour;
630 if (minute < 10)
631 minute = "0" + minute;
632 time = hour + timeFormatSeparator + minute + " " + ap;
633 }
634 else {
635 if (hour < 10)
636 hour = "0" + hour;
637 if (minute < 10)
638 minute = "0" + minute;
639 time = hour + timeFormatSeparator + minute;
640 }
641
642 if (config['showNowAsText'].Value && date.getTime() == now.getTime())
643 time = '<span class="now">' + config['nowText'].Value + '</span>';
644 log("formatTime(): " + time + ", use12hoursTimeFormat=" + use12hoursTimeFormat + ", timeFormatSeparator=" + timeFormatSeparator + ", date.toLocateTimeString(): " + date.toLocaleTimeString());
645 return time;
646 }
647
648 function updateData()
649 {
650 log('updateData()');
651 if (errorOccured) {
652 return;
653 }
654
655 // check if we got additional or less calendars since our last update
656 var newCalendarList = listCalendars();
657 if (newCalendarList == null) {
658 // Something went wrong fetching the calendars list.
659 // This usually happens when a backup is being made.
660 // Retry the next time updateData() is called by
661 // resetting errorOccured
662 log('updateData(): listCalendars() failed, trying again later...');
663 cacheEntriesHtml = ''; // make sure we replace the currently shown error message on the next update
664 errorOccured = false;
665 return;
666 }
667 if (newCalendarList.length != calendarList.length) {
668 calendarList = newCalendarList;
669 updateCalendarColors();
670 cancelNotification();
671 requestNotification();
672 lastReloadTime = null; // force calendar data reload on this update
673 }
674
675 now = new Date();
676
677 // only reload calendar data every 6 hours, visual updates occure more often
678 if (!lastReloadTime || now.getTime() - lastReloadTime.getTime() > reloadInterval) {
679 log('updateData(): reloading calendar data');
680 try {
681 // meetings have time
682 // note: anniveraries have a start time of 12:00am. So since we want to include them, we have to query the whole day and check if events have passed later
683 summertime = isSummertime(now); // cache summer time info for today
684 var meetingList = [];
685 for(var i=0; i < calendarList.length; i++) {
686 // ignore excluded calendars
687 if (config['excludedCalendars'].Value.indexOf(calendarList[i]) != -1)
688 continue;
689 var meetingListFiltering = {
690 Type:'CalendarEntry',
691 Filter:{
692 CalendarName: calendarList[i],
693 StartRange: (new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0)),
694 EndRange: (new Date(now.getFullYear(), now.getMonth() + config['monthRange'].Value, now.getDate(), 0, 0, 0))
695 }
696 }
697 var meetingResult = calendarService.IDataSource.GetList(meetingListFiltering);
698 if (meetingResult.ErrorCode != 0)
699 throw("Error fetching calendar data: " + meetingResult.ErrorCode + ': ' + meetingResult.ErrorMessage);
700 var list = meetingResult.ReturnValue;
701 meetingList = meetingList.concat(listToArray(list, calendarList[i]));
702 }
703 log("updateData(): meetingList.sort()");
704 meetingList.sort(sortCalendarEntries);
705
706 // todos don't, they start on 00:00 hrs., but should be visible anyway
707 // this will generate a list of passed todos. We have to check if they have been marked as "done" yet
708 if (config['includeTodos'].Value) {
709 var todayTodoList = [];
710 for(var i=0; i < calendarList.length; i++) {
711 // ignore excluded calendars
712 if (config['excludedCalendars'].Value.indexOf(calendarList[i]) != -1)
713 continue;
714 var todayTodoListFiltering = {
715 Type:'CalendarEntry',
716 Filter:{
717 CalendarName: calendarList[i],
718 Type: 'ToDo',
719 StartRange: (new Date(now.getFullYear() - 1, now.getMonth(), now.getDate(), 0, 0, 0)),
720 EndRange: (new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 1))
721 }
722 }
723 var todayTodoResult = calendarService.IDataSource.GetList(todayTodoListFiltering);
724 var list = todayTodoResult.ReturnValue;
725 todayTodoList = todayTodoList.concat(listToArray(list, calendarList[i]));
726 }
727 log("updateData(): todayTodoList.sort()");
728 todayTodoList.sort(sortCalendarEntries);
729 entryLists = [todayTodoList, meetingList];
730 } else {
731 entryLists = [meetingList];
732 }
733 lastReloadTime = new Date();
734 } catch(e) {
735 error('loading Calendar items list:' + e + ', line ' + e.line);
736 return;
737 }
738 }
739
740 try {
741 var entry;
742 var counter = 0;
743 var entryDate = '';
744 var dateArr = [];
745 var fontsize = getDefaultFontSize()[1] + 'px';
746 var lineheight = fontsize;
747 if (mode == 0) {
748 if (config['fontsize'].Value == config['fontsize'].ValidValues[0]) {
749 fontsize = parseInt(72 / config['eventsPerWidget'].Value) + 'px';
750 lineheight = parseInt(72 / config['eventsPerWidget'].Value) + 'px';
751 }
752 }
753 if (config['fontsize'].Value != config['fontsize'].ValidValues[0]) {
754 fontsize = config['fontsize'].Value + 'px';
755 lineheight = fontsize;
756 }
757 changeCssClass('.icon', config['cssStyle_icon'].Value + '; width:' + fontsize + '; height:' + fontsize + ';');
758 var entriesHtml = '<table style="font-size:' + fontsize + '; line-height:' + lineheight + ';">';
759 if (mode == 0)
760 entriesHtml = '<table width="307" height="82"><tr><td>' + entriesHtml; // this is needed to center the actual content vertically
761 var eventIds = [];
762 var max;
763 if (mode == 0)
764 max = (panelNum + 1) * config['eventsPerWidget'].Value;
765 else
766 max = config["maxNumberOfEventsOnFullscreen"].Value; // we can display a lot more events in fullscreen mode
767
768 if (config['enableLogging'].Value) {
769 var listinfo = "";
770 for (var i=0; i < entryLists.length; i++) {
771 listinfo = listinfo + " " + entryLists[i].length;
772 var entrieslist = "";
773 for (var j=0; j < entryLists[i].length; j++) {
774 entrieslist += entryLists[i][j].Summary + ", ";
775 }
776 log("updateData(): entrieslist: " + entrieslist);
777 }
778 log("updateData(): inner loop, " + entryLists.length + " lists, [" + listinfo + "] entries");
779 }
780
781 // the first outer loop iteration is for passed ToDos, the second loop is for all upcomming events (may also include ToDos)
782 for (var i=0; counter < max && i < entryLists.length; i++) {
783 for (var j=0; (counter < max) && (j < entryLists[i].length); j++) {
784 entry = entryLists[i][j];
785 counter++;
786
787 // output event info for debugging
788 var entryInfo = "event: ";
789 for(var k=0; k < entryFields.length; ++k) {
790 if (entry[entryFields[k]] != undefined) {
791 entryInfo += entryFields[k] + "=" + entry[entryFields[k]] + ",";
792 }
793 }
794 log(entryInfo);
795
796 // we don't want ToDos when includeTodos == false or when they are completed
797 if (entry.Type == 'ToDo' && (entry.Status == "TodoCompleted" || !config['includeTodos'].Value)) {
798 log('skipping ' + entry.id );
799 counter--;
800 continue;
801 }
802
803 // make sure that we don't include an event twice (useful for ToDos that might come up twice)
804 if (eventIds[entry.id] == 1 && entry.Type == 'ToDo') {
805 log('skipped (already included) ' + entry.id);
806 counter--;
807 continue;
808 } else
809 eventIds[entry.id] = 1;
810
811 // summary can be undefined!
812 var Summary = ((entry.Summary == null) ? '' : entry.Summary);
813 if (entry.Location != '' && entry.Location != undefined && config['showLocation'].Value)
814 Summary += ', ' + entry.Location;
815
816 // fix by yves: determine start and end dates/times
817 entryStartTime = ((entry.InstanceStartTime == null) ? entry.StartTime : entry.InstanceStartTime);
818 entryEndTime = ((entry.InstanceEndTime == null) ? entry.EndTime : entry.InstanceEndTime);
819
820 // there can be ToDos that have no date at all!
821 if (entry.Type == 'ToDo' && entry.EndTime == null)
822 entryDate = ""; // this will cause parseDate(entryDate) to return null;
823 else
824 entryDate = ((entry.Type == 'ToDo') ? entryEndTime : entryStartTime); // ToDo's use their EndTime, the rest use StartTime
825
826 // Convert date/time string to Date object
827 var date = parseDate(entryDate);
828 log('date: ' + date);
829 var endDate = ((entryEndTime == null) ? null : parseDate(entryEndTime));
830 log('endDate: ' + endDate);
831
832 // check if Meeting is actually a DayEvent. Bug introduced by "Anna" updates to various Symbian^3 devices.
833 // Note that this workaround is not 100% save! It might missinterpret some meetings as dayevents of starting and ending on 00:00
834 if (entry.Type == 'Meeting' && date.getHours() == 0 && date.getMinutes() == 0 &&
835 endDate != null && endDate.getHours() == 0 && endDate.getMinutes() == 0) {
836 log('fixing event type: changed from "Meeting" to "DayEvent".');
837 entry.Type = 'DayEvent';
838 }
839
840 // check if meeting event has already passed
841 if (entry.Type == 'Meeting') {
842 var compareTime = ((endDate == null) ? date.getTime() : endDate.getTime());
843 if (now.getTime() > compareTime) {
844 log('skipping Meeting (already passed) ' + entry.id);
845 counter--;
846 eventIds[entry.id] = 0;
847 continue;
848 }
849 }
850
851 // check if anniversary passed (not sure why they are in the list, the query was only for today - nokia?)
852 if (entry.Type == 'Anniversary') {
853 var tmp = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0,0,0);
854 if (date.getTime() < tmp.getTime()) {
855 log('skipping Anniversary (already passed) ' + entry.id);
856 counter--;
857 eventIds[entry.id] = 0;
858 continue;
859 }
860 }
861
862 // fix DayEvents end time. End times are off by 1 Second. It's possible that the event has already passed
863 if (entry.Type == 'DayEvent' && endDate != null) {
864 endDate.setMinutes(endDate.getMinutes() - 1);
865 log('fixing DayEvent endDate: ' + endDate);
866 if (now.getTime() > endDate.getTime()) {
867 log('event already passed ' + entry.id);
868 counter--;
869 eventIds[entry.id] = 0;
870 continue;
871 }
872 }
873
874 // check if the event is currently taking place
875 if (entryStartTime != null && entryEndTime != null && date != null && endDate != null) {
876 // check if we are between start and endtime
877 if ((date.getTime() < now.getTime()) && (now.getTime() < endDate.getTime())) {
878 date = now; // change appointment date/time to now
879 log('event is currently taking place: ' + date);
880 }
881 }
882
883 // skip events for the first panel in case this is the second one and we're not in fullscreen mode
884 if (mode == 0 && panelNum > 0 && counter < panelNum * config['eventsPerWidget'].Value + 1) {
885 log('skipping (already in first widget) ' + entry.id);
886 continue;
887 }
888
889 // mark overdue todos
890 var overdue = false;
891 if (entry.Type == 'ToDo' && date != null) {
892 var tmp1 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0,0,0);
893 var tmp2 = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0,0,0);
894 if (tmp1.getTime() < tmp2.getTime()) {
895 overdue = true;
896 }
897 }
898
899 // generate html output
900 entriesHtml += '<tr>';
901 if (config['showCalendarIndicator'].Value && calendarList.length - config['excludedCalendars'].Value.length > 1) {
902 entriesHtml += '<td><div class="calendar' + calendarColors[entry.CalendarName] + '" style="height:' + (lineheight.split("px")[0] - 1) + 'px; width:4px;"></div></td>';
903 }
904 if (config['showIcons'].Value)
905 entriesHtml += '<td><img class="icon" align="top" src="' + entry.Type + '.png" /></td>';
906 else
907 entriesHtml += '<td style="padding:0px;"></td>';
908 if(date == null) {
909 // some languages have very strange locale date formats, can't parse all those. Also some todos don't have dates at all.
910 entriesHtml += '<td colspan="4"><span class="date">' + entryDate + '</span> ';
911 } else {
912 var weekDay = getWeekdayLocalized(date).substr(0,config['weekDayLength'].Value);
913 log('date.toLocaleDateString(): ' + date.toLocaleDateString());
914 log('weekDay: ' + weekDay);
915 var time = formatTime(date);
916 var dateStr = formatDate(date, entryDate);
917 if (entry.Type == 'ToDo' && overdue && config['markOverdueTodos'].Value) {
918 dateStr = '<span class="overdue">' + config['overdueText'].Value + '</span>';
919 entriesHtml += '<td colspan="4" width="1px"><span class="date">' + dateStr + '</span> ';
920 } else if (entry.Type == 'ToDo' || entry.Type == 'Anniversary' || entry.Type == 'DayEvent' || entry.Type == 'Reminder') {
921 if ((isToday(date) || isTomorrow(date)) && config['showTodayAsText'].Value) // show weekday if the date string is not text. looks odd otherwise
922 entriesHtml += '<td colspan="4" width="1px"><span class="date">' + dateStr + '</span> ';
923 else
924 entriesHtml += '<td class="weekDay" width="1px">' + weekDay + '</td><td width="1px" class="date">' + dateStr + '</td><td colspan="2">';
925 } else if (entry.Type == 'Meeting') {
926 if (config['showCombinedDateTime'].Value) {
927 if (isToday(date))
928 entriesHtml += '<td width="1px" colspan="4"><span class="today">' + time + '</span> ';
929 else if (isTomorrow(date))
930 entriesHtml += '<td width="1px" colspan="4"><span class="tomorrow">' + dateStr + '</span> <span class="time">' + time + '</span> ';
931 else
932 entriesHtml += '<td width="1px" class="weekDay">' + weekDay + '</td><td width="1px" class="date">' + dateStr + '</td><td colspan="2">';
933 } else {
934 if ((isToday(date) || isTomorrow(date)) && config['showTodayAsText'].Value)
935 entriesHtml += '<td colspan="4" width="1px"><span class="today">' + dateStr + '</span> <span class="time">' + time + '</span> ';
936 else
937 entriesHtml += '<td width="1px" class="weekDay">' + weekDay + '</td><td width="1px" class="date">' + dateStr + '</td><td width="1px" class="time">' + time + '</td><td>';
938 }
939 }
940 }
941 entriesHtml += '<span class="description">' + Summary + '</span></td></tr>';
942 }
943 }
944 entriesHtml += '</table>';
945 if (mode == 0)
946 entriesHtml = entriesHtml + '</td></tr></table>';
947 if (config['showNothingText'].Value && entriesHtml == '<table></table>') {
948 var text = config['nothingText'].Value.replace(/%d/, config['monthRange'].Value);
949 entriesHtml = '<div style="width:295px; height:75px; text-align:center; line-height:75px; overflow:visible;">' + text + '</div>';
950 }
951 log("output: " + entriesHtml);
952 if (cacheEntriesHtml != entriesHtml) {
953 if (mode == 0)
954 document.getElementById('calendarList').innerHTML = entriesHtml;
955 else
956 document.getElementById('fullscreenCalendarList').innerHTML = entriesHtml;
957 cacheEntriesHtml = entriesHtml;
958 }
959
960 lastUpdateTime = new Date();
961 } catch(e) {
962 error('displaying list:' + e + ', line ' + e.line);
963 return;
964 }
965 }
966
967 // called by handleOnShow() and onResize events
968 function updateScreen()
969 {
970 log('updateScreen(): mode=' + mode + ', window.innerHeight=' + window.innerHeight);
971
972 // check if opening fullscreen
973
974 // Note: according to Nokia's documentation, an innerHeight of >91 is an indicator for fullscreen view.
975 // However a bug in E6's firmware causes different window widths and heights (disabled compatibility scaling).
976 // So far, values of 104 and 115 for window.innerHeight were reported, we use a safty margin here and check
977 // for 150 instead.
978 if( window.innerHeight > 150 && mode == 0) {
979 mode = 1;
980 cacheEntriesHtml = '';
981 document.getElementById('body').style.backgroundImage = "";
982 showFullscreen();
983 }
984 else if (window.innerHeight <= 150 && mode != 0) {
985 mode = 0;
986 cacheEntriesHtml = '';
987 showHomescreen();
988 }
989
990 if (mode == 0)
991 updateHomescreen(); // check for screen rotation
992 else if (mode == 1)
993 updateFullscreen();
994 }
995
996 function handleOnShow()
997 {
998 updateScreen();
999
1000 var time = new Date();
1001 if (time.getTime() - lastUpdateTime.getTime() > config['updateDataInterval'].Value * 60 * 1000) {
1002 log('updateScreen(): force updateData() because last update was too long ago (' + (time.getTime() - lastUpdateTime.getTime()) / 1000 + 's)');
1003 clearUpdateTimer();
1004 updateData();
1005 setUpdateTimer(); // reinitialize update timer
1006 }
1007 }
1008
1009 function launchCalendar()
1010 {
1011 try {
1012 widget.openApplication(config['calendarApp'].Value, "");
1013 if (config['hideWidgetOnCalendarOpen'].Value)
1014 window.close();
1015 } catch(e) {
1016 error('starting Calendar App');
1017 return;
1018 }
1019 }
1020
1021 function init()
1022 {
1023 log('New widget instance starting up...');
1024
1025 try {
1026 // call calendar service
1027 if (device != "undefined")
1028 calendarService = device.getServiceObject("Service.Calendar", "IDataSource");
1029 else
1030 throw('device object does not exist');
1031 } catch(e) {
1032 error('loading Calendar service: ' + e + ', line ' + e.line + '<br /><a onclick="widget.openURL(\'http://comingnext.sf.net/help\'); return false;" href="http://comingnext.sf.net/help">' + getLocalizedText('menu.help') + '</a>');
1033 //return;
1034 }
1035
1036 calendarList = listCalendars();
1037 loadSettings();
1038 updateCalendarColors();
1039 collectLocales();
1040 //updateData();
1041 requestNotification();
1042 document.getElementById("settingsTitle").innerHTML = getLocalizedText('menu.settings');
1043 setUpdateTimer();
1044 if (window.innerHeight > 91) {
1045 mode = 0; // we're starting fullscreen, we set mode to homescreen in order to let updateScreen() do all the work for us
1046 }
1047 else {
1048 mode = 1;
1049 }
1050 checkForOffByOneBug();
1051 log("init(): updateScreen()");
1052 updateScreen();
1053 if (config['useBackgroundImage'].Value)
1054 // check for screen rotation every 1 secs
1055 screenRotationTimer = window.setInterval('checkOrientation()', 1000 * 1);
1056
1057 // call updateScreen() when widget changes from background to forground
1058 window.widget.onshow = handleOnShow;
1059
1060 log("init(): finished...");
1061 if (!errorOccured)
1062 statupSuccessful = true;
1063 }
1064
1065 function checkForOffByOneBug() {
1066 var tz = new Date().getTimezoneOffset();
1067 if (tz < 0) {
1068 tz = -tz;
1069 }
1070 tz = tz / 60;
1071 if (tz > 12) {
1072 displayOffset = 24*3600*1000;
1073 }
1074 }
1075
1076 function checkOrientation()
1077 {
1078 //updateScreen();
1079 if (mode == 0)
1080 updateHomescreen(); // check for screen rotation
1081 }
1082
1083 function setUpdateTimer()
1084 {
1085 updateTimer = window.setInterval('updateTimerCallback()', 1000 * 60 * config['updateDataInterval'].Value);
1086 }
1087
1088 function clearUpdateTimer()
1089 {
1090 window.clearInterval(updateTimer);
1091 }
1092
1093 function updateTimerCallback()
1094 {
1095 log("updateTimerCallback()");
1096 updateData();
1097 }
1098
1099 function createMenu()
1100 {
1101 window.menu.setLeftSoftkeyLabel("",null);
1102 window.menu.setRightSoftkeyLabel("",null);
1103 var id = 0;
1104 var menuSettings = new MenuItem(getLocalizedText('menu.settings'), id++);
1105 var menuCallApp = new MenuItem(getLocalizedText('menu.openCalendarApp'), id++);
1106 var menuHelp = new MenuItem(getLocalizedText('menu.help'), id++);
1107 var menuUpdate = new MenuItem(getLocalizedText('menu.update'), id++);
1108 var menuAbout = new MenuItem(getLocalizedText('menu.about'), id++);
1109 menuSettings.onSelect = showSettings;
1110 menuAbout.onSelect = showAbout;
1111 menuCallApp.onSelect = launchCalendar;
1112 menuUpdate.onSelect = showUpdate;
1113 menuHelp.onSelect = showHelp;
1114 window.menu.clear();
1115 window.menu.append(menuCallApp);
1116 window.menu.append(menuSettings);
1117 window.menu.append(menuHelp);
1118 window.menu.append(menuUpdate);
1119 window.menu.append(menuAbout);
1120 }
1121
1122 function showSettings()
1123 {
1124 mode = 2;
1125 hideViews();
1126 document.getElementById("settingsView").style.display = "block";
1127 document.onclick = null;
1128
1129 window.menu.setLeftSoftkeyLabel(getLocalizedText('settings.save'), function()
1130 {
1131 for (var key in config) {
1132 if (config[key].Type == 'String')
1133 config[key].Value = document.forms[0].elements["settings." + key].value;
1134 else if (config[key].Type == 'Int') {
1135 config[key].Value = parseInt(document.forms[0].elements["settings." + key].value);
1136 if (config[key].Value < 0 || isNaN(config[key].Value))
1137 config[key].Value = config[key].Default;
1138 }
1139 else if (config[key].Type == 'Bool')
1140 config[key].Value = document.forms[0].elements["settings." + key].checked;
1141 else if (config[key].Type == 'UID') {
1142 config[key].Value = parseInt(document.forms[0].elements["settings." + key].value);
1143 if (isNaN(config[key].Value))
1144 config[key].Value = config[key].Default;
1145 }
1146 else if (config[key].Type == 'Enum') {
1147 config[key].Value = document.forms[0].elements["settings." + key].value;
1148 if (config[key].ValidValues.indexOf(config[key].Value) == -1)
1149 config[key].Value = config[key].Default;
1150 }
1151 else if (config[key].Type == 'Array') {
1152 if (key == 'excludedCalendars') {
1153 config[key].Value = new Array();
1154 for(var i=0; i < calendarList.length; i++) {
1155 var element = document.forms[0].elements["settings." + key + "." + calendarList[i]];
1156 if (element != null && element.checked == false)
1157 config[key].Value.push(calendarList[i]);
1158 }
1159 }
1160 }
1161 }
1162
1163 updateCssClasses();
1164
1165 saveSettings();
1166
1167 mode = 1;
1168 showFullscreen();
1169 });
1170 window.menu.setRightSoftkeyLabel(getLocalizedText('settings.cancel'), function()
1171 {
1172 mode = 1;
1173 showFullscreen();
1174 });
1175
1176 var settingsHtml = '<form>';
1177 for (var key in config) {
1178 if (config[key].Type == 'String') {
1179 var prefix = "";
1180 if (key.substring(0,9) == "cssStyle_")
1181 prefix = getLocalizedText('settings.cssStyle_prefix');
1182 settingsHtml += '<table><tr><td>' + prefix + getLocalizedText('settings.name.' + key) + '<br /><input class="textInput" name="settings.' + key + '" type="text" value="' + config[key].Value + '" /></td>' + printHintBox(getLocalizedText('settings.info.' + key)) + '<hr />';
1183 }
1184 else if (config[key].Type == 'Int')
1185 settingsHtml += '<table><tr><td>' + getLocalizedText('settings.name.' + key) + '<br /><input class="textInput" name="settings.' + key + '" type="text" value="' + config[key].Value + '" /></td>' + printHintBox(getLocalizedText('settings.info.' + key)) + '<hr />';
1186 else if (config[key].Type == 'Bool')
1187 settingsHtml += '<table><tr><td>' + getLocalizedText('settings.name.' + key) + '<br /><input name="settings.' + key + '" type="checkbox" value="true" ' + (config[key].Value ? 'checked="checked"' : '') + '/></td>' + printHintBox(getLocalizedText('settings.info.' + key)) + '<hr />';
1188 else if (config[key].Type == 'UID')
1189 settingsHtml += '<table><tr><td>' + getLocalizedText('settings.name.' + key) + '<br /><input class="textInput" name="settings.' + key + '" type="text" value="0x' + config[key].Value.toString(16) + '" /></td>' + printHintBox(getLocalizedText('settings.info.' + key)) + '<hr />';
1190 else if (config[key].Type == 'Enum') {
1191 settingsHtml += '<table><tr><td>' + getLocalizedText('settings.name.' + key) + '<br /><select name="settings.' + key + '" size="1">';
1192 for(var i = 0; i < config[key].ValidValues.length; i++) {
1193 var text = getLocalizedText('settings.validValues.' + key + '.' + config[key].ValidValues[i]);
1194 if (text.indexOf('ERROR') == 0)
1195 text = config[key].ValidValues[i];
1196 settingsHtml += '<option value="' + config[key].ValidValues[i] + '"' + (config[key].Value == config[key].ValidValues[i] ? ' selected="selected"' : '') + '>' + text + '</option>';
1197 }
1198 settingsHtml += '</select></div></td>' + printHintBox(getLocalizedText('settings.info.' + key)) + '<hr />';
1199 }
1200 else if (config[key].Type == 'Array') {
1201 settingsHtml += '<table><tr><td>' + getLocalizedText('settings.name.' + key) + '<br />';
1202 if (key == 'excludedCalendars') {
1203 for(var i=0; i < calendarList.length; i++) {
1204 var checked = 'checked="checked"';
1205 if (config[key].Value.indexOf(calendarList[i]) != -1)
1206 checked = '';
1207 settingsHtml += '<input name="settings.' + key + '.' + calendarList[i] + '" type="checkbox" value="' + calendarList[i] + '" ' + checked + '/> ' + calendarList[i] + '<br />';
1208 }
1209 }
1210 settingsHtml += '</td>' + printHintBox(getLocalizedText('settings.info.' + key)) + '<hr />';
1211 }
1212 }
1213 settingsHtml += '<input name="reset" type="button" value="' + getLocalizedText('settings.restoreDefaults') + '" onclick="javascript:restoreDefaultSettings();showSettings();" />';
1214 settingsHtml += '</form>';
1215 document.getElementById("settingsList").innerHTML = settingsHtml;
1216 }
1217
1218 function changeCssClass(classname, properties)
1219 {
1220 for(var i = 0; i < document.styleSheets[0]['cssRules'].length; i++)
1221 {
1222 if (document.styleSheets[0]['cssRules'][i].selectorText == classname) {
1223 document.styleSheets[0].deleteRule(i);
1224 document.styleSheets[0].insertRule(classname + ' { ' + properties + ' }', document.styleSheets[0]['cssRules'].length);
1225 break;
1226 }
1227 }
1228 }
1229
1230 function updateCssClasses()
1231 {
1232 for(var key in config) {
1233 changeCssClass(getLocalizedText('settings.name.' + key), config[key].Value);
1234 }
1235 }
1236
1237 function getSettingsCalEntryId()
1238 {
1239 if (settingsCalEntryId == null) {
1240 // check if entry already exists
1241 var listFiltering = {
1242 Type:'CalendarEntry',
1243 Filter:{
1244 StartRange: new Date(1999, 11, 30), // note: due to Nokia's buggy calendar API, the settings event can be on 01.01.2000 AND on 31.12.1999, depending on when the calendar entry was created (in summer or winter). It is not even possible to narrow the search down to these two days (probably because of DST offsets). So we're looking for an event between 30.12.1999 and 02.01.2000!
1245 EndRange: new Date(2000, 0, 2),
1246 SearchText: 'ComingNext Settings|',
1247 Type: 'DayEvent'
1248 }
1249 }
1250 var result = null;
1251 try {
1252 result = calendarService.IDataSource.GetList(listFiltering);
1253 if (result.ErrorCode)
1254 throw(result.ErrorMessage);
1255 }
1256 catch (e) {
1257 error("getSettingsCalEntryId: GetList() failed: " + e + ', line ' + e.line);
1258 return;
1259 }
1260 var list = result.ReturnValue;
1261 var entry = list.getNext();
1262 if (entry != undefined) {
1263 settingsCalEntryId = entry.LocalId;
1264 log("settingsCalEntryId=" + settingsCalEntryId);
1265 }
1266 else { // create settings item
1267 var item = new Object();
1268 item.Type = "DayEvent";
1269 item.StartTime = new Date(2000, 0, 1);
1270 item.Summary = "ComingNext Settings|";
1271
1272 var criteria = new Object();
1273 criteria.Type = "CalendarEntry";
1274 criteria.Item = item;
1275
1276 try {
1277 var result = calendarService.IDataSource.Add(criteria);
1278 if (result.ErrorCode)
1279 throw(result.ErrorMessage);
1280 } catch (e) {
1281 error("getSettingsCalEntryId: " + e + ', line ' + e.line);
1282 }
1283
1284 getSettingsCalEntryId();
1285 }
1286 }
1287 }
1288
1289 function restoreDefaultSettings()
1290 {
1291 for (var key in config)
1292 config[key].Value = config[key].Default;
1293 }
1294
1295 function loadSettings()
1296 {
1297 getSettingsCalEntryId();
1298 var listFiltering = {
1299 Type:'CalendarEntry',
1300 Filter:{
1301 LocalId: settingsCalEntryId
1302 }
1303 }
1304 var result = null;
1305 try {
1306 result = calendarService.IDataSource.GetList(listFiltering);
1307 if (result.ErrorCode)
1308 throw(result.ErrorMessage);
1309 }
1310 catch (e) {
1311 error("loadSettings: GetList() failed: " + e + ', line ' + e.line);
1312 return;
1313 }
1314 var entry = result.ReturnValue.getNext();
1315 if (entry != undefined) {
1316 log("Loading Settings...");
1317 // only reload settings if they chanced since the last reload
1318 if (settingsCache != entry.Summary)
1319 {
1320 restoreDefaultSettings();
1321 var stringlist = entry.Summary.split("|");
1322 // skip the first two entries, those contain header and version info
1323 for(var i = 2; i < stringlist.length - 1; i++) {
1324 var pair = stringlist[i].split('=');
1325 var key = pair[0];
1326 var value = pair[1];
1327 if (key == null || value == null || config[key] == null) {
1328 log('Warning: unknown or invalid setting: ' + stringlist[i]);
1329 continue;
1330 }
1331 log('stringlist[' + i + ']: ' + key + '=\'' + value + '\'');
1332 if (config[key].Type == 'Int') {
1333 config[key].Value = Number(value);
1334 if (isNaN(config[key].Value))
1335 config[key].Value = config[key].Default;
1336 }
1337 else if (config[key].Type == 'String')
1338 config[key].Value = value;
1339 else if (config[key].Type == 'Bool')
1340 config[key].Value = (value == 'true')
1341 else if (config[key].Type == 'Enum')
1342 config[key].Value = value;
1343 else if (config[key].Type == 'UID') {
1344 config[key].Value = Number(value);
1345 if (isNaN(config[key].Value))
1346 config[key].Value = config[key].Default;
1347 }
1348 else if (config[key].Type == 'Array') {
1349 config[key].Value = value.split("^");
1350 if (config[key].Value.length == 1 && config[key].Value[0] == "") {
1351 config[key].Value = [];
1352 }
1353 }
1354 }
1355 settingsCache = entry.Summary;
1356 updateCssClasses();
1357 }
1358 else {
1359 log("Settings already cached and did not change");
1360 }
1361 }
1362 else {
1363 error("Failed to load settings, calendar entry could not be found");
1364 }
1365 }
1366
1367 function saveSettings()
1368 {
1369 getSettingsCalEntryId();
1370 var item = new Object();
1371 item.Type = "DayEvent";
1372 item.StartTime = new Date(2000, 0, 1);
1373 item.LocalId = settingsCalEntryId;
1374 item.Summary = "ComingNext Settings|" + version + "|";
1375
1376 for (var key in config) {
1377 if (config[key].Type == 'Int')
1378 item.Summary += key + "=" + config[key].Value.toString() + "|";
1379 else if (config[key].Type == 'String')
1380 item.Summary += key + "=" + config[key].Value + "|";
1381 else if (config[key].Type == 'Bool')
1382 item.Summary += key + "=" + (config[key].Value ? 'true' : 'false') + "|";
1383 else if (config[key].Type == 'Enum')
1384 item.Summary += key + "=" + config[key].Value + "|";
1385 else if (config[key].Type == 'UID')
1386 item.Summary += key + "=" + config[key].Value.toString() + "|";
1387 else if (config[key].Type == 'Array')
1388 item.Summary += key + "=" + config[key].Value.join("^") + "|";
1389 }
1390 settingsCache = item.Summary;
1391
1392 var criteria = new Object();
1393 criteria.Type = "CalendarEntry";
1394 criteria.Item = item;
1395
1396 log("Saving settings to calendar entry: " + item.Summary);
1397 try {
1398 var result = calendarService.IDataSource.Add(criteria);
1399 if (result.ErrorCode)
1400 throw(result.ErrorMessage);
1401 } catch (e) {
1402 error("saveSettings: " + e + ', line ' + e.line);
1403 }
1404
1405 lastReloadTime = null; // force calendar data reload on next update
1406 clearUpdateTimer();
1407 setUpdateTimer();
1408 }
1409
1410 function toggleVisibility(elementId)
1411 {
1412 if (document.getElementById(elementId).style.display == "none")
1413 document.getElementById(elementId).style.display = "block";
1414 else
1415 document.getElementById(elementId).style.display = "none";
1416 }
1417
1418 var uniqueId = 0;
1419 function printHintBox(text)
1420 {
1421 uniqueId++;
1422 return '<td width="1%" align="right" onclick="javascript:toggleVisibility(\'info' + uniqueId + '\')">' + getLocalizedText('settings.help') + '</td></tr></table>'+
1423 '<div class="settingsInfo" id="info' + uniqueId + '" style="display:none">' + text + '</div>';
1424 }
1425
1426 function showAbout()
1427 {
1428 mode = 3;
1429 hideViews();
1430 document.getElementById("aboutView").style.display = "block";
1431 document.onclick = null;
1432
1433 window.menu.setLeftSoftkeyLabel(" ", function(){});
1434 window.menu.setRightSoftkeyLabel(getLocalizedText('softkey.back'), function()
1435 {
1436 mode = 1;
1437 showFullscreen();
1438 });
1439
1440 //document.getElementById("aboutView").innerHTML = 'aboutView';
1441 document.getElementById("name").innerHTML = "Coming Next " + version;
1442 }
1443
1444 function showHelp() {
1445 widget.openURL('http://comingnext.sf.net/help');
1446 }
1447
1448 function updateFullscreen()
1449 {
1450 }
1451
1452 function showFullscreen()
1453 {
1454 log("showFullscreen()");
1455 hideViews();
1456 document.getElementById("fullscreenView").style.display = "block";
1457 document.getElementById('body').className = "backgroundFullscreen";
1458 if (!errorOccured)
1459 document.onclick = launchCalendar;
1460 createMenu();
1461 updateData();
1462 }
1463
1464 function getBackgroundImage()
1465 {
1466 if (errorOccured)
1467 return '';
1468 var bgImage;
1469 if (config['backgroundImageLocation'].Value == config['backgroundImageLocation'].ValidValues[0]) // internal
1470 bgImage = 'background_' + orientation + '.png';
1471 else
1472 bgImage = 'C:/Data/background_' + panelNum + '_' + orientation + '.png';
1473 return bgImage;
1474 }
1475
1476 function updateHomescreen()
1477 {
1478 if (config['useBackgroundImage'].Value) {
1479 // check if we have a completely unknown screen resolution
1480 var screenHeight = screen.height;
1481 var screenWidth = screen.width;
1482 if (screenHeight != 640 && screenHeight != 480 && screenHeight != 360)
1483 screenHeight = 360; // we can only assume we're in portrait mode, so we set the screen dims as needed for the following code
1484 if (screenWidth != 640 && screenWidth != 480 && screenWidth != 360)
1485 screenWidth = 640; // we can only assume we're in portrait mode, so we set the screen dims as needed for the following code
1486
1487 // check for screen rotation
1488 if (orientation != 'portrait' && ((screenWidth == 360 && screenHeight == 640) || (screenWidth == 640 && screenHeight == 480))) {
1489 window.widget.prepareForTransition("fade");
1490 orientation = 'portrait';
1491 document.getElementById('body').style.backgroundImage = 'url(' + getBackgroundImage() + ')';
1492 document.getElementById('body').style.backgroundColor = 'none';
1493 window.widget.performTransition();
1494 } else if (orientation != 'landscape' && ((screenWidth == 640 && screenHeight == 360) || (screenWidth == 480 && screenHeight == 640))) {
1495 window.widget.prepareForTransition("fade");
1496 orientation = 'landscape';
1497 document.getElementById('body').style.backgroundImage = 'url(' + getBackgroundImage() + ')';
1498 document.getElementById('body').style.backgroundColor = 'none';
1499 window.widget.performTransition();
1500 }
1501 else if (document.getElementById('body').style.backgroundImage == "")
1502 {
1503 document.getElementById('body').style.backgroundImage = 'url(' + getBackgroundImage() + ')';
1504 }
1505 }
1506 }
1507
1508 function showHomescreen()
1509 {
1510 log("showHomescreen()");
1511 hideViews();
1512 document.getElementById("homescreenView").style.display = "block";
1513 document.getElementById('body').className = "background";
1514 document.onclick = null;
1515 updateData();
1516 }
1517
1518 function getLocalizedText(p_Txt)
1519 {
1520 if (localizedText[p_Txt])
1521 return localizedText[p_Txt];
1522 else
1523 return 'ERROR: missing translation for ' + p_Txt;
1524 }
1525
1526 function showUpdate()
1527 {
1528 mode = 4;
1529 hideViews();
1530 document.getElementById("updateView").style.display = "block";
1531 document.onclick = null;
1532
1533 window.menu.setLeftSoftkeyLabel(getLocalizedText('update.checknow'), function(){
1534 checkForUpdate();
1535 });
1536 window.menu.setRightSoftkeyLabel(getLocalizedText('softkey.back'), function()
1537 {
1538 mode = 1;
1539 showFullscreen();
1540 });
1541
1542 document.getElementById("currentVersion").innerHTML = getLocalizedText("update.current") + version;
1543 checkForUpdate();
1544 }
1545
1546 function checkForUpdate()
1547 {
1548 // asynch XHR to server url
1549 reqV = new XMLHttpRequest();
1550 reqV.onreadystatechange = checkForUpdateCallback;
1551 document.getElementById("updateDiv").innerHTML = getLocalizedText("update.checking");
1552 reqV.open("GET", versionURL, true);
1553 reqV.send(null);
1554 }
1555
1556 function checkForUpdateCallback()
1557 {
1558 if (reqV.readyState == 4) {
1559 if (reqV.status == 200) {
1560 var resultXml = reqV.responseText;
1561 if (resultXml) {
1562 var div = document.getElementById("tmp");
1563 div.innerHTML = resultXml;
1564 var newVersion = div.getElementsByTagName('version')[0].innerHTML;
1565 var newVersionURL = div.getElementsByTagName('url')[0].innerHTML;
1566 div.innerHTML = "";
1567 if (version != newVersion) {
1568 document.getElementById("updateDiv").innerHTML = getLocalizedText("update.download").replace(/%1/, newVersion).replace(/%2/, newVersionURL);
1569 }
1570 else {
1571 document.getElementById("updateDiv").innerHTML = getLocalizedText("update.nonewversion");
1572 }
1573 }
1574 }
1575 else {
1576 document.getElementById("updateDiv").innerHTML = getLocalizedText("update.error") + reqV.status + " " + reqV.responseText;
1577 }
1578 }
1579 }
1580
1581 function hideViews()
1582 {
1583 document.getElementById("homescreenView").style.display = "none";
1584 document.getElementById("fullscreenView").style.display = "none";
1585 document.getElementById("aboutView").style.display = "none";
1586 document.getElementById("settingsView").style.display = "none";
1587 document.getElementById("updateView").style.display = "none";
1588 }
1589
1590 function listCalendars()
1591 {
1592 if (errorOccured) {
1593 return null;
1594 }
1595
1596 try {
1597 var criteria = {
1598 Type:'Calendar',
1599 Filter:{
1600 DefaultCalendar: false
1601 }
1602 }
1603
1604 var calendarsResult = calendarService.IDataSource.GetList(criteria);
1605 if (calendarsResult.ErrorCode != 0)
1606 throw("Error fetching list of calendars: " + calendarsResult.ErrorCode + ': ' + calendarsResult.ErrorMessage);
1607 var calendarListIterator = calendarsResult.ReturnValue;
1608
1609 var calendars = [];
1610 var count = 0;
1611 var item;
1612 while (( item = calendarListIterator.getNext()) != undefined ) {
1613 calendars[count++] = item;
1614 }
1615 log("Available Calendars: " + calendars.join(", "));
1616 return calendars;
1617 } catch(e) {
1618 error('listing calendars:' + e + ', line ' + e.line);
1619 return null;
1620 }
1621 }
1622
1623 // Copies all objects and their properties to an array. Data is copied so nothing gets lost when the reference is removed
1624 // Note: this will also set the "CalendarName" property of every entry to the name specified by the calendarName parameter if it has not been defined already
1625 function listToArray(list, calendarName)
1626 {
1627 var array = new Array();
1628 var item;
1629 var txt = "";
1630 while (( item = list.getNext()) != undefined ) {
1631 var itemCopy = new Object();
1632 for(var i=0; i < entryFields.length; i++) {
1633 itemCopy[entryFields[i]] = item[entryFields[i]];
1634 }
1635 // for some reason, the CalendarName property is never correctly queried, so we assign it manually here
1636 if (!itemCopy['CalendarName']) {
1637 itemCopy['CalendarName'] = calendarName;
1638 }
1639 if (config['anonymizeLogging'].Value && config['enableLogging'].Value) {
1640 if (itemCopy['Summary'])
1641 itemCopy['Summary'] = getHashForString(itemCopy['Summary']);
1642 if (itemCopy['Location'])
1643 itemCopy['Location'] = getHashForString(itemCopy['Location']);
1644 }
1645 array.push(itemCopy);
1646 txt += array[array.length - 1].Summary + ", ";
1647 }
1648 log("listToArray(): " + txt);
1649 return array;
1650 }
1651
1652 function sortCalendarEntries(a, b)
1653 {
1654 var atime, btime;
1655 log("sortCalendarEntries(" + a.Summary + "," + b.Summary + ")");
1656
1657 if (a.InstanceStartTime != null) {
1658 atime = a.InstanceStartTime;
1659 }
1660 else if (a.StartTime != null) {
1661 atime = a.StartTime;
1662 }
1663 else if (a.InstanceEndTime != null) {
1664 atime = a.InstanceEndTime;
1665 }
1666 else if (a.EndTime != null) {
1667 atime = a.EndTime;
1668 }
1669
1670 if (b.InstanceStartTime != null) {
1671 btime = b.InstanceStartTime;
1672 }
1673 else if (b.StartTime != null) {
1674 btime = b.StartTime;
1675 }
1676 else if (b.InstanceEndTime != null) {
1677 btime = b.InstanceEndTime;
1678 }
1679 else if (b.EndTime != null) {
1680 btime = b.EndTime;
1681 }
1682
1683 if (atime && btime) {
1684
1685 atime = parseDate(atime);
1686 btime = parseDate(btime);
1687
1688 // sort by date & time
1689 if (atime < btime) {
1690 return -1;
1691 }
1692 else if (atime > btime) {
1693 return 1;
1694 }
1695 // sort by type
1696 else if (a.Type != b.Type) {
1697 if (a.Type < b.Type) {
1698 return -1;
1699 }
1700 else if (a.Type > b.Type) {
1701 return 1;
1702 }
1703 }
1704 // sort by description
1705 else if (a.Summary && b.Summary && a.Summary != b.Summary) {
1706 if (a.Summary < b.Summary) {
1707 return -1;
1708 }
1709 else if (a.Summary > b.Summary) {
1710 return 1;
1711 }
1712 }
1713 }
1714 // NOTE: events my have no date information at all. In that case, we list events without date first
1715 else if (atime && !btime) {
1716 return 1;
1717 }
1718 else if (!atime && btime) {
1719 return -1;
1720 }
1721 else if (!atime && !btime) {
1722 // sort by type
1723 if (a.Type != b.Type) {
1724 if (a.Type < b.Type) {
1725 return -1;
1726 }
1727 else if (a.Type > b.Type) {
1728 return 1;
1729 }
1730 }
1731 // sort by description
1732 else if (a.Summary && b.Summary && a.Summary != b.Summary) {
1733 if (a.Summary < b.Summary) {
1734 return -1;
1735 }
1736 else if (a.Summary > b.Summary) {
1737 return 1;
1738 }
1739 }
1740 }
1741
1742 return 0;
1743 }
1744
1745 function updateCalendarColors()
1746 {
1747 var maxColors = 6;
1748 calendarColors = [];
1749 if (calendarList.length > maxColors) {
1750 log("updateCalendarColors(): Warning: more calendars than available indicator colors");
1751 }
1752 for(var i=0; i < calendarList.length; i++) {
1753 calendarColors[calendarList[i]] = (i % maxColors) + 1;
1754 }
1755 }
1756
1757 function log(message)
1758 {
1759 if (config['enableLogging'].Value) {
1760 console.info(message);
1761 }
1762 }
1763
1764 function getDefaultFontSize()
1765 {
1766 if (defaultFontSize == null) {
1767 var pa = document.body;
1768 var who = document.createElement('div');
1769 who.className = 'defaultEm';
1770 who.appendChild(document.createTextNode('M'));
1771 pa.appendChild(who);
1772 var fs = [who.offsetWidth, who.offsetHeight];
1773 pa.removeChild(who);
1774 defaultFontSize = fs;
1775 }
1776 return defaultFontSize;
1777 }
1778
1779 function getHashForString(string)
1780 {
1781 // cheap hashing, loosly based on Java's String.hashCode()
1782 for (var hash = 0, i = 0; i < string.length; i++)
1783 hash = (hash << 5) - hash + string.charCodeAt(i);
1784 hash = hash & hash; // Convert to 32bit integer
1785 if (hash < 0)
1786 hash = -hash;
1787 return hash.toString(16).toUpperCase();
1788 }
1789
1790 </script>
1791
1792 <style type="text/css">
1793 a { color:#aaccff }
1794 table { margin:0px; padding:0px; border-spacing:0px; border-collapse: collapse; }
1795 td { padding:0px 5px 0px 0px; white-space:nowrap; overflow:visible; margin:0px; }
1796 hr { color:#ffffff; background-color:#ffffff; height:1px; text-align:left; border-style:none; }
1797 .settingsInfo { display:none; font-style:italic; }
1798 .title { font-weight:bold; font-size:14pt; }
1799 .textInput { width:90%; }
1800 .credits { margin-left:40px; text-indent: -20px; margin-bottom:0px; }
1801 #homescreenView { width: 312px; height:82px; overflow:hidden; }
1802 #calendarList { position:absolute; left:5px; top:0px; width:307px; height:82px; overflow:hidden; }
1803 #name { text-align:center; }
1804 #appicon { display: block; margin-left: auto; margin-right: auto; margin-top: 10px; }
1805 #smallappicon { width:22px; height:22px; margin-right:10px; float:left; }
1806 .defaultEm { font-size:1em; position:absolute; line-height:1; padding:0; visibility:hidden; }
1807 </style>
1808
1809 </head>
1810
1811 <body onload="javascript:setTimeout('init()', 10)" onresize="javascript:updateScreen()" id="body" class="background">
1812 <div id="homescreenView">
1813 <div id="calendarList">loading...</div>
1814 </div>
1815 <div id="fullscreenView" style="display:none;">
1816 <img src="Icon.png" id="smallappicon">
1817 <h1 class="title">Coming Next</h1>
1818 <hr />
1819 <div id="fullscreenCalendarList">loading...</div>
1820 </div>
1821 <div id="settingsView" style="display:none">
1822 <img src="Icon.png" id="smallappicon">
1823 <h1 id="settingsTitle" class="title">Settings</h1>
1824 <hr />
1825 <div id="settingsList"></div>
1826 </div>
1827 <div id="aboutView" style="display:none">
1828 <img src="Icon.png" id="appicon">
1829 <h1 id="name">Coming Next</h1>
1830 <hr />
1831 <p>Created by Dr. Cochambre and Michael Prager.</p>
1832 <p>Contributions:</p>
1833 <p class="credits">Paul Moore (bug fixes, new features and code cleanup)</p>
1834 <p class="credits">Manfred Hanselmann (DST support)</p>
1835 <p class="credits">Christophe Milsent (translation support & French translation)</p>
1836 <p class="credits">Flavio Nathan (Portuguese-Brazilian translation)</p>
1837 <p class="credits">Tokeda (Russian translation)</p>
1838 <p class="credits">Marcella Ferrari (Italian translation)</p>
1839 <p class="credits">Venos (Italian translation)</p>
1840 <p class="credits">Francisco Rodero (Catalan translation)</p>
1841 <p class="credits">zbigzbig20 (Polish translation)</p>
1842 <p class="credits">Streamkeskus (Finnish translation)</p>
1843 <p class="credits">renek (Czech translation)</p>
1844 <p>This software is open source and licensed under the GPLv3.</p>
1845 <p>Visit <a onclick="widget.openURL('http://comingnext.sf.net/'); return false;" href="http://comingnext.sf.net/">comingnext.sf.net</a> for free updates.</p>
1846 <hr />
1847 </div>
1848 <div id="updateView" style="display:none">
1849 <img src="Icon.png" id="smallappicon">
1850 <h1 class="title">Check for update</h1>
1851 <hr />
1852 <div id="currentVersion">Coming Next ??</div>
1853 <div id="updateDiv"></div>
1854 <div id="tmp" style="display:none;"></div>
1855 </div>
1856 </body>
1857
1858 </html>