Browse Source

Merge pull request #778 from shaddockh/TSH-ATOMIC-EXTENSION-USERPREFS

User preferences system for extensions
JoshEngebretson 9 years ago
parent
commit
5d1825b0ba

+ 39 - 1
Script/AtomicEditor/editor/Editor.ts

@@ -55,7 +55,7 @@ class Editor extends Atomic.ScriptObject {
 
 
         this.editorLicense = new EditorLicense();
         this.editorLicense = new EditorLicense();
 
 
-        EditorUI.initialize();
+        EditorUI.initialize(this);
 
 
         this.playMode = new PlayMode();
         this.playMode = new PlayMode();
 
 
@@ -138,6 +138,44 @@ class Editor extends Atomic.ScriptObject {
         return true;
         return true;
     }
     }
 
 
+    /**
+     * Return a preference value or the provided default from the user settings file
+     * @param  {string} extensionName name of the extension the preference lives under
+     * @param  {string} preferenceName name of the preference to retrieve
+     * @param  {number | boolean | string} defaultValue value to return if pref doesn't exist
+     * @return {number|boolean|string}
+     */
+    getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: number): number;
+    getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: string): string;
+    getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: boolean): boolean;
+    getUserPreference(extensionName: string, preferenceName: string, defaultValue?: any): any {
+        return Preferences.getInstance().getUserPreference(extensionName, preferenceName, defaultValue);
+    }
+
+
+    /**
+     * Sets a user preference value in the user settings file
+     * @param  {string} extensionName name of the extension the preference lives under
+     * @param  {string} preferenceName name of the preference to set
+     * @param  {number | boolean | string} value value to set
+     */
+    setUserPreference(extensionName: string, preferenceName: string, value: number | boolean | string) {
+        Preferences.getInstance().setUserPreference(extensionName, preferenceName, value);
+        this.sendEvent(EditorEvents.UserPreferencesChangedNotification);
+    }
+
+    /**
+     * Sets a group of user preference values in the user settings file located in the project.  Elements in the
+     * group will merge in with existing group preferences.  Use this method if setting a bunch of settings
+     * at once.
+     * @param  {string} settingsGroup name of the group the preference lives under
+     * @param  {string} groupPreferenceValues an object literal containing all of the preferences for the group.
+     */
+    setUserPreferenceGroup(settingsGroup: string, groupPreferenceValues: Object) {
+        Preferences.getInstance().setUserPreferenceGroup(settingsGroup, groupPreferenceValues);
+        this.sendEvent(EditorEvents.UserPreferencesChangedNotification);
+    }
+
     handleEditorLoadProject(event: EditorEvents.LoadProjectEvent): boolean {
     handleEditorLoadProject(event: EditorEvents.LoadProjectEvent): boolean {
 
 
         var system = ToolCore.getToolSystem();
         var system = ToolCore.getToolSystem();

+ 2 - 0
Script/AtomicEditor/editor/EditorEvents.ts

@@ -157,3 +157,5 @@ export const RemoveCurrentAssetAssigned = "RemoveCurrentAssetAssigned";
 export interface RemoveCurrentAssetAssignedEvent {
 export interface RemoveCurrentAssetAssignedEvent {
 
 
 }
 }
+
+export const UserPreferencesChangedNotification  = "UserPreferencesChangedNotification";

+ 110 - 0
Script/AtomicEditor/editor/Preferences.ts

@@ -31,6 +31,8 @@ class Preferences {
     private static instance: Preferences;
     private static instance: Preferences;
     private _prefs: PreferencesFormat;
     private _prefs: PreferencesFormat;
 
 
+    private cachedProjectPreferences: Object = null;
+
     constructor() {
     constructor() {
         this.fileSystem = Atomic.getFileSystem();
         this.fileSystem = Atomic.getFileSystem();
         Preferences.instance = this;
         Preferences.instance = this;
@@ -145,6 +147,114 @@ class Preferences {
     static getInstance(): Preferences {
     static getInstance(): Preferences {
         return Preferences.instance;
         return Preferences.instance;
     }
     }
+
+    /**
+     * Return a preference value or the provided default from the user settings file located in the project
+     * @param  {string} settingsGroup name of the group these settings should fall under
+     * @param  {string} preferenceName name of the preference to retrieve
+     * @param  {number | boolean | string} defaultValue value to return if pref doesn't exist
+     * @return {number|boolean|string}
+     */
+    getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: number): number;
+    getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: string): string;
+    getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: boolean): boolean;
+    getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: any): any {
+
+        // Cache the settings so we don't keep going out to the file
+        if (this.cachedProjectPreferences == null) {
+            const prefsFileLoc = ToolCore.toolSystem.project.userPrefsFullPath;
+            if (Atomic.fileSystem.fileExists(prefsFileLoc)) {
+                let prefsFile = new Atomic.File(prefsFileLoc, Atomic.FILE_READ);
+                try {
+                    let prefs = JSON.parse(prefsFile.readText());
+                    this.cachedProjectPreferences = prefs;
+                } finally {
+                    prefsFile.close();
+                }
+            }
+        }
+
+        if (this.cachedProjectPreferences && this.cachedProjectPreferences[settingsGroup]) {
+            return this.cachedProjectPreferences[settingsGroup][preferenceName] || defaultValue;
+        }
+
+        // if all else fails
+        return defaultValue;
+    }
+
+    /**
+     * Sets a user preference value in the user settings file located in the project
+     * @param  {string} settingsGroup name of the group the preference lives under
+     * @param  {string} preferenceName name of the preference to set
+     * @param  {number | boolean | string} value value to set
+     */
+    setUserPreference(settingsGroup: string, preferenceName: string, value: number | boolean | string) {
+
+        const prefsFileLoc = ToolCore.toolSystem.project.userPrefsFullPath;
+        let prefs = {};
+
+        if (Atomic.fileSystem.fileExists(prefsFileLoc)) {
+            let prefsFile = new Atomic.File(prefsFileLoc, Atomic.FILE_READ);
+            try {
+                prefs = JSON.parse(prefsFile.readText());
+            } finally {
+                prefsFile.close();
+            }
+        }
+
+        prefs[settingsGroup] = prefs[settingsGroup] || {};
+        prefs[settingsGroup][preferenceName] = value;
+
+        let saveFile = new Atomic.File(prefsFileLoc, Atomic.FILE_WRITE);
+        try {
+            saveFile.writeString(JSON.stringify(prefs, null, "  "));
+        } finally {
+            saveFile.flush();
+            saveFile.close();
+        }
+
+        // Cache the update
+        this.cachedProjectPreferences = prefs;
+    }
+
+
+    /**
+     * Sets a group of user preference values in the user settings file located in the project.  Elements in the
+     * group will merge in with existing group preferences.  Use this method if setting a bunch of settings
+     * at once.
+     * @param  {string} settingsGroup name of the group the preference lives under
+     * @param  {string} groupPreferenceValues an object literal containing all of the preferences for the group.
+     */
+    setUserPreferenceGroup(settingsGroup: string, groupPreferenceValues: Object) {
+
+        const prefsFileLoc = ToolCore.toolSystem.project.userPrefsFullPath;
+        let prefs = {};
+
+        if (Atomic.fileSystem.fileExists(prefsFileLoc)) {
+            let prefsFile = new Atomic.File(prefsFileLoc, Atomic.FILE_READ);
+            try {
+                prefs = JSON.parse(prefsFile.readText());
+            } finally {
+                prefsFile.close();
+            }
+        }
+
+        prefs[settingsGroup] = prefs[settingsGroup] || {};
+        for (let preferenceName in groupPreferenceValues) {
+            prefs[settingsGroup][preferenceName] = groupPreferenceValues[preferenceName];
+        }
+
+        let saveFile = new Atomic.File(prefsFileLoc, Atomic.FILE_WRITE);
+        try {
+            saveFile.writeString(JSON.stringify(prefs, null, "  "));
+        } finally {
+            saveFile.flush();
+            saveFile.close();
+        }
+
+        // Cache the update
+        this.cachedProjectPreferences = prefs;
+    }
 }
 }
 
 
 interface WindowData {
 interface WindowData {

+ 43 - 5
Script/AtomicEditor/hostExtensions/HostExtensionServices.ts

@@ -25,6 +25,8 @@ import * as EditorUI from "../ui/EditorUI";
 import MainFrame = require("../ui/frames/MainFrame");
 import MainFrame = require("../ui/frames/MainFrame");
 import ModalOps = require("../ui/modal/ModalOps");
 import ModalOps = require("../ui/modal/ModalOps");
 import ResourceOps = require("../resources/ResourceOps");
 import ResourceOps = require("../resources/ResourceOps");
+import Editor = require("../editor/Editor");
+
 /**
 /**
  * Generic registry for storing Editor Extension Services
  * Generic registry for storing Editor Extension Services
  */
  */
@@ -123,6 +125,42 @@ export class ProjectServicesProvider extends ServicesProvider<Editor.HostExtensi
             }
             }
         });
         });
     }
     }
