changeset 41:85577160a0d4

Many changes: implement global application preferences on devices etc.; extended editor window support with Control mapping features for now
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Sat, 21 Jun 2014 23:32:33 +0100
parents 50e4859d9769
children 1526d2fbe01e
files Builds/Linux/Makefile Builds/Linux32/Makefile Source/Display/KeyboardTesterDisplay.cpp Source/GUI/ControlWindowMainComponent.cpp Source/GUI/GraphicsDisplayWindow.h Source/GUI/MainWindow.cpp Source/GUI/MainWindow.h Source/GUI/MappingEditorComponent.h Source/GUI/MappingExtendedEditorWindow.h Source/GUI/MappingListComponent.cpp Source/GUI/MappingListComponent.h Source/GUI/MappingListItem.cpp Source/GUI/MappingListItem.h Source/GUI/PreferencesComponent.cpp Source/GUI/PreferencesComponent.h Source/GUI/PreferencesWindow.h Source/Main.cpp Source/MainApplicationController.cpp Source/MainApplicationController.h Source/Mappings/Control/TouchkeyControlMapping.cpp Source/Mappings/Control/TouchkeyControlMappingExtendedEditor.cpp Source/Mappings/Control/TouchkeyControlMappingExtendedEditor.h Source/Mappings/Control/TouchkeyControlMappingFactory.cpp Source/Mappings/Control/TouchkeyControlMappingFactory.h Source/Mappings/TouchkeyBaseMappingFactory.h Source/TouchKeys/MidiInputController.cpp Source/TouchKeys/MidiInputController.h Source/TouchKeys/MidiKeyboardSegment.cpp Source/TouchKeys/MidiKeyboardSegment.h Source/TouchKeys/MidiOutputController.cpp Source/TouchKeys/MidiOutputController.h Source/TouchKeys/OscMidiConverter.cpp Source/TouchKeys/OscMidiConverter.h TouchKeys.jucer
diffstat 34 files changed, 2386 insertions(+), 175 deletions(-) [+]
line wrap: on
line diff
--- a/Builds/Linux/Makefile	Fri Mar 21 23:13:19 2014 +0000
+++ b/Builds/Linux/Makefile	Sat Jun 21 23:32:33 2014 +0100
@@ -49,6 +49,7 @@
 endif
 
 OBJECTS := \
+  $(OBJDIR)/PreferencesComponent_8c094f62.o \
   $(OBJDIR)/MainWindow_ca618186.o \
   $(OBJDIR)/KeyboardZoneComponent_fd0d7a77.o \
   $(OBJDIR)/ControlWindowMainComponent_c67f9014.o \
@@ -68,6 +69,7 @@
   $(OBJDIR)/TouchkeyMultiFingerTriggerMappingFactory_e811112a.o \
   $(OBJDIR)/TouchkeyKeyDivisionMapping_cea38eb0.o \
   $(OBJDIR)/TouchkeyKeyDivisionMappingFactory_33b42a44.o \
+  $(OBJDIR)/TouchkeyControlMappingExtendedEditor_bb11f4.o \
   $(OBJDIR)/TouchkeyControlMappingShortEditor_993f27a5.o \
   $(OBJDIR)/TouchkeyControlMapping_1e638c8e.o \
   $(OBJDIR)/TouchkeyControlMappingFactory_1db276a6.o \
@@ -134,6 +136,11 @@
 	@echo Stripping TouchKeys
 	-@strip --strip-unneeded $(OUTDIR)/$(TARGET)
 
+$(OBJDIR)/PreferencesComponent_8c094f62.o: ../../Source/GUI/PreferencesComponent.cpp
+	-@mkdir -p $(OBJDIR)
+	@echo "Compiling PreferencesComponent.cpp"
+	@$(CXX) $(CXXFLAGS) -o "$@" -c "$<"
+
 $(OBJDIR)/MainWindow_ca618186.o: ../../Source/GUI/MainWindow.cpp
 	-@mkdir -p $(OBJDIR)
 	@echo "Compiling MainWindow.cpp"
@@ -229,6 +236,11 @@
 	@echo "Compiling TouchkeyKeyDivisionMappingFactory.cpp"
 	@$(CXX) $(CXXFLAGS) -o "$@" -c "$<"
 
+$(OBJDIR)/TouchkeyControlMappingExtendedEditor_bb11f4.o: ../../Source/Mappings/Control/TouchkeyControlMappingExtendedEditor.cpp
+	-@mkdir -p $(OBJDIR)
+	@echo "Compiling TouchkeyControlMappingExtendedEditor.cpp"
+	@$(CXX) $(CXXFLAGS) -o "$@" -c "$<"
+
 $(OBJDIR)/TouchkeyControlMappingShortEditor_993f27a5.o: ../../Source/Mappings/Control/TouchkeyControlMappingShortEditor.cpp
 	-@mkdir -p $(OBJDIR)
 	@echo "Compiling TouchkeyControlMappingShortEditor.cpp"
--- a/Builds/Linux32/Makefile	Fri Mar 21 23:13:19 2014 +0000
+++ b/Builds/Linux32/Makefile	Sat Jun 21 23:32:33 2014 +0100
@@ -49,6 +49,7 @@
 endif
 
 OBJECTS := \
+  $(OBJDIR)/PreferencesComponent_8c094f62.o \
   $(OBJDIR)/MainWindow_ca618186.o \
   $(OBJDIR)/KeyboardZoneComponent_fd0d7a77.o \
   $(OBJDIR)/ControlWindowMainComponent_c67f9014.o \
@@ -68,6 +69,7 @@
   $(OBJDIR)/TouchkeyMultiFingerTriggerMappingFactory_e811112a.o \
   $(OBJDIR)/TouchkeyKeyDivisionMapping_cea38eb0.o \
   $(OBJDIR)/TouchkeyKeyDivisionMappingFactory_33b42a44.o \
+  $(OBJDIR)/TouchkeyControlMappingExtendedEditor_bb11f4.o \
   $(OBJDIR)/TouchkeyControlMappingShortEditor_993f27a5.o \
   $(OBJDIR)/TouchkeyControlMapping_1e638c8e.o \
   $(OBJDIR)/TouchkeyControlMappingFactory_1db276a6.o \
@@ -134,6 +136,11 @@
 	@echo Stripping TouchKeys
 	-@strip --strip-unneeded $(OUTDIR)/$(TARGET)
 
+$(OBJDIR)/PreferencesComponent_8c094f62.o: ../../Source/GUI/PreferencesComponent.cpp
+	-@mkdir -p $(OBJDIR)
+	@echo "Compiling PreferencesComponent.cpp"
+	@$(CXX) $(CXXFLAGS) -o "$@" -c "$<"
+
 $(OBJDIR)/MainWindow_ca618186.o: ../../Source/GUI/MainWindow.cpp
 	-@mkdir -p $(OBJDIR)
 	@echo "Compiling MainWindow.cpp"
@@ -229,6 +236,11 @@
 	@echo "Compiling TouchkeyKeyDivisionMappingFactory.cpp"
 	@$(CXX) $(CXXFLAGS) -o "$@" -c "$<"
 
+$(OBJDIR)/TouchkeyControlMappingExtendedEditor_bb11f4.o: ../../Source/Mappings/Control/TouchkeyControlMappingExtendedEditor.cpp
+	-@mkdir -p $(OBJDIR)
+	@echo "Compiling TouchkeyControlMappingExtendedEditor.cpp"
+	@$(CXX) $(CXXFLAGS) -o "$@" -c "$<"
+
 $(OBJDIR)/TouchkeyControlMappingShortEditor_993f27a5.o: ../../Source/Mappings/Control/TouchkeyControlMappingShortEditor.cpp
 	-@mkdir -p $(OBJDIR)
 	@echo "Compiling TouchkeyControlMappingShortEditor.cpp"
--- a/Source/Display/KeyboardTesterDisplay.cpp	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/Display/KeyboardTesterDisplay.cpp	Sat Jun 21 23:32:33 2014 +0100
@@ -88,8 +88,6 @@
 	
 	// Restore to the original location we used when drawing the keys
 	glPopMatrix();
-    
-	needsUpdate_ = false;    
 	glFlush();
 }
 
@@ -119,7 +117,7 @@
     else
         keySensorActive_[key] &= ~(1 << sensor);
     currentlyActiveKey_ = key;
-    needsUpdate_ = true;
+    tellCanvasToRepaint();
     
     if(allSensorsGood(currentlyActiveKey_)) {
         controller_.touchkeySensorTestSetKey(key + 1);
--- a/Source/GUI/ControlWindowMainComponent.cpp	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/GUI/ControlWindowMainComponent.cpp	Sat Jun 21 23:32:33 2014 +0100
@@ -554,9 +554,9 @@
         counter++;
     }
 
-    if(!lastSelectedDeviceExists)
+    if(!lastSelectedDeviceExists && lastSelectedMidiInputID_ >= 0)
         controller_->disablePrimaryMIDIInputPort();
-    if(!lastSelectedAuxDeviceExists)
+    if(!lastSelectedAuxDeviceExists && lastSelectedMidiAuxInputID_ >= 0)
         controller_->disableAllMIDIInputPorts(true);
 }
 
--- a/Source/GUI/GraphicsDisplayWindow.h	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/GUI/GraphicsDisplayWindow.h	Sat Jun 21 23:32:33 2014 +0100
@@ -51,8 +51,8 @@
         getConstrainer()->setFixedAspectRatio(display_.keyboardAspectRatio());
         setBoundsConstrained(getBounds());
         
-        // Show window
-        setVisible(true);
+        // Don't show window yet
+        setVisible(false);
     }
 
     ~GraphicsDisplayWindow()
--- a/Source/GUI/MainWindow.cpp	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/GUI/MainWindow.cpp	Sat Jun 21 23:32:33 2014 +0100
@@ -127,6 +127,8 @@
 #ifdef ENABLE_TOUCHKEYS_SENSOR_TEST
         menu.addCommandItem(&commandManager_, kCommandTestTouchkeySensors);
 #endif
+        menu.addSeparator();
+        menu.addCommandItem(&commandManager_, kCommandPreferences);
     }
     else if(menuIndex == 3) { // Window
         menu.addCommandItem(&commandManager_, kCommandShowControlWindow);
@@ -170,6 +172,8 @@
 #ifdef ENABLE_TOUCHKEYS_SENSOR_TEST
         kCommandTestTouchkeySensors,
 #endif
+        kCommandPreferences,
+        
         // Window
         kCommandShowControlWindow,
         kCommandShowKeyboardWindow
@@ -279,6 +283,11 @@
             result.setTicked(controller_.touchkeySensorTestIsRunning());
             break;
 #endif
+        case kCommandPreferences:
+            result.setInfo("Preferences...", "General application preferences", controlCategory, 0);
+            result.setTicked(false);
+            result.setActive(true);
+            break;
             
         // *** Window Menu ***
         case kCommandShowControlWindow:
@@ -339,6 +348,9 @@
                 controller_.touchkeySensorTestStop();
             break;
 #endif
+        case kCommandPreferences:
+            controller_.showPreferencesWindow();
+            break;
         case kCommandShowControlWindow:
             toFront(true);
             break;
--- a/Source/GUI/MainWindow.h	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/GUI/MainWindow.h	Sat Jun 21 23:32:33 2014 +0100
@@ -52,6 +52,7 @@
         kCommandLoggingPlay,
         kCommandEnableExperimentalMappings,
         kCommandTestTouchkeySensors,
+        kCommandPreferences,
         
         // Window menu
         kCommandShowControlWindow = 0x2030,
--- a/Source/GUI/MappingEditorComponent.h	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/GUI/MappingEditorComponent.h	Sat Jun 21 23:32:33 2014 +0100
@@ -47,6 +47,11 @@
     // Method to synchronize the GUI state to the underlying
     // state of the mapping. Implemented in the subclass.
     virtual void synchronize() {}
+    
+    // Get a human-readable name for the mapping
+    // Generally, extended editors should implement this but short editors
+    // don't need to
+    virtual String getDescription() { return "Mapping"; }
 
     virtual void paint (Graphics& g)
     {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Source/GUI/MappingExtendedEditorWindow.h	Sat Jun 21 23:32:33 2014 +0100
@@ -0,0 +1,92 @@
+/*
+  ==============================================================================
+
+    MappingExtendedEditorWindow.h
+    Created: 18 Jun 2014 10:57:00am
+    Author:  Andrew McPherson
+
+  ==============================================================================
+*/
+
+#ifndef MAPPINGEXTENDEDEDITORWINDOW_H_INCLUDED
+#define MAPPINGEXTENDEDEDITORWINDOW_H_INCLUDED
+
+#include "../../JuceLibraryCode/JuceHeader.h"
+#include "../TouchKeys/MidiKeyboardSegment.h"
+#include "../Mappings/MappingFactory.h"
+#include "MappingListComponent.h"
+
+//==============================================================================
+/*
+*/
+class MappingExtendedEditorWindow    : public DocumentWindow
+{
+public:
+    MappingExtendedEditorWindow(MappingListComponent& listComponent,
+                                MidiKeyboardSegment& segment, MappingFactory& factory)
+    : DocumentWindow("", Colours::lightgrey, DocumentWindow::minimiseButton | DocumentWindow::closeButton),
+      listComponent_(listComponent), segment_(segment), factory_(factory), editor_(0)
+    {
+        setUsingNativeTitleBar(true);
+        setResizable(false, false);
+        
+        if((segment_.indexOfMappingFactory(&factory_) >= 0) && factory_.hasExtendedEditor()) {
+            editor_ = factory_.createExtendedEditor();
+        
+            // Set properties
+            setContentOwned(editor_, true);
+            
+            // Start interface in sync
+            editor_->synchronize();
+        }
+        
+        // Show window
+        setVisible(true);
+    }
+
+    ~MappingExtendedEditorWindow()
+    {
+    }
+
+    // Method used by Juce timer which we will use for periodic UI updates
+    // from the underlying system state
+    void synchronize() {
+        if(editor_ == 0)
+            return;
+        editor_->synchronize();
+        setName(editor_->getDescription());
+    }
+    
+    // Check whether this window still points to a valid mapping
+    bool isValid() {
+        if(segment_.indexOfMappingFactory(&factory_) < 0)
+            return false;
+        return true;
+    }
+    
+    // Return the factory associated with this window
+    MappingFactory *factory() {
+        return &factory_;
+    }
+    
+    void closeButtonPressed() {
+        // Close the window and delete it
+        listComponent_.closeExtendedEditorWindow(this);
+    }
+    
+    void resized() {
+        // This method is where you should set the bounds of any child
+        // components that your component contains..
+    }
+
+private:
+    MappingListComponent& listComponent_;
+    MidiKeyboardSegment& segment_;
+    MappingFactory& factory_;
+    MappingEditorComponent *editor_;
+    
+    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MappingExtendedEditorWindow)
+};
+
+
+#endif  // MAPPINGEXTENDEDEDITORWINDOW_H_INCLUDED
--- a/Source/GUI/MappingListComponent.cpp	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/GUI/MappingListComponent.cpp	Sat Jun 21 23:32:33 2014 +0100
@@ -25,6 +25,7 @@
 
 #include "../JuceLibraryCode/JuceHeader.h"
 #include "MappingListComponent.h"
+#include "MappingExtendedEditorWindow.h"
 
 //==============================================================================
 MappingListComponent::MappingListComponent() : controller_(0), keyboardSegment_(0),
@@ -38,28 +39,9 @@
     listBox_.setRowHeight(72);
 }
 
