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