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