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