+
+    /**
+     * Return a preference value or the provided default from the user settings file
+     * @param  {string} extensionName name of the extension the preference lives under
+     * @param  {string} preferenceName name of the preference to retrieve
+     * @param  {number | boolean | string} defaultValue value to return if pref doesn't exist
+     * @return {number|boolean|string}
+     */
+    getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: number): number;
+    getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: string): string;
+    getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: boolean): boolean;
+    getUserPreference(extensionName: string, preferenceName: string, defaultValue?: any): any {
+        return EditorUI.getEditor().getUserPreference(extensionName, preferenceName, defaultValue);
+    }
+
+
+    /**
+     * Sets a user preference value in the user settings file
+     * @param  {string} extensionName name of the extension the preference lives under
+     * @param  {string} preferenceName name of the preference to set
+     * @param  {number | boolean | string} value value to set
+     */
+    setUserPreference(extensionName: string, preferenceName: string, value: number | boolean | string) {
+        EditorUI.getEditor().setUserPreference(extensionName, preferenceName, value);
+    }
+
+    /**
+     * Sets a group of user preference values in the user settings file located in the project.  Elements in the
+     * group will merge in with existing group preferences.  Use this method if setting a bunch of settings
+     * at once.
+     * @param  {string} extensionName name of the group the preference lives under
+     * @param  {string} groupPreferenceValues an object literal containing all of the preferences for the group.
+     */
+    setUserPreferenceGroup(extensionName: string, groupPreferenceValues: Object) {
+        EditorUI.getEditor().setUserPreferenceGroup(extensionName, groupPreferenceValues);
+    }
 }
 }
 
 
 /**
 /**
@@ -196,7 +234,7 @@ export class ResourceServicesProvider extends ServicesProvider<Editor.HostExtens
     }
     }
 
 
     /**
     /**
-     * Create New Material 
+     * Create New Material
      * @param  {string} resourcePath
      * @param  {string} resourcePath
      * @param  {string} materialName
      * @param  {string} materialName
      * @param  {boolean} reportError
      * @param  {boolean} reportError
@@ -391,7 +429,7 @@ export class UIServicesProvider extends ServicesProvider<Editor.HostExtensions.U
                     }
                     }
                 }
                 }
             } catch (e) {
             } catch (e) {
-               EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
+                EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
             }
             }
         });
         });
     }
     }
@@ -403,7 +441,7 @@ export class UIServicesProvider extends ServicesProvider<Editor.HostExtensions.U
      * @type {boolean} return true if handled
      * @type {boolean} return true if handled
      */
      */
     hierarchyContextItemClicked(node: Atomic.Node, refid: string): boolean {
     hierarchyContextItemClicked(node: Atomic.Node, refid: string): boolean {
-        if (!node) 
+        if (!node)
             return false;
             return false;
 
 
         // run through and find any services that can handle this.
         // run through and find any services that can handle this.
@@ -416,7 +454,7 @@ export class UIServicesProvider extends ServicesProvider<Editor.HostExtensions.U
                     }
                     }
                 }
                 }
             } catch (e) {
             } catch (e) {
-               EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
+                EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
             }
             }
         });
         });
     }
     }
@@ -442,7 +480,7 @@ export class UIServicesProvider extends ServicesProvider<Editor.HostExtensions.U
                     }
                     }
                 }
                 }
             } catch (e) {
             } catch (e) {
-               EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
+                EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
             }
             }
         });
         });
     }
     }

+ 12 - 5
Script/AtomicEditor/ui/EditorUI.ts

@@ -25,6 +25,7 @@ import MainFrame = require("./frames/MainFrame");
 import ModalOps = require("./modal/ModalOps");
 import ModalOps = require("./modal/ModalOps");
 import Shortcuts = require("./Shortcuts");
 import Shortcuts = require("./Shortcuts");
 import ServiceLocator from "../hostExtensions/ServiceLocator";
 import ServiceLocator from "../hostExtensions/ServiceLocator";
