UI to add libraries was introduced
authorEugene Ostroukhov <eugeneo@symbian.org>
Mon, 19 Apr 2010 18:03:51 -0700
changeset 310 e9484be98cfe
parent 309 c01f5ab28a11
child 311 eef7c6acd0f3
UI to add libraries was introduced
org.symbian.tools.wrttools/icons/phonegap.png
org.symbian.tools.wrttools/libraries/phonegap.js
org.symbian.tools.wrttools/plugin.xml
org.symbian.tools.wrttools/schema/jsLibraries.exsd
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/Activator.java
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/WRTImages.java
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/AddLibraryPopupMenu.java
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/IJSLibraryInstaller.java
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/JSLibrary.java
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/LibrariesUtils.java
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/LibraryManager.java
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/PhoneGapInstaller.java
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/WRTKitInstaller.java
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/handlers/AddJSLibrary.java
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/wizards/WRTProjectLibraryWizardPage.java
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/wizards/WrtWidgetWizard.java
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/wizards/libraries/AddLibrariesWizard.java
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/wizards/libraries/LibraryLabelProvider.java
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/wizards/libraries/LibrarySelectionPage.java
org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/wizards/libraries/WRTProjectLibraryWizardPage.java
Binary file org.symbian.tools.wrttools/icons/phonegap.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.symbian.tools.wrttools/libraries/phonegap.js	Mon Apr 19 18:03:51 2010 -0700
@@ -0,0 +1,1743 @@
+if (typeof(DeviceInfo) != 'object')
+    DeviceInfo = {};
+
+/**
+ * This represents the PhoneGap API itself, and provides a global namespace for accessing
+ * information about the state of PhoneGap.
+ * @class
+ */
+PhoneGap = {
+    queue: {
+        ready: true,
+        commands: [],
+        timer: null
+    },
+    _constructors: []
+};
+
+/**
+ * Boolean flag indicating if the PhoneGap API is available and initialized.
+ */
+PhoneGap.available = DeviceInfo.uuid != undefined;
+
+/**
+ * Execute a PhoneGap command in a queued fashion, to ensure commands do not
+ * execute with any race conditions, and only run when PhoneGap is ready to
+ * recieve them.
+ * @param {String} command Command to be run in PhoneGap, e.g. "ClassName.method"
+ * @param {String[]} [args] Zero or more arguments to pass to the method
+ */
+PhoneGap.exec = function() {
+    PhoneGap.queue.commands.push(arguments);
+    if (PhoneGap.queue.timer == null)
+        PhoneGap.queue.timer = setInterval(PhoneGap.run_command, 10);
+};
+/**
+ * Internal function used to dispatch the request to PhoneGap.  This needs to be implemented per-platform to
+ * ensure that methods are called on the phone in a way appropriate for that device.
+ * @private
+ */
+PhoneGap.run_command = function() {
+};
+
+/**
+ * This class contains acceleration information
+ * @constructor
+ * @param {Number} x The force applied by the device in the x-axis.
+ * @param {Number} y The force applied by the device in the y-axis.
+ * @param {Number} z The force applied by the device in the z-axis.
+ */
+function Acceleration(x, y, z) {
+	/**
+	 * The force applied by the device in the x-axis.
+	 */
+	this.x = x;
+	/**
+	 * The force applied by the device in the y-axis.
+	 */
+	this.y = y;
+	/**
+	 * The force applied by the device in the z-axis.
+	 */
+	this.z = z;
+	/**
+	 * The time that the acceleration was obtained.
+	 */
+	this.timestamp = new Date().getTime();
+}
+
+/**
+ * This class specifies the options for requesting acceleration data.
+ * @constructor
+ */
+function AccelerationOptions() {
+	/**
+	 * The timeout after which if acceleration data cannot be obtained the errorCallback
+	 * is called.
+	 */
+	this.timeout = 10000;
+}
+/**
+ * This class provides access to device accelerometer data.
+ * @constructor
+ */
+function Accelerometer() {
+	/**
+	 * The last known acceleration.
+	 */
+	this.lastAcceleration = null;
+}
+
+/**
+ * Asynchronously aquires the current acceleration.
+ * @param {Function} successCallback The function to call when the acceleration
+ * data is available
+ * @param {Function} errorCallback The function to call when there is an error 
+ * getting the acceleration data.
+ * @param {AccelerationOptions} options The options for getting the accelerometer data
+ * such as timeout.
+ */
+
+Accelerometer.prototype.getCurrentAcceleration = function(successCallback, errorCallback, options) {
+	// If the acceleration is available then call success
+	// If the acceleration is not available then call error
+	
+	try {
+		alert(1);
+		if (!this.serviceObj) 
+			this.serviceObj = this.getServiceObj();
+		
+		if (this.serviceObj == null) 
+			throw {
+				name: "DeviceErr",
+				message: "Could not initialize service object"
+			};
+		
+		//get the sensor channel
+		var SensorParams = {
+			SearchCriterion: "AccelerometerAxis"
+		};
+		var returnvalue = this.serviceObj.ISensor.FindSensorChannel(SensorParams);
+		alert(2);
+		var error = returnvalue["ErrorCode"];
+		var errmsg = returnvalue["ErrorMessage"];
+		if (!(error == 0 || error == 1012)) {
+			var ex = {
+				name: "Unable to find Sensor Channel: " + error,
+				message: errmsg
+			};
+			throw ex;
+		}
+		var channelInfoMap = returnvalue["ReturnValue"][0];
+		var criteria = {
+			ChannelInfoMap: channelInfoMap,
+			ListeningType: "ChannelData"
+		};
+		
+		if (typeof(successCallback) != 'function') 
+			successCallback = function(){
+			};
+		if (typeof(errorCallback) != 'function') 
+			errorCallback = function(){
+			};
+		
+		this.success_callback = successCallback;
+		this.error_callback = errorCallback;
+		//create a closure to persist this instance of Accelerometer into the RegisterForNofication callback
+		var obj = this;
+		
+		// TODO: this call crashes WRT, but there is no other way to read the accel sensor
+		// http://discussion.forum.nokia.com/forum/showthread.php?t=182151&highlight=memory+leak
+		this.serviceObj.ISensor.RegisterForNotification(criteria, function(transId, eventCode, result){
+			try {
+				alert(10);
+				var criteria = {
+					TransactionID: transId
+				};
+				obj.serviceObj.ISensor.Cancel(criteria);
+				
+				var accel = new Acceleration(result.ReturnValue.XAxisData, result.ReturnValue.YAxisData, result.ReturnValue.ZAxisData);
+				Accelerometer.lastAcceleration = accel;
+				
+				obj.success_callback(accel);
+				
+			} 
+			catch (ex) {
+				obj.serviceObj.ISensor.Cancel(criteria);
+				obj.error_callback(ex);
+			}
+			
+		});
+		alert(4);
+	} catch (ex) {
+		alert(5);
+		errorCallback(ex);
+	}
+
+};
+
+
+/**
+ * Asynchronously aquires the acceleration repeatedly at a given interval.
+ * @param {Function} successCallback The function to call each time the acceleration
+ * data is available
+ * @param {Function} errorCallback The function to call when there is an error 
+ * getting the acceleration data.
+ * @param {AccelerationOptions} options The options for getting the accelerometer data
+ * such as timeout.
+ */
+
+Accelerometer.prototype.watchAcceleration = function(successCallback, errorCallback, options) {
+	this.getCurrentAcceleration(successCallback, errorCallback, options);
+	// TODO: add the interval id to a list so we can clear all watches
+ 	var frequency = (options != undefined)? options.frequency : 10000;
+	return setInterval(function() {
+		navigator.accelerometer.getCurrentAcceleration(successCallback, errorCallback, options);
+	}, frequency);
+};
+
+/**
+ * Clears the specified accelerometer watch.
+ * @param {String} watchId The ID of the watch returned from #watchAcceleration.
+ */
+Accelerometer.prototype.clearWatch = function(watchId) {
+	clearInterval(watchId);
+};
+
+//gets the Acceleration Service Object from WRT
+Accelerometer.prototype.getServiceObj = function() {
+	var so;
+	
+    try {
+        so = device.getServiceObject("Service.Sensor", "ISensor");
+    } catch (ex) {
+		throw {
+			name: "DeviceError",
+			message: "Could not initialize accel service object (" + ex.name + ": " + ex.message + ")"
+		};
+    }		
+	return so;
+};
+
+if (typeof navigator.accelerometer == "undefined") navigator.accelerometer = new Accelerometer();/**
+ * This class provides access to the device camera.
+ * @constructor
+ */
+function Camera() {
+	this.success_callback = null;
+	this.error_callback = null;
+}
+
+/**
+ * We use the Platform Services 2.0 API here. So we must include a portion of the
+ * PS 2.0 source code (camera API). 
+ * @param {Function} successCallback
+ * @param {Function} errorCallback
+ * @param {Object} options
+ */
+Camera.prototype.getPicture = function(successCallback, errorCallback, options){
+	try {
+		if (!this.serviceObj) {
+			this.serviceObj = com.nokia.device.load("", "com.nokia.device.camera", "");
+		}
+		if (!this.serviceObj) {
+			throw {
+				name: "CameraError",
+				message: "could not load camera service"
+			};
+		}
+		var obj = this;
+		
+		obj.success_callback = successCallback;
+		obj.error_callback = errorCallback;
+		this.serviceObj.startCamera( function(transactionID, errorCode, outPut) { 
+			//outPut should be an array of image urls (local), or an error code
+			if (errorCode == 0) {
+				obj.success_callback(outPut);
+			}
+			else {
+				obj.error_callback({
+					name: "CameraError",
+					message: errorCode
+				});
+			}
+		});
+		
+	} catch (ex) {
+		errorCallback.call(ex);
+	}
+	
+};
+
+if (typeof navigator.camera == "undefined") navigator.camera = new Camera();/*
+Copyright © 2009 Nokia. All rights reserved.
+Code licensed under the BSD License:
+Software License Agreement (BSD License) Copyright © 2009 Nokia.
+All rights reserved.
+Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+Neither the name of Nokia Corporation. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Nokia Corporation. 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+version: 1.0
+*/
+
+
+// utility.js
+//
+// This file contains some utility functions for S60 providers
+
+
+// Start an application and wait for it to exit
+
+//TBD: Get rid of this global, use closures instead
+
+DeviceError.prototype = new Error(); //inheritance occurs here
+DeviceError.prototype.constructor = DeviceError; //If this not present then, it uses default constructor of Error
+
+//constructor for DeviceError.
+function DeviceError(message,code) 
+{
+	this.toString = concatenate;
+	this.code = code;
+	this.name = "DeviceException";//we can even overwrite default name "Error"
+	this.message=message; 
+}
+
+function concatenate()
+{
+	return (this.name+":"+" "+this.message+" "+this.code);
+}
+
+function splitErrorMessage(errmessage)
+{
+	if(errmessage.search(/:/)!=-1)
+	{
+		if((errmessage.split(":").length)==2)
+		{
+			return errmessage.split(":")[1];
+		}
+		if((errmessage.split(":").length)>2)
+		{
+			return errmessage.split(":")[2];
+		}
+	}
+	return errmessage;
+}
+
+
+var __s60_start_and_wait_cb;
+
+function __s60_on_app_exit(){
+  widget.onshow = null;
+  if(__s60_start_and_wait_cb != null){
+    __s60_start_and_wait_cb();
+  }
+}
+
+function __s60_on_app_start(){
+  widget.onhide = null;
+  widget.onshow = __s60_on_app_exit;
+}
+
+// This function cannot actually force JS to wait,
+// but it does supply a callback the apps can use
+// to continue processing on return from the app.
+// Apps should take care not to reinvoke this and
+// should be careful about any other processing
+// that might happen while the app is running.
+
+function __s60_start_and_wait(id, args, app_exit_cb){
+  __s60_start_and_wait_cb = app_exit_cb;
+  widget.onhide = __s60_on_app_start;
+  widget.openApplication(id, args);
+}
+
+function __s60_api_not_supported(){
+  throw(err_ServiceNotSupported);
+}
+
+function __s60_enumerate_object(object, namespace, func, param){
+    var key;
+    for(key in object){
+       
+        var propname;
+       	if(namespace){
+	    propname = namespace + "." + key;
+	}
+	else{
+	    propname = key;
+	}
+        var value = object[key];
+        if(typeof value == "object"){
+	  __s60_enumerate_object(value, propname, func, param);
+	}
+	else {
+	  func(propname,value, param);
+	}
+    }
+}
+/*
+Copyright © 2009 Nokia. All rights reserved.
+Code licensed under the BSD License:
+Software License Agreement (BSD License) Copyright © 2009 Nokia.
+All rights reserved.
+Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+Neither the name of Nokia Corporation. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Nokia Corporation. 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+version: 1.0
+*/
+
+
+var __device_debug_on__ = true;
+var err_missing_argument = 1003;
+var event_cancelled = 3;
+var err_bad_argument = 1002;
+var err_InvalidService_Argument = 1000;
+var err_ServiceNotReady = 1006;
+var err_ServiceNotSupported = 1004;
+
+function __device_debug(text){
+  //if(__device_debug_on__) alert(text);
+}
+
+function __device_handle_exception(e, text){
+	__device_debug(text);
+	throw(e);
+}
+
+function __device_typeof(value)
+{
+	// First check to see if the value is undefined.
+	if (value == undefined) {
+        return "undefined";
+    }
+	// Check for objects created with the "new" keyword.
+	if (value instanceof Object) {
+		// Check whether it's a string object.
+		if (value instanceof String) {
+			return "String";
+		}
+		// Check whether it's an array object/array literal.
+		else 
+			if (value instanceof Array) {
+				return "Array";
+			}
+	}
+	// dealing with a literal.
+		if (typeof value) {
+			if (typeof value == "object") {
+				if (typeof value == "object" && !value) {
+					return "null";
+				}
+			}
+           // if not null check for other types
+			
+				// Check if it's a string literal.
+			else if (typeof value == "string") {
+					return "string";
+				}
+		}	 
+}
+
+
+// The top-level service object. It would be nice to use a namespace here 
+// (com.nokia.device.service), but emulating namespaces still allows name clashes.
+/*
+var sp_device = {
+        //services: null; // TBD: Make the services list a member of this object?
+	load: __device_service_load,
+        listServices: __device_service_list,
+        listInterfaces: __device_service_interfaces,
+        version: "0.1",
+        info: "device prototype"
+};
+*/
+
+if(undefined == com)
+    var com={};
+
+if( typeof com != "object")
+    throw("com defined as non object");
+
+if(undefined == com.nokia)
+    com.nokia = {};
+
+if( typeof com.nokia != "object")
+    throw("com.nokia defined as non object");
+
+if(undefined == com.nokia.device)
+    com.nokia.device = {
+        load: __device_service_load,
+        listServices: __device_service_list,
+        listInterfaces: __device_service_interfaces,
+        version: "0.1",
+        info: "device prototype"
+        };
+else
+    throw("com.nokia.device already defined");
+
+com.nokia.device.SORT_ASCENDING = 0;
+com.nokia.device.SORT_DESCENDING = 1;
+
+com.nokia.device.SORT_BY_DATE = 0;
+com.nokia.device.SORT_BY_SENDER = 1;
+
+com.nokia.device.STATUS_READ = 0;
+com.nokia.device.STATUS_UNREAD = 1;
+
+
+// Configure the services offered.
+
+var __device_services_inited = false;
+
+var __device_services = [
+
+  // For now, the only service is the base "device"" service
+  {
+    "name":"com.nokia.device",
+    "version": 0.1, 
+    "interfaces": []
+  }
+];
+
+// Initialize the configured services.
+
+function __device_services_init(){
+  if(__device_services_inited){
+    return;
+  }
+  __device_services_inited = true;
+
+  // Get the service-specific service entries. Note that these
+  // need to be individually wrapped by try/catch blocks so that the
+  // interpreter gracefully handles missing services. 
+
+  try {
+    __device_services[0].interfaces.push(__device_geolocation_service_entry);
+  }catch (e){
+    __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_camera_service_entry);
+  }catch (e){
+    __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_media_service_entry);
+  }catch (e){
+//    __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_contacts_service_entry);
+  }catch (e){
+//    __device_debug("Missing library implementation: " + e);
+  }
+ try {
+    __device_services[0].interfaces.push(__device_messaging_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_calendar_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_landmarks_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_event_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_sysinfo_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_sensors_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+
+}
+
+function __device_get_implementation(i){
+  //__device_debug("get_implementation: " + i);
+  return  new i.proto(new(i.providers[0].instance));
+}
+
+function __device_get_descriptor(i){
+  //__device_debug("get_descriptor: " + i);
+  return new i.descriptor(new(i.providers[0].descriptor));
+}
+
+function __device_get_interface(s, interfaceName, version){
+  //__device_debug("get_interface: " + s + " " + interfaceName);
+  var i = s.interfaces;
+  if((interfaceName == null) || (interfaceName == '')){
+    // Interface name not specified, get first interface, ignoring version
+    return __device_get_implementation(i[0]);
+  }
+
+  // Find first match of name and version
+  for (var d in i){
+  
+    if(i[d].name == null){
+      __device_update_descriptor(i[d]);
+    }
+    if(i[d].name == undefined){
+      continue;
+    }
+    if (i[d].name == interfaceName){
+      // Match version if specified
+      if ((version == null) || (version == '') || (i[d].version >= version)){
+	return __device_get_implementation(i[d]);
+      }
+    }
+  }
+  return null;
+}
+
+// Implemention of the load method
+
+function __device_service_load(serviceName, interfaceName, version){
+
+  __device_services_init();
+  
+  // Service name is specified
+   if ((serviceName != null) && (serviceName != '') &&(serviceName != "*")){
+    for(var s in __device_services){
+      if (serviceName == __device_services[s].name){
+	return __device_get_interface(__device_services[s], interfaceName, version);
+      }
+    }
+  // Service name not specified, get first implementation 
+  } else {
+    //__device_debug("Trying to get interface implementations: ");
+    for(var s in __device_services){
+      //__device_debug("service_load: " + s + ":" +  __device_services[s].name + ": " + interfaceName);
+      var i = __device_get_interface(__device_services[s], interfaceName, version);
+      if (i != null){
+	return i;
+      }
+    }
+  }
+  return null;
+}
+
+// Lazily fill in the descriptor table
+
+function __device_update_descriptor(i){
+  var d = __device_get_descriptor(i);
+  i.name = d.interfaceName;
+  i.version = d.version;  
+}
+// Get an array of interface descriptors for a service
+
+function __device_interface_list(s){
+  var retval = new Array();
+  for(var i in s.interfaces){
+    if(s.interfaces[i].name == null){
+      __device_update_descriptor(s.interfaces[i]);
+    }
+    if(s.interfaces[i].name == undefined){
+      continue;
+    }
+    retval[i] = new Object();
+    retval[i].name = s.interfaces[i].name;
+    retval[i].version = s.interfaces[i].version;
+  }  
+  return retval;
+}
+
+// Get a service description
+
+function __device_service_descriptor(s){
+  this.name = s.name;
+  this.version = s.version;
+  this.interfaces = __device_interface_list(s);
+  this.toString = __device_service_descriptor_to_string;
+}
+
+function __device_service_descriptor_to_string(){
+  var is = "\nInterfaces(s): ";
+
+  for (i in this.interfaces){
+    is += "\n" + this.interfaces[i].name + " " + this.interfaces[0].version;
+  }
+  return ("Service: " + this.name + is);
+}
+
+// Implement the listServices method 
+
+function __device_service_list(serviceName, interfaceName, version){
+  //__device_debug("__device_service_list: " + serviceName + " " + interfaceName);
+  __device_services_init();
+  var retval = new Array();
+  var n = 0;
+  
+  //Treat empty service and interface names as wildcards
+  if ((serviceName == null)|| (serviceName == '')/* || (serviceName == undefined)*/){
+    serviceName = ".*"; 
+  }
+  if ((interfaceName == null) || (interfaceName == '') /*|| (serviceName == undefined)*/){
+    interfaceName = ".*";
+  }
+ 
+  if ((typeof serviceName != "string") || (typeof interfaceName != "string")) {
+  	return retval;
+  }
+  
+  // This method does regular expression matching of service and interface
+
+  var sregx = new RegExp(serviceName);
+  var iregx = new RegExp(interfaceName);
+ 
+  for(var s in __device_services){
+   //__device_debug (serviceName + "==" + __device_services[s].name + "?:" + sregx.test(__device_services[s].name));
+    if (sregx.test(__device_services[s].name)){
+      // Find the first matching interface 
+        
+      for(var i in __device_services[s].interfaces){
+        if(__device_services[s].interfaces[i].name == null){
+          __device_update_descriptor(__device_services[s].interfaces[i]);
+	}
+        if(__device_services[s].interfaces[i].name == undefined){
+	  continue;
+	}
+	//__device_debug (interfaceName + "==" + __device_services[s].interfaces[i].name + "?:" + iregx.test(__device_services[s].interfaces[i].name));
+	if (iregx.test(__device_services[s].interfaces[i].name)){
+	  if ((version == null) || (version == '') || (__device_services[s].interfaces[i].version >= version)){
+            // An interface matched, we're done.
+            retval[n] = new __device_service_descriptor(__device_services[s]);
+            break; 
+	  }
+	}
+      }
+    }
+    ++n;
+  }
+  return retval;
+}
+
+// Implement the listInterfaces method
+    
+function __device_service_interfaces(serviceName){
+  __device_services_init();
+  if(serviceName==null||serviceName==undefined||serviceName==''){
+  	throw new DeviceError("Framework: listInterfaces: serviceName is missing", err_missing_argument);
+  }
+  for (var s in __device_services){
+    if(__device_services[s].name == serviceName){
+      return __device_interface_list(__device_services[s]);
+    }
+  }
+  return null;
+}
+
+function modifyObjectBaseProp(obj){
+  for (pro in obj) {
+    if(typeof obj[pro] == "function" )
+      obj[pro] = 0;
+    }
+};
+/*
+Copyright © 2009 Nokia. All rights reserved.
+Code licensed under the BSD License:
+Software License Agreement (BSD License) Copyright © 2009 Nokia.
+All rights reserved.
+Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+Neither the name of Nokia Corporation. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Nokia Corporation. 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+version: 1.0
+*/
+
+
+// S60 sp-based camera provider
+
+function __sp_camera_descriptor(){
+  //__device_debug("sp_camera_descriptor");
+  //Read-only properties
+  this.interfaceName = "com.nokia.device.camera";
+  this.version = "0.1";
+  //Class-static properties 
+}
+
+// TBD make local to closure funcs
+var __sp_camera_start_date;
+
+function __sp_camera_instance(){
+  //__device_debug("sp_camera_instance");
+  //Descriptor
+  this.descriptor = new __sp_camera_descriptor();
+  //Core methods
+  this.startCamera = __sp_startCamera;
+  this.stopViewfinder = __s60_api_not_supported;
+  //Extended methods
+  this.takePicture = __s60_api_not_supported;
+  //Private data
+}
+
+var CAMERA_APP_ID = 0x101f857a;
+
+//Apps should take care that this is not reinvoked
+//while the viewfinder is running. 
+
+function __sp_startCamera(camera_cb){
+
+	//If callback is null , then return missing argument error
+    if( camera_cb == null )
+        throw new DeviceError("Camera:startCamera:callback is missing", err_missing_argument);
+        
+	//If the callback is not a function, then return bad type error
+	if( typeof(camera_cb) != "function" )
+	    throw new DeviceError("Camera:startCamera:callback is a non-function", err_bad_argument);
+
+  var finished = function (){
+    var invoker = function (arg1, arg2, arg3){
+      //__device_debug("invoker with: " + camera_cb);
+      var it = arg3.ReturnValue;
+      var item;
+      var items = new Array();
+      while (( item = it.getNext()) != undefined){
+          var d = new Date(Date.parse(item.FileDate));
+          //__device_debug(item.FileName + " " + d );
+          // Items returned in reverse date order, so stop iterating before
+          // reaching initial date. (Should be able to do this more efficiently
+          // with sp filter, but that doesn't seem to work right now.)
+          if (d > __sp_camera_start_date) {
+              var pathname = item.FileNameAndPath.replace(/\\/g, "/");
+              var fileScheme = "file:///";
+              //Non-patched builds don't allow file scheme TBD: change this for patched builds
+              items.unshift(fileScheme + pathname);
+          }
+      }
+      var dummyTransID = 0;
+      var dummyStatusCode = 0;
+      camera_cb(dummyTransID, dummyStatusCode, items);
+    };
+
+    
+    //When camera returns, get the image(s) created
+    try {
+      var mso = device.getServiceObject("Service.MediaManagement", "IDataSource");
+    }
+    catch(e) {
+      __device_handle_exception (e, "media service not available : " + e);
+    }
+    
+    var criteria = new Object();
+	modifyObjectBaseProp(criteria);
+    criteria.Type = 'FileInfo';
+    criteria.Filter = new Object();
+	modifyObjectBaseProp(criteria.Filter);
+    criteria.Filter.FileType = 'Image';
+    //criteria.Filter.Key = 'FileDate';
+    //criteria.Filter.StartRange = null;
+    //criteria.Filter.EndRange = null;
+    criteria.Sort = new Object();
+	modifyObjectBaseProp(criteria.Sort);
+    criteria.Sort.Key = 'FileDate';
+    criteria.Sort.Order = 'Descending';
+    
+    try {
+      var rval = mso.IDataSource.GetList(criteria, invoker);
+    }
+    catch (e) {
+      __device_handle_exception (e, "media service GetList failed: " + e);
+    }
+  };
+
+  __sp_camera_start_date = new Date();
+  __s60_start_and_wait(CAMERA_APP_ID, "", finished);
+  var dummyTid = 0;
+  return dummyTid;
+}
+
+
+/*
+Copyright © 2009 Nokia. All rights reserved.
+Code licensed under the BSD License:
+Software License Agreement (BSD License) Copyright © 2009 Nokia.
+All rights reserved.
+Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+Neither the name of Nokia Corporation. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Nokia Corporation. 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+version: 1.0
+*/
+
+
+// Camera service interface
+
+var __device_camera_service_entry =  {"name": null, 
+					 "version": null,
+					 "proto": __device_camera,
+					 "descriptor": __device_camera_descriptor,
+					 "providers": [{"descriptor": __sp_camera_descriptor, "instance": __sp_camera_instance}]
+					};
+
+function __device_camera_descriptor(provider){
+  this.interfaceName = provider.interfaceName;
+  this.version = provider.version;
+}
+
+
+// Private camera  prototype: called from service factory
+function __device_camera(provider){
+  //Private properties
+  this.provider = provider;
+  //Read-only properties
+  this.interfaceName = provider.descriptor.interfaceName;
+  this.version = provider.descriptor.version;
+ // this.supportedMediaTypes = provider.supportedMediaTypes;
+ // this.supportedSizes = provider.supportedSizes;
+  //Core methods
+  this.startCamera = __device_camera_startCamera;
+  this.stopViewfinder = __device_camera_stopViewfinder;
+  //Extended methods
+  this.takePicture = __device_camera_takePicture;
+}
+
+
+//Why bother to define these methods? Because the camera
+//object defines the contract for providers!
+
+function __device_camera_startCamera(camera_cb){
+  return this.provider.startCamera(camera_cb);
+}
+
+function __device_camera_stopViewfinder(){
+  this.provider.stopViewfinder();
+}
+
+function __device_camera_takePicture(format){
+  this.provider.takePicture(format);
+}
+/**
+ * This class provides access to the device contacts.
+ * @constructor
+ */
+
+function Contacts() {
+	
+}
+
+function Contact() {
+	this.id = null;
+	this.name = { 
+		formatted: "",
+		givenName: "",
+		familyName: ""
+	};
+    this.phones = [];
+    this.emails = [];
+}
+
+Contact.prototype.displayName = function()
+{
+    // TODO: can be tuned according to prefs
+	return this.givenName + " " + this.familyName;
+};
+
+/*
+ * @param {ContactsFilter} filter Object with filter properties. filter.name only for now.
+ * @param {function} successCallback Callback function on success
+ * @param {function} errorCallback Callback function on failure
+ * @param {object} options Object with properties .page and .limit for paging
+ */
+
+Contacts.prototype.find = function(filter, successCallback, errorCallback, options) {
+	try {
+		
+		this.contactsService = device.getServiceObject("Service.Contact", "IDataSource");
+		this.options = options;
+		
+		var criteria = new Object();
+		criteria.Type = "Contact";
+		if (filter && filter.name)
+			criteria.Filter = { SearchVal: filter.name };
+		
+		if (typeof(successCallback) != 'function') 
+			successCallback = function(){};
+		if (typeof(errorCallback) != 'function') 
+			errorCallback = function(){};
+		if (typeof options == 'object'){
+			if (isNaN(this.options.limit))
+				this.options.limit = 200;
+			if (isNaN(this.options.page))
+				this.options.page = 1;
+		}
+		
+		//need a closure here to bind this method to this instance of the Contacts object
+		this.global_success = successCallback;
+		var obj = this;
+		
+		//WRT: result.ReturnValue is an iterator of contacts
+		this.contactsService.IDataSource.GetList(criteria, function(transId, eventCode, result){
+			obj.success_callback(result.ReturnValue);
+		});
+	} 
+	catch (ex) {
+		errorCallback(ex);
+	}
+};
+
+Contacts.prototype.success_callback = function(contacts_iterator) {
+	var gapContacts = new Array();
+	contacts_iterator.reset();
+    var contact;
+	var i = 0;
+	var end = this.options.page * this.options.limit;
+	var start = end - this.options.limit;
+	while ((contact = contacts_iterator.getNext()) != undefined && i < end) {
+		try {
+			if (i >= start) {
+				var gapContact = new Contact();
+				gapContact.name.givenName = Contacts.GetValue(contact, "FirstName");
+				gapContact.name.familyName = Contacts.GetValue(contact, "LastName");
+				gapContact.name.formatted = gapContact.firstName + " " + gapContact.lastName;
+				gapContact.emails = Contacts.getEmailsList(contact);
+				gapContact.phones = Contacts.getPhonesList(contact);
+				gapContact.address = Contacts.getAddress(contact);
+				gapContact.id = Contacts.GetValue(contact, "id");
+				gapContacts.push(gapContact);
+			}
+			i++;
+		} catch (e) {
+			alert("ContactsError (" + e.name + ": " + e.message + ")");
+		}
+	}
+	this.contacts = gapContacts;
+	this.global_success(gapContacts);
+};
+
+Contacts.getEmailsList = function(contact) {
+	var emails = new Array();
+	try {
+			emails[0] = { type:"General", address: Contacts.GetValue(contact, "EmailGen") };
+			emails[1] = { type:"Work", address: Contacts.GetValue(contact, "EmailWork") };		
+			emails[2] = { type:"Home", address: Contacts.GetValue(contact, "EmailHome") };
+	} catch (e) {
+		emails = [];
+	}
+	return emails;
+};
+
+Contacts.getPhonesList = function(contact) {
+	var phones = new Array();
+	try {
+			phones[0] = { type:"Mobile", number: Contacts.GetValue(contact, "MobilePhoneGen") };
+			phones[1] = { type:"Home", number: Contacts.GetValue(contact, "LandPhoneGen") };
+			phones[2] = { type:"Fax", number: Contacts.GetValue(contact, "FaxNumberGen") };
+			phones[3] = { type:"Work", number: Contacts.GetValue(contact, "LandPhoneWork") };
+			phones[4] = { type:"WorkMobile", number: Contacts.GetValue(contact, "MobilePhoneWork") };
+	} catch (e) {
+		phones = [];
+	}
+	return phones;
+};
+
+Contacts.getAddress = function(contact) {
+	var address = "";
+	try {
+		address = Contacts.GetValue(contact, "AddrLabelHome") + ", " + Contacts.GetValue(contact, "AddrStreetHome") + ", " +
+				Contacts.GetValue(contact, "AddrLocalHome") + ", " + Contacts.GetValue(contact, "AddrRegionHome") + ", " + 
+				Contacts.GetValue(contact, "AddrPostCodeHome") + ", " + Contacts.GetValue(contact, "AddrCountryHome");
+	} catch (e) {
+		address = "";
+	}
+	return address;
+};
+
+Contacts.GetValue = function(contactObj, key) {
+	try {
+		return contactObj[key]["Value"];
+	} catch (e) {
+		return "";
+	}
+};
+
+if (typeof navigator.contacts == "undefined") navigator.contacts = new Contacts();
+PhoneGap.ExtendWrtDeviceObj = function(){
+	
+	if (!window.device)
+		window.device = {};
+	navigator.device = window.device;
+
+	try {
+	
+		if (window.menu)
+	    	window.menu.hideSoftkeys();
+		
+		device.available = PhoneGap.available;
+		device.platform = null;
+		device.version = null;
+		device.name = null;
+		device.uuid = null;
+		
+		var so = device.getServiceObject("Service.SysInfo", "ISysInfo");
+		var pf = PhoneGap.GetWrtPlatformVersion(so);
+		device.platform = pf.platform;
+		device.version = pf.version;
+		device.uuid = PhoneGap.GetWrtDeviceProperty(so, "IMEI");
+		device.name = PhoneGap.GetWrtDeviceProperty(so, "PhoneModel");
+	} 
+	catch (e) {
+		device.available = false;
+	}
+};
+
+PhoneGap.GetWrtDeviceProperty = function(serviceObj, key) {
+	var criteria = { "Entity": "Device", "Key": key };
+	var result = serviceObj.ISysInfo.GetInfo(criteria);
+	if (result.ErrorCode == 0) {
+		return result.ReturnValue.StringData;
+	}
+	else {
+		return null;
+	}
+};
+
+PhoneGap.GetWrtPlatformVersion = function(serviceObj) {
+	var criteria = { "Entity": "Device", "Key": "PlatformVersion" };
+	var result = serviceObj.ISysInfo.GetInfo(criteria);
+	if (result.ErrorCode == 0) {
+		var version = {};
+		version.platform = result.ReturnValue.MajorVersion;
+		version.version = result.ReturnValue.MinorVersion;
+		return version;
+	}
+	else {
+		return null;
+	}
+};
+
+PhoneGap.ExtendWrtDeviceObj();/**
+ * This class provides access to device GPS data.
+ * @constructor
+ */
+function Geolocation() {
+    /**
+     * The last known GPS position.
+     */
+    this.lastPosition = null;
+    this.lastError = null;
+    this.callbacks = {
+        onLocationChanged: [],
+        onError:           []
+    };
+};
+
+/**
+ * Asynchronously aquires the current position.
+ * @param {Function} successCallback The function to call when the position
+ * data is available
+ * @param {Function} errorCallback The function to call when there is an error 
+ * getting the position data.
+ * @param {PositionOptions} options The options for getting the position data
+ * such as timeout.
+ */
+Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallback, options) {
+    var referenceTime = 0;
+    if (this.lastPosition)
+        referenceTime = this.lastPosition.timestamp;
+    else
+        this.start(options);
+
+    var timeout = 20000;
+    var interval = 500;
+    if (typeof(options) == 'object' && options.interval)
+        interval = options.interval;
+
+    if (typeof(successCallback) != 'function')
+        successCallback = function() {};
+    if (typeof(errorCallback) != 'function')
+        errorCallback = function() {};
+
+    var dis = this;
+    var delay = 0;
+    var timer = setInterval(function() {
+        delay += interval;
+		//if we have a new position, call success and cancel the timer
+        if (dis.lastPosition && dis.lastPosition.timestamp > referenceTime) {
+            successCallback(dis.lastPosition);
+            clearInterval(timer);
+        } else if (delay >= timeout) { //else if timeout has occured then call error and cancel the timer
+            errorCallback();
+            clearInterval(timer);
+        }
+		//else the interval gets called again
+    }, interval);
+};
+
+/**
+ * Asynchronously aquires the position repeatedly at a given interval.
+ * @param {Function} successCallback The function to call each time the position
+ * data is available
+ * @param {Function} errorCallback The function to call when there is an error 
+ * getting the position data.
+ * @param {PositionOptions} options The options for getting the position data
+ * such as timeout and the frequency of the watch.
+ */
+Geolocation.prototype.watchPosition = function(successCallback, errorCallback, options) {
+	// Invoke the appropriate callback with a new Position object every time the implementation 
+	// determines that the position of the hosting device has changed. 
+	this.getCurrentPosition(successCallback, errorCallback, options);
+	var frequency = 10000;
+        if (typeof options == 'object' && options.frequency)
+            frequency = options.frequency;
+	var that = this;
+	return setInterval(function() {
+		that.getCurrentPosition(successCallback, errorCallback, options);
+	}, frequency);
+};
+
+
+/**
+ * Clears the specified position watch.
+ * @param {String} watchId The ID of the watch returned from #watchPosition.
+ */
+Geolocation.prototype.clearWatch = function(watchId) {
+	clearInterval(watchId);
+};
+
+Geolocation.prototype.start = function(options) {
+	var so = device.getServiceObject("Service.Location", "ILocation");
+	
+	//construct the criteria for our location request
+	var updateOptions = new Object();
+	// Specify that location information need not be guaranteed. This helps in
+	// that the widget doesn't need to wait for that information possibly indefinitely.
+	updateOptions.PartialUpdates = true;
+	
+	//default 15 seconds
+	if (typeof(options) == 'object' && options.timeout) 
+		//options.timeout in in ms, updateOptions.UpdateTimeout in microsecs
+		updateOptions.UpdateTimeOut = options.timeout * 1000;
+
+	//default 1 second
+	if (typeof(options) == 'object' && options.interval) 
+		//options.timeout in in ms, updateOptions.UpdateTimeout in microsecs
+		updateOptions.UpdateInterval = options.interval * 1000;
+	
+	// Initialize the criteria for the GetLocation call
+	var trackCriteria = new Object();
+	// could use "BasicLocationInformation" or "GenericLocationInfo"
+	trackCriteria.LocationInformationClass = "GenericLocationInfo";
+	trackCriteria.Updateoptions = updateOptions;
+	
+	var dis = this;
+	so.ILocation.Trace(trackCriteria, function(transId, eventCode, result) {
+		var retVal = result.ReturnValue;
+
+		if (result.ErrorCode != 0 || isNaN(retVal.Latitude))
+			return;
+		
+		// heading options: retVal.TrueCourse, retVal.MagneticHeading, retVal.Heading, retVal.MagneticCourse
+		// but retVal.Heading was the only field being returned with data on the test device (Nokia 5800)
+		// WRT does not provide accuracy
+		var newCoords = new Coordinates(retVal.Latitude, retVal.Longitude, retVal.Altitude, null, retVal.Heading, retVal.HorizontalSpeed);
+		var positionObj = { coords: newCoords, timestamp: (new Date()).getTime() };
+
+		dis.lastPosition = positionObj;
+	});
+	
+};
+
+
+if (typeof navigator.geolocation == "undefined") navigator.geolocation = new Geolocation();
+
+/**
+ * This class provides access to the device media, interfaces to both sound and video
+ * @constructor
+ */
+function Media(src, successCallback, errorCallback) {
+	this.src = src;
+	this.successCallback = successCallback;
+	this.errorCallback = errorCallback;												
+}
+
+Media.prototype.record = function() {
+};
+
+Media.prototype.play = function(src) {
+
+	if (document.getElementById('gapsound'))
+		document.body.removeChild(document.getElementById('gapsound'));
+	var obj;
+	obj = document.createElement("embed");
+	obj.setAttribute("id", "gapsound");
+	obj.setAttribute("type", "audio/x-mpeg");
+	obj.setAttribute("width", "0");
+	obj.setAttribute("width", "0");
+	obj.setAttribute("hidden", "true");
+	obj.setAttribute("autostart", "true");
+	obj.setAttribute("src", src);
+	document.body.appendChild(obj);
+};
+
+Media.prototype.pause = function() {
+};
+
+Media.prototype.stop = function() {
+};
+
+if (typeof navigator.media == "undefined") navigator.media = new Media();
+/**
+ * This class provides access to notifications on the device.
+ */
+function Notification() {
+	
+}
+
+Notification.prototype.vibrate = function(mills)
+{
+	
+	if (!Notification.getSysinfoObject())
+		Notification.embedSysinfoObject();
+	
+	this.sysinfo = Notification.getSysinfoObject();
+	this.sysinfo.startvibra(mills, 100);
+};
+
+//TODO: this is not beeping
+Notification.prototype.beep = function(count, volume)
+{
+	if (!Notification.getSysinfoObject())
+		Notification.embedSysinfoObject();
+	
+	this.sysinfo = Notification.getSysinfoObject();	
+	this.sysinfo.beep(220,2000);
+};
+
+
+/**
+ * Open a native alert dialog, with a customizable title and button text.
+ * @param {String} message Message to print in the body of the alert
+ * @param {String} [title="Alert"] Title of the alert dialog (default: Alert)
+ * @param {String} [buttonLabel="OK"] Label of the close button (default: OK)
+ */
+Notification.prototype.alert = function(message, title, buttonLabel) {
+    // Default is to use a browser alert; this will use "index.html" as the title though
+    alert(message);
+};
+
+/**
+ * Start spinning the activity indicator on the statusbar
+ */
+Notification.prototype.activityStart = function() {
+};
+
+/**
+ * Stop spinning the activity indicator on the statusbar, if it's currently spinning
+ */
+Notification.prototype.activityStop = function() {
+};
+
+/**
+ * Causes the device to blink a status LED.
+ * @param {Integer} count The number of blinks.
+ * @param {String} colour The colour of the light.
+ */
+Notification.prototype.blink = function(count, colour) {
+	
+};
+
+Notification.embedSysinfoObject = function() {
+	var el = document.createElement("embed");
+	el.setAttribute("type", "application/x-systeminfo-widget");
+	el.setAttribute("hidden", "yes");
+	document.getElementsByTagName("body")[0].appendChild(el);
+	return;
+};
+
+Notification.getSysinfoObject = function() {
+	return document.embeds[0];
+};
+
+if (typeof navigator.notification == "undefined") navigator.notification = new Notification();
+/**
+ * This class provides access to the device orientation.
+ * @constructor
+ */
+function Orientation() {
+	/**
+	 * The current orientation, or null if the orientation hasn't changed yet.
+	 */
+	this.currentOrientation = null;
+}
+
+/**
+ * Set the current orientation of the phone.  This is called from the device automatically.
+ * 
+ * When the orientation is changed, the DOMEvent \c orientationChanged is dispatched against
+ * the document element.  The event has the property \c orientation which can be used to retrieve
+ * the device's current orientation, in addition to the \c Orientation.currentOrientation class property.
+ *
+ * @param {Number} orientation The orientation to be set
+ */
+Orientation.prototype.setOrientation = function(orientation) {
+		if (orientation == this.currentOrientation) 
+			return;
+		var old = this.currentOrientation;
+
+		this.currentOrientation = orientation;
+		var e = document.createEvent('Events');
+		e.initEvent('orientationChanged', 'false', 'false');
+		e.orientation = orientation;
+		e.oldOrientation = old;
+		document.dispatchEvent(e);
+};
+
+/**
+ * Asynchronously aquires the current orientation.
+ * @param {Function} successCallback The function to call when the orientation
+ * is known.
+ * @param {Function} errorCallback The function to call when there is an error 
+ * getting the orientation.
+ */
+Orientation.prototype.getCurrentOrientation = function(successCallback, errorCallback) {
+	// If the orientation is available then call success
+	// If the orientation is not available then call error
+	try {
+		if (!this.serviceObj) 
+			this.serviceObj = this.getServiceObj();
+		
+		if (this.serviceObj == null) 
+			errorCallback({
+				name: "DeviceErr",
+				message: "Could not initialize service object"
+			});
+		
+		//get the sensor channel
+		var SensorParams = {
+			SearchCriterion: "Orientation"
+		};
+		var returnvalue = this.serviceObj.ISensor.FindSensorChannel(SensorParams);
+		
+		var error = returnvalue["ErrorCode"];
+		var errmsg = returnvalue["ErrorMessage"];
+		if (!(error == 0 || error == 1012)) {
+			var ex = {
+				name: "Unable to find Sensor Channel: " + error,
+				message: errmsg
+			};
+			errorCallback(ex);
+		}
+		var channelInfoMap = returnvalue["ReturnValue"][0];
+		var criteria = {
+			ChannelInfoMap: channelInfoMap,
+			ListeningType: "ChannelData"
+		};
+		
+		if (typeof(successCallback) != 'function') 
+			successCallback = function(){
+			};
+		if (typeof(errorCallback) != 'function') 
+			errorCallback = function(){
+			};
+		
+		this.success_callback = successCallback;
+		this.error_callback = errorCallback;
+		
+		//create a closure to persist this instance of orientation object into the RegisterForNofication callback
+		var obj = this;
+		
+		this.serviceObj.ISensor.RegisterForNotification(criteria, function(transId, eventCode, result){
+			alert(1);
+			var criteria = {
+				TransactionID: transId
+			};
+			try {
+				var orientation = result.ReturnValue.DeviceOrientation;
+				obj.serviceObj.ISensor.Cancel(criteria);
+				
+				obj.setOrientation(orientation);
+				
+				obj.success_callback(orientation);
+				
+			} 
+			catch (ex) {
+				obj.serviceObj.ISensor.Cancel(criteria);
+				obj.error_callback(ex);
+			}
+			
+		});
+	} catch (ex) {
+		errorCallback({ name: "OrientationError", message: ex.name + ": " + ex.message });
+	}
+};
+
+/**
+ * Asynchronously aquires the orientation repeatedly at a given interval.
+ * @param {Function} successCallback The function to call each time the orientation
+ * data is available.
+ * @param {Function} errorCallback The function to call when there is an error 
+ * getting the orientation data.
+ */
+Orientation.prototype.watchOrientation = function(successCallback, errorCallback, options) {
+	// Invoke the appropriate callback with a new Position object every time the implementation 
+	// determines that the position of the hosting device has changed. 
+	this.getCurrentOrientation(successCallback, errorCallback);
+	var frequency = (options != undefined)? options.frequency : 1000;
+	return setInterval(function() {
+		navigator.orientation.getCurrentOrientation(successCallback, errorCallback);
+	}, frequency);
+};
+
+/**
+ * Clears the specified orientation watch.
+ * @param {String} watchId The ID of the watch returned from #watchOrientation.
+ */
+Orientation.prototype.clearWatch = function(watchId) {
+	clearInterval(watchId);
+};
+
+//gets the Acceleration Service Object from WRT
+Orientation.prototype.getServiceObj = function() {
+	var so;
+	
+    try {
+        so = device.getServiceObject("Service.Sensor", "ISensor");
+    } catch (ex) {
+		throw {
+			name: "DeviceError",
+			message: ex.name + ": " + ex.message
+		};
+    }		
+	return so;
+};
+
+if (typeof navigator.orientation == "undefined") navigator.orientation = new Orientation();
+/**
+ * This class contains position information.
+ * @param {Object} lat
+ * @param {Object} lng
+ * @param {Object} acc
+ * @param {Object} alt
+ * @param {Object} altacc
+ * @param {Object} head
+ * @param {Object} vel
+ * @constructor
+ */
+function Position(coords, timestamp) {
+	this.coords = coords;
+        this.timestamp = new Date().getTime();
+}
+
+function Coordinates(lat, lng, alt, acc, head, vel) {
+	/**
+	 * The latitude of the position.
+	 */
+	this.latitude = lat;
+	/**
+	 * The longitude of the position,
+	 */
+	this.longitude = lng;
+	/**
+	 * The accuracy of the position.
+	 */
+	this.accuracy = acc;
+	/**
+	 * The altitude of the position.
+	 */
+	this.altitude = alt;
+	/**
+	 * The direction the device is moving at the position.
+	 */
+	this.heading = head;
+	/**
+	 * The velocity with which the device is moving at the position.
+	 */
+	this.speed = vel;
+}
+
+/**
+ * This class specifies the options for requesting position data.
+ * @constructor
+ */
+function PositionOptions() {
+	/**
+	 * Specifies the desired position accuracy.
+	 */
+	this.enableHighAccuracy = true;
+	/**
+	 * The timeout after which if position data cannot be obtained the errorCallback
+	 * is called.
+	 */
+	this.timeout = 10000;
+}
+
+/**
+ * This class contains information about any GSP errors.
+ * @constructor
+ */
+function PositionError() {
+	this.code = null;
+	this.message = "";
+}
+
+PositionError.UNKNOWN_ERROR = 0;
+PositionError.PERMISSION_DENIED = 1;
+PositionError.POSITION_UNAVAILABLE = 2;
+PositionError.TIMEOUT = 3;
+/**
+ * This class provides access to the device SMS functionality.
+ * @constructor
+ */
+function Sms() {
+
+}
+
+/**
+ * Sends an SMS message.
+ * @param {Integer} number The phone number to send the message to.
+ * @param {String} message The contents of the SMS message to send.
+ * @param {Function} successCallback The function to call when the SMS message is sent.
+ * @param {Function} errorCallback The function to call when there is an error sending the SMS message.
+ * @param {PositionOptions} options The options for accessing the GPS location such as timeout and accuracy.
+ */
+Sms.prototype.send = function(number, message, successCallback, errorCallback, options) {
+    try {
+		if (!this.serviceObj)
+			this.serviceObj = this.getServiceObj();
+			
+	    // Setup input params using dot syntax
+	    var criteria = new Object();
+	    criteria.MessageType = 'SMS';
+	    criteria.To = number;
+	    criteria.BodyText = message;
+
+      	var result = this.serviceObj.IMessaging.Send(criteria);
+    	if (result.ErrorCode != 0 && result.ErrorCode != "0")
+		{
+			var exception = { name: "SMSError", message: result.ErrorMessage };
+			throw exception;
+		} else {
+			successCallback.call();
+		}
+    }
+  	catch(ex)
+  	{
+		errorCallback.call({ name: "SmsError", message: ex.name + ": " + ex.message });
+  	}
+
+};
+
+
+//gets the Sms Service Object from WRT
+Sms.prototype.getServiceObj = function() {
+	var so;
+	
+    try {
+        so = device.getServiceObject("Service.Messaging", "IMessaging");
+    } catch (ex) {
+		throw {
+			name: "SmsError",
+			message: "Failed to load sms service (" + ex.name + ": " + ex.message + ")"
+		};
+    }		
+	return so;
+};
+
+if (typeof navigator.sms == "undefined") navigator.sms = new Sms();/**
+ * @author ryan
+ */
+
+function Storage() {
+	this.length = null;
+	this.available = true;
+	this.serialized = null;
+	this.items = null;
+	
+	if (!window.widget) {
+		this.available = false;
+		return;
+	}
+	var pref = window.widget.preferenceForKey(Storage.PREFERENCE_KEY);
+	
+	//storage not yet created
+	if (pref == "undefined" || pref == undefined) {
+		this.length = 0;
+		this.serialized = "({})";
+		this.items = {};
+		window.widget.setPreferenceForKey(this.serialized, Storage.PREFERENCE_KEY);
+	} else {
+		this.serialized = pref;'({"store_test": { "key": "store_test", "data": "asdfasdfs" },})';
+
+		this.items = eval(this.serialized);
+
+	}
+}
+
+Storage.PREFERENCE_KEY = "phonegap_storage_pref_key";
+
+Storage.prototype.index = function (key) {
+	
+};
+
+Storage.prototype.getItem = function (key) {
+	try {
+		return this.items[key].data;
+	} catch (ex) {
+		return null;
+	}
+};
+
+Storage.prototype.setItem = function (key, data) {
+	
+	if (!this.items[key])
+		this.length++;
+	this.items[key] = {
+		"key": key,
+		"data": data
+	};
+	
+	this.serialize();
+};
+
+Storage.prototype.removeItem = function (key) {
+	if (this.items[key]) {
+		this.items[key] = undefined;
+		this.length--;
+	}
+	this.serialize();
+};
+
+Storage.prototype.clear = function () {
+	this.length = 0;
+	this.serialized = "({})";
+	this.items = {};
+};
+
+Storage.prototype.serialize = function() {
+	var json = "";
+	
+	for (key in this.items) {
+		var item = this.items[key];
+		json += "\"" + item.key + "\": { \"key\": \"" + item.key + "\", \"data\": \"" + item.data + "\" }, ";
+	}
+
+	window.widget.setPreferenceForKey( "({" + json + "})", Storage.PREFERENCE_KEY);
+};
+
+if (typeof navigator.storage == "undefined" ) navigator.storage = new Storage();
--- a/org.symbian.tools.wrttools/plugin.xml	Mon Apr 19 10:37:57 2010 -0700
+++ b/org.symbian.tools.wrttools/plugin.xml	Mon Apr 19 18:03:51 2010 -0700
@@ -2,6 +2,7 @@
 <?eclipse version="3.4"?>
 <plugin>
    <extension-point id="projectTemplates" name="WRT Project Templates" schema="schema/projectTemplates.exsd"/>