-MappingListComponent::~MappingListComponent() {}
-
-#if 0
-void MappingListComponent::paint (Graphics& g) {
-    /* This demo code just fills the component's background and
-       draws some placeholder text to get you started.
-
-       You should replace everything in this method with your own
-       drawing code..
-    */
-
-    g.fillAll (Colours::white);   // clear the background
-
-    g.setColour (Colours::grey);
-    g.drawRect (getLocalBounds(), 1);   // draw an outline around the component
-
-    g.setColour (Colours::lightblue);
-    g.setFont (14.0f);
-    g.drawText ("MappingListComponent", getLocalBounds(),
-                Justification::centred, true);   // draw some placeholder text
+MappingListComponent::~MappingListComponent() {
+    clearExtendedEditorWindows();
 }
-#endif
 
 void MappingListComponent::resized() {
     // This method is where you should set the bounds of any child
@@ -126,12 +108,12 @@
 }
 
 
-void MappingListComponent::synchronize()
-{
+void MappingListComponent::synchronize() {
     if(keyboardSegment_ != 0) {
         if(lastMappingFactoryIdentifier_ != keyboardSegment_->mappingFactoryUniqueIdentifier()) {
             lastMappingFactoryIdentifier_ = keyboardSegment_->mappingFactoryUniqueIdentifier();
             listBox_.updateContent();
+            updateExtendedEditorWindows();
         }
     }
     
@@ -140,6 +122,106 @@
         if(listItem != 0)
             listItem->synchronize();
     }
+    
+    synchronizeExtendedEditorWindows();
+}
+
+// Open an extended editor window for the given component
+// Store the new window in the list; it is deleted when it is closed
+void MappingListComponent::openExtendedEditorWindow(MappingFactory *factory) {
+    if(factory == 0)
+        return;
+    
+    ScopedLock sl(extendedEditorWindowsMutex_);
+    
+    MappingExtendedEditorWindow *window = new MappingExtendedEditorWindow(*this,
+                                                                          *keyboardSegment_, *factory);
+    extendedEditorWindows_.push_back(window);
+}
+
+// Close an extended editor window and remove it from the list
+void MappingListComponent::closeExtendedEditorWindow(MappingExtendedEditorWindow *window) {
+    ScopedLock sl(extendedEditorWindowsMutex_);
+
+    closeExtendedEditorWindowHelper(window);
+}
+
+MappingExtendedEditorWindow* MappingListComponent::extendedEditorWindowForFactory(MappingFactory *factory) {
+    ScopedLock sl(extendedEditorWindowsMutex_);
+    
+    list<MappingExtendedEditorWindow*>::iterator it;
+    for(it = extendedEditorWindows_.begin(); it != extendedEditorWindows_.end(); ++it) {
+        if((*it)->factory() == factory)
+            return *it;
+    }
+    
+    return 0;
+}
+
+// Update extended editor windows
+void MappingListComponent::synchronizeExtendedEditorWindows() {
+    // Update extended editor windows
+    ScopedLock sl(extendedEditorWindowsMutex_);
+    
+    list<MappingExtendedEditorWindow*>::iterator it;
+    for(it = extendedEditorWindows_.begin(); it != extendedEditorWindows_.end(); ++it) {
+        (*it)->synchronize();
+    }
+}
+
+// Close an extended editor window and remove it from the list (interval version without lock)
+void MappingListComponent::closeExtendedEditorWindowHelper(MappingExtendedEditorWindow *window) {
+    window->setVisible(false);
+    
+    list<MappingExtendedEditorWindow*>::iterator it;
+    bool found = true;
+    
+    // Remove this window from the list (handling multiple entries just in case)
+    while(found) {
+        found = false;
+        for(it = extendedEditorWindows_.begin(); it != extendedEditorWindows_.end(); ++it) {
+            if(*it == window) {
+                extendedEditorWindows_.erase(it);
+                found = true;
+                break;
+            }
+        }
+    }
+    
+    // Delete the window which in turn deletes the editor component
+    delete window;
+}
+
+// Find the invalid extended editor windows and close them
+void MappingListComponent::updateExtendedEditorWindows() {
+    ScopedLock sl(extendedEditorWindowsMutex_);
+    
+    list<MappingExtendedEditorWindow*>::iterator it;
+    bool found = true;
+    
+    // Remove the window from the list if it is invalid
+    while(found) {
+        found = false;
+        for(it = extendedEditorWindows_.begin(); it != extendedEditorWindows_.end(); ++it) {
+            if(!(*it)->isValid()) {
+                closeExtendedEditorWindowHelper(*it);
+                found = true;
+                break;
+            }
+        }
+    }
+}
+
+// Remove all extend editor windows
+void MappingListComponent::clearExtendedEditorWindows() {
+    ScopedLock sl(extendedEditorWindowsMutex_);
+    
+    list<MappingExtendedEditorWindow*>::iterator it;
+    for(it = extendedEditorWindows_.begin(); it != extendedEditorWindows_.end(); ++it) {
+        delete *it;
+    }
+    
+    extendedEditorWindows_.clear();
 }
 
 #endif  // TOUCHKEYS_NO_GUI
--- a/Source/GUI/MappingListComponent.h	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/GUI/MappingListComponent.h	Sat Jun 21 23:32:33 2014 +0100
@@ -27,12 +27,15 @@
 #define __MAPPINGLISTCOMPONENT_H_51502151__
 
 #include <vector>
+#include <list>
 #include "../JuceLibraryCode/JuceHeader.h"
 #include "../MainApplicationController.h"
 #include "../TouchKeys/MidiKeyboardSegment.h"
 #include "MappingListItem.h"
 #include "../Mappings/MappingFactory.h"
 
+class MappingExtendedEditorWindow;
+
 //==============================================================================
 /*
 */
@@ -62,6 +65,13 @@
     // Add or delete a mapping based on a Factory class created elsewhere
     void addMapping(MappingFactory* factory);
     void deleteMapping(MappingFactory* factory);
+    
+    // Return which segment this component refers to
+    int segmentNumber() {
+        if(keyboardSegment_ == 0)
+            return -1;
+        return keyboardSegment_->outputPort();
+    }
 
     // *** ListBox methods ***
     int getNumRows();
@@ -78,11 +88,36 @@
     // Update UI state to reflect underlying system state
     void synchronize();
     
+    // *** Extended editor window methods ***
+    // Open an extended editor window for the given component
+    void openExtendedEditorWindow(MappingFactory *factory);
+    
+    // Close an extended editor window and remove it from the list
+    void closeExtendedEditorWindow(MappingExtendedEditorWindow *window);
+    
+    // Find an extended editor window for a given factory, if it exists
+    MappingExtendedEditorWindow *extendedEditorWindowForFactory(MappingFactory *factory);
+    
 private:
+    // Sync the UI for the extended editor windows
+    void synchronizeExtendedEditorWindows();
+    
+    // Internal helper function for closing window
+    void closeExtendedEditorWindowHelper(MappingExtendedEditorWindow *window);
+    
+    // Find the invalid editor windows and clsoe them
+    void updateExtendedEditorWindows();
+    
+    // Close all extended editor windows
+    void clearExtendedEditorWindows();
+    
     ListBox listBox_;
     MainApplicationController *controller_;
     MidiKeyboardSegment *keyboardSegment_;
     
+    CriticalSection extendedEditorWindowsMutex_;
+    list<MappingExtendedEditorWindow*> extendedEditorWindows_;
+    
     int lastMappingFactoryIdentifier_;
     
     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MappingListComponent)
--- a/Source/GUI/MappingListItem.cpp	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/GUI/MappingListItem.cpp	Sat Jun 21 23:32:33 2014 +0100
@@ -20,6 +20,7 @@
 //[Headers] You can add your own extra header files here...
 #ifndef TOUCHKEYS_NO_GUI
 #include "MappingListComponent.h"
+#include "MappingExtendedEditorWindow.h"
 //[/Headers]
 
 #include "MappingListItem.h"
@@ -157,6 +158,14 @@
     else if (buttonThatWasClicked == showDetailsButton)
     {
         //[UserButtonCode_showDetailsButton] -- add your button handler code here..
+        // Create an extended editor window
+        MappingExtendedEditorWindow *window = listComponent_.extendedEditorWindowForFactory(factory_);
+        if(window != 0) {
+            window->setVisible(true);
+            window->toFront(true);
+        }
+        else if(factory_->hasExtendedEditor())
+            listComponent_.openExtendedEditorWindow(factory_);
         //[/UserButtonCode_showDetailsButton]
     }
     else if (buttonThatWasClicked == deleteButton)
@@ -215,12 +224,9 @@
     }
 
     if(factory_->hasExtendedEditor()) {
-        // Has an extended editor: make one and keep it around for adding to a new window
-        mappingLongEditorComponent = factory_->createExtendedEditor();
         showDetailsButton->setEnabled(true);
     }
     else {
-        mappingLongEditorComponent = nullptr;
         showDetailsButton->setEnabled(false);
     }
 
@@ -242,8 +248,6 @@
     // Update the short and long components if present
     if(mappingShortEditorComponent != 0)
         mappingShortEditorComponent->synchronize();
-    if(mappingLongEditorComponent != 0)
-        mappingLongEditorComponent->synchronize();
 }
 //[/MiscUserCode]
 
--- a/Source/GUI/MappingListItem.h	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/GUI/MappingListItem.h	Sat Jun 21 23:32:33 2014 +0100
@@ -67,8 +67,6 @@
     //[UserVariables]   -- You can add your own custom variables in this section.
     MappingFactory *factory_;
     MappingListComponent& listComponent_;
-
-    ScopedPointer<MappingEditorComponent> mappingLongEditorComponent;
     //[/UserVariables]
 
     //==============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Source/GUI/PreferencesComponent.cpp	Sat Jun 21 23:32:33 2014 +0100
@@ -0,0 +1,251 @@
+/*
+  ==============================================================================
+
+  This is an automatically generated GUI class created by the Introjucer!
+
+  Be careful when adding custom code to these files, as only the code within
+  the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded
+  and re-saved.
+
+  Created with Introjucer version: 3.1.0
+
+  ------------------------------------------------------------------------------
+
+  The Introjucer is part of the JUCE library - "Jules' Utility Class Extensions"
+  Copyright 2004-13 by Raw Material Software Ltd.
+
+  ==============================================================================
+*/
+
+//[Headers] You can add your own extra header files here...
+#include "../MainApplicationController.h"
+//[/Headers]
+
+#include "PreferencesComponent.h"
+
+
+//[MiscUserDefs] You can add your own user definitions and misc code here...
+//[/MiscUserDefs]
+
+//==============================================================================
+PreferencesComponent::PreferencesComponent ()
+    : controller_(0)
+{
+    addAndMakeVisible (startupPresetComboBox = new ComboBox ("Startup preset combo box"));
+    startupPresetComboBox->setEditableText (false);
+    startupPresetComboBox->setJustificationType (Justification::centredLeft);
+    startupPresetComboBox->setTextWhenNothingSelected (String::empty);
+    startupPresetComboBox->setTextWhenNoChoicesAvailable ("(no choices)");
+    startupPresetComboBox->addListener (this);
+
+    addAndMakeVisible (label4 = new Label ("new label",
+                                           "Load preset on startup:"));
+    label4->setFont (Font (15.00f, Font::plain));
+    label4->setJustificationType (Justification::centredLeft);
+    label4->setEditable (false, false, false);
+    label4->setColour (TextEditor::textColourId, Colours::black);
+    label4->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (startTouchKeysButton = new ToggleButton ("auto start TouchKeys button"));
+    startTouchKeysButton->setButtonText ("Start TouchKeys on startup");
+    startTouchKeysButton->addListener (this);
+
+    addAndMakeVisible (autodetectButton = new ToggleButton ("Autodetect button"));
+    autodetectButton->setButtonText ("Autodetect TouchKeys octave on each start");
+    autodetectButton->addListener (this);
+
+    addAndMakeVisible (defaultsButton = new TextButton ("new button"));
+    defaultsButton->setButtonText ("Reset to Defaults...");
+    defaultsButton->addListener (this);
+
+
+    //[UserPreSize]
+    //[/UserPreSize]
+
+    setSize (296, 152);
+
+
+    //[Constructor] You can add your own custom stuff here..
+
+    // Initialise the combo box
+    startupPresetComboBox->addItem("None", kStartupPresetNone);
+    startupPresetComboBox->addItem("Vibrato and Pitch Bend", kStartupPresetVibratoPitchBend);
+    startupPresetComboBox->addItem("Last Saved", kStartupPresetLastSaved);
+    startupPresetComboBox->addItem("Choose...", kStartupPresetChoose);
+
+    //[/Constructor]
+}
+
+PreferencesComponent::~PreferencesComponent()
+{
+    //[Destructor_pre]. You can add your own custom destruction code here..
+    //[/Destructor_pre]
+
+    startupPresetComboBox = nullptr;
+    label4 = nullptr;
+    startTouchKeysButton = nullptr;
+    autodetectButton = nullptr;
+    defaultsButton = nullptr;
+
+
+    //[Destructor]. You can add your own custom destruction code here..
+    //[/Destructor]
+}
+
+//==============================================================================
+void PreferencesComponent::paint (Graphics& g)
+{
+    //[UserPrePaint] Add your own custom painting code here..
+    //[/UserPrePaint]
+
+    g.fillAll (Colour (0xffd2d2d2));
+
+    //[UserPaint] Add your own custom painting code here..
+    //[/UserPaint]
+}
+
+void PreferencesComponent::resized()
+{
+    startupPresetComboBox->setBounds (16, 32, 264, 24);
+    label4->setBounds (16, 8, 160, 24);
+    startTouchKeysButton->setBounds (16, 64, 208, 24);
+    autodetectButton->setBounds (16, 88, 272, 24);
+    defaultsButton->setBounds (16, 120, 144, 24);
+    //[UserResized] Add your own custom resize handling here..
+    //[/UserResized]
+}
+
+void PreferencesComponent::comboBoxChanged (ComboBox* comboBoxThatHasChanged)
+{
+    //[UsercomboBoxChanged_Pre]
+    if(controller_ == 0)
+        return;
+    //[/UsercomboBoxChanged_Pre]
+
+    if (comboBoxThatHasChanged == startupPresetComboBox)
+    {
+        //[UserComboBoxCode_startupPresetComboBox] -- add your combo box handling code here..
+        int selection = startupPresetComboBox->getSelectedId();
+        if(selection == kStartupPresetNone) {
+            controller_->setPrefsStartupPresetNone();
+        }
+        else if(selection == kStartupPresetVibratoPitchBend) {
+            controller_->setPrefsStartupPresetVibratoPitchBend();
+        }
+        else if(selection == kStartupPresetLastSaved) {
+            controller_->setPrefsStartupPresetLastSaved();
+        }
+        else if(selection == kStartupPresetChoose) {
+            // Bring up window to choose a preset
+            FileChooser myChooser ("Select a preset...",
+                                   File::nonexistent, // File::getSpecialLocation (File::userHomeDirectory),
+                                   "*.tkpreset");
+            if(myChooser.browseForFileToOpen()) {
+                controller_->setPrefsStartupPreset(myChooser.getResult().getFullPathName());
+            }
+            // Otherwise user clicked cancel and we go back to whatever was there before
+        }
+        //[/UserComboBoxCode_startupPresetComboBox]
+    }
+
+    //[UsercomboBoxChanged_Post]
+    //[/UsercomboBoxChanged_Post]
+}
+
+void PreferencesComponent::buttonClicked (Button* buttonThatWasClicked)
+{
+    //[UserbuttonClicked_Pre]
+    if(controller_ == 0)
+        return;
+    //[/UserbuttonClicked_Pre]
+
+    if (buttonThatWasClicked == startTouchKeysButton)
+    {
+        //[UserButtonCode_startTouchKeysButton] -- add your button handler code here..
+        controller_->setPrefsAutoStartTouchKeys(startTouchKeysButton->getToggleState());
+        //[/UserButtonCode_startTouchKeysButton]
+    }
+    else if (buttonThatWasClicked == autodetectButton)
+    {
+        //[UserButtonCode_autodetectButton] -- add your button handler code here..
+        controller_->setPrefsAutodetectOctave(autodetectButton->getToggleState());
+        //[/UserButtonCode_autodetectButton]
+    }
+    else if (buttonThatWasClicked == defaultsButton)
+    {
+        //[UserButtonCode_defaultsButton] -- add your button handler code here..
+        controller_->resetPreferences();
+        //[/UserButtonCode_defaultsButton]
+    }
+
+    //[UserbuttonClicked_Post]
+    //[/UserbuttonClicked_Post]
+}
+
+
+
+//[MiscUserCode] You can add your own definitions of your custom methods or any other code here...
+
+// Synchronize the UI state with the underlying state of the controller
+void PreferencesComponent::synchronize(bool forceUpdates) {
+    if(controller_ == 0)
+        return;
+
+    startTouchKeysButton->setToggleState(controller_->getPrefsAutoStartTouchKeys(), dontSendNotification);
+    autodetectButton->setToggleState(controller_->getPrefsAutodetectOctave(), dontSendNotification);
+
+    if(controller_->getPrefsStartupPresetNone())
+        startupPresetComboBox->setSelectedId(kStartupPresetNone);
+    else if(controller_->getPrefsStartupPresetVibratoPitchBend())
+        startupPresetComboBox->setSelectedId(kStartupPresetVibratoPitchBend);
+    else if(controller_->getPrefsStartupPresetLastSaved())
+        startupPresetComboBox->setSelectedId(kStartupPresetLastSaved);
+    else {
+        String path = controller_->getPrefsStartupPreset();
+        startupPresetComboBox->setText(path);
+    }
+}
+
+//[/MiscUserCode]
+
+
+//==============================================================================
+#if 0
+/*  -- Introjucer information section --
+
+    This is where the Introjucer stores the metadata that describe this GUI layout, so
+    make changes in here at your peril!
+
+BEGIN_JUCER_METADATA
+
+<JUCER_COMPONENT documentType="Component" className="PreferencesComponent" componentName=""
+                 parentClasses="public Component" constructorParams="" variableInitialisers="controller_(0)"
+                 snapPixels="8" snapActive="1" snapShown="1" overlayOpacity="0.330"
+                 fixedSize="1" initialWidth="296" initialHeight="152">
+  <BACKGROUND backgroundColour="ffd2d2d2"/>
+  <COMBOBOX name="Startup preset combo box" id="244410f02f6c1c72" memberName="startupPresetComboBox"
+            virtualName="" explicitFocusOrder="0" pos="16 32 264 24" editable="0"
+            layout="33" items="" textWhenNonSelected="" textWhenNoItems="(no choices)"/>
+  <LABEL name="new label" id="e9b3daa69a8ac5c" memberName="label4" virtualName=""
+         explicitFocusOrder="0" pos="16 8 160 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Load preset on startup:" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="0" italic="0" justification="33"/>
+  <TOGGLEBUTTON name="auto start TouchKeys button" id="62c82600413ca060" memberName="startTouchKeysButton"
+                virtualName="" explicitFocusOrder="0" pos="16 64 208 24" buttonText="Start TouchKeys on startup"
+                connectedEdges="0" needsCallback="1" radioGroupId="0" state="0"/>
+  <TOGGLEBUTTON name="Autodetect button" id="69a491dfca4ea997" memberName="autodetectButton"
+                virtualName="" explicitFocusOrder="0" pos="16 88 272 24" buttonText="Autodetect TouchKeys octave on each start"
+                connectedEdges="0" needsCallback="1" radioGroupId="0" state="0"/>
+  <TEXTBUTTON name="new button" id="89690e14d6bf00c0" memberName="defaultsButton"
+              virtualName="" explicitFocusOrder="0" pos="16 120 144 24" buttonText="Reset to Defaults..."
+              connectedEdges="0" needsCallback="1" radioGroupId="0"/>
+</JUCER_COMPONENT>
+
+END_JUCER_METADATA
+*/
+#endif
+
+
+//[EndFile] You can add extra defines here...
+//[/EndFile]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Source/GUI/PreferencesComponent.h	Sat Jun 21 23:32:33 2014 +0100
@@ -0,0 +1,96 @@
+/*
+  ==============================================================================
+
+  This is an automatically generated GUI class created by the Introjucer!
+
+  Be careful when adding custom code to these files, as only the code within
+  the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded
+  and re-saved.
+
+  Created with Introjucer version: 3.1.0
+
+  ------------------------------------------------------------------------------
+
+  The Introjucer is part of the JUCE library - "Jules' Utility Class Extensions"
+  Copyright 2004-13 by Raw Material Software Ltd.
+
+  ==============================================================================
+*/
+
+#ifndef __JUCE_HEADER_248C56742F074362__
+#define __JUCE_HEADER_248C56742F074362__
+
+//[Headers]     -- You can add your own extra header files here --
+#include "JuceHeader.h"
+
+class MainApplicationController;
+//[/Headers]
+
+
+
+//==============================================================================
+/**
+                                                                    //[Comments]
+    An auto-generated component, created by the Introjucer.
+
+    Describe your class and how it works here!
+                                                                    //[/Comments]
+*/
+class PreferencesComponent  : public Component,
+                              public ComboBoxListener,
+                              public ButtonListener
+{
+public:
+    //==============================================================================
+    PreferencesComponent ();
+    ~PreferencesComponent();
+
+    //==============================================================================
+    //[UserMethods]     -- You can add your own custom methods in this section.
+
+    void setMainApplicationController(MainApplicationController *controller) {
+        // Attach the user interface to the controller and vice-versa
+        controller_ = controller;
+    }
+
+    // Synchronize UI state to match underlying state of the back end
+    void synchronize(bool forceUpdates = false);
+
+    //[/UserMethods]
+
+    void paint (Graphics& g);
+    void resized();
+    void comboBoxChanged (ComboBox* comboBoxThatHasChanged);
+    void buttonClicked (Button* buttonThatWasClicked);
+
+
+
+private:
+    //[UserVariables]   -- You can add your own custom variables in this section.
+    enum {
+        kStartupPresetNone = 1,
+        kStartupPresetVibratoPitchBend,
+        kStartupPresetLastSaved,
+        kStartupPresetChoose
+    };
+
+    MainApplicationController *controller_; // Pointer to the main application controller
+
+    //[/UserVariables]
+
+    //==============================================================================
+    ScopedPointer<ComboBox> startupPresetComboBox;
+    ScopedPointer<Label> label4;
+    ScopedPointer<ToggleButton> startTouchKeysButton;
+    ScopedPointer<ToggleButton> autodetectButton;
+    ScopedPointer<TextButton> defaultsButton;
+
+
+    //==============================================================================
+    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PreferencesComponent)
+};
+
+//[EndFile] You can add extra defines here...
+//[/EndFile]
+
+#endif   // __JUCE_HEADER_248C56742F074362__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Source/GUI/PreferencesWindow.h	Sat Jun 21 23:32:33 2014 +0100
@@ -0,0 +1,65 @@
+/*
+  ==============================================================================
+
+    PreferencesWindow.h
+    Created: 17 Jun 2014 11:22:44pm
+    Author:  Andrew McPherson
+
+  ==============================================================================
+*/
+
+#ifndef PREFERENCESWINDOW_H_INCLUDED
+#define PREFERENCESWINDOW_H_INCLUDED
+
+#include "../../JuceLibraryCode/JuceHeader.h"
+#include "PreferencesComponent.h"
+#include "../MainApplicationController.h"
+
+//==============================================================================
+/*
+*/
+class PreferencesWindow : public DocumentWindow, public Timer
+{
+public:
+    PreferencesWindow(MainApplicationController& controller)
+    : DocumentWindow("Preferences", Colours::lightgrey, DocumentWindow::allButtons)
+    {
+        // Make a new preferences component
+        preferencesComponent_ = new PreferencesComponent();
+        preferencesComponent_->setMainApplicationController(&controller);
+        
+        // Set properties
+        setContentOwned(preferencesComponent_, true);
+        setUsingNativeTitleBar(true);
+        setResizable(false, false);
+        
+        // Don't show window yet
+        setVisible(false);
+        
+        // Start a timer that will keep the interface in sync with the application
+        startTimer(50);
+    }
+
+    ~PreferencesWindow()
+    {
+    }
+    
+    // Method used by Juce timer which we will use for periodic UI updates
+    // from the underlying system state
+    void timerCallback() {
+        preferencesComponent_->synchronize();
+    }
+    
+    void closeButtonPressed()
+    {
+        setVisible(false);
+    }
+
+private:
+    PreferencesComponent *preferencesComponent_;
+    
+    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PreferencesWindow)
+};
+
+
+#endif  // PREFERENCESWINDOW_H_INCLUDED
--- a/Source/Main.cpp	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/Main.cpp	Sat Jun 21 23:32:33 2014 +0100
@@ -26,6 +26,8 @@
 #ifndef TOUCHKEYS_NO_GUI
 #include "GUI/MainWindow.h"
 #include "GUI/GraphicsDisplayWindow.h"
