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