+   <extension-point id="jsLibraries" name="JavaScript Libraries" schema="schema/jsLibraries.exsd"/>
 
 <!-- Generic Project Builder and Project Natures  -->		
  	
@@ -643,6 +644,13 @@
           id="org.symbian.tools.wrttools.commands.package"
           name="Package Application">
     </command>
+    <command
+          categoryId="org.symbian.tools.wrttools.commands.maincategory"
+          defaultHandler="org.symbian.tools.wrttools.handlers.AddJSLibrary"
+          description="Adds JavaScript libraries to WRT projects"
+          id="org.symbian.tools.wrttools.addlibrary"
+          name="Add JavaScript Libraries...">
+    </command>
  </extension>
  <extension
        point="org.eclipse.ui.menus">
@@ -669,6 +677,15 @@
              visible="true">
        </separator>
        <command
+             commandId="org.symbian.tools.wrttools.addlibrary"
+             id="org.symbian.tools.wrttools.toolbars.addlibrary"
+             style="push">
+       </command>
+       <separator
+             name="org.symbian.tools.wrttools.deploypackage"
+             visible="true">
+       </separator>
+       <command
              commandId="org.symbian.tools.wrttools.commands.deploy"
              icon="icons/deploy_widget.gif"
              id="org.symbian.tools.wrttools.toolbars.deploy">