+import Editor = require("editor/Editor");
 
 
 // this is designed with public get functions to solve
 // this is designed with public get functions to solve
 // circular dependency issues in TS
 // circular dependency issues in TS
@@ -47,8 +48,12 @@ export function getShortcuts():Shortcuts {
   return editorUI.shortcuts;
   return editorUI.shortcuts;
 }
 }
 
 
-export function initialize() {
-  editorUI = new EditorUI();
+export function initialize(editor: Editor) {
+  editorUI = new EditorUI(editor);
+}
+
+export function getEditor(): Editor {
+    return editorUI.editor;
 }
 }
 
 
 export function shutdown() {
 export function shutdown() {
@@ -68,10 +73,9 @@ export function getCurrentResourceEditor():Editor.ResourceEditor {
     return getMainFrame().resourceframe.currentResourceEditor;
     return getMainFrame().resourceframe.currentResourceEditor;
 }
 }
 
 
-
 class EditorUI extends Atomic.ScriptObject {
 class EditorUI extends Atomic.ScriptObject {
 
 
-  constructor() {
+  constructor(editor: Editor) {
 
 
     super();
     super();
 
 
@@ -83,6 +87,8 @@ class EditorUI extends Atomic.ScriptObject {
 
 
     this.view.addChild(this.mainframe);
     this.view.addChild(this.mainframe);
 
 
+    this.editor = editor;
+
     this.subscribeToEvent("ScreenMode", (ev:Atomic.ScreenModeEvent) => {
     this.subscribeToEvent("ScreenMode", (ev:Atomic.ScreenModeEvent) => {
 
 
       this.mainframe.setSize(ev.width, ev.height);
       this.mainframe.setSize(ev.width, ev.height);
@@ -97,7 +103,7 @@ class EditorUI extends Atomic.ScriptObject {
 
 
     // Hook the service locator into the event system and give it the ui objects it needs
     // Hook the service locator into the event system and give it the ui objects it needs
     ServiceLocator.uiServices.init(
     ServiceLocator.uiServices.init(
-      this.mainframe, 
+      this.mainframe,
       this.modalOps);
       this.modalOps);
     ServiceLocator.subscribeToEvents(this.mainframe);
     ServiceLocator.subscribeToEvents(this.mainframe);
 
 
@@ -115,5 +121,6 @@ class EditorUI extends Atomic.ScriptObject {
   mainframe: MainFrame;
   mainframe: MainFrame;
   modalOps: ModalOps;
   modalOps: ModalOps;
   shortcuts: Shortcuts;
   shortcuts: Shortcuts;
+  editor: Editor;
 
 
 }
 }

+ 19 - 1
Script/AtomicEditor/ui/frames/ResourceFrame.ts

@@ -100,7 +100,7 @@ class ResourceFrame extends ScriptWidget {
 
 
         if (ext == ".js" || ext == ".txt" || ext == ".json" || ext == ".ts") {
         if (ext == ".js" || ext == ".txt" || ext == ".json" || ext == ".ts") {
 
 
-            editor = new Editor.JSResourceEditor(path, this.tabcontainer);
+             editor = new Editor.JSResourceEditor(path, this.tabcontainer);
 
 
         } else if (ext == ".scene") {
         } else if (ext == ".scene") {
 
 
@@ -226,6 +226,23 @@ class ResourceFrame extends ScriptWidget {
         }
         }
     }
     }
 
 
+    handleUserPreferencesChanged() {
+        let prefsPath = ToolCore.toolSystem.project.userPrefsFullPath;
+        if (Atomic.fileSystem.fileExists(prefsPath)) {
+            for (let editorKey in this.editors) {
+                let editor = this.editors[editorKey];
+                if (editor.typeName == "JSResourceEditor") {
+                    let jsEditor = <Editor.JSResourceEditor>editor;
+
+                    // Get a reference to the web client so we can call the load preferences method
+                    let webClient = jsEditor.webView.webClient;
+
+                    webClient.executeJavaScript(`HOST_loadPreferences("atomic://${prefsPath}");`);
+                }
+            }
+        }
+    }
+
     handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
     handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
 
 
         if (ev.type == Atomic.UI_EVENT_TYPE_TAB_CHANGED && ev.target == this.tabcontainer) {
         if (ev.type == Atomic.UI_EVENT_TYPE_TAB_CHANGED && ev.target == this.tabcontainer) {
@@ -298,6 +315,7 @@ class ResourceFrame extends ScriptWidget {
         this.subscribeToEvent(EditorEvents.EditorResourceClose, (ev: EditorEvents.EditorCloseResourceEvent) => this.handleCloseResource(ev));
         this.subscribeToEvent(EditorEvents.EditorResourceClose, (ev: EditorEvents.EditorCloseResourceEvent) => this.handleCloseResource(ev));
         this.subscribeToEvent(EditorEvents.RenameResourceNotification, (ev: EditorEvents.RenameResourceEvent) => this.handleRenameResource(ev));
         this.subscribeToEvent(EditorEvents.RenameResourceNotification, (ev: EditorEvents.RenameResourceEvent) => this.handleRenameResource(ev));
         this.subscribeToEvent(EditorEvents.DeleteResourceNotification, (data) => this.handleDeleteResource(data));
         this.subscribeToEvent(EditorEvents.DeleteResourceNotification, (data) => this.handleDeleteResource(data));
+        this.subscribeToEvent(EditorEvents.UserPreferencesChangedNotification, (data) => this.handleUserPreferencesChanged());
 
 
         this.subscribeToEvent(UIEvents.ResourceEditorChanged, (data) => this.handleResourceEditorChanged(data));
         this.subscribeToEvent(UIEvents.ResourceEditorChanged, (data) => this.handleResourceEditorChanged(data));
 
 

+ 1 - 0
Script/AtomicWebViewEditor/clientExtensions/ClientExtensionEventNames.ts

@@ -30,4 +30,5 @@ export default class ClientExtensionEventNames {
     static ResourceRenamedEvent = "ResourceRenamedEvent";
     static ResourceRenamedEvent = "ResourceRenamedEvent";
     static ResourceDeletedEvent = "ResourceDeletedEvent";
     static ResourceDeletedEvent = "ResourceDeletedEvent";
     static ProjectUnloadedEvent = "ProjectUnloadedEvent";
     static ProjectUnloadedEvent = "ProjectUnloadedEvent";
+    static PreferencesChangedEvent = "PreferencesChangedEvent";
 }
 }

+ 62 - 2
Script/AtomicWebViewEditor/clientExtensions/ClientExtensionServices.ts

@@ -21,6 +21,7 @@
 //
 //
 
 
 import ClientExtensionEventNames from "./ClientExtensionEventNames";
 import ClientExtensionEventNames from "./ClientExtensionEventNames";
+import HostInteropType from "../interop";
 
 
 // Entry point for web view extensions -- extensions that live inside the web view
 // Entry point for web view extensions -- extensions that live inside the web view
 interface EventSubscription {
 interface EventSubscription {
@@ -54,7 +55,7 @@ export class EventDispatcher implements Editor.Extensions.EventDispatcher {
 /**
 /**
  * Generic registry for storing Editor Extension Services
  * Generic registry for storing Editor Extension Services
  */
  */
-class ServiceRegistry<T extends Editor.Extensions.ServiceEventListener> implements Editor.Extensions.ServicesProvider<T> {
+class ServicesProvider<T extends Editor.Extensions.ServiceEventListener> implements Editor.Extensions.ServicesProvider<T> {
     registeredServices: T[] = [];
     registeredServices: T[] = [];
 
 
     /**
     /**
@@ -73,7 +74,18 @@ class ServiceRegistry<T extends Editor.Extensions.ServiceEventListener> implemen
     }
     }
 }
 }
 
 
-export class ExtensionServiceRegistry extends ServiceRegistry<Editor.ClientExtensions.WebViewService> {
+export class WebViewServicesProvider extends ServicesProvider<Editor.ClientExtensions.WebViewServiceEventListener> {
+
+    private userPreferences = {};
+
+    /**
+     * Sets the preferences for the service locator
+     * @param  {any} prefs
+     * @return {[type]}
+     */
+    setPreferences(prefs : any) {
+        this.userPreferences = prefs;
+    }
 
 
     /**
     /**
      * Allow this service registry to subscribe to events that it is interested in
      * Allow this service registry to subscribe to events that it is interested in
@@ -86,6 +98,7 @@ export class ExtensionServiceRegistry extends ServiceRegistry<Editor.ClientExten
         eventDispatcher.subscribeToEvent(ClientExtensionEventNames.ProjectUnloadedEvent, (ev) => this.projectUnloaded());
         eventDispatcher.subscribeToEvent(ClientExtensionEventNames.ProjectUnloadedEvent, (ev) => this.projectUnloaded());
         eventDispatcher.subscribeToEvent(ClientExtensionEventNames.ResourceDeletedEvent, (ev) => this.deleteResource(ev));
         eventDispatcher.subscribeToEvent(ClientExtensionEventNames.ResourceDeletedEvent, (ev) => this.deleteResource(ev));
         eventDispatcher.subscribeToEvent(ClientExtensionEventNames.CodeSavedEvent, (ev) => this.saveCode(ev));
         eventDispatcher.subscribeToEvent(ClientExtensionEventNames.CodeSavedEvent, (ev) => this.saveCode(ev));
+        eventDispatcher.subscribeToEvent(ClientExtensionEventNames.PreferencesChangedEvent, (ev) => this.preferencesChanged());
     }
     }
 
 
     /**
     /**
@@ -189,4 +202,51 @@ export class ExtensionServiceRegistry extends ServiceRegistry<Editor.ClientExten
             }
             }
         });
         });
     }
     }
+
+    /**
+     * Called when preferences changes
+     * @param  {Editor.EditorEvents.PreferencesChangedEvent} ev
+     */
+    preferencesChanged() {
+        this.registeredServices.forEach((service) => {
+            // Notify services that the project has been unloaded
+            try {
+                if (service.preferencesChanged) {
+                    service.preferencesChanged();
+                }
+            } catch (e) {
+                alert(`Extension Error:\n Error detected in extension ${service.name}\n \n ${e.stack}`);
+            }
+        });
+    }
+
+    /**
+     * Returns the Host Interop module
+     * @return {Editor.ClientExtensions.HostInterop}
+     */
+    getHostInterop(): Editor.ClientExtensions.HostInterop {
+        return HostInteropType.getInstance();
+    }
+
+
+    /**
+     * Return a preference value or the provided default from the user settings file
+     * @param  {string} gorupName name of the group the preference lives under
+     * @param  {string} preferenceName name of the preference to retrieve
+     * @param  {number | boolean | string} defaultValue value to return if pref doesn't exist
+     * @return {number|boolean|string}
+     */
+    getUserPreference(groupName: string, preferenceName: string, defaultValue?: number | boolean | string): number | boolean | string {
+        if (this.userPreferences) {
+            let prefs = this.userPreferences[groupName];
+            if (prefs) {
+                return prefs[groupName][preferenceName] || defaultValue;
+            }
+        }
+
+        // if all else fails
+        return defaultValue;
+    }
+
+
 }
 }

+ 4 - 13
Script/AtomicWebViewEditor/clientExtensions/ServiceLocator.ts

@@ -19,7 +19,6 @@
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 // THE SOFTWARE.
 // THE SOFTWARE.
 //
 //
-import HostInteropType from "../interop";
 import * as ClientExtensionServices from "./ClientExtensionServices";
 import * as ClientExtensionServices from "./ClientExtensionServices";
 
 
 // Initialize and configure the extensions
 // Initialize and configure the extensions
@@ -34,24 +33,15 @@ import tbExtension from "./languageExtensions/turbobadger/TurboBadgerLanguageExt
 export class ClientServiceLocatorType implements Editor.ClientExtensions.ClientServiceLocator {
 export class ClientServiceLocatorType implements Editor.ClientExtensions.ClientServiceLocator {
 
 
     constructor() {
     constructor() {
-        this.services = new ClientExtensionServices.ExtensionServiceRegistry();
-        this.services.subscribeToEvents(this);
+        this.clientServices = new ClientExtensionServices.WebViewServicesProvider();
+        this.clientServices.subscribeToEvents(this);
     }
     }
 
 
-    private services: ClientExtensionServices.ExtensionServiceRegistry;
     private eventDispatcher: Editor.Extensions.EventDispatcher = new ClientExtensionServices.EventDispatcher();
     private eventDispatcher: Editor.Extensions.EventDispatcher = new ClientExtensionServices.EventDispatcher();
 
 
-    /**
-     * Returns the Host Interop module
-     * @return {Editor.ClientExtensions.HostInterop}
-     */
-    getHostInterop(): Editor.ClientExtensions.HostInterop {
-        return HostInteropType.getInstance();
-    }
-
+    clientServices: ClientExtensionServices.WebViewServicesProvider;
     loadService(service: Editor.ClientExtensions.ClientEditorService) {
     loadService(service: Editor.ClientExtensions.ClientEditorService) {
         try {
         try {
-            this.services.register(service);
             service.initialize(this);
             service.initialize(this);
         } catch (e) {
         } catch (e) {
             alert(`Extension Error:\n Error detected in extension ${service.name}\n \n ${e.stack}`);
             alert(`Extension Error:\n Error detected in extension ${service.name}\n \n ${e.stack}`);
@@ -79,6 +69,7 @@ export class ClientServiceLocatorType implements Editor.ClientExtensions.ClientS
             this.eventDispatcher.subscribeToEvent(eventType, callback);
             this.eventDispatcher.subscribeToEvent(eventType, callback);
         }
         }
     }
     }
+
 }
 }
 
 
 // Singleton service locator that can be referenced
 // Singleton service locator that can be referenced

+ 2 - 1
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/javascript/JavascriptLanguageExtension.ts

@@ -23,7 +23,7 @@
 /**
 /**
  * Resource extension that handles configuring the editor for Javascript
  * Resource extension that handles configuring the editor for Javascript
  */
  */
-export default class JavascriptLanguageExtension implements Editor.ClientExtensions.WebViewService {
+export default class JavascriptLanguageExtension implements Editor.ClientExtensions.WebViewServiceEventListener {
     name: string = "ClientJavascriptLanguageExtension";
     name: string = "ClientJavascriptLanguageExtension";
     description: string = "Javascript language services for the editor.";
     description: string = "Javascript language services for the editor.";
 
 
@@ -36,6 +36,7 @@ export default class JavascriptLanguageExtension implements Editor.ClientExtensi
     initialize(serviceLocator: Editor.ClientExtensions.ClientServiceLocator) {
     initialize(serviceLocator: Editor.ClientExtensions.ClientServiceLocator) {
         // initialize the language service
         // initialize the language service
         this.serviceLocator = serviceLocator;
         this.serviceLocator = serviceLocator;
+        serviceLocator.clientServices.register(this);
     }
     }
 
 
     /**
     /**

+ 2 - 1
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/turbobadger/TurboBadgerLanguageExtension.ts

@@ -23,7 +23,7 @@
 /**
 /**
  * Resource extension that handles configuring the editor for Javascript
  * Resource extension that handles configuring the editor for Javascript
  */
  */
-export default class TurboBadgerLanguageExtension implements Editor.ClientExtensions.WebViewService {
+export default class TurboBadgerLanguageExtension implements Editor.ClientExtensions.WebViewServiceEventListener {
     name: string = "ClientTurboBadgerLanguageExtension";
     name: string = "ClientTurboBadgerLanguageExtension";
     description: string = "TurboBadger language services for the editor.";
     description: string = "TurboBadger language services for the editor.";
 
 
@@ -36,6 +36,7 @@ export default class TurboBadgerLanguageExtension implements Editor.ClientExtens
     initialize(serviceLocator: Editor.ClientExtensions.ClientServiceLocator) {
     initialize(serviceLocator: Editor.ClientExtensions.ClientServiceLocator) {
         // initialize the language service
         // initialize the language service
         this.serviceLocator = serviceLocator;
         this.serviceLocator = serviceLocator;
+        serviceLocator.clientServices.register(this);
     }
     }
 
 
     /**
     /**

+ 11 - 1
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/TypescriptLanguageExtension.ts

@@ -28,7 +28,7 @@ import ClientExtensionEventNames from "../../ClientExtensionEventNames";
 /**
 /**
  * Resource extension that handles compiling or transpling typescript on file save.
  * Resource extension that handles compiling or transpling typescript on file save.
  */
  */
-export default class TypescriptLanguageExtension implements Editor.ClientExtensions.WebViewService {
+export default class TypescriptLanguageExtension implements Editor.ClientExtensions.WebViewServiceEventListener {
     name: string = "ClientTypescriptLanguageExtension";
     name: string = "ClientTypescriptLanguageExtension";
     description: string = "This extension handles typescript language features such as completion, compilation, etc.";
     description: string = "This extension handles typescript language features such as completion, compilation, etc.";
 
 
@@ -57,6 +57,7 @@ export default class TypescriptLanguageExtension implements Editor.ClientExtensi
     initialize(serviceLocator: Editor.ClientExtensions.ClientServiceLocator) {
     initialize(serviceLocator: Editor.ClientExtensions.ClientServiceLocator) {
         // initialize the language service
         // initialize the language service
         this.serviceLocator = serviceLocator;
         this.serviceLocator = serviceLocator;
+        serviceLocator.clientServices.register(this);
     }
     }
 
 
     /**
     /**
@@ -300,4 +301,13 @@ export default class TypescriptLanguageExtension implements Editor.ClientExtensi
             this.worker.port.postMessage(message);
             this.worker.port.postMessage(message);
         }
         }
     }
     }
+
+    /**
+     * Called when the user preferences have been changed (or initially loaded)
+     * @return {[type]}
+     */
+    preferencesChanged() {
+        // Stub function for now
+        this.serviceLocator.clientServices.getUserPreference("TypescriptLanguageExtension", "CompileOnSave", true);
+    }
 }
 }

+ 9 - 0
Script/AtomicWebViewEditor/editor/editorCommands.ts

@@ -123,3 +123,12 @@ export function codeSaved(path: string, fileExt: string, contents: string) {
     };
     };
     serviceLocator.sendEvent(ClientExtensionEventNames.CodeSavedEvent, data);
     serviceLocator.sendEvent(ClientExtensionEventNames.CodeSavedEvent, data);
 }
 }
+
+/**
+ * Called when new preferences are available (or initially with current prefs)
+ * @param  {any} prefs
+ */
+export function loadPreferences(prefs: any) {
+    serviceLocator.clientServices.setPreferences(prefs);
+    serviceLocator.sendEvent(ClientExtensionEventNames.PreferencesChangedEvent, null);
+}

+ 21 - 0
Script/AtomicWebViewEditor/interop.ts

@@ -76,6 +76,7 @@ export default class HostInteropType {
     static EDITOR_SAVE_FILE = "editorSaveFile";
     static EDITOR_SAVE_FILE = "editorSaveFile";
     static EDITOR_LOAD_COMPLETE = "editorLoadComplete";
     static EDITOR_LOAD_COMPLETE = "editorLoadComplete";
     static EDITOR_CHANGE = "editorChange";
     static EDITOR_CHANGE = "editorChange";
+    static EDITOR_GET_USER_PREFS = "editorGetUserPrefs";
 
 
     constructor() {
     constructor() {
         // Set up the window object so the host can call into it
         // Set up the window object so the host can call into it
@@ -85,6 +86,7 @@ export default class HostInteropType {
         window.HOST_projectUnloaded = this.projectUnloaded.bind(this);
         window.HOST_projectUnloaded = this.projectUnloaded.bind(this);
         window.HOST_resourceRenamed = this.resourceRenamed.bind(this);
         window.HOST_resourceRenamed = this.resourceRenamed.bind(this);
         window.HOST_resourceDeleted = this.resourceDeleted.bind(this);
         window.HOST_resourceDeleted = this.resourceDeleted.bind(this);
+        window.HOST_loadPreferences = this.loadPreferences.bind(this);
     }
     }
 
 
     /**
     /**
@@ -106,6 +108,9 @@ export default class HostInteropType {
         // get the code
         // get the code
         this.getResource(codeUrl).then((src: string) => {
         this.getResource(codeUrl).then((src: string) => {
             editorCommands.loadCodeIntoEditor(src, filename, fileExt);
             editorCommands.loadCodeIntoEditor(src, filename, fileExt);
+            atomicQueryPromise({
+                message: HostInteropType.EDITOR_GET_USER_PREFS
+            });
         }).catch((e: Editor.ClientExtensions.AtomicErrorMessage) => {
         }).catch((e: Editor.ClientExtensions.AtomicErrorMessage) => {
             console.log("Error loading code: " + e.error_message);
             console.log("Error loading code: " + e.error_message);
         });
         });
@@ -213,6 +218,22 @@ export default class HostInteropType {
     resourceDeleted(path: string) {
     resourceDeleted(path: string) {
         editorCommands.resourceDeleted(path);
         editorCommands.resourceDeleted(path);
     }
     }
+
+    /**
+     * Host is notifying client that there are preferences to load and passing us the path
+     * of the prefs.
+     * @param  {string} prefUrl
+     */
+    loadPreferences(prefUrl: string) {
+        console.log("Load preferences called for :" + prefUrl);
+        // load prefs
+        this.getResource(prefUrl).then((prefsJson: string) => {
+            let prefs = JSON.parse(prefsJson);
+            editorCommands.loadPreferences(prefs);
+        }).catch((e: Editor.ClientExtensions.AtomicErrorMessage) => {
+            console.log("Error loading preferences: " + e.error_message);
+        });
+    }
 }
 }
 
 
 HostInteropType.getInstance().editorLoaded();
 HostInteropType.getInstance().editorLoaded();

+ 1 - 0
Script/AtomicWebViewEditor/typings/WindowExt.d.ts

@@ -31,4 +31,5 @@ interface Window {
     HOST_projectUnloaded: () => void;
     HOST_projectUnloaded: () => void;
     HOST_resourceRenamed: (path: string, newPath: string) => void;
     HOST_resourceRenamed: (path: string, newPath: string) => void;
     HOST_resourceDeleted: (path: string) => void;
     HOST_resourceDeleted: (path: string) => void;
+    HOST_loadPreferences: (path: string) => void;
 }
 }

+ 1 - 1
Script/Packages/Editor/Editor.json

@@ -1,6 +1,6 @@
 {
 {
 	"name" : "Editor",
 	"name" : "Editor",
-	"includes" : ["<Atomic/Graphics/DebugRenderer.h>"],
+	"includes" : ["<Atomic/Graphics/DebugRenderer.h>", "<AtomicWebView/UIWebView.h>"],
 	"sources" : ["Source/AtomicEditor/Application", "Source/AtomicEditor/Utils",
 	"sources" : ["Source/AtomicEditor/Application", "Source/AtomicEditor/Utils",
 							 "Source/AtomicEditor/EditorMode", "Source/AtomicEditor/PlayerMode",
 							 "Source/AtomicEditor/EditorMode", "Source/AtomicEditor/PlayerMode",
 							 "Source/AtomicEditor/Editors", "Source/AtomicEditor/Editors/SceneEditor3D",
 							 "Source/AtomicEditor/Editors", "Source/AtomicEditor/Editors/SceneEditor3D",

+ 1 - 1
Script/Packages/Editor/Package.json

@@ -2,7 +2,7 @@
 {
 {
   "name" : "Editor",
   "name" : "Editor",
   "namespace" : "AtomicEditor",
   "namespace" : "AtomicEditor",
-  "dependencies" : ["Script/Packages/Atomic"],
+  "dependencies" : ["Script/Packages/WebView"],
   "modules" : ["Editor"],
   "modules" : ["Editor"],
   "platforms" : ["WINDOWS", "MACOSX", "LINUX"]
   "platforms" : ["WINDOWS", "MACOSX", "LINUX"]
 }
 }

+ 55 - 3
Script/TypeScript/EditorWork.d.ts

@@ -140,6 +140,12 @@ declare module Editor.EditorEvents {
         serializable: Atomic.Serializable;
         serializable: Atomic.Serializable;
 
 
     }
     }
+
+    export interface PreferencesChangedEvent {
+
+        preferences: any;
+
+    }
 }
 }
 
 
 declare module Editor.Extensions {
 declare module Editor.Extensions {
@@ -256,7 +262,27 @@ declare module Editor.HostExtensions {
         projectLoaded?(ev: EditorEvents.LoadProjectEvent);
         projectLoaded?(ev: EditorEvents.LoadProjectEvent);
         playerStarted?();
         playerStarted?();
     }
     }
-    export interface ProjectServicesProvider extends Editor.Extensions.ServicesProvider<ProjectServicesEventListener> { }
+    export interface ProjectServicesProvider extends Editor.Extensions.ServicesProvider<ProjectServicesEventListener> {
+
+        /**
+         * Return a preference value or the provided default from the user settings file
+         * @param  {string} extensionName name of the extension the preference lives under
+         * @param  {string} preferenceName name of the preference to retrieve
+         * @param  {number | boolean | string} defaultValue value to return if pref doesn't exist
+         * @return {number|boolean|string}
+         */
+        getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: number): number;
+        getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: string): string;
+        getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: boolean): boolean;
+
+        /**
+         * Sets a user preference value in the user settings file
+         * @param  {string} extensionName name of the extension the preference lives under
+         * @param  {string} preferenceName name of the preference to set
+         * @param  {number | boolean | string} value value to set
+         */
+        setUserPreference(extensionName: string, preferenceName: string, value: number | boolean | string);
+    }
 
 
     export interface SceneServicesEventListener extends Editor.Extensions.ServiceEventListener {
     export interface SceneServicesEventListener extends Editor.Extensions.ServiceEventListener {
         activeSceneEditorChanged?(ev: EditorEvents.ActiveSceneEditorChangeEvent);
         activeSceneEditorChanged?(ev: EditorEvents.ActiveSceneEditorChangeEvent);
@@ -294,7 +320,11 @@ declare module Editor.ClientExtensions {
      * or by the editor itself.
      * or by the editor itself.
      */
      */
     export interface ClientServiceLocator extends Editor.Extensions.ServiceLoader {
     export interface ClientServiceLocator extends Editor.Extensions.ServiceLoader {
-        getHostInterop(): HostInterop;
+        /**
+         * Exposed services
+         * @type {WebViewServicesProvider}
+         */
+        clientServices: WebViewServicesProvider;
     }
     }
 
 
     export interface ClientEditorService extends Editor.Extensions.EditorServiceExtension {
     export interface ClientEditorService extends Editor.Extensions.EditorServiceExtension {
@@ -304,13 +334,35 @@ declare module Editor.ClientExtensions {
         initialize(serviceLocator: ClientServiceLocator);
         initialize(serviceLocator: ClientServiceLocator);
     }
     }
 
 
-    export interface WebViewService extends Editor.Extensions.EditorServiceExtension {
+    export interface WebViewServiceEventListener extends Editor.Extensions.EditorServiceExtension {
         configureEditor?(ev: EditorEvents.EditorFileEvent);
         configureEditor?(ev: EditorEvents.EditorFileEvent);
         codeLoaded?(ev: EditorEvents.CodeLoadedEvent);
         codeLoaded?(ev: EditorEvents.CodeLoadedEvent);
         save?(ev: EditorEvents.CodeSavedEvent);
         save?(ev: EditorEvents.CodeSavedEvent);
         delete?(ev: EditorEvents.DeleteResourceEvent);
         delete?(ev: EditorEvents.DeleteResourceEvent);
         rename?(ev: EditorEvents.RenameResourceEvent);
         rename?(ev: EditorEvents.RenameResourceEvent);
         projectUnloaded?();
         projectUnloaded?();
+        preferencesChanged?();
+    }
+
+    /**
+     * Available methods exposed to client services
+     */
+    export interface WebViewServicesProvider extends Editor.Extensions.ServicesProvider<WebViewServiceEventListener> {
+
+        /**
+         * Get a reference to the interop to talk to the host
+         * @return {HostInterop}
+         */
+        getHostInterop(): HostInterop;
+
+        /**
+         * Return a preference value or the provided default from the user settings file
+         * @param  {string} extensionName name of the extension the preference lives under
+         * @param  {string} preferenceName name of the preference to retrieve
+         * @param  {number | boolean | string} defaultValue value to return if pref doesn't exist
+         * @return {number|boolean|string}
+         */
+        getUserPreference(extensionName: string, preferenceName: string, defaultValue?: number | boolean | string): number | boolean | string;
     }
     }
 
 
     export interface AtomicErrorMessage {
     export interface AtomicErrorMessage {

+ 1 - 2
Source/Atomic/Resource/ResourceEvents.h

@@ -74,7 +74,7 @@ EVENT(E_RESOURCEBACKGROUNDLOADED, ResourceBackgroundLoaded)
     PARAM(P_SUCCESS, Success);                      // bool
     PARAM(P_SUCCESS, Success);                      // bool
     PARAM(P_RESOURCE, Resource);                    // Resource pointer
     PARAM(P_RESOURCE, Resource);                    // Resource pointer
 }
 }
-    
+
 /// Resource was renamed
 /// Resource was renamed
 EVENT(E_RENAMERESOURCENOTIFICATION, RenameResourceNotification)
 EVENT(E_RENAMERESOURCENOTIFICATION, RenameResourceNotification)
 {
 {
@@ -95,7 +95,6 @@ EVENT(E_PROJECTUNLOADEDNOTIFICATION, ProjecUnloadedNotification)
 {
 {
 }
 }
     
     
-
 /// Language changed.
 /// Language changed.
 EVENT(E_CHANGELANGUAGE, ChangeLanguage)
 EVENT(E_CHANGELANGUAGE, ChangeLanguage)
 {
 {

+ 24 - 11
Source/AtomicEditor/Editors/JSResourceEditor.cpp

@@ -33,6 +33,8 @@
 #include <AtomicJS/Javascript/JSVM.h>
 #include <AtomicJS/Javascript/JSVM.h>
 
 
 #include <ToolCore/ToolEnvironment.h>
 #include <ToolCore/ToolEnvironment.h>
+#include <ToolCore/ToolSystem.h>
+#include <ToolCore/Project/Project.h>
 
 
 #include <AtomicWebView/WebViewEvents.h>
 #include <AtomicWebView/WebViewEvents.h>
 #include <AtomicWebView/UIWebView.h>
 #include <AtomicWebView/UIWebView.h>
@@ -92,12 +94,12 @@ JSResourceEditor ::JSResourceEditor(Context* context, const String &fullpath, UI
     c->AddChild(webView_->GetInternalWidget());
     c->AddChild(webView_->GetInternalWidget());
 
 
 }
 }
-    
+
 JSResourceEditor::~JSResourceEditor()
 JSResourceEditor::~JSResourceEditor()
 {
 {
 
 
 }
 }
-   
+
 String getNormalizedPath(const String& path)
 String getNormalizedPath(const String& path)
 {
 {
     // Full path is the fully qualified path from the root of the filesystem.  In order
     // Full path is the fully qualified path from the root of the filesystem.  In order
@@ -107,34 +109,34 @@ String getNormalizedPath(const String& path)
     const String& RESOURCES_MARKER = "resources/";
     const String& RESOURCES_MARKER = "resources/";
     return path.SubstringUTF8(path.ToLower().Find(RESOURCES_MARKER));
     return path.SubstringUTF8(path.ToLower().Find(RESOURCES_MARKER));
 }
 }
-    
+
 void JSResourceEditor::HandleRenameResourceNotification(StringHash eventType, VariantMap& eventData)
 void JSResourceEditor::HandleRenameResourceNotification(StringHash eventType, VariantMap& eventData)
 {
 {
     using namespace RenameResourceNotification;
     using namespace RenameResourceNotification;
     const String& newPath = eventData[P_NEWRESOURCEPATH].GetString();
     const String& newPath = eventData[P_NEWRESOURCEPATH].GetString();
     const String& path = eventData[P_RESOURCEPATH].GetString();
     const String& path = eventData[P_RESOURCEPATH].GetString();
-    
+
     webClient_->ExecuteJavaScript(ToString("HOST_resourceRenamed(\"%s\",\"%s\");", getNormalizedPath(path).CString(), getNormalizedPath(newPath).CString()));
     webClient_->ExecuteJavaScript(ToString("HOST_resourceRenamed(\"%s\",\"%s\");", getNormalizedPath(path).CString(), getNormalizedPath(newPath).CString()));
-    
+
     if (fullpath_.Compare(path) == 0) {
     if (fullpath_.Compare(path) == 0) {
         fullpath_ = newPath;
         fullpath_ = newPath;
         SetModified(modified_);
         SetModified(modified_);
     }
     }
 }
 }
-    
+
 void JSResourceEditor::HandleDeleteResourceNotification(StringHash eventType, VariantMap& eventData)
 void JSResourceEditor::HandleDeleteResourceNotification(StringHash eventType, VariantMap& eventData)
 {
 {
     using namespace DeleteResourceNotification;
     using namespace DeleteResourceNotification;
     const String& path = eventData[P_RESOURCEPATH].GetString();
     const String& path = eventData[P_RESOURCEPATH].GetString();
-    
+
     webClient_->ExecuteJavaScript(ToString("HOST_resourceDeleted(\"%s\");", getNormalizedPath(path).CString()));
     webClient_->ExecuteJavaScript(ToString("HOST_resourceDeleted(\"%s\");", getNormalizedPath(path).CString()));
 }
 }
-    
+
 void JSResourceEditor::HandleProjectUnloadedNotification(StringHash eventType, VariantMap& eventData)
 void JSResourceEditor::HandleProjectUnloadedNotification(StringHash eventType, VariantMap& eventData)
 {
 {
     webClient_->ExecuteJavaScript("HOST_projectUnloaded();");
     webClient_->ExecuteJavaScript("HOST_projectUnloaded();");
 }
 }
-    
+
 void JSResourceEditor::HandleWebViewLoadEnd(StringHash eventType, VariantMap& eventData)
 void JSResourceEditor::HandleWebViewLoadEnd(StringHash eventType, VariantMap& eventData)
 {
 {
     // need to wait until we get an editor load complete message since we could
     // need to wait until we get an editor load complete message since we could
@@ -150,9 +152,10 @@ void JSResourceEditor::HandleWebMessage(StringHash eventType, VariantMap& eventD
     const String& EDITOR_SAVE_CODE = "editorSaveCode";
     const String& EDITOR_SAVE_CODE = "editorSaveCode";
     const String& EDITOR_SAVE_FILE = "editorSaveFile";
     const String& EDITOR_SAVE_FILE = "editorSaveFile";
     const String& EDITOR_LOAD_COMPLETE = "editorLoadComplete";
     const String& EDITOR_LOAD_COMPLETE = "editorLoadComplete";
-    
+    const String& EDITOR_GET_USER_PREFS = "editorGetUserPrefs";
+
     String normalizedPath = getNormalizedPath(fullpath_);
     String normalizedPath = getNormalizedPath(fullpath_);
-    
+
     WebMessageHandler* handler = static_cast<WebMessageHandler*>(eventData[P_HANDLER].GetPtr());
     WebMessageHandler* handler = static_cast<WebMessageHandler*>(eventData[P_HANDLER].GetPtr());
 
 
     if (request == EDITOR_CHANGE)
     if (request == EDITOR_CHANGE)
@@ -188,6 +191,16 @@ void JSResourceEditor::HandleWebMessage(StringHash eventType, VariantMap& eventD
                 file.Write((void*) code.CString(), code.Length());
                 file.Write((void*) code.CString(), code.Length());
                 file.Close();
                 file.Close();
             }
             }
+            else if (message == EDITOR_GET_USER_PREFS)
+            {
+                ToolSystem* tsys = GetSubsystem<ToolSystem>();
+                Project* proj = tsys->GetProject();
+                FileSystem* fileSystem = GetSubsystem<FileSystem>();
+                if (fileSystem->FileExists(proj->GetUserPrefsFullPath()))
+                {
+                    webClient_->ExecuteJavaScript(ToString("HOST_loadPreferences(\"atomic://%s\");", proj->GetUserPrefsFullPath().CString()));
+                }
+            }
         }
         }
     }
     }
 
 

+ 4 - 1
Source/AtomicEditor/Editors/JSResourceEditor.h

@@ -47,6 +47,9 @@ public:
 
 
     virtual ~JSResourceEditor();
     virtual ~JSResourceEditor();
 
 
+    /// Get the editor's UIWebView
+    UIWebView* GetWebView() const { return webView_; }
+
     bool OnEvent(const TBWidgetEvent &ev);
     bool OnEvent(const TBWidgetEvent &ev);
 
 
     bool FindText(const String& findText, unsigned flags);
     bool FindText(const String& findText, unsigned flags);
@@ -60,7 +63,7 @@ public:
     void SetFocus();
     void SetFocus();
 
 
     bool Save();
     bool Save();
-    
+
 private:
 private:
 
 
     void HandleWebViewLoadEnd(StringHash eventType, VariantMap& eventData);
     void HandleWebViewLoadEnd(StringHash eventType, VariantMap& eventData);

+ 9 - 1
Source/ToolCore/Project/ProjectUserPrefs.cpp

@@ -136,9 +136,17 @@ bool ProjectUserPrefs::Load(const String& path)
 
 
 void ProjectUserPrefs::Save(const String& path)
 void ProjectUserPrefs::Save(const String& path)
 {
 {
-
     SharedPtr<JSONFile> jsonFile(new JSONFile(context_));
     SharedPtr<JSONFile> jsonFile(new JSONFile(context_));
 
 
+    // Load existing prefs file if it exists and just update editor settings.  The
+    // file may contain other settings we are not aware of so we shouldn't overwrite those
+    SharedPtr<File> oldFile(new File(context_, path));
+    if (oldFile->IsOpen())
+    {
+        bool result = jsonFile->Load(*oldFile);
+        oldFile->Close();
+    }
+
     JSONValue& root = jsonFile->GetRoot();
     JSONValue& root = jsonFile->GetRoot();
 
 
     SharedPtr<File> file(new File(context_, path, FILE_WRITE));
     SharedPtr<File> file(new File(context_, path, FILE_WRITE));