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