@@ -732,6 +749,35 @@
              visible="true">
        </separator>
     </menuContribution>
+    <menuContribution
+          locationURI="popup:org.eclipse.ui.popup.any?after=group.build">
+       <dynamic
+             class="org.symbian.tools.wrttools.core.libraries.AddLibraryPopupMenu$LibrariesPopupMenu"
+             id="org.symbian.tools.wrttools.addlibrary">
+          <visibleWhen>
+             <and>
+                <iterate
+                      ifEmpty="false"
+                      operator="and">
+                   <adapt
+                         type="org.eclipse.core.resources.IProject">
+                      <test
+                            property="org.eclipse.core.resources.projectNature"
+                            value="org.symbian.tools.wrttools.WidgetProjectNature">
+                      </test>
+                   </adapt>
+                </iterate>
+                <count
+                      value="1">
+                </count>
+             </and>
+          </visibleWhen>
+       </dynamic>
+       <separator
+             name="org.symbian.tools.wrttools.libs"
+             visible="true">
+       </separator>
+    </menuContribution>
  </extension>
  <extension
        point="org.eclipse.ui.handlers">
@@ -814,5 +860,20 @@
     <persistent
           value="true">
     </persistent>
+ </extension>
+ <extension
+       point="org.symbian.tools.wrttools.jsLibraries">
+    <library
+          icon="icons/main16_prev.gif"
+          id="org.symbian.wrtkit"
+          installer="org.symbian.tools.wrttools.core.libraries.WRTKitInstaller"
+          name="WRTKit">
+    </library>
+    <library
+          icon="icons/phonegap.png"
+          id="phonegap"
+          installer="org.symbian.tools.wrttools.core.libraries.PhoneGapInstaller"
+          name="PhoneGap">
+    </library>
  </extension>
 </plugin>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.symbian.tools.wrttools/schema/jsLibraries.exsd	Mon Apr 19 18:03:51 2010 -0700
