]> code.delx.au - comingnext/blob - comingNext/index.html
fixed weekday extraction from dates
[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 event has already passed
787 if (entry.Type == 'Meeting') {
788 var compareTime = ((endDate == null) ? date.getTime() : endDate.getTime());
789 if (now.getTime() > compareTime) {
790 log('skipping Meeting (already passed) ' + entry.id);
791 counter--;
792 eventIds[entry.id] = 0;
793 continue;
794 }
795 }
796
797 // check if anniversary passed (not sure why they are in the list, the query was only for today - nokia?)
798 if (entry.Type == 'Anniversary') {
799 var tmp = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0,0,0);
800 if (date.getTime() < tmp.getTime()) {
801 log('skipping Anniversary (already passed) ' + entry.id);
802 counter--;
803 eventIds[entry.id] = 0;
804 continue;
805 }
806 }
807
808 // fix DayEvents end time. End times are off by 1 Second. It's possible that the event has already passed
809 if (entry.Type == 'DayEvent' && endDate != null) {
810 endDate.setMinutes(endDate.getMinutes() - 1);
811 log('fixing DayEvent endDate: ' + endDate);
812 if (now.getTime() > endDate.getTime()) {
813 log('event already passed ' + entry.id);
814 counter--;
815 eventIds[entry.id] = 0;
816 continue;
817 }
818 }
819
820 // check if the event is currently taking place
821 if (entryStartTime != null && entryEndTime != null && date != null && endDate != null) {
822 // check if we are between start and endtime
823 if ((date.getTime() < now.getTime()) && (now.getTime() < endDate.getTime())) {
824 date = now; // change appointment date/time to now
825 log('event is currently taking place: ' + date);
826 }
827 }
828
829 // skip events for the first panel in case this is the second one and we're not in fullscreen mode
830 if (mode == 0 && panelNum > 0 && counter < panelNum * config['eventsPerWidget'].Value + 1) {
831 log('skipping (already in first widget) ' + entry.id);
832 continue;
833 }
834
835 // mark overdue todos
836 var overdue = false;
837 if (entry.Type == 'ToDo' && date != null) {
838 var tmp1 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0,0,0);
839 var tmp2 = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0,0,0);
840 if (tmp1.getTime() < tmp2.getTime()) {
841 overdue = true;
842 }
843 }
844
845 // generate html output
846 entriesHtml += '<tr>';
847 if (config['showCalendarIndicator'].Value && calendarList.length - config['excludedCalendars'].Value.length > 1) {
848 entriesHtml += '<td><span class="calendar' + calendarColors[entry.CalendarName] + '">&nbsp;</span></td>';
849 }
850 entriesHtml += '<td><img class="icon" src="' + entry.Type + '.png" /></td>';
851 if(date == null) {
852 // some languages have very strange locale date formats, can't parse all those. Also some todos don't have dates at all.
853 entriesHtml += '<td colspan="4"><span class="date">' + entryDate + '</span> ';
854 } else {
855 var weekDay = getWeekdayLocalized(date).substr(0,config['weekDayLength'].Value);
856 log('date.toLocaleDateString(): ' + date.toLocaleDateString());
857 log('weekDay: ' + weekDay);
858 var time = formatTime(date);
859 var dateStr = formatDate(date, entryDate);
860 if (entry.Type == 'ToDo' && overdue && config['markOverdueTodos'].Value) {
861 dateStr = '<span class="overdue">' + config['overdueText'].Value + '</span>';
862 entriesHtml += '<td colspan="4" width="1px"><span class="date">' + dateStr + '</span> ';
863 } else if (entry.Type == 'ToDo' || entry.Type == 'Anniversary' || entry.Type == 'DayEvent' || entry.Type == 'Reminder') {
864 if ((isToday(date) || isTomorrow(date)) && config['showTodayAsText'].Value) // show weekday if the date string is not text. looks odd otherwise
865 entriesHtml += '<td colspan="4" width="1px"><span class="date">' + dateStr + '</span> ';
866 else
867 entriesHtml += '<td class="weekDay" width="1px">' + weekDay + '</td><td width="1px" class="date">' + dateStr + '</td><td colspan="2">';
868 } else if (entry.Type == 'Meeting') {
869 if (config['showCombinedDateTime'].Value) {
870 if (isToday(date))
871 entriesHtml += '<td width="1px" colspan="4"><span class="today">' + time + '</span> ';
872 else if (isTomorrow(date))
873 entriesHtml += '<td width="1px" colspan="4"><span class="tomorrow">' + dateStr + '</span> <span class="time">' + time + '</span> ';
874 else
875 entriesHtml += '<td width="1px" class="weekDay">' + weekDay + '</td><td width="1px" class="date">' + dateStr + '</td><td colspan="2">';
876 } else {
877 if ((isToday(date) || isTomorrow(date)) && config['showTodayAsText'].Value)
878 entriesHtml += '<td colspan="4" width="1px"><span class="today">' + dateStr + '</span> <span class="time">' + time + '</span> ';
879 else
880 entriesHtml += '<td width="1px" class="weekDay">' + weekDay + '</td><td width="1px" class="date">' + dateStr + '</td><td width="1px" class="time">' + time + '</td><td>';
881 }
882 }
883 }
884 entriesHtml += '<span class="description">' + Summary + '</span></td></tr>';
885 }
886 }
887 entriesHtml += '</table>';
888 if (config['showNothingText'].Value && entriesHtml == '<table></table>') {
889 var text = config['nothingText'].Value.replace(/%d/, config['monthRange'].Value);
890 entriesHtml = '<div style="width:295px; height:75px; text-align:center; line-height:75px; overflow:visible;">' + text + '</div>';
891 }
892 if (cacheEntriesHtml != entriesHtml) {
893 if (mode == 0)
894 document.getElementById('calendarList').innerHTML = entriesHtml;
895 else
896 document.getElementById('fullscreenCalendarList').innerHTML = entriesHtml;
897 cacheEntriesHtml = entriesHtml;
898 }
899
900 lastUpdateTime = new Date();
901 } catch(e) {
902 error('displaying list:' + e + ', line ' + e.line);
903 return;
904 }
905 }
906
907 // called by handleOnShow() and onResize events
908 function updateScreen()
909 {
910 log('updateScreen()');
911
912 // check if opening fullscreen
913 if( window.innerHeight > 91 && mode == 0) {
914 mode = 1;
915 cacheEntriesHtml = '';
916 document.getElementById('body').style.backgroundImage = "";
917 showFullscreen();
918 }
919 else if (window.innerHeight <= 91 && mode != 0) {
920 mode = 0;
921 cacheEntriesHtml = '';
922 showHomescreen();
923 }
924
925 if (mode == 0)
926 updateHomescreen(); // check for screen rotation
927 else if (mode == 1)
928 updateFullscreen();
929 }
930
931 function handleOnShow()
932 {
933 updateScreen();
934
935 var time = new Date();
936 if (time.getTime() - lastUpdateTime.getTime() > config['updateDataInterval'].Value * 60 * 1000) {
937 log('updateScreen(): force updateData() because last update was too long ago (' + (time.getTime() - lastUpdateTime.getTime()) / 1000 + 's)');
938 clearUpdateTimer();
939 updateData();
940 setUpdateTimer(); // reinitialize update timer
941 }
942 }
943
944 function launchCalendar()
945 {
946 try {
947 widget.openApplication(config['calendarApp'].Value, "");
948 if (config['hideWidgetOnCalendarOpen'].Value)
949 window.close();
950 } catch(e) {
951 error('starting Calendar App');
952 return;
953 }
954 }
955
956 function init()
957 {
958 log('New widget instance starting up...');
959
960 try {
961 // call calendar service
962 if (device != "undefined")
963 calendarService = device.getServiceObject("Service.Calendar", "IDataSource");
964 else
965 throw('device object does not exist');
966 } catch(e) {
967 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>');
968 //return;
969 }
970
971 calendarList = listCalendars();
972 loadSettings();
973 updateCalendarColors();
974 collectLocales();
975 //updateData();
976 requestNotification();
977 document.getElementById("settingsTitle").innerHTML = getLocalizedText('menu.settings');
978 setUpdateTimer();
979 if (window.innerHeight > 91) {
980 mode = 0; // we're starting fullscreen, we set mode to homescreen in order to let updateScreen() do all the work for us
981 }
982 else {
983 mode = 1;
984 }
985 log("init(): updateScreen()");
986 updateScreen();
987 if (config['useBackgroundImage'].Value)
988 // check for screen rotation every 1 secs
989 screenRotationTimer = window.setInterval('checkOrientation()', 1000 * 1);
990
991 // call updateScreen() when widget changes from background to forground
992 window.widget.onshow = handleOnShow;
993
994 log("init(): finished...");
995 if (!errorOccured)
996 statupSuccessful = true;
997 }
998
999 function checkOrientation()
1000 {
1001 //updateScreen();
1002 if (mode == 0)
1003 updateHomescreen(); // check for screen rotation
1004 }
1005
1006 function setUpdateTimer()
1007 {
1008 updateTimer = window.setInterval('updateTimerCallback()', 1000 * 60 * config['updateDataInterval'].Value);
1009 }
1010
1011 function clearUpdateTimer()
1012 {
1013 window.clearInterval(updateTimer);
1014 }
1015
1016 function updateTimerCallback()
1017 {
1018 log("updateTimerCallback()");
1019 updateData();
1020 }
1021
1022 function createMenu()
1023 {
1024 window.menu.setLeftSoftkeyLabel("",null);
1025 window.menu.setRightSoftkeyLabel("",null);
1026 var id = 0;
1027 var menuSettings = new MenuItem(getLocalizedText('menu.settings'), id++);
1028 var menuCallApp = new MenuItem(getLocalizedText('menu.openCalendarApp'), id++);
1029 var menuHelp = new MenuItem(getLocalizedText('menu.help'), id++);
1030 var menuUpdate = new MenuItem(getLocalizedText('menu.update'), id++);
1031 var menuAbout = new MenuItem(getLocalizedText('menu.about'), id++);
1032 menuSettings.onSelect = showSettings;
1033 menuAbout.onSelect = showAbout;
1034 menuCallApp.onSelect = launchCalendar;
1035 menuUpdate.onSelect = showUpdate;
1036 menuHelp.onSelect = showHelp;
1037 window.menu.clear();
1038 window.menu.append(menuCallApp);
1039 window.menu.append(menuSettings);
1040 window.menu.append(menuHelp);
1041 window.menu.append(menuUpdate);
1042 window.menu.append(menuAbout);
1043 }
1044
1045 function showSettings()
1046 {
1047 mode = 2;
1048 hideViews();
1049 document.getElementById("settingsView").style.display = "block";
1050 document.onclick = null;
1051
1052 window.menu.setLeftSoftkeyLabel(getLocalizedText('settings.save'), function()
1053 {
1054 for (var key in config) {
1055 if (config[key].Type == 'String')
1056 config[key].Value = document.forms[0].elements["settings." + key].value;
1057 else if (config[key].Type == 'Int') {
1058 config[key].Value = parseInt(document.forms[0].elements["settings." + key].value);
1059 if (config[key].Value < 0 || isNaN(config[key].Value))
1060 config[key].Value = config[key].Default;
1061 }
1062 else if (config[key].Type == 'Bool')
1063 config[key].Value = document.forms[0].elements["settings." + key].checked;
1064 else if (config[key].Type == 'UID') {
1065 config[key].Value = parseInt(document.forms[0].elements["settings." + key].value);
1066 if (isNaN(config[key].Value))
1067 config[key].Value = config[key].Default;
1068 }
1069 else if (config[key].Type == 'Enum') {
1070 config[key].Value = document.forms[0].elements["settings." + key].value;
1071 if (config[key].ValidValues.indexOf(config[key].Value) == -1)
1072 config[key].Value = config[key].Default;
1073 }
1074 else if (config[key].Type == 'Array') {
1075 if (key == 'excludedCalendars') {
1076 config[key].Value = new Array();
1077 for(var i=0; i < calendarList.length; i++) {
1078 var element = document.forms[0].elements["settings." + key + "." + calendarList[i]];
1079 if (element != null && element.checked == false)
1080 config[key].Value.push(calendarList[i]);
1081 }
1082 }
1083 }
1084 }
1085
1086 updateCssClasses();
1087
1088 saveSettings();
1089
1090 mode = 1;
1091 showFullscreen();
1092 });
1093 window.menu.setRightSoftkeyLabel(getLocalizedText('settings.cancel'), function()
1094 {
1095 mode = 1;
1096 showFullscreen();
1097 });
1098
1099 var settingsHtml = '<form>';
1100 for (var key in config) {
1101 if (config[key].Type == 'String') {
1102 var prefix = "";
1103 if (key.substring(0,9) == "cssStyle_")
1104 prefix = getLocalizedText('settings.cssStyle_prefix');
1105 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 />';
1106 }
1107 else if (config[key].Type == 'Int')
1108 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 />';
1109 else if (config[key].Type == 'Bool')
1110 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 />';
1111 else if (config[key].Type == 'UID')
1112 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 />';
1113 else if (config[key].Type == 'Enum') {
1114 settingsHtml += '<table><tr><td>' + getLocalizedText('settings.name.' + key) + '<br /><select name="settings.' + key + '" size="1">';
1115 for(var i = 0; i < config[key].ValidValues.length; i++)
1116 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>';
1117 settingsHtml += '</select></div></td>' + printHintBox(getLocalizedText('settings.info.' + key)) + '<hr />';
1118 }
1119 else if (config[key].Type == 'Array') {
1120 settingsHtml += '<table><tr><td>' + getLocalizedText('settings.name.' + key) + '<br />';
1121 if (key == 'excludedCalendars') {
1122 for(var i=0; i < calendarList.length; i++) {
1123 var checked = 'checked="checked"';
1124 if (config[key].Value.indexOf(calendarList[i]) != -1)
1125 checked = '';
1126 settingsHtml += '<input name="settings.' + key + '.' + calendarList[i] + '" type="checkbox" value="' + calendarList[i] + '" ' + checked + '/> ' + calendarList[i] + '<br />';
1127 }
1128 }
1129 settingsHtml += '</td>' + printHintBox(getLocalizedText('settings.info.' + key)) + '<hr />';
1130 }
1131 }
1132 settingsHtml += '<input name="reset" type="button" value="' + getLocalizedText('settings.restoreDefaults') + '" onclick="javascript:restoreDefaultSettings();showSettings();" />';
1133 settingsHtml += '</form>';
1134 document.getElementById("settingsList").innerHTML = settingsHtml;
1135 }
1136
1137 function changeCssClass(classname, properties)
1138 {
1139 for(var i = 0; i < document.styleSheets[0]['cssRules'].length; i++)
1140 {
1141 if (document.styleSheets[0]['cssRules'][i].selectorText == classname) {
1142 document.styleSheets[0].deleteRule(i);
1143 document.styleSheets[0].insertRule(classname + ' { ' + properties + ' }', document.styleSheets[0]['cssRules'].length);
1144 break;
1145 }
1146 }
1147 }
1148
1149 function updateCssClasses()
1150 {
1151 for(var key in config) {
1152 changeCssClass(getLocalizedText('settings.name.' + key), config[key].Value);
1153 }
1154 }
1155
1156 function getSettingsCalEntryId()
1157 {
1158 if (settingsCalEntryId == null) {
1159 // check if entry already exists
1160 var listFiltering = {
1161 Type:'CalendarEntry',
1162 Filter:{
1163 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!
1164 EndRange: new Date(2000, 0, 2),
1165 SearchText: 'ComingNext Settings|',
1166 Type: 'DayEvent'
1167 }
1168 }
1169 var result = null;
1170 try {
1171 result = calendarService.IDataSource.GetList(listFiltering);
1172 if (result.ErrorCode)
1173 throw(result.ErrorMessage);
1174 }
1175 catch (e) {
1176 error("getSettingsCalEntryId: GetList() failed: " + e + ', line ' + e.line);
1177 return;
1178 }
1179 var list = result.ReturnValue;
1180 var entry = list.getNext();
1181 if (entry != undefined) {
1182 settingsCalEntryId = entry.LocalId;
1183 log("settingsCalEntryId=" + settingsCalEntryId);
1184 }
1185 else { // create settings item
1186 var item = new Object();
1187 item.Type = "DayEvent";
1188 item.StartTime = new Date(2000, 0, 1);
1189 item.Summary = "ComingNext Settings|";
1190
1191 var criteria = new Object();
1192 criteria.Type = "CalendarEntry";
1193 criteria.Item = item;
1194
1195 try {
1196 var result = calendarService.IDataSource.Add(criteria);
1197 if (result.ErrorCode)
1198 throw(result.ErrorMessage);
1199 } catch (e) {
1200 error("getSettingsCalEntryId: " + e + ', line ' + e.line);
1201 }
1202
1203 getSettingsCalEntryId();
1204 }
1205 }
1206 }
1207
1208 function restoreDefaultSettings()
1209 {
1210 for (var key in config)
1211 config[key].Value = config[key].Default;
1212 }
1213
1214 function loadSettings()
1215 {
1216 getSettingsCalEntryId();
1217 var listFiltering = {
1218 Type:'CalendarEntry',
1219 Filter:{
1220 LocalId: settingsCalEntryId
1221 }
1222 }
1223 var result = null;
1224 try {
1225 result = calendarService.IDataSource.GetList(listFiltering);
1226 if (result.ErrorCode)
1227 throw(result.ErrorMessage);
1228 }
1229 catch (e) {
1230 error("loadSettings: GetList() failed: " + e + ', line ' + e.line);
1231 return;
1232 }
1233 var entry = result.ReturnValue.getNext();
1234 if (entry != undefined) {
1235 log("Loading Settings...");
1236 // only reload settings if they chanced since the last reload
1237 if (settingsCache != entry.Summary)
1238 {
1239 restoreDefaultSettings();
1240 var stringlist = entry.Summary.split("|");
1241 // skip the first two entries, those contain header and version info
1242 for(var i = 2; i < stringlist.length - 1; i++) {
1243 var pair = stringlist[i].split('=');
1244 var key = pair[0];
1245 var value = pair[1];
1246 if (key == null || value == null || config[key] == null) {
1247 log('Warning: unknown or invalid setting: ' + stringlist[i]);
1248 continue;
1249 }
1250 log('stringlist: ' + key + '=\'' + value + '\'');
1251 if (config[key].Type == 'Int') {
1252 config[key].Value = Number(value);
1253 if (isNaN(config[key].Value))
1254 config[key].Value = config[key].Default;
1255 }
1256 else if (config[key].Type == 'String')
1257 config[key].Value = value;
1258 else if (config[key].Type == 'Bool')
1259 config[key].Value = (value == 'true')
1260 else if (config[key].Type == 'Enum')
1261 config[key].Value = value;
1262 else if (config[key].Type == 'UID') {
1263 config[key].Value = Number(value);
1264 if (isNaN(config[key].Value))
1265 config[key].Value = config[key].Default;
1266 }
1267 else if (config[key].Type == 'Array') {
1268 config[key].Value = value.split("^");
1269 if (config[key].Value.length == 1 && config[key].Value[0] == "") {
1270 config[key].Value = [];
1271 }
1272 }
1273 }
1274 settingsCache = entry.Summary;
1275 updateCssClasses();
1276 }
1277 else {
1278 log("Settings already cached and did not change");
1279 }
1280 }
1281 else {
1282 error("Failed to load settings, calendar entry could not be found");
1283 }
1284 }
1285
1286 function saveSettings()
1287 {
1288 getSettingsCalEntryId();
1289 var item = new Object();
1290 item.Type = "DayEvent";
1291 item.StartTime = new Date(2000, 0, 1);
1292 item.LocalId = settingsCalEntryId;
1293 item.Summary = "ComingNext Settings|" + version + "|";
1294
1295 for (var key in config) {
1296 if (config[key].Type == 'Int')
1297 item.Summary += key + "=" + config[key].Value.toString() + "|";
1298 else if (config[key].Type == 'String')
1299 item.Summary += key + "=" + config[key].Value + "|";
1300 else if (config[key].Type == 'Bool')
1301 item.Summary += key + "=" + (config[key].Value ? 'true' : 'false') + "|";
1302 else if (config[key].Type == 'Enum')
1303 item.Summary += key + "=" + config[key].Value + "|";
1304 else if (config[key].Type == 'UID')
1305 item.Summary += key + "=" + config[key].Value.toString() + "|";
1306 else if (config[key].Type == 'Array')
1307 item.Summary += key + "=" + config[key].Value.join("^") + "|";
1308 }
1309 settingsCache = item.Summary;
1310
1311 var criteria = new Object();
1312 criteria.Type = "CalendarEntry";
1313 criteria.Item = item;
1314
1315 log("Saving settings to calendar entry: " + item.Summary);
1316 try {
1317 var result = calendarService.IDataSource.Add(criteria);
1318 if (result.ErrorCode)
1319 throw(result.ErrorMessage);
1320 } catch (e) {
1321 error("saveSettings: " + e + ', line ' + e.line);
1322 }
1323
1324 lastReloadTime = null; // force calendar data reload on next update
1325 clearUpdateTimer();
1326 setUpdateTimer();
1327 }
1328
1329 function toggleVisibility(elementId)
1330 {
1331 if (document.getElementById(elementId).style.display == "none")
1332 document.getElementById(elementId).style.display = "block";
1333 else
1334 document.getElementById(elementId).style.display = "none";
1335 }
1336
1337 var uniqueId = 0;
1338 function printHintBox(text)
1339 {
1340 uniqueId++;
1341 return '<td width="1%" align="right" onclick="javascript:toggleVisibility(\'info' + uniqueId + '\')">' + getLocalizedText('settings.help') + '</td></tr></table>'+
1342 '<div class="settingsInfo" id="info' + uniqueId + '">' + text + '</div>';
1343 }
1344
1345 function showAbout()
1346 {
1347 mode = 3;
1348 hideViews();
1349 document.getElementById("aboutView").style.display = "block";
1350 document.onclick = null;
1351
1352 window.menu.setLeftSoftkeyLabel(" ", function(){});
1353 window.menu.setRightSoftkeyLabel(getLocalizedText('softkey.back'), function()
1354 {
1355 mode = 1;
1356 showFullscreen();
1357 });
1358
1359 //document.getElementById("aboutView").innerHTML = 'aboutView';
1360 document.getElementById("name").innerHTML = "Coming Next " + version;
1361 }
1362
1363 function showHelp() {
1364 widget.openURL('http://comingnext.sf.net/help');
1365 }
1366
1367 function updateFullscreen()
1368 {
1369 }
1370
1371 function showFullscreen()
1372 {
1373 log("showFullscreen()");
1374 hideViews();
1375 document.getElementById("fullscreenView").style.display = "block";
1376 document.getElementById('body').className = "backgroundFullscreen";
1377 if (!errorOccured)
1378 document.onclick = launchCalendar;
1379 createMenu();
1380 updateData();
1381 }
1382
1383 function getBackgroundImage()
1384 {
1385 if (errorOccured)
1386 return '';
1387 var bgImage;
1388 if (config['backgroundImageLocation'].Value == config['backgroundImageLocation'].ValidValues[0]) // internal
1389 bgImage = 'background_' + orientation + '.png';
1390 else
1391 bgImage = 'C:/Data/background_' + panelNum + '_' + orientation + '.png';
1392 return bgImage;
1393 }
1394
1395 function updateHomescreen()
1396 {
1397 if (config['useBackgroundImage'].Value) {
1398 // check if we have a completely unknown screen resolution
1399 var screenHeight = screen.height;
1400 var screenWidth = screen.width;
1401 if (screenHeight != 640 && screenHeight != 480 && screenHeight != 360)
1402 screenHeight = 360; // we can only assume we're in portrait mode, so we set the screen dims as needed for the following code
1403 if (screenWidth != 640 && screenWidth != 480 && screenWidth != 360)
1404 screenWidth = 640; // we can only assume we're in portrait mode, so we set the screen dims as needed for the following code
1405
1406 // check for screen rotation
1407 if (orientation != 'portrait' && ((screenWidth == 360 && screenHeight == 640) || (screenWidth == 640 && screenHeight == 480))) {
1408 window.widget.prepareForTransition("fade");
1409 orientation = 'portrait';
1410 document.getElementById('body').style.backgroundImage = 'url(' + getBackgroundImage() + ')';
1411 document.getElementById('body').style.backgroundColor = 'none';
1412 window.widget.performTransition();
1413 } else if (orientation != 'landscape' && ((screenWidth == 640 && screenHeight == 360) || (screenWidth == 480 && screenHeight == 640))) {
1414 window.widget.prepareForTransition("fade");
1415 orientation = 'landscape';
1416 document.getElementById('body').style.backgroundImage = 'url(' + getBackgroundImage() + ')';
1417 document.getElementById('body').style.backgroundColor = 'none';
1418 window.widget.performTransition();
1419 }
1420 else if (document.getElementById('body').style.backgroundImage == "")
1421 {
1422 document.getElementById('body').style.backgroundImage = 'url(' + getBackgroundImage() + ')';
1423 }
1424 }
1425 }
1426
1427 function showHomescreen()
1428 {
1429 log("showHomescreen()");
1430 hideViews();
1431 document.getElementById("homescreenView").style.display = "block";
1432 document.getElementById('body').className = "background";
1433 document.onclick = null;
1434 updateData();
1435 }
1436
1437 function getLocalizedText(p_Txt)
1438 {
1439 if (localizedText[p_Txt])
1440 return localizedText[p_Txt];
1441 else
1442 return 'ERROR: missing translation for ' + p_Txt;
1443 }
1444
1445 function showUpdate()
1446 {
1447 mode = 4;
1448 hideViews();
1449 document.getElementById("updateView").style.display = "block";
1450 document.onclick = null;
1451
1452 window.menu.setLeftSoftkeyLabel(getLocalizedText('update.checknow'), function(){
1453 checkForUpdate();
1454 });
1455 window.menu.setRightSoftkeyLabel(getLocalizedText('softkey.back'), function()
1456 {
1457 mode = 1;
1458 showFullscreen();
1459 });
1460
1461 document.getElementById("currentVersion").innerHTML = getLocalizedText("update.current") + version;
1462 checkForUpdate();
1463 }
1464
1465 function checkForUpdate()
1466 {
1467 // asynch XHR to server url
1468 reqV = new XMLHttpRequest();
1469 reqV.onreadystatechange = checkForUpdateCallback;
1470 document.getElementById("updateDiv").innerHTML = getLocalizedText("update.checking");
1471 reqV.open("GET", versionURL, true);
1472 reqV.setRequestHeader( "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT" ); // disable caching
1473 reqV.send(null);
1474 }
1475
1476 function checkForUpdateCallback()
1477 {
1478 if (reqV.readyState == 4) {
1479 if (reqV.status == 200) {
1480 var resultXml = reqV.responseText;
1481 if (resultXml) {
1482 var div = document.getElementById("tmp");
1483 div.innerHTML = resultXml;
1484 var newVersion = div.getElementsByTagName('version')[0].innerHTML;
1485 var newVersionURL = div.getElementsByTagName('url')[0].innerHTML;
1486 div.innerHTML = "";
1487 if (version != newVersion) {
1488 document.getElementById("updateDiv").innerHTML = getLocalizedText("update.download").replace(/%1/, newVersion).replace(/%2/, newVersionURL);
1489 }
1490 else {
1491 document.getElementById("updateDiv").innerHTML = getLocalizedText("update.nonewversion");
1492 }
1493 }
1494 }
1495 else {
1496 document.getElementById("updateDiv").innerHTML = getLocalizedText("update.error") + reqV.status + " " + reqV.responseText;
1497 }
1498 }
1499 }
1500
1501 function hideViews()
1502 {
1503 document.getElementById("homescreenView").style.display = "none";
1504 document.getElementById("fullscreenView").style.display = "none";
1505 document.getElementById("aboutView").style.display = "none";
1506 document.getElementById("settingsView").style.display = "none";
1507 document.getElementById("updateView").style.display = "none";
1508 }
1509
1510 function listCalendars()
1511 {
1512 if (errorOccured) {
1513 return null;
1514 }
1515
1516 try {
1517 var criteria = {
1518 Type:'Calendar',
1519 Filter:{
1520 DefaultCalendar: false
1521 }
1522 }
1523
1524 var calendarsResult = calendarService.IDataSource.GetList(criteria);
1525 if (calendarsResult.ErrorCode != 0)
1526 throw("Error fetching list of calendars: " + calendarsResult.ErrorCode + ': ' + calendarsResult.ErrorMessage);
1527 var calendarListIterator = calendarsResult.ReturnValue;
1528
1529 var calendars = [];
1530 var count = 0;
1531 var item;
1532 while (( item = calendarListIterator.getNext()) != undefined ) {
1533 calendars[count++] = item;
1534 }
1535 log("Available Calendars: " + calendars.join(", "));
1536 return calendars;
1537 } catch(e) {
1538 error('listing calendars:' + e + ', line ' + e.line);
1539 return null;
1540 }
1541 }
1542
1543 // Copies all objects and their properties to an array. Data is copied so nothing gets lost when the reference is removed
1544 // 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
1545 function listToArray(list, calendarName)
1546 {
1547 var array = new Array();
1548 var item;
1549 var txt = "";
1550 while (( item = list.getNext()) != undefined ) {
1551 var itemCopy = new Object();
1552 for(var i=0; i < entryFields.length; i++) {
1553 itemCopy[entryFields[i]] = item[entryFields[i]];
1554 }
1555 // for some reason, the CalendarName property is never correctly queried, so we assign it manually here
1556 if (!itemCopy['CalendarName']) {
1557 itemCopy['CalendarName'] = calendarName;
1558 }
1559 array.push(itemCopy);
1560 txt += array[array.length - 1].Summary + ", ";
1561 }
1562 log("listToArray(): " + txt);
1563 return array;
1564 }
1565
1566 function sortCalendarEntries(a, b)
1567 {
1568 var atime, btime;
1569 log("sortCalendarEntries(" + a.Summary + "," + b.Summary + ")");
1570
1571 if (a.InstanceStartTime != null) {
1572 atime = a.InstanceStartTime;
1573 }
1574 else if (a.StartTime != null) {
1575 atime = a.StartTime;
1576 }
1577 else if (a.InstanceEndTime != null) {
1578 atime = a.InstanceEndTime;
1579 }
1580 else if (a.EndTime != null) {
1581 atime = a.EndTime;
1582 }
1583
1584 if (b.InstanceStartTime != null) {
1585 btime = b.InstanceStartTime;
1586 }
1587 else if (b.StartTime != null) {
1588 btime = b.StartTime;
1589 }
1590 else if (b.InstanceEndTime != null) {
1591 btime = b.InstanceEndTime;
1592 }
1593 else if (b.EndTime != null) {
1594 btime = b.EndTime;
1595 }
1596
1597 if (atime && btime) {
1598
1599 atime = parseDate(atime);
1600 btime = parseDate(btime);
1601
1602 // sort by date & time
1603 if (atime < btime) {
1604 return -1;
1605 }
1606 else if (atime > btime) {
1607 return 1;
1608 }
1609 // sort by type
1610 else if (a.Type != b.Type) {
1611 if (a.Type < b.Type) {
1612 return -1;
1613 }
1614 else if (a.Type > b.Type) {
1615 return 1;
1616 }
1617 }
1618 // sort by description
1619 else if (a.Summary && b.Summary && a.Summary != b.Summary) {
1620 if (a.Summary < b.Summary) {
1621 return -1;
1622 }
1623 else if (a.Summary > b.Summary) {
1624 return 1;
1625 }
1626 }
1627 }
1628 // NOTE: events my have no date information at all. In that case, we list events without date first
1629 else if (atime && !btime) {
1630 return 1;
1631 }
1632 else if (!atime && btime) {
1633 return -1;
1634 }
1635 else if (!atime && !btime) {
1636 // sort by type
1637 if (a.Type != b.Type) {
1638 if (a.Type < b.Type) {
1639 return -1;
1640 }
1641 else if (a.Type > b.Type) {
1642 return 1;
1643 }
1644 }
1645 // sort by description
1646 else if (a.Summary && b.Summary && a.Summary != b.Summary) {
1647 if (a.Summary < b.Summary) {
1648 return -1;
1649 }
1650 else if (a.Summary > b.Summary) {
1651 return 1;
1652 }
1653 }
1654 }
1655
1656 return 0;
1657 }
1658
1659 function updateCalendarColors()
1660 {
1661 var maxColors = 6;
1662 calendarColors = [];
1663 if (calendarList.length > maxColors) {
1664 log("updateCalendarColors(): Warning: more calendars than available indicator colors");
1665 }
1666 for(var i=0; i < calendarList.length; i++) {
1667 calendarColors[calendarList[i]] = (i % maxColors) + 1;
1668 }
1669 }
1670
1671 function log(message)
1672 {
1673 if (config['enableLogging'].Value) {
1674 console.info(message);
1675 }
1676 }
1677
1678 </script>
1679
1680 <style type="text/css">
1681 a { color:#aaccff }
1682 table { margin:0px; padding:0px; border-spacing:0px; }
1683 td { padding:0px 5px 0px 0px; white-space:nowrap; overflow:hidden; }
1684 hr { color:#ffffff; background-color:#ffffff; height:1px; text-align:left; border-style:none; }
1685 .settingsInfo { display:none; font-style:italic; }
1686 .title { font-weight:bold; font-size:14pt; }
1687 .textInput { width:90%; }
1688 .credits { margin-left:40px; text-indent: -20px; margin-bottom:0px; }
1689 #homescreenView { width: 315px; height:91px; overflow:hidden; }
1690 #calendarList { position:absolute; left:5px; top:4px; width:295px; height:75px; overflow:hidden; }
1691 #name { text-align:center; }
1692 #appicon { display: block; margin-left: auto; margin-right: auto; margin-top: 10px; }
1693 #smallappicon { width:22px; height:22px; margin-right:10px; float:left; }
1694 </style>
1695
1696 </head>
1697
1698 <body onload="javascript:setTimeout('init()', 10)" onresize="javascript:updateScreen()" id="body" class="background">
1699 <div id="homescreenView">
1700 <div id="calendarList">loading...</div>
1701 </div>
1702 <div id="fullscreenView" style="display:none;">
1703 <img src="Icon.png" id="smallappicon">
1704 <h1 class="title">Coming Next</h1>
1705 <hr />
1706 <div id="fullscreenCalendarList">loading...</div>
1707 </div>
1708 <div id="settingsView" style="display:none">
1709 <img src="Icon.png" id="smallappicon">
1710 <h1 id="settingsTitle" class="title">Settings</h1>
1711 <hr />
1712 <div id="settingsList"></div>
1713 </div>
1714 <div id="aboutView" style="display:none">
1715 <img src="Icon.png" id="appicon">
1716 <h1 id="name">Coming Next</h1>
1717 <hr />
1718 <p>Created by Dr. Cochambre and Michael Prager.</p>
1719 <p>Contributions:</p>
1720 <p class="credits">Paul Moore (bug fixes, new features and code cleanup)</p>
1721 <p class="credits">Manfred Hanselmann (DST support)</p>
1722 <p class="credits">Christophe Milsent (translation support & french translation)</p>
1723 <p class="credits">Flavio Nathan (portuguese-brazilian translation)</p>
1724 <p class="credits">Tokeda (russian translation)</p>
1725 <p class="credits">Marcella Ferrari (italian translation)</p>
1726 <p class="credits">Venos (italian translation)</p>
1727 <p>This software is open source and licensed under the GPLv3.</p>
1728 <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>
1729 <hr />
1730 </div>
1731 <div id="updateView" style="display:none">
1732 <img src="Icon.png" id="smallappicon">
1733 <h1 class="title">Check for update</h1>
1734 <hr />
1735 <div id="currentVersion">Coming Next ??</div>
1736 <div id="updateDiv"></div>
1737 <div id="tmp" style="display:none;"></div>
1738 </div>
1739 </body>
1740
1741 </html>