+#include "GUI/PreferencesWindow.h"
+#include "GUI/PreferencesComponent.h"
 #include "Display/OpenGLJuceCanvas.h"
 
 //==============================================================================
@@ -45,8 +47,11 @@
 
         mainWindow_ = new MainWindow(controller_);
         keyboardDisplayWindow_ = new GraphicsDisplayWindow("TouchKeys Display", controller_.keyboardDisplay());
+        preferencesWindow_ = new PreferencesWindow(controller_);
         
         controller_.setKeyboardDisplayWindow(keyboardDisplayWindow_);
+        controller_.setPreferencesWindow(preferencesWindow_);
+        controller_.initialise();
     }
 
     void shutdown() {
@@ -57,7 +62,9 @@
         mainWindow_ = nullptr; // (deletes our window)
         
         controller_.setKeyboardDisplayWindow(0);    // Delete display window and disconnect from controller
+        controller_.setPreferencesWindow(0);
         keyboardDisplayWindow_ = nullptr;
+        preferencesWindow_ = nullptr;
     }
 
     //==============================================================================
@@ -76,6 +83,7 @@
 private:
     ScopedPointer<MainWindow> mainWindow_;
     ScopedPointer<GraphicsDisplayWindow> keyboardDisplayWindow_;
+    ScopedPointer<PreferencesWindow> preferencesWindow_;
     MainApplicationController controller_;
 };
 
--- a/Source/MainApplicationController.cpp	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/MainApplicationController.cpp	Sat Jun 21 23:32:33 2014 +0100
@@ -56,6 +56,7 @@
   keyboardDisplayWindow_(0),
   keyboardTesterDisplay_(0),
   keyboardTesterWindow_(0),
+  preferencesWindow_(0),
 #endif
   segmentCounter_(0),
   loggingActive_(false)
@@ -70,18 +71,30 @@
     keyboardController_.setMidiOutputController(&midiOutputController_);
     keyboardController_.setGUI(&keyboardDisplay_);
 	midiInputController_.setMidiOutputController(&midiOutputController_);
-    
-    // Set up an initial OSC transmit host/port
-    oscTransmitter_.addAddress(kDefaultOscTransmitHost, kDefaultOscTransmitPort);
 
     // Set up default logging directory
     loggingDirectory_ = (File::getSpecialLocation(File::userHomeDirectory).getFullPathName() + "/Desktop").toUTF8();
     
+    // Configure application properties
+    PropertiesFile::Options options;
+    options.applicationName = "TouchKeys";
+    options.folderName = "TouchKeys";
+    options.filenameSuffix = ".properties";
+    options.osxLibrarySubFolder = "Application Support";
+    applicationProperties_.setStorageParameters(options);
+    
     // Defaults for display, until we get other information
     keyboardDisplay_.setKeyboardRange(36, 72);
     
     // Add one keyboard segment at the beginning
     midiSegmentAdd();
+    
+    // Load the current preferences
+    loadApplicationPreferences();
+    
+    // Set up an initial OSC transmit host/port if none has been loaded
+    if(oscTransmitter_.addresses().size() == 0)
+        oscTransmitter_.addAddress(kDefaultOscTransmitHost, kDefaultOscTransmitPort);
 }
 
 MainApplicationController::~MainApplicationController() {
@@ -91,6 +104,46 @@
 #endif
 }
 
+// Actions here run in the JUCE initialise() method once the application is loaded
+void MainApplicationController::initialise() {
+    // Load a preset if enabled
+    if(getPrefsStartupPresetLastSaved()) {
+        if(applicationProperties_.getUserSettings()->containsKey("LastSavedPreset")) {
+            String presetFile = applicationProperties_.getUserSettings()->getValue("LastSavedPreset");
+            if(presetFile != "") {
+                loadPresetFromFile(presetFile.toUTF8());
+            }
+        }
+    }
+    else if(getPrefsStartupPresetVibratoPitchBend()) {
+        if(midiInputController_.numSegments() > 0) {
+            MidiKeyboardSegment *segment = midiInputController_.segment(0);
+            
+            MappingFactory *factory = new TouchkeyVibratoMappingFactory(keyboardController_, *segment);
+            if(factory != 0)
+                segment->addMappingFactory(factory);
+            factory = new TouchkeyPitchBendMappingFactory(keyboardController_, *segment);
+            if(factory != 0)
+                segment->addMappingFactory(factory);
+        }
+    }
+    else if(!getPrefsStartupPresetNone()) {
+        String presetFile = getPrefsStartupPreset();
+        if(presetFile != "") {
+            loadPresetFromFile(presetFile.toUTF8());
+        }
+    }
+    
+    // Automatically start the TouchKeys if the preferences are enabled
+    if(getPrefsAutoStartTouchKeys() && applicationProperties_.getUserSettings()->containsKey("TouchKeysDevice")) {
+        String tkDevicePath = applicationProperties_.getUserSettings()->getValue("TouchKeysDevice");
+        if(touchkeyDeviceExists(tkDevicePath.toUTF8())) {
+            // Exists: try to open and run
+            touchkeyDeviceStartupSequence(tkDevicePath.toUTF8());
+        }
+    }
+}
+
 bool MainApplicationController::touchkeyDeviceStartupSequence(const char * path) {
 #ifdef TOUCHKEY_ENTROPY_GENERATOR_ENABLE
     if(!strcmp(path, "/dev/Entropy Generator") || !strcmp(path, "\\\\.\\Entropy Generator")) {
@@ -137,6 +190,13 @@
     // Success!
     touchkeyErrorMessage_ = "";
     touchkeyErrorOccurred_ = false;
+    
+    showKeyboardDisplayWindow();
+    
+    // Automatically detect the lowest octave if set
+    if(getPrefsAutodetectOctave())
+        touchkeyDeviceAutodetectLowestMidiNote();
+    
     return true;
 }
 
@@ -212,6 +272,27 @@
     return devices;
 }
 
+void MainApplicationController::touchkeyDeviceClearErrorMessage() {
+    touchkeyErrorMessage_ = "";
+    touchkeyErrorOccurred_ = false;
+}
+
+// Check whether a given touchkey device exists
+bool MainApplicationController::touchkeyDeviceExists(const char * path) {
+    String pathString(path);
+    File tkDeviceFile(pathString);
+    return tkDeviceFile.existsAsFile();
+}
+
+// Select a particular touchkey device
+bool MainApplicationController::openTouchkeyDevice(const char * path) {
+    bool success = touchkeyController_.openDevice(path);
+    
+    if(success)
+        applicationProperties_.getUserSettings()->setValue("TouchKeysDevice", String(path));
+    return success;
+}
+
 // Close the currently open TouchKeys device
 void MainApplicationController::closeTouchkeyDevice() {
 #ifdef TOUCHKEY_ENTROPY_GENERATOR_ENABLE
@@ -239,6 +320,21 @@
     return true;
 }
 
+// Start/stop the TouchKeys data collection
+bool MainApplicationController::startTouchkeyDevice() {
+    return touchkeyController_.startAutoGathering();
+}
+
+void MainApplicationController::stopTouchkeyDevice() {
+    touchkeyController_.stopAutoGathering();
+}
+
+// Status queries on TouchKeys
+// Returns true if device has been opened
+bool MainApplicationController::touchkeyDeviceIsOpen() {
+    return touchkeyController_.isOpen();
+}
+
 // Return true if device is collecting data
 bool MainApplicationController::touchkeyDeviceIsRunning() {
 #ifdef TOUCHKEY_ENTROPY_GENERATOR_ENABLE
@@ -251,6 +347,35 @@
 #endif
 }
 
+// Returns true if an error has occurred
+bool MainApplicationController::touchkeyDeviceErrorOccurred() {
+    return touchkeyErrorOccurred_;
+}
+
+// Return the error message if one occurred
+std::string MainApplicationController::touchkeyDeviceErrorMessage() {
+    return touchkeyErrorMessage_;
+}
+
+// How many octaves on the current device
+int MainApplicationController::touchkeyDeviceNumberOfOctaves() {
+    return touchkeyController_.numberOfOctaves();
+}
+
+// Return the lowest MIDI note
+int MainApplicationController::touchkeyDeviceLowestMidiNote() {
+    return touchkeyController_.lowestMidiNote();
+}
+
+// Set the lowest MIDI note for the TouchKeys
+void MainApplicationController::touchkeyDeviceSetLowestMidiNote(int note) {
+    keyboardDisplay_.clearAllTouches();
+    touchkeyEmulator_.setLowestMidiNote(note);
+    touchkeyController_.setLowestMidiNote(note);
+    
+    applicationProperties_.getUserSettings()->setValue("TouchKeysLowestMIDINote", note);
+}
+
 // Start an autodetection routine to match touch data to MIDI
 void MainApplicationController::touchkeyDeviceAutodetectLowestMidiNote() {
     if(touchkeyAutodetecting_)
@@ -325,7 +450,7 @@
     // consider renumbering every time a segment is removed so that we always have an index
     // 0-N which corresponds to the indexes within MidiInputController (and also the layout
     // of the tabs).
-    MidiKeyboardSegment *newSegment = midiInputController_.addSegment(segmentCounter_++, 12, 127);
+    MidiKeyboardSegment *newSegment = midiInputController_.addSegment(segmentCounter_, 12, 127);
     
     // Set up defaults
     newSegment->setModePassThrough();
@@ -335,10 +460,15 @@
     newSegment->setOutputTransposition(0);
     newSegment->setUsesKeyboardPitchWheel(true);
     
+    // Enable the MIDI output for this segment if it exists in the preferences
+    loadMIDIOutputFromApplicationPreferences(segmentCounter_);
+    
     // Enable standalone mode on the new segment if generally enabled
     if(touchkeyStandaloneModeEnabled_)
         newSegment->enableTouchkeyStandaloneMode();
     
+    segmentCounter_++;
+    
     return newSegment;
 }
 
@@ -354,6 +484,90 @@
     midiInputController_.removeSegment(segment);
 }
 
