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