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