+// Enable one MIDI input port either as primary or auxiliary
+void MainApplicationController::enableMIDIInputPort(int portNumber, bool isPrimary) {
+    midiInputController_.enablePort(portNumber, isPrimary);
+    if(isPrimary)
+        applicationProperties_.getUserSettings()->setValue("MIDIInputPrimary",
+                                                           midiInputController_.deviceName(portNumber));
+    else
+        applicationProperties_.getUserSettings()->setValue("MIDIInputAuxiliary",
+                                                           midiInputController_.deviceName(portNumber));
+}
+
+// Enable all available MIDI input ports, with one in particular selected as primary
+void MainApplicationController::enableAllMIDIInputPorts(int primaryPortNumber) {
+    midiInputController_.enableAllPorts(primaryPortNumber);
+    applicationProperties_.getUserSettings()->setValue("MIDIInputPrimary",
+                                                       midiInputController_.deviceName(primaryPortNumber));
+    applicationProperties_.getUserSettings()->setValue("MIDIInputAuxiliary", "__all__");
+}
+
+// Disable a particular MIDI input port number
+// For now, the preferences for auxiliary ports don't update; could add a complete list of enabled aux ports
+void MainApplicationController::disableMIDIInputPort(int portNumber) {
+    if(portNumber == selectedMIDIPrimaryInputPort())
+        applicationProperties_.getUserSettings()->setValue("MIDIInputPrimary", "");
+    midiInputController_.disablePort(portNumber);
+}
+
+// Disable the current primary MIDI input port
+void MainApplicationController::disablePrimaryMIDIInputPort() {
+    applicationProperties_.getUserSettings()->setValue("MIDIInputPrimary", "");
+    midiInputController_.disablePrimaryPort();
+}
+
+// Disable either all MIDI input ports or all auxiliary inputs
+void MainApplicationController::disableAllMIDIInputPorts(bool auxiliaryOnly) {
+    applicationProperties_.getUserSettings()->setValue("MIDIInputAuxiliary", "");
+    if(!auxiliaryOnly)
+        applicationProperties_.getUserSettings()->setValue("MIDIInputPrimary", "");
+    midiInputController_.disableAllPorts(auxiliaryOnly);
+}
+
+// Enable a particular MIDI output port, associating it with a segment
+void MainApplicationController::enableMIDIOutputPort(int identifier, int deviceNumber) {
+    midiOutputController_.enablePort(identifier, deviceNumber);
+    
+    String zoneName = "MIDIOutputZone";
+    zoneName += identifier;
+    applicationProperties_.getUserSettings()->setValue(zoneName, midiOutputController_.deviceName(deviceNumber));
+}
+
+#ifndef JUCE_WINDOWS
+// Create a virtual (inter-application) MIDI output port
+void MainApplicationController::enableMIDIOutputVirtualPort(int identifier, const char *name) {
+    midiOutputController_.enableVirtualPort(identifier, name);
+    
+    String zoneName = "MIDIOutputZone";
+    zoneName += identifier;
+    String zoneValue = "__virtual__";
+    zoneValue += String(name);
+    applicationProperties_.getUserSettings()->setValue(zoneName, zoneValue);
+}
+#endif
+
+// Disable a particular MIDI output port
+void MainApplicationController::disableMIDIOutputPort(int identifier) {
+    String zoneName = "MIDIOutputZone";
+    zoneName += identifier;
+    applicationProperties_.getUserSettings()->setValue(zoneName, "");
+    
+    midiOutputController_.disablePort(identifier);
+}
+
+// Disable all MIDI output ports
+void MainApplicationController::disableAllMIDIOutputPorts() {
+    std::vector<std::pair<int, int> > enabledPorts = midiOutputController_.enabledPorts();
+    for(int i = 0; i < enabledPorts.size(); i++) {
+        // For each active zone, set output port to disabled in preferences
+        String zoneName = "MIDIOutputZone";
+        zoneName += enabledPorts[i].first;
+        applicationProperties_.getUserSettings()->setValue(zoneName, "");
+    }
+    
+    midiOutputController_.disableAllPorts();
+}
 
 // Enable TouchKeys standalone mode
 void MainApplicationController::midiTouchkeysStandaloneModeEnable() {
@@ -362,6 +576,8 @@
     for(int i = 0; i < midiInputController_.numSegments(); i++) {
         midiInputController_.segment(i)->enableTouchkeyStandaloneMode();
     }
+    
+    applicationProperties_.getUserSettings()->setValue("MIDIInputPrimary", "__standalone__");
 }
 
 void MainApplicationController::midiTouchkeysStandaloneModeDisable() {
@@ -370,6 +586,147 @@
     for(int i = 0; i < midiInputController_.numSegments(); i++) {
         midiInputController_.segment(i)->disableTouchkeyStandaloneMode();
     }
+    
+    if(applicationProperties_.getUserSettings()->getValue("MIDIInputPrimary") == "__standalone__")
+        applicationProperties_.getUserSettings()->setValue("MIDIInputPrimary", "");
+}
+
+// *** OSC device methods ***
+
+// Return whether OSC transmission is enabled
+bool MainApplicationController::oscTransmitEnabled() {
+    return oscTransmitter_.enabled();
+}
+
+// Set whether OSC transmission is enabled
+void MainApplicationController::oscTransmitSetEnabled(bool enable) {
+    oscTransmitter_.setEnabled(enable);
+    applicationProperties_.getUserSettings()->setValue("OSCTransmitEnabled", enable);
+}
+
+// Return whether raw frame transmission is enabled
+bool MainApplicationController::oscTransmitRawDataEnabled() {
+    return touchkeyController_.transmitRawDataEnabled();
+}
+
+// Set whether raw frame transmission is enabled
+void MainApplicationController::oscTransmitSetRawDataEnabled(bool enable) {
+    touchkeyController_.setTransmitRawData(enable);
+    applicationProperties_.getUserSettings()->setValue("OSCTransmitRawDataEnabled", enable);
+}
+
+// Return the addresses to which OSC messages are sent
+std::vector<lo_address> MainApplicationController::oscTransmitAddresses() {
+    return oscTransmitter_.addresses();
+}
+
+// Add a new address for sending OSC messages to
+int MainApplicationController::oscTransmitAddAddress(const char * host, const char * port, int proto) {
+    int indexOfNewAddress = oscTransmitter_.addAddress(host, port, proto);
+    
+    if(indexOfNewAddress >= 0) {
+        // Successfully added; update preferences
+        String keyName = "OSCTransmitHost";
+        keyName += indexOfNewAddress;
+        applicationProperties_.getUserSettings()->setValue(keyName, String(host));
+        
+        keyName = "OSCTransmitPort";
+        keyName += indexOfNewAddress;
+        applicationProperties_.getUserSettings()->setValue(keyName, String(port));
+
+        keyName = "OSCTransmitProtocol";
+        keyName += indexOfNewAddress;
+        applicationProperties_.getUserSettings()->setValue(keyName, proto);
+    }
+    
+    return indexOfNewAddress;
+}
+
+// Remove a particular OSC address from the send list
+void MainApplicationController::oscTransmitRemoveAddress(int index) {
+    oscTransmitter_.removeAddress(index);
+    
+    // Remove this destination from the preferences, if it exists
+    String keyName = "OSCTransmitHost";
+    keyName += index;
+    
+    if(applicationProperties_.getUserSettings()->containsKey(keyName)) {
+        applicationProperties_.getUserSettings()->setValue(keyName, "");
+        
+        keyName = "OSCTransmitPort";
+        keyName += index;
+        applicationProperties_.getUserSettings()->setValue(keyName, "");
+        
+        keyName = "OSCTransmitProtocol";
+        keyName += index;
+        applicationProperties_.getUserSettings()->setValue(keyName, (int)0);
+    }
+}
+
+// Remove all OSC addresses from the send list
+void MainApplicationController::oscTransmitClearAddresses() {
+    oscTransmitter_.clearAddresses();
+    
+    for(int index = 0; index < 16; index++) {
+        // Go through and clear preferences for recent OSC hosts;
+        // 16 hosts is a sanity check
+        
+        String keyName = "OSCTransmitHost";
+        keyName += index;
+        
+        if(applicationProperties_.getUserSettings()->containsKey(keyName)) {
+            applicationProperties_.getUserSettings()->setValue(keyName, "");
+            
+            keyName = "OSCTransmitPort";
+            keyName += index;
+            applicationProperties_.getUserSettings()->setValue(keyName, "");
+            
+            keyName = "OSCTransmitProtocol";
+            keyName += index;
+            applicationProperties_.getUserSettings()->setValue(keyName, (int)0);
+        }
+    }
+
+}
+
+// OSC Input (receiver) methods
+// Enable or disable on the OSC receive, and report is status
+bool MainApplicationController::oscReceiveEnabled() {
+    return oscReceiveEnabled_;
+}
+
+// Enable method returns true on success (false only if it was
+// unable to set the port)
+bool MainApplicationController::oscReceiveSetEnabled(bool enable) {
+    applicationProperties_.getUserSettings()->setValue("OSCReceiveEnabled", enable);
+    
+    if(enable && !oscReceiveEnabled_) {
+        oscReceiveEnabled_ = true;
+        return oscReceiver_.setPort(oscReceivePort_);
+    }
+    else if(!enable && oscReceiveEnabled_) {
+        oscReceiveEnabled_ = false;
+        return oscReceiver_.setPort(0);
+    }
+    return true;
+}
+
+// Whether the OSC server is running (false means couldn't open port)
+bool MainApplicationController::oscReceiveRunning() {
+    return oscReceiver_.running();
+}
+
+// Get the current OSC receive port
+int MainApplicationController::oscReceivePort() {
+    return oscReceivePort_;
+}
+
+// Set the current OSC receive port (returns true on success)
+bool MainApplicationController::oscReceiveSetPort(int port) {
+    applicationProperties_.getUserSettings()->setValue("OSCReceivePort", port);
+    
+    oscReceivePort_ = port;
+    return oscReceiver_.setPort(port);
 }
 
 // OSC handler method
@@ -406,8 +763,10 @@
             // std::cout << "Found difference of " << noteDifference << std::endl;
 
             currentMinNote -= noteDifference;
-            if(currentMinNote >= 0 && currentMinNote <= 127)
+            if(currentMinNote >= 0 && currentMinNote <= 127) {
                 touchkeyController_.setLowestMidiNote(currentMinNote);
+                applicationProperties_.getUserSettings()->setValue("TouchKeysLowestMIDINote", currentMinNote);
+            }
             
             touchkeyDeviceStopAutodetecting();
         }
@@ -541,7 +900,13 @@
     XmlElement* segmentsElement = midiInputController_.getSegmentPreset();
     mainElement.addChildElement(segmentsElement);
     
-    return mainElement.writeToFile(outputFile, "");
+    bool result = mainElement.writeToFile(outputFile, "");
+    
+    if(result) {
+        applicationProperties_.getUserSettings()->setValue("LastSavedPreset", outputFile.getFullPathName());
+    }
+    
+    return result;
 }
 
 // Clear the current preset and restore default settings
@@ -554,6 +919,207 @@
     midiSegmentAdd();
 }
 
+// Whether to automatically start the TouchKeys on startup
+bool MainApplicationController::getPrefsAutoStartTouchKeys() {
+    if(!applicationProperties_.getUserSettings()->containsKey("StartupStartTouchKeys"))
+        return false;
+    return applicationProperties_.getUserSettings()->getBoolValue("StartupStartTouchKeys");
+}
+
+void MainApplicationController::setPrefsAutoStartTouchKeys(bool autoStart) {
+    applicationProperties_.getUserSettings()->setValue("StartupStartTouchKeys", autoStart);
+}
+
+// Whether to automatically detect the TouchKeys octave when they start
+bool MainApplicationController::getPrefsAutodetectOctave() {
+    if(!applicationProperties_.getUserSettings()->containsKey("StartupAutodetectTouchKeysOctave"))
+        return false;
+    return applicationProperties_.getUserSettings()->getBoolValue("StartupAutodetectTouchKeysOctave");
+}
+
+void MainApplicationController::setPrefsAutodetectOctave(bool autoDetect) {
+    applicationProperties_.getUserSettings()->setValue("StartupAutodetectTouchKeysOctave", autoDetect);
+}
+
+// Which preset (if any) to load at startup
+void MainApplicationController::setPrefsStartupPresetNone() {
+    applicationProperties_.getUserSettings()->setValue("StartupPreset", "__none__");
+}
+bool MainApplicationController::getPrefsStartupPresetNone() {
+    // By default, no prefs means no preset
+    if(!applicationProperties_.getUserSettings()->containsKey("StartupPreset"))
+        return true;
+    if(applicationProperties_.getUserSettings()->getValue("StartupPreset") == "__none__")
+        return true;
+    return false;
+}
+
+void MainApplicationController::setPrefsStartupPresetVibratoPitchBend() {
+    applicationProperties_.getUserSettings()->setValue("StartupPreset", "__vib_pb__");
+}
+bool MainApplicationController::getPrefsStartupPresetVibratoPitchBend() {
+    if(!applicationProperties_.getUserSettings()->containsKey("StartupPreset"))
+        return false;
+    if(applicationProperties_.getUserSettings()->getValue("StartupPreset") == "__vib_pb__")
+        return true;
+    return false;
+}
+
+void MainApplicationController::setPrefsStartupPresetLastSaved() {
+    applicationProperties_.getUserSettings()->setValue("StartupPreset", "__last__");
+}
+bool MainApplicationController::getPrefsStartupPresetLastSaved() {
+    if(!applicationProperties_.getUserSettings()->containsKey("StartupPreset"))
+        return false;
+    if(applicationProperties_.getUserSettings()->getValue("StartupPreset") == "__last__")
+        return true;
+    return false;
+}
+
+void MainApplicationController::setPrefsStartupPreset(String const& path) {
+    applicationProperties_.getUserSettings()->setValue("StartupPreset", path);
+}
+String MainApplicationController::getPrefsStartupPreset() {
+    if(!applicationProperties_.getUserSettings()->containsKey("StartupPreset"))
+        return "";
+    return applicationProperties_.getUserSettings()->getValue("StartupPreset");
+}
+
+// Reset application preferences to defaults
+void MainApplicationController::resetPreferences() {
+    // TODO: reset settings now, not after restart
+    applicationProperties_.getUserSettings()->clear();
+    
+    setPrefsStartupPresetVibratoPitchBend();
+    setPrefsAutodetectOctave(true);
+}
+
+// Load the current devices from a global preferences file
+void MainApplicationController::loadApplicationPreferences(){
+    PropertiesFile *props = applicationProperties_.getUserSettings();
+    
+    if(props == 0)
+        return;
+    
+    // A few first-time defaults if the properties file is missing
+    if(props->getAllProperties().size() == 0) {
+        resetPreferences();
+    }
+    
+    // Load TouchKeys settings
+    if(props->containsKey("TouchKeysDevice")) {
+        // TODO
+    }
+    if(props->containsKey("TouchKeysLowestMIDINote")) {
+        int note = props->getIntValue("TouchKeysLowestMIDINote");
+        if(note >= 0 && note <= 127)
+            touchkeyDeviceSetLowestMidiNote(note);
+    }
+    
+    // Load MIDI input settings
+    if(props->containsKey("MIDIInputPrimary")) {
+        String deviceName = props->getValue("MIDIInputPrimary");
+        if(deviceName == "__standalone__") {
+            midiTouchkeysStandaloneModeEnable();
+        }
+        else {
+            int index = midiInputController_.indexOfDeviceNamed(deviceName);
+            // cout << "primary input id " << index << " name " << deviceName << endl;
+            if(index >= 0)
+                enableMIDIInputPort(index, true);
+        }
+    }
+    if(props->containsKey("MIDIInputAuxiliary")) {
+        String deviceName = props->getValue("MIDIInputAuxiliary");
+        int index = midiInputController_.indexOfDeviceNamed(deviceName);
+        // cout << "aux input id " << index << " name " << deviceName << endl;
+        if(index >= 0)
+            enableMIDIInputPort(index, false);
+    }
+    
+    // MIDI output settings are loaded when segments are created
+    
+    // OSC settings
+    if(props->containsKey("OSCTransmitEnabled")) {
+        bool enable = props->getBoolValue("OSCTransmitEnabled");
+        oscTransmitSetEnabled(enable);
+    }
+    if(props->containsKey("OSCTransmitRawDataEnabled")) {
+        bool enable = props->getBoolValue("OSCTransmitRawDataEnabled");
+        oscTransmitSetRawDataEnabled(enable);
+    }
+    
+    for(int i = 0; i < 16; i++) {
+        String keyName = "OSCTransmitHost";
+        String host, port;
+        int protocol = LO_UDP;
+        
+        keyName += i;
+        if(props->containsKey(keyName)) {
+            host = props->getValue(keyName);
+        }
+        else
+            continue;
+           
+        keyName = "OSCTransmitPort";
+        keyName += i;
+        if(props->containsKey(keyName)) {
+            port = props->getValue(keyName);
+        }
+        else
+            continue;
+        
+        keyName = "OSCTransmitProtocol";
+        keyName += i;
+        if(props->containsKey(keyName)) {
+            protocol = props->getIntValue(keyName);
+        }
+        // okay to go ahead without protocol; use default
+        
+        // Check for validity
+        if(host != "" && port != "" && (protocol == LO_UDP || protocol == LO_TCP)) {
+            oscTransmitter_.addAddress(host.toUTF8(), port.toUTF8(), protocol);
+        }
+    }
+    
+    if(props->containsKey("OSCReceiveEnabled")) {
+        bool enable = props->getBoolValue("OSCReceiveEnabled");
+        oscReceiveSetEnabled(enable);
+    }
+    if(props->containsKey("OSCReceivePort")) {
+        int port = props->getIntValue("OSCReceivePort");
+        if(port >= 1 && port <= 65535)
+            oscReceiveSetPort(port);
+    }
+    
+}
+
+// Load the MIDI output device for a given zone
+void MainApplicationController::loadMIDIOutputFromApplicationPreferences(int zone) {
+    PropertiesFile *props = applicationProperties_.getUserSettings();
+    
+    String keyName = "MIDIOutputZone";
+    keyName += zone;
+    
+    if(props->containsKey(keyName)) {
+        String output = props->getValue(keyName);
+        if(output.startsWith("__virtual__")) {
+#ifndef JUCE_WINDOWS
+            // Open virtual port with the name that follows
+            String virtualPortName = output.substring(11); // length of "__virtual__"
+            midiOutputController_.enableVirtualPort(zone, virtualPortName.toUTF8());
+#endif
+        }
+        else {
+            String deviceName = props->getValue(keyName);
+            int index = midiOutputController_.indexOfDeviceNamed(deviceName);
+            // cout << "zone " << zone << " id " << index << " name " << deviceName << endl;
+            if(index >= 0)
+                enableMIDIOutputPort(zone, index);
+        }
+    }
+}
+
 #ifdef ENABLE_TOUCHKEYS_SENSOR_TEST
 // Start testing the TouchKeys sensors. Returns true on success.
 bool MainApplicationController::touchkeySensorTestStart(const char *path, int firstKey) {
--- a/Source/MainApplicationController.h	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/MainApplicationController.h	Sat Jun 21 23:32:33 2014 +0100
@@ -54,6 +54,7 @@
 
 #ifndef TOUCHKEYS_NO_GUI
 #include "GUI/GraphicsDisplayWindow.h"
+#include "GUI/PReferencesWindow.h"
 class KeyboardTesterDisplay;
 #endif
 
@@ -72,6 +73,9 @@
     // *** Destructor ***
     ~MainApplicationController();
     
+    // *** Startup actions ***
+    void initialise();
+    
     // *** TouchKeys device methods ***
     
     // Return the path prefix of the TouchKeys device
@@ -84,58 +88,45 @@
     // start data collection, all in one method. Returns true if successful.
     // Will set the error message string if not
     bool touchkeyDeviceStartupSequence(const char * path);
-    void touchkeyDeviceClearErrorMessage() {
-        touchkeyErrorMessage_ = "";
-        touchkeyErrorOccurred_ = false;
-    }
+    void touchkeyDeviceClearErrorMessage();
+    
+    // Check whether a given touchkey device exists
+    bool touchkeyDeviceExists(const char * path);
     
     // Select a particular touchkey device
-    bool openTouchkeyDevice(const char * path) {
-        return touchkeyController_.openDevice(path);
-    }
+    bool openTouchkeyDevice(const char * path);
+    
     void closeTouchkeyDevice();
     
     // Check for device present
     bool touchkeyDeviceCheckForPresence(int waitMilliseconds = 250, int tries = 10);
     
     // Start/stop the TouchKeys data collection
-    bool startTouchkeyDevice() {
-        return touchkeyController_.startAutoGathering();
-    }
-    void stopTouchkeyDevice() {
-        touchkeyController_.stopAutoGathering();
-    }
+    bool startTouchkeyDevice();
+    void stopTouchkeyDevice();
     
     // Status queries on TouchKeys
     // Returns true if device has been opened
-    bool touchkeyDeviceIsOpen() {
-        return touchkeyController_.isOpen();
-    }
+    bool touchkeyDeviceIsOpen();
+    
     // Return true if device is collecting data
     bool touchkeyDeviceIsRunning();
     
     // Returns true if an error has occurred
-    bool touchkeyDeviceErrorOccurred() {
-        return touchkeyErrorOccurred_;
-    }
+    bool touchkeyDeviceErrorOccurred();
+    
     // Return the error message if one occurred
-    std::string touchkeyDeviceErrorMessage() {
-        return touchkeyErrorMessage_;
-    }
+    std::string touchkeyDeviceErrorMessage();
+    
     // How many octaves on the current device
-    int touchkeyDeviceNumberOfOctaves() {
-        return touchkeyController_.numberOfOctaves();
-    }
+    int touchkeyDeviceNumberOfOctaves();
+    
     // Return the lowest MIDI note
-    int touchkeyDeviceLowestMidiNote() {
-        return touchkeyController_.lowestMidiNote();
-    }
+    int touchkeyDeviceLowestMidiNote();
+    
     // Set the lowest MIDI note for the TouchKeys
-    void touchkeyDeviceSetLowestMidiNote(int note) {
-        keyboardDisplay_.clearAllTouches();
-        touchkeyEmulator_.setLowestMidiNote(note);
-        touchkeyController_.setLowestMidiNote(note);
-    }
+    void touchkeyDeviceSetLowestMidiNote(int note);
+    
     // Attempt to autodetect the correct TouchKey octave from MIDI data
     void touchkeyDeviceAutodetectLowestMidiNote();
     void touchkeyDeviceStopAutodetecting();
@@ -172,35 +163,17 @@
     void midiSegmentRemove(MidiKeyboardSegment *segment);
 
     // Select MIDI input/output devices
-    void enableMIDIInputPort(int portNumber, bool isPrimary) {
-        midiInputController_.enablePort(portNumber, isPrimary);
-    }
-    void enableAllMIDIInputPorts(int primaryPortNumber) {
-        midiInputController_.enableAllPorts(primaryPortNumber);
-    }
-    void disableMIDIInputPort(int portNumber) {
-        midiInputController_.disablePort(portNumber);
-    }
-    void disablePrimaryMIDIInputPort() {
-        midiInputController_.disablePrimaryPort();
-    }
-    void disableAllMIDIInputPorts(bool auxiliaryOnly) {
-        midiInputController_.disableAllPorts(auxiliaryOnly);
-    }
-    void enableMIDIOutputPort(int identifier, int deviceNumber) {
-        midiOutputController_.enablePort(identifier, deviceNumber);
-	}
+    void enableMIDIInputPort(int portNumber, bool isPrimary);
+    void enableAllMIDIInputPorts(int primaryPortNumber);
+    void disableMIDIInputPort(int portNumber);
+    void disablePrimaryMIDIInputPort();
+    void disableAllMIDIInputPorts(bool auxiliaryOnly);
+    void enableMIDIOutputPort(int identifier, int deviceNumber);
 #ifndef JUCE_WINDOWS
-    void enableMIDIOutputVirtualPort(int identifier, const char *name) {
-        midiOutputController_.enableVirtualPort(identifier, name);
-	}
+    void enableMIDIOutputVirtualPort(int identifier, const char *name);
 #endif
-    void disableMIDIOutputPort(int identifier) {
-        midiOutputController_.disablePort(identifier);
-    }
-    void disableAllMIDIOutputPorts() {
-        midiOutputController_.disableAllPorts();
-    }
+    void disableMIDIOutputPort(int identifier);
+    void disableAllMIDIOutputPorts();
     
     // Get selected MIDI input/output devices by ID
     int selectedMIDIPrimaryInputPort() {
@@ -226,64 +199,31 @@
     
     // *** OSC device methods ***
     
-    bool oscTransmitEnabled() {
-        return oscTransmitter_.enabled();
-    }
-    void oscTransmitSetEnabled(bool enable) {
-        oscTransmitter_.setEnabled(enable);
-    }
-    bool oscTransmitRawDataEnabled() {
-        return touchkeyController_.transmitRawDataEnabled();
-    }
-    void oscTransmitSetRawDataEnabled(bool enable) {
-        touchkeyController_.setTransmitRawData(enable);
-    }
-    std::vector<lo_address> oscTransmitAddresses() {
-        return oscTransmitter_.addresses();
-    }
-    int oscTransmitAddAddress(const char * host, const char * port, int proto = LO_UDP) {
-        return oscTransmitter_.addAddress(host, port, proto);
-    }
-	void oscTransmitRemoveAddress(int index) {
-        return oscTransmitter_.removeAddress(index);
-    }
-	void oscTransmitClearAddresses() {
-        return oscTransmitter_.clearAddresses();
-    }
+    bool oscTransmitEnabled();
+    void oscTransmitSetEnabled(bool enable);
+    bool oscTransmitRawDataEnabled();
+    void oscTransmitSetRawDataEnabled(bool enable);
+    std::vector<lo_address> oscTransmitAddresses();
+    int oscTransmitAddAddress(const char * host, const char * port, int proto = LO_UDP);
+	void oscTransmitRemoveAddress(int index);
+	void oscTransmitClearAddresses();
     
     // OSC Input (receiver) methods
     // Enable or disable on the OSC receive, and report is status
-    bool oscReceiveEnabled() {
-        return oscReceiveEnabled_;
-    }
+    bool oscReceiveEnabled();
+    
     // Enable method returns true on success (false only if it was
     // unable to set the port)
-    bool oscReceiveSetEnabled(bool enable) {
-        if(enable && !oscReceiveEnabled_) {
-            oscReceiveEnabled_ = true;
-            return oscReceiver_.setPort(oscReceivePort_);
-        }
-        else if(!enable && oscReceiveEnabled_) {
-            oscReceiveEnabled_ = false;
-            return oscReceiver_.setPort(0);
-        }
-        return true;
-    }
+    bool oscReceiveSetEnabled(bool enable);
     
     // Whether the OSC server is running (false means couldn't open port)
-    bool oscReceiveRunning() {
-        return oscReceiver_.running();
-    }
+    bool oscReceiveRunning();
+    
     // Get the current OSC receive port
-    int oscReceivePort() {
-        return oscReceivePort_;
-    }
+    int oscReceivePort();
+    
     // Set the current OSC receive port (returns true on success)
-    bool oscReceiveSetPort(int port) {
-        oscReceivePort_ = port;
-        return oscReceiver_.setPort(port);
-    }
-    
+    bool oscReceiveSetPort(int port);
     
     // *** Display methods ***
     
@@ -296,6 +236,13 @@
             keyboardDisplayWindow_->toFront(true);
         }
     }
