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