]> code.delx.au - comingnext/blob - comingNext/index.html
518c31d5331c6053932d55d13c9dcd24e0a862f8
[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 == null) {
545 // Something went wrong fetching the calendars list.
546 // This usually happens when a backup is being made.
547 // Retry the next time updateData() is called by
548 // resetting errorOccured
549 log('updateData(): listCalendars() failed, trying again laster...');
550 cacheEntriesHtml = ''; // make sure we replace the currently shown error message on the next update
551 errorOccured = false;
552 return;
553 }
554 if (newCalendarList.length != calendarList.length) {
555 calendarList = newCalendarList;
556 updateCalendarColors();
557 cancelNotification();
558 requestNotification();
559 lastReloadTime = null; // force calendar data reload on this update
560 }
561
562 now = new Date();
563
564 // only reload calendar data every 6 hours, visual updates occure more often
565 if (!lastReloadTime || now.getTime() - lastReloadTime.getTime() > reloadInterval) {
566 log('updateData(): reloading calendar data');
567 try {
568 // meetings have time
569 // 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
570 summertime = isSummertime(now); // cache summer time info for today
571 var meetingList = [];
572 for(var i=0; i < calendarList.length; i++) {
573 // ignore excluded calendars
574 if (config['excludedCalendars'].Value.indexOf(calendarList[i]) != -1)
575 continue;
576 var meetingListFiltering = {
577 Type:'CalendarEntry',
578 Filter:{
579 CalendarName: calendarList[i],
580 StartRange: (new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0)),
581 EndRange: (new Date(now.getFullYear(), now.getMonth() + config['monthRange'].Value, now.getDate(), 0, 0, 0))
582 }
583 }
584 var meetingResult = calendarService.IDataSource.GetList(meetingListFiltering);
585 if (meetingResult.ErrorCode != 0)
586 throw("Error fetching calendar data: " + meetingResult.ErrorCode + ': ' + meetingResult.ErrorMessage);
587 var list = meetingResult.ReturnValue;
588 meetingList = meetingList.concat(listToArray(list, calendarList[i]));
589 }
590 log("updateData(): meetingList.sort()");
591 meetingList.sort(sortCalendarEntries);
592
593 // todos don't, they start on 00:00 hrs., but should be visible anyway
594 // this will generate a list of passed todos. We have to check if they have been marked as "done" yet
595 if (config['includeTodos'].Value) {
596 var todayTodoList = [];
597 for(var i=0; i < calendarList.length; i++) {
598 // ignore excluded calendars
599 if (config['excludedCalendars'].Value.indexOf(calendarList[i]) != -1)
600 continue;
601 var todayTodoListFiltering = {
602 Type:'CalendarEntry',
603 Filter:{
604 CalendarName: calendarList[i],
605 Type: 'ToDo',
606 StartRange: (new Date(now.getFullYear() - 1, now.getMonth(), now.getDate(), 0, 0, 0)),
607 EndRange: (new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 1))
608 }
609 }
610 var todayTodoResult = calendarService.IDataSource.GetList(todayTodoListFiltering);
611 var list = todayTodoResult.ReturnValue;
612 todayTodoList = todayTodoList.concat(listToArray(list, calendarList[i]));
613 }
614 log("updateData(): todayTodoList.sort()");
615 todayTodoList.sort(sortCalendarEntries);
616 entryLists = [todayTodoList, meetingList];
617 } else {
618 entryLists = [meetingList];
619 }
620 lastReloadTime = new Date();
621 } catch(e) {
622 error('loading Calendar items list:' + e + ', line ' + e.line);
623 return;
624 }
625 }
626
627 try {
628 var entry;
629 var counter = 0;
630 var entryDate = '';
631 var dateArr = [];
632 var fontsize = 'normal';
633 if (mode == 0) {
634 if (config['eventsPerWidget'].Value == 3) {
635 fontsize = '17pt';
636 changeCssClass('.icon', 'width:20px; height:20px');
637 }
638 else if (config['eventsPerWidget'].Value == 5) {
639 fontsize = '10pt';
640 changeCssClass('.icon', 'width:10px; height:10px');
641 }
642 else if (config['eventsPerWidget'].Value == 6) {
643 fontsize = '8pt';
644 changeCssClass('.icon', 'width:8px; height:8px');
645 }
646 }
647 else
648 changeCssClass('.icon', config['cssStyle_icon'].Value);
649 var entriesHtml = '<table style="font-size:' + fontsize + ';">';
650 var eventIds = [];
651 var max;
652 if (mode == 0)
653 max = (panelNum + 1) * config['eventsPerWidget'].Value;
654 else
655 max = 30; // we can display a lot more events in fullscreen mode
656
657 if (config['enableLogging'].Value) {
658 var listinfo = "";
659 for (var i=0; i < entryLists.length; i++) {
660 listinfo = listinfo + " " + entryLists[i].length;
661 var entrieslist = "";
662 for (var j=0; j < entryLists[i].length; j++) {
663 entrieslist += entryLists[i][j].Summary + ", ";
664 }
665 log("updateData(): entrieslist: " + entrieslist);
666 }
667 log("updateData(): inner loop, " + entryLists.length + " lists, [" + listinfo + "] entries");
668 }
669
670 // the first outer loop iteration is for passed ToDos, the second loop is for all upcomming events (may also include ToDos)
671 for (var i=0; counter < max && i < entryLists.length; i++) {
672 for (var j=0; (counter < max) && (j < entryLists[i].length); j++) {
673 entry = entryLists[i][j];
674 counter++;
675
676 // output event info for debugging
677 var entryInfo = "event: ";
678 for(var k=0; k < entryFields.length; ++k) {
679 if (entry[entryFields[k]] != undefined) {
680 entryInfo += entryFields[k] + "=" + entry[entryFields[k]] + ",";
681 }
682 }
683 log(entryInfo);
684
685 // we don't want ToDos when includeTodos == false or when they are completed
686 if (entry.Type == 'ToDo' && (entry.Status == "TodoCompleted" || !config['includeTodos'].Value)) {
687 log('skipping ' + entry.id );
688 counter--;
689 continue;
690 }
691
692 // make sure that we don't include an event twice (useful for ToDos that might come up twice)
693 if (eventIds[entry.id] == 1 && entry.Type == 'ToDo') {
694 log('skipped (already included) ' + entry.id);
695 counter--;
696 continue;
697 } else
698 eventIds[entry.id] = 1;
699
700 // summary can be undefined!
701 var Summary = ((entry.Summary == null) ? '' : entry.Summary);
702 if (entry.Location != '' && entry.Location != undefined && config['showLocation'].Value)
703 Summary += ', ' + entry.Location;
704
705 // fix by yves: determine start and end dates/times
706 entryStartTime = ((entry.InstanceStartTime == null) ? entry.StartTime : entry.InstanceStartTime);
707 entryEndTime = ((entry.InstanceEndTime == null) ? entry.EndTime : entry.InstanceEndTime);
708
709 // there can be ToDos that have no date at all!
710 if (entry.Type == 'ToDo' && entry.EndTime == null)
711 entryDate = ""; // this will cause parseDate(entryDate) to return null;
712 else
713 entryDate = ((entry.Type == 'ToDo') ? entryEndTime : entryStartTime); // ToDo's use their EndTime, the rest use StartTime
714
715 // Convert date/time string to Date object
716 var date = parseDate(entryDate);
717 log('date: ' + date);
718 var endDate = ((entryEndTime == null) ? null : parseDate(entryEndTime));
719 log('endDate: ' + endDate);
720
721 // check if meeting event has already passed
722 if (entry.Type == 'Meeting') {
723 var compareTime = ((endDate == null) ? date.getTime() : endDate.getTime());
724 if (now.getTime() > compareTime) {
725 log('skipping Meeting (already passed) ' + entry.id);
726 counter--;
727 eventIds[entry.id] = 0;
728 continue;
729 }
730 }
731
732 // check if anniversary passed (not sure why they are in the list, the query was only for today - nokia?)
733 if (entry.Type == 'Anniversary') {
734 var tmp = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0,0,0);
735 if (date.getTime() < tmp.getTime()) {
736 log('skipping Anniversary (already passed) ' + entry.id);
737 counter--;
738 eventIds[entry.id] = 0;
739 continue;
740 }
741 }
742
743 // fix DayEvents end time. End times are off by 1 Second. It's possible that the event has already passed
744 if (entry.Type == 'DayEvent' && endDate != null) {
745 endDate.setMinutes(endDate.getMinutes() - 1);
746 log('fixing DayEvent endDate: ' + endDate);
747 if (now.getTime() > endDate.getTime()) {
748 log('event already passed ' + entry.id);
749 counter--;
750 eventIds[entry.id] = 0;
751 continue;
752 }
753 }
754
755 // check if the event is currently taking place
756 if (entryStartTime != null && entryEndTime != null && date != null && endDate != null) {
757 // check if we are between start and endtime
758 if ((date.getTime() < now.getTime()) && (now.getTime() < endDate.getTime())) {
759 date = now; // change appointment date/time to now
760 log('event is currently taking place: ' + date);
761 }
762 }
763
764 // skip events for the first panel in case this is the second one and we're not in fullscreen mode
765 if (mode == 0 && panelNum > 0 && counter < panelNum * config['eventsPerWidget'].Value + 1) {
766 log('skipping (already in first widget) ' + entry.id);
767 continue;
768 }
769
770 // mark overdue todos
771 var overdue = false;
772 if (entry.Type == 'ToDo' && date != null) {
773 var tmp1 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0,0,0);
774 var tmp2 = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0,0,0);
775 if (tmp1.getTime() < tmp2.getTime()) {
776 overdue = true;
777 }
778 }
779
780 // generate html output
781 entriesHtml += '<tr>';
782 if (config['showCalendarIndicator'].Value && calendarList.length - config['excludedCalendars'].Value.length > 1) {
783 entriesHtml += '<td><span class="calendar' + calendarColors[entry.CalendarName] + '">&nbsp;</span></td>';
784 }
785 entriesHtml += '<td><img class="icon" src="' + entry.Type + '.png" /></td>';
786 if(date == null) {
787 // some languages have very strange locale date formats, can't parse all those. Also some todos don't have dates at all.
788 entriesHtml += '<td colspan="4"><span class="date">' + entryDate + '</span> ';
789 } else {
790 var weekDay = date.toLocaleDateString().substr(0,config['weekDayLength'].Value);
791 var time = formatTime(date);
792 var dateStr = formatDate(date, entryDate);
793 if (entry.Type == 'ToDo' && overdue && config['markOverdueTodos'].Value) {
794 dateStr = '<span class="overdue">' + config['overdueText'].Value + '</span>';
795 entriesHtml += '<td colspan="4" width="1px"><span class="date">' + dateStr + '</span> ';
796 } else if (entry.Type == 'ToDo' || entry.Type == 'Anniversary' || entry.Type == 'DayEvent' || entry.Type == 'Reminder') {
797 if ((isToday(date) || isTomorrow(date)) && config['showTodayAsText'].Value) // show weekday if the date string is not text. looks odd otherwise
798 entriesHtml += '<td colspan="4" width="1px"><span class="date">' + dateStr + '</span> ';
799 else
800 entriesHtml += '<td class="weekDay" width="1px">' + weekDay + '</td><td width="1px" class="date">' + dateStr + '</td><td colspan="2">';
801 } else if (entry.Type == 'Meeting') {
802 if (config['showCombinedDateTime'].Value) {
803 if (isToday(date))
804 entriesHtml += '<td width="1px" colspan="4"><span class="today">' + time + '</span> ';
805 else if (isTomorrow(date))
806 entriesHtml += '<td width="1px" colspan="4"><span class="tomorrow">' + dateStr + '</span> <span class="time">' + time + '</span> ';
807 else
808 entriesHtml += '<td width="1px" class="weekDay">' + weekDay + '</td><td width="1px" class="date">' + dateStr + '</td><td colspan="2">';
809 } else {
810 if ((isToday(date) || isTomorrow(date)) && config['showTodayAsText'].Value)
811 entriesHtml += '<td colspan="4" width="1px"><span class="today">' + dateStr + '</span> <span class="time">' + time + '</span> ';
812 else
813 entriesHtml += '<td width="1px" class="weekDay">' + weekDay + '</td><td width="1px" class="date">' + dateStr + '</td><td width="1px" class="time">' + time + '</td><td>';
814 }
815 }
816 }
817 entriesHtml += '<span class="description">' + Summary + '</span></td></tr>';
818 }
819 }
820 entriesHtml += '</table>';
821 if (config['showNothingText'].Value && entriesHtml == '<table></table>') {
822 var text = config['nothingText'].Value.replace(/%d/, config['monthRange'].Value);
823 entriesHtml = '<div style="width:295px; height:75px; text-align:center; line-height:75px; overflow:visible;">' + text + '</div>';
824 }
825 if (cacheEntriesHtml != entriesHtml) {
826 if (mode == 0)
827 document.getElementById('calendarList').innerHTML = entriesHtml;
828 else
829 document.getElementById('fullscreenCalendarList').innerHTML = entriesHtml;
830 cacheEntriesHtml = entriesHtml;
831 }
832
833 lastUpdateTime = new Date();
834 } catch(e) {
835 error('displaying list:' + e + ', line ' + e.line);
836 return;
837 }
838 }
839
840 // called by handleOnShow() and onResize events
841 function updateScreen()
842 {
843 log('updateScreen()');
844
845 // check if opening fullscreen
846 if( window.innerHeight > 91 && mode == 0) {
847 mode = 1;
848 cacheEntriesHtml = '';
849 document.getElementById('body').style.backgroundImage = "";
850 showFullscreen();
851 }
852 else if (window.innerHeight <= 91 && mode != 0) {
853 mode = 0;
854 cacheEntriesHtml = '';
855 showHomescreen();
856 }
857
858 if (mode == 0)
859 updateHomescreen(); // check for screen rotation
860 else if (mode == 1)
861 updateFullscreen();
862 }
863
864 function handleOnShow()
865 {
866 updateScreen();
867
868 var time = new Date();
869 if (time.getTime() - lastUpdateTime.getTime() > config['updateDataInterval'].Value * 60 * 1000) {
870 log('updateScreen(): force updateData() because last update was too long ago (' + (time.getTime() - lastUpdateTime.getTime()) / 1000 + 's)');
871 clearUpdateTimer();
872 updateData();
873 setUpdateTimer(); // reinitialize update timer
874 }
875 }
876
877 function launchCalendar()
878 {
879 try {
880 widget.openApplication(config['calendarApp'].Value, "");
881 if (config['hideWidgetOnCalendarOpen'].Value)
882 window.close();
883 } catch(e) {
884 error('starting Calendar App');
885 return;
886 }
887 }
888
889 function init()
890 {
891 log('New widget instance starting up...');
892
893 try {
894 // call calendar service
895 if (device != "undefined")
896 calendarService = device.getServiceObject("Service.Calendar", "IDataSource");
897 else
898 throw('device object does not exist');
899 } catch(e) {
900 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>');
901 //return;
902 }
903
904 calendarList = listCalendars();
905 loadSettings();
906 updateCalendarColors();
907 collectLocales();
908 //updateData();
909 requestNotification();
910 document.getElementById("settingsTitle").innerHTML = getLocalizedText('menu.settings');
911 setUpdateTimer();
912 if (window.innerHeight > 91) {
913 mode = 0; // we're starting fullscreen, we set mode to homescreen in order to let updateScreen() do all the work for us
914 }
915 else {
916 mode = 1;
917 }
918 log("init(): updateScreen()");
919 updateScreen();
920 if (config['useBackgroundImage'].Value)
921 // check for screen rotation every 1 secs
922 screenRotationTimer = window.setInterval('checkOrientation()', 1000 * 1);
923
924 // call updateScreen() when widget changes from background to forground
925 window.widget.onshow = handleOnShow;
926
927 log("init(): finished...");
928 if (!errorOccured)
929 statupSuccessful = true;
930 }
931
932 function checkOrientation()
933 {
934 //updateScreen();
935 if (mode == 0)
936 updateHomescreen(); // check for screen rotation
937 }
938
939 function setUpdateTimer()
940 {
941 updateTimer = window.setInterval('updateTimerCallback()', 1000 * 60 * config['updateDataInterval'].Value);
942 }
943
944 function clearUpdateTimer()
945 {
946 window.clearInterval(updateTimer);
947 }
948
949 function updateTimerCallback()
950 {
951 log("updateTimerCallback()");
952 updateData();
953 }
954
955 function createMenu()
956 {
957 window.menu.setLeftSoftkeyLabel("",null);
958 window.menu.setRightSoftkeyLabel("",null);
959 var id = 0;
960 var menuSettings = new MenuItem(getLocalizedText('menu.settings'), id++);
961 var menuCallApp = new MenuItem(getLocalizedText('menu.openCalendarApp'), id++);
962 var menuHelp = new MenuItem(getLocalizedText('menu.help'), id++);
963 var menuUpdate = new MenuItem(getLocalizedText('menu.update'), id++);
964 var menuAbout = new MenuItem(getLocalizedText('menu.about'), id++);
965 menuSettings.onSelect = showSettings;
966 menuAbout.onSelect = showAbout;
967 menuCallApp.onSelect = launchCalendar;
968 menuUpdate.onSelect = showUpdate;
969 menuHelp.onSelect = showHelp;
970 window.menu.clear();
971 window.menu.append(menuCallApp);
972 window.menu.append(menuSettings);
973 window.menu.append(menuHelp);
974 window.menu.append(menuUpdate);
975 window.menu.append(menuAbout);
976 }
977
978 function showSettings()
979 {
980 mode = 2;
981 hideViews();
982 document.getElementById("settingsView").style.display = "block";
983 document.onclick = null;
984
985 window.menu.setLeftSoftkeyLabel(getLocalizedText('settings.save'), function()
986 {
987 for (var key in config) {
988 if (config[key].Type == 'String')
989 config[key].Value = document.forms[0].elements["settings." + key].value;
990 else if (config[key].Type == 'Int') {
991 config[key].Value = parseInt(document.forms[0].elements["settings." + key].value);
992 if (config[key].Value < 0 || isNaN(config[key].Value))
993 config[key].Value = config[key].Default;
994 }
995 else if (config[key].Type == 'Bool')
996 config[key].Value = document.forms[0].elements["settings." + key].checked;
997 else if (config[key].Type == 'UID') {
998 config[key].Value = parseInt(document.forms[0].elements["settings." + key].value);
999 if (isNaN(config[key].Value))
1000 config[key].Value = config[key].Default;
1001 }
1002 else if (config[key].Type == 'Enum') {
1003 config[key].Value = document.forms[0].elements["settings." + key].value;
1004 if (config[key].ValidValues.indexOf(config[key].Value) == -1)
1005 config[key].Value = config[key].Default;
1006 }
1007 else if (config[key].Type == 'Array') {
1008 if (key == 'excludedCalendars') {
1009 config[key].Value = new Array();
1010 for(var i=0; i < calendarList.length; i++) {
1011 var element = document.forms[0].elements["settings." + key + "." + calendarList[i]];
1012 if (element != null && element.checked == false)
1013 config[key].Value.push(calendarList[i]);
1014 }
1015 }
1016 }
1017 }
1018
1019 updateCssClasses();
1020
1021 saveSettings();
1022
1023 mode = 1;
1024 showFullscreen();
1025 });
1026 window.menu.setRightSoftkeyLabel(getLocalizedText('settings.cancel'), function()
1027 {
1028 mode = 1;
1029 showFullscreen();
1030 });
1031
1032 var settingsHtml = '<form>';
1033 for (var key in config) {
1034 if (config[key].Type == 'String') {
1035 var prefix = "";
1036 if (key.substring(0,9) == "cssStyle_")
1037 prefix = getLocalizedText('settings.cssStyle_prefix');
1038 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 />';
1039 }
1040 else if (config[key].Type == 'Int')
1041 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 />';
1042 else if (config[key].Type == 'Bool')
1043 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 />';
1044 else if (config[key].Type == 'UID')
1045 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 />';
1046 else if (config[key].Type == 'Enum') {
1047 settingsHtml += '<table><tr><td>' + getLocalizedText('settings.name.' + key) + '<br /><select name="settings.' + key + '" size="1">';
1048 for(var i = 0; i < config[key].ValidValues.length; i++)
1049 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>';
1050 settingsHtml += '</select></div></td>' + printHintBox(getLocalizedText('settings.info.' + key)) + '<hr />';
1051 }
1052 else if (config[key].Type == 'Array') {
1053 settingsHtml += '<table><tr><td>' + getLocalizedText('settings.name.' + key) + '<br />';
1054 if (key == 'excludedCalendars') {
1055 for(var i=0; i < calendarList.length; i++) {
1056 var checked = 'checked="checked"';
1057 if (config[key].Value.indexOf(calendarList[i]) != -1)
1058 checked = '';
1059 settingsHtml += '<input name="settings.' + key + '.' + calendarList[i] + '" type="checkbox" value="' + calendarList[i] + '" ' + checked + '/> ' + calendarList[i] + '<br />';
1060 }
1061 }
1062 settingsHtml += '</td>' + printHintBox(getLocalizedText('settings.info.' + key)) + '<hr />';
1063 }
1064 }
1065 settingsHtml += '<input name="reset" type="button" value="' + getLocalizedText('settings.restoreDefaults') + '" onclick="javascript:restoreDefaultSettings();showSettings();" />';
1066 settingsHtml += '</form>';
1067 document.getElementById("settingsList").innerHTML = settingsHtml;
1068 }
1069
1070 function changeCssClass(classname, properties)
1071 {
1072 for(var i = 0; i < document.styleSheets[0]['cssRules'].length; i++)
1073 {
1074 if (document.styleSheets[0]['cssRules'][i].selectorText == classname) {
1075 document.styleSheets[0].deleteRule(i);
1076 document.styleSheets[0].insertRule(classname + ' { ' + properties + ' }', document.styleSheets[0]['cssRules'].length);
1077 break;
1078 }
1079 }
1080 }
1081
1082 function updateCssClasses()
1083 {
1084 for(var key in config) {
1085 changeCssClass(getLocalizedText('settings.name.' + key), config[key].Value);
1086 }
1087 }
1088
1089 function getSettingsCalEntryId()
1090 {
1091 if (settingsCalEntryId == null) {
1092 // check if entry already exists
1093 var listFiltering = {
1094 Type:'CalendarEntry',
1095 Filter:{
1096 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!
1097 EndRange: new Date(2000, 0, 2),
1098 SearchText: 'ComingNext Settings|',
1099 Type: 'DayEvent'
1100 }
1101 }
1102 var result = null;
1103 try {
1104 result = calendarService.IDataSource.GetList(listFiltering);
1105 if (result.ErrorCode)
1106 throw(result.ErrorMessage);
1107 }
1108 catch (e) {
1109 error("getSettingsCalEntryId: GetList() failed: " + e + ', line ' + e.line);
1110 return;
1111 }
1112 var list = result.ReturnValue;
1113 var entry = list.getNext();
1114 if (entry != undefined) {
1115 settingsCalEntryId = entry.LocalId;
1116 log("settingsCalEntryId=" + settingsCalEntryId);
1117 }
1118 else { // create settings item
1119 var item = new Object();
1120 item.Type = "DayEvent";
1121 item.StartTime = new Date(2000, 0, 1);
1122 item.Summary = "ComingNext Settings|";
1123
1124 var criteria = new Object();
1125 criteria.Type = "CalendarEntry";
1126 criteria.Item = item;
1127
1128 try {
1129 var result = calendarService.IDataSource.Add(criteria);
1130 if (result.ErrorCode)
1131 throw(result.ErrorMessage);
1132 } catch (e) {
1133 error("getSettingsCalEntryId: " + e + ', line ' + e.line);
1134 }
1135
1136 getSettingsCalEntryId();
1137 }
1138 }
1139 }
1140
1141 function restoreDefaultSettings()
1142 {
1143 for (var key in config)
1144 config[key].Value = config[key].Default;
1145 }
1146
1147 function loadSettings()
1148 {
1149 getSettingsCalEntryId();
1150 var listFiltering = {
1151 Type:'CalendarEntry',
1152 Filter:{
1153 LocalId: settingsCalEntryId
1154 }
1155 }
1156 var result = null;
1157 try {
1158 result = calendarService.IDataSource.GetList(listFiltering);
1159 if (result.ErrorCode)
1160 throw(result.ErrorMessage);
1161 }
1162 catch (e) {
1163 error("loadSettings: GetList() failed: " + e + ', line ' + e.line);
1164 return;
1165 }
1166 var entry = result.ReturnValue.getNext();
1167 if (entry != undefined) {
1168 log("Loading Settings...");
1169 // only reload settings if they chanced since the last reload
1170 if (settingsCache != entry.Summary)
1171 {
1172 restoreDefaultSettings();
1173 var stringlist = entry.Summary.split("|");
1174 // skip the first two entries, those contain header and version info
1175 for(var i = 2; i < stringlist.length - 1; i++) {
1176 var pair = stringlist[i].split('=');
1177 var key = pair[0];
1178 var value = pair[1];
1179 if (key == null || value == null || config[key] == null) {
1180 log('Warning: unknown or invalid setting: ' + stringlist[i]);
1181 continue;
1182 }
1183 log('stringlist: ' + key + '=\'' + value + '\'');
1184 if (config[key].Type == 'Int') {
1185 config[key].Value = Number(value);
1186 if (isNaN(config[key].Value))
1187 config[key].Value = config[key].Default;
1188 }
1189 else if (config[key].Type == 'String')
1190 config[key].Value = value;
1191 else if (config[key].Type == 'Bool')
1192 config[key].Value = (value == 'true')
1193 else if (config[key].Type == 'Enum')
1194 config[key].Value = value;
1195 else if (config[key].Type == 'UID') {
1196 config[key].Value = Number(value);
1197 if (isNaN(config[key].Value))
1198 config[key].Value = config[key].Default;
1199 }
1200 else if (config[key].Type == 'Array') {
1201 config[key].Value = value.split("^");
1202 if (config[key].Value.length == 1 && config[key].Value[0] == "") {
1203 config[key].Value = [];
1204 }
1205 }
1206 }
1207 settingsCache = entry.Summary;
1208 updateCssClasses();
1209 }
1210 else {
1211 log("Settings already cached and did not change");
1212 }
1213 }
1214 else {
1215 error("Failed to load settings, calendar entry could not be found");
1216 }
1217 }
1218
1219 function saveSettings()
1220 {
1221 getSettingsCalEntryId();
1222 var item = new Object();
1223 item.Type = "DayEvent";
1224 item.StartTime = new Date(2000, 0, 1);
1225 item.LocalId = settingsCalEntryId;
1226 item.Summary = "ComingNext Settings|" + version + "|";
1227
1228 for (var key in config) {
1229 if (config[key].Type == 'Int')
1230 item.Summary += key + "=" + config[key].Value.toString() + "|";
1231 else if (config[key].Type == 'String')
1232 item.Summary += key + "=" + config[key].Value + "|";
1233 else if (config[key].Type == 'Bool')
1234 item.Summary += key + "=" + (config[key].Value ? 'true' : 'false') + "|";
1235 else if (config[key].Type == 'Enum')
1236 item.Summary += key + "=" + config[key].Value + "|";
1237 else if (config[key].Type == 'UID')
1238 item.Summary += key + "=" + config[key].Value.toString() + "|";
1239 else if (config[key].Type == 'Array')
1240 item.Summary += key + "=" + config[key].Value.join("^") + "|";
1241 }
1242 settingsCache = item.Summary;
1243
1244 var criteria = new Object();
1245 criteria.Type = "CalendarEntry";
1246 criteria.Item = item;
1247
1248 log("Saving settings to calendar entry: " + item.Summary);
1249 try {
1250 var result = calendarService.IDataSource.Add(criteria);
1251 if (result.ErrorCode)
1252 throw(result.ErrorMessage);
1253 } catch (e) {
1254 error("saveSettings: " + e + ', line ' + e.line);
1255 }
1256
1257 lastReloadTime = null; // force calendar data reload on next update
1258 clearUpdateTimer();
1259 setUpdateTimer();
1260 }
1261
1262 function toggleVisibility(elementId)
1263 {
1264 if (document.getElementById(elementId).style.display == "none")
1265 document.getElementById(elementId).style.display = "block";
1266 else
1267 document.getElementById(elementId).style.display = "none";
1268 }
1269
1270 var uniqueId = 0;
1271 function printHintBox(text)
1272 {
1273 uniqueId++;
1274 return '<td width="1%" align="right" onclick="javascript:toggleVisibility(\'info' + uniqueId + '\')">' + getLocalizedText('settings.help') + '</td></tr></table>'+
1275 '<div class="settingsInfo" id="info' + uniqueId + '">' + text + '</div>';
1276 }
1277
1278 function showAbout()
1279 {
1280 mode = 3;
1281 hideViews();
1282 document.getElementById("aboutView").style.display = "block";
1283 document.onclick = null;
1284
1285 window.menu.setLeftSoftkeyLabel(" ", function(){});
1286 window.menu.setRightSoftkeyLabel(getLocalizedText('softkey.back'), function()
1287 {
1288 mode = 1;
1289 showFullscreen();
1290 });
1291
1292 //document.getElementById("aboutView").innerHTML = 'aboutView';
1293 document.getElementById("name").innerHTML = "Coming Next " + version;
1294 }
1295
1296 function showHelp() {
1297 widget.openURL('http://comingnext.sf.net/help');
1298 }
1299
1300 function updateFullscreen()
1301 {
1302 }
1303
1304 function showFullscreen()
1305 {
1306 log("showFullscreen()");
1307 hideViews();
1308 document.getElementById("fullscreenView").style.display = "block";
1309 document.getElementById('body').className = "backgroundFullscreen";
1310 if (!errorOccured)
1311 document.onclick = launchCalendar;
1312 createMenu();
1313 updateData();
1314 }
1315
1316 function getBackgroundImage()
1317 {
1318 if (errorOccured)
1319 return '';
1320 var bgImage;
1321 if (config['backgroundImageLocation'].Value == config['backgroundImageLocation'].ValidValues[0]) // internal
1322 bgImage = 'background_' + orientation + '.png';
1323 else
1324 bgImage = 'C:/Data/background_' + panelNum + '_' + orientation + '.png';
1325 return bgImage;
1326 }
1327
1328 function updateHomescreen()
1329 {
1330 if (config['useBackgroundImage'].Value) {
1331 // check if we have a completely unknown screen resolution
1332 var screenHeight = screen.height;
1333 var screenWidth = screen.width;
1334 if (screenHeight != 640 && screenHeight != 480 && screenHeight != 360)
1335 screenHeight = 360; // we can only assume we're in portrait mode, so we set the screen dims as needed for the following code
1336 if (screenWidth != 640 && screenWidth != 480 && screenWidth != 360)
1337 screenWidth = 640; // we can only assume we're in portrait mode, so we set the screen dims as needed for the following code
1338
1339 // check for screen rotation
1340 if (orientation != 'portrait' && ((screenWidth == 360 && screenHeight == 640) || (screenWidth == 640 && screenHeight == 480))) {
1341 window.widget.prepareForTransition("fade");
1342 orientation = 'portrait';
1343 document.getElementById('body').style.backgroundImage = 'url(' + getBackgroundImage() + ')';
1344 document.getElementById('body').style.backgroundColor = 'none';
1345 window.widget.performTransition();
1346 } else if (orientation != 'landscape' && ((screenWidth == 640 && screenHeight == 360) || (screenWidth == 480 && screenHeight == 640))) {
1347 window.widget.prepareForTransition("fade");
1348 orientation = 'landscape';
1349 document.getElementById('body').style.backgroundImage = 'url(' + getBackgroundImage() + ')';
1350 document.getElementById('body').style.backgroundColor = 'none';
1351 window.widget.performTransition();
1352 }
1353 else if (document.getElementById('body').style.backgroundImage == "")
1354 {
1355 document.getElementById('body').style.backgroundImage = 'url(' + getBackgroundImage() + ')';
1356 }
1357 }
1358 }
1359
1360 function showHomescreen()
1361 {
1362 log("showHomescreen()");
1363 hideViews();
1364 document.getElementById("homescreenView").style.display = "block";
1365 document.getElementById('body').className = "background";
1366 document.onclick = null;
1367 updateData();
1368 }
1369
1370 function getLocalizedText(p_Txt)
1371 {
1372 if (localizedText[p_Txt])
1373 return localizedText[p_Txt];
1374 else
1375 return 'ERROR: missing translation for ' + p_Txt;
1376 }
1377
1378 function showUpdate()
1379 {
1380 mode = 4;
1381 hideViews();
1382 document.getElementById("updateView").style.display = "block";
1383 document.onclick = null;
1384
1385 window.menu.setLeftSoftkeyLabel(getLocalizedText('update.checknow'), function(){
1386 checkForUpdate();
1387 });
1388 window.menu.setRightSoftkeyLabel(getLocalizedText('softkey.back'), function()
1389 {
1390 mode = 1;
1391 showFullscreen();
1392 });
1393
1394 document.getElementById("currentVersion").innerHTML = getLocalizedText("update.current") + version;
1395 checkForUpdate();
1396 }
1397
1398 function checkForUpdate()
1399 {
1400 // asynch XHR to server url
1401 reqV = new XMLHttpRequest();
1402 reqV.onreadystatechange = checkForUpdateCallback;
1403 document.getElementById("updateDiv").innerHTML = getLocalizedText("update.checking");
1404 reqV.open("GET", versionURL, true);
1405 reqV.setRequestHeader( "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT" ); // disable caching
1406 reqV.send(null);
1407 }
1408
1409 function checkForUpdateCallback()
1410 {
1411 if (reqV.readyState == 4) {
1412 if (reqV.status == 200) {
1413 var resultXml = reqV.responseText;
1414 if (resultXml) {
1415 var div = document.getElementById("tmp");
1416 div.innerHTML = resultXml;
1417 var newVersion = div.getElementsByTagName('version')[0].innerHTML;
1418 var newVersionURL = div.getElementsByTagName('url')[0].innerHTML;
1419 div.innerHTML = "";
1420 if (version != newVersion) {
1421 document.getElementById("updateDiv").innerHTML = getLocalizedText("update.download").replace(/%1/, newVersion).replace(/%2/, newVersionURL);
1422 }
1423 else {
1424 document.getElementById("updateDiv").innerHTML = getLocalizedText("update.nonewversion");
1425 }
1426 }
1427 }
1428 else {
1429 document.getElementById("updateDiv").innerHTML = getLocalizedText("update.error") + reqV.status + " " + reqV.responseText;
1430 }
1431 }
1432 }
1433
1434 function hideViews()
1435 {
1436 document.getElementById("homescreenView").style.display = "none";
1437 document.getElementById("fullscreenView").style.display = "none";
1438 document.getElementById("aboutView").style.display = "none";
1439 document.getElementById("settingsView").style.display = "none";
1440 document.getElementById("updateView").style.display = "none";
1441 }
1442
1443 function listCalendars()
1444 {
1445 if (errorOccured) {
1446 return null;
1447 }
1448
1449 try {
1450 var criteria = {
1451 Type:'Calendar',
1452 Filter:{
1453 DefaultCalendar: false
1454 }
1455 }
1456
1457 var calendarsResult = calendarService.IDataSource.GetList(criteria);
1458 if (calendarsResult.ErrorCode != 0)
1459 throw("Error fetching list of calendars: " + calendarsResult.ErrorCode + ': ' + calendarsResult.ErrorMessage);
1460 var calendarListIterator = calendarsResult.ReturnValue;
1461
1462 var calendars = [];
1463 var count = 0;
1464 var item;
1465 while (( item = calendarListIterator.getNext()) != undefined ) {
1466 calendars[count++] = item;
1467 }
1468 log("Available Calendars: " + calendars.join(", "));
1469 return calendars;
1470 } catch(e) {
1471 error('listing calendars:' + e + ', line ' + e.line);
1472 return null;
1473 }
1474 }
1475
1476 // Copies all objects and their properties to an array. Data is copied so nothing gets lost when the reference is removed
1477 // 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
1478 function listToArray(list, calendarName)
1479 {
1480 var array = new Array();
1481 var item;
1482 var txt = "";
1483 while (( item = list.getNext()) != undefined ) {
1484 var itemCopy = new Object();
1485 for(var i=0; i < entryFields.length; i++) {
1486 itemCopy[entryFields[i]] = item[entryFields[i]];
1487 }
1488 // for some reason, the CalendarName property is never correctly queried, so we assign it manually here
1489 if (!itemCopy['CalendarName']) {
1490 itemCopy['CalendarName'] = calendarName;
1491 }
1492 array.push(itemCopy);
1493 txt += array[array.length - 1].Summary + ", ";
1494 }
1495 log("listToArray(): " + txt);
1496 return array;
1497 }
1498
1499 function sortCalendarEntries(a, b)
1500 {
1501 var atime, btime;
1502 log("sortCalendarEntries(" + a.Summary + "," + b.Summary + ")");
1503
1504 if (a.InstanceStartTime != null) {
1505 atime = a.InstanceStartTime;
1506 }
1507 else if (a.StartTime != null) {
1508 atime = a.StartTime;
1509 }
1510 else if (a.InstanceEndTime != null) {
1511 atime = a.InstanceEndTime;
1512 }
1513 else if (a.EndTime != null) {
1514 atime = a.EndTime;
1515 }
1516
1517 if (b.InstanceStartTime != null) {
1518 btime = b.InstanceStartTime;
1519 }
1520 else if (b.StartTime != null) {
1521 btime = b.StartTime;
1522 }
1523 else if (b.InstanceEndTime != null) {
1524 btime = b.InstanceEndTime;
1525 }
1526 else if (b.EndTime != null) {
1527 btime = b.EndTime;
1528 }
1529
1530 if (atime && btime) {
1531
1532 atime = parseDate(atime);
1533 btime = parseDate(btime);
1534
1535 // sort by date & time
1536 if (atime < btime) {
1537 return -1;
1538 }
1539 else if (atime > btime) {
1540 return 1;
1541 }
1542 // sort by type
1543 else if (a.Type != b.Type) {
1544 if (a.Type < b.Type) {
1545 return -1;
1546 }
1547 else if (a.Type > b.Type) {
1548 return 1;
1549 }
1550 }
1551 // sort by description
1552 else if (a.Summary && b.Summary && a.Summary != b.Summary) {
1553 if (a.Summary < b.Summary) {
1554 return -1;
1555 }
1556 else if (a.Summary > b.Summary) {
1557 return 1;
1558 }
1559 }
1560 }
1561 // NOTE: events my have no date information at all. In that case, we list events without date first
1562 else if (atime && !btime) {
1563 return 1;
1564 }
1565 else if (!atime && btime) {
1566 return -1;
1567 }
1568 else if (!atime && !btime) {
1569 // sort by type
1570 if (a.Type != b.Type) {
1571 if (a.Type < b.Type) {
1572 return -1;
1573 }
1574 else if (a.Type > b.Type) {
1575 return 1;
1576 }
1577 }
1578 // sort by description
1579 else if (a.Summary && b.Summary && a.Summary != b.Summary) {
1580 if (a.Summary < b.Summary) {
1581 return -1;
1582 }
1583 else if (a.Summary > b.Summary) {
1584 return 1;
1585 }
1586 }
1587 }
1588
1589 return 0;
1590 }
1591
1592 function updateCalendarColors()
1593 {
1594 var maxColors = 6;
1595 calendarColors = [];
1596 if (calendarList.length > maxColors) {
1597 log("updateCalendarColors(): Warning: more calendars than available indicator colors");
1598 }
1599 for(var i=0; i < calendarList.length; i++) {
1600 calendarColors[calendarList[i]] = (i % maxColors) + 1;
1601 }
1602 }
1603
1604 function log(message)
1605 {
1606 if (config['enableLogging'].Value) {
1607 console.info(message);
1608 }
1609 }
1610
1611 </script>
1612
1613 <style type="text/css">
1614 a { color:#aaccff }
1615 table { margin:0px; padding:0px; border-spacing:0px; }
1616 td { padding:0px 5px 0px 0px; white-space:nowrap; overflow:hidden; }
1617 hr { color:#ffffff; background-color:#ffffff; height:1px; text-align:left; border-style:none; }
1618 .settingsInfo { display:none; font-style:italic; }
1619 .title { font-weight:bold; font-size:14pt; }
1620 .textInput { width:90%; }
1621 .credits { margin-left:40px; text-indent: -20px; margin-bottom:0px; }
1622 #homescreenView { width: 315px; height:91px; overflow:hidden; }
1623 #calendarList { position:absolute; left:5px; top:4px; width:295px; height:75px; overflow:hidden; }
1624 #name { text-align:center; }
1625 #appicon { display: block; margin-left: auto; margin-right: auto; margin-top: 10px; }
1626 #smallappicon { width:22px; height:22px; margin-right:10px; float:left; }
1627 </style>
1628
1629 </head>
1630
1631 <body onload="javascript:setTimeout('init()', 10)" onresize="javascript:updateScreen()" id="body" class="background">
1632 <div id="homescreenView">
1633 <div id="calendarList">loading...</div>
1634 </div>
1635 <div id="fullscreenView" style="display:none;">
1636 <img src="Icon.png" id="smallappicon">
1637 <h1 class="title">Coming Next</h1>
1638 <hr />
1639 <div id="fullscreenCalendarList">loading...</div>
1640 </div>
1641 <div id="settingsView" style="display:none">
1642 <img src="Icon.png" id="smallappicon">
1643 <h1 id="settingsTitle" class="title">Settings</h1>
1644 <hr />
1645 <div id="settingsList"></div>
1646 </div>
1647 <div id="aboutView" style="display:none">
1648 <img src="Icon.png" id="appicon">
1649 <h1 id="name">Coming Next</h1>
1650 <hr />
1651 <p>Created by Dr. Cochambre and Michael Prager.</p>
1652 <p>Contributions:</p>
1653 <p class="credits">Paul Moore (bug fixes, new features and code cleanup)</p>
1654 <p class="credits">Manfred Hanselmann (DST support)</p>
1655 <p class="credits">Christophe Milsent (translation support & french translation)</p>
1656 <p class="credits">Flavio Nathan (portuguese-brazilian translation)</p>
1657 <p class="credits">Tokeda (russian translation)</p>
1658 <p class="credits">Marcella Ferrari (italian translation)</p>
1659 <p class="credits">Venos (italian translation)</p>
1660 <p>This software is open source and licensed under the GPLv3.</p>
1661 <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>
1662 <hr />
1663 </div>
1664 <div id="updateView" style="display:none">
1665 <img src="Icon.png" id="smallappicon">
1666 <h1 class="title">Check for update</h1>
1667 <hr />
1668 <div id="currentVersion">Coming Next ??</div>
1669 <div id="updateDiv"></div>
1670 <div id="tmp" style="display:none;"></div>
1671 </div>
1672 </body>
1673
1674 </html>