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