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