+    void setPreferencesWindow(PreferencesWindow *window) { preferencesWindow_ = window; }
+    void showPreferencesWindow() {
+        if(preferencesWindow_ != 0) {
+            preferencesWindow_->setVisible(true);
+            preferencesWindow_->toFront(true);
+        }
+    }
 #endif
     
     // *** Logging methods ***
@@ -311,7 +258,6 @@
     
 	bool oscHandlerMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data);
     
-    
     // *** Mapping methods ***
     // Return the number of mapping factory types available
     int numberOfMappingFactories();
@@ -344,6 +290,38 @@
     // Clears the current preset and restores default settings to zones/mappings
     void clearPreset();
     
+    // *** Preferences ***
+    
+    // Whether to automatically start the TouchKeys on startup
+    bool getPrefsAutoStartTouchKeys();
+    void setPrefsAutoStartTouchKeys(bool autoStart);
+    
+    // Whether to automatically detect the TouchKeys octave when they start
+    bool getPrefsAutodetectOctave();
+    void setPrefsAutodetectOctave(bool autoDetect);
+    
+    // Which preset (if any) to load at startup
+    void setPrefsStartupPresetNone();
+    bool getPrefsStartupPresetNone();
+    
+    void setPrefsStartupPresetLastSaved();
+    bool getPrefsStartupPresetLastSaved();
+    
+    void setPrefsStartupPresetVibratoPitchBend();
+    bool getPrefsStartupPresetVibratoPitchBend();
+    
+    void setPrefsStartupPreset(String const& path);
+    String getPrefsStartupPreset();
+    
+    // Reset all preferences
+    void resetPreferences();
+    
+    // Load global preferences from file
+    void loadApplicationPreferences();
+    
+    // Load a MIDI output device from preexisting application preferences
+    void loadMIDIOutputFromApplicationPreferences(int zone);
+    
 #ifdef ENABLE_TOUCHKEYS_SENSOR_TEST
     // *** TouchKeys sensor testing methods ***
     // Start testing the TouchKeys sensors
@@ -370,6 +348,9 @@
     bool savePresetHelper(File& outputFile);
     bool loadPresetHelper(File const& inputFile);
     
+    // Application properties: for managing preferences
+    ApplicationProperties applicationProperties_;
+    
     // TouchKeys objects
     PianoKeyboard keyboardController_;
     MidiInputController midiInputController_;
@@ -403,6 +384,7 @@
     DocumentWindow *keyboardDisplayWindow_;
     KeyboardTesterDisplay *keyboardTesterDisplay_;
     GraphicsDisplayWindow *keyboardTesterWindow_;
+    PreferencesWindow *preferencesWindow_;
 #endif
     
     // Segment info
--- a/Source/Mappings/Control/TouchkeyControlMapping.cpp	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/Mappings/Control/TouchkeyControlMapping.cpp	Sat Jun 21 23:32:33 2014 +0100
@@ -366,12 +366,12 @@
                     if(latestValue > 0)
                         latestValue = 0;
                 }
+                
+                if(direction_ == kDirectionNegative)
+                    latestValue = -latestValue;
+                else if((direction_ == kDirectionBoth) && latestValue < 0)
+                    latestValue = -latestValue;
             }
-
-            if(direction_ == kDirectionNegative)
-                latestValue = -latestValue;
-            else if((direction_ == kDirectionBoth) && latestValue < 0)
-                latestValue = -latestValue;
             
             sendControlMessage(latestValue);
             lastControlValue_ = latestValue;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Source/Mappings/Control/TouchkeyControlMappingExtendedEditor.cpp	Sat Jun 21 23:32:33 2014 +0100
