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