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