@@ -0,0 +1,751 @@
+/*
+  ==============================================================================
+
+  This is an automatically generated GUI class created by the Introjucer!
+
+  Be careful when adding custom code to these files, as only the code within
+  the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded
+  and re-saved.
+
+  Created with Introjucer version: 3.1.0
+
+  ------------------------------------------------------------------------------
+
+  The Introjucer is part of the JUCE library - "Jules' Utility Class Extensions"
+  Copyright 2004-13 by Raw Material Software Ltd.
+
+  ==============================================================================
+*/
+
+//[Headers] You can add your own extra header files here...
+//[/Headers]
+
+#include "TouchkeyControlMappingExtendedEditor.h"
+
+
+//[MiscUserDefs] You can add your own user definitions and misc code here...
+//[/MiscUserDefs]
+
+//==============================================================================
+TouchkeyControlMappingExtendedEditor::TouchkeyControlMappingExtendedEditor (TouchkeyControlMappingFactory& factory)
+    : factory_(factory)
+{
+    addAndMakeVisible (inputRangeLowEditor = new TextEditor ("range low text editor"));
+    inputRangeLowEditor->setMultiLine (false);
+    inputRangeLowEditor->setReturnKeyStartsNewLine (false);
+    inputRangeLowEditor->setReadOnly (false);
+    inputRangeLowEditor->setScrollbarsShown (true);
+    inputRangeLowEditor->setCaretVisible (true);
+    inputRangeLowEditor->setPopupMenuEnabled (true);
+    inputRangeLowEditor->setText (String::empty);
+
+    addAndMakeVisible (rangeLabel = new Label ("range label",
+                                               "Input Range:"));
+    rangeLabel->setFont (Font (15.00f, Font::plain));
+    rangeLabel->setJustificationType (Justification::centredLeft);
+    rangeLabel->setEditable (false, false, false);
+    rangeLabel->setColour (TextEditor::textColourId, Colours::black);
+    rangeLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (controlLabel = new Label ("control label",
+                                                 "To Control:"));
+    controlLabel->setFont (Font (15.00f, Font::plain));
+    controlLabel->setJustificationType (Justification::centredRight);
+    controlLabel->setEditable (false, false, false);
+    controlLabel->setColour (TextEditor::textColourId, Colours::black);
+    controlLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (controlComboBox = new ComboBox ("control combo box"));
+    controlComboBox->setEditableText (false);
+    controlComboBox->setJustificationType (Justification::centredLeft);
+    controlComboBox->setTextWhenNothingSelected (String::empty);
+    controlComboBox->setTextWhenNoChoicesAvailable ("(no choices)");
+    controlComboBox->addListener (this);
+
+    addAndMakeVisible (controlLabel2 = new Label ("control label",
+                                                  "Parameter:"));
+    controlLabel2->setFont (Font (15.00f, Font::plain));
+    controlLabel2->setJustificationType (Justification::centredLeft);
+    controlLabel2->setEditable (false, false, false);
+    controlLabel2->setColour (TextEditor::textColourId, Colours::black);
+    controlLabel2->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (parameterComboBox = new ComboBox ("parameter combo box"));
+    parameterComboBox->setEditableText (false);
+    parameterComboBox->setJustificationType (Justification::centredLeft);
+    parameterComboBox->setTextWhenNothingSelected (String::empty);
+    parameterComboBox->setTextWhenNoChoicesAvailable ("(no choices)");
+    parameterComboBox->addListener (this);
+
+    addAndMakeVisible (controlLabel3 = new Label ("control label",
+                                                  "Type:"));
+    controlLabel3->setFont (Font (15.00f, Font::plain));
+    controlLabel3->setJustificationType (Justification::centredRight);
+    controlLabel3->setEditable (false, false, false);
+    controlLabel3->setColour (TextEditor::textColourId, Colours::black);
+    controlLabel3->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (typeComboBox = new ComboBox ("type combo box"));
+    typeComboBox->setEditableText (false);
+    typeComboBox->setJustificationType (Justification::centredLeft);
+    typeComboBox->setTextWhenNothingSelected (String::empty);
+    typeComboBox->setTextWhenNoChoicesAvailable ("(no choices)");
+    typeComboBox->addListener (this);
+
+    addAndMakeVisible (inputRangeHighEditor = new TextEditor ("range hi text editor"));
+    inputRangeHighEditor->setMultiLine (false);
+    inputRangeHighEditor->setReturnKeyStartsNewLine (false);
+    inputRangeHighEditor->setReadOnly (false);
+    inputRangeHighEditor->setScrollbarsShown (true);
+    inputRangeHighEditor->setCaretVisible (true);
+    inputRangeHighEditor->setPopupMenuEnabled (true);
+    inputRangeHighEditor->setText (String::empty);
+
+    addAndMakeVisible (rangeLabel2 = new Label ("range label",
+                                                "-"));
+    rangeLabel2->setFont (Font (15.00f, Font::plain));
+    rangeLabel2->setJustificationType (Justification::centredLeft);
+    rangeLabel2->setEditable (false, false, false);
+    rangeLabel2->setColour (TextEditor::textColourId, Colours::black);
+    rangeLabel2->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (rangeLabel3 = new Label ("range label",
+                                                "Output Range:"));
+    rangeLabel3->setFont (Font (15.00f, Font::plain));
+    rangeLabel3->setJustificationType (Justification::centredLeft);
+    rangeLabel3->setEditable (false, false, false);
+    rangeLabel3->setColour (TextEditor::textColourId, Colours::black);
+    rangeLabel3->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (outputRangeLowEditor = new TextEditor ("output range low text editor"));
+    outputRangeLowEditor->setMultiLine (false);
+    outputRangeLowEditor->setReturnKeyStartsNewLine (false);
+    outputRangeLowEditor->setReadOnly (false);
+    outputRangeLowEditor->setScrollbarsShown (true);
+    outputRangeLowEditor->setCaretVisible (true);
+    outputRangeLowEditor->setPopupMenuEnabled (true);
+    outputRangeLowEditor->setText (String::empty);
+
+    addAndMakeVisible (outputRangeHighEditor = new TextEditor ("output range hi text editor"));
+    outputRangeHighEditor->setMultiLine (false);
+    outputRangeHighEditor->setReturnKeyStartsNewLine (false);
+    outputRangeHighEditor->setReadOnly (false);
+    outputRangeHighEditor->setScrollbarsShown (true);
+    outputRangeHighEditor->setCaretVisible (true);
+    outputRangeHighEditor->setPopupMenuEnabled (true);
+    outputRangeHighEditor->setText (String::empty);
+
+    addAndMakeVisible (rangeLabel4 = new Label ("range label",
+                                                "-"));
+    rangeLabel4->setFont (Font (15.00f, Font::plain));
+    rangeLabel4->setJustificationType (Justification::centredLeft);
+    rangeLabel4->setEditable (false, false, false);
+    rangeLabel4->setColour (TextEditor::textColourId, Colours::black);
+    rangeLabel4->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (controlLabel4 = new Label ("control label",
+                                                  "Direction:"));
+    controlLabel4->setFont (Font (15.00f, Font::plain));
+    controlLabel4->setJustificationType (Justification::centredRight);
+    controlLabel4->setEditable (false, false, false);
+    controlLabel4->setColour (TextEditor::textColourId, Colours::black);
+    controlLabel4->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (directionComboBox = new ComboBox ("direction combo box"));
+    directionComboBox->setEditableText (false);
+    directionComboBox->setJustificationType (Justification::centredLeft);
+    directionComboBox->setTextWhenNothingSelected (String::empty);
+    directionComboBox->setTextWhenNoChoicesAvailable ("(no choices)");
+    directionComboBox->addListener (this);
+
+    addAndMakeVisible (titleLabel = new Label ("title label",
+                                               "Control Mapping (Zone N, #M)"));
+    titleLabel->setFont (Font (15.00f, Font::bold));
+    titleLabel->setJustificationType (Justification::centredLeft);
+    titleLabel->setEditable (false, false, false);
+    titleLabel->setColour (TextEditor::textColourId, Colours::black);
+    titleLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (rangeLabel5 = new Label ("range label",
+                                                "Threshold:"));
+    rangeLabel5->setFont (Font (15.00f, Font::plain));
+    rangeLabel5->setJustificationType (Justification::centredLeft);
+    rangeLabel5->setEditable (false, false, false);
+    rangeLabel5->setColour (TextEditor::textColourId, Colours::black);
+    rangeLabel5->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (thresholdEditor = new TextEditor ("threshold text editor"));
+    thresholdEditor->setMultiLine (false);
+    thresholdEditor->setReturnKeyStartsNewLine (false);
+    thresholdEditor->setReadOnly (false);
+    thresholdEditor->setScrollbarsShown (true);
+    thresholdEditor->setCaretVisible (true);
+    thresholdEditor->setPopupMenuEnabled (true);
+    thresholdEditor->setText (String::empty);
+
+    addAndMakeVisible (cc14BitButton = new ToggleButton ("new toggle button"));
+    cc14BitButton->setButtonText ("Use 14-bit CC");
+    cc14BitButton->addListener (this);
+
+    addAndMakeVisible (ignore2FingersButton = new ToggleButton ("ignore 2 fingers toggle button"));
+    ignore2FingersButton->setButtonText ("Ignore 2 Fingers");
+    ignore2FingersButton->addListener (this);
+
+    addAndMakeVisible (ignore3FingersButton = new ToggleButton ("ignore 3 fingers toggle button"));
+    ignore3FingersButton->setButtonText ("Ignore 3 Fingers");
+    ignore3FingersButton->addListener (this);
+
+    addAndMakeVisible (controlLabel6 = new Label ("control label",
+                                                  "Out of Range:"));
+    controlLabel6->setFont (Font (15.00f, Font::plain));
+    controlLabel6->setJustificationType (Justification::centredRight);
+    controlLabel6->setEditable (false, false, false);
+    controlLabel6->setColour (TextEditor::textColourId, Colours::black);
+    controlLabel6->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (outOfRangeComboBox = new ComboBox ("out of range combo box"));
+    outOfRangeComboBox->setEditableText (false);
+    outOfRangeComboBox->setJustificationType (Justification::centredLeft);
+    outOfRangeComboBox->setTextWhenNothingSelected (String::empty);
+    outOfRangeComboBox->setTextWhenNoChoicesAvailable ("(no choices)");
+    outOfRangeComboBox->addListener (this);
+
+    addAndMakeVisible (rangeLabel6 = new Label ("range label",
+                                                "Default Output:"));
+    rangeLabel6->setFont (Font (15.00f, Font::plain));
+    rangeLabel6->setJustificationType (Justification::centredLeft);
+    rangeLabel6->setEditable (false, false, false);
+    rangeLabel6->setColour (TextEditor::textColourId, Colours::black);
+    rangeLabel6->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (outputDefaultEditor = new TextEditor ("output default text editor"));
+    outputDefaultEditor->setMultiLine (false);
+    outputDefaultEditor->setReturnKeyStartsNewLine (false);
+    outputDefaultEditor->setReadOnly (false);
+    outputDefaultEditor->setScrollbarsShown (true);
+    outputDefaultEditor->setCaretVisible (true);
+    outputDefaultEditor->setPopupMenuEnabled (true);
+    outputDefaultEditor->setText (String::empty);
+
+
+    //[UserPreSize]
+    parameterComboBox->addItem("X Position", TouchkeyControlMapping::kInputParameterXPosition);
+    parameterComboBox->addItem("Y Position", TouchkeyControlMapping::kInputParameterYPosition);
+    parameterComboBox->addItem("Contact Area", TouchkeyControlMapping::kInputParameterTouchSize);
+    parameterComboBox->addItem("2-Finger Mean", TouchkeyControlMapping::kInputParameter2FingerMean);
+    parameterComboBox->addItem("2-Finger Distance", TouchkeyControlMapping::kInputParameter2FingerDistance);
+
+    typeComboBox->addItem("Absolute", TouchkeyControlMapping::kTypeAbsolute);
+    typeComboBox->addItem("1st Touch Relative", TouchkeyControlMapping::kTypeFirstTouchRelative);
+    typeComboBox->addItem("Note Onset Relative", TouchkeyControlMapping::kTypeNoteOnsetRelative);
+
+    controlComboBox->addItem("Pitch Wheel", MidiKeyboardSegment::kControlPitchWheel);
+    controlComboBox->addItem("Channel Pressure", MidiKeyboardSegment::kControlChannelAftertouch);
+    controlComboBox->addItem("Poly Aftertouch", MidiKeyboardSegment::kControlPolyphonicAftertouch);
+    for(int i = 1; i <= 119; i++) {
+        controlComboBox->addItem(String(i), i);
+    }
+
+    directionComboBox->addItem("Normal", TouchkeyControlMapping::kDirectionPositive);
+    directionComboBox->addItem("Reverse", TouchkeyControlMapping::kDirectionNegative);
+    directionComboBox->addItem("Always Positive", TouchkeyControlMapping::kDirectionBoth);
+
+    outOfRangeComboBox->addItem("Ignore", OscMidiConverter::kOutOfRangeIgnore);
+    outOfRangeComboBox->addItem("Clip", OscMidiConverter::kOutOfRangeClip);
+    outOfRangeComboBox->addItem("Extrapolate", OscMidiConverter::kOutOfRangeExtrapolate);
+    //[/UserPreSize]
+
+    setSize (448, 248);
+
+
+    //[Constructor] You can add your own custom stuff here..
+    inputRangeLowEditor->addListener(this);
+    inputRangeHighEditor->addListener(this);
+    outputRangeLowEditor->addListener(this);
+    outputRangeHighEditor->addListener(this);
+    outputDefaultEditor->addListener(this);
+    thresholdEditor->addListener(this);
+    //[/Constructor]
+}
+
+TouchkeyControlMappingExtendedEditor::~TouchkeyControlMappingExtendedEditor()
+{
+    //[Destructor_pre]. You can add your own custom destruction code here..
+    //[/Destructor_pre]
+
+    inputRangeLowEditor = nullptr;
+    rangeLabel = nullptr;
+    controlLabel = nullptr;
+    controlComboBox = nullptr;
+    controlLabel2 = nullptr;
+    parameterComboBox = nullptr;
+    controlLabel3 = nullptr;
+    typeComboBox = nullptr;
+    inputRangeHighEditor = nullptr;
+    rangeLabel2 = nullptr;
+    rangeLabel3 = nullptr;
+    outputRangeLowEditor = nullptr;
+    outputRangeHighEditor = nullptr;
+    rangeLabel4 = nullptr;
+    controlLabel4 = nullptr;
+    directionComboBox = nullptr;
+    titleLabel = nullptr;
+    rangeLabel5 = nullptr;
+    thresholdEditor = nullptr;
+    cc14BitButton = nullptr;
+    ignore2FingersButton = nullptr;
+    ignore3FingersButton = nullptr;
+    controlLabel6 = nullptr;
+    outOfRangeComboBox = nullptr;
+    rangeLabel6 = nullptr;
+    outputDefaultEditor = nullptr;
+
+
+    //[Destructor]. You can add your own custom destruction code here..
+    //[/Destructor]
+}
+
+//==============================================================================
+void TouchkeyControlMappingExtendedEditor::paint (Graphics& g)
+{
+    //[UserPrePaint] Add your own custom painting code here..
+    //[/UserPrePaint]
+
+    g.fillAll (Colour (0xffd2d2d2));
+
+    //[UserPaint] Add your own custom painting code here..
+    //[/UserPaint]
+}
+
+void TouchkeyControlMappingExtendedEditor::resized()
+{
+    inputRangeLowEditor->setBounds (112, 72, 56, 24);
+    rangeLabel->setBounds (8, 72, 104, 24);
+    controlLabel->setBounds (256, 40, 64, 24);
+    controlComboBox->setBounds (320, 40, 112, 24);
+    controlLabel2->setBounds (8, 40, 72, 24);
+    parameterComboBox->setBounds (80, 40, 160, 24);
+    controlLabel3->setBounds (264, 104, 56, 24);
+    typeComboBox->setBounds (320, 104, 112, 24);
+    inputRangeHighEditor->setBounds (184, 72, 56, 24);
+    rangeLabel2->setBounds (168, 72, 16, 24);
+    rangeLabel3->setBounds (8, 104, 96, 24);
+    outputRangeLowEditor->setBounds (112, 104, 56, 24);
+    outputRangeHighEditor->setBounds (184, 104, 56, 24);
+    rangeLabel4->setBounds (168, 104, 16, 24);
+    controlLabel4->setBounds (248, 136, 72, 24);
+    directionComboBox->setBounds (320, 136, 112, 24);
+    titleLabel->setBounds (8, 8, 424, 24);
+    rangeLabel5->setBounds (8, 168, 72, 24);
+    thresholdEditor->setBounds (112, 168, 56, 24);
+    cc14BitButton->setBounds (320, 72, 112, 24);
+    ignore2FingersButton->setBounds (8, 192, 128, 24);
+    ignore3FingersButton->setBounds (8, 216, 128, 24);
+    controlLabel6->setBounds (216, 168, 104, 24);
+    outOfRangeComboBox->setBounds (320, 168, 112, 24);
+    rangeLabel6->setBounds (8, 136, 96, 24);
+    outputDefaultEditor->setBounds (112, 136, 56, 24);
+    //[UserResized] Add your own custom resize handling here..
+    //[/UserResized]
+}
+
+void TouchkeyControlMappingExtendedEditor::comboBoxChanged (ComboBox* comboBoxThatHasChanged)
+{
+    //[UsercomboBoxChanged_Pre]
+    //[/UsercomboBoxChanged_Pre]
+
+    if (comboBoxThatHasChanged == controlComboBox)
+    {
+        //[UserComboBoxCode_controlComboBox] -- add your combo box handling code here..
+        int controller = controlComboBox->getSelectedId();
+        factory_.setController(controller);
+        //[/UserComboBoxCode_controlComboBox]
+    }
+    else if (comboBoxThatHasChanged == parameterComboBox)
+    {
+        //[UserComboBoxCode_parameterComboBox] -- add your combo box handling code here..
+        int param = parameterComboBox->getSelectedId();
+        factory_.setInputParameter(param);
+        //[/UserComboBoxCode_parameterComboBox]
+    }
+    else if (comboBoxThatHasChanged == typeComboBox)
+    {
+        //[UserComboBoxCode_typeComboBox] -- add your combo box handling code here..
+        int type = typeComboBox->getSelectedId();
+        factory_.setInputType(type);
+        //[/UserComboBoxCode_typeComboBox]
+    }
+    else if (comboBoxThatHasChanged == directionComboBox)
+    {
+        //[UserComboBoxCode_directionComboBox] -- add your combo box handling code here..
+        int direction = directionComboBox->getSelectedId();
+        factory_.setDirection(direction);
+        //[/UserComboBoxCode_directionComboBox]
+    }
+    else if (comboBoxThatHasChanged == outOfRangeComboBox)
+    {
+        //[UserComboBoxCode_outOfRangeComboBox] -- add your combo box handling code here..
+        int behavior = outOfRangeComboBox->getSelectedId();
+        factory_.setOutOfRangeBehavior(behavior);
+        //[/UserComboBoxCode_outOfRangeComboBox]
+    }
+
+    //[UsercomboBoxChanged_Post]
+    //[/UsercomboBoxChanged_Post]
+}
+
+void TouchkeyControlMappingExtendedEditor::buttonClicked (Button* buttonThatWasClicked)
+{
+    //[UserbuttonClicked_Pre]
+    //[/UserbuttonClicked_Pre]
+
+    if (buttonThatWasClicked == cc14BitButton)
+    {
+        //[UserButtonCode_cc14BitButton] -- add your button handler code here..
+        factory_.setUses14BitControl(cc14BitButton->getToggleState());
+        //[/UserButtonCode_cc14BitButton]
+    }
+    else if (buttonThatWasClicked == ignore2FingersButton)
+    {
+        //[UserButtonCode_ignore2FingersButton] -- add your button handler code here..
+        factory_.setIgnoresTwoFingers(ignore2FingersButton->getToggleState());
+        //[/UserButtonCode_ignore2FingersButton]
+    }
+    else if (buttonThatWasClicked == ignore3FingersButton)
+    {
+        //[UserButtonCode_ignore3FingersButton] -- add your button handler code here..
+        factory_.setIgnoresThreeFingers(ignore3FingersButton->getToggleState());
+        //[/UserButtonCode_ignore3FingersButton]
+    }
+
+    //[UserbuttonClicked_Post]
+    //[/UserbuttonClicked_Post]
+}
+
+
+
+//[MiscUserCode] You can add your own definitions of your custom methods or any other code here...
+
+void TouchkeyControlMappingExtendedEditor::textEditorReturnKeyPressed(TextEditor &editor)
+{
+    if(&editor == inputRangeLowEditor) {
+        float range = atof(inputRangeLowEditor->getText().toUTF8());
+        factory_.setRangeInputMin(range);
+        factory_.setRangeInputCenter(range);
+    }
+    else if(&editor == inputRangeHighEditor) {
+        float range = atof(inputRangeHighEditor->getText().toUTF8());
+        factory_.setRangeInputMax(range);
+    }
+    else if(&editor == outputRangeLowEditor) {
+        float range = atof(outputRangeLowEditor->getText().toUTF8());
+        factory_.setRangeOutputMin(range);
+    }
+    else if(&editor == outputRangeHighEditor) {
+        float range = atof(outputRangeHighEditor->getText().toUTF8());
+        factory_.setRangeOutputMax(range);
+    }
+    else if(&editor == outputDefaultEditor) {
+        float range = atof(outputDefaultEditor->getText().toUTF8());
+        factory_.setRangeOutputDefault(range);
+    }
+    else if(&editor == thresholdEditor) {
+        float thresh = atof(thresholdEditor->getText().toUTF8());
+        factory_.setThreshold(thresh);
+    }
+}
+
+void TouchkeyControlMappingExtendedEditor::textEditorEscapeKeyPressed(TextEditor &editor)
+{
+
+}
+
+void TouchkeyControlMappingExtendedEditor::textEditorFocusLost(TextEditor &editor)
+{
+    textEditorReturnKeyPressed(editor);
+}
+
+void TouchkeyControlMappingExtendedEditor::synchronize()
+{
+    // Set the title label
+    titleLabel->setText(getDescriptionHelper("Control Mapping"), dontSendNotification);
+
+    // Update the editors to reflect the current status
+    if(!inputRangeLowEditor->hasKeyboardFocus(true)) {
+        float value = factory_.getRangeInputMin();
+        char st[16];
+#ifdef _MSC_VER
+		_snprintf_s(st, 16, _TRUNCATE, "%.2f", value);
+#else
+        snprintf(st, 16, "%.2f", value);
+#endif
+        inputRangeLowEditor->setText(st);
+    }
+
+    if(!inputRangeHighEditor->hasKeyboardFocus(true)) {
+        float value = factory_.getRangeInputMax();
+        char st[16];
+#ifdef _MSC_VER
+		_snprintf_s(st, 16, _TRUNCATE, "%.2f", value);
+#else
+        snprintf(st, 16, "%.2f", value);
+#endif
+
+        inputRangeHighEditor->setText(st);
+    }
+
+    if(!outputRangeLowEditor->hasKeyboardFocus(true)) {
+        float value = factory_.getRangeOutputMin();
+        char st[16];
+#ifdef _MSC_VER
+		_snprintf_s(st, 16, _TRUNCATE, "%.2f", value);
+#else
+        snprintf(st, 16, "%.2f", value);
+#endif
+
+        outputRangeLowEditor->setText(st);
+    }
+
+    if(!outputRangeHighEditor->hasKeyboardFocus(true)) {
+        float value = factory_.getRangeOutputMax();
+        char st[16];
+#ifdef _MSC_VER
+		_snprintf_s(st, 16, _TRUNCATE, "%.2f", value);
+#else
+        snprintf(st, 16, "%.2f", value);
+#endif
+
+        outputRangeHighEditor->setText(st);
+    }
+    
+    if(!outputDefaultEditor->hasKeyboardFocus(true)) {
+        float value = factory_.getRangeOutputDefault();
+        char st[16];
+#ifdef _MSC_VER
+		_snprintf_s(st, 16, _TRUNCATE, "%.2f", value);
+#else
+        snprintf(st, 16, "%.2f", value);
+#endif
+        
+        outputDefaultEditor->setText(st);
+    }
+
+    if(factory_.getInputType() == TouchkeyControlMapping::kTypeFirstTouchRelative
+       || factory_.getInputType() == TouchkeyControlMapping::kTypeNoteOnsetRelative) {
+        thresholdEditor->setEnabled(true);
+        if(!thresholdEditor->hasKeyboardFocus(true)) {
+            float value = factory_.getThreshold();
+            char st[16];
+#ifdef _MSC_VER
+            _snprintf_s(st, 16, _TRUNCATE, "%.2f", value);
+#else
+            snprintf(st, 16, "%.2f", value);
+#endif
+
+            thresholdEditor->setText(st);
+        }
+
+        if(typeWasAbsolute_) {
+            // Add all three direction items
+            directionComboBox->clear();
+            directionComboBox->addItem("Normal", TouchkeyControlMapping::kDirectionPositive);
+            directionComboBox->addItem("Reverse", TouchkeyControlMapping::kDirectionNegative);
+            directionComboBox->addItem("Always Positive", TouchkeyControlMapping::kDirectionBoth);
+        }
+
+        typeWasAbsolute_ = false;
+    }
+    else {
+        thresholdEditor->setEnabled(false);
+        thresholdEditor->setText("", false);
+
+        if(!typeWasAbsolute_) {
+            // Add only one direction item
+            directionComboBox->clear();
+            directionComboBox->addItem("Normal", TouchkeyControlMapping::kDirectionPositive);
+        }
+
+        typeWasAbsolute_ = true;
+    }
+
+    if(factory_.getController() == MidiKeyboardSegment::kControlPitchWheel) {
+        cc14BitButton->setEnabled(false);
+        cc14BitButton->setToggleState(true, dontSendNotification);
+    }
+    else if(factory_.getController() == MidiKeyboardSegment::kControlPolyphonicAftertouch ||
+            factory_.getController() == MidiKeyboardSegment::kControlChannelAftertouch) {
+        cc14BitButton->setEnabled(false);
+        cc14BitButton->setToggleState(false, dontSendNotification);
+    }
+    else {
+        cc14BitButton->setEnabled(true);
+        cc14BitButton->setToggleState(factory_.getUses14BitControl(), dontSendNotification);
+    }
+    ignore2FingersButton->setToggleState(factory_.getIgnoresTwoFingers(), dontSendNotification);
+    ignore3FingersButton->setToggleState(factory_.getIgnoresThreeFingers(), dontSendNotification);
+
+    parameterComboBox->setSelectedId(factory_.getInputParameter(), dontSendNotification);
+    typeComboBox->setSelectedId(factory_.getInputType(), dontSendNotification);
+    controlComboBox->setSelectedId(factory_.getController(), dontSendNotification);
+    directionComboBox->setSelectedId(factory_.getDirection(), dontSendNotification);
+    outOfRangeComboBox->setSelectedId(factory_.getOutOfRangeBehavior(), dontSendNotification);
+}
+
+// Return a human-readable description of this mapping for the window
+String TouchkeyControlMappingExtendedEditor::getDescription() {
+    return getDescriptionHelper("Control");
+}
+
+// Return a human-readable description of this mapping for the window
+String TouchkeyControlMappingExtendedEditor::getDescriptionHelper(String baseName) {
+    String desc = baseName;
+
+    desc += " (Zone ";
+
+    int zone = factory_.segment().outputPort();
+    desc += zone;
+    desc += ", #";
+
+    int mappingNumber = factory_.segment().indexOfMappingFactory(&factory_);
+    desc += mappingNumber;
+    desc += ")";
+
+    return desc;
+}
+
+//[/MiscUserCode]
+
+
+//==============================================================================
+#if 0
+/*  -- Introjucer information section --
+
+    This is where the Introjucer stores the metadata that describe this GUI layout, so
+    make changes in here at your peril!
+
+BEGIN_JUCER_METADATA
+
+<JUCER_COMPONENT documentType="Component" className="TouchkeyControlMappingExtendedEditor"
+                 componentName="" parentClasses="public MappingEditorComponent, public TextEditor::Listener"
+                 constructorParams="TouchkeyControlMappingFactory&amp; factory"
+                 variableInitialisers="factory_(factory)" snapPixels="8" snapActive="1"
+                 snapShown="1" overlayOpacity="0.330" fixedSize="1" initialWidth="448"
+                 initialHeight="248">
+  <BACKGROUND backgroundColour="ffd2d2d2"/>
+  <TEXTEDITOR name="range low text editor" id="db0f62c03a58af03" memberName="inputRangeLowEditor"
+              virtualName="" explicitFocusOrder="0" pos="112 72 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <LABEL name="range label" id="1ca2d422f4c37b7f" memberName="rangeLabel"
+         virtualName="" explicitFocusOrder="0" pos="8 72 104 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Input Range:" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="0" italic="0" justification="33"/>
+  <LABEL name="control label" id="f953b12999632418" memberName="controlLabel"
+         virtualName="" explicitFocusOrder="0" pos="256 40 64 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="To Control:" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="0" italic="0" justification="34"/>
+  <COMBOBOX name="control combo box" id="f1c84bb5fd2730fb" memberName="controlComboBox"
+            virtualName="" explicitFocusOrder="0" pos="320 40 112 24" editable="0"
+            layout="33" items="" textWhenNonSelected="" textWhenNoItems="(no choices)"/>
+  <LABEL name="control label" id="5ef7c1b78fdcf616" memberName="controlLabel2"
+         virtualName="" explicitFocusOrder="0" pos="8 40 72 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Parameter:" editableSingleClick="0" editableDoubleClick="0"
+         focusDiscardsChanges="0" fontname="Default font" fontsize="15"
+         bold="0" italic="0" justification="33"/>
+  <COMBOBOX name="parameter combo box" id="f12f6f6e31042be1" memberName="parameterComboBox"
+            virtualName="" explicitFocusOrder="0" pos="80 40 160 24" editable="0"
+            layout="33" items="" textWhenNonSelected="" textWhenNoItems="(no choices)"/>
+  <LABEL name="control label" id="9ded92e82db31777" memberName="controlLabel3"
+         virtualName="" explicitFocusOrder="0" pos="264 104 56 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Type:" editableSingleClick="0" editableDoubleClick="0"
+         focusDiscardsChanges="0" fontname="Default font" fontsize="15"
+         bold="0" italic="0" justification="34"/>
+  <COMBOBOX name="type combo box" id="82d38054016f6c4f" memberName="typeComboBox"
+            virtualName="" explicitFocusOrder="0" pos="320 104 112 24" editable="0"
+            layout="33" items="" textWhenNonSelected="" textWhenNoItems="(no choices)"/>
+  <TEXTEDITOR name="range hi text editor" id="c34ac3e87db289d1" memberName="inputRangeHighEditor"
+              virtualName="" explicitFocusOrder="0" pos="184 72 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <LABEL name="range label" id="19e0ad80306cc4c0" memberName="rangeLabel2"
+         virtualName="" explicitFocusOrder="0" pos="168 72 16 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="-" editableSingleClick="0" editableDoubleClick="0"
+         focusDiscardsChanges="0" fontname="Default font" fontsize="15"
+         bold="0" italic="0" justification="33"/>
+  <LABEL name="range label" id="24ab2fe34fec697f" memberName="rangeLabel3"
+         virtualName="" explicitFocusOrder="0" pos="8 104 96 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Output Range:" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="0" italic="0" justification="33"/>
+  <TEXTEDITOR name="output range low text editor" id="15865c99a3cac858" memberName="outputRangeLowEditor"
+              virtualName="" explicitFocusOrder="0" pos="112 104 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <TEXTEDITOR name="output range hi text editor" id="6f3a73d113c72696" memberName="outputRangeHighEditor"
+              virtualName="" explicitFocusOrder="0" pos="184 104 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <LABEL name="range label" id="a86c42d542ee8780" memberName="rangeLabel4"
+         virtualName="" explicitFocusOrder="0" pos="168 104 16 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="-" editableSingleClick="0" editableDoubleClick="0"
+         focusDiscardsChanges="0" fontname="Default font" fontsize="15"
+         bold="0" italic="0" justification="33"/>
+  <LABEL name="control label" id="ff30dace0846c523" memberName="controlLabel4"
+         virtualName="" explicitFocusOrder="0" pos="248 136 72 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Direction:" editableSingleClick="0" editableDoubleClick="0"
+         focusDiscardsChanges="0" fontname="Default font" fontsize="15"
+         bold="0" italic="0" justification="34"/>
+  <COMBOBOX name="direction combo box" id="c46a92a83dfb204b" memberName="directionComboBox"
+            virtualName="" explicitFocusOrder="0" pos="320 136 112 24" editable="0"
+            layout="33" items="" textWhenNonSelected="" textWhenNoItems="(no choices)"/>
+  <LABEL name="title label" id="2346b62ce034bea2" memberName="titleLabel"
+         virtualName="" explicitFocusOrder="0" pos="8 8 424 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Control Mapping (Zone N, #M)" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="1" italic="0" justification="33"/>
+  <LABEL name="range label" id="41edb21ea9cb0304" memberName="rangeLabel5"
+         virtualName="" explicitFocusOrder="0" pos="8 168 72 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Threshold:" editableSingleClick="0" editableDoubleClick="0"
+         focusDiscardsChanges="0" fontname="Default font" fontsize="15"
+         bold="0" italic="0" justification="33"/>
+  <TEXTEDITOR name="threshold text editor" id="48a7ef0bf62a7fe6" memberName="thresholdEditor"
+              virtualName="" explicitFocusOrder="0" pos="112 168 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <TOGGLEBUTTON name="new toggle button" id="f75c92be72563883" memberName="cc14BitButton"
+                virtualName="" explicitFocusOrder="0" pos="320 72 112 24" buttonText="Use 14-bit CC"
+                connectedEdges="0" needsCallback="1" radioGroupId="0" state="0"/>
+  <TOGGLEBUTTON name="ignore 2 fingers toggle button" id="ec82d35a4bbc6688" memberName="ignore2FingersButton"
+                virtualName="" explicitFocusOrder="0" pos="8 192 128 24" buttonText="Ignore 2 Fingers"
+                connectedEdges="0" needsCallback="1" radioGroupId="0" state="0"/>
+  <TOGGLEBUTTON name="ignore 3 fingers toggle button" id="9b08149fc48c8b0" memberName="ignore3FingersButton"
+                virtualName="" explicitFocusOrder="0" pos="8 216 128 24" buttonText="Ignore 3 Fingers"
+                connectedEdges="0" needsCallback="1" radioGroupId="0" state="0"/>
+  <LABEL name="control label" id="668b66775f7ab754" memberName="controlLabel6"
+         virtualName="" explicitFocusOrder="0" pos="216 168 104 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Out of Range:" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="0" italic="0" justification="34"/>
+  <COMBOBOX name="out of range combo box" id="6c7a92d782955f43" memberName="outOfRangeComboBox"
+            virtualName="" explicitFocusOrder="0" pos="320 168 112 24" editable="0"
+            layout="33" items="" textWhenNonSelected="" textWhenNoItems="(no choices)"/>
+  <LABEL name="range label" id="6f03f49baf05157b" memberName="rangeLabel6"
+         virtualName="" explicitFocusOrder="0" pos="8 136 96 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Default Output:" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="0" italic="0" justification="33"/>
+  <TEXTEDITOR name="output default text editor" id="403f9ffcb91633fd" memberName="outputDefaultEditor"
+              virtualName="" explicitFocusOrder="0" pos="112 136 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+</JUCER_COMPONENT>
+
+END_JUCER_METADATA
+*/
+#endif
+
+
+//[EndFile] You can add extra defines here...
+//[/EndFile]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Source/Mappings/Control/TouchkeyControlMappingExtendedEditor.h	Sat Jun 21 23:32:33 2014 +0100
@@ -0,0 +1,110 @@
+/*
+  ==============================================================================
+
+  This is an automatically generated GUI class created by the Introjucer!
+
+  Be careful when adding custom code to these files, as only the code within
+  the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded
+  and re-saved.
+
+  Created with Introjucer version: 3.1.0
+
+  ------------------------------------------------------------------------------
+
+  The Introjucer is part of the JUCE library - "Jules' Utility Class Extensions"
+  Copyright 2004-13 by Raw Material Software Ltd.
+
+  ==============================================================================
+*/
+
+#ifndef __JUCE_HEADER_AD461861885EB942__
+#define __JUCE_HEADER_AD461861885EB942__
+
+//[Headers]     -- You can add your own extra header files here --
+#include "JuceHeader.h"
+#include "TouchkeyControlMappingFactory.h"
+//[/Headers]
+
+
+
+//==============================================================================
+/**
+                                                                    //[Comments]
+    An auto-generated component, created by the Introjucer.
+
+    Describe your class and how it works here!
+                                                                    //[/Comments]
+*/
+class TouchkeyControlMappingExtendedEditor  : public MappingEditorComponent,
+                                              public TextEditor::Listener,
+                                              public ComboBoxListener,
+                                              public ButtonListener
+{
+public:
+    //==============================================================================
+    TouchkeyControlMappingExtendedEditor (TouchkeyControlMappingFactory& factory);
+    ~TouchkeyControlMappingExtendedEditor();
+
+    //==============================================================================
+    //[UserMethods]     -- You can add your own custom methods in this section.
+    void textEditorTextChanged(TextEditor &editor) {}
+    void textEditorReturnKeyPressed(TextEditor &editor);
+    void textEditorEscapeKeyPressed(TextEditor &editor);
+    void textEditorFocusLost(TextEditor &editor);
+
+    void synchronize();
+    String getDescription();
+    //[/UserMethods]
+
+    void paint (Graphics& g);
+    void resized();
+    void comboBoxChanged (ComboBox* comboBoxThatHasChanged);
+    void buttonClicked (Button* buttonThatWasClicked);
+
+
+
+private:
+    //[UserVariables]   -- You can add your own custom variables in this section.
+    String getDescriptionHelper(String baseName);
+
+    TouchkeyControlMappingFactory& factory_;
+    bool typeWasAbsolute_;
+    //[/UserVariables]
+
+    //==============================================================================
+    ScopedPointer<TextEditor> inputRangeLowEditor;
+    ScopedPointer<Label> rangeLabel;
+    ScopedPointer<Label> controlLabel;
+    ScopedPointer<ComboBox> controlComboBox;
+    ScopedPointer<Label> controlLabel2;
+    ScopedPointer<ComboBox> parameterComboBox;
+    ScopedPointer<Label> controlLabel3;
+    ScopedPointer<ComboBox> typeComboBox;
+    ScopedPointer<TextEditor> inputRangeHighEditor;
+    ScopedPointer<Label> rangeLabel2;
+    ScopedPointer<Label> rangeLabel3;
+    ScopedPointer<TextEditor> outputRangeLowEditor;
+    ScopedPointer<TextEditor> outputRangeHighEditor;
+    ScopedPointer<Label> rangeLabel4;
+    ScopedPointer<Label> controlLabel4;
+    ScopedPointer<ComboBox> directionComboBox;
+    ScopedPointer<Label> titleLabel;
+    ScopedPointer<Label> rangeLabel5;
+    ScopedPointer<TextEditor> thresholdEditor;
+    ScopedPointer<ToggleButton> cc14BitButton;
+    ScopedPointer<ToggleButton> ignore2FingersButton;
+    ScopedPointer<ToggleButton> ignore3FingersButton;
+    ScopedPointer<Label> controlLabel6;
+    ScopedPointer<ComboBox> outOfRangeComboBox;
+    ScopedPointer<Label> rangeLabel6;
+    ScopedPointer<TextEditor> outputDefaultEditor;
+
+
+    //==============================================================================
+    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TouchkeyControlMappingExtendedEditor)
+};
+
+//[EndFile] You can add extra defines here...
+//[/EndFile]
+
+#endif   // __JUCE_HEADER_AD461861885EB942__
--- a/Source/Mappings/Control/TouchkeyControlMappingFactory.cpp	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/Mappings/Control/TouchkeyControlMappingFactory.cpp	Sat Jun 21 23:32:33 2014 +0100
@@ -24,6 +24,7 @@
 
 #include "TouchkeyControlMappingFactory.h"
 #include "TouchkeyControlMappingShortEditor.h"
