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