]> code.delx.au - comingnext/blobdiff - comingNext/index.html
reduced overall widget height available for text (72 instead of 82 pixels) to give...
[comingnext] / comingNext / index.html
index 965702fb3178b2e39ba1c098ddfeec56e2d2ad58..5f845372823a4eb7173b99a7b7ce8e7ec362f2ab 100644 (file)
@@ -7,43 +7,59 @@
 \r
 <style type="text/css">\r
 /* The following classes can be modified by widget settings */\r
-.background { }\r
-.backgroundFullscreen { }\r
+.background { color:#ffffff; background-color:#000000 }\r
+.backgroundFullscreen { color:#ffffff; background-color:#000000 }\r
 .weekDay { }\r
 .date { }\r
-.today { }\r
-.tomorrow { }\r
+.today { color:#ff0000 }\r
+.tomorrow { color:#0000ff }\r
 .time { }\r
-.now { }\r
+.now { color:#ff00ff }\r
 .description { }\r
-.icon { }\r
+.icon { width:15px; height:15px }\r
+.overdue { color:#ffff00 }\r
+.calendar1 { background-color:#0757cf }\r
+.calendar2 { background-color:#579f37 }\r
+.calendar3 { background-color:#ff9f07 }\r
+.calendar4 { background-color:#af8fef }\r
+.calendar5 { background-color:#57afbf }\r
+.calendar6 { background-color:#9fdf57 }\r
 </style>\r
 \r
-<script type="text/javascript" src="localizedTextStrings.js" charset="utf-8" />\r
-\r
-<script>\r
-// valid types for the config object are 'Int', 'Bool', 'String', 'Enum', 'UID'\r
+<script type="text/javascript" src="localizedTextStrings.js" charset="utf-8"></script>\r
+<script type="text/javascript" src="../debug.js" charset="utf-8"></script>\r
+<script type="text/javascript">\r
+// valid types for the config object are 'Int', 'Bool', 'String', 'Enum', 'UID', 'Array'\r
 var config = {\r
+       fontsize: { Type: 'Enum', Default: 'auto', Value: 'auto', ValidValues: ['auto', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28'],},\r
+       eventsPerWidget: { Type: 'Int', Default: 4, Value: 4,},\r
        monthRange: { Type: 'Int', Default: 2, Value: 2,},\r
        includeTodos: { Type: 'Bool', Default: true, Value: true,},\r
        useBackgroundImage: { Type: 'Bool', Default: true, Value: true,},\r
+       backgroundImageLocation: { Type: 'Enum', Default: 'internal', Value: 'internal', ValidValues: ['internal', 'external']},\r
        showCombinedDateTime: { Type: 'Bool', Default: false, Value: false,},\r
        showLocation: { Type: 'Bool', Default: true, Value: true,},\r
+       showIcons: { Type: 'Bool', Default: true, Value: true,},\r
        showTodayAsText: { Type: 'Bool', Default: true, Value: true,},\r
        todayText: { Type: 'String', Default: getLocalizedText('settings.default.todayText'), Value: getLocalizedText('settings.default.todayText'),},\r
        tomorrowText: { Type: 'String', Default: getLocalizedText('settings.default.tomorrowText'), Value: getLocalizedText('settings.default.tomorrowText'),},\r
        showNowAsText: { Type: 'Bool', Default: true, Value: true,},\r
        nowText: { Type: 'String', Default: getLocalizedText('settings.default.nowText'), Value: getLocalizedText('settings.default.nowText'),},\r
+       markOverdueTodos: { Type: 'Bool', Default: true, Value: true,},\r
+       overdueText: {Type: 'String', Default: getLocalizedText('settings.default.overdueText'), Value: getLocalizedText('settings.default.overdueText'),},\r
        dateSeparator: { Type: 'String', Default: getLocalizedText('settings.default.dateSeparator'), Value: getLocalizedText('settings.default.dateSeparator'),},\r
        dateFormat: { Type: 'Enum', Default: 'auto', Value: 'auto', ValidValues: ['auto', 'DDMM', 'MMDD'],},\r
        weekDayLength: { Type: 'Int', Default: 2, Value: 2,},\r
        updateDataInterval: { Type: 'Int', Default: 5, Value: 5,},\r
        calendarApp: { Type: 'UID', Default: 0x10005901, Value: 0x10005901,},\r
-       eventsPerWidget: { Type: 'Int', Default: 4, Value: 4,},\r
        showNothingText: { Type: 'Bool', Default: true, Value: true,},\r
        nothingText: { Type: 'String', Default: getLocalizedText('settings.default.nothingText'), Value: getLocalizedText('settings.default.nothingText'),},\r
        enableDaylightSaving: { Type: 'Bool', Default: true, Value: true,},\r
+       daylightSavingOffset: { Type: 'Int', Default: 1, Value: 1,},\r
        hideWidgetOnCalendarOpen: { Type: 'Bool', Default: false, Value: false,},\r
+       showCalendarIndicator: { Type: 'Bool', Default: true, Value: true,},\r
+       excludedCalendars: { Type: 'Array', Default: [], Value: [],},\r
+       enableLogging: { Type: 'Bool', Default: false, Value: false,},\r
        cssStyle_background: { Type: 'String', Default: 'color:#ffffff; background-color:#000000', Value: 'color:#ffffff; background-color:#000000',},\r
        cssStyle_backgroundFullscreen: { Type: 'String', Default: 'color:#ffffff; background-color:#000000', Value: 'color:#ffffff; background-color:#000000',},\r
        cssStyle_weekDay: { Type: 'String', Default: '', Value: '',},\r
@@ -53,30 +69,66 @@ var config = {
        cssStyle_time: { Type: 'String', Default: '', Value: '',},\r
        cssStyle_now: { Type: 'String', Default: 'color:#ff00ff', Value: 'color:#ff00ff',},\r
        cssStyle_description: { Type: 'String', Default: '', Value: '',},\r
-       cssStyle_icon: { Type: 'String', Default: 'width:15px; height:15px', Value: 'width:15px; height:15px',},\r
+       cssStyle_icon: { Type: 'String', Default: '', Value: '',},\r
+       cssStyle_overdue: { Type: 'String', Default: 'color:#ffff00', Value: 'color:#ffff00',},\r
+       cssStyle_calendar1: { Type: 'String', Default: 'background-color:#0757cf', Value: 'background-color:#0757cf',},\r
+       cssStyle_calendar2: { Type: 'String', Default: 'background-color:#579f37', Value: 'background-color:#579f37',},\r
+       cssStyle_calendar3: { Type: 'String', Default: 'background-color:#ff9f07', Value: 'background-color:#ff9f07',},\r
+       cssStyle_calendar4: { Type: 'String', Default: 'background-color:#af8fef', Value: 'background-color:#af8fef',},\r
+       cssStyle_calendar5: { Type: 'String', Default: 'background-color:#57afbf', Value: 'background-color:#57afbf',},\r
+       cssStyle_calendar6: { Type: 'String', Default: 'background-color:#9fdf57', Value: 'background-color:#9fdf57',},\r
 }\r
 \r
 \r
+\r
 //-------------------------------------------------------\r
 // Nothing of interest from here on...\r
 //-------------------------------------------------------\r
 var panelNum = 0; // use 1 for second panel\r
-var version = "1.25";\r
+var version = "1.37";\r
+var versionURL = "http://comingnext.sourceforge.net/version.xml";\r
 var calendarService = null;\r
 var cacheEntriesHtml = [];\r
 var months_translated = [];\r
+var weekdays_translated = [];\r
 var orientation = '';\r
 var now = new Date();\r
-var mode = 0; // 0 = homescreen, 1 = fullscreen, 2 = settings, 3 = about\r
+var mode = 0; // 0 = homescreen, 1 = fullscreen, 2 = settings, 3 = about, 4 = check for update\r
+var reqV = null; \r
+var settingsCalEntryId = null;\r
+var settingsCache = null;\r
+var notificationRequests = new Array();\r
+var calendarList = [];\r
+var calendarColors = [];\r
+var updateTimer = null;\r
+var screenRotationTimer = null;\r
+var lastUpdateTime = now; // last time we updated the display\r
+var lastReloadTime = null; // last time we fetched calendar data\r
+var reloadInterval = 6 * 60 * 60 * 1000; // = 6 hours; time interval for reloading calendar data\r
+var errorOccured = false;\r
+var entryLists = null; // stores all fetched calendar entries until data is refreshed\r
+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.\r
+var use12hoursTimeFormat = false; // defines how time should be formated: 19:00 or 07:00 pm\r
+var timeFormatSeparator = ":"; // format time 19:00 or 19.00 depending on system setting\r
+var defaultFontSize = null; // default browser font size will be set by init\r
 \r
 // vars for daylight saving time\r
-var daylightsavingWinter = 0;\r
-var daylightsavingSummer = 0;\r
-var summertime = false;\r
-\r
-window.onload = init;\r
-window.onresize = updateScreen;\r
-window.onshow = updateScreen;\r
+var summertime = false; // true, if current date is in summer, false if in winter\r
+var daylightSavingDates = new Object(); // caches calculated DST winter and summer time shift dates\r
+\r
+// this is a list of data fields a calendar event can have\r
+var entryFields = [\r
+       "id",\r
+       "Type",\r
+       "CalendarName",\r
+       "Summary",\r
+       "Location",\r
+       "Status",\r
+       "StartTime",\r
+       "EndTime",\r
+       "InstanceStartTime",\r
+       "InstanceEndTime"\r
+];\r
 \r
 function isLeapYear( year ) {\r
        if (( year % 4 == 0 && year % 100 != 0 ) || year % 400 == 0 )\r
@@ -84,7 +136,7 @@ function isLeapYear( year ) {
        else\r
                return false;\r
 }\r
-         \r
+\r
 function calcLeapYear(year, days)\r
 {\r
        if (isLeapYear(year))\r
@@ -101,70 +153,81 @@ function subToSunday(myDate, year, days, prevMonthDays)
        days = isLeapYear(year) ? --days : days;\r
        return days;\r
 }\r
-       \r
-function calcDaylightSaving()\r
+\r
+function isSummertime(curDate)\r
 {\r
-       var thisYearS = new Date(now.getFullYear(),  3, 0, 0, 0, 0 );\r
-       var thisYearW = new Date(now.getFullYear(), 10, 0, 0, 0, 0 );\r
-       var nextYearS = new Date(now.getFullYear() + 1,  3, 0, 0, 0, 0 );\r
-       var nextYearW = new Date(now.getFullYear() + 1, 10, 0, 0, 0, 0 );\r
        var summer = false;\r
        var winter = false;\r
-       \r
-       thisYearSDays = nextYearSDays = 90;\r
-       thisYearWDays = nextYearWDays = 304;\r
-       \r
-       thisYearSDays = calcLeapYear(now.getFullYear(), thisYearSDays);\r
-       thisYearWDays = calcLeapYear(now.getFullYear(), thisYearWDays);\r
-       nextYearSDays = calcLeapYear(now.getFullYear() + 1, nextYearSDays);\r
-       nextYearWDays = calcLeapYear(now.getFullYear() + 1, nextYearWDays);\r
-       \r
-       thisYearSDays = subToSunday(thisYearS, now.getFullYear(), thisYearSDays, 59);\r
-       thisYearWDays = subToSunday(thisYearW, now.getFullYear(), thisYearWDays, 273);\r
-       nextYearSDays = subToSunday(nextYearS, now.getFullYear() + 1, nextYearSDays, 59);\r
-       nextYearWDays = subToSunday(nextYearW, now.getFullYear() + 1, nextYearWDays, 273);\r
-       \r
-       daylightsavingSummer = new Date (now.getFullYear(), 03-1, thisYearSDays, 2, 0, 0);\r
-       daylightsavingWinter = new Date (now.getFullYear(), 10-1, thisYearWDays, 2, 0, 0);\r
-       if (daylightsavingSummer < now) {\r
-               daylightsavingSummer = new Date (now.getFullYear()+1, 03-1, nextYearSDays, 2, 0, 0);\r
-               var summer = true;\r
-       }\r
-       if (daylightsavingWinter < now) {\r
-               daylightsavingWinter = new Date (now.getFullYear()+1, 10-1, nextYearWDays, 2, 0, 0);\r
-               var winter = true;\r
+\r
+       // if we already calculated DST summer and winter time dates for this year, use cached values\r
+       var dst = daylightSavingDates[curDate.getFullYear()];\r
+       if (!dst) {\r
+               var thisYearS = new Date(curDate.getFullYear(),  3, 0, 0, 0, 0 );\r
+               var thisYearW = new Date(curDate.getFullYear(), 10, 0, 0, 0, 0 );\r
+               var nextYearS = new Date(curDate.getFullYear() + 1,  3, 0, 0, 0, 0 );\r
+               var nextYearW = new Date(curDate.getFullYear() + 1, 10, 0, 0, 0, 0 );\r
+               \r
+               thisYearSDays = nextYearSDays = 90;\r
+               thisYearWDays = nextYearWDays = 304;\r
+               \r
+               thisYearSDays = calcLeapYear(curDate.getFullYear(), thisYearSDays);\r
+               thisYearWDays = calcLeapYear(curDate.getFullYear(), thisYearWDays);\r
+               nextYearSDays = calcLeapYear(curDate.getFullYear() + 1, nextYearSDays);\r
+               nextYearWDays = calcLeapYear(curDate.getFullYear() + 1, nextYearWDays);\r
+               \r
+               thisYearSDays = subToSunday(thisYearS, curDate.getFullYear(), thisYearSDays, 59);\r
+               thisYearWDays = subToSunday(thisYearW, curDate.getFullYear(), thisYearWDays, 273);\r
+               nextYearSDays = subToSunday(nextYearS, curDate.getFullYear() + 1, nextYearSDays, 59);\r
+               nextYearWDays = subToSunday(nextYearW, curDate.getFullYear() + 1, nextYearWDays, 273);\r
+               \r
+               dst = {\r
+                       Summer: new Date (curDate.getFullYear(), 03-1, thisYearSDays, 2, 0, 0),\r
+                       Winter: new Date (curDate.getFullYear(), 10-1, thisYearWDays, 2, 0, 0),\r
+               }\r
+               daylightSavingDates[curDate.getFullYear()] = dst;\r
        }\r
+\r
+       if (dst.Summer < curDate)\r
+               summer = true;\r
+       if (dst.Winter < curDate)\r
+               winter = true;\r
        if (summer && !winter)\r
-               summertime = true;\r
+               return true;\r
        else\r
-               summertime = false;\r
+               return false;\r
 }\r
 \r
 function error(message)\r
 {\r
        console.info('Error: ' + message);\r
        document.getElementById("calendarList").innerHTML = 'Error: ' + message;\r
+       document.getElementById("fullscreenCalendarList").innerHTML = 'Error: ' + message;\r
+       errorOccured = true;\r
+       document.onclick = null;\r
 }\r
 \r
-function isToday(date)\r
+function areDatesEqual(date1, date2)\r
 {\r
-       if (date.getDate() == now.getDate() && date.getMonth() == now.getMonth())\r
-               return true;\r
-       return false;\r
+       return (date1.getFullYear() == date2.getFullYear() && \r
+                       date1.getMonth() == date2.getMonth() && \r
+                       date1.getDate() == date2.getDate());\r
 }\r
 \r
 function isTomorrow(date)\r
 {\r
-       if ((date.getDate() == now.getDate() + 1 && date.getMonth() == now.getMonth()) ||\r
-           (date.getDate() == 0 && date.getMonth() == now.getMonth() + 1) ||\r
-           (date.getDate() == 0 && date.getMonth() == now.getMonth() + 1 && date.getYear() == now.getYear() + 1))\r
-               return true;\r
-       return false;\r
+       // tommorow = now + 1 day\r
+       // ToDo: some days can be shorter as 24 hours(daylight saving change day)\r
+       return areDatesEqual(date, new Date (now.getTime() + 24*60*60*1000));\r
+}\r
+\r
+function isToday(date)\r
+{\r
+       return areDatesEqual(date, now);\r
 }\r
 \r
 function collectLocales()\r
 {\r
-       var tmpyear = ((panelNum == 0) ? 2000 : 2001);\r
+       var tmpyear = 2000 + panelNum;\r
        var month = 0;\r
 \r
        if (months_translated.length > 0)\r
@@ -184,7 +247,27 @@ function collectLocales()
                try {\r
                        var result = calendarService.IDataSource.Add(criteria);\r
                        if (result.ErrorCode)\r
-                               error(result.ErrorMessage);\r
+                               throw(result.ErrorMessage);\r
+               } catch (e) {\r
+                       error("collectLocales: " + e + ', line ' + e.line);\r
+               }\r
+       }\r
+       for (weekday = 0; weekday < 7; weekday++) {\r
+               var startDate = new Date(2000, 0, 2 + weekday); // date that we know for sure is a sunday\r
+\r
+               var item = new Object();\r
+               item.Type = "DayEvent";\r
+               item.StartTime = startDate;\r
+               item.Summary = "__weekday_temp" + weekday;\r
+\r
+               var criteria = new Object();\r
+               criteria.Type = "CalendarEntry";\r
+               criteria.Item = item;\r
+\r
+               try {\r
+                       var result = calendarService.IDataSource.Add(criteria);\r
+                       if (result.ErrorCode)\r
+                               throw(result.ErrorMessage);\r
                } catch (e) {\r
                        error("collectLocales: " + e + ', line ' + e.line);\r
                }\r
@@ -202,13 +285,11 @@ function collectLocales()
                        }\r
                }\r
                var result = calendarService.IDataSource.GetList(listFiltering);\r
-               if (result.ErrorCode) {\r
-                       error(result.ErrorMessage);\r
-                       return;\r
-               }\r
+               if (result.ErrorCode)\r
+                       throw(result.ErrorMessage);\r
                var list = result.ReturnValue;\r
        } catch(e) {\r
-               error(e + ', line ' + e.line);\r
+               error("collectLocales: " + e + ', line ' + e.line);\r
                return;\r
        }\r
        var ids = new Array();\r
@@ -218,7 +299,7 @@ function collectLocales()
                var dateArr = [];\r
 \r
                while (list && (entry = list.getNext()) != undefined) {\r
-                       dateArr = entry.StartTime.replace(/,/g,'').replace(/\./g,':').replace(/  /g,' ').split(' ');\r
+                       dateArr = (entry.StartTime + '').replace(/,/g,'').replace(/\./g,':').replace(/  /g,' ').split(' ');\r
                        var day = dateArr[1];\r
                        var month = dateArr[2];\r
                        var year = dateArr[3];\r
@@ -234,16 +315,53 @@ function collectLocales()
                                month = tmp;\r
                        }\r
 \r
-                       console.info(entry.StartTime + ' -> ' + month + ' ' + counter);\r
+                       log(entry.StartTime + ' -> ' + month + ' ' + counter);\r
                        ids[counter] = entry.id;\r
                        months_translated[month] = counter + 1;\r
                        counter++;\r
                }\r
        } catch(e) {\r
-               error(e + ', line ' + e.line);\r
+               error("collectLocales: " + e + ', line ' + e.line);\r
+               return;\r
+       }\r
+       try {\r
+               var startTime = new Date(2000,0,2);\r
+               var endTime = new Date(2000,0,9);\r
+               var listFiltering = {\r
+                       Type:'CalendarEntry', \r
+                       Filter:{\r
+                               StartRange: startTime,\r
+                               EndRange: endTime,\r
+                               SearchText: '__weekday_temp',\r
+                               Type: 'DayEvent'\r
+                       }\r
+               }\r
+               var result = calendarService.IDataSource.GetList(listFiltering);\r
+               if (result.ErrorCode)\r
+                       throw(result.ErrorMessage);\r
+               var weekdaylist = result.ReturnValue;\r
+       } catch(e) {\r
+               error("collectLocales: " + e + ', line ' + e.line);\r
+               return;\r
+       }\r
+       try {\r
+               var entry;\r
+               var counter2 = 0;\r
+               var curWeekday = "";\r
+\r
+               while (weekdaylist && (entry = weekdaylist.getNext()) != undefined) {\r
+                       detectTimeFormat(entry.StartTime + '');\r
+                       curWeekday = (entry.StartTime + '').split(',')[0];\r
+                       log(entry.StartTime + ' -> ' + curWeekday + ' ' + counter2);\r
+                       ids[counter + counter2] = entry.id;\r
+                       weekdays_translated[counter2] = curWeekday;\r
+                       counter2++;\r
+               }\r
+       } catch(e) {\r
+               error("collectLocales: " + e + ', line ' + e.line);\r
                return;\r
        }\r
-       console.info(ids);\r
+       log(ids);\r
        try {\r
                var criteria = new Object();\r
                criteria.Type = "CalendarEntry";\r
@@ -253,32 +371,84 @@ function collectLocales()
 \r
                var result = calendarService.IDataSource.Delete(criteria);\r
                if (result.ErrorCode)\r
-                       error(result.ErrorMessage);\r
+                       throw(result.ErrorMessage);\r
        } catch(e) {\r
                error('deleting temp calendar entries:' + e + ', line ' + e.line);\r
                return;\r
        }\r
 }\r
 \r
+function stringEndsWith(str, suffix)\r
+{\r
+       return str.indexOf(suffix, str.length - suffix.length) !== -1;\r
+}\r
+\r
+// detects the system's current time format by parsing a native calendar timestamp (this is the only reliable formating across all devices and firmwares)\r
+function detectTimeFormat(localeTimeString)\r
+{\r
+       localeTimeString = localeTimeString.toLowerCase();\r
+       use12hoursTimeFormat = stringEndsWith(localeTimeString, "am") || stringEndsWith(localeTimeString, "pm");\r
+       timeFormatSeparator = localeTimeString.indexOf(":") != -1 ? ":" : ".";\r
+}\r
+\r
 function requestNotification()\r
 {\r
        var criteria = new Object();\r
        criteria.Type = "CalendarEntry";\r
+       criteria.Filter = new Object();\r
+       for(var i=0; i < calendarList.length; i++) {\r
+               criteria.Filter.CalendarName = calendarList[i];\r
+               try {\r
+                       var notificationRequest = calendarService.IDataSource.RequestNotification(criteria, callback);\r
+                       if (notificationRequest.ErrorCode)\r
+                               error('requestNotification failed with error code ' + notificationRequest.ErrorCode);\r
+                       notificationRequests.push(notificationRequest);\r
+               } catch (e) {\r
+                       error("requestNotification: " + e + ', line ' + e.line);\r
+               }\r
+       }\r
 \r
+       var criteria2 = new Object();\r
+       criteria2.Type = "CalendarEntry";\r
+       criteria2.Filter = new Object();\r
+       criteria2.Filter.LocalIdList = new Array();\r
+       criteria2.Filter.LocalIdList[0] = settingsCalEntryId;\r
        try {\r
-               var result = calendarService.IDataSource.RequestNotification(criteria, callback);\r
-               if (result.ErrorCode)\r
-                       error('loading Calendar items list');\r
+               var notificationRequest = calendarService.IDataSource.RequestNotification(criteria2, settingsCallback);\r
+               if (notificationRequest.ErrorCode)\r
+                       error('requestNotification failed with error code ' + notificationRequest.ErrorCode);\r
+               notificationRequests.push(notificationRequest);\r
        } catch (e) {\r
                error("requestNotification: " + e + ', line ' + e.line);\r
        }\r
 }\r
 \r
+function cancelNotification()\r
+{\r
+       for(var i=0; i < notificationRequests.length; i++) {\r
+               try {\r
+                       var result = calendarService.IDataSource.Cancel(notificationRequests[i]);\r
+                       if (result.ErrorCode)\r
+                               error('cancelNotification failed with error code ' + result.ErrorCode);\r
+               } catch (e) {\r
+                       error("cancelNotification: " + e + ', line ' + e.line);\r
+               }\r
+       }\r
+}\r
+\r
 function callback(transId, eventCode, result)\r
 {\r
+       log("callback(): panelNum: " + panelNum + "  transId: " + transId + "  eventCode: " + eventCode + " result.ErrorCode: " + result.ErrorCode);\r
+       lastReloadTime = null; // force calendar data reload on next update\r
        updateData();\r
 }\r
 \r
+function settingsCallback(transId, eventCode, result)\r
+{\r
+       log("settingsCallback(): panelNum: " + panelNum + "  transId: " + transId + "  eventCode: " + eventCode + " result.ErrorCode: " + result.ErrorCode);\r
+       loadSettings();\r
+}\r
+\r
 function parseDate(dateString)\r
 {\r
        /*\r
@@ -290,60 +460,85 @@ function parseDate(dateString)
        Wednesday,  2009 August, 28 8.00.00 pm\r
        Wednesday,  2009 August, 28 08:00:00 PM\r
        */\r
+       var result = null;\r
 \r
-       if (dateString == "" || dateString == null)\r
-               return null;\r
-       var dateArr = dateString.replace(/,/g,'').replace(/\./g,':').replace(/  /g,' ').split(' ');\r
-       if (dateArr.length != 5 && dateArr.length != 6)\r
-               return null;\r
-\r
-       // parse date\r
-       var weekDay = dateArr[0];\r
-       var day = dateArr[1];\r
-       var month = dateArr[2];\r
-       var year = dateArr[3];\r
-       // make sure month is set properly\r
-       if (isNaN(parseInt(day))) {\r
-               var tmp = day;\r
-               day = month;\r
-               month = tmp;\r
-       } else if (isNaN(parseInt(year))) {\r
-               var tmp = year;\r
-               year = month;\r
-               month = tmp;\r
-       }\r
-       // make sure day and year are set properly\r
-       if (Number(day) > Number(year)) {\r
-               var tmp = year;\r
-               year = day;\r
-               day = tmp;\r
-       }\r
-       month = months_translated[month];\r
-\r
-       // parse time\r
-       var timeArr = dateArr[4].split(':');\r
-       if (timeArr.length != 3)\r
-               return null;\r
-       var hours = Number(timeArr[0]);\r
-       var minutes = Number(timeArr[1]);\r
-       var seconds = Number(timeArr[2]);\r
-       if (dateArr.length == 6 && dateArr[5].toLowerCase() == 'pm' && hours < 12)\r
-               hours += 12;\r
-       if (dateArr.length == 6 && dateArr[5].toLowerCase() == 'am' && hours == 12)\r
-               hours = 0;\r
-\r
-       console.info('year=' + year + ' month=' + month + ' day=' + day + ' hours=' + hours + ' minutes=' + minutes+ ' seconds=' + seconds);\r
-\r
+       if (dateString == "" || dateString == null || dateString == undefined)\r
+               return result;\r
+       if (dateString instanceof Date) {\r
+               // we already have a date object, no need to parse string here\r
+               result = dateString;\r
+       }\r
+       else {\r
+               var dateArr = (dateString + '').replace(/,/g, '').replace(/\./g, ':').replace(/  /g, ' ').split(' ');\r
+               if (dateArr.length != 5 && dateArr.length != 6) \r
+                       return null;\r
+               \r
+               // parse date\r
+               var weekDay = dateArr[0];\r
+               var day = dateArr[1];\r
+               var month = dateArr[2];\r
+               var year = dateArr[3];\r
+               // make sure month is set properly\r
+               if (isNaN(parseInt(day))) {\r
+                       var tmp = day;\r
+                       day = month;\r
+                       month = tmp;\r
+               }\r
+               else \r
+                       if (isNaN(parseInt(year))) {\r
+                               var tmp = year;\r
+                               year = month;\r
+                               month = tmp;\r
+                       }\r
+               // make sure day and year are set properly\r
+               if (Number(day) > Number(year)) {\r
+                       var tmp = year;\r
+                       year = day;\r
+                       day = tmp;\r
+               }\r
+               month = months_translated[month];\r
+               \r
+               // parse time\r
+               var timeArr = dateArr[4].split(':');\r
+               if (timeArr.length != 3) \r
+                       return null;\r
+               var hours = Number(timeArr[0]);\r
+               var minutes = Number(timeArr[1]);\r
+               var seconds = Number(timeArr[2]);\r
+               if (dateArr.length == 6 && dateArr[5].toLowerCase() == 'pm' && hours < 12) \r
+                       hours += 12;\r
+               if (dateArr.length == 6 && dateArr[5].toLowerCase() == 'am' && hours == 12) \r
+                       hours = 0;\r
+               \r
+               result = new Date(year, month - 1, day, hours, minutes, seconds);\r
+       }\r
+       \r
        // take care of daylight saving time\r
        if (config['enableDaylightSaving'].Value) {\r
-               var date = new Date(year, month - 1, day, hours, minutes, seconds);\r
-               if (summertime && date > daylightsavingWinter && date < daylightsavingSummer)\r
-                       hours -= 1;\r
-               else if (!summertime && date > daylightsavingSummer && date < daylightsavingWinter)\r
-                       hours += 1;\r
+               \r
+               // determine if date is in summer or winter time\r
+               var dateSummerTime = isSummertime(result);\r
+\r
+               // work around bug in Nokias calendar api resulting in dates within a different DST to be off by 1 hour\r
+               if (summertime && !dateSummerTime) {\r
+                       result = new Date(result.getTime() - 1000 * 60 * 60 * config['daylightSavingOffset'].Value); // -1 hour\r
+                       log('parseDate(): fixing time -1h: ' + result);\r
+               }\r
+               else if (!summertime && dateSummerTime) {\r
+                       result = new Date(result.getTime() + 1000 * 60 * 60 * config['daylightSavingOffset'].Value); // +1 hour\r
+                       log('parseDate(): fixing time +1h: ' + result);\r
+               }\r
        }\r
 \r
-       return new Date(year, month - 1, day, hours, minutes, seconds);\r
+       return result;\r
+}\r
+\r
+function getWeekdayLocalized(date) {\r
+       var localizedString = date.toLocaleDateString();\r
+       if (localizedString.indexOf(",") == -1) {\r
+               return weekdays_translated[date.getDay()];\r
+       } else\r
+               return localizedString.split(',')[0];\r
 }\r
 \r
 // 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"\r
@@ -359,6 +554,13 @@ function formatDate(date, format)
        if (config['showTodayAsText'].Value && isTomorrow(date))\r
                return '<span class="tomorrow">' + config['tomorrowText'].Value + '</span>';\r
 \r
+       if (format instanceof Date) {\r
+               // we don't know how to format this\r
+               if (config['dateFormat'].Value == 'auto' || config['dateFormat'].Value == 'DDMM')\r
+                       return day + config['dateSeparator'].Value + month;\r
+               else\r
+                       return month + config['dateSeparator'].Value + day;\r
+       }\r
        var dateArr = format.replace(/,/g,'').replace(/\./g,':').replace(/  /g,' ').split(' ');\r
        if (dateArr.length != 5 && dateArr.length != 6) {\r
                // we don't know how to format this\r
@@ -405,55 +607,128 @@ function formatDate(date, format)
 function formatTime(date)\r
 {\r
        // date is a Date() object\r
-       date.setSeconds(0); // we don't care about seconds\r
-       var time = date.toLocaleTimeString().replace(/[\.:]00/, ''); // remove seconds from string\r
-       if (time.replace(/\./, ':').split(':')[0].length < 2)\r
-               time = '0' + time;\r
+       var hour = date.getHours();\r
+       var minute = date.getMinutes();\r
+       \r
+       // don't use Date().toLocaleTimeString() as it is utterly broken on newer firmwares\r
+       if (use12hoursTimeFormat) {\r
+               var ap = "AM";\r
+               if (hour > 11)\r
+                       ap = "PM";\r
+               if (hour > 12)\r
+                       hour = hour - 12;\r
+               if (hour == 0)\r
+                       hour = 12;\r
+               if (hour < 10)\r
+                       hour = "0" + hour;\r
+               if (minute < 10)\r
+                       minute = "0" + minute;\r
+               time = hour + timeFormatSeparator + minute + " " + ap;\r
+       }\r
+       else {\r
+               if (hour < 10)\r
+                       hour = "0" + hour;\r
+               if (minute < 10)\r
+                       minute = "0" + minute;\r
+               time = hour + timeFormatSeparator + minute;\r
+       }\r
+       \r
        if (config['showNowAsText'].Value && date.getTime() == now.getTime())\r
                time = '<span class="now">' + config['nowText'].Value + '</span>';\r
+       log("formatTime(): " + time + ", use12hoursTimeFormat=" + use12hoursTimeFormat + ", timeFormatSeparator=" + timeFormatSeparator + ", date.toLocateTimeString(): " + date.toLocaleTimeString());\r
        return time;\r
 }\r
 \r
 function updateData()\r
 {\r
-       console.info('updateData()');\r
-       calcDaylightSaving();\r
-       try {\r
-               // meetings have time\r
-               // 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\r
-               now = new Date();\r
-               var meetingListFiltering = {\r
-                       Type:'CalendarEntry',\r
-                       Filter:{\r
-                               StartRange: (new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0)),\r
-                               EndRange: (new Date(now.getFullYear(), now.getMonth() + config['monthRange'].Value, now.getDate(), 0, 0, 0))\r
+       log('updateData()');\r
+       if (errorOccured) {\r
+               return;\r
+       }\r
+\r
+       // check if we got additional or less calendars since our last update\r
+       var newCalendarList = listCalendars();\r
+       if (newCalendarList == null) {\r
+               // Something went wrong fetching the calendars list.\r
+               // This usually happens when a backup is being made.\r
+               // Retry the next time updateData() is called by \r
+               // resetting errorOccured\r
+               log('updateData(): listCalendars() failed, trying again later...');\r
+               cacheEntriesHtml = ''; // make sure we replace the currently shown error message on the next update\r
+               errorOccured = false;\r
+               return;\r
+       }\r
+       if (newCalendarList.length != calendarList.length) {\r
+               calendarList = newCalendarList;\r
+               updateCalendarColors();\r
+               cancelNotification();\r
+               requestNotification();\r
+               lastReloadTime = null; // force calendar data reload on this update\r
+       }\r
+\r
+       now = new Date();\r
+       \r
+       // only reload calendar data every 6 hours, visual updates occure more often\r
+       if (!lastReloadTime || now.getTime() - lastReloadTime.getTime() > reloadInterval) {\r
+               log('updateData(): reloading calendar data');\r
+               try {\r
+                       // meetings have time\r
+                       // 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\r
+                       summertime = isSummertime(now); // cache summer time info for today\r
+                       var meetingList = [];\r
+                       for(var i=0; i < calendarList.length; i++) {\r
+                               // ignore excluded calendars\r
+                               if (config['excludedCalendars'].Value.indexOf(calendarList[i]) != -1)\r
+                                       continue;\r
+                               var meetingListFiltering = {\r
+                                       Type:'CalendarEntry',\r
+                                       Filter:{\r
+                                               CalendarName: calendarList[i],\r
+                                               StartRange: (new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0)),\r
+                                               EndRange: (new Date(now.getFullYear(), now.getMonth() + config['monthRange'].Value, now.getDate(), 0, 0, 0))\r
+                                       }\r
+                               }\r
+                               var meetingResult = calendarService.IDataSource.GetList(meetingListFiltering);\r
+                               if (meetingResult.ErrorCode != 0)\r
+                                       throw("Error fetching calendar data: " + meetingResult.ErrorCode + ': ' + meetingResult.ErrorMessage);\r
+                               var list = meetingResult.ReturnValue;\r
+                               meetingList = meetingList.concat(listToArray(list, calendarList[i]));\r
                        }\r
-               }\r
-               var meetingResult = calendarService.IDataSource.GetList(meetingListFiltering);\r
-               if (meetingResult.ErrorCode != 0)\r
-                       throw("Error fetching calendar data: " + meetingResult.ErrorCode + ': ' + meetingResult.ErrorMessage);\r
-               var meetingList = meetingResult.ReturnValue;\r
-\r
-               // todos don't, they start on 00:00 hrs., but should be visible anyway\r
-               // this will generate a list of passed todos. We have to check if they have been marked as "done" yet\r
-               if (config['includeTodos'].Value) {\r
-                       var todayTodoListFiltering = {\r
-                               Type:'CalendarEntry',\r
-                               Filter:{\r
-                                       Type: 'ToDo',\r
-                                       StartRange: (new Date(now.getFullYear() - 1, now.getMonth(), now.getDate(), 0, 0, 0)),\r
-                                       EndRange: (new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 1))\r
+                       log("updateData(): meetingList.sort()");\r
+                       meetingList.sort(sortCalendarEntries);\r
+\r
+                       // todos don't, they start on 00:00 hrs., but should be visible anyway\r
+                       // this will generate a list of passed todos. We have to check if they have been marked as "done" yet\r
+                       if (config['includeTodos'].Value) {\r
+                               var todayTodoList = [];\r
+                               for(var i=0; i < calendarList.length; i++) {\r
+                                       // ignore excluded calendars\r
+                                       if (config['excludedCalendars'].Value.indexOf(calendarList[i]) != -1)\r
+                                               continue;\r
+                                       var todayTodoListFiltering = {\r
+                                               Type:'CalendarEntry',\r
+                                               Filter:{\r
+                                                       CalendarName: calendarList[i],\r
+                                                       Type: 'ToDo',\r
+                                                       StartRange: (new Date(now.getFullYear() - 1, now.getMonth(), now.getDate(), 0, 0, 0)),\r
+                                                       EndRange: (new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 1))\r
+                                               }\r
+                                       }\r
+                                       var todayTodoResult = calendarService.IDataSource.GetList(todayTodoListFiltering);\r
+                                       var list = todayTodoResult.ReturnValue;\r
+                                       todayTodoList = todayTodoList.concat(listToArray(list, calendarList[i]));\r
                                }\r
+                               log("updateData(): todayTodoList.sort()");\r
+                               todayTodoList.sort(sortCalendarEntries);\r
+                               entryLists = [todayTodoList, meetingList];\r
+                       } else {\r
+                               entryLists = [meetingList];\r
                        }\r
-                       var todayTodoResult = calendarService.IDataSource.GetList(todayTodoListFiltering);\r
-                       var todayTodoList = todayTodoResult.ReturnValue;\r
-                       var entryLists = [todayTodoList, meetingList];\r
-               } else {\r
-                       var entryLists = [meetingList];\r
+                       lastReloadTime = new Date();\r
+               } catch(e) {\r
+                       error('loading Calendar items list:' + e + ', line ' + e.line);\r
+                       return;\r
                }\r
-       } catch(e) {\r
-               error('loading Calendar items list:' + e + ', line ' + e.line);\r
-               return;\r
        }\r
 \r
        try {\r
@@ -461,42 +736,67 @@ function updateData()
                var counter = 0;\r
                var entryDate = '';\r
                var dateArr = [];\r
-               var entriesHtml = '<table>';\r
+               var fontsize = getDefaultFontSize()[1] + 'px';\r
+               var lineheight = fontsize;\r
+               if (mode == 0) {\r
+                       if (config['fontsize'].Value == config['fontsize'].ValidValues[0]) {\r
+                               fontsize = parseInt(72 / config['eventsPerWidget'].Value) + 'px';\r
+                               lineheight = parseInt(72 / config['eventsPerWidget'].Value) + 'px';\r
+                       }\r
+               }\r
+               if (config['fontsize'].Value != config['fontsize'].ValidValues[0]) {\r
+                       fontsize = config['fontsize'].Value + 'px';\r
+                       lineheight = fontsize;\r
+               }\r
+               changeCssClass('.icon', config['cssStyle_icon'].Value + '; width:' + fontsize + '; height:' + fontsize + ';');\r
+               var entriesHtml = '<table style="font-size:' + fontsize + '; line-height:' + lineheight + ';">';\r
+               if (mode == 0)\r
+                       entriesHtml = '<table width="307" height="82"><tr><td>' + entriesHtml; // this is needed to center the actual content vertically\r
                var eventIds = [];\r
                var max;\r
                if (mode == 0)\r
-                       max = ((panelNum == 0) ? config['eventsPerWidget'].Value : 2 * config['eventsPerWidget'].Value);\r
+                       max = (panelNum + 1) * config['eventsPerWidget'].Value;\r
                else\r
                        max = 30; // we can display a lot more events in fullscreen mode\r
 \r
+               if (config['enableLogging'].Value) {\r
+                       var listinfo = "";\r
+                       for (var i=0; i < entryLists.length; i++) {\r
+                               listinfo = listinfo + " " + entryLists[i].length;\r
+                               var entrieslist = "";\r
+                               for (var j=0; j < entryLists[i].length; j++) {\r
+                                       entrieslist += entryLists[i][j].Summary + ", ";\r
+                               }\r
+                               log("updateData(): entrieslist: " + entrieslist);\r
+                       }\r
+                       log("updateData(): inner loop, " + entryLists.length + " lists, [" + listinfo + "] entries");\r
+               }\r
+               \r
                // the first outer loop iteration is for passed ToDos, the second loop is for all upcomming events (may also include ToDos)\r
                for (var i=0; counter < max && i < entryLists.length; i++) {\r
-                       while (counter < max && (entry = entryLists[i].getNext()) != undefined) {\r
+                       for (var j=0; (counter < max) && (j < entryLists[i].length); j++) {\r
+                               entry = entryLists[i][j];\r
                                counter++;\r
 \r
                                // output event info for debugging\r
-                               console.info(\r
-                                       'event: Id=' + entry.id + \r
-                                       ',Type=' + entry.Type + \r
-                                       ',Summary=' + entry.Summary + \r
-                                       ',Location=' + entry.Location + \r
-                                       ',Status=' + entry.Status + \r
-                                       ',StartTime=' + entry.StartTime +\r
-                                       ',EndTime=' + entry.EndTime +\r
-                                       ',InstanceStartTime=' + entry.InstanceStartTime +\r
-                                       ',InstanceEndTime=' + entry.InstanceEndTime\r
-                               );\r
+                               var entryInfo = "event: ";\r
+                               for(var k=0; k < entryFields.length; ++k) {\r
+                                       if (entry[entryFields[k]] != undefined) {\r
+                                               entryInfo += entryFields[k] + "=" + entry[entryFields[k]] + ",";\r
+                                       }\r
+                               }\r
+                               log(entryInfo);\r
 \r
                                // we don't want ToDos when includeTodos == false or when they are completed\r
                                if (entry.Type == 'ToDo' && (entry.Status == "TodoCompleted" || !config['includeTodos'].Value)) {\r
-                                       console.info('skipping ' + entry.id );\r
+                                       log('skipping ' + entry.id );\r
                                        counter--;\r
                                        continue;\r
                                }\r
 \r
                                // make sure that we don't include an event twice (useful for ToDos that might come up twice)\r
                                if (eventIds[entry.id] == 1 && entry.Type == 'ToDo') {\r
-                                       console.info('skipped (already included) ' + entry.id);\r
+                                       log('skipped (already included) ' + entry.id);\r
                                        counter--;\r
                                        continue;\r
                                } else\r
@@ -504,7 +804,7 @@ function updateData()
 \r
                                // summary can be undefined!\r
                                var Summary = ((entry.Summary == null) ? '' : entry.Summary);\r
-                               if (entry.Type == 'Meeting' && entry.Location != '' && config['showLocation'].Value)\r
+                               if (entry.Location != '' && entry.Location != undefined && config['showLocation'].Value)\r
                                        Summary += ', ' + entry.Location;\r
                                \r
                                // fix by yves: determine start and end dates/times\r
@@ -519,15 +819,23 @@ function updateData()
 \r
                                // Convert date/time string to Date object\r
                                var date = parseDate(entryDate);\r
-                               console.info('date: ' + date);\r
+                               log('date: ' + date);\r
                                var endDate = ((entryEndTime == null) ? null : parseDate(entryEndTime));\r
-                               console.info('endDate: ' + endDate);\r
+                               log('endDate: ' + endDate);\r
+                               \r
+                               // check if Meeting is actually a DayEvent. Bug introduced by "Anna" updates to various Symbian^3 devices.\r
+                               // Note that this workaround is not 100% save! It might missinterpret some meetings as dayevents of starting and ending on 00:00\r
+                               if (entry.Type == 'Meeting' && date.getHours() == 0 && date.getMinutes() == 0 && \r
+                                       endDate != null && endDate.getHours() == 0 && endDate.getMinutes() == 0) {\r
+                                       log('fixing event type: changed from "Meeting" to "DayEvent".');\r
+                                       entry.Type = 'DayEvent';\r
+                               }\r
 \r
                                // check if meeting event has already passed\r
                                if (entry.Type == 'Meeting') {\r
                                        var compareTime = ((endDate == null) ? date.getTime() : endDate.getTime());\r
                                        if (now.getTime() > compareTime) {\r
-                                               console.info('skipping Meeting (already passed) ' + entry.id);\r
+                                               log('skipping Meeting (already passed) ' + entry.id);\r
                                                counter--;\r
                                                eventIds[entry.id] = 0;\r
                                                continue;\r
@@ -538,7 +846,7 @@ function updateData()
                                if (entry.Type == 'Anniversary') {\r
                                        var tmp = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0,0,0);\r
                                        if (date.getTime() < tmp.getTime()) {\r
-                                               console.info('skipping Anniversary (already passed) ' + entry.id);\r
+                                               log('skipping Anniversary (already passed) ' + entry.id);\r
                                                counter--;\r
                                                eventIds[entry.id] = 0;\r
                                                continue;\r
@@ -548,9 +856,9 @@ function updateData()
                                // fix DayEvents end time. End times are off by 1 Second. It's possible that the event has already passed\r
                                if (entry.Type == 'DayEvent' && endDate != null) {\r
                                        endDate.setMinutes(endDate.getMinutes() - 1);\r
-                                       console.info('fixing DayEvent endDate: ' + endDate);\r
+                                       log('fixing DayEvent endDate: ' + endDate);\r
                                        if (now.getTime() > endDate.getTime()) {\r
-                                               console.info('event already passed ' + entry.id);\r
+                                               log('event already passed ' + entry.id);\r
                                                counter--;\r
                                                eventIds[entry.id] = 0;\r
                                                continue;\r
@@ -562,26 +870,48 @@ function updateData()
                                        // check if we are between start and endtime\r
                                        if ((date.getTime() < now.getTime()) && (now.getTime() < endDate.getTime())) {\r
                                                date = now; // change appointment date/time to now\r
-                                               console.info('event is currently taking place: ' + date);\r
+                                               log('event is currently taking place: ' + date);\r
                                        }\r
                                }\r
 \r
                                // skip events for the first panel in case this is the second one and we're not in fullscreen mode\r
-                               if (mode == 0 && panelNum == 1 && counter < config['eventsPerWidget'].Value + 1) {\r
-                                       console.info('skipping (already in first widget) ' + entry.id);\r
+                               if (mode == 0 && panelNum > 0 && counter < panelNum * config['eventsPerWidget'].Value + 1) {\r
+                                       log('skipping (already in first widget) ' + entry.id);\r
                                        continue;\r
                                }\r
+                               \r
+                               // mark overdue todos\r
+                               var overdue = false;\r
+                               if (entry.Type == 'ToDo' && date != null) {\r
+                                       var tmp1 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0,0,0);\r
+                                       var tmp2 = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0,0,0);\r
+                                       if (tmp1.getTime() < tmp2.getTime()) {\r
+                                               overdue = true;\r
+                                       }\r
+                               }\r
 \r
                                // generate html output\r
-                               entriesHtml += '<tr><td><img class="icon" src="' + entry.Type + '.png" /></td>';\r
+                               entriesHtml += '<tr>';\r
+                               if (config['showCalendarIndicator'].Value && calendarList.length - config['excludedCalendars'].Value.length > 1) {\r
+                                       entriesHtml += '<td><div class="calendar' + calendarColors[entry.CalendarName] + '" style="height:' + (lineheight.split("px")[0] - 1) + 'px; width:4px;"></div></td>';\r
+                               }\r
+                               if (config['showIcons'].Value)\r
+                                       entriesHtml += '<td><img class="icon" align="top" src="' + entry.Type + '.png" /></td>';\r
+                               else\r
+                                       entriesHtml += '<td style="padding:0px;"></td>';\r
                                if(date == null) {\r
                                        // some languages have very strange locale date formats, can't parse all those. Also some todos don't have dates at all.\r
                                        entriesHtml += '<td colspan="4"><span class="date">' + entryDate + '</span> ';\r
                                } else {\r
-                                       var weekDay = date.toLocaleDateString().substr(0,config['weekDayLength'].Value);\r
+                                       var weekDay = getWeekdayLocalized(date).substr(0,config['weekDayLength'].Value);\r
+                                       log('date.toLocaleDateString(): ' + date.toLocaleDateString());\r
+                                       log('weekDay: ' + weekDay);\r
                                        var time = formatTime(date);\r
                                        var dateStr = formatDate(date, entryDate);\r
-                                       if (entry.Type == 'ToDo' || entry.Type == 'Anniversary' || entry.Type == 'DayEvent' || entry.Type == 'Reminder') {\r
+                                       if (entry.Type == 'ToDo' && overdue && config['markOverdueTodos'].Value) {\r
+                                               dateStr = '<span class="overdue">' + config['overdueText'].Value + '</span>';\r
+                                               entriesHtml += '<td colspan="4" width="1px"><span class="date">' + dateStr + '</span> ';\r
+                                       } else if (entry.Type == 'ToDo' || entry.Type == 'Anniversary' || entry.Type == 'DayEvent' || entry.Type == 'Reminder') {\r
                                                if ((isToday(date) || isTomorrow(date)) && config['showTodayAsText'].Value) // show weekday if the date string is not text. looks odd otherwise\r
                                                        entriesHtml += '<td colspan="4" width="1px"><span class="date">' + dateStr + '</span> ';\r
                                                else\r
@@ -606,10 +936,13 @@ function updateData()
                        }\r
                }\r
                entriesHtml += '</table>';\r
+               if (mode == 0)\r
+                       entriesHtml = entriesHtml + '</td></tr></table>';\r
                if (config['showNothingText'].Value && entriesHtml == '<table></table>') {\r
                        var text = config['nothingText'].Value.replace(/%d/, config['monthRange'].Value);\r
                        entriesHtml = '<div style="width:295px; height:75px; text-align:center; line-height:75px; overflow:visible;">' + text + '</div>';\r
                }\r
+               log("output: " + entriesHtml);\r
                if (cacheEntriesHtml != entriesHtml) {\r
                        if (mode == 0)\r
                                document.getElementById('calendarList').innerHTML = entriesHtml;\r
@@ -617,33 +950,56 @@ function updateData()
                                document.getElementById('fullscreenCalendarList').innerHTML = entriesHtml;\r
                        cacheEntriesHtml = entriesHtml;\r
                }\r
+               \r
+               lastUpdateTime = new Date();\r
        } catch(e) {\r
                error('displaying list:' + e + ', line ' + e.line);\r
                return;\r
        }\r
 }\r
 \r
+// called by handleOnShow() and onResize events\r
 function updateScreen()\r
 {\r
+       log('updateScreen(): mode=' + mode + ', window.innerHeight=' + window.innerHeight);\r
+\r
        // check if opening fullscreen\r
-       if( window.innerHeight > 91 && mode == 0) {\r
+\r
+       // Note: according to Nokia's documentation, an innerHeight of >91 is an indicator for fullscreen view. \r
+       // However a bug in E6's firmware causes different window widths and heights (disabled compatibility scaling). \r
+       // So far, values of 104 and 115 for window.innerHeight were reported, we use a safty margin here and check \r
+       // for 150 instead.\r
+       if( window.innerHeight > 150 && mode == 0) {\r
                mode = 1;\r
                cacheEntriesHtml = '';\r
                document.getElementById('body').style.backgroundImage = "";\r
                showFullscreen();\r
        }\r
-       else if (window.innerHeight <= 91 && mode != 0) {\r
+       else if (window.innerHeight <= 150 && mode != 0) {\r
                mode = 0;\r
                cacheEntriesHtml = '';\r
                showHomescreen();\r
        }\r
        \r
        if (mode == 0)\r
-               updateHomescreen();\r
+               updateHomescreen(); // check for screen rotation\r
        else if (mode == 1)\r
                updateFullscreen();\r
 }\r
 \r
+function handleOnShow()\r
+{\r
+       updateScreen();\r
+\r
+       var time = new Date();\r
+       if (time.getTime() - lastUpdateTime.getTime() > config['updateDataInterval'].Value * 60 * 1000) {\r
+               log('updateScreen(): force updateData() because last update was too long ago (' + (time.getTime() - lastUpdateTime.getTime()) / 1000 + 's)');\r
+               clearUpdateTimer();\r
+               updateData();\r
+               setUpdateTimer(); // reinitialize update timer\r
+       }\r
+}\r
+\r
 function launchCalendar()\r
 {\r
        try {\r
@@ -658,7 +1014,7 @@ function launchCalendar()
 \r
 function init()\r
 {\r
-       console.info('New widget instance starting up...');\r
+       log('New widget instance starting up...');\r
        \r
        try {\r
                // call calendar service\r
@@ -667,23 +1023,59 @@ function init()
                else\r
                        throw('device object does not exist');\r
        } catch(e) {\r
-               error('loading Calendar service: ' + e + ', line ' + e.line);\r
-               return;\r
+               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>');\r
+               //return;\r
        }\r
 \r
+       calendarList = listCalendars();\r
        loadSettings();\r
-       updateCssClasses();\r
+       updateCalendarColors();\r
        collectLocales();\r
        //updateData();\r
        requestNotification();\r
-       window.setInterval('updateData()', 1000 * 60 * config['updateDataInterval'].Value);\r
-\r
-       mode = 0;\r
-       showHomescreen();\r
+       document.getElementById("settingsTitle").innerHTML = getLocalizedText('menu.settings');\r
+       setUpdateTimer();\r
+       if (window.innerHeight > 91) {\r
+               mode = 0; // we're starting fullscreen, we set mode to homescreen in order to let updateScreen() do all the work for us\r
+       }\r
+       else {\r
+               mode = 1;\r
+       }\r
+       log("init(): updateScreen()");\r
        updateScreen();\r
        if (config['useBackgroundImage'].Value)\r
                // check for screen rotation every 1 secs\r
-               window.setInterval('updateScreen()', 1000 * 1);\r
+               screenRotationTimer = window.setInterval('checkOrientation()', 1000 * 1);\r
+       \r
+       // call updateScreen() when widget changes from background to forground\r
+       window.widget.onshow = handleOnShow;\r
+\r
+       log("init(): finished...");\r
+       if (!errorOccured)\r
+               statupSuccessful = true;\r
+}\r
+\r
+function checkOrientation()\r
+{\r
+       //updateScreen();\r
+       if (mode == 0)\r
+               updateHomescreen(); // check for screen rotation\r
+}\r
+\r
+function setUpdateTimer()\r
+{\r
+       updateTimer = window.setInterval('updateTimerCallback()', 1000 * 60 * config['updateDataInterval'].Value);\r
+}\r
+\r
+function clearUpdateTimer() \r
+{\r
+       window.clearInterval(updateTimer);\r
+}\r
+\r
+function updateTimerCallback()\r
+{\r
+       log("updateTimerCallback()");\r
+       updateData();\r
 }\r
 \r
 function createMenu()\r
@@ -693,22 +1085,26 @@ function createMenu()
        var id = 0;\r
        var menuSettings = new MenuItem(getLocalizedText('menu.settings'), id++);\r
        var menuCallApp = new MenuItem(getLocalizedText('menu.openCalendarApp'), id++);\r
+       var menuHelp = new MenuItem(getLocalizedText('menu.help'), id++);\r
+       var menuUpdate = new MenuItem(getLocalizedText('menu.update'), id++);\r
        var menuAbout = new MenuItem(getLocalizedText('menu.about'), id++);\r
        menuSettings.onSelect = showSettings;\r
        menuAbout.onSelect = showAbout;\r
        menuCallApp.onSelect = launchCalendar;\r
+       menuUpdate.onSelect = showUpdate;\r
+       menuHelp.onSelect = showHelp;\r
        window.menu.clear();\r
        window.menu.append(menuCallApp);\r
        window.menu.append(menuSettings);\r
+       window.menu.append(menuHelp);\r
+       window.menu.append(menuUpdate);\r
        window.menu.append(menuAbout);  \r
 }\r
 \r
 function showSettings()\r
 {\r
        mode = 2;\r
-       document.getElementById("homescreenView").style.display = "none";\r
-       document.getElementById("fullscreenView").style.display = "none";\r
-       document.getElementById("aboutView").style.display = "none";\r
+       hideViews();\r
        document.getElementById("settingsView").style.display = "block";\r
        document.onclick = null;\r
        \r
@@ -719,18 +1115,31 @@ function showSettings()
                                config[key].Value = document.forms[0].elements["settings." + key].value;\r
                        else if (config[key].Type == 'Int') {\r
                                config[key].Value = parseInt(document.forms[0].elements["settings." + key].value);\r
-                               if (config[key].Value < 0)\r
+                               if (config[key].Value < 0 || isNaN(config[key].Value))\r
                                        config[key].Value = config[key].Default;\r
                        }\r
                        else if (config[key].Type == 'Bool')\r
                                config[key].Value = document.forms[0].elements["settings." + key].checked;\r
-                       else if (config[key].Type == 'UID')\r
+                       else if (config[key].Type == 'UID') {\r
                                config[key].Value = parseInt(document.forms[0].elements["settings." + key].value);\r
+                               if (isNaN(config[key].Value))\r
+                                       config[key].Value = config[key].Default;\r
+                       }\r
                        else if (config[key].Type == 'Enum') {\r
                                config[key].Value = document.forms[0].elements["settings." + key].value;\r
                                if (config[key].ValidValues.indexOf(config[key].Value) == -1)\r
                                        config[key].Value = config[key].Default;\r
                        }\r
+                       else if (config[key].Type == 'Array') {\r
+                               if (key == 'excludedCalendars') {\r
+                                       config[key].Value = new Array();\r
+                                       for(var i=0; i < calendarList.length; i++) {\r
+                                               var element = document.forms[0].elements["settings." + key + "." + calendarList[i]];\r
+                                               if (element != null && element.checked == false)\r
+                                                       config[key].Value.push(calendarList[i]);\r
+                                       }\r
+                               }\r
+                       }\r
                }\r
                                \r
                updateCssClasses();\r
@@ -748,8 +1157,12 @@ function showSettings()
        \r
        var settingsHtml = '<form>';\r
        for (var key in config) {\r
-               if (config[key].Type == 'String')\r
-                       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 />';\r
+               if (config[key].Type == 'String') {\r
+                       var prefix = "";\r
+                       if (key.substring(0,9) == "cssStyle_")\r
+                               prefix = getLocalizedText('settings.cssStyle_prefix');\r
+                       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 />';\r
+               }\r
                else if (config[key].Type == 'Int')\r
                        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 />';\r
                else if (config[key].Type == 'Bool')\r
@@ -758,10 +1171,26 @@ function showSettings()
                        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 />';\r
                else if (config[key].Type == 'Enum') {\r
                        settingsHtml += '<table><tr><td>' + getLocalizedText('settings.name.' + key) + '<br /><select name="settings.' + key + '" size="1">';\r
-                       for(var i = 0; i < config[key].ValidValues.length; i++)\r
-                               settingsHtml += '<option label="' + config[key].ValidValues[i] + '"' + (config[key].Value == config[key].ValidValues[i] ? ' selected="selected"' : '') + '>' + config[key].ValidValues[i] + '</option>';\r
+                       for(var i = 0; i < config[key].ValidValues.length; i++) {\r
+                               var text = getLocalizedText('settings.validValues.' + key + '.' + config[key].ValidValues[i]);\r
+                               if (text.indexOf('ERROR') == 0)\r
+                                       text = config[key].ValidValues[i];\r
+                               settingsHtml += '<option value="' + config[key].ValidValues[i] + '"' + (config[key].Value == config[key].ValidValues[i] ? ' selected="selected"' : '') + '>' + text + '</option>';\r
+                       }\r
                        settingsHtml += '</select></div></td>' + printHintBox(getLocalizedText('settings.info.' + key)) + '<hr />';\r
                }\r
+               else if (config[key].Type == 'Array') {\r
+                       settingsHtml += '<table><tr><td>' + getLocalizedText('settings.name.' + key) + '<br />';\r
+                       if (key == 'excludedCalendars') {\r
+                               for(var i=0; i < calendarList.length; i++) {\r
+                                       var checked = 'checked="checked"';\r
+                                       if (config[key].Value.indexOf(calendarList[i]) != -1)\r
+                                               checked = '';\r
+                                       settingsHtml += '<input name="settings.' + key + '.' + calendarList[i] + '" type="checkbox" value="' + calendarList[i] + '" ' + checked + '/> ' + calendarList[i] + '<br />';\r
+                               }\r
+                       }\r
+                       settingsHtml += '</td>' + printHintBox(getLocalizedText('settings.info.' + key)) + '<hr />';\r
+               }\r
        }\r
        settingsHtml += '<input name="reset" type="button" value="' + getLocalizedText('settings.restoreDefaults') + '" onclick="javascript:restoreDefaultSettings();showSettings();" />';\r
        settingsHtml += '</form>';\r
@@ -787,6 +1216,58 @@ function updateCssClasses()
        }\r
 }\r
 \r
+function getSettingsCalEntryId()\r
+{\r
+       if (settingsCalEntryId == null) {\r
+               // check if entry already exists\r
+               var listFiltering = {\r
+                       Type:'CalendarEntry', \r
+                       Filter:{\r
+                               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!\r
+                               EndRange: new Date(2000, 0, 2),\r
+                               SearchText: 'ComingNext Settings|',\r
+                               Type: 'DayEvent'\r
+                       }\r
+               }\r
+               var result = null;\r
+               try {\r
+                       result = calendarService.IDataSource.GetList(listFiltering);\r
+                       if (result.ErrorCode)\r
+                               throw(result.ErrorMessage);\r
+               }\r
+               catch (e) {\r
+                       error("getSettingsCalEntryId: GetList() failed: " + e + ', line ' + e.line);\r
+                       return;\r
+               }\r
+               var list = result.ReturnValue;\r
+               var entry = list.getNext();\r
+               if (entry != undefined) {\r
+                       settingsCalEntryId = entry.LocalId;\r
+                       log("settingsCalEntryId=" + settingsCalEntryId);\r
+               }\r
+               else { // create settings item\r
+                       var item = new Object();\r
+                       item.Type = "DayEvent";\r
+                       item.StartTime = new Date(2000, 0, 1);\r
+                       item.Summary = "ComingNext Settings|";\r
+                       \r
+                       var criteria = new Object();\r
+                       criteria.Type = "CalendarEntry";\r
+                       criteria.Item = item;\r
+\r
+                       try {\r
+                               var result = calendarService.IDataSource.Add(criteria);\r
+                               if (result.ErrorCode)\r
+                                       throw(result.ErrorMessage);\r
+                       } catch (e) {\r
+                               error("getSettingsCalEntryId: " + e + ', line ' + e.line);\r
+                       }\r
+\r
+                       getSettingsCalEntryId();\r
+               }\r
+       }\r
+}\r
+\r
 function restoreDefaultSettings()\r
 {\r
        for (var key in config)\r
@@ -795,39 +1276,117 @@ function restoreDefaultSettings()
 \r
 function loadSettings()\r
 {\r
-       for (var key in config) {\r
-               if (widget.preferenceForKey(key)) {\r
-                       if (config[key].Type == 'Int')\r
-                               config[key].Value = Number(widget.preferenceForKey(key));\r
-                       else if (config[key].Type == 'String')\r
-                               config[key].Value = widget.preferenceForKey(key);\r
-                       else if (config[key].Type == 'Bool')\r
-                               config[key].Value = (widget.preferenceForKey(key) == 'true')\r
-                       else if (config[key].Type == 'Enum')\r
-                               config[key].Value = widget.preferenceForKey(key);\r
-                       else if (config[key].Type == 'UID')\r
-                               config[key].Value = Number(widget.preferenceForKey(key));\r
+       getSettingsCalEntryId();\r
+       var listFiltering = {\r
+               Type:'CalendarEntry', \r
+               Filter:{\r
+                       LocalId: settingsCalEntryId\r
                }\r
-               else\r
-                       config[key].Value = config[key].Default;\r
-               console.info('Settings: ' + key + '=\'' + config[key].Value + '\'');\r
+       }\r
+       var result = null;\r
+       try     {\r
+               result = calendarService.IDataSource.GetList(listFiltering);\r
+               if (result.ErrorCode)\r
+                       throw(result.ErrorMessage);\r
+       }\r
+       catch (e) {\r
+               error("loadSettings: GetList() failed: " + e + ', line ' + e.line);\r
+               return;\r
+       }\r
+       var entry = result.ReturnValue.getNext();\r
+       if (entry != undefined) {\r
+               log("Loading Settings...");\r
+               // only reload settings if they chanced since the last reload\r
+               if (settingsCache != entry.Summary)\r
+               {\r
+                       restoreDefaultSettings();\r
+                       var stringlist = entry.Summary.split("|");\r
+                       // skip the first two entries, those contain header and version info\r
+                       for(var i = 2; i < stringlist.length - 1; i++) {\r
+                               var pair = stringlist[i].split('=');\r
+                               var key = pair[0];\r
+                               var value = pair[1];\r
+                               if (key == null || value == null || config[key] == null) {\r
+                                       log('Warning: unknown or invalid setting: ' + stringlist[i]);\r
+                                       continue;\r
+                               }\r
+                               log('stringlist[' + i + ']: ' + key + '=\'' + value + '\'');\r
+                               if (config[key].Type == 'Int') {\r
+                                       config[key].Value = Number(value);\r
+                                       if (isNaN(config[key].Value))\r
+                                               config[key].Value = config[key].Default;\r
+                               }\r
+                               else if (config[key].Type == 'String')\r
+                                       config[key].Value = value;\r
+                               else if (config[key].Type == 'Bool')\r
+                                       config[key].Value = (value == 'true')\r
+                               else if (config[key].Type == 'Enum')\r
+                                       config[key].Value = value;\r
+                               else if (config[key].Type == 'UID') {\r
+                                       config[key].Value = Number(value);\r
+                                       if (isNaN(config[key].Value))\r
+                                               config[key].Value = config[key].Default;\r
+                               }\r
+                               else if (config[key].Type == 'Array') {\r
+                                       config[key].Value = value.split("^");\r
+                                       if (config[key].Value.length == 1 && config[key].Value[0] == "") {\r
+                                               config[key].Value = [];\r
+                                       }\r
+                               }\r
+                       }\r
+                       settingsCache = entry.Summary;\r
+                       updateCssClasses();\r
+               }\r
+               else {\r
+                       log("Settings already cached and did not change");\r
+               }\r
+       }\r
+       else {\r
+               error("Failed to load settings, calendar entry could not be found");\r
        }\r
 }\r
 \r
 function saveSettings()\r
 {\r
+       getSettingsCalEntryId();\r
+       var item = new Object();\r
+       item.Type = "DayEvent";\r
+       item.StartTime = new Date(2000, 0, 1);\r
+       item.LocalId = settingsCalEntryId;\r
+       item.Summary = "ComingNext Settings|" + version + "|";\r
+\r
        for (var key in config) {\r
                if (config[key].Type == 'Int')\r
-                       widget.setPreferenceForKey(config[key].Value.toString(), key);\r
+                       item.Summary += key + "=" + config[key].Value.toString() + "|";\r
                else if (config[key].Type == 'String')\r
-                       widget.setPreferenceForKey(config[key].Value, key);\r
+                       item.Summary += key + "=" + config[key].Value + "|";\r
                else if (config[key].Type == 'Bool')\r
-                       widget.setPreferenceForKey(config[key].Value ? 'true' : 'false', key);\r
+                       item.Summary += key + "=" + (config[key].Value ? 'true' : 'false') + "|";\r
                else if (config[key].Type == 'Enum')\r
-                       widget.setPreferenceForKey(config[key].Value, key);\r
+                       item.Summary += key + "=" + config[key].Value + "|";\r
                else if (config[key].Type == 'UID')\r
-                       widget.setPreferenceForKey(config[key].Value.toString(), key);\r
+                       item.Summary += key + "=" + config[key].Value.toString() + "|";\r
+               else if (config[key].Type == 'Array')\r
+                       item.Summary += key + "=" + config[key].Value.join("^") + "|";\r
+       }\r
+       settingsCache = item.Summary;\r
+       \r
+       var criteria = new Object();\r
+       criteria.Type = "CalendarEntry";\r
+       criteria.Item = item;\r
+\r
+       log("Saving settings to calendar entry: " + item.Summary);\r
+       try {\r
+               var result = calendarService.IDataSource.Add(criteria);\r
+               if (result.ErrorCode)\r
+                       throw(result.ErrorMessage);\r
+       } catch (e) {\r
+               error("saveSettings: " + e + ', line ' + e.line);\r
        }\r
+       \r
+       lastReloadTime = null; // force calendar data reload on next update\r
+       clearUpdateTimer();\r
+       setUpdateTimer();\r
 }\r
 \r
 function toggleVisibility(elementId)\r
@@ -843,20 +1402,18 @@ function printHintBox(text)
 {\r
        uniqueId++;\r
        return '<td width="1%" align="right" onclick="javascript:toggleVisibility(\'info' + uniqueId + '\')">' + getLocalizedText('settings.help') + '</td></tr></table>'+\r
-              '<div class="settingsInfo" id="info' + uniqueId + '">' + text + '</div>';\r
+              '<div class="settingsInfo" id="info' + uniqueId + '" style="display:none">' + text + '</div>';\r
 }\r
 \r
 function showAbout()\r
 {\r
        mode = 3;\r
-       document.getElementById("homescreenView").style.display = "none";\r
-       document.getElementById("fullscreenView").style.display = "none";\r
+       hideViews();\r
        document.getElementById("aboutView").style.display = "block";\r
-       document.getElementById("settingsView").style.display = "none";\r
        document.onclick = null;\r
        \r
        window.menu.setLeftSoftkeyLabel(" ", function(){});\r
-       window.menu.setRightSoftkeyLabel("Back", function()\r
+       window.menu.setRightSoftkeyLabel(getLocalizedText('softkey.back'), function()\r
        {\r
                mode = 1;\r
                showFullscreen();\r
@@ -866,52 +1423,75 @@ function showAbout()
        document.getElementById("name").innerHTML = "Coming Next " + version;\r
 }\r
 \r
+function showHelp() {\r
+       widget.openURL('http://comingnext.sf.net/help');\r
+}\r
+\r
 function updateFullscreen()\r
 {\r
 }\r
 \r
 function showFullscreen()\r
 {\r
-       document.getElementById("homescreenView").style.display = "none";\r
+       log("showFullscreen()");\r
+       hideViews();\r
        document.getElementById("fullscreenView").style.display = "block";\r
-       document.getElementById("aboutView").style.display = "none";\r
-       document.getElementById("settingsView").style.display = "none";\r
        document.getElementById('body').className = "backgroundFullscreen";\r
-       document.onclick = launchCalendar;\r
+       if (!errorOccured)\r
+               document.onclick = launchCalendar;\r
        createMenu();\r
        updateData();\r
 }\r
 \r
+function getBackgroundImage()\r
+{\r
+       if (errorOccured)\r
+               return '';\r
+       var bgImage;\r
+       if (config['backgroundImageLocation'].Value == config['backgroundImageLocation'].ValidValues[0]) // internal\r
+               bgImage = 'background_' + orientation + '.png';\r
+       else\r
+               bgImage = 'C:/Data/background_' + panelNum + '_' + orientation + '.png';\r
+       return bgImage;\r
+}\r
+\r
 function updateHomescreen()\r
 {\r
        if (config['useBackgroundImage'].Value) {\r
+               // check if we have a completely unknown screen resolution\r
+               var screenHeight = screen.height;\r
+               var screenWidth = screen.width;\r
+               if (screenHeight != 640 && screenHeight != 480 && screenHeight != 360)\r
+                       screenHeight = 360; // we can only assume we're in portrait mode, so we set the screen dims as needed for the following code\r
+               if (screenWidth != 640 && screenWidth != 480 && screenWidth != 360)\r
+                       screenWidth = 640; // we can only assume we're in portrait mode, so we set the screen dims as needed for the following code\r
+               \r
                // check for screen rotation\r
-               if (orientation != 'portrait' && screen.width == 360 && screen.height == 640) {\r
+               if (orientation != 'portrait' && ((screenWidth == 360 && screenHeight == 640) || (screenWidth == 640 && screenHeight == 480))) {\r
                        window.widget.prepareForTransition("fade");\r
                        orientation = 'portrait';\r
-                       document.getElementById('body').style.backgroundImage = 'url(background_' + orientation + '.png)';\r
+                       document.getElementById('body').style.backgroundImage = 'url(' + getBackgroundImage() + ')';\r
                        document.getElementById('body').style.backgroundColor = 'none';\r
                        window.widget.performTransition();\r
-               } else if (orientation != 'landscape' && screen.width == 640 && screen.height == 360) {\r
+               } else if (orientation != 'landscape' && ((screenWidth == 640 && screenHeight == 360) || (screenWidth == 480 && screenHeight == 640))) {\r
                        window.widget.prepareForTransition("fade");\r
                        orientation = 'landscape';\r
-                       document.getElementById('body').style.backgroundImage = 'url(background_' + orientation + '.png)';\r
+                       document.getElementById('body').style.backgroundImage = 'url(' + getBackgroundImage() + ')';\r
                        document.getElementById('body').style.backgroundColor = 'none';\r
                        window.widget.performTransition();\r
                }\r
                else if (document.getElementById('body').style.backgroundImage == "")\r
                {\r
-                       document.getElementById('body').style.backgroundImage = 'url(background_' + orientation + '.png)';\r
+                       document.getElementById('body').style.backgroundImage = 'url(' + getBackgroundImage() + ')';\r
                }\r
        }\r
 }\r
 \r
 function showHomescreen()\r
 {\r
+       log("showHomescreen()");\r
+       hideViews();\r
        document.getElementById("homescreenView").style.display = "block";\r
-       document.getElementById("fullscreenView").style.display = "none";\r
-       document.getElementById("aboutView").style.display = "none";\r
-       document.getElementById("settingsView").style.display = "none";\r
        document.getElementById('body').className = "background";\r
        document.onclick = null;\r
        updateData();\r
@@ -924,28 +1504,278 @@ function getLocalizedText(p_Txt)
        else \r
                return 'ERROR: missing translation for ' + p_Txt;\r
 }\r
+\r
+function showUpdate()\r
+{\r
+       mode = 4;\r
+       hideViews();\r
+       document.getElementById("updateView").style.display = "block";\r
+       document.onclick = null;\r
+       \r
+       window.menu.setLeftSoftkeyLabel(getLocalizedText('update.checknow'), function(){\r
+               checkForUpdate();\r
+       });\r
+       window.menu.setRightSoftkeyLabel(getLocalizedText('softkey.back'), function()\r
+       {\r
+               mode = 1;\r
+               showFullscreen();\r
+       });\r
+       \r
+       document.getElementById("currentVersion").innerHTML = getLocalizedText("update.current") + version;\r
+       checkForUpdate();\r
+}\r
+\r
+function checkForUpdate()\r
+{\r
+       // asynch XHR to server url\r
+       reqV = new XMLHttpRequest();\r
+       reqV.onreadystatechange = checkForUpdateCallback;\r
+       document.getElementById("updateDiv").innerHTML = getLocalizedText("update.checking");\r
+       reqV.open("GET", versionURL, true);\r
+       reqV.send(null);\r
+}\r
+\r
+function checkForUpdateCallback()\r
+{ \r
+       if (reqV.readyState == 4) {\r
+               if (reqV.status == 200) {\r
+                       var resultXml = reqV.responseText;\r
+                       if (resultXml) {\r
+                               var div = document.getElementById("tmp");\r
+                               div.innerHTML = resultXml;\r
+                               var newVersion = div.getElementsByTagName('version')[0].innerHTML;\r
+                               var newVersionURL = div.getElementsByTagName('url')[0].innerHTML;\r
+                               div.innerHTML = "";\r
+                               if (version != newVersion) {\r
+                                       document.getElementById("updateDiv").innerHTML = getLocalizedText("update.download").replace(/%1/, newVersion).replace(/%2/, newVersionURL);\r
+                               }\r
+                               else {\r
+                                       document.getElementById("updateDiv").innerHTML = getLocalizedText("update.nonewversion");\r
+                               }\r
+                       }\r
+               }\r
+               else {\r
+                       document.getElementById("updateDiv").innerHTML = getLocalizedText("update.error") + reqV.status + " " + reqV.responseText;\r
+               }\r
+       }\r
+}\r
+\r
+function hideViews()\r
+{\r
+       document.getElementById("homescreenView").style.display = "none";\r
+       document.getElementById("fullscreenView").style.display = "none";\r
+       document.getElementById("aboutView").style.display = "none";\r
+       document.getElementById("settingsView").style.display = "none";\r
+       document.getElementById("updateView").style.display = "none";\r
+}\r
+\r
+function listCalendars()\r
+{\r
+       if (errorOccured) {\r
+               return null;\r
+       }\r
+\r
+       try {\r
+               var criteria = {\r
+                       Type:'Calendar', \r
+                       Filter:{\r
+                               DefaultCalendar: false\r
+                       }\r
+               }\r
+               \r
+               var calendarsResult = calendarService.IDataSource.GetList(criteria);\r
+               if (calendarsResult.ErrorCode != 0)\r
+                       throw("Error fetching list of calendars: " + calendarsResult.ErrorCode + ': ' + calendarsResult.ErrorMessage);\r
+               var calendarListIterator = calendarsResult.ReturnValue;\r
+               \r
+               var calendars = [];\r
+               var count = 0;\r
+               var item;\r
+               while (( item = calendarListIterator.getNext()) != undefined ) {\r
+                       calendars[count++] = item;\r
+               }\r
+               log("Available Calendars: " + calendars.join(", "));\r
+               return calendars;\r
+       } catch(e) {\r
+               error('listing calendars:' + e + ', line ' + e.line);\r
+               return null;\r
+       }\r
+}\r
+\r
+// Copies all objects and their properties to an array. Data is copied so nothing gets lost when the reference is removed\r
+// 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\r
+function listToArray(list, calendarName)\r
+{\r
+       var array = new Array();\r
+       var item;\r
+       var txt = "";\r
+       while (( item = list.getNext()) != undefined ) {\r
+               var itemCopy = new Object();\r
+               for(var i=0; i < entryFields.length; i++) {\r
+                       itemCopy[entryFields[i]] = item[entryFields[i]];\r
+               }\r
+               // for some reason, the CalendarName property is never correctly queried, so we assign it manually here\r
+               if (!itemCopy['CalendarName']) {\r
+                       itemCopy['CalendarName'] = calendarName;\r
+               }\r
+               array.push(itemCopy);\r
+               txt += array[array.length - 1].Summary + ", ";\r
+       }\r
+       log("listToArray(): " + txt);\r
+       return array;\r
+}\r
+\r
+function sortCalendarEntries(a, b)\r
+{\r
+       var atime, btime;\r
+       log("sortCalendarEntries(" + a.Summary + "," + b.Summary + ")");\r
+       \r
+       if (a.InstanceStartTime != null) {\r
+               atime = a.InstanceStartTime;\r
+       }\r
+       else if (a.StartTime != null) {\r
+               atime = a.StartTime;\r
+       }\r
+       else if (a.InstanceEndTime != null) {\r
+               atime = a.InstanceEndTime;\r
+       }\r
+       else if (a.EndTime != null) {\r
+               atime = a.EndTime;\r
+       }\r
+       \r
+       if (b.InstanceStartTime != null) {\r
+               btime = b.InstanceStartTime;\r
+       }\r
+       else if (b.StartTime != null) {\r
+               btime = b.StartTime;\r
+       }\r
+       else if (b.InstanceEndTime != null) {\r
+               btime = b.InstanceEndTime;\r
+       }\r
+       else if (b.EndTime != null) {\r
+               btime = b.EndTime;\r
+       }\r
+       \r
+       if (atime && btime) {\r
+       \r
+               atime = parseDate(atime);\r
+               btime = parseDate(btime);\r
+       \r
+               // sort by date & time\r
+               if (atime < btime) {\r
+                       return -1;\r
+               }\r
+               else if (atime > btime) {\r
+                       return 1;\r
+               }\r
+               // sort by type\r
+               else if (a.Type != b.Type) {\r
+                       if (a.Type < b.Type) {\r
+                               return -1;\r
+                       }\r
+                       else if (a.Type > b.Type) {\r
+                               return 1;\r
+                       }\r
+               }\r
+               // sort by description\r
+               else if (a.Summary && b.Summary && a.Summary != b.Summary) {\r
+                       if (a.Summary < b.Summary) {\r
+                               return -1;\r
+                       }\r
+                       else if (a.Summary > b.Summary) {\r
+                               return 1;\r
+                       }\r
+               }\r
+       }\r
+       // NOTE: events my have no date information at all. In that case, we list events without date first\r
+       else if (atime && !btime) {\r
+               return 1;\r
+       }\r
+       else if (!atime && btime) {\r
+               return -1;\r
+       }\r
+       else if (!atime && !btime) {\r
+               // sort by type\r
+               if (a.Type != b.Type) {\r
+                       if (a.Type < b.Type) {\r
+                               return -1;\r
+                       }\r
+                       else if (a.Type > b.Type) {\r
+                               return 1;\r
+                       }\r
+               }\r
+               // sort by description\r
+               else if (a.Summary && b.Summary && a.Summary != b.Summary) {\r
+                       if (a.Summary < b.Summary) {\r
+                               return -1;\r
+                       }\r
+                       else if (a.Summary > b.Summary) {\r
+                               return 1;\r
+                       }\r
+               }\r
+       }\r
+\r
+       return 0;\r
+}\r
+\r
+function updateCalendarColors()\r
+{\r
+       var maxColors = 6;\r
+       calendarColors = [];\r
+       if (calendarList.length > maxColors) {\r
+               log("updateCalendarColors(): Warning: more calendars than available indicator colors");\r
+       }\r
+       for(var i=0; i < calendarList.length; i++) {\r
+               calendarColors[calendarList[i]] = (i % maxColors) + 1;\r
+       }\r
+}\r
+\r
+function log(message)\r
+{\r
+       if (config['enableLogging'].Value) {\r
+               console.info(message);\r
+       }\r
+}\r
+\r
+function getDefaultFontSize()\r
+{\r
+       if (defaultFontSize == null) {\r
+               var pa = document.body;\r
+               var who = document.createElement('div');\r
+               who.className = 'defaultEm';\r
+               who.appendChild(document.createTextNode('M'));\r
+               pa.appendChild(who);\r
+               var fs = [who.offsetWidth, who.offsetHeight];\r
+               pa.removeChild(who);\r
+               defaultFontSize = fs;\r
+       }\r
+       return defaultFontSize;\r
+}\r
+\r
 </script>\r
 \r
 <style type="text/css">\r
-table { margin:0px; padding:0px; border-spacing:0px; }\r
-td { padding:0px 5px 0px 0px; white-space:nowrap; overflow:hidden; }\r
+a { color:#aaccff }\r
+table { margin:0px; padding:0px; border-spacing:0px; border-collapse: collapse; }\r
+td { padding:0px 5px 0px 0px; white-space:nowrap; overflow:visible; margin:0px; }\r
 hr { color:#ffffff; background-color:#ffffff; height:1px; text-align:left; border-style:none; }\r
 .settingsInfo { display:none; font-style:italic; }\r
 .title { font-weight:bold; font-size:14pt; }\r
 .textInput { width:90%; }\r
 .credits { margin-left:40px; text-indent: -20px; margin-bottom:0px; }\r
-#homescreenView { width: 315px; height:91px; overflow:hidden; }\r
-#calendarList { position:absolute; left:10px; top:4px; width:295px; height:75px; overflow:hidden; }\r
+#homescreenView { width: 312px; height:82px; overflow:hidden; }\r
+#calendarList { position:absolute; left:5px; top:0px; width:307px; height:82px; overflow:hidden; }\r
 #name { text-align:center; }\r
 #appicon { display: block; margin-left: auto; margin-right: auto; margin-top: 10px; }\r
 #smallappicon { width:22px; height:22px; margin-right:10px; float:left; }\r
+.defaultEm { font-size:1em; position:absolute; line-height:1; padding:0; visibility:hidden; } \r
 </style>\r
 \r
 </head>\r
 \r
-<body id="body" class="background">\r
+<body onload="javascript:setTimeout('init()', 10)" onresize="javascript:updateScreen()" id="body" class="background">\r
 <div id="homescreenView">\r
-       <div id="calendarList"></div>\r
+       <div id="calendarList">loading...</div>\r
 </div>\r
 <div id="fullscreenView" style="display:none;">\r
        <img src="Icon.png" id="smallappicon">\r
@@ -955,7 +1785,7 @@ hr { color:#ffffff; background-color:#ffffff; height:1px; text-align:left; borde
 </div>\r
 <div id="settingsView" style="display:none">\r
        <img src="Icon.png" id="smallappicon">\r
-       <h1 class="title">Settings</h1>\r
+       <h1 id="settingsTitle" class="title">Settings</h1>\r
        <hr />\r
        <div id="settingsList"></div>\r
 </div>\r
@@ -967,10 +1797,26 @@ hr { color:#ffffff; background-color:#ffffff; height:1px; text-align:left; borde
        <p>Contributions:</p>\r
                <p class="credits">Paul Moore (bug fixes, new features and code cleanup)</p>\r
                <p class="credits">Manfred Hanselmann (DST support)</p>\r
-               <p class="credits">Christophe Milsent (translation support & french translation</p>\r
+               <p class="credits">Christophe Milsent (translation support & French translation)</p>\r
+               <p class="credits">Flavio Nathan (Portuguese-Brazilian translation)</p>\r
+               <p class="credits">Tokeda (Russian translation)</p>\r
+               <p class="credits">Marcella Ferrari (Italian translation)</p>\r
+               <p class="credits">Venos (Italian translation)</p>\r
+               <p class="credits">Francisco Rodero (Catalan translation)</p>\r
+               <p class="credits">zbigzbig20 (Polish translation)</p>\r
+               <p class="credits">Streamkeskus (Finnish translation)</p>\r
+               <p class="credits">renek (Czech translation)</p>\r
        <p>This software is open source and licensed under the GPLv3.</p>\r
-       <p>Visit sourceforge.net/projects/comingnext for free updates.</p>\r
+       <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>\r
+       <hr />\r
+</div>\r
+<div id="updateView" style="display:none">\r
+       <img src="Icon.png" id="smallappicon">\r
+       <h1 class="title">Check for update</h1>\r
        <hr />\r
+       <div id="currentVersion">Coming Next ??</div>\r
+       <div id="updateDiv"></div>\r
+       <div id="tmp" style="display:none;"></div>\r
 </div>\r
 </body>\r
 \r