[dasher: 13/28] Refactor movement, remove BP_DASHER_PAUSED



commit 9eae07fcd762b166490cafb053ad1af70e7a60a0
Author: Alan Lawrence <acl33 inf phy cam ac uk>
Date:   Wed Sep 21 17:04:05 2011 +0100

    Refactor movement, remove BP_DASHER_PAUSED
    
    Filters schedule one step or many steps as/when req'd (e.g. in Timer or on Key).
    
    This makes the two DasherModel movement methods, ScheduleZoom & ScheduleOneStep,
     _even_more_ similar...hopefully we will soon be able to combine them?
    
    Note much ambiguity remains as to when to call m_pInterface->Done() (was Stop).

 Src/DasherCore/AlternatingDirectMode.cpp  |    2 +-
 Src/DasherCore/BasicLog.cpp               |    3 +-
 Src/DasherCore/BasicLog.h                 |    4 +-
 Src/DasherCore/ButtonMode.cpp             |    4 +-
 Src/DasherCore/ButtonMode.h               |    2 +-
 Src/DasherCore/ButtonMultiPress.cpp       |   10 +-
 Src/DasherCore/ButtonMultiPress.h         |    6 +-
 Src/DasherCore/CircleStartHandler.cpp     |   69 +++++-----
 Src/DasherCore/CircleStartHandler.h       |   10 +-
 Src/DasherCore/ClickFilter.cpp            |    6 +-
 Src/DasherCore/ClickFilter.h              |    5 +-
 Src/DasherCore/ControlManager.cpp         |    8 +-
 Src/DasherCore/DashIntfScreenMsgs.cpp     |   32 ++---
 Src/DasherCore/DashIntfScreenMsgs.h       |    5 +-
 Src/DasherCore/DasherButtons.cpp          |   10 +-
 Src/DasherCore/DasherButtons.h            |    4 +-
 Src/DasherCore/DasherInterfaceBase.cpp    |   50 ++++---
 Src/DasherCore/DasherInterfaceBase.h      |   36 +++--
 Src/DasherCore/DasherModel.cpp            |  212 ++++++++++++++---------------
 Src/DasherCore/DasherModel.h              |   48 +++----
 Src/DasherCore/DefaultFilter.cpp          |   70 +++++-----
 Src/DasherCore/DefaultFilter.h            |    6 +-
 Src/DasherCore/DynamicButtons.cpp         |   65 ++++-----
 Src/DasherCore/DynamicButtons.h           |   19 +--
 Src/DasherCore/DynamicFilter.cpp          |   11 +-
 Src/DasherCore/DynamicFilter.h            |   14 ++-
 Src/DasherCore/GameModule.cpp             |   15 ++-
 Src/DasherCore/GameModule.h               |    2 +
 Src/DasherCore/InputFilter.h              |   48 ++++++-
 Src/DasherCore/OneButtonDynamicFilter.cpp |    5 +-
 Src/DasherCore/OneButtonDynamicFilter.h   |    2 +-
 Src/DasherCore/OneButtonFilter.cpp        |   13 +--
 Src/DasherCore/OneButtonFilter.h          |    5 +-
 Src/DasherCore/OneDimensionalFilter.cpp   |   31 ++--
 Src/DasherCore/Parameters.cpp             |    1 -
 Src/DasherCore/Parameters.h               |    2 +-
 Src/DasherCore/StartHandler.h             |    3 +-
 Src/DasherCore/StylusFilter.cpp           |   28 ++---
 Src/DasherCore/StylusFilter.h             |    7 +-
 Src/DasherCore/TwoBoxStartHandler.cpp     |   11 +-
 Src/DasherCore/TwoBoxStartHandler.h       |    4 +-
 Src/DasherCore/TwoButtonDynamicFilter.cpp |    8 +-
 Src/DasherCore/TwoButtonDynamicFilter.h   |    4 +-
 Src/DasherCore/TwoPushDynamicFilter.cpp   |   20 +--
 Src/DasherCore/TwoPushDynamicFilter.h     |    6 +-
 Src/DasherCore/UserLog.cpp                |    7 +-
 Src/DasherCore/UserLog.h                  |    2 +-
 Src/DasherCore/UserLogBase.cpp            |   15 +--
 Src/DasherCore/UserLogBase.h              |    9 +-
 Src/Gtk2/DasherControl.cpp                |    5 +-
 Src/Gtk2/DasherControl.h                  |    4 +-
 Src/Gtk2/GtkDasherControl.cpp             |    2 +-
 Src/iPhone/Classes/DasherAppDelegate.mm   |    2 +-
 Src/iPhone/Classes/IPhoneFilters.h        |    4 +-
 Src/iPhone/Classes/IPhoneFilters.mm       |   22 ++--
 55 files changed, 506 insertions(+), 492 deletions(-)
---
diff --git a/Src/DasherCore/AlternatingDirectMode.cpp b/Src/DasherCore/AlternatingDirectMode.cpp
index ddf7960..9ba5d74 100644
--- a/Src/DasherCore/AlternatingDirectMode.cpp
+++ b/Src/DasherCore/AlternatingDirectMode.cpp
@@ -108,7 +108,7 @@ void CAlternatingDirectMode::DirectKeyDown(unsigned long iTime, int iId, CDasher
       return;
   }
   //iTargetBox now indicates the box into which to zoom
