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