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