-  pModel->ScheduleZoom(iTime, m_pBoxes[iTargetBox].iTop,  m_pBoxes[iTargetBox].iBottom);
+  ScheduleZoom(pModel, m_pBoxes[iTargetBox].iTop,  m_pBoxes[iTargetBox].iBottom);
 }
 
 bool CAlternatingDirectMode::GetSettings(SModuleSettings **pSettings, int *iCount) {
diff --git a/Src/DasherCore/BasicLog.cpp b/Src/DasherCore/BasicLog.cpp
index 3b04771..43f194c 100644
--- a/Src/DasherCore/BasicLog.cpp
+++ b/Src/DasherCore/BasicLog.cpp
@@ -14,7 +14,8 @@
 
 using namespace Dasher;
 
-CBasicLog::CBasicLog(CSettingsUser *pCreateFrom, CDasherInterfaceBase *pIntf) : CUserLogBase(pCreateFrom,pIntf) {
+CBasicLog::CBasicLog(CSettingsUser *pCreateFrom, CDasherInterfaceBase *pIntf)
+: CUserLogBase(pIntf), CSettingsUser(pCreateFrom) {
   m_iSymbolCount = 0;
   m_bStarted = false;
 }
diff --git a/Src/DasherCore/BasicLog.h b/Src/DasherCore/BasicLog.h
index 34de634..9bb95ee 100644
--- a/Src/DasherCore/BasicLog.h
+++ b/Src/DasherCore/BasicLog.h
@@ -6,9 +6,9 @@
 
 /// \ingroup Logging
 /// @{
-class CBasicLog : public CUserLogBase {
+class CBasicLog : public CUserLogBase, public Dasher::CSettingsUser {
  public:
-  CBasicLog(CSettingsUser *pCreateFrom, Dasher::CDasherInterfaceBase *pIntf);
+  CBasicLog(Dasher::CSettingsUser *pCreateFrom, Dasher::CDasherInterfaceBase *pIntf);
   ~CBasicLog();
 
   virtual void AddParam(const string& strName, const string& strValue, int iOptionMask = 0) {};
diff --git a/Src/DasherCore/ButtonMode.cpp b/Src/DasherCore/ButtonMode.cpp
index d8c4e5b..248e9ce 100644
--- a/Src/DasherCore/ButtonMode.cpp
+++ b/Src/DasherCore/ButtonMode.cpp
@@ -156,14 +156,14 @@ bool CButtonMode::DecorateView(CDasherView *pView, CDasherInput *pInput) {
   return bRV;
 }
 
-bool CButtonMode::Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CExpansionPolicy **pol) {
+void CButtonMode::Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CExpansionPolicy **pol) {
   bool m_bOldHighlight(m_bHighlight);
   m_bHighlight = (Time - m_iLastTime < 200);
 
   if(m_bOldHighlight != m_bHighlight)
     m_bDecorationChanged = true;
 
-  return CDasherButtons::Timer(Time, pView, pInput, pModel, pol);
+  CDasherButtons::Timer(Time, pView, pInput, pModel, pol);
 }
 
 void CButtonMode::KeyDown(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel) {
diff --git a/Src/DasherCore/ButtonMode.h b/Src/DasherCore/ButtonMode.h
index 454ac94..a6fb2ac 100644
--- a/Src/DasherCore/ButtonMode.h
+++ b/Src/DasherCore/ButtonMode.h
@@ -24,7 +24,7 @@ class CButtonMode : public CDasherButtons, protected CSettingsObserver
   CButtonMode(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, bool bMenu, int iID, const char *szName);
 
   virtual void HandleEvent(int iParameter);
-  bool Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CExpansionPolicy **pol);
+  void Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CExpansionPolicy **pol);
   bool DecorateView(CDasherView *pView, CDasherInput *pInput);
 
   //override to get mouse clicks/taps back again
diff --git a/Src/DasherCore/ButtonMultiPress.cpp b/Src/DasherCore/ButtonMultiPress.cpp
index 4ab679f..ec68f79 100644
--- a/Src/DasherCore/ButtonMultiPress.cpp
+++ b/Src/DasherCore/ButtonMultiPress.cpp
@@ -66,14 +66,12 @@ void CButtonMultiPress::pause()
   m_deQueueTimes.clear();
 }
 
-void CButtonMultiPress::reverse()
-{
-  CDynamicButtons::reverse();
+void CButtonMultiPress::reverse(unsigned long iTime) {
+  CDynamicButtons::reverse(iTime);
   m_deQueueTimes.clear();
 }
 
-void CButtonMultiPress::run()
-{
+void CButtonMultiPress::run(unsigned long iTime) {
   if (!isRunning()) m_deQueueTimes.clear();
-  CDynamicButtons::run();
+  CDynamicButtons::run(iTime);
 }
diff --git a/Src/DasherCore/ButtonMultiPress.h b/Src/DasherCore/ButtonMultiPress.h
index 40d59e6..f1fa348 100644
--- a/Src/DasherCore/ButtonMultiPress.h
+++ b/Src/DasherCore/ButtonMultiPress.h
@@ -37,11 +37,11 @@ class CButtonMultiPress : public CDynamicButtons {
 
   virtual void KeyDown(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel);
 
+  void pause();
  protected:
   virtual unsigned int maxClickCount()=0;
-  virtual void reverse();
-  virtual void pause();
-  virtual void run();
+  void reverse(unsigned long iTime);
+  void run(unsigned long iTime);
 
  private:
   virtual void RevertPresses(int iCount) {};
diff --git a/Src/DasherCore/CircleStartHandler.cpp b/Src/DasherCore/CircleStartHandler.cpp
index a6017da..b5edc5a 100644
--- a/Src/DasherCore/CircleStartHandler.cpp
+++ b/Src/DasherCore/CircleStartHandler.cpp
@@ -36,20 +36,17 @@ CCircleStartHandler::~CCircleStartHandler() {
   if (m_pView) m_pView->Observable<CDasherView*>::Unregister(this);
 }
 
-void CCircleStartHandler::ComputeScreenLoc(CDasherView *pView) {
-  if (pView != m_pView) {
-    if (m_pView) m_pView->Observable<CDasherView*>::Unregister(this);
-    (m_pView=pView)->Observable<CDasherView*>::Register(this);
-  } else if (m_iScreenRadius!=-1) return;
+CDasherScreen::point CCircleStartHandler::CircleCenter(CDasherView *pView) {
+  if (m_iScreenRadius!=-1) return m_screenCircleCenter;
 
-  pView->Dasher2Screen(CDasherModel::ORIGIN_X, CDasherModel::ORIGIN_Y, m_screenCircleCenter.x, m_screenCircleCenter.y);
+  m_pView->Dasher2Screen(CDasherModel::ORIGIN_X, CDasherModel::ORIGIN_Y, m_screenCircleCenter.x, m_screenCircleCenter.y);
   //compute radius against orientation. It'd be simpler to use
   // Math.min(screen width, screen height) * LP_CIRCLE_PERCENT / 100
   // - should we?
   screenint iEdgeX, iEdgeY;
-  pView->Dasher2Screen(CDasherModel::ORIGIN_X, CDasherModel::ORIGIN_Y + (CDasherModel::MAX_Y*GetLongParameter(LP_CIRCLE_PERCENT))/100, iEdgeX, iEdgeY);
+  m_pView->Dasher2Screen(CDasherModel::ORIGIN_X, CDasherModel::ORIGIN_Y + (CDasherModel::MAX_Y*GetLongParameter(LP_CIRCLE_PERCENT))/100, iEdgeX, iEdgeY);
 
-  const Opts::ScreenOrientations iDirection(pView->GetOrientation());
+  const Opts::ScreenOrientations iDirection(m_pView->GetOrientation());
 
   if((iDirection == Opts::TopToBottom) || (iDirection == Opts::BottomToTop)) {
     m_iScreenRadius = iEdgeX - m_screenCircleCenter.x;
@@ -57,14 +54,16 @@ void CCircleStartHandler::ComputeScreenLoc(CDasherView *pView) {
   else {
     m_iScreenRadius = iEdgeY - m_screenCircleCenter.y;
   }
+  return m_screenCircleCenter;
 }
 
 bool CCircleStartHandler::DecorateView(CDasherView *pView) {
-  ComputeScreenLoc(pView);
+  if (!m_pView) (m_pView=pView)->Observable<CDasherView*>::Register(this);
+  CDasherScreen::point ctr = CircleCenter(pView);
 
   const bool bAboutToChange = m_bInCircle && m_iEnterTime != std::numeric_limits<long>::max();
   int fillColor, lineColor, lineWidth;
-  if (GetBoolParameter(BP_DASHER_PAUSED)) {
+  if (m_pFilter->isPaused()) {
     lineColor=2; lineWidth=1;
     fillColor = bAboutToChange ? 241 : 242;
   } else {
@@ -72,16 +71,17 @@ bool CCircleStartHandler::DecorateView(CDasherView *pView) {
     lineWidth = bAboutToChange ? 3 : 1;
   }
 
-  pView->Screen()->DrawCircle(m_screenCircleCenter.x, m_screenCircleCenter.y, m_iScreenRadius, fillColor, lineColor, lineWidth);
+  pView->Screen()->DrawCircle(ctr.x, ctr.y, m_iScreenRadius, fillColor, lineColor, lineWidth);
 
   return true;
 }
 
 void CCircleStartHandler::Timer(unsigned long iTime, dasherint mouseX, dasherint mouseY,CDasherView *pView) {
-  ComputeScreenLoc(pView);
+  if (!m_pView) (m_pView=pView)->Observable<CDasherView*>::Register(this);
+  CDasherScreen::point ctr = CircleCenter(pView);
   screenint x,y;
   pView->Dasher2Screen(mouseX, mouseY, x, y);
-  x-=m_screenCircleCenter.x; y-=m_screenCircleCenter.y;
+  x-=ctr.x; y-=ctr.y;
   const bool inCircleNow = x*x + y*y <= (m_iScreenRadius * m_iScreenRadius);
 
   if (inCircleNow) {
@@ -89,11 +89,11 @@ void CCircleStartHandler::Timer(unsigned long iTime, dasherint mouseX, dasherint
       //still in circle...check they aren't still in there after prev. activation
       if (m_iEnterTime != std::numeric_limits<long>::max() && iTime - m_iEnterTime > 1000) {
         //activate!
-        if (GetBoolParameter(BP_DASHER_PAUSED))
-          m_pFilter->Unpause(iTime);
+        if (m_pFilter->isPaused())
+          m_pFilter->run(iTime);
         else
-          m_pFilter->m_pInterface->Stop();
-        //note our HandleEvent method will then set
+          m_pFilter->stop();
+        //note our onPause method will then set
         //   m_iEnterTime = std::numeric_limits<long>::max()
         // thus preventing us from firing until user leaves circle and enters again
       }
@@ -109,24 +109,29 @@ void CCircleStartHandler::Timer(unsigned long iTime, dasherint mouseX, dasherint
 }
 
 void CCircleStartHandler::HandleEvent(int iParameter) {
-  switch (iParameter) {
-    case LP_CIRCLE_PERCENT:
-      //recompute geometry.
-      m_iScreenRadius = -1;
-      break;
-    case BP_DASHER_PAUSED:
-      m_iEnterTime = std::numeric_limits<long>::max();
-      //In one-dimensional mode, we have that (un)pausing can _move_ the circle, thus,
-      // clicking (or using any other start mechanism) can cause the circle to appear
-      // around the mouse. If this happens, you should have to leave and re-enter
-      // the circle before the start handler does anything. The following ensures this.
-      m_bInCircle = true;
-      break;
-  }
+  if (iParameter==LP_CIRCLE_PERCENT)
+      m_iScreenRadius = -1; //recompute geometry.
+}
+
+void CCircleStartHandler::onPause() {
+    m_iEnterTime = std::numeric_limits<long>::max();
+    //In one-dimensional mode, we have that (un)pausing can _move_ the circle, thus,
+    // clicking (or using any other start mechanism) can cause the circle to appear
+    // around the mouse. If this happens, you should have to leave and re-enter
+    // the circle before the start handler does anything. The following ensures this.
+    m_bInCircle = true;
+}
+
+void CCircleStartHandler::onRun(unsigned long iTime) {
+  //reset things in exactly the same way as when we pause...
+  onPause();
 }
 
 void CCircleStartHandler::HandleEvent(CDasherView *pNewView) {
   //need to recompute geometry...
   m_iScreenRadius = -1; //even if it's the same view
-  ComputeScreenLoc(pNewView);
+  if (pNewView != m_pView) {
+    if (m_pView) m_pView->Observable<CDasherView*>::Unregister(this);
+    (m_pView=pNewView)->Observable<CDasherView*>::Register(this);
+  }
 }
diff --git a/Src/DasherCore/CircleStartHandler.h b/Src/DasherCore/CircleStartHandler.h
index 351471d..1cf6129 100644
--- a/Src/DasherCore/CircleStartHandler.h
+++ b/Src/DasherCore/CircleStartHandler.h
@@ -16,7 +16,8 @@ public:
   virtual void Timer(unsigned long iTime, dasherint iX, dasherint iY, CDasherView *pView);
   virtual void HandleEvent(int iParameter);
   virtual void HandleEvent(CDasherView *pView);
-
+  void onPause();
+  void onRun(unsigned long iTime);
 protected:
   ///Time (as unix timestamp) when user entered circle; max() => already acted upon
   long m_iEnterTime;
@@ -24,10 +25,11 @@ protected:
   bool m_bInCircle;
   ///Radius of circle in screen coordinates (-1 = needs recomputing)
   int m_iScreenRadius;
-  ///Center of screen circle (needs recomputing if radius does)
-  CDasherScreen::point m_screenCircleCenter;
   CDasherView *m_pView;
-  virtual void ComputeScreenLoc(CDasherView *pView);
+  virtual CDasherScreen::point CircleCenter(CDasherView *pView);
+private:
+  ///Cached center of screen circle (needs recomputing if radius does)
+  CDasherScreen::point m_screenCircleCenter;
 };
 }
 /// @}
diff --git a/Src/DasherCore/ClickFilter.cpp b/Src/DasherCore/ClickFilter.cpp
index 4c55cd6..e9aa0dd 100644
--- a/Src/DasherCore/ClickFilter.cpp
+++ b/Src/DasherCore/ClickFilter.cpp
@@ -78,10 +78,6 @@ void CZoomAdjuster::AdjustZoomCoords(myint &iDasherX, myint &iDasherY, CDasherVi
   }
 }
 
-bool CClickFilter::Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CExpansionPolicy **pol) {
-  return pModel->NextScheduledStep();
-}
-
 void CClickFilter::KeyDown(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel) {
   switch(iId) {
   case 100: // Mouse clicks
@@ -91,7 +87,7 @@ void CClickFilter::KeyDown(unsigned long iTime, int iId, CDasherView *pView, CDa
 
       pInput->GetDasherCoords(iDasherX, iDasherY, pView);
       AdjustZoomCoords(iDasherX, iDasherY, pView);
-      pModel->ScheduleZoom(iTime, iDasherY-iDasherX, iDasherY+iDasherX);
+      ScheduleZoom(pModel,iDasherY-iDasherX, iDasherY+iDasherX);
     }
     break;
   default:
diff --git a/Src/DasherCore/ClickFilter.h b/Src/DasherCore/ClickFilter.h
index 6cb9e0e..36231fd 100644
--- a/Src/DasherCore/ClickFilter.h
+++ b/Src/DasherCore/ClickFilter.h
@@ -17,13 +17,12 @@ namespace Dasher {
     void AdjustZoomCoords(myint &iDasherX, myint &iDasherY, CDasherView *comp);
   };
   
-class CClickFilter : public CInputFilter, private CZoomAdjuster {
+class CClickFilter : public CStaticFilter, private CZoomAdjuster {
  public:
   CClickFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface)
-    : CInputFilter(pInterface, 7, _("Click Mode")), CZoomAdjuster(pCreator) { };
+    : CStaticFilter(pInterface, 7, _("Click Mode")), CZoomAdjuster(pCreator) { };
 
   virtual bool DecorateView(CDasherView *pView, CDasherInput *pInput);
-  virtual bool Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *pDasherModel, CExpansionPolicy **pol);
   virtual void KeyDown(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel);
   virtual bool GetSettings(SModuleSettings **pSettings, int *iCount);
   
diff --git a/Src/DasherCore/ControlManager.cpp b/Src/DasherCore/ControlManager.cpp
index 5e8828b..9483bdb 100644
--- a/Src/DasherCore/ControlManager.cpp
+++ b/Src/DasherCore/ControlManager.cpp
@@ -251,7 +251,7 @@ CControlManager::CControlManager(CSettingsUser *pCreateFrom, CNodeCreationManage
   m_pPause = new Pause("Pause",241);
   m_pPause->successors.push_back(NULL);
   m_pPause->successors.push_back(GetRootTemplate());
-  m_pStop = new MethodTemplate<CDasherInterfaceBase>("Stop", 242, pInterface, &CDasherInterfaceBase::Stop);
+  m_pStop = new MethodTemplate<CDasherInterfaceBase>(_("Done"), 242, pInterface, &CDasherInterfaceBase::Done);
   m_pStop->successors.push_back(NULL);
   m_pStop->successors.push_back(GetRootTemplate());
 
@@ -268,7 +268,7 @@ CControlManager::CControlManager(CSettingsUser *pCreateFrom, CNodeCreationManage
 CControlBase::Pause::Pause(const string &strLabel, int iColour) : NodeTemplate(strLabel,iColour) {
 }
 void CControlBase::Pause::happen(CContNode *pNode) {
-  pNode->mgr()->SetBoolParameter(BP_DASHER_PAUSED,true);
+  pNode->mgr()->m_pInterface->GetActiveInputMethod()->pause();
 }
 
 CControlBase::NodeTemplate *CControlManager::parseOther(const XML_Char *name, const XML_Char **atts) {
@@ -389,14 +389,14 @@ void CControlManager::updateActions() {
   //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() && GetBoolParameter(BP_CONTROL_MODE_HAS_HALT))
+  if (m_pInterface->hasDone() && GetBoolParameter(BP_CONTROL_MODE_HAS_HALT))
     vRootSuccessors.push_back(m_pStop);
   if (it!=vOldRootSuccessors.end() && *it == m_pStop) 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(m_pInterface->GetActiveInputMethod());
-  if (pInput->supportsPause() && (m_pInterface->hasStopTriggers() || GetBoolParameter(BP_CONTROL_MODE_HAS_HALT)))
+  if (pInput->supportsPause() && (m_pInterface->hasDone() || GetBoolParameter(BP_CONTROL_MODE_HAS_HALT)))
     vRootSuccessors.push_back(m_pPause);
   if (it!=vOldRootSuccessors.end() && *it == m_pPause) it++;
 
diff --git a/Src/DasherCore/DashIntfScreenMsgs.cpp b/Src/DasherCore/DashIntfScreenMsgs.cpp
index a9d007c..7ef8b30 100644
--- a/Src/DasherCore/DashIntfScreenMsgs.cpp
+++ b/Src/DasherCore/DashIntfScreenMsgs.cpp
@@ -12,7 +12,7 @@ void CDashIntfScreenMsgs::Message(const string &strText, bool bInterrupt) {
   CDasherScreen::Label *lab = m_DasherScreen->MakeLabel(strText,GetLongParameter(LP_MESSAGE_FONTSIZE));
   if (bInterrupt) {
     m_dqModalMessages.push_back(pair<CDasherScreen::Label*,bool>(lab,false));
-    SetBoolParameter(BP_DASHER_PAUSED, true);
+    GetActiveInputMethod()->pause();
   }
   else
     m_dqAsyncMessages.push_back(pair<CDasherScreen::Label*,unsigned long>(lab, 0));
@@ -81,25 +81,21 @@ void CDashIntfScreenMsgs::ChangeScreen(CDasherScreen *pNewScreen) {
   }
 }
 
-void CDashIntfScreenMsgs::HandleEvent(int iParameter) {
-  CDashIntfSettings::HandleEvent(iParameter);
-  if (iParameter==BP_DASHER_PAUSED && !GetBoolParameter(BP_DASHER_PAUSED)) {
-    //just unpaused.
-    while (!m_dqModalMessages.empty()) {
-      if (m_dqModalMessages.front().second) {
-        //Message has been displayed; delete it
-        delete m_dqModalMessages.front().first; //the label
-        m_dqModalMessages.pop_front();
-      } else {
-        //there are more, not-yet displayed, modal messages!
-        //These should be after any that were displayed (which have now been erased), so:
-        // do not unpause; next frame will render more messages instead.
-        m_pDasherModel->ClearScheduledSteps();
-        SetBoolParameter(BP_DASHER_PAUSED,true);
-        return;
-      }
+void CDashIntfScreenMsgs::onUnpause(unsigned long lTime) {
+  while (!m_dqModalMessages.empty()) {
+    if (m_dqModalMessages.front().second) {
+      //Message has been displayed; delete it
+      delete m_dqModalMessages.front().first; //the label
+      m_dqModalMessages.pop_front();
+    } else {
+      //there are more, not-yet displayed, modal messages!
+      //These should be after any that were displayed (which have now been erased),
+      // so do not unpause; next frame will render more messages instead.
+      GetActiveInputMethod()->pause();
+      return;
     }
   }
+  CDasherInterfaceBase::onUnpause(lTime);
 }
 
 CGameModule *CDashIntfScreenMsgs::CreateGameModule(CDasherView *pView, CDasherModel *pModel) {
diff --git a/Src/DasherCore/DashIntfScreenMsgs.h b/Src/DasherCore/DashIntfScreenMsgs.h
index bd7d6d5..a589e25 100644
--- a/Src/DasherCore/DashIntfScreenMsgs.h
+++ b/Src/DasherCore/DashIntfScreenMsgs.h
@@ -55,9 +55,8 @@ public:
   ///Override to re-MakeLabel any messages.
   void ChangeScreen(CDasherScreen *pNewScreen);
   
-  ///Listen for BP_DASHER_PAUSED being cleared to flush any modal messages that
-  /// have been displayed before resuming.
-  void HandleEvent(int iParameter);
+  ///Flush any modal messages that have been displayed before resuming.
+  void onUnpause(unsigned long lTime);
   
   ///Implement to return a ScreenGameModule, i.e. rendering text prompts
   /// onto the Screen with Labels, much as we do for messages!
diff --git a/Src/DasherCore/DasherButtons.cpp b/Src/DasherCore/DasherButtons.cpp
index 135c28b..c04adb1 100644
--- a/Src/DasherCore/DasherButtons.cpp
+++ b/Src/DasherCore/DasherButtons.cpp
@@ -24,7 +24,7 @@ static char THIS_FILE[] = __FILE__;
 using namespace Dasher;
 
 CDasherButtons::CDasherButtons(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, bool bMenu, ModuleID_t iID, const char *szName)
-  : CSettingsUser(pCreator), CInputFilter(pInterface, iID, szName), m_bMenu(bMenu), m_bDecorationChanged(true), m_pBoxes(NULL), iActiveBox(0) {}
+  : CStaticFilter(pInterface, iID, szName), CSettingsUser(pCreator), m_bMenu(bMenu), m_bDecorationChanged(true), m_pBoxes(NULL), iActiveBox(0) {}
 
 CDasherButtons::~CDasherButtons()
 {
@@ -54,7 +54,7 @@ void CDasherButtons::KeyDown(unsigned long iTime, int iId, CDasherView *pView, C
     case 3:
     case 100:
       m_bDecorationChanged = true;
-      pModel->ScheduleZoom(iTime, m_pBoxes[iActiveBox].iTop, m_pBoxes[iActiveBox].iBottom);
+      ScheduleZoom(pModel, m_pBoxes[iActiveBox].iTop, m_pBoxes[iActiveBox].iBottom);
       if(iActiveBox != m_iNumBoxes-1)
         iActiveBox = 0;
       break;
@@ -76,10 +76,10 @@ void CDasherButtons::DirectKeyDown(unsigned long iTime, int iId, CDasherView *pV
   else
   iActiveBox = m_iNumBoxes-2;
 
-  pModel->ScheduleZoom(iTime, m_pBoxes[iActiveBox].iTop,m_pBoxes[iActiveBox].iBottom);
+  ScheduleZoom(pModel, m_pBoxes[iActiveBox].iTop,m_pBoxes[iActiveBox].iBottom);
 }
 
-bool CDasherButtons::Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CExpansionPolicy **pol) {
+void CDasherButtons::Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CExpansionPolicy **pol) {
   if (m_bMenu && GetLongParameter(LP_BUTTON_SCAN_TIME) &&
       Time > m_iScanTime) {
     m_iScanTime = Time + GetLongParameter(LP_BUTTON_SCAN_TIME);
@@ -94,8 +94,6 @@ bool CDasherButtons::Timer(unsigned long Time, CDasherView *pView, CDasherInput
 
   pInput->GetDasherCoords(iDasherX, iDasherY, pView);
   // ----
-
-  return pModel->NextScheduledStep();
 }
 
 void CDasherButtons::NewDrawGoTo(CDasherView *pView, myint iDasherMin, myint iDasherMax, bool bActive) {
diff --git a/Src/DasherCore/DasherButtons.h b/Src/DasherCore/DasherButtons.h
index 32db423..0adb5af 100644
--- a/Src/DasherCore/DasherButtons.h
+++ b/Src/DasherCore/DasherButtons.h
@@ -18,7 +18,7 @@ using namespace std;
 namespace Dasher {
 /// \ingroup Input
 /// @{
-class CDasherButtons : public CInputFilter, protected CSettingsUser
+class CDasherButtons : public CStaticFilter, protected CSettingsUser
 {
  public:
   CDasherButtons(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, bool bMenu, ModuleID_t iID, const char *szName);
@@ -28,7 +28,7 @@ class CDasherButtons : public CInputFilter, protected CSettingsUser
   virtual bool DecorateView(CDasherView *pView, CDasherInput *pInput)=0;
   
   void KeyDown(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel);
-  bool Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *m_pDasherModel, CExpansionPolicy **pol);
+  void Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *m_pDasherModel, CExpansionPolicy **pol);
   void Activate();
   
   struct SBoxInfo {
diff --git a/Src/DasherCore/DasherInterfaceBase.cpp b/Src/DasherCore/DasherInterfaceBase.cpp
index 6c92a03..e51e938 100644
--- a/Src/DasherCore/DasherInterfaceBase.cpp
+++ b/Src/DasherCore/DasherInterfaceBase.cpp
@@ -276,9 +276,6 @@ void CDasherInterfaceBase::HandleEvent(int iParameter) {
     CreateInputFilter();
     ScheduleRedraw();
     break;
-  case BP_DASHER_PAUSED:
-    ScheduleRedraw();
-    break;
   case LP_MARGIN_WIDTH:
   case BP_NONLINEAR_Y:
   case LP_NONLINEAR_X:
@@ -445,15 +442,12 @@ void CDasherInterfaceBase::TextAction::NotifyOffset(int iOffset) {
 }
 
 
-bool CDasherInterfaceBase::hasStopTriggers() {
+bool CDasherInterfaceBase::hasDone() {
   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);
-
+void CDasherInterfaceBase::Done() {
   ScheduleRedraw();
 
 #ifndef _WIN32_WCE
@@ -516,28 +510,32 @@ void CDasherInterfaceBase::NewFrame(unsigned long iTime, bool bForceRedraw) {
       m_DasherScreen->SendMarker(1); //decorations - don't draw any
       bBlit = true;
     } else {
-      bool bChanged(false), bWasPaused(GetBoolParameter(BP_DASHER_PAUSED));
       CExpansionPolicy *pol=m_defaultPolicy;
   
-      //1. Move around in the model
+      //1. Schedule any per-frame movement in the model...
       if(m_pInputFilter) {
-        bChanged = m_pInputFilter->Timer(iTime, m_pDasherView, m_pInput, m_pDasherModel, &pol);
+        m_pInputFilter->Timer(iTime, m_pDasherView, m_pInput, m_pDasherModel, &pol);
       }
       //2. Render...
-      //check: if we were paused before, and the input filter didn't unpause,
-      // then nothing can have changed:
-      DASHER_ASSERT(!bWasPaused || !GetBoolParameter(BP_DASHER_PAUSED) || !bChanged);
 
       //If we've been told to render another frame via ScheduleRedraw,
       // that's the same as passing in true to NewFrame.
       if (m_bRedrawScheduled) bForceRedraw=true;
       m_bRedrawScheduled=false;
 
-      //If we moved, definitely need to render the nodes. We also make sure
-      // to render at least one more frame - think that's a bit of policy
-      // just to be on the safe side, and may not be strictly necessary...
-      if (bChanged) bForceRedraw=m_bRedrawScheduled=true;
-
+      //Apply any movement that has been scheduled
+      if (m_pDasherModel->NextScheduledStep()) {
+        //yes, we moved...
+        if (!m_bLastMoved) onUnpause(iTime);
+        // ...so definitely need to render the nodes. We also make sure
+        // to render at least one more frame - think that's a bit of policy
+        // just to be on the safe side, and may not be strictly necessary...
+        bForceRedraw=m_bRedrawScheduled=m_bLastMoved=true;
+      } else {
+        //no movement
+        if (m_bLastMoved) bForceRedraw=true;//move into onPause() method if reqd
+        m_bLastMoved=false;
+      }
       //2. Render nodes decorations, messages
       bBlit = Redraw(iTime, bForceRedraw, *pol);
 
@@ -555,6 +553,14 @@ void CDasherInterfaceBase::NewFrame(unsigned long iTime, bool bForceRedraw) {
   bReentered=false;
 }
 
+void CDasherInterfaceBase::onUnpause(unsigned long lTime) {
+  //TODO When Game+UserLog modules are combined => reduce to just one call here
+  if (m_pGameModule)
+    m_pGameModule->StartWriting(lTime);
+  if (m_pUserLog)
+      m_pUserLog->StartWriting();
+}
+
 bool CDasherInterfaceBase::Redraw(unsigned long ulTime, bool bRedrawNodes, CExpansionPolicy &policy) {
   DASHER_ASSERT(m_pDasherView);
 
@@ -742,11 +748,9 @@ void CDasherInterfaceBase::KeyUp(unsigned long iTime, int iId) {
   }
 }
 
-void CDasherInterfaceBase::CreateInputFilter()
-{
-  SetBoolParameter(BP_DASHER_PAUSED,true); //seems a sensible precaution!
-
+void CDasherInterfaceBase::CreateInputFilter() {
   if(m_pInputFilter) {
+    m_pInputFilter->pause();
     m_pInputFilter->Deactivate();
     m_pInputFilter = NULL;
   }
diff --git a/Src/DasherCore/DasherInterfaceBase.h b/Src/DasherCore/DasherInterfaceBase.h
index e7b00ca..0a88681 100644
--- a/Src/DasherCore/DasherInterfaceBase.h
+++ b/Src/DasherCore/DasherInterfaceBase.h
@@ -198,16 +198,17 @@ public:
   /// Methods used to instruct dynamic motion of Dasher to start or stop
   /// @{
 
-  /// 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();
-
-  ///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();
+  /// Call when the user has finished writing a piece of text, to execute
+  /// any "on-stop" actions: the default implements speak on stop (if
+  /// BP_SPEAK_ON_STOP is set) and copy-on-stop (if BP_COPY_ALL_ON_STOP) is set;
+  /// subclasses may override to do more.
+  virtual void Done();
+
+  ///Whether the Done() method does anything (and so should be presented
+  /// to the user) - default deals with speak/copy-on-stop, and subclasses
+  /// which override Done() to add additional on-stop actions must/should
+  /// override this to match.
+  virtual bool hasDone();
   /// @}
 
   ///
@@ -410,6 +411,12 @@ protected:
 
   /// @}
 
+  ///Called (from NewFrame) if this frame moved and the previous didn't
+  /// (moved = was scheduled in the model, even if no actual change to
+  /// co-ordinates - the latter might occur if e.g. running default filter
+  /// but with the mouse precisely over the crosshair)
+  virtual void onUnpause(unsigned long lTime);
+  
   CDasherScreen *m_DasherScreen;
 
   CDasherModel * const m_pDasherModel;
@@ -513,11 +520,12 @@ protected:
   /// (so may still be NULL even if locked)
   CDasherScreen::Label *m_pLockLabel;
 
-  /// @name State variables
-  /// Represent the current overall state of the core
-  /// @{
+  ///Whether a full redraw (inc of nodes) has been requested externally,
+  /// via ScheduleRedraw, for the next frame
   bool m_bRedrawScheduled;
-  bool m_bOldVisible;
+  
+  ///Whether we moved anywhere in the last call to NewFrame.
+  bool m_bLastMoved;
 
   /// @}
 
diff --git a/Src/DasherCore/DasherModel.cpp b/Src/DasherCore/DasherModel.cpp
index 1127e8d..b3ed700 100644
--- a/Src/DasherCore/DasherModel.cpp
+++ b/Src/DasherCore/DasherModel.cpp
@@ -250,119 +250,12 @@ int CDasherModel::GetOffset() {
   return m_pLastOutput ? m_pLastOutput->offset()+1 : m_Root ? m_Root->offset()+1 : 0;
 };
 
-void CDasherModel::Get_new_root_coords(dasherint X, dasherint Y, dasherint &r1, dasherint &r2, int iSteps, dasherint iMinSize) {
-  DASHER_ASSERT(m_Root != NULL);
-  // Avoid X == 0, as this corresponds to infinite zoom
-  if (X <= 0) X = 1;
-
-  // If X is too large we risk overflow errors, so limit it
-  dasherint iMaxX = (1 << 29) / iSteps;
-  if (X > iMaxX) X = iMaxX;
-
-  // Mouse coords X, Y
-  // const dasherint Y1 = 0;
-  const dasherint Y2(MAX_Y);
-
-  // Calculate what the extremes of the viewport will be when the
-  // point under the cursor is at the cross-hair. This is where
-  // we want to be in iSteps updates
-
-  dasherint y1(Y - (Y2 * X) / (2 * ORIGIN_X));
-  dasherint y2(Y + (Y2 * X) / (2 * ORIGIN_Y));
-  dasherint oy1(y1),oy2(y2); //back these up to use later
-  // iSteps is the number of update steps we need to get the point
-  // under the cursor over to the cross hair. Calculated in order to
-  // keep a constant bit-rate.
-
-  DASHER_ASSERT(iSteps > 0);
-
-  // Calculate the new values of y1 and y2 required to perform a single update
-  // step.
-  {
-    const dasherint denom = Y2 + (iSteps - 1) * (y2 - y1),
-      newy1 = y1 * Y2 / denom,
-      newy2 = ((y2 * iSteps - y1 * (iSteps - 1)) * Y2) / denom;
-
-    y1 = newy1;
-    y2 = newy2;
-  }
-
-  // Calculate the minimum size of the viewport corresponding to the
-  // maximum zoom.
-
-  if((y2 - y1) < iMinSize) {
-    const dasherint newy1 = y1 * (Y2 - iMinSize) / (Y2 - (y2 - y1)),
-      newy2 = newy1 + iMinSize;
-
-    y1 = newy1;
-    y2 = newy2;
-  }
-  
-  //okay, we now have target bounds for the viewport, after allowing for framerate etc.
-  // we now go there in one step...
-  
-  // new root{min,max} r1,r2, old root{min,max} R1,R2
-  const dasherint R1 = m_Rootmin;
-  const dasherint R2 = m_Rootmax;  
-
-  // If |(0,Y2)| = |(y1,y2)|, the "zoom factor" is 1, so we just translate.
-  if (Y2 == y2 - y1)
-    {
-      r1 = R1 - y1;
-      r2 = R2 - y1;
-      return;
-    }
-
-  // There is a point C on the y-axis such the ratios (y1-C):(Y1-C) and
-  // (y2-C):(Y2-C) are equal - iow that divides the "target" region y1-y2
-  // into the same proportions as it divides the screen (0-Y2). I.e., this
-  // is the center of expansion - the point on the y-axis which everything
-  // moves away from (or towards, if reversing).
-
-  //We prefer to compute C from the _original_ (y1,y2) pair, as this is more
-  // accurate (and avoids drifting up/down when heading straight along the
-  // x-axis in dynamic button modes). However...
-  if (((y2-y1) < Y2) ^ ((oy2-oy1) < Y2)) {
-    //Sometimes (very occasionally), the calculation of a single-step above
-    // can turn a zoom-in into a zoom-out, or vice versa, when the movement
-    // is mostly translation. In which case, must compute C consistently with
-    // the (scaled, single-step) movement we are going to perform, or else we
-    // will end up suddenly going the wrong way along the y-axis (i.e., the
-    // sense of translation will be reversed) !
-    oy1=y1; oy2=y2;
-  }
-  const dasherint C = (oy1 * Y2) / (oy1 + Y2 - oy2);
-
-  r1 = ((R1 - C) * Y2) / (y2 - y1) + C;
-  r2 = ((R2 - C) * Y2) / (y2 - y1) + C;
-}
-
 bool CDasherModel::NextScheduledStep()
 {
-  DASHER_ASSERT (!GetBoolParameter(BP_DASHER_PAUSED) || m_deGotoQueue.size()==0);
   if (m_deGotoQueue.size() == 0) return false;
-  myint iNewMin, iNewMax;
-  iNewMin = m_deGotoQueue.front().iN1;
-  iNewMax = m_deGotoQueue.front().iN2;
+  myint newRootmin(m_deGotoQueue.front().iN1), newRootmax(m_deGotoQueue.front().iN2);
   m_deGotoQueue.pop_front();
 
-  UpdateBounds(iNewMin, iNewMax);
-  if (m_deGotoQueue.size() == 0) SetBoolParameter(BP_DASHER_PAUSED, true);
-  return true;
-}
-
-void CDasherModel::OneStepTowards(myint miMousex, myint miMousey, int iSteps, dasherint iMinSize) {
-  DASHER_ASSERT(!GetBoolParameter(BP_DASHER_PAUSED));
-
-  myint iNewMin, iNewMax;
-  // works out next viewpoint
-  Get_new_root_coords(miMousex, miMousey, iNewMin, iNewMax, iSteps, iMinSize);
-  
-  UpdateBounds(iNewMin, iNewMax);
-}
-
-void CDasherModel::UpdateBounds(myint newRootmin, myint newRootmax) {
-
   m_dTotalNats += log((newRootmax - newRootmin) / static_cast<double>(m_Rootmax - m_Rootmin));
 
   m_iDisplayOffset = (m_iDisplayOffset * 90) / 100;
@@ -383,7 +276,7 @@ void CDasherModel::UpdateBounds(myint newRootmin, myint newRootmax) {
         if (GetBoolParameter(BP_GAME_MODE) && !pChild->GetFlag(NF_GAME)) {
           //If the user's strayed that far off the game path,
           // having Dasher stop seems reasonable!
-          return;
+          return false;
         }
 
         //make pChild the root node...
@@ -424,8 +317,103 @@ void CDasherModel::UpdateBounds(myint newRootmin, myint newRootmax) {
   if ((newRootmax - newRootmin) > MAX_Y / 4) {
     m_Rootmax = newRootmax;
     m_Rootmin = newRootmin;
+    return true;
   } //else, we just stop - this prevents the user from zooming too far back
-  //outside the root node (when we can't generate an older root).
+    //outside the root node (when we can't generate an older root).  return true;
+  return false;
+}
+
+void CDasherModel::ScheduleOneStep(myint X, myint Y, int iSteps, dasherint iMinSize) {
+  myint r1, r2;
+  // works out next viewpoint
+
+  DASHER_ASSERT(m_Root != NULL);
+  // Avoid X == 0, as this corresponds to infinite zoom
+  if (X <= 0) X = 1;
+  
+  // If X is too large we risk overflow errors, so limit it
+  dasherint iMaxX = (1 << 29) / iSteps;
+  if (X > iMaxX) X = iMaxX;
+  
+  // Mouse coords X, Y
+  // const dasherint Y1 = 0;
+  const dasherint Y2(MAX_Y);
+  
+  // Calculate what the extremes of the viewport will be when the
+  // point under the cursor is at the cross-hair. This is where
+  // we want to be in iSteps updates
+  
+  dasherint y1(Y - (Y2 * X) / (2 * ORIGIN_X));
+  dasherint y2(Y + (Y2 * X) / (2 * ORIGIN_Y));
+  dasherint oy1(y1),oy2(y2); //back these up to use later
+  
+  // iSteps is the number of update steps we need to get the point
+  // under the cursor over to the cross hair. Calculated in order to
+  // keep a constant bit-rate.
+  DASHER_ASSERT(iSteps > 0);
+  
+  // Calculate the new values of y1 and y2 required to perform a single update
+  // step.
+  {
+    const dasherint denom = Y2 + (iSteps - 1) * (y2 - y1),
+    newy1 = y1 * Y2 / denom,
+    newy2 = ((y2 * iSteps - y1 * (iSteps - 1)) * Y2) / denom;
+    
+    y1 = newy1;
+    y2 = newy2;
+  }
+  
+  // Calculate the minimum size of the viewport corresponding to the
+  // maximum zoom.
+  
+  if((y2 - y1) < iMinSize) {
+    const dasherint newy1 = y1 * (Y2 - iMinSize) / (Y2 - (y2 - y1)),
+    newy2 = newy1 + iMinSize;
+    
+    y1 = newy1;
+    y2 = newy2;
+  }
+  
+  //okay, we now have target bounds for the viewport, after allowing for framerate etc.
+  // we now go there in one step...
+  
+  // new root{min,max} r1,r2, old root{min,max} R1,R2
+  const dasherint R1 = m_Rootmin;
+  const dasherint R2 = m_Rootmax;  
+  
+  // If |(0,Y2)| = |(y1,y2)|, the "zoom factor" is 1, so we just translate.
+  if (Y2 == y2 - y1) {
+    r1 = R1 - y1;
+    r2 = R2 - y1;
+  } else {
+    // There is a point C on the y-axis such the ratios (y1-C):(Y1-C) and
+    // (y2-C):(Y2-C) are equal - iow that divides the "target" region y1-y2
+    // into the same proportions as it divides the screen (0-Y2). I.e., this
+    // is the center of expansion - the point on the y-axis which everything
+    // moves away from (or towards, if reversing).
+    
+    //We prefer to compute C from the _original_ (y1,y2) pair, as this is more
+    // accurate (and avoids drifting up/down when heading straight along the
+    // x-axis in dynamic button modes). However...
+    if (((y2-y1) < Y2) ^ ((oy2-oy1) < Y2)) {
+      //Sometimes (very occasionally), the calculation of a single-step above
+      // can turn a zoom-in into a zoom-out, or vice versa, when the movement
+      // is mostly translation. In which case, must compute C consistently with
+      // the (scaled, single-step) movement we are going to perform, or else we
+      // will end up suddenly going the wrong way along the y-axis (i.e., the
+      // sense of translation will be reversed) !
+      oy1=y1; oy2=y2;
+    }
+    const dasherint C = (oy1 * Y2) / (oy1 + Y2 - oy2);
+    
+    r1 = ((R1 - C) * Y2) / (y2 - y1) + C;
+    r2 = ((R2 - C) * Y2) / (y2 - y1) + C;
+  }
+  m_deGotoQueue.clear();
+  SGotoItem item;
+  item.iN1 = r1;
+  item.iN2 = r2;
+  m_deGotoQueue.push_back(item);
 }
 
 void CDasherModel::OutputTo(CDasherNode *pNewNode) {
@@ -519,7 +507,7 @@ void CDasherModel::RenderToView(CDasherView *pView, CExpansionPolicy &policy) {
 
 }
 
-void CDasherModel::ScheduleZoom(long time, dasherint y1, dasherint y2) {
+void CDasherModel::ScheduleZoom(dasherint y1, dasherint y2) {
   DASHER_ASSERT(y2>y1);
 
   // Rename for readability.
@@ -563,8 +551,6 @@ void CDasherModel::ScheduleZoom(long time, dasherint y1, dasherint y2) {
       sNewItem.iN2 = r2 - (s * (r2 - R2)) / nsteps;
       m_deGotoQueue.push_back(sNewItem);
   }
-  //steps having been scheduled, we're gonna start moving accordingly...
-  SetBoolParameter(BP_DASHER_PAUSED, false);
 }
 
 void CDasherModel::ClearScheduledSteps() {
diff --git a/Src/DasherCore/DasherModel.h b/Src/DasherCore/DasherModel.h
index 529bab3..ada4a03 100644
--- a/Src/DasherCore/DasherModel.h
+++ b/Src/DasherCore/DasherModel.h
@@ -80,9 +80,19 @@ class Dasher::CDasherModel: private CSettingsUser, public Observable<CDasherNode
   /// @{
 
   ///
-  /// Update the root location with *one step* towards the specified
-  /// co-ordinates - used by timer callbacks (for non-button modes)
-  void OneStepTowards(myint, myint, int iSteps, dasherint iMinSize);
+  /// Schedules *one step*  of movement towards the specified
+  /// co-ordinates - used by timer callbacks for non-button modes.
+  /// Interpolates movement according to iSteps and iMinSize, and calculates
+  /// new co-ordinates for the root node (after *one step*) into m_deGotoQueue
+  /// just as ScheduleZoom. For further information, see Doc/geometry.tex.
+  ///
+  /// \param mousex dasherx co-ordinate towards which to move (e.g. mouse pos)
+  /// \param mousey dashery co-ordinate towards which to move (e.g. mouse pos)
+  /// \param iSteps number of frames which should get us all the way to (mousex,mousey)
+  /// \param iMinSize limit on rate of expansion due to bitrate (as moving
+  /// all the way to the mouse at mousex==1 would be an absurd rate of data entry,
+  /// becoming infinite at mousex==0).
+  void ScheduleOneStep(myint mousex, myint mousey, int iSteps, dasherint iMinSize);
 
   ///
   /// Apply an offset to the 'target' coordinates - implements the jumps in
@@ -136,19 +146,23 @@ class Dasher::CDasherModel: private CSettingsUser, public Observable<CDasherNode
   ///
   /// Schedule a zoom such that the given range of Dasher coordinates
   /// will fill the Y-axis. (used in click mode, button mode etc.)
-  /// Note that safety margin, max-zoom, etc., as desired, are the responsibility
-  /// of the caller; this method requires only that y2 > y1.
+  /// Note that this will take LP_ZOOM_STEPS frames to complete; safety margin,
+  /// max-zoom, etc., as desired, are the responsibility of the caller
+  /// (this method requires only that y2 > y1).
   /// \param y1 Minimum Y-coordinate (will be moved to dasher-y of 0)
   /// \param y2 Maximum Y-coordinate (will be moved to dasher-y of 4096)
   ///
-  void ScheduleZoom(long time, dasherint y1, dasherint y2);
+  void ScheduleZoom(dasherint y1, dasherint y2);
 
+  ///Cancel any steps previously scheduled (most likely by ScheduleZoom)
   void ClearScheduledSteps();
 
   ///
-  /// Update the bounds of the root node for the next step in any
-  /// still-in-progress zoom scheduled by ScheduleZoom (does nothing
-  /// if no steps remaining / no zoom scheduled).
+  /// Called by DasherInterfaceBase to update the bounds of the root node for
+  /// the next step that has been scheduled (whether a multi-step zoom or a
+  /// single step from ScheduleOneStep).
+  /// \return True if this moves the model (by applying a previously-scheduled
+  /// step); false if there were no scheduled steps (=> the model hasn't moved).
   ///
   bool NextScheduledStep();
 
@@ -186,10 +200,6 @@ class Dasher::CDasherModel: private CSettingsUser, public Observable<CDasherNode
 
  private:
 
-  /// Common portion of OneStepTowards / NextScheduledStep, taking
-  /// bounds for the root node in the next frame.
-  void UpdateBounds(myint iNewMin, myint iNewMax);
-
   /// Struct representing intermediate stages in the goto queue
   ///
   struct SGotoItem {
@@ -242,18 +252,6 @@ class Dasher::CDasherModel: private CSettingsUser, public Observable<CDasherNode
   // Information entered so far in this model
   double m_dTotalNats;
 
-  /// Calculate the new co-ordinates for the root node after a single
-  /// update step. For further information, see Doc/geometry.tex.
-  ///
-  /// \param mousex x mouse co-ordinate measured right to left.
-  /// \param mousey y mouse co-ordinate measured top to bottom.
-  /// \param iNewMin New root min
-  /// \param iNewMax New root max
-  /// \param iSteps Number of frames which should get us all the way to (mousex,mousey)
-  /// \param iMinSize limit on rate of expansion due to bitrate (as we use a poor
-  /// approximation to the true interpolation!)
-  void Get_new_root_coords(myint mousex, myint mousey, myint &iNewMin, myint &iNewMax, int iSteps, dasherint iMinSize);
-
   ///
   /// Make a child of the root into a new root
   ///
diff --git a/Src/DasherCore/DefaultFilter.cpp b/Src/DasherCore/DefaultFilter.cpp
index 8707e45..3ba3ab0 100644
--- a/Src/DasherCore/DefaultFilter.cpp
+++ b/Src/DasherCore/DefaultFilter.cpp
@@ -106,15 +106,14 @@ bool CDefaultFilter::DecorateView(CDasherView *pView, CDasherInput *pInput) {
   return bDidSomething;
 }
 
-bool CDefaultFilter::Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) {
+void CDefaultFilter::Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) {
   if (!(m_bGotMouseCoords = pInput->GetDasherCoords(m_iLastX, m_iLastY, pView))) {
-    m_pInterface->Stop(); //does nothing if already paused
-    return false;
+    stop();
+    return;
   };
   //Got coordinates
   ApplyTransform(m_iLastX, m_iLastY, pView);
-  bool bDidSomething(false);
-  if (!GetBoolParameter(BP_DASHER_PAUSED))
+  if (!isPaused())
   {
     if(GetBoolParameter(BP_STOP_OUTSIDE)) {
       myint iDasherMinX;
@@ -124,8 +123,8 @@ bool CDefaultFilter::Timer(unsigned long Time, CDasherView *pView, CDasherInput
       pView->VisibleRegion(iDasherMinX, iDasherMinY, iDasherMaxX, iDasherMaxY);
 
       if((m_iLastX > iDasherMaxX) || (m_iLastX < iDasherMinX) || (m_iLastY > iDasherMaxY) || (m_iLastY < iDasherMinY)) {
-        m_pInterface->Stop();
-        return false;
+        stop();
+        return;
       }
     }
 
@@ -133,7 +132,6 @@ bool CDefaultFilter::Timer(unsigned long Time, CDasherView *pView, CDasherInput
     if (m_bTurbo) dSpeedMul*=1.75;
     
     OneStepTowards(m_pDasherModel, m_iLastX,m_iLastY, Time, dSpeedMul);
-    bDidSomething = true;
 
     if (GetLongParameter(LP_BOOSTFACTOR)==100 && dSpeedMul==1.0)
       m_pAutoSpeedControl->SpeedControl(m_iLastX, m_iLastY, pView);
@@ -141,45 +139,45 @@ bool CDefaultFilter::Timer(unsigned long Time, CDasherView *pView, CDasherInput
 
   if(m_pStartHandler)
     m_pStartHandler->Timer(Time, m_iLastX, m_iLastY, pView);
+}
 
-  return bDidSomething;
+void CDefaultFilter::run(unsigned long iTime) {
+  CDynamicFilter::run(iTime);
+  if (m_pStartHandler) m_pStartHandler->onRun(iTime);
+}
+
+void CDefaultFilter::pause() {
+  CDynamicFilter::pause();
+  if (m_pStartHandler) m_pStartHandler->onPause();
 }
 
 void CDefaultFilter::KeyDown(unsigned long iTime, int iId, CDasherView *pDasherView, CDasherInput *pInput, CDasherModel *pModel) {
 
-  switch(iId) {
-  case 0: // Start on space
-    // FIXME - wrap this in a 'start/stop' method (and use for buttons as well as keys)
-    if(GetBoolParameter(BP_START_SPACE)) {
-      if(GetBoolParameter(BP_DASHER_PAUSED))
-        Unpause(iTime);
-      else
-	m_pInterface->Stop();
-    }
-    break;
-  case 100: // Start on mouse
-    if(GetBoolParameter(BP_START_MOUSE)) {
-      if(GetBoolParameter(BP_DASHER_PAUSED))
-        Unpause(iTime);
-      else
-	m_pInterface->Stop();
-    }
-    break;
-    case 101: case 102: //Other mouse buttons, if platforms support?
-    case 1: //button 1
-      if (GetBoolParameter(BP_TURBO_MODE)) {
-        m_bTurbo = true;
-      }
-  default:
-    break;
+  if ((iId==0 && GetBoolParameter(BP_START_SPACE))
+      || (iId==100 && GetBoolParameter(BP_START_MOUSE))) {
+    if(isPaused())
+      run(iTime);
+    else
+      stop();
+  }
+  else if (iId==101 || iId==102 || iId==1) {
+    //Other mouse buttons, if platforms support; or button 1
+    if (GetBoolParameter(BP_TURBO_MODE))
+      m_bTurbo = true;
   }
 }
 
 void CDefaultFilter::KeyUp(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel) {
-  if (iId==101 || iId==1)
+  if (iId==101 || iId==102 || iId==1)
     m_bTurbo=false;
 }
 
+void CDefaultFilter::stop() {
+  if (isPaused()) return;
+  pause();
+  m_pInterface->Done();
+}
+
 void CDefaultFilter::HandleEvent(int iParameter) {
   switch (iParameter) {
   case BP_CIRCLE_START:
@@ -264,7 +262,7 @@ void CDefaultFilter::ApplyOffset(myint &iDasherX, myint &iDasherY) {
 
   iDasherY += 10 * GetLongParameter(LP_TARGET_OFFSET);
 
-  if(GetBoolParameter(BP_AUTOCALIBRATE) && !GetBoolParameter(BP_DASHER_PAUSED)) {
+  if(GetBoolParameter(BP_AUTOCALIBRATE) && !isPaused()) {
     // Auto-update the offset
 
     m_iSum += CDasherModel::ORIGIN_Y - iDasherY; // Distance above crosshair
diff --git a/Src/DasherCore/DefaultFilter.h b/Src/DasherCore/DefaultFilter.h
index f42c90e..6eb7f75 100644
--- a/Src/DasherCore/DefaultFilter.h
+++ b/Src/DasherCore/DefaultFilter.h
@@ -17,14 +17,18 @@ class CDefaultFilter : public CDynamicFilter, public CSettingsObserver {
   virtual void HandleEvent(int iParameter);
 
   virtual bool DecorateView(CDasherView *pView, CDasherInput *pInput);
-  virtual bool Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CExpansionPolicy **pol);
+  virtual void Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CExpansionPolicy **pol);
   virtual void KeyDown(unsigned long iTime, int iId, CDasherView *pDasherView, CDasherInput *pInput, CDasherModel *pModel);
   virtual void KeyUp(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel);
   virtual void Activate();
   virtual void Deactivate();
   bool GetSettings(SModuleSettings **, int *);
+  void pause();
+  //pauses, and calls the interface's Done() method
+  void stop();
  protected:
   void CreateStartHandler();
+  void run(unsigned long iTime);
   virtual CStartHandler *MakeStartHandler();
   virtual void ApplyTransform(myint &iDasherX, myint &iDasherY, CDasherView *pView);
   void ApplyOffset(myint &iDasherX, myint &iDasherY);
diff --git a/Src/DasherCore/DynamicButtons.cpp b/Src/DasherCore/DynamicButtons.cpp
index f241b1c..a337021 100644
--- a/Src/DasherCore/DynamicButtons.cpp
+++ b/Src/DasherCore/DynamicButtons.cpp
@@ -24,32 +24,31 @@
 using namespace Dasher;
 
 CDynamicButtons::CDynamicButtons(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID, const char *szName)
-  : CDynamicFilter(pCreator, pInterface, pFramerate, iID, szName), CSettingsObserver(pCreator), m_pModel(NULL) {
+  : CDynamicFilter(pCreator, pInterface, pFramerate, iID, szName), m_pModel(NULL) {
   m_bDecorationChanged = true;
   m_bKeyDown = false;
   pause();
 }
 
-bool CDynamicButtons::Timer(unsigned long iTime, CDasherView *pDasherView, CDasherInput *pInput, CDasherModel *m_pDasherModel, CExpansionPolicy **pol)
+void CDynamicButtons::Timer(unsigned long iTime, CDasherView *pDasherView, CDasherInput *pInput, CDasherModel *m_pDasherModel, CExpansionPolicy **pol)
 {
   if(m_bKeyDown && !m_bKeyHandled && ((iTime - m_iKeyDownTime) > GetLongParameter(LP_HOLD_TIME))) {
     ButtonEvent(iTime, m_iHeldId, 1, m_pDasherModel);
     m_bKeyHandled = true;
     //return true; //ACL although that's what old DynamicButtons did, surely we should progress normally?
   }
-  if (isPaused()) return false;
+  if (isPaused()) return;
   if (isReversing()) {
     OneStepTowards(m_pDasherModel, 41943,2048, iTime, SlowStartSpeedMul(iTime));
-    return true;
-  }
-  //moving forwards. Check auto speed control...
-  if (GetBoolParameter(BP_AUTO_SPEEDCONTROL) && m_uSpeedControlTime < iTime)
-  {
-	  if (m_uSpeedControlTime > 0) //has actually been set?
-        SetLongParameter(LP_MAX_BITRATE, GetLongParameter(LP_MAX_BITRATE) * (1.0 + GetLongParameter(LP_DYNAMIC_SPEED_INC)/100.0));
-	  m_uSpeedControlTime = iTime + 1000*GetLongParameter(LP_DYNAMIC_SPEED_FREQ);
+  } else {
+    //moving forwards. Check auto speed control...
+    if (GetBoolParameter(BP_AUTO_SPEEDCONTROL) && m_uSpeedControlTime < iTime) {
+        if (m_uSpeedControlTime > 0) //has actually been set?
+          SetLongParameter(LP_MAX_BITRATE, GetLongParameter(LP_MAX_BITRATE) * (1.0 + GetLongParameter(LP_DYNAMIC_SPEED_INC)/100.0));
+        m_uSpeedControlTime = iTime + 1000*GetLongParameter(LP_DYNAMIC_SPEED_FREQ);
+    }
+    TimerImpl(iTime, pDasherView, m_pDasherModel, pol);
   }
-  return TimerImpl(iTime, pDasherView, m_pDasherModel, pol);
 }
 
 void CDynamicButtons::KeyDown(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel) {
@@ -87,15 +86,13 @@ void CDynamicButtons::ButtonEvent(unsigned long iTime, int iButton, int iType, C
     //Any button causes a restart
     if(CUserLogBase *pUserLog=m_pInterface->GetUserLogPtr())
       pUserLog->KeyDown(iButton, iType, 1);
-    run();
-    Unpause(iTime);
+    run(iTime);
   } else if (isReversing()) {
     //Any button pauses
     if(CUserLogBase *pUserLog=m_pInterface->GetUserLogPtr())
       pUserLog->KeyDown(iButton, iType, 2);
-    
-    m_pInterface->Stop();
-    //change in BP_DASHER_PAUSED calls pause().
+    m_pInterface->Done();
+    pause();
   } else {
     //running; examine event/button-press type
     switch(iType) {
@@ -104,14 +101,15 @@ void CDynamicButtons::ButtonEvent(unsigned long iTime, int iButton, int iType, C
         //dedicated pause button
         if(CUserLogBase *pUserLog=m_pInterface->GetUserLogPtr())
           pUserLog->KeyDown(iButton, iType, 2);
-        m_pInterface->Stop();
+        m_pInterface->Done();
+        pause();
         break;
       }
       else if(iButton == 1) {
         //dedicated reverse button
         if(CUserLogBase *pUserLog=m_pInterface->GetUserLogPtr())
           pUserLog->KeyDown(iButton, iType, 6);
-        reverse();
+        reverse(iTime);
         break;
       }
       //else - any non-special button - fall through
@@ -121,24 +119,14 @@ void CDynamicButtons::ButtonEvent(unsigned long iTime, int iButton, int iType, C
   }
 }
 
-void CDynamicButtons::HandleEvent(int iParameter) {
-  if (iParameter==BP_DASHER_PAUSED) {
-    if (GetBoolParameter(BP_DASHER_PAUSED))
-      pause(); //make sure we're in sync
-    else if (m_pInterface->GetActiveInputMethod()==this && isPaused())
-      //if we're active: can't unpause, as we don't know which way to go, run or reverse?
-      SetBoolParameter(BP_DASHER_PAUSED, true);
-  }
-}
-
 void CDynamicButtons::pause() {
-  m_iState = 0;
+  CDynamicFilter::pause();
   if (m_pModel) m_pModel->AbortOffset();
 }
 
-void CDynamicButtons::reverse()
-{
-  m_iState = 1;
+void CDynamicButtons::reverse(unsigned long iTime) {
+  m_bForwards=false;
+  CDynamicFilter::run(iTime);
   if (GetBoolParameter(BP_AUTO_SPEEDCONTROL)) {
     //treat reversing as a sign of distress --> slow down!
     SetLongParameter(LP_MAX_BITRATE, GetLongParameter(LP_MAX_BITRATE) *
@@ -146,11 +134,12 @@ void CDynamicButtons::reverse()
   }
 }
 
-void CDynamicButtons::run()
-{
-  if (m_iState<2) //wasn't running previously
-    m_uSpeedControlTime = 0; //will be set in Timer()
-  m_iState = 2;
+void CDynamicButtons::run(unsigned long iTime) {
+  m_bForwards=true;
+  if (!isPaused()) return;
+  //wasn't running previously
+  CDynamicFilter::run(iTime);
+  m_uSpeedControlTime = 0; //will be set in Timer()
 }
 
 void CDynamicButtons::ApplyOffset(CDasherModel *pModel, int iOffset) {
diff --git a/Src/DasherCore/DynamicButtons.h b/Src/DasherCore/DynamicButtons.h
index 3b76a1e..f308324 100644
--- a/Src/DasherCore/DynamicButtons.h
+++ b/Src/DasherCore/DynamicButtons.h
@@ -27,18 +27,16 @@
 /// @{
 namespace Dasher {
 ///filter with three states: paused, reversing, running. Hold any button down to reverse.
-class CDynamicButtons : public CDynamicFilter, public CSettingsObserver {
+class CDynamicButtons : public CDynamicFilter {
  public:
   CDynamicButtons(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID, const char *szName);
 
   ///when reversing, backs off; when paused, does nothing; when running, delegates to TimerImpl
-  virtual bool Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *m_pDasherModel, CExpansionPolicy **pol);
+  virtual void Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *m_pDasherModel, CExpansionPolicy **pol);
 
   virtual void KeyDown(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel);
   virtual void KeyUp(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel);
 
-  //respond to changes to BP_DASHER_PAUSED to keep m_iState in sync
-  virtual void HandleEvent(int iParameter);
  protected:
   ///Called when a key event is detected - could be a single press (a la KeyDown/KeyUp),
   /// but is also called with explicit indication of "long" or other types of press,
@@ -58,20 +56,19 @@ class CDynamicButtons : public CDynamicFilter, public CSettingsObserver {
   bool m_bKeyDown;
   bool m_bKeyHandled;
   bool m_bDecorationChanged;
-  bool isPaused() {return m_iState == 0;}
-  bool isReversing() {return m_iState == 1;}
-  bool isRunning() {return m_iState==2;}
+  bool isReversing() {return !isPaused() && !m_bForwards;}
+  bool isRunning() {return !isPaused() && m_bForwards;}
   virtual void pause();
-  virtual void reverse();
-  virtual void run();
+  virtual void reverse(unsigned long iTime);
+  virtual void run(unsigned long iTime);
 
-  virtual bool TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) = 0;
+  virtual void TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) = 0;
 
   ///Subclasses should all this (rather than pModel->Offset()) to offset the model
   /// (it also stores the model, to abort the offset upon pause if necessary)
   void ApplyOffset(CDasherModel *pModel, int iOffset);
   private:
-    int m_iState; // 0 = paused, 1 = reversing, >=2 = running (extensible by subclasses)
+    bool m_bForwards;
     int m_iHeldId;
     unsigned long m_iKeyDownTime;
     unsigned long m_uSpeedControlTime;
diff --git a/Src/DasherCore/DynamicFilter.cpp b/Src/DasherCore/DynamicFilter.cpp
index 026f17c..aa73f10 100644
--- a/Src/DasherCore/DynamicFilter.cpp
+++ b/Src/DasherCore/DynamicFilter.cpp
@@ -23,7 +23,8 @@
 
 using namespace Dasher;
 
-CDynamicFilter::CDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID, const char *szName) : CInputFilter(pInterface, iID, szName), CSettingsUser(pCreator), m_pFramerate(pFramerate) {
+CDynamicFilter::CDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID, const char *szName)
+: CInputFilter(pInterface, iID, szName), CSettingsUser(pCreator), m_bPaused(true), m_pFramerate(pFramerate) {
 }
 
 bool CDynamicFilter::OneStepTowards(CDasherModel *pModel, myint y1, myint y2, unsigned long iTime, double dSpeedMul) {
@@ -34,7 +35,7 @@ bool CDynamicFilter::OneStepTowards(CDasherModel *pModel, myint y1, myint y2, un
   // Adjust for slow start etc. TODO: can we fix to use integer math (or at least no pow?)
   if (dSpeedMul!=1.0) dRXMax=pow(dRXMax, dSpeedMul);
   
-  pModel->OneStepTowards(y1, y2, static_cast<int>(m_pFramerate->Steps() / dSpeedMul), static_cast<myint>(CDasherModel::MAX_Y/dRXMax));
+  pModel->ScheduleOneStep(y1, y2, static_cast<int>(m_pFramerate->Steps() / dSpeedMul), static_cast<myint>(CDasherModel::MAX_Y/dRXMax));
   return true;
 }
 
@@ -47,10 +48,10 @@ double CDynamicFilter::SlowStartSpeedMul(unsigned long iTime) {
   return 1.0;
 }
 
-void CDynamicFilter::Unpause(unsigned long Time) {
-  if (!GetBoolParameter(BP_DASHER_PAUSED)) return; //already running, no need to / can't really do anything
+void CDynamicFilter::run(unsigned long Time) {
+  if (!isPaused()) return; //already running, no need to / can't really do anything
   
-  SetBoolParameter(BP_DASHER_PAUSED, false);
+  m_bPaused = false;
 
   m_pFramerate->Reset_framerate(Time);
   m_iStartTime = Time;
diff --git a/Src/DasherCore/DynamicFilter.h b/Src/DasherCore/DynamicFilter.h
index 33791e9..a5c2119 100644
--- a/Src/DasherCore/DynamicFilter.h
+++ b/Src/DasherCore/DynamicFilter.h
@@ -38,20 +38,26 @@ class CDynamicFilter : public CInputFilter, public CSettingsUser {
   CDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID, const char *szName);
 
   virtual bool supportsPause() {return true;}
-
+  
+  void pause() {m_bPaused = true;}
+  
  protected:
   bool OneStepTowards(CDasherModel *pModel, myint y1, myint y2, unsigned long iTime, double dSpeedMul);
   double SlowStartSpeedMul(unsigned long iTime);
 
-  /// Starts moving.  Clears BP_DASHER_PAUSED.
-  /// (But does nothing if BP_DASHER_PAUSED is currently set).
+  ///Starts moving, i.e. just records that we are no longer paused,
+  /// for the next call to Timer. Protected, to prevent external calls 
+  /// so subclasses have control over all calls, as a call to run() may not be
+  /// appropriate without performing other setup first.
   /// \param Time Time in ms, used to keep a constant frame rate and
   /// initialize slow start.
-  void Unpause(unsigned long iTime);
+  virtual void run(unsigned long iTime);
+  bool isPaused() {return m_bPaused;}
  private:
   CFrameRate *m_pFramerate;
   //Time at which Unpause() was called, used for Slow Start.
   unsigned long m_iStartTime;
+  bool m_bPaused;
 };
 }
 #endif
diff --git a/Src/DasherCore/GameModule.cpp b/Src/DasherCore/GameModule.cpp
index 053ff12..7c8071a 100644
--- a/Src/DasherCore/GameModule.cpp
+++ b/Src/DasherCore/GameModule.cpp
@@ -93,13 +93,18 @@ void CGameModule::SetWordGenerator(const CAlphInfo *pAlph, CWordGeneratorBase *p
   }
 }
 
+void CGameModule::StartWriting(unsigned long lTime) {
+  if (!m_ulSentenceStartTime) {
+    m_ulSentenceStartTime = lTime;
+    m_dSentenceStartNats = numeric_limits<double>::max();
+  }
+}
+
 void CGameModule::DecorateView(unsigned long lTime, CDasherView *pView, CDasherModel *pModel) {
 
-  if (GetBoolParameter(BP_DASHER_PAUSED) && !m_ulSentenceStartTime) {
-    m_ulSentenceStartTime = lTime;
+  if (m_dSentenceStartNats == numeric_limits<double>::max())
     m_dSentenceStartNats = pModel->GetNats();
-  }
-  
+
   m_vTargetY.push_back((m_y1+m_y2)/2);
   
   //draw a line along the y axis
@@ -133,7 +138,7 @@ void CGameModule::DecorateView(unsigned long lTime, CDasherView *pView, CDasherM
   if(m_iLastSym  == m_vTargetSymbols.size() - 1) {
     m_pInterface->Message(ComputeStats(m_vTargetY),true);
     m_vTargetY.clear(); //could preserve if samples not excessive...but is it meaningful (given restart)?
-    SetBoolParameter(BP_DASHER_PAUSED, true);
+    m_pInterface->GetActiveInputMethod()->pause();
     m_ulTotalTime += (lTime - m_ulSentenceStartTime);
     m_dTotalNats += (pModel->GetNats() - m_dSentenceStartNats);
     m_uiTotalSyms += m_vTargetSymbols.size();
diff --git a/Src/DasherCore/GameModule.h b/Src/DasherCore/GameModule.h
index 3fa13c2..ecdcac0 100644
--- a/Src/DasherCore/GameModule.h
+++ b/Src/DasherCore/GameModule.h
@@ -47,6 +47,8 @@ class CGameModule : protected CSettingsUser, protected TransientObserver<const C
 
   ~CGameModule();
 
+  void StartWriting(unsigned long lTime);
+  
   /**
    * Draws Game Mode specific visuals to the screen.
    * \param pView The Dasher View to be modified
diff --git a/Src/DasherCore/InputFilter.h b/Src/DasherCore/InputFilter.h
index 38ceadc..33f60a1 100644
--- a/Src/DasherCore/InputFilter.h
+++ b/Src/DasherCore/InputFilter.h
@@ -19,13 +19,34 @@ class CInputFilter : public CDasherModule {
     : CDasherModule(iID, InputMethod, szName), m_pInterface(pInterface) {
   };
 
+  ///Called after nodes have been rendered, to draw any decorations on the view.
+  ///\return True if the decorations were (potentially) different from the last
+  /// frame; false if the decorations drawn are definitely the same as those
+  /// drawn in the previous frame.
   virtual bool DecorateView(CDasherView *pView, CDasherInput *pInput) { return false; };
 
   virtual void KeyDown(unsigned long Time, int iId, CDasherView *pDasherView, CDasherInput *pInput, CDasherModel *pModel) {}
   
   virtual void KeyUp(unsigned long Time, int iId, CDasherView *pDasherView, CDasherInput *pInput, CDasherModel *pModel) {}
 
-  virtual bool Timer(unsigned long Time, CDasherView *m_pDasherView, CDasherInput *pInput, CDasherModel *m_pDasherModel, CExpansionPolicy **pol)=0;// { return false; };
+  ///Called for each frame, before rendering. Filter thus has the opportunity
+  /// to schedule one or more steps with the model (or cancel scheduled steps!);
+  /// after calling, model will move to the next step that has been scheduled -
+  /// whether by Timer(), or elsewhere. Thus, a filter in which movement is
+  /// continuous while the filter is in some unpaused state, can ScheduleOneStep
+  /// in each call to Timer; alternatively, a filter responding to clicks, can
+  /// schedule a whole bunch of steps when a click occurs, and then Timer need
+  /// do nothing (as the model will run through those steps, one per frame).
+  ///\param pView useful for co-ordinate conversions; however, no drawing should
+  /// be performed here (that should be done in DecorateView).
+  virtual void Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CExpansionPolicy **pol) {};
+
+  ///Called to tell the Filter to halt any movement that may be in progress:
+  /// e.g. if some UI action has occurred taking focus/control away from the
+  /// Dasher canvas. Thus, filters should (a) ensure they do not schedule any
+  /// movement in subsequent Timer() calls; and (b) also ensure the model is
+  /// cleared of any steps already scheduled with it.
+  virtual void pause() = 0;
 
   virtual void Activate() {};
   virtual void Deactivate() {};
@@ -33,12 +54,35 @@ class CInputFilter : public CDasherModule {
   virtual bool GetMinWidth(int &iMinWidth) {
     return false;
   }
-  
+
+  ///Used by Control Mode to determine whether to display a Pause node (perhaps
+  /// in addition to a Stop/Done). If general, this is desirable if the filter
+  /// will continuously enter bits in the absence of user input, and thus there
+  /// should be the option to tell the filter to stop doing this (without the
+  /// user necessarily having finished the phrase); no if the filter will not
+  /// enter bits without the user actively pushing buttons / performing input.
   virtual bool supportsPause() {return false;}
   
  protected:
   CDasherInterfaceBase * const m_pInterface;
 };
+  
+  ///Simple class, basis for filters using ScheduleZoom rather than
+  /// ScheduleOneStep, providing a ScheduleZoom method wrapping the
+  /// DasherModel, such that pause() cancels any such zoom in progress.
+  class CStaticFilter : public CInputFilter {
+  public:
+    CStaticFilter(CDasherInterfaceBase *pIntf, ModuleID_t iId, const char *szName)
+    : CInputFilter(pIntf, iId, szName), m_pModel(NULL) {
+    }
+    void pause() {if (m_pModel) m_pModel->ClearScheduledSteps();}
+  protected:
+    void ScheduleZoom(CDasherModel *pModel, myint x, myint y) {
+      (m_pModel = pModel)->ScheduleZoom(x,y);
+    }
+  private:
+    CDasherModel *m_pModel;
+  };
 }
 /// @}
 
diff --git a/Src/DasherCore/OneButtonDynamicFilter.cpp b/Src/DasherCore/OneButtonDynamicFilter.cpp
index dec1b31..cb813fd 100644
--- a/Src/DasherCore/OneButtonDynamicFilter.cpp
+++ b/Src/DasherCore/OneButtonDynamicFilter.cpp
@@ -108,15 +108,14 @@ void COneButtonDynamicFilter::KeyUp(unsigned long Time, int iId, CDasherView *pD
   CButtonMultiPress::KeyUp(Time, iId, pDasherView, pInput, pModel);
 }
 
-bool COneButtonDynamicFilter::TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) {
+void COneButtonDynamicFilter::TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) {
   OneStepTowards(m_pDasherModel, m_iTargetX[m_iTarget], m_iTargetY[m_iTarget], Time, SlowStartSpeedMul(Time));
-  return true;
 }
 
 void COneButtonDynamicFilter::ActionButton(unsigned long iTime, int iButton, int iType, CDasherModel *pModel) {
   if (iType != 0) {
     //double/long push
-    reverse();
+    reverse(iTime);
     return;
   }
     
diff --git a/Src/DasherCore/OneButtonDynamicFilter.h b/Src/DasherCore/OneButtonDynamicFilter.h
index 467f921..858c648 100644
--- a/Src/DasherCore/OneButtonDynamicFilter.h
+++ b/Src/DasherCore/OneButtonDynamicFilter.h
@@ -41,7 +41,7 @@ class COneButtonDynamicFilter : public CButtonMultiPress {
 
  private:
   unsigned int maxClickCount() {return 2;} //double-click to reverse
-  virtual bool TimerImpl(unsigned long Time, CDasherView *pView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol);
+  virtual void TimerImpl(unsigned long Time, CDasherView *pView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol);
   virtual void ActionButton(unsigned long iTime, int iButton, int iType, CDasherModel *pModel);
   
   int m_iTarget;
diff --git a/Src/DasherCore/OneButtonFilter.cpp b/Src/DasherCore/OneButtonFilter.cpp
index 6ebd8c8..c09291b 100644
--- a/Src/DasherCore/OneButtonFilter.cpp
+++ b/Src/DasherCore/OneButtonFilter.cpp
@@ -14,16 +14,13 @@ static SModuleSettings sSettings[] = {
 };
 
 COneButtonFilter::COneButtonFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface)
-  : CInputFilter(pInterface, 9, "Static One Button Mode"), CSettingsUser(pCreator) {
+  : CStaticFilter(pInterface, 9, "Static One Button Mode"), CSettingsUser(pCreator) {
 
   bStarted = 0;
   iLocation = 0;
 
 }
 
-COneButtonFilter::~COneButtonFilter() {
-}
-
 bool COneButtonFilter::DecorateView(CDasherView *pView, CDasherInput *pInput) {
 
   CDasherScreen *pScreen(pView->Screen());
@@ -52,7 +49,7 @@ bool COneButtonFilter::DecorateView(CDasherView *pView, CDasherInput *pInput) {
   return true;
 }
 
-bool COneButtonFilter::Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) {
+void COneButtonFilter::Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) {
 
   if(bStarted) {
     iLocation = (Time - iStartTime) * 4096 / GetLongParameter(LP_STATIC1B_TIME);
@@ -66,8 +63,6 @@ bool COneButtonFilter::Timer(unsigned long Time, CDasherView *pView, CDasherInpu
       iLocation = 8192-iLocation;
     }
   }
-
-  return m_pDasherModel->NextScheduledStep();
 }
 
 void COneButtonFilter::KeyDown(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel) {
@@ -75,12 +70,12 @@ void COneButtonFilter::KeyDown(unsigned long iTime, int iId, CDasherView *pView,
     if (iLocation == 0) {
       //back up by one zoom step.
       const myint x(GetLongParameter(LP_STATIC1B_ZOOM)*2048);
-      pModel->ScheduleZoom(iTime, 2048-x, 2048+x);
+      ScheduleZoom(pModel, 2048-x, 2048+x);
     } else {
       iLocation -= (GetLongParameter(LP_DYNAMIC_BUTTON_LAG)*4096) / GetLongParameter(LP_STATIC1B_TIME);
       if (iLocation>4096) iLocation =8192-iLocation;
       const myint x(2048/GetLongParameter(LP_STATIC1B_ZOOM));
-      pModel->ScheduleZoom(iTime, 2048-x, 2048+x);
+      ScheduleZoom(pModel, 2048-x, 2048+x);
     }
     bStarted = false;
   } else {
diff --git a/Src/DasherCore/OneButtonFilter.h b/Src/DasherCore/OneButtonFilter.h
index 79dccc6..347e12b 100644
--- a/Src/DasherCore/OneButtonFilter.h
+++ b/Src/DasherCore/OneButtonFilter.h
@@ -6,13 +6,12 @@
 namespace Dasher {
 /// \ingroup InputFilter
 /// @{
-class COneButtonFilter : public CInputFilter, private CSettingsUser {
+class COneButtonFilter : public CStaticFilter, private CSettingsUser {
  public:
   COneButtonFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface);
-  ~COneButtonFilter();
 
   virtual bool DecorateView(CDasherView *pView, CDasherInput *pInput);
-  virtual bool Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *m_pDasherModel, CExpansionPolicy **pol);
+  virtual void Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *m_pDasherModel, CExpansionPolicy **pol);
   virtual void KeyDown(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel);
   bool GetSettings(SModuleSettings **pSettings, int *iCount);
  private:
diff --git a/Src/DasherCore/OneDimensionalFilter.cpp b/Src/DasherCore/OneDimensionalFilter.cpp
index 464363c..90ebe52 100644
--- a/Src/DasherCore/OneDimensionalFilter.cpp
+++ b/Src/DasherCore/OneDimensionalFilter.cpp
@@ -80,24 +80,25 @@ CStartHandler *COneDimensionalFilter::MakeStartHandler() {
     public:
       C1DCircleStartHandler(COneDimensionalFilter *f) : CCircleStartHandler(f) {
       }
-      void ComputeScreenLoc(CDasherView *pView) {
-        if (m_iScreenRadius!=-1) return;
-        CCircleStartHandler::ComputeScreenLoc(pView);
-        if (GetBoolParameter(BP_DASHER_PAUSED)) {
-          //put start circle at center of 1D transform, rather than center of screen
-          // (leave m_iScreenRadius, in pixels, as computed by above)
+      CDasherScreen::point CircleCenter(CDasherView *pView) {
+        if (m_iScreenRadius==-1) {//if we need to recompute
+          CCircleStartHandler::CircleCenter(pView); //that does the radius
           const myint rad(GetLongParameter(LP_CIRCLE_PERCENT) * CDasherModel::ORIGIN_Y / 100); //~~rad/2 in dasher-coords
-          pView->Dasher2Screen(CDasherModel::ORIGIN_X-static_cast<COneDimensionalFilter*>(m_pFilter)->forwardmax+rad, CDasherModel::ORIGIN_Y,m_screenCircleCenter.x, m_screenCircleCenter.y);
-        } 
-      }
-      void HandleEvent(int iParameter) {
-        if (iParameter==BP_DASHER_PAUSED) {
-          //circle needs to move for pause/unpause; setting radius to -1 causes
-          // next call to DecorateView or Timer to re-call ComputeScreenLoc.
-          m_iScreenRadius=-1;
+          m_pView->Dasher2Screen(CDasherModel::ORIGIN_X-static_cast<COneDimensionalFilter*>(m_pFilter)->forwardmax+rad, CDasherModel::ORIGIN_Y,m_fwdCenter.x, m_fwdCenter.y);
         }
-        CCircleStartHandler::HandleEvent(iParameter);
+        if (!static_cast<COneDimensionalFilter*>(m_pFilter)->isPaused()) return CCircleStartHandler::CircleCenter(pView);
+        //paused. put start circle at center of 1D transform, rather than center of screen
+        // but keep the same m_iScreenRadius, in pixels - so recompute if necessary:
+        return m_fwdCenter;
+      }
+      void onPause() {
+        //circle needs to move for pause/unpause; setting radius to -1 causes
+        // next call to DecorateView or Timer to re-call ComputeScreenLoc.
+        m_iScreenRadius=-1;
+        CCircleStartHandler::onPause();
       }
+    private:
+      CDasherScreen::point m_fwdCenter;
     };
     return new C1DCircleStartHandler(this);
   }
diff --git a/Src/DasherCore/Parameters.cpp b/Src/DasherCore/Parameters.cpp
index 9be39c1..3a4fc78 100644
--- a/Src/DasherCore/Parameters.cpp
+++ b/Src/DasherCore/Parameters.cpp
@@ -24,7 +24,6 @@ const bp_table boolparamtable[] = {
   {BP_TURBO_MODE, "TurboMode", PERS, true, "Boost speed when holding key1 or right mouse button"},
   {BP_AUTOCALIBRATE, "Autocalibrate", PERS, false, "Automatically learn TargetOffset e.g. gazetracking"},
   {BP_REMAP_XTREME, "RemapXtreme", PERS, false, "Pointer at extreme Y translates more and zooms less"},
-  {BP_DASHER_PAUSED, "DasherPaused", !PERS, true, "Dasher Paused"},
   {BP_GAME_MODE, "GameMode", !PERS, false, "Dasher Game Mode"},
   {BP_LM_DICTIONARY, "Dictionary", PERS, true, "Whether the word-based language model uses a dictionary"},
   {BP_LM_LETTER_EXCLUSION, "LetterExclusion", PERS, true, "Whether to do letter exclusion in the word-based model"},
diff --git a/Src/DasherCore/Parameters.h b/Src/DasherCore/Parameters.h
index 0a171bd..89b5ed7 100644
--- a/Src/DasherCore/Parameters.h
+++ b/Src/DasherCore/Parameters.h
@@ -33,7 +33,7 @@ enum {
   BP_START_SPACE, BP_STOP_IDLE, BP_CONTROL_MODE, 
   BP_COLOUR_MODE, BP_MOUSEPOS_MODE,
   BP_PALETTE_CHANGE, BP_TURBO_MODE,
-  BP_AUTOCALIBRATE, BP_REMAP_XTREME, BP_DASHER_PAUSED,
+  BP_AUTOCALIBRATE, BP_REMAP_XTREME,
   BP_GAME_MODE, BP_LM_DICTIONARY, 
   BP_LM_LETTER_EXCLUSION, BP_AUTO_SPEEDCONTROL,
   BP_LM_ADAPTIVE, BP_SOCKET_INPUT_ENABLE, BP_SOCKET_DEBUG, 
diff --git a/Src/DasherCore/StartHandler.h b/Src/DasherCore/StartHandler.h
index 14f71e2..caa8366 100644
--- a/Src/DasherCore/StartHandler.h
+++ b/Src/DasherCore/StartHandler.h
@@ -17,7 +17,8 @@ public:
   
   virtual bool DecorateView(CDasherView *pView) = 0;
   virtual void Timer(unsigned long iTime, dasherint iX, dasherint iY, CDasherView *pView) = 0;
-
+  virtual void onRun(unsigned long iTime) {}
+  virtual void onPause() {}
 protected:
   CDefaultFilter * const m_pFilter;
 };
diff --git a/Src/DasherCore/StylusFilter.cpp b/Src/DasherCore/StylusFilter.cpp
index 5f043c0..a8adda9 100644
--- a/Src/DasherCore/StylusFilter.cpp
+++ b/Src/DasherCore/StylusFilter.cpp
@@ -7,38 +7,32 @@
 using namespace Dasher;
 
 CStylusFilter::CStylusFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID, const char *szName)
-  : CDefaultFilter(pCreator, pInterface, pFramerate, iID, szName) {
-}
-
-bool CStylusFilter::Timer(unsigned long iTime, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CExpansionPolicy **pol)
-{
-  //First, try to continue any zoom scheduled by a previous click...
-  if (pModel->NextScheduledStep()) {
-    //note that this skips the rest of CDefaultFilter::Timer;
-    //however, given we're paused, this is only the Start Handler,
-    //which we're not using anyway.
-    return true;
-  }
-  return CDefaultFilter::Timer(iTime, pView, pInput, pModel, pol);
+  : CDefaultFilter(pCreator, pInterface, pFramerate, iID, szName), m_pModel(NULL) {
 }
 
 void CStylusFilter::KeyDown(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel) {
   if(iId == 100) {
-    pModel->ClearScheduledSteps();
-    Unpause(iTime);
+    //pModel->ClearScheduledSteps(); //no need - each one step scheduled by superclass, will do this
+    run(iTime);
     m_iKeyDownTime = iTime;
   } else
     CDefaultFilter::KeyDown(iTime, iId, pView, pInput, pModel);
 }
 
+void CStylusFilter::pause() {
+  CDefaultFilter::pause();
+  if (m_pModel) m_pModel->ClearScheduledSteps();
+}
+
 void CStylusFilter::KeyUp(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel) {
   if(iId == 100) {
+    pause(); //stops superclass from scheduling any more one-step movements
     if (iTime - m_iKeyDownTime < GetLongParameter(LP_TAP_TIME)) {
       pInput->GetDasherCoords(m_iLastX, m_iLastY, pView);
       ApplyClickTransform(m_iLastX, m_iLastY, pView);
-      pModel->ScheduleZoom(iTime, m_iLastY-m_iLastX, m_iLastY+m_iLastX);
+      (m_pModel=pModel)->ScheduleZoom(m_iLastY-m_iLastX, m_iLastY+m_iLastX);
     } else {
-      m_pInterface->Stop();
+      m_pInterface->Done();
     }
   } else
     CDefaultFilter::KeyUp(iTime, iId, pView, pInput, pModel);
diff --git a/Src/DasherCore/StylusFilter.h b/Src/DasherCore/StylusFilter.h
index 8ee2222..7e6e4dd 100644
--- a/Src/DasherCore/StylusFilter.h
+++ b/Src/DasherCore/StylusFilter.h
@@ -13,7 +13,11 @@ class CStylusFilter : public CDefaultFilter {
   ///Override DefaultFilter (which supports pause), as we don't
   /// - motion requires continually holding stylus against screen
   virtual bool supportsPause() {return false;}
-  virtual bool Timer(unsigned long Time, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CExpansionPolicy **pol);
+
+  //no Timer method required: DefaultFilter does fine when we're moving,
+  // and will ignore any zoom scheduled by a click.
+  
+  void pause();
   virtual void KeyDown(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel);
   virtual void KeyUp(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel);
  protected:
@@ -24,6 +28,7 @@ class CStylusFilter : public CDefaultFilter {
   virtual CStartHandler *MakeStartHandler();
  private:
   unsigned long m_iKeyDownTime;
+  CDasherModel *m_pModel;
 };
 }
 /// @}
diff --git a/Src/DasherCore/TwoBoxStartHandler.cpp b/Src/DasherCore/TwoBoxStartHandler.cpp
index a5305a6..0f52f2d 100644
--- a/Src/DasherCore/TwoBoxStartHandler.cpp
+++ b/Src/DasherCore/TwoBoxStartHandler.cpp
@@ -5,11 +5,11 @@
 using namespace Dasher;
 
 CTwoBoxStartHandler::CTwoBoxStartHandler(CDefaultFilter *pCreator)
-: CStartHandler(pCreator), CSettingsUserObserver(pCreator), m_bFirstBox(true), m_iBoxEntered(std::numeric_limits<long>::max()) {
+: CStartHandler(pCreator), CSettingsUser(pCreator), m_bFirstBox(true), m_iBoxEntered(std::numeric_limits<long>::max()) {
 }
 
 bool CTwoBoxStartHandler::DecorateView(CDasherView *pView) {
-  if (!GetBoolParameter(BP_DASHER_PAUSED)) return false;
+  if (!m_pFilter->isPaused()) return false;
   
   int iHeight = pView->Screen()->GetHeight();
   int iWidth = pView->Screen()->GetWidth();
@@ -27,7 +27,7 @@ bool CTwoBoxStartHandler::DecorateView(CDasherView *pView) {
 }
 
 void CTwoBoxStartHandler::Timer(unsigned long iTime, dasherint iDasherX, dasherint iDasherY, CDasherView *pView) { 
-  if (!GetBoolParameter(BP_DASHER_PAUSED)) return;
+  if (!m_pFilter->isPaused()) return;
   
   int iBoxMin, iBoxMax;
   if(m_bFirstBox) {
@@ -52,7 +52,7 @@ void CTwoBoxStartHandler::Timer(unsigned long iTime, dasherint iDasherX, dasheri
       if(m_bFirstBox)
         m_bFirstBox=false;
       else
-        m_pFilter->Unpause(iTime);
+        m_pFilter->run(iTime);
       m_iBoxEntered = std::numeric_limits<long>::max();
     }
   } else {
@@ -64,7 +64,6 @@ void CTwoBoxStartHandler::Timer(unsigned long iTime, dasherint iDasherX, dasheri
   }
 }
 
-void CTwoBoxStartHandler::HandleEvent(int iParameter) {
-  if (iParameter==BP_DASHER_PAUSED)
+void CTwoBoxStartHandler::onPause() {
     m_bFirstBox = true;
 }
diff --git a/Src/DasherCore/TwoBoxStartHandler.h b/Src/DasherCore/TwoBoxStartHandler.h
index 35709b8..cf6d8f1 100644
--- a/Src/DasherCore/TwoBoxStartHandler.h
+++ b/Src/DasherCore/TwoBoxStartHandler.h
@@ -7,13 +7,13 @@
 namespace Dasher {
 /// \ingroup Start
 /// @{
-class CTwoBoxStartHandler : public CStartHandler, public CSettingsUserObserver {
+class CTwoBoxStartHandler : public CStartHandler, public CSettingsUser {
 public:
   CTwoBoxStartHandler(CDefaultFilter *pCreator);
 
   virtual bool DecorateView(CDasherView *pView);
   virtual void Timer(unsigned long iTime, dasherint iX, dasherint iY, CDasherView *pView);
-  virtual void HandleEvent(int iParameter);
+  virtual void onPause();
 
  private:
   ///Box currently being displayed, _iff_ BP_DASHER_PAUSED is set
diff --git a/Src/DasherCore/TwoButtonDynamicFilter.cpp b/Src/DasherCore/TwoButtonDynamicFilter.cpp
index ad3b4ea..4be5a14 100644
--- a/Src/DasherCore/TwoButtonDynamicFilter.cpp
+++ b/Src/DasherCore/TwoButtonDynamicFilter.cpp
@@ -49,7 +49,7 @@ static SModuleSettings sSettings[] = {
 };
 
 CTwoButtonDynamicFilter::CTwoButtonDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate)
-  : CButtonMultiPress(pCreator, pInterface, pFramerate, 14, _("Two Button Dynamic Mode")), m_iMouseButton(-1)
+  : CButtonMultiPress(pCreator, pInterface, pFramerate, 14, _("Two Button Dynamic Mode")), CSettingsObserver(pCreator), m_iMouseButton(-1)
 {
   //ensure that m_dLagMul is properly initialised
   HandleEvent(LP_DYNAMIC_BUTTON_LAG);
@@ -117,9 +117,8 @@ void CTwoButtonDynamicFilter::KeyUp(unsigned long Time, int iId, CDasherView *pV
   CButtonMultiPress::KeyUp(Time, iId, pView, pInput,pModel);
 }
 
-bool CTwoButtonDynamicFilter::TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) {
+void CTwoButtonDynamicFilter::TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) {
   OneStepTowards(m_pDasherModel, 100,2048, Time, SlowStartSpeedMul(Time));
-  return true;
 }
 
 void CTwoButtonDynamicFilter::ActionButton(unsigned long iTime, int iButton, int iType, CDasherModel *pModel) {
@@ -134,7 +133,7 @@ void CTwoButtonDynamicFilter::ActionButton(unsigned long iTime, int iButton, int
     dFactor *= - (1.0 + exp(pModel->GetNats())); //prev jump is further now
   }
   else if (iType != 0) {
-    reverse();
+    reverse(iTime);
     return;
   }
   
@@ -186,5 +185,4 @@ void CTwoButtonDynamicFilter::HandleEvent(int iParameter)
   case LP_TWO_BUTTON_OFFSET:
       m_bDecorationChanged = true;
   }
-  CButtonMultiPress::HandleEvent(iParameter);
 }
diff --git a/Src/DasherCore/TwoButtonDynamicFilter.h b/Src/DasherCore/TwoButtonDynamicFilter.h
index d4669b4..451391e 100644
--- a/Src/DasherCore/TwoButtonDynamicFilter.h
+++ b/Src/DasherCore/TwoButtonDynamicFilter.h
@@ -28,7 +28,7 @@
 namespace Dasher {
 /// \ingroup InputFilter
 /// @{
-class CTwoButtonDynamicFilter : public CButtonMultiPress {
+class CTwoButtonDynamicFilter : public CButtonMultiPress, public CSettingsObserver {
  public:
   CTwoButtonDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate);
 
@@ -48,7 +48,7 @@ class CTwoButtonDynamicFilter : public CButtonMultiPress {
 	
  private:
   unsigned int maxClickCount() {return GetBoolParameter(BP_2B_INVERT_DOUBLE) ? 3 : 2;}
-  virtual bool TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol);
+  virtual void TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol);
   virtual void ActionButton(unsigned long iTime, int iButton, int iType, CDasherModel *pModel);
   double m_dLagMul;
   ///id of physical key, whose pressing we have emulated, in response
diff --git a/Src/DasherCore/TwoPushDynamicFilter.cpp b/Src/DasherCore/TwoPushDynamicFilter.cpp
index e7feef7..e6bf99c 100644
--- a/Src/DasherCore/TwoPushDynamicFilter.cpp
+++ b/Src/DasherCore/TwoPushDynamicFilter.cpp
@@ -44,7 +44,7 @@ static SModuleSettings sSettings[] = {
 };
 
 CTwoPushDynamicFilter::CTwoPushDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate)
-  : CDynamicButtons(pCreator, pInterface, pFramerate, 14, _("Two-push Dynamic Mode (New One Button)")), m_dNatsSinceFirstPush(-std::numeric_limits<double>::infinity()) {
+  : CDynamicButtons(pCreator, pInterface, pFramerate, 14, _("Two-push Dynamic Mode (New One Button)")), CSettingsObserver(pCreator), m_dNatsSinceFirstPush(-std::numeric_limits<double>::infinity()) {
   
   HandleEvent(LP_TWO_PUSH_OUTER);//and all the others too!
 }
@@ -99,9 +99,7 @@ bool CTwoPushDynamicFilter::DecorateView(CDasherView *pView, CDasherInput *pInpu
   return bRV;
 }
 
-void CTwoPushDynamicFilter::HandleEvent(int iParameter)
-{
-  CDynamicButtons::HandleEvent(iParameter);
+void CTwoPushDynamicFilter::HandleEvent(int iParameter) {
   switch (iParameter)
   {
     case LP_TWO_PUSH_OUTER:
@@ -189,7 +187,7 @@ void CTwoPushDynamicFilter::ActionButton(unsigned long iTime, int iButton, int i
   // 1 = long click
   
   if (iType != 0) {
-    reverse();
+    reverse(iTime);
     return;
   }
   if (m_dNatsSinceFirstPush == -std::numeric_limits<double>::infinity()) //no button pushed (recently)
@@ -202,7 +200,7 @@ void CTwoPushDynamicFilter::ActionButton(unsigned long iTime, int iButton, int i
   {
 //cout << "Second push - event type " << iType << " logGrowth " << pModel->GetNats() << "\n";
     if (m_iActiveMarker == -1)
-      reverse();
+      reverse(iTime);
     else
     {
       ApplyOffset(pModel,m_aiTarget[m_iActiveMarker]);
@@ -220,8 +218,7 @@ bool doSet(int &var, const int val)
   return true;
 }
 
-bool CTwoPushDynamicFilter::TimerImpl(unsigned long iTime, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol)
-{
+void CTwoPushDynamicFilter::TimerImpl(unsigned long iTime, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) {
   DASHER_ASSERT(isRunning());
   if (m_dNatsSinceFirstPush > -std::numeric_limits<double>::infinity()) // first button has been pushed
   {
@@ -248,7 +245,7 @@ bool CTwoPushDynamicFilter::TimerImpl(unsigned long iTime, CDasherView *m_pDashe
     {
 //cout << " growth " << dLogGrowth << " - reversing\n";
       //button pushed, but then waited too long.
-      reverse();
+      reverse(iTime);
     }
     else if (dLogGrowth >= m_dMinShortTwoPushTime && dLogGrowth <= m_dMaxShortTwoPushTime)
       m_bDecorationChanged |= doSet(m_iActiveMarker, 0 /*up*/);
@@ -257,12 +254,11 @@ bool CTwoPushDynamicFilter::TimerImpl(unsigned long iTime, CDasherView *m_pDashe
     else m_bDecorationChanged |= doSet(m_iActiveMarker, -1 /*in middle (neither/both) or too short*/);
   }
   OneStepTowards(m_pDasherModel, 100, 2048, iTime, SlowStartSpeedMul(iTime));
-  return true;
 }
 
-void CTwoPushDynamicFilter::run() {
+void CTwoPushDynamicFilter::run(unsigned long iTime) {
   m_dNatsSinceFirstPush = -std::numeric_limits<double>::infinity();
-  CDynamicButtons::run();
+  CDynamicButtons::run(iTime);
 }
 
 bool CTwoPushDynamicFilter::GetSettings(SModuleSettings **pSettings, int *iCount) {
diff --git a/Src/DasherCore/TwoPushDynamicFilter.h b/Src/DasherCore/TwoPushDynamicFilter.h
index 6a0c7a4..f893e8f 100644
--- a/Src/DasherCore/TwoPushDynamicFilter.h
+++ b/Src/DasherCore/TwoPushDynamicFilter.h
@@ -25,7 +25,7 @@
 namespace Dasher {
 /// \ingroup InputFilter
 /// @{
-class CTwoPushDynamicFilter : public CDynamicButtons /*long push, but do our own "multi-push" detection*/ {
+class CTwoPushDynamicFilter : public CDynamicButtons, public CSettingsObserver /*long push, but do our own "multi-push" detection*/ {
  public:
   CTwoPushDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate);
   
@@ -40,12 +40,12 @@ class CTwoPushDynamicFilter : public CDynamicButtons /*long push, but do our own
   virtual void KeyUp(unsigned long Time, int iId, CDasherView *pDasherView, CDasherInput *pInput, CDasherModel *pModel);
 
  protected:
-  virtual bool TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol);
+  virtual void TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol);
   virtual void ActionButton(unsigned long iTime, int iButton, int iType, CDasherModel *pModel);
 
   virtual void HandleEvent(int iParameter);
 
-  virtual void run();
+  virtual void run(unsigned long iTime);
 
  private:
   double m_dLogUpMul, m_dLogDownMul, m_dLagBits;
diff --git a/Src/DasherCore/UserLog.cpp b/Src/DasherCore/UserLog.cpp
index 1be4d83..947e89e 100644
--- a/Src/DasherCore/UserLog.cpp
+++ b/Src/DasherCore/UserLog.cpp
@@ -46,8 +46,8 @@ static UserLogParamMask s_UserLogParamMaskTable [] = {
 };
 
 CUserLog::CUserLog(CSettingsUser *pCreateFrom,
-                   Observable<const CEditEvent *> *pObsv,
-                   int iLogTypeMask) : CUserLogBase(pCreateFrom, pObsv) {
+                   Observable<const CEditEvent *> *pObsv, int iLogTypeMask)
+: CUserLogBase(pObsv), CSettingsUserObserver(pCreateFrom) {
   //CFunctionLogger f1("CUserLog::CUserLog", g_pLogger);
 
   InitMemberVars();
@@ -1134,7 +1134,8 @@ void CUserLog::UpdateParam(int iParameter, int iOptionMask)
 // TODO these are broken by settings rewrite. Fix???
 
 // Load the object from an XML file
-CUserLog::CUserLog(string strXMLFilename) : CUserLogBase(NULL, NULL) {
+CUserLog::CUserLog(string strXMLFilename)
+: CUserLogBase(NULL), CSettingsUserObserver(NULL) {
   //CFunctionLogger f1("CUserLog::CUserLog(XML)", g_pLogger);
 
   InitMemberVars();
diff --git a/Src/DasherCore/UserLog.h b/Src/DasherCore/UserLog.h
index fd12675..af91ee9 100644
--- a/Src/DasherCore/UserLog.h
+++ b/Src/DasherCore/UserLog.h
@@ -74,7 +74,7 @@ typedef vector<VECTOR_STRING>::iterator     VECTOR_VECTOR_STRING_ITER;
 
 // We need to be notified when parameters we are logging get changed, but UserLogBase
 // is already watching BP_DASHER_PAUSED
-class CUserLog : public CUserLogBase {
+class CUserLog : public CUserLogBase, protected Dasher::CSettingsUserObserver {
 public:
   CUserLog(Dasher::CSettingsUser *pCreateFrom, Observable<const Dasher::CEditEvent *> *pHandler, int iLogTypeMask);
 
diff --git a/Src/DasherCore/UserLogBase.cpp b/Src/DasherCore/UserLogBase.cpp
index 14b9787..894362c 100644
--- a/Src/DasherCore/UserLogBase.cpp
+++ b/Src/DasherCore/UserLogBase.cpp
@@ -14,8 +14,8 @@
 
 using namespace Dasher;
 
-CUserLogBase::CUserLogBase(CSettingsUser *pCreateFrom, Observable<const CEditEvent *> *pHandler)
-: CSettingsUserObserver(pCreateFrom), TransientObserver<const CEditEvent *>(pHandler), m_iNumDeleted(0) {
+CUserLogBase::CUserLogBase(Observable<const CEditEvent *> *pHandler)
+: TransientObserver<const CEditEvent *>(pHandler), m_iNumDeleted(0) {
 };
 
 void CUserLogBase::HandleEvent(const CEditEvent *evt) {
@@ -28,17 +28,6 @@ void CUserLogBase::HandleEvent(const CEditEvent *evt) {
   }
 }
 
-void CUserLogBase::HandleEvent(int iParameter) {
-  if (iParameter==BP_DASHER_PAUSED) {
-    if (!GetBoolParameter(BP_DASHER_PAUSED)) {
-      //Just unpaused
-      StartWriting(); //note this happens for _each_ click/zoom
-      // in Click Mode, Direct Mode, Menu Mode etc.
-      // (but StartWriting generally ignores extra calls after the first)
-    }
-  }
-}
-
 void CUserLogBase::FrameEnded() {
   //pass on added/deleted if any, and get ready for next frame
   if (m_iNumDeleted) {
diff --git a/Src/DasherCore/UserLogBase.h b/Src/DasherCore/UserLogBase.h
index 177bd2c..4cbfe65 100644
--- a/Src/DasherCore/UserLogBase.h
+++ b/Src/DasherCore/UserLogBase.h
@@ -16,13 +16,16 @@ namespace Dasher {
 
 /// \defgroup Logging Logging routines
 /// @{
-class CUserLogBase : protected Dasher::CSettingsUserObserver, protected TransientObserver<const Dasher::CEditEvent *> {
+class CUserLogBase : protected TransientObserver<const Dasher::CEditEvent *> {
  public:
-  CUserLogBase(CSettingsUser *pCreateFrom, Observable<const Dasher::CEditEvent*> *pHandler);
+  CUserLogBase(Observable<const Dasher::CEditEvent*> *pHandler);
 
   virtual void AddParam(const std::string& strName, const std::string& strValue, int iOptionMask = 0) = 0;
   virtual void AddParam(const std::string& strName, double dValue, int iOptionMask = 0) = 0;
   virtual void AddParam(const std::string& strName, int iValue, int iOptionMask = 0) = 0;
+  //Called when the user starts moving. Note this happens for _each_ click/zoom
+  // in Click Mode, Direct Mode, Menu Mode etc. (so should ignore extra calls
+  // after the first.)
   virtual void StartWriting() = 0;
   virtual void StopWriting(float dNats) = 0;
   virtual void StopWriting() = 0;
@@ -36,8 +39,6 @@ class CUserLogBase : protected Dasher::CSettingsUserObserver, protected Transien
   virtual void SetOuputFilename(const std::string& strFilename = "") = 0;
   virtual int GetLogLevelMask() = 0;
   virtual void KeyDown(int iId, int iType, int iEffect) = 0;
-  ///Watch for BP_DASHER_PAUSED being cleared, call StartWriting().
-  virtual void HandleEvent(int iParameter);
   ///Watches output events to record symbols added/deleted
   virtual void HandleEvent(const Dasher::CEditEvent *pEvent);
   ///Passes record of symbols added/deleted to AddSymbols/DeleteSymbols
diff --git a/Src/Gtk2/DasherControl.cpp b/Src/Gtk2/DasherControl.cpp
index a06afa7..781709c 100644
--- a/Src/Gtk2/DasherControl.cpp
+++ b/Src/Gtk2/DasherControl.cpp
@@ -585,9 +585,8 @@ gboolean CDasherControl::ExposeEvent() {
   return 0;
 }
 
-void CDasherControl::Stop() {
-  if (GetBoolParameter(BP_DASHER_PAUSED)) return;
-  CDasherInterfaceBase::Stop();
+void CDasherControl::Done() {
+  CDasherInterfaceBase::Done();
   g_signal_emit_by_name(GTK_WIDGET(m_pDasherControl), "dasher_stop");
 }
 
diff --git a/Src/Gtk2/DasherControl.h b/Src/Gtk2/DasherControl.h
index e9aa8a1..55a6249 100644
--- a/Src/Gtk2/DasherControl.h
+++ b/Src/Gtk2/DasherControl.h
@@ -130,8 +130,8 @@ public:
 
   gboolean ExposeEvent();
 
-  ///Override to broadcast signal...
-  virtual void Stop();
+  ///Override to broadcast dasher_stop signal...
+  virtual void Done();
 
   virtual void WriteTrainFile(const std::string &filename, const std::string &strNewText);
   virtual int GetFileSize(const std::string &strFileName);
diff --git a/Src/Gtk2/GtkDasherControl.cpp b/Src/Gtk2/GtkDasherControl.cpp
index 4e0c397..6645ea1 100644
--- a/Src/Gtk2/GtkDasherControl.cpp
+++ b/Src/Gtk2/GtkDasherControl.cpp
@@ -290,7 +290,7 @@ gtk_dasher_control_get_module_settings(GtkDasherControl * pControl, const gchar
 void 
 gtk_dasher_control_force_pause(GtkDasherControl *pControl) {
   GtkDasherControlPrivate *pPrivate = GTK_DASHER_CONTROL_GET_PRIVATE(pControl);
-  pPrivate->pControl->Stop();
+  pPrivate->pControl->GetActiveInputMethod()->pause();
 }
 
 const char *
diff --git a/Src/iPhone/Classes/DasherAppDelegate.mm b/Src/iPhone/Classes/DasherAppDelegate.mm
index 31eae7a..7809b57 100644
--- a/Src/iPhone/Classes/DasherAppDelegate.mm
+++ b/Src/iPhone/Classes/DasherAppDelegate.mm
@@ -318,7 +318,7 @@ using namespace Dasher;
 
 - (void)settings {
   //avoid awful muddle if we change out of tap-to-start mode whilst running.... 
-  _dasherInterface->Stop();
+  _dasherInterface->GetActiveInputMethod()->pause();
 	
   UITabBarController *tabs = [[[UITabBarController alloc] init] autorelease];
   tabs.title = @"Settings";
diff --git a/Src/iPhone/Classes/IPhoneFilters.h b/Src/iPhone/Classes/IPhoneFilters.h
index 507536e..15487d4 100644
--- a/Src/iPhone/Classes/IPhoneFilters.h
+++ b/Src/iPhone/Classes/IPhoneFilters.h
@@ -33,11 +33,11 @@ public:
   ///override to enable hold-to-go
 	virtual void KeyUp(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel);
 
-  ///respond to BP_DASHER_PAUSED by engaging wakelock (if !hold-to-go)
-  virtual void HandleEvent(int iParameter);
+  virtual void pause(); ///Override to re-enable idle timer
   void iPhonePrefsChanged(NSString *key);
   bool supportsPause();
 protected:
+  virtual void run(unsigned long iTime); ///Override to disable idle timer
   ///Override to choose whether to apply 1D transform or not, and to get X coord from touch if appropriate
 	virtual void ApplyTransform(myint &iDasherX, myint &iDasherY, CDasherView *pView);
   ///Never apply offset (just eyetracker remapping!) - otherwise would be done when operating in 2d mode
diff --git a/Src/iPhone/Classes/IPhoneFilters.mm b/Src/iPhone/Classes/IPhoneFilters.mm
index c46a1fe..f76f562 100644
--- a/Src/iPhone/Classes/IPhoneFilters.mm
+++ b/Src/iPhone/Classes/IPhoneFilters.mm
@@ -43,13 +43,13 @@ void CIPhoneTiltFilter::ApplyTransform(myint &iDasherX, myint &iDasherY, CDasher
 
 void CIPhoneTiltFilter::KeyDown(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel) {
 	if(iId == 100 && bHoldToGo)
-		Unpause(iTime);
+		run(iTime);
   else COneDimensionalFilter::KeyDown(iTime, iId, pView, pInput, pModel);
 }
 
 void CIPhoneTiltFilter::KeyUp(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel) {
 	if(iId == 100 && bHoldToGo)
-		m_pInterface->Stop();
+		stop();
   else COneDimensionalFilter::KeyUp(iTime, iId, pView, pInput, pModel);
 }
 
@@ -61,12 +61,14 @@ void CIPhoneTiltFilter::ApplyOffset(myint &iDasherX, myint &iDasherY) {
   //Do not apply LP_TARGET_OFFSET, or BP_AUTO_CALIBRATE
 }
 
-void CIPhoneTiltFilter::HandleEvent(int iParameter) {
-  if (iParameter==BP_DASHER_PAUSED
-      && m_pInterface->GetActiveInputMethod()==this) {
-    [UIApplication sharedApplication].idleTimerDisabled=(!GetBoolParameter(BP_DASHER_PAUSED) && !bHoldToGo);
-  }
-  COneDimensionalFilter::HandleEvent(iParameter);
+void CIPhoneTiltFilter::pause() {
+  COneDimensionalFilter::pause();
+  [UIApplication sharedApplication].idleTimerDisabled=NO;
+}
+
+void CIPhoneTiltFilter::run(unsigned long iTime) {
+  COneDimensionalFilter::run(iTime);
+  [UIApplication sharedApplication].idleTimerDisabled = !bHoldToGo;
 }
 
 void CIPhoneTiltFilter::iPhonePrefsChanged(NSString *key) {
@@ -137,12 +139,12 @@ CIPhoneTwoFingerFilter::CIPhoneTwoFingerFilter(CSettingsUser *pCreator, CDasherI
 }
 
 void CIPhoneTwoFingerFilter::KeyDown(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel) {
-  if (iId==101) Unpause(iTime);
+  if (iId==101) run(iTime);
   else if (iId!=100) CDefaultFilter::KeyDown(iTime, iId, pView, pInput, pModel);
 }
 
 void CIPhoneTwoFingerFilter::KeyUp(unsigned long iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel) {
-  if (iId==101) m_pInterface->Stop();
+  if (iId==101) stop();
   else if (iId!=100) CDefaultFilter::KeyUp(iTime, iId, pView, pInput, pModel);
 }
 



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