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">
6 <title>Coming Next
</title>
8 <style type=
"text/css">
9 /* The following classes can be modified by widget settings */
11 .backgroundFullscreen { }
23 <script type=
"text/javascript" src=
"localizedTextStrings.js" charset=
"utf-8" />
26 // valid types for the config object are 'Int', 'Bool', 'String', 'Enum', 'UID'
28 monthRange: { Type: 'Int', Default:
2, Value:
2,},
29 includeTodos: { Type: 'Bool', Default: true, Value: true,},
30 useBackgroundImage: { Type: 'Bool', Default: true, Value: true,},
31 backgroundImageLocation: { Type: 'Enum', Default: 'internal', Value: 'internal', ValidValues: ['internal', 'external']},
32 showCombinedDateTime: { Type: 'Bool', Default: false, Value: false,},
33 showLocation: { Type: 'Bool', Default: true, Value: true,},
34 showTodayAsText: { Type: 'Bool', Default: true, Value: true,},
35 todayText: { Type: 'String', Default: getLocalizedText('settings.default.todayText'), Value: getLocalizedText('settings.default.todayText'),},
36 tomorrowText: { Type: 'String', Default: getLocalizedText('settings.default.tomorrowText'), Value: getLocalizedText('settings.default.tomorrowText'),},
37 showNowAsText: { Type: 'Bool', Default: true, Value: true,},
38 nowText: { Type: 'String', Default: getLocalizedText('settings.default.nowText'), Value: getLocalizedText('settings.default.nowText'),},
39 markOverdueTodos: { Type: 'Bool', Default: true, Value: true,},
40 overdueText: {Type: 'String', Default: getLocalizedText('settings.default.overdueText'), Value: getLocalizedText('settings.default.overdueText'),},
41 dateSeparator: { Type: 'String', Default: getLocalizedText('settings.default.dateSeparator'), Value: getLocalizedText('settings.default.dateSeparator'),},
42 dateFormat: { Type: 'Enum', Default: 'auto', Value: 'auto', ValidValues: ['auto', 'DDMM', 'MMDD'],},
43 weekDayLength: { Type: 'Int', Default:
2, Value:
2,},
44 updateDataInterval: { Type: 'Int', Default:
5, Value:
5,},
45 calendarApp: { Type: 'UID', Default:
0x10005901, Value:
0x10005901,},
46 eventsPerWidget: { Type: 'Int', Default:
4, Value:
4,},
47 showNothingText: { Type: 'Bool', Default: true, Value: true,},
48 nothingText: { Type: 'String', Default: getLocalizedText('settings.default.nothingText'), Value: getLocalizedText('settings.default.nothingText'),},
49 enableDaylightSaving: { Type: 'Bool', Default: true, Value: true,},
50 daylightSavingOffset: { Type: 'Int', Default:
1, Value:
1,},
51 hideWidgetOnCalendarOpen: { Type: 'Bool', Default: false, Value: false,},
52 showCalendarIndicator: { Type: 'Bool', Default: true, Value: true,},
53 cssStyle_background: { Type: 'String', Default: 'color:#ffffff; background-color:#
000000', Value: 'color:#ffffff; background-color:#
000000',},
54 cssStyle_backgroundFullscreen: { Type: 'String', Default: 'color:#ffffff; background-color:#
000000', Value: 'color:#ffffff; background-color:#
000000',},
55 cssStyle_weekDay: { Type: 'String', Default: '', Value: '',},
56 cssStyle_date: { Type: 'String', Default: '', Value: '',},
57 cssStyle_today: { Type: 'String', Default: 'color:#ff0000', Value: 'color:#ff0000',},
58 cssStyle_tomorrow: { Type: 'String', Default: 'color:#
0000ff', Value: 'color:#
0000ff',},
59 cssStyle_time: { Type: 'String', Default: '', Value: '',},
60 cssStyle_now: { Type: 'String', Default: 'color:#ff00ff', Value: 'color:#ff00ff',},
61 cssStyle_description: { Type: 'String', Default: '', Value: '',},
62 cssStyle_icon: { Type: 'String', Default: 'width:
15px; height:
15px', Value: 'width:
15px; height:
15px',},
63 cssStyle_overdue: { Type: 'String', Default: 'color:#ffff00', Value: 'color:#ffff00',},
64 cssStyle_calendar1: { Type: 'String', Default: 'background-color:#
800000', Value: 'background-color:#
800000',},
65 cssStyle_calendar2: { Type: 'String', Default: 'background-color:#ff0000', Value: 'background-color:#ff0000',},
66 cssStyle_calendar3: { Type: 'String', Default: 'background-color:#
808000', Value: 'background-color:#
808000',},
67 cssStyle_calendar4: { Type: 'String', Default: 'background-color:#ffff00', Value: 'background-color:#ffff00',},
68 cssStyle_calendar5: { Type: 'String', Default: 'background-color:#
008000', Value: 'background-color:#
008000',},
69 cssStyle_calendar6: { Type: 'String', Default: 'background-color:#
008080', Value: 'background-color:#
008080',},
74 //-------------------------------------------------------
75 // Nothing of interest from here on...
76 //-------------------------------------------------------
77 var panelNum =
0; // use
1 for second panel
79 var versionURL =
"http://comingnext.sourceforge.net/version.xml";
80 var calendarService = null;
81 var cacheEntriesHtml = [];
82 var months_translated = [];
85 var mode =
0; //
0 = homescreen,
1 = fullscreen,
2 = settings,
3 = about,
4 = check for update
87 var settingsCalEntryId = null;
88 var settingsCache = null;
89 var notificationRequests = new Array();
90 var calendarList = [];
91 var calendarColors = [];
93 // vars for daylight saving time
94 var summertime = false; // true, if current date is in summer, false if in winter
95 var daylightSavingDates = new Object(); // caches calculated DST winter and summer time shift dates
97 // this is a list of data fields a calendar event can have
111 window.onload = init;
112 window.onresize = updateScreen;
113 window.onshow = updateScreen;
115 function isLeapYear( year ) {
116 if (( year %
4 ==
0 && year %
100 !=
0 ) || year %
400 ==
0 )
122 function calcLeapYear(year, days)
124 if (isLeapYear(year))
130 function subToSunday(myDate, year, days, prevMonthDays)
132 for (i = myDate.getDay(); i
> 0 ;i--)
134 days -= prevMonthDays;
135 days = isLeapYear(year) ? --days : days;
139 function isSummertime(curDate)
144 // if we already calculated DST summer and winter time dates for this year, use cached values
145 var dst = daylightSavingDates[curDate.getFullYear()];
147 var thisYearS = new Date(curDate.getFullYear(),
3,
0,
0,
0,
0 );
148 var thisYearW = new Date(curDate.getFullYear(),
10,
0,
0,
0,
0 );
149 var nextYearS = new Date(curDate.getFullYear() +
1,
3,
0,
0,
0,
0 );
150 var nextYearW = new Date(curDate.getFullYear() +
1,
10,
0,
0,
0,
0 );
152 thisYearSDays = nextYearSDays =
90;
153 thisYearWDays = nextYearWDays =
304;
155 thisYearSDays = calcLeapYear(curDate.getFullYear(), thisYearSDays);
156 thisYearWDays = calcLeapYear(curDate.getFullYear(), thisYearWDays);
157 nextYearSDays = calcLeapYear(curDate.getFullYear() +
1, nextYearSDays);
158 nextYearWDays = calcLeapYear(curDate.getFullYear() +
1, nextYearWDays);
160 thisYearSDays = subToSunday(thisYearS, curDate.getFullYear(), thisYearSDays,
59);
161 thisYearWDays = subToSunday(thisYearW, curDate.getFullYear(), thisYearWDays,
273);
162 nextYearSDays = subToSunday(nextYearS, curDate.getFullYear() +
1, nextYearSDays,
59);
163 nextYearWDays = subToSunday(nextYearW, curDate.getFullYear() +
1, nextYearWDays,
273);
166 Summer: new Date (curDate.getFullYear(),
03-
1, thisYearSDays,
2,
0,
0),
167 Winter: new Date (curDate.getFullYear(),
10-
1, thisYearWDays,
2,
0,
0),
169 daylightSavingDates[curDate.getFullYear()] = dst;
172 if (dst.Summer < curDate)
174 if (dst.Winter < curDate)
176 if (summer && !winter)
182 function error(message)
184 console.info('Error: ' + message);
185 document.getElementById(
"calendarList").innerHTML = 'Error: ' + message;
188 function areDatesEqual(date1, date2)
190 return (date1.getFullYear() == date2.getFullYear() &&
191 date1.getMonth() == date2.getMonth() &&
192 date1.getDate() == date2.getDate());
195 function isTomorrow(date)
197 // tommorow = now +
1 day
198 // ToDo: some days can be shorter as
24 hours(daylight saving change day)
199 return areDatesEqual(date, new Date (now.getTime() +
24*
60*
60*
1000));
202 function isToday(date)
204 return areDatesEqual(date, now);
207 function collectLocales()
209 var tmpyear =
2000 + panelNum;
212 if (months_translated.length
> 0)
214 for (month =
0; month <
12; month++) {
215 var startDate = new Date(tmpyear, month,
15);
217 var item = new Object();
218 item.Type =
"DayEvent";
219 item.StartTime = startDate;
220 item.Summary =
"__temp" + month;
222 var criteria = new Object();
223 criteria.Type =
"CalendarEntry";
224 criteria.Item = item;
227 var result = calendarService.IDataSource.Add(criteria);
228 if (result.ErrorCode)
229 error(result.ErrorMessage);
231 error(
"collectLocales: " + e + ', line ' + e.line);
235 var startTime = new Date(tmpyear,
0,
1);
236 var endTime = new Date(tmpyear,
11,
31);
237 var listFiltering = {
238 Type:'CalendarEntry',
240 StartRange: startTime,
242 SearchText: '__temp',
246 var result = calendarService.IDataSource.GetList(listFiltering);
247 if (result.ErrorCode) {
248 error(result.ErrorMessage);
251 var list = result.ReturnValue;
253 error(e + ', line ' + e.line);
256 var ids = new Array();
262 while (list && (entry = list.getNext()) != undefined) {
263 dateArr = entry.StartTime.replace(/,/g,'').replace(/\./g,':').replace(/ /g,' ').split(' ');
264 var day = dateArr[
1];
265 var month = dateArr[
2];
266 var year = dateArr[
3];
268 // make sure month is set properly
269 if (isNaN(parseInt(day))) {
273 } else if (isNaN(parseInt(year))) {
279 console.info(entry.StartTime + ' -
> ' + month + ' ' + counter);
280 ids[counter] = entry.id;
281 months_translated[month] = counter +
1;
285 error(e + ', line ' + e.line);
290 var criteria = new Object();
291 criteria.Type =
"CalendarEntry";
296 var result = calendarService.IDataSource.Delete(criteria);
297 if (result.ErrorCode)
298 error(result.ErrorMessage);
300 error('deleting temp calendar entries:' + e + ', line ' + e.line);
305 function requestNotification()
307 var criteria = new Object();
308 criteria.Type =
"CalendarEntry";
309 criteria.Filter = new Object();
310 for(var i=
0; i < calendarList.length; i++) {
311 criteria.Filter.CalendarName = calendarList[i];
313 var notificationRequest = calendarService.IDataSource.RequestNotification(criteria, callback);
314 if (notificationRequest.ErrorCode)
315 error('requestNotification failed with error code ' + notificationRequest.ErrorCode);
316 notificationRequests.push(notificationRequest);
318 error(
"requestNotification: " + e + ', line ' + e.line);
322 var criteria2 = new Object();
323 criteria2.Type =
"CalendarEntry";
324 criteria2.Filter = new Object();
325 criteria2.Filter.LocalIdList = new Array();
326 criteria2.Filter.LocalIdList[
0] = settingsCalEntryId;
328 var notificationRequest = calendarService.IDataSource.RequestNotification(criteria2, settingsCallback);
329 if (notificationRequest.ErrorCode)
330 error('requestNotification failed with error code ' + notificationRequest.ErrorCode);
331 notificationRequests.push(notificationRequest);
333 error(
"requestNotification: " + e + ', line ' + e.line);
337 function cancelNotification()
339 for(var i=
0; i < notificationRequests.length; i++) {
341 var result = calendarService.IDataSource.Cancel(notificationRequests[i]);
342 if (result.ErrorCode)
343 error('cancelNotification failed with error code ' + result.ErrorCode);
345 error(
"cancelNotification: " + e + ', line ' + e.line);
350 function callback(transId, eventCode, result)
352 console.info(
"callback(): panelNum: %d transId: %d eventCode: %d result.ErrorCode: %d", panelNum, transId, eventCode, result.ErrorCode);
356 function settingsCallback(transId, eventCode, result)
358 console.info(
"settingsCallback(): panelNum: %d transId: %d eventCode: %d result.ErrorCode: %d", panelNum, transId, eventCode, result.ErrorCode);
362 function parseDate(dateString)
365 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:
366 Wednesday,
26 August,
2009 24:
00:
00
367 Wednesday,
26 August,
2009 12:
00:
00 am
368 Wednesday, August
26,
2009 12:
00:
00 am
369 Wednesday,
2009 August,
26 12:
00:
00 am
370 Wednesday,
2009 August,
28 8.00.00 pm
371 Wednesday,
2009 August,
28 08:
00:
00 PM
374 if (dateString ==
"" || dateString == null)
376 var dateArr = dateString.replace(/,/g,'').replace(/\./g,':').replace(/ /g,' ').split(' ');
377 if (dateArr.length !=
5 && dateArr.length !=
6)
381 var weekDay = dateArr[
0];
382 var day = dateArr[
1];
383 var month = dateArr[
2];
384 var year = dateArr[
3];
385 // make sure month is set properly
386 if (isNaN(parseInt(day))) {
390 } else if (isNaN(parseInt(year))) {
395 // make sure day and year are set properly
396 if (Number(day)
> Number(year)) {
401 month = months_translated[month];
404 var timeArr = dateArr[
4].split(':');
405 if (timeArr.length !=
3)
407 var hours = Number(timeArr[
0]);
408 var minutes = Number(timeArr[
1]);
409 var seconds = Number(timeArr[
2]);
410 if (dateArr.length ==
6 && dateArr[
5].toLowerCase() == 'pm' && hours <
12)
412 if (dateArr.length ==
6 && dateArr[
5].toLowerCase() == 'am' && hours ==
12)
415 var result = new Date(year, month -
1, day, hours, minutes, seconds);
417 // take care of daylight saving time
418 if (config['enableDaylightSaving'].Value) {
420 // determine if date is in summer or winter time
421 var dateSummerTime = isSummertime(result);
423 // work around bug in Nokias calendar api resulting in dates within a different DST to be off by
1 hour
424 if (summertime && !dateSummerTime) {
425 result = new Date(result.getTime() -
1000 *
60 *
60 * config['daylightSavingOffset'].Value); // -
1 hour
426 console.info('parseDate(): fixing time -
1h: ' + result);
428 else if (!summertime && dateSummerTime) {
429 result = new Date(result.getTime() +
1000 *
60 *
60 * config['daylightSavingOffset'].Value); // +
1 hour
430 console.info('parseDate(): fixing time +
1h: ' + result);
437 // 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"
438 function formatDate(date, format)
440 var day = date.getDate().toString();
441 var month = (date.getMonth() +
1).toString();
442 while (day.length <
2) { day = '
0' + day; }
443 while (month.length <
2) { month = '
0' + month; }
445 if (config['showTodayAsText'].Value && isToday(date))
446 return '
<span class=
"today">' + config['todayText'].Value + '
</span>';
447 if (config['showTodayAsText'].Value && isTomorrow(date))
448 return '
<span class=
"tomorrow">' + config['tomorrowText'].Value + '
</span>';
450 var dateArr = format.replace(/,/g,'').replace(/\./g,':').replace(/ /g,' ').split(' ');
451 if (dateArr.length !=
5 && dateArr.length !=
6) {
452 // we don't know how to format this
453 if (config['dateFormat'].Value == 'auto' || config['dateFormat'].Value == 'DDMM')
454 return day + config['dateSeparator'].Value + month;
456 return month + config['dateSeparator'].Value + day;
460 if (config['dateFormat'].Value == 'MMDD')
462 else if (config['dateFormat'].Value == 'DDMM')
465 // config['dateFormat'].Value == 'auto', try to detect system setting
467 var day_ = dateArr[
1];
468 var month_ = dateArr[
2];
469 var year_ = dateArr[
3];
470 // make sure month is set properly
471 if (isNaN(parseInt(day_))) {
476 } else if (isNaN(parseInt(year_))) {
482 // make sure day and year are set properly
483 if (Number(day_)
> Number(year_))
488 return day + config['dateSeparator'].Value + month;
490 return month + config['dateSeparator'].Value + day;
493 function formatTime(date)
495 // date is a Date() object
496 date.setSeconds(
0); // we don't care about seconds
497 var time = date.toLocaleTimeString().replace(/[\.:]
00/, ''); // remove seconds from string
498 if (time.replace(/\./, ':').split(':')[
0].length <
2)
500 if (config['showNowAsText'].Value && date.getTime() == now.getTime())
501 time = '
<span class=
"now">' + config['nowText'].Value + '
</span>';
505 function updateData()
507 console.info('updateData()');
509 // check if we got additional or less calendars since our last update
510 var newCalendarList = listCalendars();
511 if (newCalendarList.length != calendarList.length) {
512 calendarList = newCalendarList;
513 updateCalendarColors();
514 cancelNotification();
515 requestNotification();
519 // meetings have time
520 // 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
522 summertime = isSummertime(now); // cache summer time info for today
523 var meetingList = [];
524 for(var i=
0; i < calendarList.length; i++) {
525 var meetingListFiltering = {
526 Type:'CalendarEntry',
528 CalendarName: calendarList[i],
529 StartRange: (new Date(now.getFullYear(), now.getMonth(), now.getDate(),
0,
0,
0)),
530 EndRange: (new Date(now.getFullYear(), now.getMonth() + config['monthRange'].Value, now.getDate(),
0,
0,
0))
533 var meetingResult = calendarService.IDataSource.GetList(meetingListFiltering);
534 if (meetingResult.ErrorCode !=
0)
535 throw(
"Error fetching calendar data: " + meetingResult.ErrorCode + ': ' + meetingResult.ErrorMessage);
536 var list = meetingResult.ReturnValue;
537 meetingList = meetingList.concat(listToArray(list));
539 console.info(
"updateData(): meetingList.sort()");
540 meetingList.sort(sortCalendarEntries);
542 // todos don't, they start on
00:
00 hrs., but should be visible anyway
543 // this will generate a list of passed todos. We have to check if they have been marked as
"done" yet
544 if (config['includeTodos'].Value) {
545 var todayTodoList = [];
546 for(var i=
0; i < calendarList.length; i++) {
547 var todayTodoListFiltering = {
548 Type:'CalendarEntry',
550 CalendarName: calendarList[i],
552 StartRange: (new Date(now.getFullYear() -
1, now.getMonth(), now.getDate(),
0,
0,
0)),
553 EndRange: (new Date(now.getFullYear(), now.getMonth(), now.getDate(),
0,
0,
1))
556 var todayTodoResult = calendarService.IDataSource.GetList(todayTodoListFiltering);
557 var list = todayTodoResult.ReturnValue;
558 todayTodoList = todayTodoList.concat(listToArray(list));
560 console.info(
"updateData(): todayTodoList.sort()");
561 todayTodoList.sort(sortCalendarEntries);
562 var entryLists = [todayTodoList, meetingList];
564 var entryLists = [meetingList];
567 error('loading Calendar items list:' + e + ', line ' + e.line);
576 var fontsize = 'normal';
578 if (config['eventsPerWidget'].Value ==
3) {
580 changeCssClass('.icon', 'width:
20px; height:
20px');
582 else if (config['eventsPerWidget'].Value ==
5) {
584 changeCssClass('.icon', 'width:
10px; height:
10px');
586 else if (config['eventsPerWidget'].Value ==
6) {
588 changeCssClass('.icon', 'width:
8px; height:
8px');
592 changeCssClass('.icon', config['cssStyle_icon'].Value);
593 var entriesHtml = '
<table style=
"font-size:' + fontsize + ';">';
597 max = (panelNum +
1) * config['eventsPerWidget'].Value;
599 max =
30; // we can display a lot more events in fullscreen mode
602 for (var i=
0; i < entryLists.length; i++) {
603 listinfo = listinfo +
" " + entryLists[i].length;
604 var entrieslist =
"";
605 for (var j=
0; j < entryLists[i].length; j++) {
606 entrieslist += entryLists[i][j].Summary +
", ";
608 console.info(
"updateData(): entrieslist: " + entrieslist);
610 console.info(
"updateData(): inner loop, " + entryLists.length +
" lists, [" + listinfo +
"] entries");
612 // the first outer loop iteration is for passed ToDos, the second loop is for all upcomming events (may also include ToDos)
613 for (var i=
0; counter < max && i < entryLists.length; i++) {
614 for (var j=
0; (counter < max) && (j < entryLists[i].length); j++) {
615 entry = entryLists[i][j];
618 // output event info for debugging
619 var entryInfo =
"event: ";
620 for(var k=
0; k < entryFields.length; ++k) {
621 if (entry[entryFields[k]] != undefined) {
622 entryInfo += entryFields[k] +
"=" + entry[entryFields[k]] +
",";
625 console.info(entryInfo);
627 // we don't want ToDos when includeTodos == false or when they are completed
628 if (entry.Type == 'ToDo' && (entry.Status ==
"TodoCompleted" || !config['includeTodos'].Value)) {
629 console.info('skipping ' + entry.id );
634 // make sure that we don't include an event twice (useful for ToDos that might come up twice)
635 if (eventIds[entry.id] ==
1 && entry.Type == 'ToDo') {
636 console.info('skipped (already included) ' + entry.id);
640 eventIds[entry.id] =
1;
642 // summary can be undefined!
643 var Summary = ((entry.Summary == null) ? '' : entry.Summary);
644 if (entry.Type == 'Meeting' && entry.Location != '' && config['showLocation'].Value)
645 Summary += ', ' + entry.Location;
647 // fix by yves: determine start and end dates/times
648 entryStartTime = ((entry.InstanceStartTime == null) ? entry.StartTime : entry.InstanceStartTime);
649 entryEndTime = ((entry.InstanceEndTime == null) ? entry.EndTime : entry.InstanceEndTime);
651 // there can be ToDos that have no date at all!
652 if (entry.Type == 'ToDo' && entry.EndTime == null)
653 entryDate =
""; // this will cause parseDate(entryDate) to return null;
655 entryDate = ((entry.Type == 'ToDo') ? entryEndTime : entryStartTime); // ToDo's use their EndTime, the rest use StartTime
657 // Convert date/time string to Date object
658 var date = parseDate(entryDate);
659 console.info('date: ' + date);
660 var endDate = ((entryEndTime == null) ? null : parseDate(entryEndTime));
661 console.info('endDate: ' + endDate);
663 // check if meeting event has already passed
664 if (entry.Type == 'Meeting') {
665 var compareTime = ((endDate == null) ? date.getTime() : endDate.getTime());
666 if (now.getTime()
> compareTime) {
667 console.info('skipping Meeting (already passed) ' + entry.id);
669 eventIds[entry.id] =
0;
674 // check if anniversary passed (not sure why they are in the list, the query was only for today - nokia?)
675 if (entry.Type == 'Anniversary') {
676 var tmp = new Date(now.getFullYear(), now.getMonth(), now.getDate(),
0,
0,
0);
677 if (date.getTime() < tmp.getTime()) {
678 console.info('skipping Anniversary (already passed) ' + entry.id);
680 eventIds[entry.id] =
0;
685 // fix DayEvents end time. End times are off by
1 Second. It's possible that the event has already passed
686 if (entry.Type == 'DayEvent' && endDate != null) {
687 endDate.setMinutes(endDate.getMinutes() -
1);
688 console.info('fixing DayEvent endDate: ' + endDate);
689 if (now.getTime()
> endDate.getTime()) {
690 console.info('event already passed ' + entry.id);
692 eventIds[entry.id] =
0;
697 // check if the event is currently taking place
698 if (entryStartTime != null && entryEndTime != null && date != null && endDate != null) {
699 // check if we are between start and endtime
700 if ((date.getTime() < now.getTime()) && (now.getTime() < endDate.getTime())) {
701 date = now; // change appointment date/time to now
702 console.info('event is currently taking place: ' + date);
706 // skip events for the first panel in case this is the second one and we're not in fullscreen mode
707 if (mode ==
0 && panelNum
> 0 && counter < panelNum * config['eventsPerWidget'].Value +
1) {
708 console.info('skipping (already in first widget) ' + entry.id);
712 // mark overdue todos
714 if (entry.Type == 'ToDo' && date != null) {
715 var tmp1 = new Date(date.getFullYear(), date.getMonth(), date.getDate(),
0,
0,
0);
716 var tmp2 = new Date(now.getFullYear(), now.getMonth(), now.getDate(),
0,
0,
0);
717 if (tmp1.getTime() < tmp2.getTime()) {
722 // generate html output
723 entriesHtml += '
<tr>';
724 if (config['showCalendarIndicator'].Value && calendarList.length
> 1) {
725 entriesHtml += '
<td class=
"calendar' + calendarColors[entry.CalendarName] + '"> </td>';
727 entriesHtml += '
<td><img class=
"icon" src=
"' + entry.Type + '.png" /></td>';
729 // some languages have very strange locale date formats, can't parse all those. Also some todos don't have dates at all.
730 entriesHtml += '
<td colspan=
"4"><span class=
"date">' + entryDate + '
</span> ';
732 var weekDay = date.toLocaleDateString().substr(
0,config['weekDayLength'].Value);
733 var time = formatTime(date);
734 var dateStr = formatDate(date, entryDate);
735 if (entry.Type == 'ToDo' && overdue && config['markOverdueTodos'].Value) {
736 dateStr = '
<span class=
"overdue">' + config['overdueText'].Value + '
</span>';
737 entriesHtml += '
<td colspan=
"4" width=
"1px"><span class=
"date">' + dateStr + '
</span> ';
738 } else if (entry.Type == 'ToDo' || entry.Type == 'Anniversary' || entry.Type == 'DayEvent' || entry.Type == 'Reminder') {
739 if ((isToday(date) || isTomorrow(date)) && config['showTodayAsText'].Value) // show weekday if the date string is not text. looks odd otherwise
740 entriesHtml += '
<td colspan=
"4" width=
"1px"><span class=
"date">' + dateStr + '
</span> ';
742 entriesHtml += '
<td class=
"weekDay" width=
"1px">' + weekDay + '
</td><td width=
"1px" class=
"date">' + dateStr + '
</td><td colspan=
"2">';
743 } else if (entry.Type == 'Meeting') {
744 if (config['showCombinedDateTime'].Value) {
746 entriesHtml += '
<td width=
"1px" colspan=
"4"><span class=
"today">' + time + '
</span> ';
747 else if (isTomorrow(date))
748 entriesHtml += '
<td width=
"1px" colspan=
"4"><span class=
"tomorrow">' + dateStr + '
</span> <span class=
"time">' + time + '
</span> ';
750 entriesHtml += '
<td width=
"1px" class=
"weekDay">' + weekDay + '
</td><td width=
"1px" class=
"date">' + dateStr + '
</td><td colspan=
"2">';
752 if ((isToday(date) || isTomorrow(date)) && config['showTodayAsText'].Value)
753 entriesHtml += '
<td colspan=
"4" width=
"1px"><span class=
"today">' + dateStr + '
</span> <span class=
"time">' + time + '
</span> ';
755 entriesHtml += '
<td width=
"1px" class=
"weekDay">' + weekDay + '
</td><td width=
"1px" class=
"date">' + dateStr + '
</td><td width=
"1px" class=
"time">' + time + '
</td><td>';
759 entriesHtml += '
<span class=
"description">' + Summary + '
</span></td></tr>';
762 entriesHtml += '
</table>';
763 if (config['showNothingText'].Value && entriesHtml == '
<table></table>') {
764 var text = config['nothingText'].Value.replace(/%d/, config['monthRange'].Value);
765 entriesHtml = '
<div style=
"width:295px; height:75px; text-align:center; line-height:75px; overflow:visible;">' + text + '
</div>';
767 if (cacheEntriesHtml != entriesHtml) {
769 document.getElementById('calendarList').innerHTML = entriesHtml;
771 document.getElementById('fullscreenCalendarList').innerHTML = entriesHtml;
772 cacheEntriesHtml = entriesHtml;
775 error('displaying list:' + e + ', line ' + e.line);
780 function updateScreen()
782 // check if opening fullscreen
783 if( window.innerHeight
> 91 && mode ==
0) {
785 cacheEntriesHtml = '';
786 document.getElementById('body').style.backgroundImage =
"";
789 else if (window.innerHeight <=
91 && mode !=
0) {
791 cacheEntriesHtml = '';
801 function launchCalendar()
804 widget.openApplication(config['calendarApp'].Value,
"");
805 if (config['hideWidgetOnCalendarOpen'].Value)
808 error('starting Calendar App');
815 console.info('New widget instance starting up...');
818 // call calendar service
819 if (device !=
"undefined")
820 calendarService = device.getServiceObject(
"Service.Calendar",
"IDataSource");
822 throw('device object does not exist');
824 error('loading Calendar service: ' + e + ', line ' + e.line);
828 calendarList = listCalendars();
830 updateCalendarColors();
833 requestNotification();
834 window.setInterval('updateData()',
1000 *
60 * config['updateDataInterval'].Value);
835 document.getElementById(
"settingsTitle").innerHTML = getLocalizedText('menu.settings');
837 if (window.innerHeight
> 91) {
838 mode =
0; // we're starting fullscreen, we set mode to homescreen in order to let updateScreen() do all the work for us
843 console.info(
"init(): updateScreen()");
845 if (config['useBackgroundImage'].Value)
846 // check for screen rotation every
1 secs
847 window.setInterval('updateScreen()',
1000 *
1);
848 console.info(
"init(): finished...");
851 function createMenu()
853 window.menu.setLeftSoftkeyLabel(
"",null);
854 window.menu.setRightSoftkeyLabel(
"",null);
856 var menuSettings = new MenuItem(getLocalizedText('menu.settings'), id++);
857 var menuCallApp = new MenuItem(getLocalizedText('menu.openCalendarApp'), id++);
858 var menuUpdate = new MenuItem(getLocalizedText('menu.update'), id++);
859 var menuAbout = new MenuItem(getLocalizedText('menu.about'), id++);
860 menuSettings.onSelect = showSettings;
861 menuAbout.onSelect = showAbout;
862 menuCallApp.onSelect = launchCalendar;
863 menuUpdate.onSelect = showUpdate;
865 window.menu.append(menuCallApp);
866 window.menu.append(menuSettings);
867 window.menu.append(menuUpdate);
868 window.menu.append(menuAbout);
871 function showSettings()
875 document.getElementById(
"settingsView").style.display =
"block";
876 document.onclick = null;
878 window.menu.setLeftSoftkeyLabel(getLocalizedText('settings.save'), function()
880 for (var key in config) {
881 if (config[key].Type == 'String')
882 config[key].Value = document.forms[
0].elements[
"settings." + key].value;
883 else if (config[key].Type == 'Int') {
884 config[key].Value = parseInt(document.forms[
0].elements[
"settings." + key].value);
885 if (config[key].Value <
0)
886 config[key].Value = config[key].Default;
888 else if (config[key].Type == 'Bool')
889 config[key].Value = document.forms[
0].elements[
"settings." + key].checked;
890 else if (config[key].Type == 'UID')
891 config[key].Value = parseInt(document.forms[
0].elements[
"settings." + key].value);
892 else if (config[key].Type == 'Enum') {
893 config[key].Value = document.forms[
0].elements[
"settings." + key].value;
894 if (config[key].ValidValues.indexOf(config[key].Value) == -
1)
895 config[key].Value = config[key].Default;
906 window.menu.setRightSoftkeyLabel(getLocalizedText('settings.cancel'), function()
912 var settingsHtml = '
<form>';
913 for (var key in config) {
914 if (config[key].Type == 'String') {
916 if (key.substring(
0,
9) ==
"cssStyle_")
917 prefix = getLocalizedText('settings.cssStyle_prefix');
918 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 />';
920 else if (config[key].Type == 'Int')
921 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 />';
922 else if (config[key].Type == 'Bool')
923 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 />';
924 else if (config[key].Type == 'UID')
925 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 />';
926 else if (config[key].Type == 'Enum') {
927 settingsHtml += '
<table><tr><td>' + getLocalizedText('settings.name.' + key) + '
<br /><select name=
"settings.' + key + '" size=
"1">';
928 for(var i =
0; i < config[key].ValidValues.length; i++)
929 settingsHtml += '
<option value=
"' + config[key].ValidValues[i] + '"' + (config[key].Value == config[key].ValidValues[i] ? '
selected=
"selected"' : '') + '
>' + getLocalizedText('settings.validValues.' + key + '.' + config[key].ValidValues[i]) + '
</option>';
930 settingsHtml += '
</select></div></td>' + printHintBox(getLocalizedText('settings.info.' + key)) + '
<hr />';
933 settingsHtml += '
<input name=
"reset" type=
"button" value=
"' + getLocalizedText('settings.restoreDefaults') + '" onclick=
"javascript:restoreDefaultSettings();showSettings();" />';
934 settingsHtml += '
</form>';
935 document.getElementById(
"settingsList").innerHTML = settingsHtml;
938 function changeCssClass(classname, properties)
940 for(var i =
0; i < document.styleSheets[
0]['cssRules'].length; i++)
942 if (document.styleSheets[
0]['cssRules'][i].selectorText == classname) {
943 document.styleSheets[
0].deleteRule(i);
944 document.styleSheets[
0].insertRule(classname + ' { ' + properties + ' }', document.styleSheets[
0]['cssRules'].length);
950 function updateCssClasses()
952 for(var key in config) {
953 changeCssClass(getLocalizedText('settings.name.' + key), config[key].Value);
957 function getSettingsCalEntryId()
959 if (settingsCalEntryId == null) {
960 // check if entry already exists
961 var listFiltering = {
962 Type:'CalendarEntry',
964 StartRange: new Date(
2000,
0,
1),
965 EndRange: new Date(
2000,
0,
1),
966 SearchText: 'ComingNext Settings|',
970 var result = calendarService.IDataSource.GetList(listFiltering);
971 if (result.ErrorCode) {
972 error(result.ErrorMessage);
975 var list = result.ReturnValue;
976 var entry = list.getNext();
977 if (entry != undefined) {
978 settingsCalEntryId = entry.LocalId;
979 console.info(
"settingsCalEntryId=" + settingsCalEntryId);
981 else { // create settings item
982 var item = new Object();
983 item.Type =
"DayEvent";
984 item.StartTime = new Date(
2000,
0,
1);
985 item.Summary =
"ComingNext Settings|";
987 var criteria = new Object();
988 criteria.Type =
"CalendarEntry";
989 criteria.Item = item;
992 var result = calendarService.IDataSource.Add(criteria);
993 if (result.ErrorCode)
994 error(result.ErrorMessage);
996 error(
"getSettingsCalEntryId: " + e + ', line ' + e.line);
999 getSettingsCalEntryId();
1004 function restoreDefaultSettings()
1006 for (var key in config)
1007 config[key].Value = config[key].Default;
1010 function loadSettings()
1012 getSettingsCalEntryId();
1013 var listFiltering = {
1014 Type:'CalendarEntry',
1016 LocalId: settingsCalEntryId
1019 var result = calendarService.IDataSource.GetList(listFiltering);
1020 if (result.ErrorCode) {
1021 error(result.ErrorMessage);
1024 var entry = result.ReturnValue.getNext();
1025 if (entry != undefined) {
1026 console.info(
"Loading Settings...");
1027 // only reload settings if they chanced since the last reload
1028 if (settingsCache != entry.Summary)
1030 restoreDefaultSettings();
1031 var stringlist = entry.Summary.split(
"|");
1032 // skip the first two entries, those contain header and version info
1033 for(var i =
2; i < stringlist.length -
1; i++) {
1034 var pair = stringlist[i].split('=');
1036 var value = pair[
1];
1037 console.info('stringlist: ' + key + '=\'' + value + '\'');
1038 if (config[key].Type == 'Int')
1039 config[key].Value = Number(value);
1040 else if (config[key].Type == 'String')
1041 config[key].Value = value;
1042 else if (config[key].Type == 'Bool')
1043 config[key].Value = (value == 'true')
1044 else if (config[key].Type == 'Enum')
1045 config[key].Value = value;
1046 else if (config[key].Type == 'UID')
1047 config[key].Value = Number(value);
1049 settingsCache = entry.Summary;
1053 console.info(
"Settings already cached and did not change");
1057 error(
"Failed to load settings, calendar entry could not be found");
1061 function saveSettings()
1063 getSettingsCalEntryId();
1064 var item = new Object();
1065 item.Type =
"DayEvent";
1066 item.StartTime = new Date(
2000,
0,
1);
1067 item.LocalId = settingsCalEntryId;
1068 item.Summary =
"ComingNext Settings|" + version +
"|";
1070 for (var key in config) {
1071 if (config[key].Type == 'Int')
1072 item.Summary += key +
"=" + config[key].Value.toString() +
"|";
1073 else if (config[key].Type == 'String')
1074 item.Summary += key +
"=" + config[key].Value +
"|";
1075 else if (config[key].Type == 'Bool')
1076 item.Summary += key +
"=" + (config[key].Value ? 'true' : 'false') +
"|";
1077 else if (config[key].Type == 'Enum')
1078 item.Summary += key +
"=" + config[key].Value +
"|";
1079 else if (config[key].Type == 'UID')
1080 item.Summary += key +
"=" + config[key].Value.toString() +
"|";
1082 settingsCache = item.Summary;
1084 var criteria = new Object();
1085 criteria.Type =
"CalendarEntry";
1086 criteria.Item = item;
1088 console.info(
"Saving settings to calendar entry: " + item.Summary);
1090 var result = calendarService.IDataSource.Add(criteria);
1091 if (result.ErrorCode)
1092 error(result.ErrorMessage);
1094 error(
"saveSettings: " + e + ', line ' + e.line);
1098 function toggleVisibility(elementId)
1100 if (document.getElementById(elementId).style.display ==
"none")
1101 document.getElementById(elementId).style.display =
"block";
1103 document.getElementById(elementId).style.display =
"none";
1107 function printHintBox(text)
1110 return '
<td width=
"1%" align=
"right" onclick=
"javascript:toggleVisibility(\'info' + uniqueId + '\')">' + getLocalizedText('settings.help') + '
</td></tr></table>'+
1111 '
<div class=
"settingsInfo" id=
"info' + uniqueId + '">' + text + '
</div>';
1114 function showAbout()
1118 document.getElementById(
"aboutView").style.display =
"block";
1119 document.onclick = null;
1121 window.menu.setLeftSoftkeyLabel(
" ", function(){});
1122 window.menu.setRightSoftkeyLabel(getLocalizedText('softkey.back'), function()
1128 //document.getElementById(
"aboutView").innerHTML = 'aboutView';
1129 document.getElementById(
"name").innerHTML =
"Coming Next " + version;
1132 function updateFullscreen()
1136 function showFullscreen()
1138 console.info(
"showFullscreen()");
1140 document.getElementById(
"fullscreenView").style.display =
"block";
1141 document.getElementById('body').className =
"backgroundFullscreen";
1142 document.onclick = launchCalendar;
1147 function getBackgroundImage()
1150 if (config['backgroundImageLocation'].Value == config['backgroundImageLocation'].ValidValues[
0]) // internal
1151 bgImage = 'background_' + orientation + '.png';
1153 bgImage = 'C:/Data/background_' + panelNum + '_' + orientation + '.png';
1157 function updateHomescreen()
1159 if (config['useBackgroundImage'].Value) {
1160 // check for screen rotation
1161 if (orientation != 'portrait' && screen.width ==
360 && screen.height ==
640) {
1162 window.widget.prepareForTransition(
"fade");
1163 orientation = 'portrait';
1164 document.getElementById('body').style.backgroundImage = 'url(' + getBackgroundImage() + ')';
1165 document.getElementById('body').style.backgroundColor = 'none';
1166 window.widget.performTransition();
1167 } else if (orientation != 'landscape' && screen.width ==
640 && screen.height ==
360) {
1168 window.widget.prepareForTransition(
"fade");
1169 orientation = 'landscape';
1170 document.getElementById('body').style.backgroundImage = 'url(' + getBackgroundImage() + ')';
1171 document.getElementById('body').style.backgroundColor = 'none';
1172 window.widget.performTransition();
1174 else if (document.getElementById('body').style.backgroundImage ==
"")
1176 document.getElementById('body').style.backgroundImage = 'url(' + getBackgroundImage() + ')';
1181 function showHomescreen()
1183 console.info(
"showHomescreen()");
1185 document.getElementById(
"homescreenView").style.display =
"block";
1186 document.getElementById('body').className =
"background";
1187 document.onclick = null;
1191 function getLocalizedText(p_Txt)
1193 if (localizedText[p_Txt])
1194 return localizedText[p_Txt];
1196 return 'ERROR: missing translation for ' + p_Txt;
1199 function showUpdate()
1203 document.getElementById(
"updateView").style.display =
"block";
1204 document.onclick = null;
1206 window.menu.setLeftSoftkeyLabel(getLocalizedText('update.checknow'), function(){
1209 window.menu.setRightSoftkeyLabel(getLocalizedText('softkey.back'), function()
1215 document.getElementById(
"currentVersion").innerHTML = getLocalizedText(
"update.current") + version;
1219 function checkForUpdate()
1221 // asynch XHR to server url
1222 reqV = new XMLHttpRequest();
1223 reqV.onreadystatechange = checkForUpdateCallback;
1224 document.getElementById(
"updateDiv").innerHTML = getLocalizedText(
"update.checking");
1225 reqV.open(
"GET", versionURL, true);
1226 reqV.setRequestHeader(
"If-Modified-Since",
"Sat, 1 Jan 2000 00:00:00 GMT" ); // disable caching
1230 function checkForUpdateCallback()
1232 if (reqV.readyState ==
4) {
1233 if (reqV.status ==
200) {
1234 var resultXml = reqV.responseText;
1236 var div = document.getElementById(
"tmp");
1237 div.innerHTML = resultXml;
1238 var newVersion = div.getElementsByTagName('version')[
0].innerHTML;
1239 var newVersionURL = div.getElementsByTagName('url')[
0].innerHTML;
1241 if (version != newVersion) {
1242 document.getElementById(
"updateDiv").innerHTML = getLocalizedText(
"update.download").replace(/%
1/, newVersion).replace(/%
2/, newVersionURL);
1245 document.getElementById(
"updateDiv").innerHTML = getLocalizedText(
"update.nonewversion");
1250 document.getElementById(
"updateDiv").innerHTML = getLocalizedText(
"update.error") + reqV.status +
" " + reqV.responseText;
1255 function hideViews()
1257 document.getElementById(
"homescreenView").style.display =
"none";
1258 document.getElementById(
"fullscreenView").style.display =
"none";
1259 document.getElementById(
"aboutView").style.display =
"none";
1260 document.getElementById(
"settingsView").style.display =
"none";
1261 document.getElementById(
"updateView").style.display =
"none";
1264 function listCalendars()
1270 DefaultCalendar: false
1274 var calendarsResult = calendarService.IDataSource.GetList(criteria);
1275 if (calendarsResult.ErrorCode !=
0)
1276 throw(
"Error fetching list of calendars: " + calendarsResult.ErrorCode + ': ' + calendarsResult.ErrorMessage);
1277 var calendarListIterator = calendarsResult.ReturnValue;
1282 while (( item = calendarListIterator.getNext()) != undefined ) {
1283 calendars[count++] = item;
1285 console.info(
"Available Calendars: " + calendars.join(
", "));
1288 error('listing calendars:' + e + ', line ' + e.line);
1293 // Copies all objects and their properties to an array. Data is copied so nothing gets lost when the reference is removed
1294 function listToArray(list)
1296 var array = new Array();
1299 while (( item = list.getNext()) != undefined ) {
1300 var itemCopy = new Object();
1301 for(var i=
0; i < entryFields.length; i++) {
1302 itemCopy[entryFields[i]] = item[entryFields[i]];
1304 array.push(itemCopy);
1305 txt += array[array.length -
1].Summary +
", ";
1307 console.info(
"listToArray(): " + txt);
1311 function sortCalendarEntries(a, b)
1314 console.info(
"sortCalendarEntries(" + a.Summary +
"," + b.Summary +
")");
1316 if (a.InstanceStartTime != null) {
1317 atime = a.InstanceStartTime;
1319 else if (a.StartTime != null) {
1320 atime = a.StartTime;
1322 else if (a.InstanceEndTime != null) {
1323 atime = a.InstanceEndTime;
1325 else if (a.EndTime != null) {
1329 if (b.InstanceStartTime != null) {
1330 btime = b.InstanceStartTime;
1332 else if (b.StartTime != null) {
1333 btime = b.StartTime;
1335 else if (b.InstanceEndTime != null) {
1336 btime = b.InstanceEndTime;
1338 else if (b.EndTime != null) {
1342 if (atime && btime) {
1344 atime = parseDate(atime);
1345 btime = parseDate(btime);
1347 // sort by date & time
1348 if (atime < btime) {
1351 else if (atime
> btime) {
1355 else if (a.Type != b.Type) {
1356 if (a.Type < b.Type) {
1359 else if (a.Type
> b.Type) {
1363 // sort by description
1364 else if (a.Summary && b.Summary && a.Summary != b.Summary) {
1365 if (a.Summary < b.Summary) {
1368 else if (a.Summary
> b.Summary) {
1377 function updateCalendarColors()
1379 calendarColors = [];
1380 if (calendarList.length
> 6) {
1381 console.info(
"updateCalendarColors(): Warning: more calendars than available indicator colors");
1383 for(var i=
0; i < calendarList.length; i++) {
1384 calendarColors[calendarList[i]] = (i %
6) +
1;
1390 <style type=
"text/css">
1391 table { margin:
0px; padding:
0px; border-spacing:
0px; }
1392 td { padding:
0px
5px
0px
0px; white-space:nowrap; overflow:hidden; }
1393 hr { color:#ffffff; background-color:#ffffff; height:
1px; text-align:left; border-style:none; }
1394 .settingsInfo { display:none; font-style:italic; }
1395 .title { font-weight:bold; font-size:
14pt; }
1396 .textInput { width:
90%; }
1397 .credits { margin-left:
40px; text-indent: -
20px; margin-bottom:
0px; }
1398 #homescreenView { width:
315px; height:
91px; overflow:hidden; }
1399 #calendarList { position:absolute; left:
10px; top:
4px; width:
295px; height:
75px; overflow:hidden; }
1400 #name { text-align:center; }
1401 #appicon { display: block; margin-left: auto; margin-right: auto; margin-top:
10px; }
1402 #smallappicon { width:
22px; height:
22px; margin-right:
10px; float:left; }
1407 <body id=
"body" class=
"background">
1408 <div id=
"homescreenView">
1409 <div id=
"calendarList"></div>
1411 <div id=
"fullscreenView" style=
"display:none;">
1412 <img src=
"Icon.png" id=
"smallappicon">
1413 <h1 class=
"title">Coming Next
</h1>
1415 <div id=
"fullscreenCalendarList">loading...
</div>
1417 <div id=
"settingsView" style=
"display:none">
1418 <img src=
"Icon.png" id=
"smallappicon">
1419 <h1 id=
"settingsTitle" class=
"title">Settings
</h1>
1421 <div id=
"settingsList"></div>
1423 <div id=
"aboutView" style=
"display:none">
1424 <img src=
"Icon.png" id=
"appicon">
1425 <h1 id=
"name">Coming Next
</h1>
1427 <p>Created by Dr. Cochambre and Michael Prager.
</p>
1428 <p>Contributions:
</p>
1429 <p class=
"credits">Paul Moore (bug fixes, new features and code cleanup)
</p>
1430 <p class=
"credits">Manfred Hanselmann (DST support)
</p>
1431 <p class=
"credits">Christophe Milsent (translation support & french translation)
</p>
1432 <p class=
"credits">Flavio Nathan (portuguese-brazilian translation)
</p>
1433 <p class=
"credits">Tokeda (russian translation)
</p>
1434 <p>This software is open source and licensed under the GPLv3.
</p>
1435 <p>Visit
<a href=
"http://sourceforge.net/projects/comingnext">sourceforge.net/projects/comingnext
</a> for free updates.
</p>
1438 <div id=
"updateView" style=
"display:none">
1439 <img src=
"Icon.png" id=
"smallappicon">
1440 <h1 class=
"title">Check for update
</h1>
1442 <div id=
"currentVersion">Coming Next ??
</div>
1443 <div id=
"updateDiv"></div>
1444 <div id=
"tmp" style=
"display:none;"></div>