[dasher: 4/11] Rewrite control mode! inc platform-independent speech & clipboard



commit 16dca8a699b27b6862ba705c966f86051a78bf5f
Author: Alan Lawrence <acl33 islay inf phy cam ac uk>
Date:   Thu May 13 15:21:13 2010 +0100

    Rewrite control mode! inc platform-independent speech & clipboard
    
    ...Controlled by BP_CONTROL_MODE_HAS_{SPEECH,COPY}.
    *Also provided BP_SPEAK_ALL_ON_STOP, BP_COPY_ALL_ON_STOP, BP_SPEAK_WORDS.
    *Stop and Pause in control mode according to CInputFilter::supportsPause(),
      BP_CONTROL_MODE_HAS_HALT, and DasherInterface::hasStopTriggers.
    *BP_CONTROL_MODE_HAS_EDIT allows turning off move/delete
    
    Win32: Much code removed as equivalent now in DasherCore, but more TODO 2 build!
    MacOSX also updated, but needs GUI for BP_*, & CTL_EDIT/DELETE handler (?!)
    Gtk2 unchanged - old mechanism should still work, just not using new code...
      (Note implementation of CDasherInterfaceBase::GetAllContext just throws)
    iPhone: basic impl (no speech) with 'Control Mode' btn in misc settings
      *textAtOffset now truncates desired region to lie within text, following Gtk2
      *CTL_EDIT/DELETE search through textbox for alphabet space/para/default-ctx
      *Also make textview first responder => displays cursor upon output/move/del!

 Src/DasherCore/AlphabetManager.cpp           |   26 +++---
 Src/DasherCore/ControlManager.cpp            |  107 ++++++++++++++++++-
 Src/DasherCore/ControlManager.h              |   38 ++++++-
 Src/DasherCore/DasherInterfaceBase.cpp       |  101 ++++++++++++++----
 Src/DasherCore/DasherInterfaceBase.h         |   54 +++++++++-
 Src/DasherCore/DefaultFilter.h               |    1 +
 Src/DasherCore/DynamicFilter.h               |    2 +
 Src/DasherCore/InputFilter.h                 |    2 +
 Src/DasherCore/NodeCreationManager.cpp       |   43 +++-----
 Src/DasherCore/NodeCreationManager.h         |   24 +---
 Src/DasherCore/Parameters.h                  |   13 +++
 Src/DasherCore/StylusFilter.h                |    4 +-
 Src/Gtk2/DasherControl.h                     |    4 +
 Src/MacOSX/COSXDasherControl.h               |    7 +-
 Src/MacOSX/COSXDasherControl.mm              |   21 ++++
 Src/MacOSX/Chatter.h                         |   40 -------
 Src/MacOSX/Chatter.m                         |  144 --------------------------
 Src/MacOSX/Dasher.xcodeproj/project.pbxproj  |   16 ++--
 Src/MacOSX/DasherApp.h                       |    8 +-
 Src/MacOSX/DasherApp.mm                      |   40 +++++++-
 Src/MacOSX/DasherEdit.h                      |    2 +
 Src/MacOSX/DasherEdit.mm                     |   10 ++-
 Src/MacOSX/DasherViewAqua.mm                 |    1 -
 Src/MacOSX/DasherViewOpenGL.mm               |    1 -
 Src/MacOSX/Queue.h                           |   21 ++++
 Src/MacOSX/Queue.m                           |   57 ++++++++++
 Src/Win32/ActionSpeech.cpp                   |   58 ----------
 Src/Win32/ActionSpeech.h                     |   27 -----
 Src/Win32/Dasher.cpp                         |   67 +++++++++++--
 Src/Win32/Dasher.h                           |   17 +++-
 Src/Win32/DasherWindow.cpp                   |   22 ----
 Src/Win32/Widgets/AdvancedPage.cpp           |    7 +-
 Src/Win32/Widgets/Edit.cpp                   |   97 +-----------------
 Src/Win32/Widgets/Edit.h                     |   11 --
 Src/iPhone/Classes/CDasherInterfaceBridge.h  |    3 +
 Src/iPhone/Classes/CDasherInterfaceBridge.mm |   52 +++++++++-
 Src/iPhone/Classes/DasherAppDelegate.h       |   15 +++-
 Src/iPhone/Classes/DasherAppDelegate.mm      |   90 ++++++++++++++--
 Src/iPhone/Classes/InputMethodSelector.mm    |    1 +
 Src/iPhone/Classes/MiscSettings.mm           |   33 ++++++
 40 files changed, 750 insertions(+), 537 deletions(-)