+#include "TouchkeyControlMappingExtendedEditor.h"
 
 const int TouchkeyControlMappingFactory::kDefaultController = 1;
 const float TouchkeyControlMappingFactory::kDefaultOutputRangeMin = 0.0;
@@ -51,6 +52,13 @@
 
 // ***** Accessors / Modifiers *****
 
+int TouchkeyControlMappingFactory::getDirection() {
+    // Get the direction of motion. This is always positive for
+    if(inputType_ == TouchkeyControlMapping::kTypeAbsolute)
+        return TouchkeyControlMapping::kDirectionPositive;
+    return direction_;
+}
+
 void TouchkeyControlMappingFactory::setInputParameter(int inputParameter) {
     inputParameter_ = inputParameter;
 }
@@ -91,7 +99,8 @@
     }
 
     setMidiParameters(controller, inputRangeMin_, inputRangeMax_, inputRangeCenter_,
-                      outputDefault_, outputRangeMin_, outputRangeMax_);
+                      outputDefault_, outputRangeMin_, outputRangeMax_,
+                      -1, use14BitControl_, outOfRangeBehavior_);
     
     // Listen to incoming controls from the keyboard too, if this is enabled
     // in MidiKeyboardSegment
@@ -113,7 +122,8 @@
     //    return;
     //midiConverter_->setControlMinValue(controlName_.c_str(), inputRangeMin_);
     setMidiParameters(midiControllerNumber_, inputRangeMin_, inputRangeMax_, inputRangeCenter_,
-                      outputDefault_, outputRangeMin_, outputRangeMax_);
+                      outputDefault_, outputRangeMin_, outputRangeMax_,
+                      -1, use14BitControl_, outOfRangeBehavior_);
 }
 
 void TouchkeyControlMappingFactory::setRangeInputMax(float inputMax) {
@@ -129,7 +139,8 @@
     //    return;
     //midiConverter_->setControlMaxValue(controlName_.c_str(), inputRangeMax_);
     setMidiParameters(midiControllerNumber_, inputRangeMin_, inputRangeMax_, inputRangeCenter_,
-                      outputDefault_, outputRangeMin_, outputRangeMax_);
+                      outputDefault_, outputRangeMin_, outputRangeMax_,
+                      -1, use14BitControl_, outOfRangeBehavior_);
 }
 
 void TouchkeyControlMappingFactory::setRangeInputCenter(float inputCenter) {
@@ -145,28 +156,32 @@
     //    return;
     //midiConverter_->setControlCenterValue(controlName_.c_str(), inputRangeCenter_);
     setMidiParameters(midiControllerNumber_, inputRangeMin_, inputRangeMax_, inputRangeCenter_,
-                      outputDefault_, outputRangeMin_, outputRangeMax_);
+                      outputDefault_, outputRangeMin_, outputRangeMax_,
+                      -1, use14BitControl_, outOfRangeBehavior_);
 }
 
 void TouchkeyControlMappingFactory::setRangeOutputMin(float outputMin) {
     outputRangeMin_ = outputMin;
     
     setMidiParameters(midiControllerNumber_, inputRangeMin_, inputRangeMax_, inputRangeCenter_,
-                      outputDefault_, outputRangeMin_, outputRangeMax_);
+                      outputDefault_, outputRangeMin_, outputRangeMax_,
+                      -1, use14BitControl_, outOfRangeBehavior_);
 }
 
 void TouchkeyControlMappingFactory::setRangeOutputMax(float outputMax) {
     outputRangeMax_ = outputMax;
     
     setMidiParameters(midiControllerNumber_, inputRangeMin_, inputRangeMax_, inputRangeCenter_,
-                      outputDefault_, outputRangeMin_, outputRangeMax_);
+                      outputDefault_, outputRangeMin_, outputRangeMax_,
+                      -1, use14BitControl_, outOfRangeBehavior_);
 }
 
 void TouchkeyControlMappingFactory::setRangeOutputDefault(float outputDefault) {
     outputDefault_ = outputDefault;
     
     setMidiParameters(midiControllerNumber_, inputRangeMin_, inputRangeMax_, inputRangeCenter_,
-                      outputDefault_, outputRangeMin_, outputRangeMax_);
+                      outputDefault_, outputRangeMin_, outputRangeMax_,
+                      -1, use14BitControl_, outOfRangeBehavior_);
 }
 
 void TouchkeyControlMappingFactory::setThreshold(float threshold) {
@@ -185,11 +200,32 @@
     direction_ = direction;
 }
 
