]> code.delx.au - comingnext/blobdiff - comingNext/index.html
added check for disabled compatibility scaling on E6. This solves bug #3509394, howev...
[comingnext] / comingNext / index.html
index 7ba8bf68ea64c225f24e5264c21c1fdb321cd491..1e2f2a337b89ea7c63130de532e8377b41e42c3c 100644 (file)
 <title>Coming Next</title>\r
 \r
 <style type="text/css">\r
-.background { color:#ffffff; background-color:#000000 } /* Defines the background of the widget. If you want to use a background image, set useBackgroundImage = true below */\r
-                                       /* for the default themes, black, gray, and light blue, codes are #292029, #e7dfe7, #009aef */\r
-.backgroundFullscreen { color:#ffffff; background-color:#000000 } /* Same as background but for the fullscreen version of the widget */\r
-.weekDay { }                           /* Defines the appearance of all week day texts */\r
-.date { }                              /* Defines the appearance of all date texts */\r
-.today { color:#ff0000; }              /* Defines the appearance of "Today" text */\r
-.tomorrow { color:#0000ff; }           /* Defines the appearance of "Tomorrow" text */\r
-.time { }                              /* Defines the appearance of all time texts */\r
-.now { color:#ff00ff; }                        /* Defines the appearance of "Now" text */\r
-.description { }                       /* Defines the appearance of all event descriptions */\r
-.icon { width:15px; height:15px; }     /* Defines size and appearance of icons */\r
+/* The following classes can be modified by widget settings */\r
+.background { color:#ffffff; background-color:#000000 }\r
+.backgroundFullscreen { color:#ffffff; background-color:#000000 }\r
+.weekDay { }\r
+.date { }\r
+.today { color:#ff0000 }\r
+.tomorrow { color:#0000ff }\r
+.time { }\r
+.now { color:#ff00ff }\r
+.description { }\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
-var monthRange = 2;            // number of months to include in the event list\r
-var includeTodos = true;       // disable to remove ToDos from event list\r
-var useBackgroundImage = true; // use background_portrait.png and background_landscape.png to fake transparency. Set to "false" to use a solid background color\r
-var showCombinedDateTime = false;// only show the time for events happening today, otherwise just show the date\r
-var showLocation = true;       // show the location for meeting events\r
-var showTodayAsText = true;    // if enabled, the current date will be shown as "Today" instead of "31.12"\r
-var todayText = 'Today';       // text to display for "Today"\r
-var tomorrowText = 'Tomorrow'; // text to display for "Tomorrow"\r
-var showNowAsText = true;      // if enabled, the appointment time will be shown as "Now" instead of "12:00"\r
-var nowText = 'Now';           // text to display for "Now"\r
-var dateSeparator = '.';       // separator for dates. e.g. "31.12" or "31/12"\r
-var dateFormat = 'auto'                // how dates will be displayed. 'auto' will autodetect your phone's date format setting. 'MMDD' will write month first, 'DDMM' will write day first\r
-var weekDayLength = 2;         // defines how many characters of the weekday will be shown. E.g. 2 will cut "Friday" to "Fr"\r
-var updateDataInterval = 5;    // how many minutes to wait before updating the displayed data. The higher the number, the less battery is used\r
-var calendarApp = 0x10005901;  // UID of the calendar app to run when clicking the widget. 0x10005901 = buildin calendar, 0x20004ec1 = Epocware Handy Calendar\r
-var eventsPerWidget = 4;       // number of events to show per widget. Default is 4\r
-var showNothingText = true;    // if set to "true", show a text if no events are in the list\r
-var nothingText = 'No further events within ' + monthRange + ' months';                // text to show when no events are in the list\r
-var enableDaylightSaving = true;// enable this if you are in a timezone that has daylight saving time (+1h)\r
-\r
-var cssStyle_background = "color:#ffffff; background-color:#000000"; // Defines the background of the widget. If you want to use a background image, set useBackgroundImage = true below. For the default themes, black, gray, and light blue, codes are #292029, #e7dfe7, #009aef\r
-var cssStyle_backgroundFullscreen = "color:#ffffff; background-color:#000000"; // Same as background but for the fullscreen version of the widget\r
-var cssStyle_weekDay = "";                     // Defines the appearance of all week day texts\r
-var cssStyle_date = "";                                // Defines the appearance of all date texts\r
-var cssStyle_today = "color:#ff0000";          // Defines the appearance of "Today" text\r
-var cssStyle_tomorrow = "color:#0000ff";       // Defines the appearance of "Tomorrow" text\r
-var cssStyle_time = "";                                // Defines the appearance of all time texts\r
-var cssStyle_now = "color:#ff00ff";            // Defines the appearance of "Now" text\r
-var cssStyle_description = "";                 // Defines the appearance of all event descriptions\r
-var cssStyle_icon = "width:15px; height:15px"; // Defines size and appearance of icons\r
+// valid types for the config object are 'Int', 'Bool', 'String', 'Enum', 'UID', 'Array'\r
+var config = {\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
+       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
+       cssStyle_date: { Type: 'String', Default: '', Value: '',},\r
+       cssStyle_today: { Type: 'String', Default: 'color:#ff0000', Value: 'color:#ff0000',},\r
+       cssStyle_tomorrow: { Type: 'String', Default: 'color:#0000ff', Value: 'color:#0000ff',},\r
+       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_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.23";\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
 \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
@@ -78,7 +133,7 @@ function isLeapYear( year ) {
        else\r
                return false;\r
 }\r
-         \r
+\r
 function calcLeapYear(year, days)\r
 {\r
        if (isLeapYear(year))\r
@@ -95,70 +150,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
@@ -178,7 +244,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
@@ -196,13 +282,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
@@ -212,7 +296,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
@@ -228,16 +312,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
@@ -247,32 +368,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
@@ -284,60 +457,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
+       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
-       // 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
+       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
-       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
+       \r
        // take care of daylight saving time\r
-       if (enableDaylightSaving) {\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
+       if (config['enableDaylightSaving'].Value) {\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
@@ -348,27 +546,34 @@ function formatDate(date, format)
        while (day.length < 2) { day = '0' + day; }\r
        while (month.length < 2) { month = '0' + month; }\r
 \r
-       if (showTodayAsText && isToday(date))\r
-               return '<span class="today">' + todayText + '</span>';\r
-       if (showTodayAsText && isTomorrow(date))\r
-               return '<span class="tomorrow">' + tomorrowText + '</span>';\r
+       if (config['showTodayAsText'].Value && isToday(date))\r
+               return '<span class="today">' + config['todayText'].Value + '</span>';\r
+       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
-               if (dateFormat == 'auto' || dateFormat == 'DDMM')\r
-                       return day + dateSeparator + month;\r
+               if (config['dateFormat'].Value == 'auto' || config['dateFormat'].Value == 'DDMM')\r
+                       return day + config['dateSeparator'].Value + month;\r
                else\r
-                       return month + dateSeparator + day;\r
+                       return month + config['dateSeparator'].Value + day;\r
        }\r
 \r
        var dayFirst = true;\r
-       if (dateFormat == 'MMDD')\r
+       if (config['dateFormat'].Value == 'MMDD')\r
                dayFirst = false;\r
-       else if (dateFormat == 'DDMM')\r
+       else if (config['dateFormat'].Value == 'DDMM')\r
                dayFirst = true;\r
        else {\r
-               // dateFormat == 'auto', try to detect system setting\r
+               // config['dateFormat'].Value == 'auto', try to detect system setting\r
                // parse date\r
                var day_ = dateArr[1];\r
                var month_ = dateArr[2];\r
@@ -391,60 +596,136 @@ function formatDate(date, format)
        }\r
 \r
        if (dayFirst)\r
-               return day + dateSeparator + month;\r
+               return day + config['dateSeparator'].Value + month;\r
        else\r
-               return month + dateSeparator + day;\r
+               return month + config['dateSeparator'].Value + day;\r
 }\r
 \r
 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
-       if (showNowAsText && date.getTime() == now.getTime())\r
-               time = '<span class="now">' + nowText + '</span>';\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
-       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() + monthRange, 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
-               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 (includeTodos) {\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
@@ -452,42 +733,72 @@ function updateData()
                var counter = 0;\r
                var entryDate = '';\r
                var dateArr = [];\r
-               var entriesHtml = '<table>';\r
+               var fontsize = 'normal';\r
+               var lineheight = 'normal';\r
+               if (mode == 0) {\r
+                       fontsize = parseInt(72 / config['eventsPerWidget'].Value) + 'px';\r
+                       lineheight = parseInt(82 / config['eventsPerWidget'].Value) + 'px';\r
+                       \r
+                       if (config['eventsPerWidget'].Value == 3) {\r
+                               changeCssClass('.icon', 'width:20px; height:20px');\r
+                       }\r
+                       else if (config['eventsPerWidget'].Value == 5) {\r
+                               changeCssClass('.icon', 'width:10px; height:10px');\r
+                       }\r
+                       else if (config['eventsPerWidget'].Value == 6) {\r
+                               changeCssClass('.icon', 'width:8px; height:8px');\r
+                       }\r
+               }\r
+               else\r
+                       changeCssClass('.icon', config['cssStyle_icon'].Value);\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) ? eventsPerWidget : 2 * eventsPerWidget);\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" || !includeTodos)) {\r
-                                       console.info('skipping ' + entry.id );\r
+                               if (entry.Type == 'ToDo' && (entry.Status == "TodoCompleted" || !config['includeTodos'].Value)) {\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) {\r
-                                       console.info('skipped (already included) ' + entry.id);\r
+                               if (eventIds[entry.id] == 1 && entry.Type == 'ToDo') {\r
+                                       log('skipped (already included) ' + entry.id);\r
                                        counter--;\r
                                        continue;\r
                                } else\r
@@ -495,7 +806,7 @@ function updateData()
 \r
                                // summary can be undefined!\r
                                var Summary = ((entry.Summary == null) ? '' : entry.Summary);\r
-                               if (entry.Type == 'Meeting' && entry.Location != '' && showLocation)\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
@@ -510,15 +821,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
@@ -529,7 +848,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
@@ -539,9 +858,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
@@ -553,32 +872,51 @@ 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 < eventsPerWidget + 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><span class="calendar' + calendarColors[entry.CalendarName] + '">&nbsp;</span></td>';\r
+                               }\r
+                               entriesHtml += '<td><img class="icon" src="' + entry.Type + '.png" /></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,weekDayLength);\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 ((isToday(date) || isTomorrow(date)) && showTodayAsText) // show weekday if the date string is not text. looks odd otherwise\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
                                                        entriesHtml += '<td class="weekDay" width="1px">' + weekDay + '</td><td width="1px" class="date">' + dateStr + '</td><td colspan="2">';\r
                                        } else if (entry.Type == 'Meeting') {\r
-                                               if (showCombinedDateTime) {\r
+                                               if (config['showCombinedDateTime'].Value) {\r
                                                        if (isToday(date))\r
                                                                entriesHtml += '<td width="1px" colspan="4"><span class="today">' + time + '</span> ';\r
                                                        else if (isTomorrow(date))\r
@@ -586,7 +924,7 @@ function updateData()
                                                        else\r
                                                                entriesHtml += '<td width="1px" class="weekDay">' + weekDay + '</td><td width="1px" class="date">' + dateStr + '</td><td colspan="2">';\r
                                                } else {\r
-                                                       if ((isToday(date) || isTomorrow(date)) && showTodayAsText)\r
+                                                       if ((isToday(date) || isTomorrow(date)) && config['showTodayAsText'].Value)\r
                                                                entriesHtml += '<td colspan="4" width="1px"><span class="today">' + dateStr + '</span> <span class="time">' + time + '</span> ';\r
                                                        else\r
                                                                entriesHtml += '<td width="1px" class="weekDay">' + weekDay + '</td><td width="1px" class="date">' + dateStr + '</td><td width="1px" class="time">' + time + '</td><td>';\r
@@ -597,8 +935,13 @@ function updateData()
                        }\r
                }\r
                entriesHtml += '</table>';\r
-               if (showNothingText && entriesHtml == '<table></table>')\r
-                       entriesHtml = '<div style="width:295px; height:75px; text-align:center; line-height:75px; overflow:visible;">' + nothingText + '</div>';\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
@@ -606,38 +949,62 @@ 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
-               widget.openApplication(calendarApp, "");\r
-               //window.close();\r
+               widget.openApplication(config['calendarApp'].Value, "");\r
+               if (config['hideWidgetOnCalendarOpen'].Value)\r
+                       window.close();\r
        } catch(e) {\r
                error('starting Calendar App');\r
                return;\r
@@ -646,27 +1013,68 @@ function launchCalendar()
 \r
 function init()\r
 {\r
+       log('New widget instance starting up...');\r
+       \r
        try {\r
                // call calendar service\r
-               calendarService = device.getServiceObject("Service.Calendar", "IDataSource");\r
+               if (device != "undefined")\r
+                       calendarService = device.getServiceObject("Service.Calendar", "IDataSource");\r
+               else\r
+                       throw('device object does not exist');\r
        } catch(e) {\r
-               error('loading Calendar service');\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 * updateDataInterval);\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 (useBackgroundImage)\r
-               // check for screen rotation every 3 secs\r
-               window.setInterval('updateScreen()', 1000 * 3);\r
+       if (config['useBackgroundImage'].Value)\r
+               // check for screen rotation every 1 secs\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
@@ -674,69 +1082,64 @@ function createMenu()
        window.menu.setLeftSoftkeyLabel("",null);\r
        window.menu.setRightSoftkeyLabel("",null);\r
        var id = 0;\r
-       var menuSettings = new MenuItem("Settings", id++);\r
-       var menuCallApp = new MenuItem("Open Calendar App", id++);\r
-       var menuAbout = new MenuItem("About", id++);\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
-       window.menu.setLeftSoftkeyLabel("Save", function()\r
+       window.menu.setLeftSoftkeyLabel(getLocalizedText('settings.save'), function()\r
        {\r
-               //document.forms[0].elements["settings.monthRange"].value = monthRange;\r
-               monthRange = parseInt(document.forms[0].elements["settings.monthRange"].value);\r
-               if (monthRange < 0 || monthRange > 100)\r
-                       monthRange = 2;\r
-               includeTodos = document.forms[0].elements["settings.includeTodos"].checked;\r
-               useBackgroundImage = document.forms[0].elements["settings.useBackgroundImage"].checked;\r
-               showCombinedDateTime = document.forms[0].elements["settings.showCombinedDateTime"].checked;\r
-               showLocation = document.forms[0].elements["settings.showLocation"].checked;\r
-               showTodayAsText = document.forms[0].elements["settings.showTodayAsText"].checked;\r
-               todayText = document.forms[0].elements["settings.todayText"].value;\r
-               tomorrowText = document.forms[0].elements["settings.tomorrowText"].value;\r
-               showNowAsText = document.forms[0].elements["settings.showNowAsText"].checked;\r
-               nowText = document.forms[0].elements["settings.nowText"].value;\r
-               dateSeparator = document.forms[0].elements["settings.dateSeparator"].value;\r
-               dateFormat = document.forms[0].elements["settings.dateFormat"].value;\r
-               if (dateFormat != 'auto' && dateFormat != 'DDMM' && dateFormat != 'MMDD')\r
-                       dateFormat = 'auto';\r
-               weekDayLength = Number(parseInt(document.forms[0].elements["settings.weekDayLength"].value));\r
-               if (weekDayLength < 0 || weekDayLength > 20)\r
-                       weekDayLength = 2;\r
-               updateDataInterval = parseInt(document.forms[0].elements["settings.updateDataInterval"].value);\r
-               if (updateDataInterval < 1 || updateDataInterval > 1000)\r
-                       updateDataInterval = 5;\r
-               calendarApp = parseInt(document.forms[0].elements["settings.calendarApp"].value);\r
-               eventsPerWidget = parseInt(document.forms[0].elements["settings.eventsPerWidget"].value);\r
-               if (eventsPerWidget < 1 || eventsPerWidget > 10)\r
-                       eventsPerWidget = 4;\r
-               showNothingText = document.forms[0].elements["settings.showNothingText"].checked;\r
-               nothingText = document.forms[0].elements["settings.nothingText"].value;\r
-               enableDaylightSaving = document.forms[0].elements["settings.enableDaylightSaving"].checked;\r
-               \r
-               cssStyle_background = document.forms[0].elements["settings.cssStyle_background"].value;\r
-               cssStyle_backgroundFullscreen = document.forms[0].elements["settings.cssStyle_backgroundFullscreen"].value;\r
-               cssStyle_weekDay = document.forms[0].elements["settings.cssStyle_weekDay"].value;\r
-               cssStyle_date = document.forms[0].elements["settings.cssStyle_date"].value;\r
-               cssStyle_today = document.forms[0].elements["settings.cssStyle_today"].value;\r
-               cssStyle_tomorrow = document.forms[0].elements["settings.cssStyle_tomorrow"].value;\r
-               cssStyle_time = document.forms[0].elements["settings.cssStyle_time"].value;\r
-               cssStyle_now = document.forms[0].elements["settings.cssStyle_now"].value;\r
-               cssStyle_description = document.forms[0].elements["settings.cssStyle_description"].value;\r
-               cssStyle_icon = document.forms[0].elements["settings.cssStyle_icon"].value;\r
+               for (var key in config) {\r
+                       if (config[key].Type == 'String')\r
+                               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 || 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
+                               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
                \r
@@ -745,50 +1148,48 @@ function showSettings()
                mode = 1;\r
                showFullscreen();\r
        });\r
-       window.menu.setRightSoftkeyLabel("Cancel", function()\r
+       window.menu.setRightSoftkeyLabel(getLocalizedText('settings.cancel'), function()\r
        {\r
                mode = 1;\r
                showFullscreen();\r
        });\r
        \r
-       document.getElementById("settingsList").innerHTML = \r
-       '<form>' +\r
-       '<table><tr><td>Month Range:<br><input class="textInput" name="settings.monthRange" type="text" value="' + monthRange + '" /></td>' + printHintBox('number of months to include in the event list') +\r
-       '<hr><table><tr><td>Include ToDos:<br><input name="settings.includeTodos" type="checkbox" value="true" ' + (includeTodos ? 'checked="checked"' : '') + '/></td>' + printHintBox('disable to remove ToDos from event list') +\r
-       '<hr><table><tr><td>Use Background Image:<br><input name="settings.useBackgroundImage" type="checkbox" value="true" ' + (useBackgroundImage ? 'checked="checked"' : '') + '/></td>' + printHintBox('use background_portrait.png and background_landscape.png to fake transparency. Disable to use a solid background color') +\r
-       '<hr><table><tr><td>Show Combined Date & Time:<br><input name="settings.showCombinedDateTime" type="checkbox" value="true" ' + (showCombinedDateTime ? 'checked="checked"' : '') + '/></td>' + printHintBox('only show the time for events happening today, otherwise just show the date') +\r
-       '<hr><table><tr><td>Show Location:<br><input name="settings.showLocation" type="checkbox" value="true" ' + (showLocation ? 'checked="checked"' : '') + '/></td>' + printHintBox('show the location for meeting events') +\r
-       '<hr><table><tr><td>Show Today As Text:<br><input name="settings.showTodayAsText" type="checkbox" value="true" ' + (showTodayAsText ? 'checked="checked"' : '') + '/></td>' + printHintBox('if enabled, the current date will be shown as "Today" instead of "31.12"') +\r
-       '<hr><table><tr><td>"Today" Text:<br><input class="textInput" name="settings.todayText" type="text" value="' + todayText + '" /></td>' + printHintBox('text to display for "Today"') + '</tr>' +\r
-       '<hr><table><tr><td>"Tomorrow" Text:<br><input class="textInput" name="settings.tomorrowText" type="text" value="' + tomorrowText + '" /></td>' + printHintBox('text to display for "Tomorrow"') +\r
-       '<hr><table><tr><td>Show "Now" As Text:<br><input name="settings.showNowAsText" type="checkbox" value="true" ' + (showNowAsText ? 'checked="checked"' : '') + '/></td>' + printHintBox('if enabled, the appointment time will be shown as "Now" instead of "12:00"') +\r
-       '<hr><table><tr><td>"Now" Text:<br><input class="textInput" name="settings.nowText" type="text" value="' + nowText + '" /></td>' + printHintBox('text to display for "Now"') +\r
-       '<hr><table><tr><td>Date Separator:<br><input class="textInput" name="settings.dateSeparator" type="text" value="' + dateSeparator + '" /></td>' + printHintBox('separator for dates. e.g. "31.12" or "31/12"') +\r
-       '<hr><table><tr><td>Date Format:<br><select name="settings.dateFormat" size="1">' + \r
-               '<option label="auto detect"' + (dateFormat == 'auto' ? ' selected="selected"' : '') + '>auto</option>' +\r
-               '<option label="MMDD"' + (dateFormat == 'MMDD' ? ' selected="selected"' : '') + '>MMDD</option>' +\r
-               '<option label="DDMM"' + (dateFormat == 'DDMM' ? ' selected="selected"' : '') + '>DDMM</option>' +\r
-               '</select></div>' + printHintBox('how dates will be displayed. \'auto\' will autodetect your phone\'s date format setting. \'MMDD\' will write month first, \'DDMM\' will write day first') + '</tr>' +\r
-       '<hr><table><tr><td>Weekday Length:<br><input class="textInput" name="settings.weekDayLength" type="text" value="' + weekDayLength + '" /></td>' + printHintBox('defines how many characters of the weekday will be shown. E.g. 2 will cut "Friday" to "Fr"') +\r
-       '<hr><table><tr><td>Update Data Interval:<br><input class="textInput" name="settings.updateDataInterval" type="text" value="' + updateDataInterval + '" /></td>' + printHintBox('how many minutes to wait before updating the displayed data. The higher the number, the less battery is used') +\r
-       '<hr><table><tr><td>Calendar Application UID:<br><input class="textInput" name="settings.calendarApp" type="text" value="0x' + calendarApp.toString(16) + '" /></td>' + printHintBox('UID of the calendar app to run when clicking the widget. 0x10005901 = buildin calendar, 0x20004ec1 = Epocware Handy Calendar') +\r
-       '<hr><table><tr><td>Events Per Widget:<br><input class="textInput" name="settings.eventsPerWidget" type="text" value="' + eventsPerWidget + '" /></td>' + printHintBox('number of events to show per widget. Default is 4') +\r
-       '<hr><table><tr><td>Show Nothing Text:<br><input name="settings.showNothingText" type="checkbox" value="true" ' + (showNothingText ? 'checked="checked"' : '') + '/></td>' + printHintBox('if enabled, show a text if no events are in the list') +\r
-       '<hr><table><tr><td>"nothing" Text:<br><input class="textInput" name="settings.nothingText" type="text" value="' + nothingText + '" /></td>' + printHintBox('text to show when no events are in the list') +\r
-       '<hr><table><tr><td>Enable Daylight Saving:<br><input name="settings.enableDaylightSaving" type="checkbox" value="true" ' + (enableDaylightSaving ? 'checked="checked"' : '') + '/></td>' + printHintBox('enable this if you are in a timezone that has daylight saving time (+1h)') +\r
-       '<hr style="margin-bottom:60px;"><h1 class="title">CSS Styles</h1>' +\r
-       '<hr><table><tr><td>.background:<br><input class="textInput" name="settings.cssStyle_background" type="text" value="' + cssStyle_background + '" /></td>' + printHintBox('Defines the background of the widget. If you want to use a background image, enable "useBackgroundImage" instead. For the default themes, black, gray, and light blue, codes are #292029, #e7dfe7, #009aef') +\r
-       '<hr><table><tr><td>.backgroundFullscreen:<br><input class="textInput" name="settings.cssStyle_backgroundFullscreen" type="text" value="' + cssStyle_backgroundFullscreen + '" /></td>' + printHintBox('Same as background but for the fullscreen version of the widget') +\r
-       '<hr><table><tr><td>.weekDay:<br><input class="textInput" name="settings.cssStyle_weekDay" type="text" value="' + cssStyle_weekDay + '" /></td>' + printHintBox('Defines the appearance of all week day texts') +\r
-       '<hr><table><tr><td>.date:<br><input class="textInput" name="settings.cssStyle_date" type="text" value="' + cssStyle_date + '" /></td>' + printHintBox('Defines the appearance of all date texts') +\r
-       '<hr><table><tr><td>.today:<br><input class="textInput" name="settings.cssStyle_today" type="text" value="' + cssStyle_today + '" /></td>' + printHintBox('Defines the appearance of "Today" text') +\r
-       '<hr><table><tr><td>.tomorrow:<br><input class="textInput" name="settings.cssStyle_tomorrow" type="text" value="' + cssStyle_tomorrow + '" /></td>' + printHintBox('Defines the appearance of "Tomorrow" text') +\r
-       '<hr><table><tr><td>.time:<br><input class="textInput" name="settings.cssStyle_time" type="text" value="' + cssStyle_time + '" /></td>' + printHintBox('Defines the appearance of all time texts') +\r
-       '<hr><table><tr><td>.now:<br><input class="textInput" name="settings.cssStyle_now" type="text" value="' + cssStyle_now + '" /></td>' + printHintBox('Defines the appearance of "Now" text') +\r
-       '<hr><table><tr><td>.description:<br><input class="textInput" name="settings.cssStyle_description" type="text" value="' + cssStyle_description + '" /></td>' + printHintBox('Defines the appearance of all event descriptions') +\r
-       '<hr><table><tr><td>.icon:<br><input class="textInput" name="settings.cssStyle_icon" type="text" value="' + cssStyle_icon + '" /></td>' + printHintBox('Defines size and appearance of icons') +\r
-       '<hr style="margin-bottom:60px;"><input name="reset" type="button" value="Restore Defaults" onclick="javascript:restoreDefaultSettings();showSettings();" />' +\r
-       '</form>';\r
+       var settingsHtml = '<form>';\r
+       for (var key in config) {\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
+                       settingsHtml += '<table><tr><td>' + getLocalizedText('settings.name.' + key) + '<br /><input name="settings.' + key + '" type="checkbox" value="true" ' + (config[key].Value ? 'checked="checked"' : '') + '/></td>' + printHintBox(getLocalizedText('settings.info.' + key)) + '<hr />';\r
+               else if (config[key].Type == 'UID')\r
+                       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 value="' + config[key].ValidValues[i] + '"' + (config[key].Value == config[key].ValidValues[i] ? ' selected="selected"' : '') + '>' + getLocalizedText('settings.validValues.' + key + '.' + config[key].ValidValues[i]) + '</option>';\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
+       document.getElementById("settingsList").innerHTML = settingsHtml;\r
 }\r
 \r
 function changeCssClass(classname, properties)\r
@@ -805,234 +1206,182 @@ function changeCssClass(classname, properties)
 \r
 function updateCssClasses()\r
 {\r
-       changeCssClass(".background", cssStyle_background);\r
-       changeCssClass(".backgroundFullscreen", cssStyle_backgroundFullscreen);\r
-       changeCssClass(".weekDay", cssStyle_weekDay);\r
-       changeCssClass(".date", cssStyle_date);\r
-       changeCssClass(".today", cssStyle_today);\r
-       changeCssClass(".tomorrow", cssStyle_tomorrow);\r
-       changeCssClass(".time", cssStyle_time);\r
-       changeCssClass(".now", cssStyle_now);\r
-       changeCssClass(".description", cssStyle_description);\r
-       changeCssClass(".icon", cssStyle_icon);\r
+       for(var key in config) {\r
+               changeCssClass(getLocalizedText('settings.name.' + key), config[key].Value);\r
+       }\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
-       monthRange = 2;\r
-       includeTodos = true;\r
-       useBackgroundImage = true;\r
-       showCombinedDateTime = false;\r
-       showLocation = true;\r
-       showTodayAsText = true;\r
-       todayText = 'Today';\r
-       tomorrowText = 'Tomorrow';\r
-       showNowAsText = true;\r
-       nowText = 'Now';\r
-       dateSeparator = '.';\r
-       dateFormat = 'auto'\r
-       weekDayLength = 2;\r
-       updateDataInterval = 5;\r
-       calendarApp = 0x10005901;\r
-       eventsPerWidget = 4;\r
-       showNothingText = true;\r
-       nothingText = 'No further events within ' + monthRange + ' months';\r
-       enableDaylightSaving = true;\r
-\r
-       cssStyle_background = "color:#ffffff; background-color:#000000";\r
-       cssStyle_backgroundFullscreen = "color:#ffffff; background-color:#000000";\r
-       cssStyle_weekDay = "";\r
-       cssStyle_date = "";\r
-       cssStyle_today = "color:#ff0000";\r
-       cssStyle_tomorrow = "color:#0000ff";\r
-       cssStyle_time = "";\r
-       cssStyle_now = "color:#ff00ff";\r
-       cssStyle_description = "";\r
-       cssStyle_icon = "width:15px; height:15px";\r
+       for (var key in config)\r
+               config[key].Value = config[key].Default;\r
 }\r
 \r
 function loadSettings()\r
 {\r
-       if (widget.preferenceForKey('monthRange'))\r
-               monthRange = Number(widget.preferenceForKey('monthRange'));\r
-       else\r
-               monthRange = 2;\r
-       \r
-       if (widget.preferenceForKey('includeTodos'))\r
-               includeTodos = (widget.preferenceForKey('includeTodos') == 'true');\r
-       else\r
-               includeTodos = true;\r
-       \r
-       if (widget.preferenceForKey('useBackgroundImage'))\r
-               useBackgroundImage = (widget.preferenceForKey('useBackgroundImage') == 'true');\r
-       else\r
-               useBackgroundImage = true;\r
-       \r
-       if (widget.preferenceForKey('showCombinedDateTime'))\r
-               showCombinedDateTime = (widget.preferenceForKey('showCombinedDateTime') == 'true');\r
-       else\r
-               showCombinedDateTime = false;\r
-       \r
-       if (widget.preferenceForKey('showLocation'))\r
-               showLocation = (widget.preferenceForKey('showLocation') == 'true');\r
-       else\r
-               showLocation = true;\r
-       \r
-       if (widget.preferenceForKey('showTodayAsText'))\r
-               showTodayAsText = (widget.preferenceForKey('showTodayAsText') == 'true');\r
-       else\r
-               showTodayAsText = true;\r
-       \r
-       if (widget.preferenceForKey('todayText'))\r
-               todayText = widget.preferenceForKey('todayText');\r
-       else\r
-               todayText = 'Today';\r
-       \r
-       if (widget.preferenceForKey('tomorrowText'))\r
-               tomorrowText = widget.preferenceForKey('tomorrowText');\r
-       else\r
-               tomorrowText = 'Tomorrow';\r
-       \r
-       if (widget.preferenceForKey('showNowAsText'))\r
-               showNowAsText = (widget.preferenceForKey('showNowAsText') == 'true');\r
-       else\r
-               showNowAsText = true;\r
-       \r
-       if (widget.preferenceForKey('nowText'))\r
-               nowText = widget.preferenceForKey('nowText');\r
-       else\r
-               nowText = 'Now';\r
-       \r
-       if (widget.preferenceForKey('dateSeparator'))\r
-               dateSeparator = widget.preferenceForKey('dateSeparator');\r
-       else\r
-               dateSeparator = '.';\r
-       \r
-       if (widget.preferenceForKey('dateFormat'))\r
-               dateFormat = widget.preferenceForKey('dateFormat');\r
-       else\r
-               dateFormat = 'auto';\r
-       \r
-       if (widget.preferenceForKey('weekDayLength'))\r
-               weekDayLength = Number(widget.preferenceForKey('weekDayLength'));\r
-       else\r
-               weekDayLength = 2;\r
-       \r
-       if (widget.preferenceForKey('updateDataInterval'))\r
-               updateDataInterval = Number(widget.preferenceForKey('updateDataInterval'));\r
-       else\r
-               updateDataInterval = 5;\r
-       \r
-       if (widget.preferenceForKey('calendarApp'))\r
-               calendarApp = Number(widget.preferenceForKey('calendarApp'));\r
-       else\r
-               calendarApp = 0x10005901;\r
-       \r
-       if (widget.preferenceForKey('eventsPerWidget'))\r
-               eventsPerWidget = Number(widget.preferenceForKey('eventsPerWidget'));\r
-       else\r
-               eventsPerWidget = 4;\r
-       \r
-       if (widget.preferenceForKey('showNothingText'))\r
-               showNothingText = (widget.preferenceForKey('showNothingText') == 'true');\r
-       else\r
-               showNothingText = true;\r
-       \r
-       if (widget.preferenceForKey('nothingText'))\r
-               nothingText = widget.preferenceForKey('nothingText');\r
-       else\r
-               nothingText = 'No further events within ' + monthRange + ' months';\r
-       \r
-       if (widget.preferenceForKey('enableDaylightSaving'))\r
-               enableDaylightSaving = (widget.preferenceForKey('enableDaylightSaving') == 'true');\r
-       else\r
-               enableDaylightSaving = true;\r
-       \r
-       // CSS styles\r
-               \r
-       if (widget.preferenceForKey('cssStyle_background'))\r
-               cssStyle_background = widget.preferenceForKey('cssStyle_background');\r
-       else\r
-               cssStyle_background = "color:#ffffff; background-color:#000000";\r
-\r
-       if (widget.preferenceForKey('cssStyle_backgroundFullscreen'))\r
-               cssStyle_backgroundFullscreen = widget.preferenceForKey('cssStyle_backgroundFullscreen');\r
-       else\r
-               cssStyle_backgroundFullscreen = "color:#ffffff; background-color:#000000";\r
-               \r
-       if (widget.preferenceForKey('cssStyle_weekDay'))\r
-               cssStyle_weekDay = widget.preferenceForKey('cssStyle_weekDay');\r
-       else\r
-               cssStyle_weekDay = "";\r
-               \r
-       if (widget.preferenceForKey('cssStyle_date'))\r
-               cssStyle_date = widget.preferenceForKey('cssStyle_date');\r
-       else\r
-               cssStyle_date = "";\r
-               \r
-       if (widget.preferenceForKey('cssStyle_today'))\r
-               cssStyle_today = widget.preferenceForKey('cssStyle_today');\r
-       else\r
-               cssStyle_today = "color:#ff0000";\r
-       \r
-       if (widget.preferenceForKey('cssStyle_tomorrow'))\r
-               cssStyle_tomorrow = widget.preferenceForKey('cssStyle_tomorrow');\r
-       else\r
-               cssStyle_tomorrow = "color:#0000ff";\r
-       \r
-       if (widget.preferenceForKey('cssStyle_time'))\r
-               cssStyle_time = widget.preferenceForKey('cssStyle_time');\r
-       else\r
-               cssStyle_time = "";\r
-       \r
-       if (widget.preferenceForKey('cssStyle_now'))\r
-               cssStyle_now = widget.preferenceForKey('cssStyle_now');\r
-       else\r
-               cssStyle_now = "color:#ff00ff";\r
-       \r
-       if (widget.preferenceForKey('cssStyle_description'))\r
-               cssStyle_description = widget.preferenceForKey('cssStyle_description');\r
-       else\r
-               cssStyle_description = "";\r
-       \r
-       if (widget.preferenceForKey('cssStyle_icon'))\r
-               cssStyle_icon = widget.preferenceForKey('cssStyle_icon');\r
-       else\r
-               cssStyle_icon = "width:15px; height:15px";\r
+       getSettingsCalEntryId();\r
+       var listFiltering = {\r
+               Type:'CalendarEntry', \r
+               Filter:{\r
+                       LocalId: settingsCalEntryId\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("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
-       widget.setPreferenceForKey(monthRange.toString(), 'monthRange');\r
-       widget.setPreferenceForKey(includeTodos ? 'true' : 'false', 'includeTodos');\r
-       widget.setPreferenceForKey(useBackgroundImage ? 'true' : 'false', 'useBackgroundImage');\r
-       widget.setPreferenceForKey(showCombinedDateTime ? 'true' : 'false', 'showCombinedDateTime');\r
-       widget.setPreferenceForKey(showLocation ? 'true' : 'false', 'showLocation');\r
-       widget.setPreferenceForKey(showTodayAsText ? 'true' : 'false', 'showTodayAsText');\r
-       widget.setPreferenceForKey(todayText, 'todayText');\r
-       widget.setPreferenceForKey(tomorrowText, 'tomorrowText');\r
-       widget.setPreferenceForKey(showNowAsText ? 'true' : 'false', 'showNowAsText');\r
-       widget.setPreferenceForKey(nowText, 'nowText');\r
-       widget.setPreferenceForKey(dateSeparator, 'dateSeparator');\r
-       widget.setPreferenceForKey(dateFormat, 'dateFormat');\r
-       widget.setPreferenceForKey(weekDayLength.toString(), 'weekDayLength');\r
-       widget.setPreferenceForKey(updateDataInterval.toString(), 'updateDataInterval');\r
-       widget.setPreferenceForKey(calendarApp.toString(), 'calendarApp');\r
-       widget.setPreferenceForKey(eventsPerWidget.toString(), 'eventsPerWidget');\r
-       widget.setPreferenceForKey(showNothingText ? 'true' : 'false', 'showNothingText');\r
-       widget.setPreferenceForKey(nothingText, 'nothingText');\r
-       widget.setPreferenceForKey(enableDaylightSaving ? 'true' : 'false', 'enableDaylightSaving');\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
+                       item.Summary += key + "=" + config[key].Value.toString() + "|";\r
+               else if (config[key].Type == 'String')\r
+                       item.Summary += key + "=" + config[key].Value + "|";\r
+               else if (config[key].Type == 'Bool')\r
+                       item.Summary += key + "=" + (config[key].Value ? 'true' : 'false') + "|";\r
+               else if (config[key].Type == 'Enum')\r
+                       item.Summary += key + "=" + config[key].Value + "|";\r
+               else if (config[key].Type == 'UID')\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
-       widget.setPreferenceForKey(cssStyle_background, 'cssStyle_background');\r
-       widget.setPreferenceForKey(cssStyle_backgroundFullscreen, 'cssStyle_backgroundFullscreen');\r
-       widget.setPreferenceForKey(cssStyle_weekDay, 'cssStyle_weekDay');\r
-       widget.setPreferenceForKey(cssStyle_date, 'cssStyle_date');\r
-       widget.setPreferenceForKey(cssStyle_today, 'cssStyle_today');\r
-       widget.setPreferenceForKey(cssStyle_tomorrow, 'cssStyle_tomorrow');\r
-       widget.setPreferenceForKey(cssStyle_time, 'cssStyle_time');\r
-       widget.setPreferenceForKey(cssStyle_now, 'cssStyle_now');\r
-       widget.setPreferenceForKey(cssStyle_description, 'cssStyle_description');\r
-       widget.setPreferenceForKey(cssStyle_icon, 'cssStyle_icon');\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
@@ -1047,20 +1396,19 @@ var uniqueId = 0;
 function printHintBox(text)\r
 {\r
        uniqueId++;\r
-       return '<td width="1%" align="right" onclick="javascript:toggleVisibility(\'info' + uniqueId + '\')">Help</td></tr></table>'+\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
 }\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
@@ -1070,86 +1418,354 @@ 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
+       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 (useBackgroundImage) {\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('body').className = "background";\r
+       document.onclick = null;\r
+       updateData();\r
+}\r
+\r
+function getLocalizedText(p_Txt)\r
+{\r
+       if (localizedText[p_Txt])\r
+               return localizedText[p_Txt];\r
+       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.setRequestHeader( "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT" ); // disable caching\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('body').className = "background";\r
-       updateData();\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
 </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:hidden; 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
-#homescreenView { width: 315px; height:91px; overflow:hidden; }\r
-#calendarList { position:absolute; left:10px; top:4px; width:295px; height:75px; overflow:hidden; }\r
+.credits { margin-left:40px; text-indent: -20px; margin-bottom:0px; }\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: 50px; }\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
 </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" onClick="javascript:launchCalendar()">\r
-       <h1 class="title" onClick="javascript:launchCalendar()">Coming Next</h1>\r
+<div id="fullscreenView" style="display:none;">\r
+       <img src="Icon.png" id="smallappicon">\r
+       <h1 class="title">Coming Next</h1>\r
        <hr />\r
        <div id="fullscreenCalendarList">loading...</div>\r
 </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
@@ -1158,9 +1774,29 @@ hr { color:#ffffff; background-color:#ffffff; height:1px; text-align:left; borde
        <h1 id="name">Coming Next</h1>\r
        <hr />\r
        <p>Created by Dr. Cochambre and Michael Prager.</p>\r
+       <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">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 https://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