---
diff --git a/Src/DasherCore/AlphabetManager.cpp b/Src/DasherCore/AlphabetManager.cpp
index bd5ce3e..ce97765 100644
--- a/Src/DasherCore/AlphabetManager.cpp
+++ b/Src/DasherCore/AlphabetManager.cpp
@@ -261,25 +261,25 @@ CDasherNode *CAlphabetManager::CreateSymbolNode(CAlphNode *pParent, symbol iSymb
 
   // TODO: Need to fix fact that this is created even when control mode is switched off
   if(iSymbol == m_pNCManager->GetAlphabet()->GetControlSymbol()) {
-      //ACL setting offset as one more than parent for consistency with "proper" symbol nodes...
-      pNewNode = m_pNCManager->GetCtrlRoot(pParent, iLbnd, iHbnd, pParent->offset()+1); 
-
+    CControlManager *pMgr = m_pNCManager->GetControlManager();
+    if (pMgr) {
 #ifdef _WIN32_WCE
-      //no control manager - but (TODO!) we still try to create (0-size!) control node...
-      DASHER_ASSERT(!pNewNode);
-      // For now, just hack it so we get a normal root node here
-      pNewNode = m_pNCManager->GetAlphRoot(pParent, iLbnd, iHbnd, false, pParent->m_iOffset+1);
-#else
-      DASHER_ASSERT(pNewNode);
+      DASHER_ASSERT(false);
 #endif
+      //ACL leave offset as is - like its groupnode parent, but unlike its alphnode siblings,
+      //the control node does not enter a symbol....
+      pNewNode = pMgr->GetRoot(pParent, iLbnd, iHbnd, pParent->offset());
+    } else {
+      //Control mode currently turned off...
+      DASHER_ASSERT(iLbnd == iHbnd); //zero size
+      pNewNode = NULL;
     }
-    else if(iSymbol == m_pNCManager->GetAlphabet()->GetStartConversionSymbol()) {
+  } else if(iSymbol == m_pNCManager->GetAlphabet()->GetStartConversionSymbol()) {
       //  else if(iSymbol == m_pNCManager->GetSpaceSymbol()) {
 
       //ACL setting m_iOffset+1 for consistency with "proper" symbol nodes...
       pNewNode = m_pNCManager->GetConvRoot(pParent, iLbnd, iHbnd, pParent->offset()+1);
-    }
-    else {
+  } else {
       // TODO: Exceptions / error handling in general
 
       CAlphNode *pAlphNode;
@@ -370,7 +370,7 @@ void CAlphabetManager::IterateChildGroups(CAlphNode *pParent, SGroupInfo *pParen
       //3. loop round inner loop...
     }
     //created a new node - symbol or (group which will have >1 child).
-    DASHER_ASSERT(pParent->GetChildren().back()==pNewChild);
+    DASHER_ASSERT((i-1 == m_pNCManager->GetAlphabet()->GetControlSymbol() && pNewChild==NULL) || pParent->GetChildren().back()==pNewChild);
     //now adjust the node we've actually created, to take account of any elided group(s)...
     // tho not if we've reused the existing node, assume that's been adjusted already
     if (pNewChild && pNewChild!=buildAround) pNewChild->PrependElidedGroup(iOverrideColour, groupPrefix);
diff --git a/Src/DasherCore/ControlManager.cpp b/Src/DasherCore/ControlManager.cpp
index 84dce9c..b546f87 100644
--- a/Src/DasherCore/ControlManager.cpp
+++ b/Src/DasherCore/ControlManager.cpp
@@ -95,7 +95,7 @@ void CControlBase::CContNode::PopulateChildren() {
     if( *it == NULL ) {
       // Escape back to alphabet
       
-      pNewNode = m_pMgr->m_pNCManager->GetAlphRoot(this, iLbnd, iHbnd, false, offset());
+      pNewNode = m_pMgr->m_pNCManager->GetAlphRoot(this, iLbnd, iHbnd, false, offset()+1);
     }
     else {
       
@@ -340,7 +340,7 @@ void COrigNodes::RegisterNode( int iID, std::string strLabel, int iColour ) {
 }
 
 void COrigNodes::ConnectNode(int iChild, int iParent, int iAfter) {
-  //ACL duplicating old functionality here. Node idea had been to do
+  //ACL duplicating old functionality here. Idea had been to do
   // something with iAfter "(eventually -1 = start, -2 = end)", but
   // since this wasn't used, and this is all legacy code anyway ;-),
   // I'm leaving as is...
@@ -361,7 +361,6 @@ void COrigNodes::DisconnectNode(int iChild, int iParent) {
   }
 }
 
-
 void COrigNodes::XmlStartHandler(void *pUserData, const XML_Char *szName, const XML_Char **aszAttr) {
   COrigNodes *pMgr(static_cast<COrigNodes *>(pUserData));
   int colour=-1;
@@ -389,5 +388,105 @@ void COrigNodes::XmlEndHandler(void *pUserData, const XML_Char *szName) {
 void COrigNodes::XmlCDataHandler(void *pUserData, const XML_Char *szData, int iLength){
 }
 
-CControlManager::CControlManager(CNodeCreationManager *pNCMgr, CDasherInterfaceBase *pInterface) : COrigNodes(pNCMgr,pInterface) {
+CControlManager::CControlManager(CEventHandler *pEventHandler, CSettingsStore *pSettingsStore, CNodeCreationManager *pNCManager, CDasherInterfaceBase *pInterface)
+: CDasherComponent(pEventHandler, pSettingsStore), COrigNodes(pNCManager, pInterface), m_pSpeech(NULL), m_pCopy(NULL) {
+  
+  updateActions();
+}
+
+CControlManager::~CControlManager() {
+  delete m_pSpeech;
+  delete m_pCopy;
+}
+
+class TextActionHeader : public CDasherInterfaceBase::TextAction, public CControlBase::NodeTemplate {
+public:
+  TextActionHeader(CDasherInterfaceBase *pIntf, const string &strHdr, NodeTemplate *pRoot) : TextAction(pIntf), NodeTemplate(strHdr,-1),
+  m_all(this, "All", &CDasherInterfaceBase::TextAction::executeOnAll),
+  m_new(this, "New", &CDasherInterfaceBase::TextAction::executeOnNew),
+  m_again(this, "Repeat", &CDasherInterfaceBase::TextAction::executeLast) {
+    successors.push_back(&m_all); m_all.successors.push_back(NULL); m_all.successors.push_back(pRoot);
+    successors.push_back(&m_new); m_new.successors.push_back(NULL); m_new.successors.push_back(pRoot);
+    successors.push_back(&m_again); m_again.successors.push_back(NULL); m_again.successors.push_back(pRoot);
+  }
+private:
+  CControlBase::MethodTemplate<CDasherInterfaceBase::TextAction> m_all, m_new, m_again;
+};
+
+class SpeechHeader : public TextActionHeader {
+public:
+  SpeechHeader(CDasherInterfaceBase *pIntf, NodeTemplate *pRoot) : TextActionHeader(pIntf, "Speak", pRoot) {
+  }
+  void operator()(const std::string &strText) {
+    m_pIntf->Speak(strText, true);
+  }
+};
+
+class CopyHeader  : public TextActionHeader {
+public:
+  CopyHeader(CDasherInterfaceBase *pIntf, NodeTemplate *pRoot) : TextActionHeader(pIntf, "Copy", pRoot) {
+  }
+  void operator()(const std::string &strText) {
+    m_pIntf->CopyToClipboard(strText);
+  }
+};
+
+void CControlManager::HandleEvent(CEvent *pEvent) {
+  if (pEvent->m_iEventType == EV_PARAM_NOTIFY) {
+    switch (static_cast<CParameterNotificationEvent *>(pEvent)->m_iParameter) {
+      case BP_CONTROL_MODE_HAS_HALT:
+      case BP_CONTROL_MODE_HAS_EDIT:
+      case BP_CONTROL_MODE_HAS_SPEECH:
+      case BP_CONTROL_MODE_HAS_COPY:
+      case BP_COPY_ALL_ON_STOP:
+      case BP_SPEAK_ALL_ON_STOP:
+      case SP_INPUT_FILTER:
+        updateActions();
+    }
+  }
 }
+
+void CControlManager::updateActions() {
+  vector<NodeTemplate *> &vRootSuccessors(GetRootTemplate()->successors);
+  vector<NodeTemplate *> vOldRootSuccessors;
+  vOldRootSuccessors.swap(vRootSuccessors);
+  vector<NodeTemplate *>::iterator it=vOldRootSuccessors.begin();
+  DASHER_ASSERT(*it == NULL); //escape back to alphabet
+  vRootSuccessors.push_back(*it++);
+  
+  //stop does something, and we're told to add a node for it
+  // (either a dynamic filter where the user can't use the normal stop mechanism precisely,
+  //  or a static filter but a 'stop' action is easier than using speak->all / copy->all then pause)
+  if (m_pInterface->hasStopTriggers() && m_pInterface->GetBoolParameter(BP_CONTROL_MODE_HAS_HALT))
+    vRootSuccessors.push_back(m_perId[CTL_STOP]);
+  if (*it == m_perId[CTL_STOP]) it++;
+  
+  //filter is pauseable, and either 'stop' would do something (so pause is different),
+  // or we're told to have a stop node but it would be indistinguishable from pause (=>have pause)
+  CInputFilter *pInput(static_cast<CInputFilter *>(m_pInterface->GetModuleByName(m_pInterface->GetStringParameter(SP_INPUT_FILTER))));
+  if (pInput->supportsPause() && (m_pInterface->hasStopTriggers() || m_pInterface->GetBoolParameter(BP_CONTROL_MODE_HAS_HALT)))
+    vRootSuccessors.push_back(m_perId[CTL_PAUSE]);
+  if (*it == m_perId[CTL_PAUSE]) it++;
+  
+  if (m_pInterface->GetBoolParameter(BP_CONTROL_MODE_HAS_SPEECH) && m_pInterface->SupportsSpeech()) {
+    if (!m_pSpeech) m_pSpeech = new SpeechHeader(m_pInterface, GetRootTemplate());
+    vRootSuccessors.push_back(m_pSpeech);
+  }
+  if (*it == m_pSpeech) it++;
+  
+  if (m_pInterface->GetBoolParameter(BP_CONTROL_MODE_HAS_COPY) && m_pInterface->SupportsClipboard()) {
+    if (!m_pCopy) m_pCopy = new CopyHeader(m_pInterface, GetRootTemplate());
+    vRootSuccessors.push_back(m_pCopy);
+  }
+  if (*it == m_pCopy) it++;
+
+  if (m_pInterface->GetBoolParameter(BP_CONTROL_MODE_HAS_EDIT)) {
+    vRootSuccessors.push_back(m_perId[CTL_MOVE]);
+    vRootSuccessors.push_back(m_perId[CTL_DELETE]);
+  }
+  if (*it == m_perId[CTL_MOVE]) it++;
+  if (*it == m_perId[CTL_DELETE]) it++;
+  
+  //copy anything else (custom) that might have been added...
+  while (it != vOldRootSuccessors.end()) vRootSuccessors.push_back(*it++);
+}
\ No newline at end of file
diff --git a/Src/DasherCore/ControlManager.h b/Src/DasherCore/ControlManager.h
index f5a2bde..4bd092b 100644
--- a/Src/DasherCore/ControlManager.h
+++ b/Src/DasherCore/ControlManager.h
@@ -42,6 +42,8 @@
 
 using namespace std;
 
+class CNodeCreationManager;
+
 namespace Dasher {
 
   class CDasherModel;
@@ -112,6 +114,22 @@ namespace Dasher {
       int m_iColour;
     };
     
+    template <typename T> class MethodTemplate : public NodeTemplate {
+    public:
+      ///pointer to a function "void X()", that is a member of a T...
+      typedef void (T::*Method)();
+      MethodTemplate(T *pRecv, const std::string &strLabel, Method f) : NodeTemplate(strLabel,-1),m_pRecv(pRecv),m_f(f) {
+        std::cout << "Creating " << this << " with receiver " << pRecv << std::endl;
+      }
+      virtual void happen(CContNode *pNode) {
+        //invoke pointer-to-member-function m_f on object *m_pRecv!
+        (m_pRecv->*m_f)();
+      }
+    private:
+      T *m_pRecv;
+      Method m_f;
+    };
+    
     class EventBroadcast : public NodeTemplate {
     public:
       EventBroadcast(int iEvent, const std::string &strLabel, int iColour);
@@ -188,10 +206,24 @@ namespace Dasher {
     CDasherInterfaceBase *m_pInterface;
   };
   
-  ///subclass which we actually construct - more of a marker than anything for now.
-  class CControlManager : public COrigNodes {
+  ///subclass which we actually construct...
+  class CControlManager : public CDasherComponent, public COrigNodes {
   public:
-    CControlManager(CNodeCreationManager *pNCManager, CDasherInterfaceBase *pInterface);
+    CControlManager(CEventHandler *pEventHandler, CSettingsStore *pSettingsStore, CNodeCreationManager *pNCManager, CDasherInterfaceBase *pInterface);
+    void HandleEvent(CEvent *pEvent);
+    
+    ///Recomputes which of pause, stop, speak and copy the root control node should have amongst its children.
+    /// Automatically called whenever copy-on-stop/speak-on-stop or input filter changes;
+    /// subclasses of CDasherInterfaceBase should also call this if
+    ///  (a) they override Stop() and hasStopTriggers() with additional actions, if these are enabled/disabled
+    ///      and this causes the value returned by hasStopTriggers() to change;
+    ///  (b) the values returned by SupportsSpeech() and/or SupportsClipboard() ever change.
+    void updateActions();
+    ~CControlManager();
+    
+  private:
+    ///group headers, with three children each (all/new/repeat)
+    NodeTemplate *m_pSpeech, *m_pCopy;
   };
   /// @}
 }
diff --git a/Src/DasherCore/DasherInterfaceBase.cpp b/Src/DasherCore/DasherInterfaceBase.cpp
index 2f552aa..b652d46 100644
--- a/Src/DasherCore/DasherInterfaceBase.cpp
+++ b/Src/DasherCore/DasherInterfaceBase.cpp
@@ -111,6 +111,8 @@ CDasherInterfaceBase::CDasherInterfaceBase() {
   strCurrentContext = ". ";
   strTrainfileBuffer = "";
 
+  m_strCurrentWord = "";
+
   // Create an event handler.
   m_pEventHandler = new CEventHandler(this);
 
@@ -140,7 +142,6 @@ void CDasherInterfaceBase::Realize() {
   m_ColourIO = new CColourIO(GetStringParameter(SP_SYSTEM_LOC), GetStringParameter(SP_USER_LOC), vColourFiles);
 
   ChangeColours();
-  ChangeAlphabet(); // This creates the NodeCreationManager, the Alphabet
 
   // Create the user logging object if we are suppose to.  We wait
   // until now so we have the real value of the parameter and not
@@ -163,6 +164,9 @@ void CDasherInterfaceBase::Realize() {
 
   CreateInput();
   CreateInputFilter();
+  
+  ChangeAlphabet(); // This creates the NodeCreationManager, the Alphabet
+
   SetupActionButtons();
   CParameterNotificationEvent oEvent(LP_NODE_BUDGET);
   InterfaceEventHandler(&oEvent);
@@ -344,11 +348,20 @@ void CDasherInterfaceBase::InterfaceEventHandler(Dasher::CEvent *pEvent) {
 	strCurrentContext = strCurrentContext.substr( strCurrentContext.size() - 20 );
       if(GetBoolParameter(BP_LM_ADAPTIVE))
 	 strTrainfileBuffer += pEditEvent->m_sText;
+      if (GetBoolParameter(BP_SPEAK_WORDS) && SupportsSpeech()) {
+        if (pEditEvent->m_sText == m_Alphabet->GetText(m_Alphabet->GetSpaceSymbol())) {
+          Speak(m_strCurrentWord, false);
+          m_strCurrentWord="";
+        } else
+          m_strCurrentWord+=pEditEvent->m_sText;
+      }
     }
     else if(pEditEvent->m_iEditType == 2) {
       strCurrentContext = strCurrentContext.substr( 0, strCurrentContext.size() - pEditEvent->m_sText.size());
       if(GetBoolParameter(BP_LM_ADAPTIVE))
 	 strTrainfileBuffer = strTrainfileBuffer.substr( 0, strTrainfileBuffer.size() - pEditEvent->m_sText.size());
+      if (GetBoolParameter(BP_SPEAK_WORDS))
+        m_strCurrentWord = m_strCurrentWord.substr(0, max(0ul, m_strCurrentWord.size()-pEditEvent->m_sText.size()));
     }
   }
 }
@@ -369,11 +382,9 @@ void CDasherInterfaceBase::CreateModel(int iOffset) {
   if(!m_pNCManager)
     return;
 
-  if(m_pDasherModel) {
-    delete m_pDasherModel;
-    m_pDasherModel = 0;
-  }
-
+  delete m_pDasherModel;
+  
+  // TODO: Eventually we'll not have to pass the NC manager to the model...(?)
   m_pDasherModel = new CDasherModel(m_pEventHandler, m_pSettingsStore, m_pNCManager, this, m_pDasherView, iOffset);
 
   // Notify the teacher of the new model
@@ -393,23 +404,14 @@ void CDasherInterfaceBase::CreateNCManager() {
   if( lmID == -1 )
     return;
 
-    int iOffset;
-
-    if(m_pDasherModel)
-      iOffset = m_pDasherModel->GetOffset();
-    else
-      iOffset = 0; // TODO: Is this right?
+  //0 seems the right offset if we don't have a model (=at startup)...
+  int iOffset = m_pDasherModel ? m_pDasherModel->GetOffset() : 0;
 
     // Delete the old model and create a new one
-    if(m_pDasherModel) {
-      delete m_pDasherModel;
-      m_pDasherModel = 0;
-    }
+    // (must delete model first, as its nodes refer to NodeManagers inside the NCManager)
+  delete m_pDasherModel; m_pDasherModel = NULL;
 
-    if(m_pNCManager) {
-      delete m_pNCManager;
-      m_pNCManager = 0;
-    }
+  delete m_pNCManager;
 
     m_pNCManager = new CNodeCreationManager(this, m_pEventHandler, m_pSettingsStore, m_AlphIO);
 
@@ -419,6 +421,40 @@ void CDasherInterfaceBase::CreateNCManager() {
     CreateModel(iOffset);
 }
 
+CDasherInterfaceBase::TextAction::TextAction(CDasherInterfaceBase *pIntf) : m_pIntf(pIntf) {
+  m_iStartOffset= (pIntf->m_pDasherModel) ? pIntf->m_pDasherModel->GetOffset() : 0;
+  pIntf->m_vTextActions.insert(this);
+}
+
+CDasherInterfaceBase::TextAction::~TextAction() {
+  m_pIntf->m_vTextActions.erase(this);
+}
+
+void CDasherInterfaceBase::TextAction::executeOnAll() {
+  (*this)(strLast = m_pIntf->GetAllContext());
+  m_iStartOffset = m_pIntf->m_pDasherModel->GetOffset();
+}
+
+void CDasherInterfaceBase::TextAction::executeOnNew() {
+  int iNewOffset(m_pIntf->m_pDasherModel->GetOffset());
+  (*this)(strLast = m_pIntf->GetContext(m_iStartOffset, iNewOffset-m_iStartOffset));
+  m_iStartOffset=iNewOffset;
+}
+
+void CDasherInterfaceBase::TextAction::executeLast() {
+  (*this)(strLast);
+}
+
+void CDasherInterfaceBase::TextAction::NotifyOffset(int iOffset) {
+  m_iStartOffset = min(iOffset, m_iStartOffset);
+}
+
+
+bool CDasherInterfaceBase::hasStopTriggers() {
+  return (GetBoolParameter(BP_COPY_ALL_ON_STOP) && SupportsClipboard())
+  || (GetBoolParameter(BP_SPEAK_ALL_ON_STOP) && SupportsSpeech());
+}
+
 void CDasherInterfaceBase::Stop() {
   if (GetBoolParameter(BP_DASHER_PAUSED)) return; //already paused, no need to do anything.
   SetBoolParameter(BP_DASHER_PAUSED, true);
@@ -430,6 +466,13 @@ void CDasherInterfaceBase::Stop() {
   if (m_pUserLog != NULL)
     m_pUserLog->StopWriting((float) GetNats());
 #endif
+  
+  if (GetBoolParameter(BP_COPY_ALL_ON_STOP) && SupportsClipboard()) {
+    CopyToClipboard(GetAllContext());
+  }
+  if (GetBoolParameter(BP_SPEAK_ALL_ON_STOP) && SupportsSpeech()) {
+    Speak(GetAllContext(), true);
+  }
 }
 
 void CDasherInterfaceBase::GameMessageIn(int message, void* messagedata) {
@@ -764,6 +807,11 @@ std::string CDasherInterfaceBase::GetContext(int iStart, int iLength) {
   return m_strContext;
 }
 
+void CDasherInterfaceBase::ClearAllContext() {
+  SetBuffer(0);
+  strCurrentContext = "";
+}
+
 void CDasherInterfaceBase::SetContext(std::string strNewContext) {
   m_strContext = strNewContext;
 }
@@ -771,15 +819,18 @@ void CDasherInterfaceBase::SetContext(std::string strNewContext) {
 // Control mode stuff
 
 void CDasherInterfaceBase::RegisterNode( int iID, const std::string &strLabel, int iColour ) {
-  m_pNCManager->RegisterNode(iID, strLabel, iColour);
+  CControlManager *pMgr(m_pNCManager->GetControlManager());
+  if (pMgr) pMgr->RegisterNode(iID, strLabel, iColour);
 }
 
 void CDasherInterfaceBase::ConnectNode(int iChild, int iParent, int iAfter) {
-  m_pNCManager->ConnectNode(iChild, iParent, iAfter);
+  CControlManager *pMgr(m_pNCManager->GetControlManager());
+  pMgr->ConnectNode(iChild, iParent, iAfter);
 }
 
 void CDasherInterfaceBase::DisconnectNode(int iChild, int iParent) {
-  m_pNCManager->DisconnectNode(iChild, iParent);
+  CControlManager *pMgr(m_pNCManager->GetControlManager());
+  pMgr->DisconnectNode(iChild, iParent);
 }
 
 void CDasherInterfaceBase::SetBoolParameter(int iParameter, bool bValue) {
@@ -1091,6 +1142,10 @@ void CDasherInterfaceBase::UnsetBuffer() {
 void CDasherInterfaceBase::SetOffset(int iOffset) {
   if(m_pDasherModel)
     m_pDasherModel->SetOffset(iOffset, m_pDasherView);
+  //ACL TODO FIXME check that CTL_MOVE, etc., eventually come here?
+  for (set<TextAction *,bool(*)(TextAction *,TextAction *)>::iterator it = m_vTextActions.begin(); it!=m_vTextActions.end(); it++) {
+    (*it)->NotifyOffset(iOffset);
+  }
 }
 
 // Returns 0 on success, an error string on failure.
diff --git a/Src/DasherCore/DasherInterfaceBase.h b/Src/DasherCore/DasherInterfaceBase.h
index 3a72144..d03c274 100644
--- a/Src/DasherCore/DasherInterfaceBase.h
+++ b/Src/DasherCore/DasherInterfaceBase.h
@@ -41,13 +41,14 @@
 #include "InputFilter.h"
 #include "ModuleManager.h"
 
-#include <map>
+#include <set>
 #include <algorithm>
 
 namespace Dasher {
   class CDasherScreen;
   class CDasherView;
   class CDasherInput;
+  class CInputFilter;
   class CDasherModel;
   class CEventHandler;
   class CEvent;
@@ -204,12 +205,44 @@ public:
   void PreSetNotify(int iParameter, const std::string &sValue);
 
 
+  ///Does this subclass support speech (i.e. the speak(string) method?)
+  /// Default is just to return false.
+  virtual bool SupportsSpeech() {return false;}
+  ///Does this subclass support clipboard copying (i.e. the copyToClipboard(string) method?)
+  /// Default is just to return false.
+  virtual bool SupportsClipboard() {return false;}
+  
+  ///Subclasses supporting speech should override to speak the supplied text
+  /// (Default implementation does nothing)
+  virtual void Speak(const std::string &text, bool bInterrupt) {}
+  
+  ///Subclasses supporting clipboard operations should override to copy
+  /// the specified text to the clipboard. (Default implementation does nothing).
+  virtual void CopyToClipboard(const std::string &text) {}
+  
+  class TextAction {
+  public:
+    TextAction(CDasherInterfaceBase *pMgr);
+    void executeOnAll();
+    void executeOnNew();
+    void executeLast();
+    void NotifyOffset(int iOffset);
+    virtual ~TextAction();
+  protected:
+    virtual void operator()(const std::string &strText)=0;
+    CDasherInterfaceBase *m_pIntf;
+  private:
+    int m_iStartOffset;
+    std::string strLast;
+  };
+  
 
   /// @name Starting and stopping
   /// Methods used to instruct dynamic motion of Dasher to start or stop
   /// @{ 
 
-  /// Stop Dasher - Sets BP_DASHER_PAUSED; subclasses may override to do more
+  /// Stop Dasher - Sets BP_DASHER_PAUSED and executes any on-stop actions
+  ///  (speech, clipboard - subclasses may override to do more).
   /// (But does nothing if BP_DASHER_PAUSED is not set)
   virtual void Stop();
 
@@ -218,6 +251,11 @@ public:
   /// \param Time Time in ms, used to keep a constant frame rate
   void Unpause(unsigned long Time);
 
+  ///Whether any actions are currently setup to occur when Dasher 'stop's.
+  /// Default is to return TRUE iff we support speech and BP_SPEAK_ON_STOP is set,
+  /// and/or if we support clipboard and BP_COPY_ALL_ON_STOP is set; subclasses may
+  /// override if they have additional on-stop actions.
+  virtual bool hasStopTriggers();
   /// @}
 
 
@@ -344,6 +382,11 @@ public:
   }; 
 
   std::string GetContext(int iStart, int iLength);
+  
+  ///Subclasses should override to clear text edit box, etc., etc., but then
+  /// call this (superclass) implementation as well to rebuild the model...
+  virtual void ClearAllContext();
+  virtual std::string GetAllContext()=0;
 
   /// Set a key value pair by name - designed to allow operation from
   /// the command line.  Returns 0 on success, an error string on failure. 
@@ -351,7 +394,7 @@ public:
   const char* ClSet(const std::string &strKey, const std::string &strValue);
 
   void ImportTrainingText(const std::string &strPath);
-
+  
 protected:
 
   /// @name Startup
@@ -546,6 +589,9 @@ protected:
 
   std::string strTrainfileBuffer;
   std::string strCurrentContext;
+  
+  ///builds up the word currently being entered for speech.
+  std::string m_strCurrentWord;
 
   std::string m_strContext;
 
@@ -560,6 +606,8 @@ protected:
   /// @}
 
   bool m_bLastChanged;
+  
+  std::set<TextAction *> m_vTextActions;
 };
 /// @}
 
diff --git a/Src/DasherCore/DefaultFilter.h b/Src/DasherCore/DefaultFilter.h
index b4a8826..5373fea 100644
--- a/Src/DasherCore/DefaultFilter.h
+++ b/Src/DasherCore/DefaultFilter.h
@@ -12,6 +12,7 @@ class CDefaultFilter : public CInputFilter {
  public:
   CDefaultFilter(Dasher::CEventHandler * pEventHandler, CSettingsStore *pSettingsStore, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName);
   ~CDefaultFilter();
+  virtual bool supportsPause() {return true;}
 
   virtual void HandleEvent(Dasher::CEvent * pEvent);
 
diff --git a/Src/DasherCore/DynamicFilter.h b/Src/DasherCore/DynamicFilter.h
index c0580bc..7a94357 100644
--- a/Src/DasherCore/DynamicFilter.h
+++ b/Src/DasherCore/DynamicFilter.h
@@ -31,6 +31,8 @@ class CDynamicFilter : public CInputFilter {
  public:
   CDynamicFilter(Dasher::CEventHandler * pEventHandler, CSettingsStore *pSettingsStore, CDasherInterfaceBase *pInterface, ModuleID_t iID, int iType, const char *szName);
   
+  virtual bool supportsPause() {return true;}
+  
   ///when reversing, backs off; when paused, does nothing; when running, delegates to TimerImpl
   virtual bool Timer(int Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, Dasher::VECTOR_SYMBOL_PROB *pAdded, int *pNumDeleted, CExpansionPolicy **pol); 
 
diff --git a/Src/DasherCore/InputFilter.h b/Src/DasherCore/InputFilter.h
index 25a704a..e7105d6 100644
--- a/Src/DasherCore/InputFilter.h
+++ b/Src/DasherCore/InputFilter.h
@@ -38,6 +38,8 @@ class CInputFilter : public CDasherModule {
     return false;
   }
   
+  virtual bool supportsPause() {return false;}
+  
  protected:
   CDasherInterfaceBase *m_pInterface;
 
diff --git a/Src/DasherCore/NodeCreationManager.cpp b/Src/DasherCore/NodeCreationManager.cpp
index 506beda..c321056 100644
--- a/Src/DasherCore/NodeCreationManager.cpp
+++ b/Src/DasherCore/NodeCreationManager.cpp
@@ -16,7 +16,8 @@ using namespace Dasher;
 CNodeCreationManager::CNodeCreationManager(Dasher::CDasherInterfaceBase *pInterface,
 					   Dasher::CEventHandler *pEventHandler, 
 					   CSettingsStore *pSettingsStore,
-					   Dasher::CAlphIO *pAlphIO) : CDasherComponent(pEventHandler, pSettingsStore) {
+					   Dasher::CAlphIO *pAlphIO) : CDasherComponent(pEventHandler, pSettingsStore),
+  m_pInterface(pInterface), m_pControlManager(NULL) {
 
   const Dasher::CAlphIO::AlphInfo &oAlphInfo(pAlphIO->GetInfo(pSettingsStore->GetStringParameter(SP_ALPHABET_ID)));
   m_pAlphabet = new CAlphabet(oAlphInfo);
@@ -119,11 +120,8 @@ CNodeCreationManager::CNodeCreationManager(Dasher::CDasherInterfaceBase *pInterf
     delete pLM2;
   }
 #endif
-#ifndef _WIN32_WCE
-  m_pControlManager = new CControlManager(this,pInterface);
-#else
-  m_pControlManager = 0;
-#endif
+
+  HandleEvent(&CParameterNotificationEvent(BP_CONTROL_MODE));
   
   switch(oAlphInfo.m_iConversionID) {
     default:
@@ -154,32 +152,10 @@ CNodeCreationManager::~CNodeCreationManager() {
   if (m_pConversionManager) m_pConversionManager->Unref();
 }
 
-void CNodeCreationManager::RegisterNode( int iID, const std::string &strLabel, int iColour ) {
-  if(m_pControlManager)
-    m_pControlManager->RegisterNode(iID, strLabel, iColour);
-}
-
-void CNodeCreationManager::ConnectNode(int iChild, int iParent, int iAfter) {
-  if(m_pControlManager)
-    m_pControlManager->ConnectNode(iChild, iParent, iAfter);
-}
-
-void CNodeCreationManager::DisconnectNode(int iChild, int iParent) {
-  if(m_pControlManager)
-    m_pControlManager->DisconnectNode(iChild, iParent);
-}
-
 CDasherNode *CNodeCreationManager::GetAlphRoot(Dasher::CDasherNode *pParent, unsigned int iLower, unsigned int iUpper, bool bEnteredLast, int iOffset) { 
  return m_pAlphabetManager->GetRoot(pParent, iLower, iUpper, bEnteredLast, iOffset);
 }
 
-CDasherNode *CNodeCreationManager::GetCtrlRoot(Dasher::CDasherNode *pParent, unsigned int iLower, unsigned int iUpper, int iOffset) { 
- if(m_pControlManager)
- return m_pControlManager->GetRoot(pParent, iLower, iUpper, iOffset);
- else
- return NULL;
-}
-
 CDasherNode *CNodeCreationManager::GetConvRoot(Dasher::CDasherNode *pParent, unsigned int iLower, unsigned int iUpper, int iOffset) { 
  if(m_pConversionManager)
    return m_pConversionManager->GetRoot(pParent, iLower, iUpper, iOffset);
@@ -242,6 +218,17 @@ void CNodeCreationManager::GetProbs(CLanguageModel::Context context, std::vector
 
 }
 
+void CNodeCreationManager::HandleEvent(CEvent *pEvent) {
+  if (pEvent->m_iEventType == EV_PARAM_NOTIFY) {
+    if (static_cast<CParameterNotificationEvent *>(pEvent)->m_iParameter == BP_CONTROL_MODE) {
+      delete m_pControlManager;
+      m_pControlManager = (GetBoolParameter(BP_CONTROL_MODE))
+        ? new CControlManager(m_pEventHandler, m_pSettingsStore, this, m_pInterface)
+        : NULL;
+    } 
+  }
+}
+
 void 
 CNodeCreationManager::ImportTrainingText(const std::string &strPath) {
 	m_pTrainer->LoadFile(strPath);
diff --git a/Src/DasherCore/NodeCreationManager.h b/Src/DasherCore/NodeCreationManager.h
index f189feb..0f16117 100644
--- a/Src/DasherCore/NodeCreationManager.h
+++ b/Src/DasherCore/NodeCreationManager.h
@@ -5,9 +5,11 @@
 #include "Alphabet/AlphIO.h"
 #include "AlphabetManager.h"
 #include "ConversionManager.h"
+#include "ControlManager.h"
 #include "DasherComponent.h"
 #include "LanguageModelling/LanguageModel.h"
 #include "Trainer.h"
+#include "Event.h"
 
 #include <string>
 #include <vector>
@@ -28,32 +30,17 @@ class CNodeCreationManager : public Dasher::CDasherComponent {
 		       Dasher::CAlphIO *pAlphIO);
   ~CNodeCreationManager();
   
+  //we watch for changes to BP_CONTROL_MODE and create the Control Manager lazily
+  void HandleEvent(Dasher::CEvent *pEvent);
   ///
   /// Get a root node of a particular type
   ///
 
   Dasher::CDasherNode *GetAlphRoot(Dasher::CDasherNode *pParent, unsigned int iLower, unsigned int iUpper, bool bEnteredLast, int iOffset);
-  Dasher::CDasherNode *GetCtrlRoot(Dasher::CDasherNode *pParent, unsigned int iLower, unsigned int iUpper, int iOffset);
   Dasher::CDasherNode *GetConvRoot(Dasher::CDasherNode *pParent, unsigned int iLower, unsigned int iUpper, int iOffset);
 
-  ///
-  /// Register a control node
-  ///
-
-  void RegisterNode( int iID, const std::string &strLabel, int iColour );
-
-  ///
-  /// Connect control nodes in the tree
-  ///
+  Dasher::CControlManager *GetControlManager() {return m_pControlManager;}
   
-  void ConnectNode(int iChild, int iParent, int iAfter);
-
-  ///
-  /// Disconnect control nodes
-  ///
-
-  void DisconnectNode(int iChild, int iParent);
-
   void GetProbs(Dasher::CLanguageModel::Context context, std::vector <unsigned int >&Probs, int iNorm) const;
 
   ///
@@ -71,6 +58,7 @@ class CNodeCreationManager : public Dasher::CDasherComponent {
   Dasher::CAlphabet *m_pAlphabet;        // pointer to the alphabet
   Dasher::CTrainer *m_pTrainer;
   
+  Dasher::CDasherInterfaceBase *m_pInterface;
   
   Dasher::CAlphabetManager *m_pAlphabetManager;
   Dasher::CControlManager *m_pControlManager;
diff --git a/Src/DasherCore/Parameters.h b/Src/DasherCore/Parameters.h
index 2e4b37e..61b50f7 100644
--- a/Src/DasherCore/Parameters.h
+++ b/Src/DasherCore/Parameters.h
@@ -42,6 +42,8 @@ enum {
   BP_CIRCLE_START, BP_GLOBAL_KEYBOARD, BP_NONLINEAR_Y,
   BP_SMOOTH_OFFSET, BP_CONVERSION_MODE, BP_STOP_OUTSIDE, BP_BACKOFF_BUTTON,
   BP_TWOBUTTON_REVERSE, BP_2B_INVERT_DOUBLE, BP_SLOW_START,
+  BP_COPY_ALL_ON_STOP, BP_SPEAK_ALL_ON_STOP, BP_SPEAK_WORDS,
+  BP_CONTROL_MODE_HAS_HALT, BP_CONTROL_MODE_HAS_EDIT, BP_CONTROL_MODE_HAS_COPY, BP_CONTROL_MODE_HAS_SPEECH,
 #ifdef TARGET_OS_IPHONE
   BP_CUSTOM_TILT, BP_DOUBLE_X,
 #endif
@@ -167,6 +169,17 @@ static bp_table boolparamtable[] = {
   {BP_TWOBUTTON_REVERSE, "TwoButtonReverse", PERS, false, "Reverse the up/down buttons in two button mode"},
   {BP_2B_INVERT_DOUBLE, "TwoButtonInvertDouble", PERS, false, "Double-press acts as opposite button in two-button mode"},
   {BP_SLOW_START, "SlowStart", PERS, false, "Start at low speed and increase"},
+  {BP_COPY_ALL_ON_STOP, "CopyOnStop", PERS, false, "Copy all text to clipboard whenever we stop"},
+  {BP_SPEAK_ALL_ON_STOP, "SpeakOnStop", PERS, false, "Speak all text whenever we stop"},
+  {BP_SPEAK_WORDS, "SpeakWords", PERS, false, "Speak words as they are written"},
+  {BP_CONTROL_MODE_HAS_HALT, "ControlHasHalt", PERS, false, "Force Control Mode to provide a stop action (triggering clipboard/speech)"},
+#ifdef TARGET_OS_MAC
+  {BP_CONTROL_MODE_HAS_EDIT, "ControlHasEdit", PERS, false, "Provide editing functions in control mode (forward & backward movement & deletion)"},
+#else
+  {BP_CONTROL_MODE_HAS_EDIT, "ControlHasEdit", PERS, true, "Provide editing functions in control mode (forward & backward movement & deletion)"},
+#endif
+  {BP_CONTROL_MODE_HAS_COPY, "ControlHasCopy", PERS, true, "Provide copy-to-clipboard actions in Control Mode (if platforms supports)"},
+  {BP_CONTROL_MODE_HAS_SPEECH, "ControlHasSpeech", PERS, true, "Provide speech actions in Control Mode (if platform supports)"},
 #ifdef TARGET_OS_IPHONE
   {BP_CUSTOM_TILT, "CustomTilt", PERS, false, "Use custom tilt axes"},
   {BP_DOUBLE_X, "DoubleXCoords", PERS, false, "Double X-coordinate of touch"},
diff --git a/Src/DasherCore/StylusFilter.h b/Src/DasherCore/StylusFilter.h
index 8b9ea79..3dd1905 100644
--- a/Src/DasherCore/StylusFilter.h
+++ b/Src/DasherCore/StylusFilter.h
@@ -9,7 +9,9 @@ namespace Dasher {
 class CStylusFilter : public CDefaultFilter {
  public:
   CStylusFilter(Dasher::CEventHandler * pEventHandler, CSettingsStore *pSettingsStore, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName);
-
+  ///Override DefaultFilter (which supports pause), as we don't
+  /// - motion requires continually holding stylus against screen
+  virtual bool supportsPause() {return false;}
   virtual bool Timer(int Time, CDasherView *pView, CDasherModel *pModel, Dasher::VECTOR_SYMBOL_PROB *pAdded, int *pNumDeleted, CExpansionPolicy **pol);
   virtual void KeyDown(int iTime, int iId, CDasherView *pView, CDasherModel *pModel, CUserLogBase *pUserLog);
   virtual void KeyUp(int iTime, int iId, CDasherView *pView, CDasherModel *pModel);
diff --git a/Src/Gtk2/DasherControl.h b/Src/Gtk2/DasherControl.h
index a3ef525..669ceae 100644
--- a/Src/Gtk2/DasherControl.h
+++ b/Src/Gtk2/DasherControl.h
@@ -130,6 +130,10 @@ public:
 
   ///Override to broadcast signal...
   virtual void Stop();
+  
+  virtual std::string GetAllContext() {
+    throw "Hack to make Gtk2 compile, should not be called as supportsSpeech()/supportsClipboard()==false";
+  }
 
 private:
   //  virtual void CreateSettingsStore();
diff --git a/Src/MacOSX/COSXDasherControl.h b/Src/MacOSX/COSXDasherControl.h
index 312aa19..b47a845 100644
--- a/Src/MacOSX/COSXDasherControl.h
+++ b/Src/MacOSX/COSXDasherControl.h
@@ -45,6 +45,8 @@ public:
   void SetParameter(NSString *aKey, id aValue);
   NSDictionary *ParameterDictionary();
   void goddamn(unsigned long iTime, bool bForceRedraw);
+  std::string GetAllContext();
+  void ClearAllContext();
   
 private:
   virtual void ScanAlphabetFiles(std::vector<std::string> &vFileList);
@@ -57,7 +59,10 @@ private:
   virtual void WriteTrainFile(const std::string &strNewText);
   virtual void StartTimer();
   virtual void ShutdownTimer();
-  
+  virtual bool SupportsSpeech();
+  virtual void Speak(const std::string &strText, bool bInterrupt);
+  virtual bool SupportsClipboard() {return true;}
+  virtual void CopyToClipboard(const std::string &strText);
   ///
   /// Pass events coming from the core to the appropriate handler.
   ///
diff --git a/Src/MacOSX/COSXDasherControl.mm b/Src/MacOSX/COSXDasherControl.mm
index 72450da..eec13c1 100644
--- a/Src/MacOSX/COSXDasherControl.mm
+++ b/Src/MacOSX/COSXDasherControl.mm
@@ -288,4 +288,25 @@ void COSXDasherControl::SetParameter(NSString *aKey, id aValue) {
   }
 }
 
+bool COSXDasherControl::SupportsSpeech() {
+  return [dasherApp supportsSpeech];
+}
+
+void COSXDasherControl::Speak(const std::string &strText, bool bInterrupt) {
+  [dasherApp speak:NSStringFromStdString(strText) interrupt:bInterrupt];
+}
+
+void COSXDasherControl::CopyToClipboard(const std::string &strText) {
+  [dasherApp copyToClipboard:NSStringFromStdString(strText)];
+}
+
+std::string COSXDasherControl::GetAllContext() {
+  return StdStringFromNSString([dasherEdit allContext]);
+}
+
+void COSXDasherControl::ClearAllContext() {
+  [dasherEdit clearContext];
+  CDasherInterfaceBase::ClearAllContext();
+}
+
 
diff --git a/Src/MacOSX/Dasher.xcodeproj/project.pbxproj b/Src/MacOSX/Dasher.xcodeproj/project.pbxproj
index 0d89023..a86a47b 100755
--- a/Src/MacOSX/Dasher.xcodeproj/project.pbxproj
+++ b/Src/MacOSX/Dasher.xcodeproj/project.pbxproj
@@ -160,8 +160,6 @@
 		1974FE6F0714861B00B95DA0 /* ZippyCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 1946CABA0481AD440000000A /* ZippyCache.m */; };
 		1974FE700714861B00B95DA0 /* ZippyString.m in Sources */ = {isa = PBXBuildFile; fileRef = 1946CABC0481AD440000000A /* ZippyString.m */; };
 		1974FE780714861B00B95DA0 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
-		19875621071AFB470034ECCB /* Chatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 1987561F071AFB470034ECCB /* Chatter.h */; };
-		19875622071AFB470034ECCB /* Chatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 19875620071AFB470034ECCB /* Chatter.m */; };
 		1988ABBD0C9FF97000D97977 /* GameMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = 1988ABB80C9FF97000D97977 /* GameMessages.h */; };
 		1988ABBE0C9FF97000D97977 /* GameStatistics.h in Headers */ = {isa = PBXBuildFile; fileRef = 1988ABB90C9FF97000D97977 /* GameStatistics.h */; };
 		1988ABBF0C9FF97000D97977 /* PinyinParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1988ABBA0C9FF97000D97977 /* PinyinParser.cpp */; };
@@ -363,6 +361,8 @@
 		335901B41009E5A900821255 /* ConversionHelper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 335901B31009E5A900821255 /* ConversionHelper.cpp */; };
 		335DB0FB100B332C006DB155 /* alphabet.spyDict.xml in Resources */ = {isa = PBXBuildFile; fileRef = 335DB0FA100B332C006DB155 /* alphabet.spyDict.xml */; };
 		335DB101100B3358006DB155 /* training_spyDict.txt in Resources */ = {isa = PBXBuildFile; fileRef = 335DB100100B3358006DB155 /* training_spyDict.txt */; };
+		339055E81195FBD0001BE240 /* Queue.h in Headers */ = {isa = PBXBuildFile; fileRef = 339055E61195FBD0001BE240 /* Queue.h */; };
+		339055E91195FBD0001BE240 /* Queue.m in Sources */ = {isa = PBXBuildFile; fileRef = 339055E71195FBD0001BE240 /* Queue.m */; };
 		33ABFEC60FC379EA00EA2BA5 /* ButtonMultiPress.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33ABFEC40FC379EA00EA2BA5 /* ButtonMultiPress.cpp */; };
 		33ABFEC70FC379EA00EA2BA5 /* ButtonMultiPress.h in Headers */ = {isa = PBXBuildFile; fileRef = 33ABFEC50FC379EA00EA2BA5 /* ButtonMultiPress.h */; };
 		33E173C70F3E0B6400D19B38 /* Makefile.am in Resources */ = {isa = PBXBuildFile; fileRef = 33E173A70F3E0B6400D19B38 /* Makefile.am */; };
@@ -567,8 +567,6 @@
 		1974FD9C07145C6500B95DA0 /* Credits.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = Credits.html; sourceTree = "<group>"; };
 		1974FE7A0714861B00B95DA0 /* Dasher.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Dasher.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		1974FE7C0714861B00B95DA0 /* Info-Dasher.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-Dasher.plist"; sourceTree = SOURCE_ROOT; };
-		1987561F071AFB470034ECCB /* Chatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Chatter.h; sourceTree = "<group>"; };
-		19875620071AFB470034ECCB /* Chatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Chatter.m; sourceTree = "<group>"; };
 		1988ABB70C9FF97000D97977 /* GameLevel.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = GameLevel.cpp; sourceTree = "<group>"; };
 		1988ABB80C9FF97000D97977 /* GameMessages.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = GameMessages.h; sourceTree = "<group>"; };
 		1988ABB90C9FF97000D97977 /* GameStatistics.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = GameStatistics.h; sourceTree = "<group>"; };
@@ -779,6 +777,8 @@
 		335901B31009E5A900821255 /* ConversionHelper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ConversionHelper.cpp; sourceTree = "<group>"; };
 		335DB0FA100B332C006DB155 /* alphabet.spyDict.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = alphabet.spyDict.xml; sourceTree = "<group>"; };
 		335DB100100B3358006DB155 /* training_spyDict.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = training_spyDict.txt; sourceTree = "<group>"; };
+		339055E61195FBD0001BE240 /* Queue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Queue.h; sourceTree = "<group>"; };
+		339055E71195FBD0001BE240 /* Queue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Queue.m; sourceTree = "<group>"; };
 		33ABFEC40FC379EA00EA2BA5 /* ButtonMultiPress.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ButtonMultiPress.cpp; sourceTree = "<group>"; };
 		33ABFEC50FC379EA00EA2BA5 /* ButtonMultiPress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ButtonMultiPress.h; sourceTree = "<group>"; };
 		33E173A70F3E0B6400D19B38 /* Makefile.am */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Makefile.am; sourceTree = "<group>"; };
@@ -844,8 +844,6 @@
 				19E1AE4E0B0DB73300F3466C /* COSXDasherScreen.mm */,
 				19B57A74080D4E4900BCE3C6 /* AppWatcher.h */,
 				19B57A72080D4E4000BCE3C6 /* AppWatcher.m */,
-				1987561F071AFB470034ECCB /* Chatter.h */,
-				19875620071AFB470034ECCB /* Chatter.m */,
 				19C49619045029A40000000A /* DasherApp.h */,
 				19EEDB310450E75F0000000A /* DasherApp.mm */,
 				1904CDA5048813400000000A /* DasherEdit.h */,
@@ -879,6 +877,8 @@
 				19F36D8D0B18B60E002F41F1 /* ZippyStringGlyph.m */,
 				19F36D8A0B18B60E002F41F1 /* ZippyStringImage.h */,
 				19F36D8B0B18B60E002F41F1 /* ZippyStringImage.m */,
+				339055E61195FBD0001BE240 /* Queue.h */,
+				339055E71195FBD0001BE240 /* Queue.m */,
 			);
 			name = "Mac OS X Classes";
 			sourceTree = "<group>";
@@ -1397,7 +1397,6 @@
 				198EC7B407153D6E00474B38 /* KeyboardEvent.h in Headers */,
 				198EC7B707153D6E00474B38 /* LowLevelKeyboardHandling.h in Headers */,
 				198EC7B807153D6E00474B38 /* UnicharGenerator.h in Headers */,
-				19875621071AFB470034ECCB /* Chatter.h in Headers */,
 				19B57A75080D4E4900BCE3C6 /* AppWatcher.h in Headers */,
 				190257FD0B0C981300178CCD /* DasherEdit.h in Headers */,
 				190258010B0C981900178CCD /* DasherViewOpenGL.h in Headers */,
@@ -1498,6 +1497,7 @@
 				33135356102C6D8E00E28220 /* ButtonMode.h in Headers */,
 				3300115310A2EA7700D31B1D /* ExpansionPolicy.h in Headers */,
 				3300114910A2E9C900D31B1D /* DelayedDraw.h in Headers */,
+				339055E81195FBD0001BE240 /* Queue.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1761,7 +1761,6 @@
 				198EC7B507153D6E00474B38 /* KeyboardEvent.m in Sources */,
 				198EC7B607153D6E00474B38 /* LowLevelKeyboardHandling.c in Sources */,
 				198EC7B907153D6E00474B38 /* UnicharGenerator.m in Sources */,
-				19875622071AFB470034ECCB /* Chatter.m in Sources */,
 				19B57A73080D4E4000BCE3C6 /* AppWatcher.m in Sources */,
 				190257FC0B0C980800178CCD /* DasherApp.mm in Sources */,
 				190257FE0B0C981400178CCD /* DasherEdit.mm in Sources */,
@@ -1846,6 +1845,7 @@
 				33135353102C6D8E00E28220 /* CompassMode.cpp in Sources */,
 				33135355102C6D8E00E28220 /* ButtonMode.cpp in Sources */,
 				3300115210A2EA7700D31B1D /* ExpansionPolicy.cpp in Sources */,
+				339055E91195FBD0001BE240 /* Queue.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/Src/MacOSX/DasherApp.h b/Src/MacOSX/DasherApp.h
index 5217f2f..c120c68 100644
--- a/Src/MacOSX/DasherApp.h
+++ b/Src/MacOSX/DasherApp.h
@@ -14,6 +14,7 @@
 //#import "DasherAppInterface.h"
 #import "COSXDasherScreen.h"
 #import "DasherViewCocoa.h"
+#import "Queue.h"
 
 @class AppWatcher;
 @class DasherView;
@@ -27,7 +28,8 @@
   
   IBOutlet AppWatcher *appWatcher;
   NSTimer *_timer;
-  
+  Queue *spQ;
+  NSSpeechSynthesizer *spSyn;
 }
 
 - (void)start;
@@ -56,5 +58,7 @@
 - (void)setTimer:(NSTimer *)newTimer;
 - (void)timerCallback:(NSTimer *)aTimer;
 - (void)dealloc;
-
+- (bool)supportsSpeech;
+- (void)speak:(NSString *)sText interrupt:(bool)bInt;
+- (void)copyToClipboard:(NSString *)sText;
 @end
diff --git a/Src/MacOSX/DasherApp.mm b/Src/MacOSX/DasherApp.mm
index 66a3273..2aed2cf 100644
--- a/Src/MacOSX/DasherApp.mm
+++ b/Src/MacOSX/DasherApp.mm
@@ -80,7 +80,8 @@
 {
   if (self = [super init])
     {
-    [self setAquaDasherControl:new COSXDasherControl(self)];
+      [self setAquaDasherControl:new COSXDasherControl(self)];
+      spQ = [[Queue alloc] init];
     }
 
   return self;
@@ -196,4 +197,41 @@
   [super dealloc]; 
 }  
 
+- (bool)supportsSpeech {
+  if (!spSyn) {
+    //hmmm. don't see any way for this to (indicate) fail(ure)...???
+    spSyn = [[NSSpeechSynthesizer alloc] init];
+    [spSyn setDelegate:self];
+  }
+  return YES;
+}
+
+- (void)speak:(NSString *)sText interrupt:(bool)bInt {
+  if (bInt)
+    [spQ clear];
+  else {
+    @synchronized(spQ) {
+      if ([spSyn isSpeaking] || [spQ hasItems]) {
+        [spQ push:sText];
+        return;
+      }
+    }
+  }
+  [spSyn startSpeakingString:sText];
+}
+
+-(void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)success {
+  @synchronized(spQ) {
+    if ([spQ hasItems]) {
+      [spSyn startSpeakingString:[spQ pop]];
+    }
+  }
+}
+
+-(void)copyToClipboard:(NSString *)sText {
+  NSPasteboard *pboard = [NSPasteboard generalPasteboard];
+  [pboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
+  [pboard setString:sText forType:NSStringPboardType];
+}
+
 @end
diff --git a/Src/MacOSX/DasherEdit.h b/Src/MacOSX/DasherEdit.h
index a74d700..2618510 100644
--- a/Src/MacOSX/DasherEdit.h
+++ b/Src/MacOSX/DasherEdit.h
@@ -19,5 +19,7 @@
 - (void)outputCallback:(NSString *)aString targetApp:(AXUIElementRef)aTargetApp;
 - (void)deleteCallback:(NSString *)s targetApp:(AXUIElementRef)aTargetApp;
 - (NSString *)textAtOffset:(int)iOffset Length:(int)iLength;
+- (NSString *)allContext;
+- (void)clearContext;
 
 @end
diff --git a/Src/MacOSX/DasherEdit.mm b/Src/MacOSX/DasherEdit.mm
index 64cbc8c..1ce3343 100644
--- a/Src/MacOSX/DasherEdit.mm
+++ b/Src/MacOSX/DasherEdit.mm
@@ -9,7 +9,6 @@
 #import "DasherEdit.h"
 #import "PreferencesController.h"
 #import "UnicharGenerator.h"
-#import "Chatter.h"
 #import "../Common/Common.h"
 #import <Carbon/Carbon.h>
 
@@ -38,7 +37,6 @@
   [self sendString:aString toTargetApp:aTargetApp];
   dasherIsModifyingText = NO;
   [allTextEntered appendString:aString];
-  [[Chatter sharedInstance] addToBufferedText:aString];
 }
 
 - (void)deleteCallback:(NSString *)s targetApp:(AXUIElementRef)aTargetApp
@@ -54,7 +52,6 @@
     }
   dasherIsModifyingText = NO;
   [allTextEntered deleteCharactersInRange:NSMakeRange([allTextEntered length]-len, len)];
-  [[Chatter sharedInstance] removeFromBufferedText:s];
 }
 
 -(NSString *)textAtOffset:(int)iOffset Length:(int)iLength {
@@ -62,5 +59,12 @@
   return [allTextEntered substringWithRange:NSMakeRange(iOffset,iLength)];
 }
 
+-(NSString *)allContext {
+  return allTextEntered;
+}
+
+-(void)clearContext {
+  [allTextEntered setString:@""];
+}
 
 @end
diff --git a/Src/MacOSX/DasherViewAqua.mm b/Src/MacOSX/DasherViewAqua.mm
index f45bce3..6b8234d 100755
--- a/Src/MacOSX/DasherViewAqua.mm
+++ b/Src/MacOSX/DasherViewAqua.mm
@@ -17,7 +17,6 @@
 
 #import "ZippyCache.h"
 #import "ZippyString.h"
-#import "Chatter.h"
 #import "DasherUtil.h"
 #import "DasherApp.h"
 
diff --git a/Src/MacOSX/DasherViewOpenGL.mm b/Src/MacOSX/DasherViewOpenGL.mm
index 9d01ccc..abaf937 100755
--- a/Src/MacOSX/DasherViewOpenGL.mm
+++ b/Src/MacOSX/DasherViewOpenGL.mm
@@ -15,7 +15,6 @@
 
 #import <sys/time.h>
 
-#import "Chatter.h"
 #import "DasherUtil.h"
 #import "DasherApp.h"
 #import "GLUtils.h"
diff --git a/Src/MacOSX/Queue.h b/Src/MacOSX/Queue.h
new file mode 100644
index 0000000..c84733b
--- /dev/null
+++ b/Src/MacOSX/Queue.h
@@ -0,0 +1,21 @@
+//
+//  Queue.h
+//  Dasher
+//
+//  Created by Alan Lawrence on 08/05/2010.
+//  Copyright 2010 Cavendish Laboratory. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+ interface Queue : NSObject {
+  NSMutableArray *m_in, *m_out;
+}
+
+-(id)init;
+-(void)push:(id)obj;
+-(id)pop;
+-(bool)hasItems;
+-(void)clear;
+ end
diff --git a/Src/MacOSX/Queue.m b/Src/MacOSX/Queue.m
new file mode 100644
index 0000000..43d029d
--- /dev/null
+++ b/Src/MacOSX/Queue.m
@@ -0,0 +1,57 @@
+//
+//  Queue.m
+//  Dasher
+//
+//  Created by Alan Lawrence on 08/05/2010.
+//  Copyright 2010 Cavendish Laboratory. All rights reserved.
+//
+
+#import "Queue.h"
+
+
+ implementation Queue
+
+-(id)init {
+  if (self = [super init]) {
+    m_in = [NSMutableArray arrayWithCapacity:5];
+    [m_in retain];
+    m_out = [NSMutableArray arrayWithCapacity:5];
+    [m_out retain];
+  }
+  return self;
+}
+
+-(void)clear {
+  [m_in removeAllObjects];
+  [m_out removeAllObjects];
+  NSLog(@"Cleared\n");
+}
+
+-(void)push:(id)obj {
+  [m_in addObject:obj];
+  NSLog(@"Push %@, now %i items\n",obj,[m_in count]+[m_out count]);
+}
+
+-(id)pop {
+  NSLog(@"Pop - %i+%i items...",[m_in count],[m_out count]);
+  if ([m_out count]==0) {
+    [m_out addObjectsFromArray:[[m_in reverseObjectEnumerator] allObjects]];
+    [m_in removeAllObjects];
+  }
+  id ret= [m_out lastObject];
+  [m_out removeLastObject];
+  NSLog(@"now %i + %i\n",[m_in count],[m_out count]);
+  return ret;
+}
+
+-(bool)hasItems {
+  return [m_in count]>0 || [m_out count]>0;
+}
+
+-(void)dealloc {
+  [m_in release];
+  [m_out release];
+  [super dealloc];
+}
+
+ end
diff --git a/Src/Win32/Dasher.cpp b/Src/Win32/Dasher.cpp
index dcd4ce8..cd678e6 100644
--- a/Src/Win32/Dasher.cpp
+++ b/Src/Win32/Dasher.cpp
@@ -16,6 +16,9 @@
 
 #include "Common/WinOptions.h"
 
+//ACL not sure what headers we need to include to get clipboard operations, but may need:
+//#include <afxpriv.h>
+
 #ifndef _WIN32_WCE
 #include <sys/stat.h>
 #endif
@@ -59,13 +62,6 @@ void CDasher::CreateModules() {
   RegisterModule(new CDasherMouseInput(m_pEventHandler, m_pSettingsStore, m_pCanvas->getwindow()));
 }
 
-void CDasher::Stop() {
-  if (!GetBoolParameter(BP_DASHER_PAUSED)) {
-    CDasherInterfaceBase::Stop();
-    if (m_pEdit) m_pEdit->HandleStop();
-  }
-}
-
 void CDasher::Main() {
   if(m_pCanvas) {
 		m_pCanvas->DoFrame();
@@ -301,3 +297,60 @@ void CDasher::Move(int iX, int iY, int iWidth, int iHeight) {
 void CDasher::TakeFocus() {
   // TODO: Implement me
 }
+#ifndef _WIN32_WCE
+bool CDasher::SupportsSpeech() {
+  if (!m_bAttemptedSpeech) {
+    //try to create speech synthesizer lazily, saving resources if no speech req'd.
+    HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);
+    
+    if(hr!=S_OK)
+      pVoice=0;
+    else if (pVoice) {
+      //ACL Do we need to check pVoice? copying old code again, previous comment said:
+      // TODO: Why is this needed?
+      pVoice->Speak(L"",SPF_ASYNC,NULL);
+    }
+    m_bAttemptedSpeech = true;
+  }
+  return pVoice;
+}
+
+void CDasher::Speak(const string &strText, bool bInterrupt) {
+  //ACL TODO - take account of bInterrupt
+  if (pVoice)
+    pVoice->Speak(strText.c_str(), SPF_ASYNC, NULL);
+}
+#endif
+bool CDasher::SupportsClipboard {
+  return true;
+}
+
+void CDasher::CopyToClipboard(const string &strText) {
+  CString cText(strText.c_str());
+  if (OpenClipboard())
+  {
+    EmptyClipboard(); //also frees memory containing any previous data
+    
+    //Allocate global memory for string - enough for characters + NULL.
+    HGLOBAL hClipboardData = GlobalAlloc(GMEM_DDESHARE, strData.GetLength()+1);
+    
+    //GlobalLock returns a pointer to the data associated with the handle returned from GlobalAlloc    
+    char * pchData = (char*)GlobalLock(hClipboardData);
+
+    //now fill it...
+    strcpy(pchData, LPCSTR(strData));
+    
+    //Unlock memory, i.e. release our access to it - 
+    // but don't free it (with GlobalFree), as it will "belong"
+    // to the clipboard.
+    GlobalUnlock(hClipboardData);
+    
+    //Now, point the clipboard at that global memory...
+    //ACL may have to use CF_TEXT or CF_OEMTEXT prior to WinNT/2K???
+    SetClipboardData(CF_UNICODETEXT,hClipboardData);
+    
+    //Finally, unlock the clipboard (i.e. a pointer to the data on it!)
+    // so that other applications can see / modify it
+    CloseClipboard();
+  }
+}
diff --git a/Src/Win32/Dasher.h b/Src/Win32/Dasher.h
index 4d15c09..b57a5c0 100644
--- a/Src/Win32/Dasher.h
+++ b/Src/Win32/Dasher.h
@@ -4,6 +4,7 @@
 #include "../DasherCore/DasherInterfaceBase.h"
 #include "../DasherCore/UserLog.h"
 
+#include <sapi.h>
 #include <string>
 #include <vector>
 
@@ -46,11 +47,16 @@ public:
   void GameMessageOut(int message, const void* messagedata);
   
   virtual void WriteTrainFile(const std::string &strNewText);
-  ///Override to implement copy/speak-on-stop
-  void Stop();
   void Main(); 
 
-
+#ifndef _WIN32_WCE
+  //on WinCE, do not support speech - so use defaults from CDasherInterfaceBase 
+  bool SupportsSpeech();
+  void Speak(const std::string &text, bool bInterrupt);
+#endif
+  bool SupportsClipboard() {return true;};
+  void CopyToClipboard(const std::string &text);
+  
 private:
 
   virtual void ScanAlphabetFiles(std::vector<std::string> &vFileList);
@@ -69,7 +75,10 @@ private:
 
   CCanvas *m_pCanvas;
   CDashEditbox *m_pEdit;
- 
   HWND m_hParent;
+#ifndef _WIN32_WCE
+  ISpVoice *pVoice;
+  bool attemptedSpeech;
+#endif
 };
 }
diff --git a/Src/Win32/DasherWindow.cpp b/Src/Win32/DasherWindow.cpp
index c70ba0d..a156742 100644
--- a/Src/Win32/DasherWindow.cpp
+++ b/Src/Win32/DasherWindow.cpp
@@ -137,28 +137,6 @@ HWND CDasherWindow::Create() {
   if (!hSplitter)
 	  return 0;
 
-  // Add extra control nodes
-
-  m_pDasher->RegisterNode( Dasher::CControlManager::CTL_USER, "Speak", -1 );
-  m_pDasher->RegisterNode( Dasher::CControlManager::CTL_USER+1, "All", -1 );
-  m_pDasher->RegisterNode( Dasher::CControlManager::CTL_USER+2, "New", -1 );
-  m_pDasher->RegisterNode( Dasher::CControlManager::CTL_USER+3, "Repeat", -1 );
-
-  m_pDasher->ConnectNode(Dasher::CControlManager::CTL_USER, Dasher::CControlManager::CTL_ROOT, -2);
-
-  m_pDasher->ConnectNode(Dasher::CControlManager::CTL_USER+1, Dasher::CControlManager::CTL_USER, -2);
-  m_pDasher->ConnectNode(Dasher::CControlManager::CTL_USER+2, Dasher::CControlManager::CTL_USER, -2);
-  m_pDasher->ConnectNode(Dasher::CControlManager::CTL_USER+3, Dasher::CControlManager::CTL_USER, -2);
- 
-  m_pDasher->ConnectNode(-1, Dasher::CControlManager::CTL_USER+1, -2);
-  m_pDasher->ConnectNode(Dasher::CControlManager::CTL_ROOT, Dasher::CControlManager::CTL_USER+1, -2);
-  
-  m_pDasher->ConnectNode(-1, Dasher::CControlManager::CTL_USER+2, -2);
-  m_pDasher->ConnectNode(Dasher::CControlManager::CTL_ROOT, Dasher::CControlManager::CTL_USER+2, -2);
-
-  m_pDasher->ConnectNode(-1, Dasher::CControlManager::CTL_USER+3, -2);
-  m_pDasher->ConnectNode(Dasher::CControlManager::CTL_ROOT, Dasher::CControlManager::CTL_USER+3, -2);
-
   m_pGameModeHelper = 0;
 
   return hWnd;
diff --git a/Src/Win32/Widgets/AdvancedPage.cpp b/Src/Win32/Widgets/AdvancedPage.cpp
index 5ae21b6..1ce2156 100644
--- a/Src/Win32/Widgets/AdvancedPage.cpp
+++ b/Src/Win32/Widgets/AdvancedPage.cpp
@@ -42,9 +42,10 @@ static menuentry menutable[] = {
   {BP_SHOW_SLIDER, IDC_CHECK2},
   {APP_BP_TIME_STAMP, IDC_TIMESTAMP},
   {BP_CONTROL_MODE, IDC_CONTROLMODE},  // Not global setting - specific to editbox/widget
-  {APP_BP_SPEECH_WORD, IDC_CHECK4},
-  {APP_BP_SPEECH_MODE, IDC_CHECK3},
-  {APP_BP_COPY_ALL_ON_STOP, IDC_COPYONSTOP}
+  {BP_SPEAK_WORDS, IDC_CHECK4},
+  {BP_SPEAK_ALL_ON_STOP, IDC_CHECK3},
+  {BP_COPY_ALL_ON_STOP, IDC_COPYONSTOP}
+//ACL TODO BP_CONTROL_MODE_HAS_HALT, BP_CONTROL_MODE_HAS_SPEECH, BP_CONTROL_MODE_HAS_COPY - and perhaps automatically disable the latter two in direct mode, too?
 };
 
 std::string CAdvancedPage::GetControlText(HWND Dialog, int ControlID) 
diff --git a/Src/Win32/Widgets/Edit.cpp b/Src/Win32/Widgets/Edit.cpp
index db0ca3f..2bf5823 100644
--- a/Src/Win32/Widgets/Edit.cpp
+++ b/Src/Win32/Widgets/Edit.cpp
@@ -31,9 +31,6 @@
 #include "FilenameGUI.h"
 #include "../resource.h"
 #include "../../DasherCore/DasherInterfaceBase.h"
-#ifndef _WIN32_WCE
-#include "../ActionSpeech.h"
-#endif
 
 #include "../Common/DasherEncodingToCP.h"
 
@@ -60,20 +57,6 @@ CEdit::CEdit(CAppSettings *pAppSettings) {
 #ifndef _WIN32_WCE
   m_Font = GetCodePageFont(CodePage, 14);
 #endif
-
-  // TODO: Generalise this (and don't duplicate - read directly from
-  // text buffer).
-  speech.resize(0);
-
-#ifndef _WIN32_WCE
-  // TODO: Generalise actions, implement those present in Linux
-  // version.
-  m_pActionSpeech = new CActionSpeech;
-  m_pActionSpeech->Activate();
-#else
-  m_pActionSpeech = 0;
-#endif
-
 }
 
 HWND CEdit::Create(HWND hParent, bool bNewWithDate) {
@@ -93,11 +76,6 @@ CEdit::~CEdit() {
   delete m_FilenameGUI;
   if(FileHandle != INVALID_HANDLE_VALUE)
     CloseHandle(FileHandle);
-
-#ifndef _WIN32_WCE
-  m_pActionSpeech->Deactivate();
-  delete m_pActionSpeech;
-#endif
 }
 
 void CEdit::Move(int x, int y, int Width, int Height) {
@@ -393,17 +371,6 @@ void CEdit::Copy() {
 */
 }
 
-void CEdit::CopyAll() {
-  // One might think this would lead to flickering of selecting and
-  // unselecting. It doesn't seem to. Using the clipboard directly
-  // is fiddly, so this cheat is useful.
-  DWORD start, finish;
-  SendMessage(EM_GETSEL, (LONG) & start, (LONG) & finish);
-  SendMessage(EM_SETSEL, 0, -1);
-  SendMessage(WM_COPY, 0, 0);
-  SendMessage(EM_SETSEL, (LONG) start, (LONG) finish);
-}
-
 void CEdit::Paste() {
   SendMessage(WM_PASTE, 0, 0);
 }
@@ -414,7 +381,6 @@ void CEdit::SelectAll() {
 
 void CEdit::Clear() {
   SendMessage(WM_SETTEXT, 0, (LPARAM) TEXT(""));
-  speech.resize(0);
 }
 
 void CEdit::SetEncoding(Dasher::Opts::FileEncodingFormats Encoding) {
@@ -523,19 +489,6 @@ void CEdit::output(const std::string &sText) {
 #endif
   }
   m_Output += sText;
-
-  UTF8string_to_wstring(sText, newchar);
-  speech += newchar;
-
-  // Slightly hacky word by word preview
-  if(newchar == L" ") {
-    if(m_pAppSettings->GetBoolParameter(APP_BP_SPEECH_WORD) && m_pActionSpeech->GetActive())
-      m_pActionSpeech->Preview(m_strCurrentWord);
-    m_strCurrentWord = L"";
-  } 
-  else {
-    m_strCurrentWord += newchar;
-  }
 }
 
 void CEdit::Move(int iDirection, int iDist) {
@@ -909,55 +862,12 @@ if(m_pAppSettings->GetLongParameter(APP_LP_STYLE) == APP_STYLE_DIRECT) {
 #endif
   }
 
-  // Shorten the speech buffer (?)
-  if(speech.length() >= iLength) {
-    speech.resize(speech.length() - iLength);
-  }
-
-  // Shorten the speech buffer (?)
-  if(m_strCurrentWord.length() >= iLength) {
-    m_strCurrentWord.resize(m_strCurrentWord.length() - iLength);
-  }
-
   // And the output buffer (?)
   if(m_Output.length() >= iLength) {
     m_Output.resize(m_Output.length() - iLength);
   }
 }
 
-void CEdit::speak(int what) {
-  if(!m_pActionSpeech->GetActive())
-    return;
-
-  // TODO: The remainder of this function is somewhat horrible and hacky...
-  
-  // TODO: Horrible hack - don't speak in direct entry mode
-  if(m_pAppSettings->GetLongParameter(APP_LP_STYLE) == APP_STYLE_DIRECT)
-    return;
-
-  std::wstring strSpeech;
-
-  if(what == 1) { // All
-    int speechlength = GetWindowTextLength();
-    LPTSTR allspeech = new TCHAR[speechlength + 1];
-    GetWindowText(allspeech, speechlength + 1);
-    strSpeech = allspeech;
-    lastspeech = allspeech;
-    delete allspeech;
-    speech.resize(0);
-  }
-  else if(what == 2) { // New
-    strSpeech = speech;
-    lastspeech = speech;
-    speech.resize(0);
-  }
-  else if(what == 3) {
-    strSpeech = lastspeech;
-  }
-
-  m_pActionSpeech->Execute(strSpeech);
-}
-
 void CEdit::SetNewWithDate(bool bNewWithDate) {
   if(m_FilenameGUI)
     m_FilenameGUI->SetNewWithDate(bNewWithDate);
@@ -1003,10 +913,5 @@ void CEdit::HandleEditEvent(Dasher::CEvent *pEvent) {
 }
 
 void CEdit::HandleStop() {
-  // TODO: These should be more generally implemented as 
-  if(m_pAppSettings->GetBoolParameter(APP_BP_SPEECH_MODE))
-    speak(2);
-
-  if(m_pAppSettings->GetBoolParameter(APP_BP_COPY_ALL_ON_STOP))
-    CopyAll();
+  //speech and copy-to-clipboard are now global/platform-independent...
 }
diff --git a/Src/Win32/Widgets/Edit.h b/Src/Win32/Widgets/Edit.h
index 3ca9df7..c6d0773 100644
--- a/Src/Win32/Widgets/Edit.h
+++ b/Src/Win32/Widgets/Edit.h
@@ -136,9 +136,6 @@ class CEdit : public ATL::CWindowImpl<CEdit> {
   // remove the previous character
   void deletetext(const std::string & sText);
   
-  // speak text
-  void speak(int what);
-  
   void SetNewWithDate(bool bNewWithDate);
   void HandleEvent(Dasher::CEvent *pEvent);
 
@@ -184,19 +181,11 @@ class CEdit : public ATL::CWindowImpl<CEdit> {
 #ifdef _UNICODE
   INPUT fakekey[2];
 #endif
-    
-  Tstring speech;
-  Tstring lastspeech;
-  Tstring newchar;
   
   void InsertText(Tstring InsertText);  // add symbol to edit control
   
   CAppSettings *m_pAppSettings;
   HWND m_hWnd;
-  
-  CDasherAction *m_pActionSpeech;
-
-  std::wstring m_strCurrentWord;
 };
 
 #endif /* #ifndef __Edit_h__ */
diff --git a/Src/iPhone/Classes/CDasherInterfaceBridge.h b/Src/iPhone/Classes/CDasherInterfaceBridge.h
index 1fafcd1..4d92c56 100644
--- a/Src/iPhone/Classes/CDasherInterfaceBridge.h
+++ b/Src/iPhone/Classes/CDasherInterfaceBridge.h
@@ -47,6 +47,9 @@ public:
   
   void NotifyTouch(screenint x, screenint y);
   void SetTiltAxes(Vec3 main, float off, Vec3 slow, float off2);
+  bool SupportsClipboard() {return true;}
+  void CopyToClipboard(const std::string &strText);
+  std::string GetAllContext();
 private:
   virtual void ScanAlphabetFiles(std::vector<std::string> &vFileList);
   virtual void ScanColourFiles(std::vector<std::string> &vFileList);
diff --git a/Src/iPhone/Classes/CDasherInterfaceBridge.mm b/Src/iPhone/Classes/CDasherInterfaceBridge.mm
index c7f8c55..1594f5b 100644
--- a/Src/iPhone/Classes/CDasherInterfaceBridge.mm
+++ b/Src/iPhone/Classes/CDasherInterfaceBridge.mm
@@ -13,7 +13,7 @@
 #import "DasherAppDelegate.h"
 #import "Event.h"
 #import "CalibrationController.h"
-
+#import "ControlManager.h"
 #import "../Common/Common.h"
 
 #import <iostream>
@@ -24,9 +24,9 @@
 
 using namespace std;
 
-
 CDasherInterfaceBridge::CDasherInterfaceBridge(DasherAppDelegate *aDasherApp) : dasherApp(aDasherApp) {
   Realize();
+  [dasherApp setAlphabet:GetAlphabet()];
 }
 
 void CDasherInterfaceBridge::CreateModules() {
@@ -167,7 +167,9 @@ void CDasherInterfaceBridge::ExternalEventHandler(Dasher::CEvent *pEvent) {
 		Dasher::CParameterNotificationEvent *pEvt(static_cast<Dasher::CParameterNotificationEvent *>(pEvent));
 		if (pEvt->m_iParameter == LP_MAX_BITRATE || pEvt->m_iParameter == LP_BOOSTFACTOR)
 			[dasherApp notifySpeedChange];
-	  }
+    else if (pEvt->m_iParameter == SP_ALPHABET_ID)
+      [dasherApp setAlphabet:GetAlphabet()];
+    }
       break;
     case EV_EDIT:
 	  {
@@ -200,7 +202,40 @@ void CDasherInterfaceBridge::ExternalEventHandler(Dasher::CEvent *pEvent) {
       break;
 	}
     case EV_CONTROL:
-      NSLog(@"ExternalEventHandler, m_iEventType = EV_CONTROL");
+      switch (static_cast<CControlEvent *>(pEvent)->m_iID) {
+        case CControlManager::CTL_MOVE_FORWARD_CHAR:
+          [dasherApp move:EDIT_CHAR forwards:YES]; break;
+        case CControlManager::CTL_MOVE_FORWARD_WORD:
+          [dasherApp move:EDIT_WORD forwards:YES]; break;
+        case CControlManager::CTL_MOVE_FORWARD_LINE:
+          [dasherApp move:EDIT_LINE forwards:YES]; break;
+        case CControlManager::CTL_MOVE_FORWARD_FILE:
+          [dasherApp move:EDIT_FILE forwards:YES]; break;
+        case CControlManager::CTL_MOVE_BACKWARD_CHAR:
+          [dasherApp move:EDIT_CHAR forwards:NO]; break;
+        case CControlManager::CTL_MOVE_BACKWARD_WORD:
+          [dasherApp move:EDIT_WORD forwards:NO]; break;
+        case CControlManager::CTL_MOVE_BACKWARD_LINE:
+          [dasherApp move:EDIT_LINE forwards:NO]; break;
+        case CControlManager::CTL_MOVE_BACKWARD_FILE:
+          [dasherApp move:EDIT_FILE forwards:NO]; break;
+        case CControlManager::CTL_DELETE_FORWARD_CHAR:
+          [dasherApp del:EDIT_CHAR forwards:YES]; break;
+        case CControlManager::CTL_DELETE_FORWARD_WORD:
+          [dasherApp del:EDIT_WORD forwards:YES]; break;
+        case CControlManager::CTL_DELETE_FORWARD_LINE:
+          [dasherApp del:EDIT_LINE forwards:YES]; break;
+        case CControlManager::CTL_DELETE_FORWARD_FILE:
+          [dasherApp del:EDIT_FILE forwards:YES]; break;
+        case CControlManager::CTL_DELETE_BACKWARD_CHAR:
+          [dasherApp del:EDIT_CHAR forwards:NO]; break;
+        case CControlManager::CTL_DELETE_BACKWARD_WORD:
+          [dasherApp del:EDIT_WORD forwards:NO]; break;
+        case CControlManager::CTL_DELETE_BACKWARD_LINE:
+          [dasherApp del:EDIT_LINE forwards:NO]; break;
+        case CControlManager::CTL_DELETE_BACKWARD_FILE:
+        [dasherApp del:EDIT_FILE forwards:NO]; break;
+      }
       break;
     case EV_COMMAND:
       NSLog(@"ExternalEventHandler, m_iEventType = EV_COMMAND");
@@ -230,6 +265,15 @@ void CDasherInterfaceBridge::ExternalEventHandler(Dasher::CEvent *pEvent) {
   
 }
 
+void CDasherInterfaceBridge::CopyToClipboard(const std::string &strText) {
+  CDasherInterfaceBase::CopyToClipboard(strText);
+  [UIPasteboard generalPasteboard].string=NSStringFromStdString(strText);
+}
+
+string CDasherInterfaceBridge::GetAllContext() {
+  return StdStringFromNSString([dasherApp allText]);
+}
+
 int CDasherInterfaceBridge::GetFileSize(const std::string &strFileName) {
   struct stat sStatInfo;
   
diff --git a/Src/iPhone/Classes/DasherAppDelegate.h b/Src/iPhone/Classes/DasherAppDelegate.h
index 0035933..30f2c5e 100644
--- a/Src/iPhone/Classes/DasherAppDelegate.h
+++ b/Src/iPhone/Classes/DasherAppDelegate.h
@@ -11,6 +11,13 @@
 #import "CDasherScreenBridge.h"
 #import "TextView.h"
 
+typedef enum {
+  EDIT_CHAR,
+  EDIT_WORD,
+  EDIT_LINE,
+  EDIT_FILE
+} EEditDistance;
+
 @class EAGLView;
 
 @interface DasherAppDelegate : UIViewController <UIApplicationDelegate, UIActionSheetDelegate, UITextViewDelegate> {
@@ -27,14 +34,20 @@
   BOOL m_bLandscapeSupported;
   /// Should really be part of UIViewController (lockable), below...but then, how to find?
   UILabel *screenLockLabel;
+  
+  NSString *m_wordBoundary, *m_sentenceBoundary, *m_lineBoundary;
 }
 
 - (void)startTimer;
 - (void)shutdownTimer;
+- (void)setAlphabet:(CAlphabet *)pAlph;
 - (void)outputCallback:(NSString *)s;
 - (void)deleteCallback:(NSString *)s;
+- (void)move:(EEditDistance)amt forwards:(BOOL)bForwards;
+- (void)del:(EEditDistance)amt forwards:(BOOL)bForwards;
+- (NSString *)allText;
 - (void)notifySpeedChange;
-- (NSString *)textAtOffset:(int)offset Length:(int)length;
+- (NSString *)textAtOffset:(unsigned int)offset Length:(unsigned int)length;
 - (void)setLockText:(NSString *)s;
 - (void)displayMessage:(NSString *)msg ID:(int)iId Type:(int)type;
 - (void)setLandscapeSupported:(BOOL)supported;
diff --git a/Src/iPhone/Classes/DasherAppDelegate.mm b/Src/iPhone/Classes/DasherAppDelegate.mm
index 1e0fe2f..a8bf7a1 100644
--- a/Src/iPhone/Classes/DasherAppDelegate.mm
+++ b/Src/iPhone/Classes/DasherAppDelegate.mm
@@ -25,6 +25,9 @@
 - (void)speedSlid:(id)slider;
 - (CGRect)doLayout:(UIInterfaceOrientation)orient;
 @property (retain) UILabel *screenLockLabel;
+ property (nonatomic,retain) NSString *m_wordBoundary;
+ property (nonatomic,retain) NSString *m_sentenceBoundary;
+ property (nonatomic,retain) NSString *m_lineBoundary;
 @end
 
 //we can't call setHidden:BOOL with performSelector:withObject:, as passing [NSNumber numberWithBool:YES] (as one should)
@@ -36,7 +39,9 @@
 @implementation DasherAppDelegate
 
 @synthesize screenLockLabel;
-
+ synthesize m_wordBoundary;
+ synthesize m_sentenceBoundary;
+ synthesize m_lineBoundary;
 -(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
   if (interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
     return NO;
@@ -156,6 +161,7 @@
 	text.editable = NO;
 	text.delegate = self;
 	selectedText.location = selectedText.length = 0;
+  text.selectedRange=selectedText;
 
   messageLabel.backgroundColor = [UIColor grayColor];
   messageLabel.textColor = [UIColor whiteColor];
@@ -217,6 +223,9 @@
 	[self notifySpeedChange];
   self.dasherInterface->OnUIRealised(); //that does startAnimation...
   doneSetup = YES;
+  //The following will cause the text cursor to be displayed whenever
+  // any change is made to the textbox...
+  [text becomeFirstResponder];
 }
 
 - (void)displayMessage:(NSString *)msg ID:(int)iId Type:(int)type {
@@ -313,18 +322,16 @@
 
 - (void)outputCallback:(NSString *)s {
 	text.text=[text.text stringByReplacingCharactersInRange:selectedText withString:s];
-	selectedText.location++; 
+	selectedText.location+=[s length]; 
 	selectedText.length = 0;
-	text.selectedRange = selectedText; //shows keyboard, seems unavoidable :-(
+	text.selectedRange = selectedText;
 	//This isn't quite right, it jumps up then down again once you have >3 lines...
 	[text scrollRangeToVisible:selectedText];
 }
 
 - (void)deleteCallback:(NSString *)s {
   if (selectedText.length == 0) selectedText.location -= (selectedText.length = 1); //select previous character
-  text.text=[text.text stringByReplacingCharactersInRange:selectedText withString:@""];
-  selectedText.length = 0;
-  text.selectedRange = selectedText;
+  [self outputCallback:@""];
 }
 
 - (void)applicationWillResignActive:(UIApplication *)application {
@@ -389,13 +396,76 @@
 	[super dealloc];
 }
 
-- (NSString *)textAtOffset:(int)offset Length:(int)length {
+- (NSString *)textAtOffset:(unsigned int)offset Length:(unsigned int)length {
   NSRange range;
-  range.location = offset;
-  range.length = length;
+  //truncate both endpoints of desired range to lie within text.
+  // (Although requiring offset+length to be within text, has identified many bugs,
+  // the editing functions in control mode are too broken to fix right now! Hence,
+  // copying the Gtk2 behaviour...)
+  range.location = max(0u,min(offset,[text.text length]));
+  range.length = min(length,[text.text length] - range.location);
   return [text.text substringWithRange:range];
 }
 
+- (void)setAlphabet:(CAlphabet *)pAlph {
+  self.m_wordBoundary = pAlph->GetSpaceSymbol() ? NSStringFromStdString(pAlph->GetText(pAlph->GetSpaceSymbol())) : @" ";
+  self.m_lineBoundary = (pAlph->GetParagraphSymbol())
+  ? NSStringFromStdString(pAlph->GetText(pAlph->GetParagraphSymbol())) : nil;
+  self.m_sentenceBoundary = (pAlph->GetDefaultContext().length()>0)
+      ? NSStringFromStdString(pAlph->GetDefaultContext())
+      : @".";
+}
+
+- (int)find:(EEditDistance)amt forwards:(BOOL)bForwards {
+  if (amt==EDIT_FILE) return bForwards ? [text.text length] : 0;
+  int pos = selectedText.location;
+  for(;;) {
+    if (bForwards) {
+      if (++pos > [text.text length]) return pos-1;
+    } else {
+      if (--pos < 0) return 0;
+    }
+    NSString *lookFor;
+    switch (amt) {
+      case EDIT_CHAR:
+        //once only, never loop
+        return pos;
+      case EDIT_WORD:
+        lookFor = m_wordBoundary;
+        break;
+      case EDIT_LINE:
+        if (m_lineBoundary && [text.text compare:m_lineBoundary options:0 range:NSMakeRange(pos, [m_lineBoundary length])] == NSOrderedSame)
+          return pos;
+        lookFor = m_lineBoundary;
+        break;
+    }
+    if ([text.text compare:lookFor options:0 range:NSMakeRange(pos, [lookFor length])] == NSOrderedSame)
+      return pos;
+  }
+}
+
+- (void)move:(EEditDistance)amt forwards:(BOOL)bForwards {
+  selectedText.location = [self find:amt forwards:bForwards];
+  selectedText.length=0;
+  text.selectedRange = selectedText;
+  [text scrollRangeToVisible:selectedText];
+}
+
+- (void)del:(EEditDistance)amt forwards:(BOOL)bForwards {
+  int to = [self find:amt forwards:bForwards];
+  if (bForwards) {
+    selectedText.length = to-selectedText.location;
+  } else {
+    selectedText.length = selectedText.location - to;
+    selectedText.location = to;
+  }
+  [self outputCallback:@""];
+}
+
+- (NSString *)allText {
+  return text.text;
+}
+
 #pragma mark TextViewDelegate methods
 
 -(void)textViewDidChangeSelection:(UITextView *)textView {
@@ -494,4 +564,4 @@
   [self setHidden:YES];
 }
 
- end
\ No newline at end of file
+ end
diff --git a/Src/iPhone/Classes/InputMethodSelector.mm b/Src/iPhone/Classes/InputMethodSelector.mm
index 7474fc3..a3128f2 100644
--- a/Src/iPhone/Classes/InputMethodSelector.mm
+++ b/Src/iPhone/Classes/InputMethodSelector.mm
@@ -59,6 +59,7 @@ SSectionDesc allMeths[] = {
 
 @interface InputMethodSelector ()
 @property (retain) NSIndexPath *selectedPath;
+- (void)doSelect;
 @end
 
 @implementation InputMethodSelector
diff --git a/Src/iPhone/Classes/MiscSettings.mm b/Src/iPhone/Classes/MiscSettings.mm
index 43f9c01..11a27e3 100644
--- a/Src/iPhone/Classes/MiscSettings.mm
+++ b/Src/iPhone/Classes/MiscSettings.mm
@@ -14,19 +14,52 @@
 static SModuleSettings _settings[] = { //note iStep and string description are ignored
   {LP_NODE_BUDGET, T_LONG, 400, 10000, 1, 0, ""}, //hopefully appropriate for an iPhone 3GS?
   {LP_MARGIN_WIDTH, T_LONG, 100, 900, 1, 0, ""},
+  {LP_DASHER_FONTSIZE, T_LONG, 1, 3, 1, 1, ""},
   {BP_AUTO_SPEEDCONTROL, T_BOOL, -1, -1, -1, -1, ""},
   {LP_NONLINEAR_X, T_LONG, 0, 10, 1, -1, ""},
   {BP_DOUBLE_X, T_BOOL, -1, -1, -1, -1, ""},
 };
 static int _count = sizeof(_settings) / sizeof(_settings[0]);
 
+static SModuleSettings _controlSettings[] = {
+  {BP_CONTROL_MODE, T_BOOL, -1, -1, -1, -1, ""},
+  {BP_CONTROL_MODE_HAS_COPY, T_BOOL, -1, -1, -1, -1, ""},
+  {BP_CONTROL_MODE_HAS_HALT, T_BOOL, -1, -1, -1, -1, ""},
+  {BP_CONTROL_MODE_HAS_EDIT, T_BOOL, -1, -1, -1, -1, ""},
+  {BP_COPY_ALL_ON_STOP, T_BOOL, -1, -1, -1, -1, ""},
+};
+
+static int _controlCount = sizeof(_controlSettings) / sizeof(_controlSettings[0]);
+
 @implementation MiscSettings
 
 - (id)init {  
 	if (self = [super initWithTitle:@"Misc" Settings:_settings Count:_count]) {
 		self.tabBarItem.image = [UIImage imageNamed:@"misc.png"];
+    if (![self.view isKindOfClass:[UIScrollView class]]) {
+      [super dealloc];
+      return nil;
+    }
+    UIScrollView *view = (UIScrollView *)self.view;
+    CGSize oldSize = [view contentSize];
+    [view setContentSize:CGSizeMake(oldSize.width, oldSize.height + 70)];
+    UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
+    btn.frame = CGRectMake(40.0, oldSize.height+20.0, oldSize.width-80.0, 30.0);
+    [btn setTitle:@"Control Mode..." forState:UIControlStateNormal];
+    [btn addTarget:self action:@selector(control) forControlEvents:UIControlEventTouchUpInside];
+    [view addSubview:btn];
 	}
 	return self;
 }
 
+- (void)control {
+  ParametersController *control = [[[ParametersController alloc] initWithTitle:@"Control Mode" Settings:_controlSettings Count:_controlCount] autorelease];
+  [control setTarget:self Selector:@selector(controlDone)];
+  [self.navigationController pushViewController:control animated:YES];
+}
+
+-(void)controlDone {
+  [self.navigationController popViewControllerAnimated:YES];
+}
+
 @end



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]