+void TouchkeyControlMappingFactory::setOutOfRangeBehavior(int behavior) {
+    outOfRangeBehavior_ = behavior;
+    
+    setMidiParameters(midiControllerNumber_, inputRangeMin_, inputRangeMax_, inputRangeCenter_,
+                      outputDefault_, outputRangeMin_, outputRangeMax_,
+                      -1, use14BitControl_, outOfRangeBehavior_);
+}
+
+void TouchkeyControlMappingFactory::setUses14BitControl(bool use) {
+    use14BitControl_ = use;
+
+    setMidiParameters(midiControllerNumber_, inputRangeMin_, inputRangeMax_, inputRangeCenter_,
+                      outputDefault_, outputRangeMin_, outputRangeMax_,
+                      -1, use14BitControl_, outOfRangeBehavior_);
+}
+
 // ***** GUI Support *****
 MappingEditorComponent* TouchkeyControlMappingFactory::createBasicEditor() {
     return new TouchkeyControlMappingShortEditor(*this);
 }
 
+MappingEditorComponent* TouchkeyControlMappingFactory::createExtendedEditor() {
+    return new TouchkeyControlMappingExtendedEditor(*this);
+}
+
+
 // ****** Preset Save/Load ******
 XmlElement* TouchkeyControlMappingFactory::getPreset() {
     PropertySet properties;
--- a/Source/Mappings/Control/TouchkeyControlMappingFactory.h	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/Mappings/Control/TouchkeyControlMappingFactory.h	Sat Jun 21 23:32:33 2014 +0100
@@ -68,7 +68,9 @@
     float getThreshold() { return threshold_; }
     bool getIgnoresTwoFingers() { return ignoresTwoFingers_; }
     bool getIgnoresThreeFingers() { return ignoresThreeFingers_; }
-    int getDirection() { return direction_; }
+    int getDirection();
+    int getOutOfRangeBehavior() { return outOfRangeBehavior_; }
+    bool getUses14BitControl() { return use14BitControl_; }
     
     void setInputParameter(int inputParameter);
     void setInputType(int inputType);
@@ -84,12 +86,14 @@
     void setIgnoresTwoFingers(bool ignoresTwo);
     void setIgnoresThreeFingers(bool ignoresThree);
     void setDirection(int direction);
+    void setOutOfRangeBehavior(int behavior);
+    void setUses14BitControl(bool use);
     
     // ***** GUI Support *****
     bool hasBasicEditor() { return true; }
     MappingEditorComponent* createBasicEditor();
-    bool hasExtendedEditor() { return false; }
-    MappingEditorComponent* createExtendedEditor() { return nullptr; }
+    bool hasExtendedEditor() { return true; }
+    MappingEditorComponent* createExtendedEditor();
     
     // ****** Preset Save/Load ******
     XmlElement* getPreset();
--- a/Source/Mappings/TouchkeyBaseMappingFactory.h	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/Mappings/TouchkeyBaseMappingFactory.h	Sat Jun 21 23:32:33 2014 +0100
@@ -56,6 +56,7 @@
       controlName_(""),
       inputRangeMin_(0.0), inputRangeMax_(1.0), inputRangeCenter_(0.0),
       outOfRangeBehavior_(OscMidiConverter::kOutOfRangeClip),
+      use14BitControl_(false),
       midiControllerNumber_(-1), bypassed_(false), activeNotes_(0x0FFF) {}
     
     // ***** Destructor *****
@@ -185,6 +186,7 @@
         inputRangeMax_ = inputMaxValue;
         inputRangeCenter_ = inputCenterValue;
         outOfRangeBehavior_ = outOfRangeBehavior;
+        use14BitControl_ = use14BitControl;
         
         // Remove listener on previous name (if any)
         //midiConverter_.removeAllControls();
@@ -448,6 +450,7 @@
     float inputRangeMin_, inputRangeMax_;               // Input ranges
     float inputRangeCenter_;      
     int outOfRangeBehavior_;                            // What happens to out of range inputs
+    bool use14BitControl_;                              // Whether to use a 14-bit control
 
     int midiControllerNumber_;                          // Which controller to use
     bool bypassed_;                                     // Whether the mapping has been bypassed by UI
--- a/Source/TouchKeys/MidiInputController.cpp	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/TouchKeys/MidiInputController.cpp	Sat Jun 21 23:32:33 2014 +0100
@@ -286,6 +286,26 @@
     return ports;
 }
 
+// Get the name of a particular MIDI input port
+String MidiInputController::deviceName(int portNumber) {
+    StringArray const& deviceStrings = MidiInput::getDevices();
+    if(portNumber < 0 || portNumber >= deviceStrings.size())
+        return "";
+    return deviceStrings[portNumber];
+}
+
+// Find the index of a device with a given name; return -1 if not found
+int MidiInputController::indexOfDeviceNamed(String const& name) {
+    StringArray const& deviceStrings = MidiInput::getDevices();
+    
+    for(int i = 0; i < deviceStrings.size(); i++) {
+        if(name == deviceStrings[i])
+            return i;
+    }
+    
+    return -1;
+}
+
 // Add a new keyboard segment. Returns a pointer to the newly created segment
 MidiKeyboardSegment* MidiInputController::addSegment(int outputPortNumber,
                                                           int noteMin, int noteMax,
--- a/Source/TouchKeys/MidiInputController.h	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/TouchKeys/MidiInputController.h	Sat Jun 21 23:32:33 2014 +0100
@@ -80,6 +80,10 @@
 	void disableAllPorts(bool auxiliaryOnly);
     int primaryActivePort();
 	vector<int> auxiliaryActivePorts();
+    
+    // Get the name of a particular port index
+    String deviceName(int portNumber);
+    int indexOfDeviceNamed(String const& name);
 
 	// Set/query the output controller
 	MidiOutputController* midiOutputController() { return midiOutputController_; }
--- a/Source/TouchKeys/MidiKeyboardSegment.cpp	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/TouchKeys/MidiKeyboardSegment.cpp	Sat Jun 21 23:32:33 2014 +0100
@@ -540,6 +540,20 @@
     return mappingFactories_;
 }
 
+// Return the specific index of a mapping factory for this segment
+int MidiKeyboardSegment::indexOfMappingFactory(MappingFactory *factory) {
+    vector<MappingFactory*>::iterator it;
+    int i = 0;
+    
+    for(it = mappingFactories_.begin(); it != mappingFactories_.end(); ++it) {
+        if(*it == factory)
+            return i;
+        i++;
+    }
+    
+    return -1;
+}
+
 // Get an XML element describing current settings (for saving presets)
 // This element will need to be released (or added to another XML element
 // that is released) by the caller
--- a/Source/TouchKeys/MidiKeyboardSegment.h	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/TouchKeys/MidiKeyboardSegment.h	Sat Jun 21 23:32:33 2014 +0100
@@ -225,10 +225,15 @@
     
     // Return a list of current mapping factories.
     vector<MappingFactory*> const& mappingFactories();
+
+    // Return the specific index of this mapping factory
+    int indexOfMappingFactory(MappingFactory *factory);
     
     // Return a unique identifier of the mapping state, so we know when something has changed
     int mappingFactoryUniqueIdentifier() { return mappingFactoryUniqueIdentifier_; }
     
+
+    
     // **** Preset methods ****
     
     // Get an XML element describing current settings (for saving presets)
--- a/Source/TouchKeys/MidiOutputController.cpp	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/TouchKeys/MidiOutputController.cpp	Sat Jun 21 23:32:33 2014 +0100
@@ -165,6 +165,26 @@
     return ports;
 }
 
+// Get the name of a particular MIDI input port
+String MidiOutputController::deviceName(int portNumber) {
+    StringArray const& deviceStrings = MidiOutput::getDevices();
+    if(portNumber < 0 || portNumber >= deviceStrings.size())
+        return "";
+    return deviceStrings[portNumber];
+}
+
+// Find the index of a device with a given name; return -1 if not found
+int MidiOutputController::indexOfDeviceNamed(String const& name) {
+    StringArray const& deviceStrings = MidiOutput::getDevices();
+    
+    for(int i = 0; i < deviceStrings.size(); i++) {
+        if(name == deviceStrings[i])
+            return i;
+    }
+    
+    return -1;
+}
+
 // Send a MIDI Note On message
 void MidiOutputController::sendNoteOn(int port, unsigned char channel, unsigned char note, unsigned char velocity) {
 	sendMessage(port,
--- a/Source/TouchKeys/MidiOutputController.h	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/TouchKeys/MidiOutputController.h	Sat Jun 21 23:32:33 2014 +0100
@@ -61,6 +61,12 @@
     int enabledPort(int identifier);
     std::vector<std::pair<int, int> > enabledPorts();
 	
+    // Get the name of a particular port index
+    String deviceName(int portNumber);
+    
+    // Find the index of a device with a given name; return -1 if not found
+    int indexOfDeviceNamed(String const& name);
+    
 	// Send MIDI messages
 	void sendNoteOn(int port, unsigned char channel, unsigned char note, unsigned char velocity);
     void sendNoteOff(int port, unsigned char channel, unsigned char note, unsigned char velocity = 64);
--- a/Source/TouchKeys/OscMidiConverter.cpp	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/TouchKeys/OscMidiConverter.cpp	Sat Jun 21 23:32:33 2014 +0100
@@ -189,11 +189,18 @@
 #endif
     }
     
+    // For 14-bit CC messages, multiply by 128 first to keep the same apparent range as
+    // the 7-bit version but adding extra resolution on the second CC number. This should
+    // not be done for pitch wheel where the values are already normalised to 14 bits.
+    if(controllerIs14Bit_ && controller_ != MidiKeyboardSegment::kControlPitchWheel)
+        controlValue *= 128.0;
+    
     int roundedControlValue = (int)floorf(controlValue + 0.5f);
-    if(roundedControlValue > controlMaxValue_)
-        roundedControlValue = controlMaxValue_;
-    if(roundedControlValue < controlMinValue_)
-        roundedControlValue = controlMinValue_;
+    int maxValue = controllerIs14Bit_ ? 16383 : 127;
+    if(roundedControlValue > maxValue)
+        roundedControlValue = maxValue;
+    if(roundedControlValue < 0)
+        roundedControlValue = 0;
     
     return roundedControlValue;
 }
--- a/Source/TouchKeys/OscMidiConverter.h	Fri Mar 21 23:13:19 2014 +0000
+++ b/Source/TouchKeys/OscMidiConverter.h	Sat Jun 21 23:32:33 2014 +0100
@@ -42,7 +42,7 @@
 public:
     // Behavior for out-of-range inputs.
     enum {
-        kOutOfRangeIgnore = 0,
+        kOutOfRangeIgnore = 1,
         kOutOfRangeClip,
         kOutOfRangeExtrapolate
     };
--- a/TouchKeys.jucer	Fri Mar 21 23:13:19 2014 +0000
+++ b/TouchKeys.jucer	Sat Jun 21 23:32:33 2014 +0100
@@ -9,6 +9,12 @@
     <FILE id="TLhblr" name="tk-icon-512.png" compile="0" resource="1" file="Resources/tk-icon-512.png"/>
     <GROUP id="{76BA3848-7941-BF85-9F61-26651AA83B29}" name="Source">
       <GROUP id="{A75C239F-4C55-DB1F-52C8-665A12DD86F8}" name="GUI">
+        <FILE id="MNTGnN" name="PreferencesWindow.h" compile="0" resource="0"
+              file="Source/GUI/PreferencesWindow.h"/>
+        <FILE id="pQJbuq" name="PreferencesComponent.cpp" compile="1" resource="0"
+              file="Source/GUI/PreferencesComponent.cpp"/>
+        <FILE id="NJXJef" name="PreferencesComponent.h" compile="0" resource="0"
+              file="Source/GUI/PreferencesComponent.h"/>
         <FILE id="EqmxWo" name="MainWindow.cpp" compile="1" resource="0" file="Source/GUI/MainWindow.cpp"/>
         <FILE id="dCLyAi" name="MainWindow.h" compile="0" resource="0" file="Source/GUI/MainWindow.h"/>
         <FILE id="AJnPIz" name="KeyboardZoneComponent.cpp" compile="1" resource="0"
@@ -31,6 +37,8 @@
               file="Source/GUI/MappingListItem.h"/>
         <FILE id="xrkguc" name="MappingEditorComponent.h" compile="0" resource="0"
               file="Source/GUI/MappingEditorComponent.h"/>
+        <FILE id="E9Won5" name="MappingExtendedEditorWindow.h" compile="0"
+              resource="0" file="Source/GUI/MappingExtendedEditorWindow.h"/>
       </GROUP>
       <GROUP id="{C6DAB7D2-B0E8-B89F-2891-B0D8BD8F53F7}" name="Mappings">
         <GROUP id="{555624D9-7465-1F1E-FFBD-8A4D46B28C27}" name="Vibrato">
@@ -102,6 +110,10 @@
                 resource="0" file="Source/Mappings/KeyDivision/TouchkeyKeyDivisionMappingFactory.h"/>
         </GROUP>
         <GROUP id="{F18CE313-6599-FF00-12CD-4CDA50007E1C}" name="Control">
+          <FILE id="Fh9cxx" name="TouchkeyControlMappingExtendedEditor.cpp" compile="1"
+                resource="0" file="Source/Mappings/Control/TouchkeyControlMappingExtendedEditor.cpp"/>
+          <FILE id="FfUw61" name="TouchkeyControlMappingExtendedEditor.h" compile="0"
+                resource="0" file="Source/Mappings/Control/TouchkeyControlMappingExtendedEditor.h"/>
           <FILE id="sTWDBG" name="TouchkeyControlMappingShortEditor.cpp" compile="1"
                 resource="0" file="Source/Mappings/Control/TouchkeyControlMappingShortEditor.cpp"/>
           <FILE id="j4D4dO" name="TouchkeyControlMappingShortEditor.h" compile="0"
@@ -260,9 +272,9 @@
                bigIcon="b3DhPc">
       <CONFIGURATIONS>
         <CONFIGURATION name="Debug" osxSDK="default" osxCompatibility="default" osxArchitecture="64BitIntel"
-                       isDebug="1" optimisation="1" targetName="TouchKeys"/>
+                       isDebug="1" optimisation="1" targetName="TouchKeys" headerPath="/usr/local/include"/>
         <CONFIGURATION name="Release" osxSDK="default" osxCompatibility="10.6 SDK" osxArchitecture="64BitUniversal"
-                       isDebug="0" optimisation="2" targetName="TouchKeys"/>
+                       isDebug="0" optimisation="2" targetName="TouchKeys" headerPath="/usr/local/include"/>
       </CONFIGURATIONS>
       <MODULEPATHS>
         <MODULEPATH id="juce_opengl" path="../juce/modules"/>