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