@@ -0,0 +1,129 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.symbian.tools.wrttools" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+      <appinfo>
+         <meta.schema plugin="org.symbian.tools.wrttools" id="jsLibraries" name="JavaScript Libraries"/>
+      </appinfo>
+      <documentation>
+         [Enter description of this extension point.]
+      </documentation>
+   </annotation>
+
+   <element name="extension">
+      <annotation>
+         <appinfo>
+            <meta.element />
+         </appinfo>
+      </annotation>
+      <complexType>
+         <sequence minOccurs="1" maxOccurs="unbounded">
+            <element ref="library"/>
+         </sequence>
+         <attribute name="point" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="id" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="name" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appinfo>
+                  <meta.attribute translatable="true"/>
+               </appinfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="library">
+      <complexType>
+         <attribute name="id" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="name" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appinfo>
+                  <meta.attribute translatable="true"/>
+               </appinfo>
+            </annotation>
+         </attribute>
+         <attribute name="icon" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appinfo>
+                  <meta.attribute kind="resource"/>
+               </appinfo>
+            </annotation>
+         </attribute>
+         <attribute name="installer" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appinfo>
+                  <meta.attribute kind="java" basedOn=":org.symbian.tools.wrttools.core.libraries.IJSLibraryInstaller"/>
+               </appinfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="since"/>
+      </appinfo>
+      <documentation>
+         [Enter the first release in which this extension point appears.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="examples"/>
+      </appinfo>
+      <documentation>
+         [Enter extension point usage example here.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="apiinfo"/>
+      </appinfo>
+      <documentation>
+         [Enter API information here.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="implementation"/>
+      </appinfo>
+      <documentation>
+         [Enter information about supplied implementation of this extension point.]
+      </documentation>
+   </annotation>
+
+
+</schema>
--- a/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/Activator.java	Mon Apr 19 10:37:57 2010 -0700
+++ b/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/Activator.java	Mon Apr 19 18:03:51 2010 -0700
@@ -19,7 +19,12 @@
 package org.symbian.tools.wrttools;
 
 import java.io.PrintStream;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.TreeSet;
 
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jface.resource.ImageRegistry;
@@ -29,6 +34,7 @@
 import org.symbian.tools.wrttools.core.libraries.JSLibrary;
 import org.symbian.tools.wrttools.core.libraries.LibraryManager;
 import org.symbian.tools.wrttools.sdt.utils.Logging;
+import org.symbian.tools.wrttools.util.ProjectUtils;
 
 import com.intel.bluetooth.BlueCoveImpl;
 
@@ -122,4 +128,27 @@
         return getDefault().manager.getLibraries();
     }
 
+    public static IProject[] getWrtProjects() {
+        IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+        Collection<IProject> prjs = new TreeSet<IProject>(new Comparator<IProject>() {
+            public int compare(IProject o1, IProject o2) {
+                if (o1 == o2) {
+                    return 0;
+                } else if (o1 == null) {
+                    return -1;
+                } else if (o2 == null) {
+                    return 1;
+                } else {
+                    return o1.getName().compareTo(o2.getName());
+                }
+            }
+        });
+        for (IProject project : projects) {
+            if (ProjectUtils.hasWrtNature(project)) {
+                prjs.add(project);
+            }
+        }
+        return prjs.toArray(new IProject[prjs.size()]);
+    }
+
 }
--- a/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/WRTImages.java	Mon Apr 19 10:37:57 2010 -0700
+++ b/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/WRTImages.java	Mon Apr 19 18:03:51 2010 -0700
@@ -77,5 +77,4 @@
     public static Image getWrtKitIcon() {
         return Activator.getDefault().getImageRegistry().get(IMAGE_WRTKIT);
     }
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/AddLibraryPopupMenu.java	Mon Apr 19 18:03:51 2010 -0700
@@ -0,0 +1,150 @@
+/**
+ * Copyright (c) 2010 Symbian Foundation and/or its subsidiary(-ies).
+ * All rights reserved.
+ * This component and the accompanying materials are made available
+ * under the terms of the License "Eclipse Public License v1.0"
+ * which accompanies this distribution, and is available
+ * at the URL "http://www.eclipse.org/legal/epl-v10.html".
+ *
+ * Initial Contributors:
+ * Symbian Foundation - initial contribution.
+ * Contributors:
+ * Description:
+ * Overview:
+ * Details:
+ * Platforms/Drives/Compatibility:
+ * Assumptions/Requirement/Pre-requisites:
+ * Failures and causes:
+ */
+package org.symbian.tools.wrttools.core.libraries;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.ActionContributionItem;
+import org.eclipse.jface.action.IMenuCreator;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MenuEvent;
+import org.eclipse.swt.events.MenuListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.wst.jsdt.core.IJavaScriptProject;
+import org.eclipse.wst.jsdt.core.JavaScriptCore;
+import org.symbian.tools.wrttools.Activator;
+
+public class AddLibraryPopupMenu extends Action implements IMenuCreator, MenuListener {
+    public static class LibrariesPopupMenu extends ActionContributionItem {
+        public LibrariesPopupMenu() {
+            super(new AddLibraryPopupMenu());
+        }
+
+        @Override
+        public void fill(Menu parent, int index) {
+            IProject project = getSelectedProject();
+            getAction().setEnabled(project != null && hasLibrariesToInstall(project));
+            super.fill(parent, index);
+        }
+    }
+
+    private Menu menu;
+
+    public AddLibraryPopupMenu() {
+        super("Add JavaScript Library", AS_DROP_DOWN_MENU);
+        setMenuCreator(this);
+    }
+
+    protected static boolean hasLibrariesToInstall(IProject project) {
+        JSLibrary[] libraries = Activator.getJSLibraries();
+        for (JSLibrary library : libraries) {
+            if (!library.isInstalled(project)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void dispose() {
+        // TODO Auto-generated method stub
+
+    }
+
+    public Menu getMenu(Control parent) {
+        return null;
+    }
+
+    public Menu getMenu(Menu parent) {
+        if (menu != null) {
+            menu.dispose();
+        }
+        menu = new Menu(parent);
+        menu.addMenuListener(this);
+        return menu;
+    }
+
+    public void menuHidden(MenuEvent e) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void menuShown(MenuEvent e) {
+        IProject p = getSelectedProject();
+        if (p != null) {
+            addActions(p);
+        }
+    }
+
+    protected static IProject getSelectedProject() {
+        IProject p = null;
+        ISelection selection = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService()
+                .getSelection();
+        if (!selection.isEmpty() && (selection instanceof IStructuredSelection)
+                && ((IStructuredSelection) selection).size() == 1) {
+            Object el = ((IStructuredSelection) selection).getFirstElement();
+            if (el instanceof IAdaptable) {
+                p = (IProject) ((IAdaptable) el).getAdapter(IProject.class);
+            }
+        }
+        return p;
+    }
+
+    private void addActions(IProject p) {
+        IJavaScriptProject jsProject = JavaScriptCore.create(p);
+        JSLibrary[] jsLibraries = Activator.getJSLibraries();
+        for (JSLibrary jsLibrary : jsLibraries) {
+            if (!jsLibrary.isInstalled(jsProject.getProject())) {
+                createMenuItem(menu, jsLibrary, jsProject);
+            }
+        }
+    }
+
+    private void createMenuItem(Menu menu2, final JSLibrary jsLibrary, final IJavaScriptProject jsProject) {
+        MenuItem item = new MenuItem(menu2, SWT.PUSH);
+        item.setText(jsLibrary.getName());
+        item.setImage(jsLibrary.getImage());
+        item.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent e) {
+                Map<String, String> empty = Collections.emptyMap();
+                try {
+                    jsLibrary.install(jsProject.getProject(), empty, new NullProgressMonitor());
+                } catch (CoreException e1) {
+                    Activator.log(e1);
+                } catch (IOException e1) {
+                    Activator.log(e1);
+                }
+            }
+        });
+    }
+}
--- a/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/IJSLibraryInstaller.java	Mon Apr 19 10:37:57 2010 -0700
+++ b/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/IJSLibraryInstaller.java	Mon Apr 19 18:03:51 2010 -0700
@@ -28,4 +28,5 @@
 public interface IJSLibraryInstaller {
     void install(IProject project, Map<String, String> parameters, IProgressMonitor monitor) throws CoreException,
             IOException;
+    boolean isInstalled(IProject project);
 }
--- a/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/JSLibrary.java	Mon Apr 19 10:37:57 2010 -0700
+++ b/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/JSLibrary.java	Mon Apr 19 18:03:51 2010 -0700
@@ -23,20 +23,35 @@
 
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.swt.graphics.Image;
+import org.symbian.tools.wrttools.Activator;
+import org.symbian.tools.wrttools.core.WRTImages;
 
 public class JSLibrary {
+    public class NullInstaller implements IJSLibraryInstaller {
+        public void install(IProject project, Map<String, String> parameters, IProgressMonitor monitor)
+                throws CoreException, IOException {
+            // Do nothing
+        }
+
+        public boolean isInstalled(IProject project) {
+            return false;
+        }
+    }
+
     private final String id;
-    private final Image image;
+    private Image image;
     private final String name;
-    private final IJSLibraryInstaller installer;
+    private IJSLibraryInstaller installer;
+    private final IConfigurationElement element;
 
-    public JSLibrary(String id, String name, Image image, IJSLibraryInstaller installer) {
+    public JSLibrary(String id, String name, IConfigurationElement element) {
         this.id = id;
         this.name = name;
-        this.image = image;
-        this.installer = installer;
+        this.element = element;
     }
 
     public String getId() {
@@ -44,6 +59,19 @@
     }
 
     public Image getImage() {
+        if (image == null) {
+            String icon = element.getAttribute("icon");
+            if (icon != null) {
+                ImageDescriptor descriptor = Activator.imageDescriptorFromPlugin(element.getContributor().getName(),
+                        icon);
+                if (descriptor != null) {
+                    image = descriptor.createImage();
+                }
+            }
+            if (image == null) {
+                image = WRTImages.getWrtKitIcon();
+            }
+        }
         return image;
     }
 
@@ -53,11 +81,32 @@
 
     public void install(IProject project, Map<String, String> parameters, IProgressMonitor monitor)
             throws CoreException, IOException {
-        installer.install(project, parameters, monitor);
+        getInstaller().install(project, parameters, monitor);
+    }
+
+    private IJSLibraryInstaller getInstaller() throws CoreException {
+        if (installer == null) {
+            if (element.getAttribute("installer") != null) {
+                installer = (IJSLibraryInstaller) element.createExecutableExtension("installer");
+            }
+            if (installer == null) {
+                installer = new NullInstaller();
+            }
+        }
+        return installer;
     }
 
     @Override
     public String toString() {
         return String.format("JSLibrary \"%s\"", getId());
     }
+
+    public boolean isInstalled(IProject project) {
+        try {
+            return getInstaller().isInstalled(project);
+        } catch (CoreException e) {
+            Activator.log(e);
+            return false;
+        }
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/LibrariesUtils.java	Mon Apr 19 18:03:51 2010 -0700
@@ -0,0 +1,110 @@
+/**
+ * Copyright (c) 2010 Symbian Foundation and/or its subsidiary(-ies).
+ * All rights reserved.
+ * This component and the accompanying materials are made available
+ * under the terms of the License "Eclipse Public License v1.0"
+ * which accompanies this distribution, and is available
+ * at the URL "http://www.eclipse.org/legal/epl-v10.html".
+ *
+ * Initial Contributors:
+ * Symbian Foundation - initial contribution.
+ * Contributors:
+ * Description:
+ * Overview:
+ * Details:
+ * Platforms/Drives/Compatibility:
+ * Assumptions/Requirement/Pre-requisites:
+ * Failures and causes:
+ */
+package org.symbian.tools.wrttools.core.libraries;
+
+import java.io.IOException;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
+import org.eclipse.wst.xml.core.internal.provisional.format.FormatProcessorXML;
+import org.symbian.tools.wrttools.util.CoreUtil;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+@SuppressWarnings("restriction")
+public class LibrariesUtils {
+    public static void addJSToHtml(IProject project, String label, String[] js, String[] css) throws CoreException,
+            IOException {
+        if (js == null) {
+            js = new String[0];
+        }
+        if (css == null) {
+            css = new String[0];
+        }
+        if (js.length == 0 && css.length == 0) {
+            return;
+        }
+        IModelManager modelManager = StructuredModelManager.getModelManager();
+        String indexFile = CoreUtil.getIndexFile(project);
+        if (indexFile != null) {
+            IFile file = project.getFile(indexFile);
+            if (file != null & file.exists()) {
+                IStructuredModel model = modelManager.getModelForEdit(file);
+                try {
+                    if (model instanceof IDOMModel) {
+                        ((IDOMModel) model).beginRecording(js, label);
+                        boolean needsSave = false;
+                        for (String jsLib : js) {
+                            needsSave |= change(((IDOMModel) model).getDocument(), jsLib);
+                        }
+                        model.endRecording(js);
+                        if (needsSave) {
+                            model.save();
+                        }
+                    }
+                } finally {
+                    if (model != null) {
+                        model.releaseFromEdit();
+                    }
+                }
+            }
+        }
+    }
+
+    private static boolean change(IDOMDocument document, String jsPath) {
+        NodeList head = document.getElementsByTagName("head");
+        if (head.getLength() == 1) {
+            IDOMElement headNode = ((IDOMElement) head.item(0));
+            NodeList elements = headNode.getElementsByTagName("script");
+            boolean needToAdd = true;
+            IDOMElement last = null;
+            for (int i = 0; i < elements.getLength(); i++) {
+                last = (IDOMElement) elements.item(i);
+                String attribute = last.getAttribute("src");
+                if (jsPath.equalsIgnoreCase(attribute)) {
+                    needToAdd = false;
+                    break;
+                }
+            }
+            if (needToAdd) {
+                Element element = document.createElement("script");
+                element.setAttribute("language", "javascript");
+                element.setAttribute("type", "text/javascript");
+                element.setAttribute("src", jsPath);
+                if (last != null && last.getNextSibling() != null) {
+                    headNode.insertBefore(element, last.getNextSibling());
+                } else {
+                    headNode.appendChild(element);
+                }
+                FormatProcessorXML formatter = new FormatProcessorXML();
+                formatter.formatNode(headNode);
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
--- a/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/LibraryManager.java	Mon Apr 19 10:37:57 2010 -0700
+++ b/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/LibraryManager.java	Mon Apr 19 18:03:51 2010 -0700
@@ -18,17 +18,61 @@
  */
 package org.symbian.tools.wrttools.core.libraries;
 
-import org.symbian.tools.wrttools.core.WRTImages;
+import java.util.Comparator;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.Platform;
+import org.symbian.tools.wrttools.Activator;
 
 public final class LibraryManager {
+    public class LibraryComparator implements Comparator<JSLibrary> {
+        public int compare(JSLibrary o1, JSLibrary o2) {
+            if (o1 == o2) {
+                return 0;
+            } else if (o1 == null) {
+                return -1;
+            } else if (o2 == null) {
+                return 1;
+            } else {
+                return o1.getName().compareTo(o2.getName());
+            }
+        }
+    }
+
     private JSLibrary[] jsLibraries;
 
     public JSLibrary[] getLibraries() {
         if (jsLibraries == null) {
-            jsLibraries = new JSLibrary[] { new JSLibrary("org.symbian.wrtkit", "WRTKit", WRTImages.getWrtKitIcon(),
-                    new WRTKitInstaller()) };
+            final Set<JSLibrary> libs = new TreeSet<JSLibrary>(new LibraryComparator());
+            IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(Activator.PLUGIN_ID,
+                    "jsLibraries");
+            IConfigurationElement[] elements = point.getConfigurationElements();
+            //            for (IConfigurationElement element : elements) {
+            //                IConfigurationElement[] children = element.getChildren();
+            for (IConfigurationElement child : elements) {
+                    JSLibrary lib = createLib(child);
+                    if (lib != null) {
+                        libs.add(lib);
+                    }
+                //                }
+            }
+            jsLibraries = libs.toArray(new JSLibrary[libs.size()]);
         }
         return jsLibraries;
     }
 
+    private JSLibrary createLib(IConfigurationElement element) {
+        String id = element.getAttribute("id");
+        String name = element.getAttribute("name");
+
+        if (id != null && name != null) {
+            return new JSLibrary(id, name, element);
+        } else {
+            return null;
+        }
+    }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/PhoneGapInstaller.java	Mon Apr 19 18:03:51 2010 -0700
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2010 Symbian Foundation and/or its subsidiary(-ies).
+ * All rights reserved.
+ * This component and the accompanying materials are made available
+ * under the terms of the License "Eclipse Public License v1.0"
+ * which accompanies this distribution, and is available
+ * at the URL "http://www.eclipse.org/legal/epl-v10.html".
+ *
+ * Initial Contributors:
+ * Symbian Foundation - initial contribution.
+ * Contributors:
+ * Description:
+ * Overview:
+ * Details:
+ * Platforms/Drives/Compatibility:
+ * Assumptions/Requirement/Pre-requisites:
+ * Failures and causes:
+ */
+package org.symbian.tools.wrttools.core.libraries;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.SubProgressMonitor;
+import org.eclipse.wst.jsdt.core.IJavaScriptProject;
+import org.eclipse.wst.jsdt.core.JavaScriptCore;
+import org.eclipse.wst.jsdt.core.JavaScriptModelException;
+import org.symbian.tools.wrttools.Activator;
+
+public class PhoneGapInstaller implements IJSLibraryInstaller {
+    private static final String PHONEGAP_JS = "phonegap.js";
+
+    public void install(IProject project, Map<String, String> parameters, IProgressMonitor monitor)
+            throws CoreException, IOException {
+        String folderName = "script";
+        monitor.beginTask("Installing PhoneGap library", 10);
+        IFolder folder = project.getFolder(folderName);
+        if (!folder.isAccessible()) {
+            folder.create(false, true, new SubProgressMonitor(monitor, 2));
+        }
+        IFile file = folder.getFile(PHONEGAP_JS);
+        if (!file.isAccessible()) {
+            InputStream stream = FileLocator.openStream(Activator.getDefault().getBundle(), new Path("libraries")
+                    .append(PHONEGAP_JS), true);
+            file.create(stream, false, new SubProgressMonitor(monitor, 3));
+        }
+        IPath path = new Path(folderName).append(PHONEGAP_JS);
+        LibrariesUtils.addJSToHtml(project, "Adding PhoneGap", new String[] { path.toString() }, null);
+        monitor.done();
+    }
+
+    public boolean isInstalled(IProject project) {
+        IJavaScriptProject jsProject = JavaScriptCore.create(project);
+        try {
+            return jsProject.findType("Accelerometer") != null && jsProject.findType("Camera") != null
+                    && jsProject.findType("Geolocation") != null;
+        } catch (JavaScriptModelException e) {
+            Activator.log(e);
+        }
+        return false;
+    }
+
+}
--- a/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/WRTKitInstaller.java	Mon Apr 19 10:37:57 2010 -0700
+++ b/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/core/libraries/WRTKitInstaller.java	Mon Apr 19 18:03:51 2010 -0700
@@ -22,7 +22,6 @@
 import java.io.InputStream;
 import java.util.Map;
 
-import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IFolder;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.CoreException;
@@ -30,20 +29,13 @@
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.SubProgressMonitor;
-import org.eclipse.wst.sse.core.StructuredModelManager;
-import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
-import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
-import org.eclipse.wst.xml.core.internal.provisional.format.FormatProcessorXML;
+import org.eclipse.wst.jsdt.core.IJavaScriptProject;
+import org.eclipse.wst.jsdt.core.IType;
+import org.eclipse.wst.jsdt.core.JavaScriptCore;
+import org.eclipse.wst.jsdt.core.JavaScriptModelException;
 import org.symbian.tools.wrttools.Activator;
-import org.symbian.tools.wrttools.util.CoreUtil;
 import org.symbian.tools.wrttools.util.ProjectUtils;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
 
-@SuppressWarnings("restriction")
 public class WRTKitInstaller implements IJSLibraryInstaller {
     private static final String JS_PATH = "WRTKit/WRTKit.js";
 
@@ -51,7 +43,6 @@
             throws CoreException, IOException {
         monitor.beginTask("Installing WRTKit library", 15);
 
-        // 1. Copy resources
         IFolder folder = project.getFolder("WRTKit");
         
         if (folder != null && !folder.exists()) {
@@ -61,67 +52,19 @@
                 true);
         ProjectUtils.unzip(zip, folder, 0, "WRTKit", new SubProgressMonitor(monitor, 10));
         
-        // 2. Register in main HTML
-        IModelManager modelManager = StructuredModelManager.getModelManager();
-        String indexFile = CoreUtil.getIndexFile(project);
-        if (indexFile != null) {
-            IFile file = project.getFile(indexFile);
-            if (file != null & file.exists()) {
-                IStructuredModel model = modelManager.getModelForEdit(file);
-                try {
-                    if (model instanceof IDOMModel) {
-                        ((IDOMModel) model).beginRecording(this, "Adding WRTKit Library");
-                        try {
-                            if (change(((IDOMModel) model).getDocument())) {
-                                model.save();
-                            }
-                        } finally {
-                            model.endRecording(this);
-                        }
-                    }
-                } finally {
-                    if (model != null) {
-                        model.releaseFromEdit();
-                    }
-                }
-            }
-        }
-        
-
+        LibrariesUtils.addJSToHtml(project, "Adding WRTKit Library", new String[] { JS_PATH }, null);
         monitor.done();
     }
 
-    private boolean change(IDOMDocument document) {
-        NodeList head = document.getElementsByTagName("head");
-        if (head.getLength() == 1) {
-            IDOMElement headNode = ((IDOMElement) head.item(0));
-            NodeList elements = headNode.getElementsByTagName("script");
-            boolean needToAdd = true;
-            IDOMElement last = null;
-            for (int i = 0; i < elements.getLength(); i++) {
-                last = (IDOMElement) elements.item(i);
-                String attribute = last.getAttribute("src");
-                if (JS_PATH.equalsIgnoreCase(attribute)) {
-                    needToAdd = false;
-                    break;
-                }
-            }
-            if (needToAdd) {
-                Element element = document.createElement("script");
-                element.setAttribute("language", "javascript");
-                element.setAttribute("type", "text/javascript");
-                element.setAttribute("src", JS_PATH);
-                if (last != null && last.getNextSibling() != null) {
-                    headNode.insertBefore(element, last.getNextSibling());
-                } else {
-                    headNode.appendChild(element);
-                }
-                FormatProcessorXML formatter = new FormatProcessorXML();
-                formatter.formatNode(headNode);
-                return true;
-            }
+    public boolean isInstalled(IProject project) {
+        IJavaScriptProject jsProject = JavaScriptCore.create(project);
+        try {
+            IType npopup = jsProject.findType("NotificationPopup");
+            IType uimanager = jsProject.findType("UIManager");
+            return npopup != null && uimanager != null;
+        } catch (JavaScriptModelException e) {
+            Activator.log(e);
+            return false;
         }
-        return false;
     }
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/handlers/AddJSLibrary.java	Mon Apr 19 18:03:51 2010 -0700
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2010 Symbian Foundation and/or its subsidiary(-ies).
+ * All rights reserved.
+ * This component and the accompanying materials are made available
+ * under the terms of the License "Eclipse Public License v1.0"
+ * which accompanies this distribution, and is available
+ * at the URL "http://www.eclipse.org/legal/epl-v10.html".
+ *
+ * Initial Contributors:
+ * Symbian Foundation - initial contribution.
+ * Contributors:
+ * Description:
+ * Overview:
+ * Details:
+ * Platforms/Drives/Compatibility:
+ * Assumptions/Requirement/Pre-requisites:
+ * Failures and causes:
+ */
+package org.symbian.tools.wrttools.handlers;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.commands.IHandler;
+import org.eclipse.jface.wizard.WizardDialog;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.handlers.HandlerUtil;
+import org.symbian.tools.wrttools.wizards.libraries.AddLibrariesWizard;
+
+public class AddJSLibrary extends AbstractHandler implements IHandler {
+
+    public Object execute(ExecutionEvent event) throws ExecutionException {
+        IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindow(event);
+        if (window != null) {
+            new WizardDialog(window.getShell(), new AddLibrariesWizard(HandlerUtil.getCurrentSelection(event))).open();
+        }
+        return null;
+    }
+
+}
--- a/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/wizards/WRTProjectLibraryWizardPage.java	Mon Apr 19 10:37:57 2010 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-/**
- * Copyright (c) 2010 Symbian Foundation and/or its subsidiary(-ies).
- * All rights reserved.
- * This component and the accompanying materials are made available
- * under the terms of the License "Eclipse Public License v1.0"
- * which accompanies this distribution, and is available
- * at the URL "http://www.eclipse.org/legal/epl-v10.html".
- *
- * Initial Contributors:
- * Symbian Foundation - initial contribution.
- * Contributors:
- * Description:
- * Overview:
- * Details:
- * Platforms/Drives/Compatibility:
- * Assumptions/Requirement/Pre-requisites:
- * Failures and causes:
- */
-package org.symbian.tools.wrttools.wizards;
-
-import org.eclipse.core.databinding.DataBindingContext;
-import org.eclipse.core.databinding.beans.BeansObservables;
-import org.eclipse.core.databinding.observable.set.IObservableSet;
-import org.eclipse.jface.databinding.viewers.IViewerObservableSet;
-import org.eclipse.jface.databinding.viewers.ViewersObservables;
-import org.eclipse.jface.viewers.ArrayContentProvider;
-import org.eclipse.jface.viewers.CheckStateChangedEvent;
-import org.eclipse.jface.viewers.CheckboxTableViewer;
-import org.eclipse.jface.viewers.ICheckStateListener;
-import org.eclipse.jface.viewers.LabelProvider;
-import org.eclipse.jface.wizard.WizardPage;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.widgets.Composite;
-import org.symbian.tools.wrttools.Activator;
-import org.symbian.tools.wrttools.core.libraries.JSLibrary;
-
-public class WRTProjectLibraryWizardPage extends WizardPage {
-    public static final class LibraryCheckStateListener implements ICheckStateListener {
-        private final WizardContext context;
-
-        public LibraryCheckStateListener(WizardContext context) {
-            this.context = context;
-        }
-
-        public void checkStateChanged(CheckStateChangedEvent event) {
-            if (!event.getChecked() && context.isRequiredLibrary((JSLibrary) event.getElement())) {
-                event.getCheckable().setChecked(event.getElement(), true);
-            }
-        }
-
-    }
-    private static final class LibraryLabelProvider extends LabelProvider {
-        @Override
-        public String getText(Object element) {
-            return ((JSLibrary) element).getName();
-        }
-
-        @Override
-        public Image getImage(Object element) {
-            return ((JSLibrary) element).getImage();
-        }
-    }
-    private CheckboxTableViewer list;
-    private final WizardContext context;
-    private final DataBindingContext bindingContext;
-
-    public WRTProjectLibraryWizardPage(WizardContext context, DataBindingContext bindingContext) {
-        super("ProjectLibraries", "WRT Project Libraries", null);
-        this.context = context;
-        this.bindingContext = bindingContext;
-        setDescription("Select libraries to add to your project");
-    }
-
-    public void createControl(Composite parent) {
-        list = CheckboxTableViewer.newCheckList(parent, SWT.BORDER);
-        list.setLabelProvider(new LibraryLabelProvider());
-        list.setContentProvider(new ArrayContentProvider());
-        list.setInput(Activator.getJSLibraries());
-        list.addCheckStateListener(new LibraryCheckStateListener(context));
-        IViewerObservableSet ui = ViewersObservables.observeCheckedElements(list, JSLibrary.class);
-        IObservableSet model = BeansObservables.observeSet(context, WizardContext.LIBRARIES);
-        bindingContext.bindSet(model, ui);
-        setControl(list.getControl());
-    }
-
-}
--- a/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/wizards/WrtWidgetWizard.java	Mon Apr 19 10:37:57 2010 -0700
+++ b/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/wizards/WrtWidgetWizard.java	Mon Apr 19 18:03:51 2010 -0700
@@ -62,6 +62,7 @@
 import org.symbian.tools.wrttools.core.libraries.JSLibrary;
 import org.symbian.tools.wrttools.util.NonClosingStream;
 import org.symbian.tools.wrttools.util.ProjectUtils;
+import org.symbian.tools.wrttools.wizards.libraries.WRTProjectLibraryWizardPage;
 
 public class WrtWidgetWizard extends Wizard implements INewWizard, IExecutableExtension {
     private WizardNewProjectCreationPage resourcePage;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/wizards/libraries/AddLibrariesWizard.java	Mon Apr 19 18:03:51 2010 -0700
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2010 Symbian Foundation and/or its subsidiary(-ies).
+ * All rights reserved.
+ * This component and the accompanying materials are made available
+ * under the terms of the License "Eclipse Public License v1.0"
+ * which accompanies this distribution, and is available
+ * at the URL "http://www.eclipse.org/legal/epl-v10.html".
+ *
+ * Initial Contributors:
+ * Symbian Foundation - initial contribution.
+ * Contributors:
+ * Description:
+ * Overview:
+ * Details:
+ * Platforms/Drives/Compatibility:
+ * Assumptions/Requirement/Pre-requisites:
+ * Failures and causes:
+ */
+package org.symbian.tools.wrttools.wizards.libraries;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubProgressMonitor;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.wizard.Wizard;
+import org.symbian.tools.wrttools.Activator;
+import org.symbian.tools.wrttools.core.libraries.JSLibrary;
+
+public class AddLibrariesWizard extends Wizard {
+    private final IProject project;
+    private LibrarySelectionPage page;
+
+    public AddLibrariesWizard(ISelection currentSelection) {
+        IProject p = null;
+        if (!currentSelection.isEmpty() && currentSelection instanceof IStructuredSelection) {
+            Object sel = ((IStructuredSelection) currentSelection).getFirstElement();
+            if (sel instanceof IAdaptable) {
+                IResource res = (IResource) ((IAdaptable) sel).getAdapter(IResource.class);
+                if (res != null) {
+                    p = res.getProject();
+                }
+            }
+        }
+        project = p;
+    }
+
+    @Override
+    public void addPages() {
+        page = new LibrarySelectionPage(project);
+        setWindowTitle("Add JavaScript Libraries");
+        addPage(page);
+    }
+
+    @Override
+    public boolean performFinish() {
+        final IProject p = page.getProject();
+        final JSLibrary[] libraries = page.getSelectedLibraries();
+        try {
+            getContainer().run(true, false, new IRunnableWithProgress() {
+                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+                    monitor.beginTask("Installing libraries", 50 * libraries.length);
+                    for (JSLibrary jsLibrary : libraries) {
+                        try {
+                            jsLibrary.install(p, new HashMap<String, String>(), new SubProgressMonitor(monitor, 50));
+                        } catch (CoreException e) {
+                            Activator.log(e);
+                        } catch (IOException e) {
+                            Activator.log(e);
+                        }
+                    }
+                }
+            });
+        } catch (InvocationTargetException e) {
+            Activator.log(e);
+        } catch (InterruptedException e) {
+            Activator.log(e);
+        }
+        return true;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/wizards/libraries/LibraryLabelProvider.java	Mon Apr 19 18:03:51 2010 -0700
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010 Symbian Foundation and/or its subsidiary(-ies).
+ * All rights reserved.
+ * This component and the accompanying materials are made available
+ * under the terms of the License "Eclipse Public License v1.0"
+ * which accompanies this distribution, and is available
+ * at the URL "http://www.eclipse.org/legal/epl-v10.html".
+ *
+ * Initial Contributors:
+ * Symbian Foundation - initial contribution.
+ * Contributors:
+ * Description:
+ * Overview:
+ * Details:
+ * Platforms/Drives/Compatibility:
+ * Assumptions/Requirement/Pre-requisites:
+ * Failures and causes:
+ */
+package org.symbian.tools.wrttools.wizards.libraries;
+
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.swt.graphics.Image;
+import org.symbian.tools.wrttools.core.libraries.JSLibrary;
+
+public final class LibraryLabelProvider extends LabelProvider {
+    @Override
+    public String getText(Object element) {
+        return ((JSLibrary) element).getName();
+    }
+
+    @Override
+    public Image getImage(Object element) {
+        return ((JSLibrary) element).getImage();
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/wizards/libraries/LibrarySelectionPage.java	Mon Apr 19 18:03:51 2010 -0700
@@ -0,0 +1,161 @@
+/**
+ * Copyright (c) 2010 Symbian Foundation and/or its subsidiary(-ies).
+ * All rights reserved.
+ * This component and the accompanying materials are made available
+ * under the terms of the License "Eclipse Public License v1.0"
+ * which accompanies this distribution, and is available
+ * at the URL "http://www.eclipse.org/legal/epl-v10.html".
+ *
+ * Initial Contributors:
+ * Symbian Foundation - initial contribution.
+ * Contributors:
+ * Description:
+ * Overview:
+ * Details:
+ * Platforms/Drives/Compatibility:
+ * Assumptions/Requirement/Pre-requisites:
+ * Failures and causes:
+ */
+package org.symbian.tools.wrttools.wizards.libraries;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.CheckStateChangedEvent;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ComboViewer;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.viewers.ICheckStateProvider;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.wizard.IWizardPage;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.ui.model.WorkbenchLabelProvider;
+import org.symbian.tools.wrttools.Activator;
+import org.symbian.tools.wrttools.core.libraries.JSLibrary;
+
+public class LibrarySelectionPage extends WizardPage implements IWizardPage {
+    public final class CheckStateListener implements ICheckStateListener {
+        public void checkStateChanged(CheckStateChangedEvent event) {
+            JSLibrary library = (JSLibrary) event.getElement();
+            if (selected != null && library.isInstalled(selected)) {
+                event.getCheckable().setChecked(library, true);
+            }
+            validate();
+        }
+    }
+
+    private IProject selected = null;
+    private CheckboxTableViewer libraryList;
+
+    public final class CheckStateProvider implements ICheckStateProvider {
+
+        public boolean isChecked(Object element) {
+            return selected != null && ((JSLibrary) element).isInstalled(selected);
+        }
+
+        public boolean isGrayed(Object element) {
+            return selected != null && ((JSLibrary) element).isInstalled(selected);
+        }
+
+    }
+
+    protected LibrarySelectionPage(IProject project) {
+        super("libraryselectionpage");
+        selected = project;
+        setTitle("Add JavaScript Libraries");
+        setDescription("Select project to add libraries to and select librarires to add");
+    }
+
+    public void createControl(Composite parent) {
+        Composite root = new Composite(parent, SWT.NONE);
+        root.setLayout(new GridLayout(2, false));
+        Label label = new Label(root, SWT.NONE);
+        label.setText("Project:");
+
+        ComboViewer viewer = new ComboViewer(root, SWT.BORDER | SWT.READ_ONLY);
+
+        viewer.addSelectionChangedListener(new ISelectionChangedListener() {
+            public void selectionChanged(SelectionChangedEvent event) {
+                ISelection selection = event.getSelection();
+                if (selection instanceof IStructuredSelection) {
+                    selectProject((IProject) ((IStructuredSelection) selection).getFirstElement());
+                } else {
+                    selectProject(null);
+                }
+            }
+        });
+        viewer.setLabelProvider(new WorkbenchLabelProvider());
+        viewer.setContentProvider(new ArrayContentProvider());
+        viewer.setInput(Activator.getWrtProjects());
+
+        viewer.getControl().setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+        libraryList = CheckboxTableViewer.newCheckList(root, SWT.SINGLE | SWT.BORDER);
+
+        GridData data = new GridData(GridData.FILL, GridData.FILL, true, true, 2, 1);
+        libraryList.getControl().setLayoutData(data);
+
+        libraryList.setContentProvider(new ArrayContentProvider());
+        libraryList.setLabelProvider(new LibraryLabelProvider());
+        libraryList.setCheckStateProvider(new CheckStateProvider());
+        libraryList.addCheckStateListener(new CheckStateListener());
+        libraryList.setInput(Activator.getJSLibraries());
+
+        if (selected != null) {
+            viewer.setSelection(new StructuredSelection(selected), true);
+        }
+        selectProject(selected);
+
+        setControl(root);
+        validate();
+    }
+
+    private void selectProject(IProject selected) {
+        this.selected = selected;
+        libraryList.getControl().setEnabled(selected != null);
+        libraryList.refresh();
+        validate();
+    }
+
+    private void validate() {
+        boolean valid = false;
+        if (selected != null) {
+            Object[] checkedElements = libraryList.getCheckedElements();
+            for (Object object : checkedElements) {
+                if (!((JSLibrary) object).isInstalled(selected)) {
+                    valid = true;
+                    break;
+                }
+            }
+        }
+        setPageComplete(valid);
+    }
+
+    public IProject getProject() {
+        return selected;
+    }
+
+    public JSLibrary[] getSelectedLibraries() {
+        final Object[] checkedElements = libraryList.getCheckedElements();
+        final Collection<JSLibrary> libraries = new HashSet<JSLibrary>();
+        for (Object object : checkedElements) {
+            final JSLibrary lib = (JSLibrary) object;
+            if (!lib.isInstalled(selected)) {
+                libraries.add(lib);
+            }
+        }
+        return libraries.toArray(new JSLibrary[libraries.size()]);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.symbian.tools.wrttools/src/org/symbian/tools/wrttools/wizards/libraries/WRTProjectLibraryWizardPage.java	Mon Apr 19 18:03:51 2010 -0700
@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) 2010 Symbian Foundation and/or its subsidiary(-ies).
+ * All rights reserved.
+ * This component and the accompanying materials are made available
+ * under the terms of the License "Eclipse Public License v1.0"
+ * which accompanies this distribution, and is available
+ * at the URL "http://www.eclipse.org/legal/epl-v10.html".
+ *
+ * Initial Contributors:
+ * Symbian Foundation - initial contribution.
+ * Contributors:
+ * Description:
+ * Overview:
+ * Details:
+ * Platforms/Drives/Compatibility:
+ * Assumptions/Requirement/Pre-requisites:
+ * Failures and causes:
+ */
+package org.symbian.tools.wrttools.wizards.libraries;
+
+import org.eclipse.core.databinding.DataBindingContext;
+import org.eclipse.core.databinding.beans.BeansObservables;
+import org.eclipse.core.databinding.observable.set.IObservableSet;
+import org.eclipse.jface.databinding.viewers.IViewerObservableSet;
+import org.eclipse.jface.databinding.viewers.ViewersObservables;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.CheckStateChangedEvent;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.symbian.tools.wrttools.Activator;
+import org.symbian.tools.wrttools.core.libraries.JSLibrary;
+import org.symbian.tools.wrttools.wizards.WizardContext;
+
+public class WRTProjectLibraryWizardPage extends WizardPage {
+    public static final class LibraryCheckStateListener implements ICheckStateListener {
+        private final WizardContext context;
+
+        public LibraryCheckStateListener(WizardContext context) {
+            this.context = context;
+        }
+
+        public void checkStateChanged(CheckStateChangedEvent event) {
+            if (!event.getChecked() && context.isRequiredLibrary((JSLibrary) event.getElement())) {
+                event.getCheckable().setChecked(event.getElement(), true);
+            }
+        }
+
+    }
+    private CheckboxTableViewer list;
+    private final WizardContext context;
+    private final DataBindingContext bindingContext;
+
+    public WRTProjectLibraryWizardPage(WizardContext context, DataBindingContext bindingContext) {
+        super("ProjectLibraries", "WRT Project Libraries", null);
+        this.context = context;
+        this.bindingContext = bindingContext;
+        setDescription("Select libraries to add to your project");
+    }
+
+    public void createControl(Composite parent) {
+        list = CheckboxTableViewer.newCheckList(parent, SWT.BORDER);
+        list.setLabelProvider(new LibraryLabelProvider());
+        list.setContentProvider(new ArrayContentProvider());
+        list.setInput(Activator.getJSLibraries());
+        list.addCheckStateListener(new LibraryCheckStateListener(context));
+        IViewerObservableSet ui = ViewersObservables.observeCheckedElements(list, JSLibrary.class);
+        IObservableSet model = BeansObservables.observeSet(context, WizardContext.LIBRARIES);
+        bindingContext.bindSet(model, ui);
+        setControl(list.getControl());
+    }
+
+}