[dasher: 1/21] CDynamicFilter inc framerate+slowstart, pass dSpeedMul to OneStepTowards



commit 96217682ba6d267620ff8441946292e0b7da93ae
Author: Alan Lawrence <acl33 inf phy cam ac uk>
Date:   Mon Jun 27 15:34:47 2011 +0100

    CDynamicFilter inc framerate+slowstart, pass dSpeedMul to OneStepTowards
    
    CDynamicFilter is superclass of all those filters which need framerate
     monitoring (=> CFrameRate separated from CDasherModel), which are also the
     filters needing slow start (=>extract from CDasherModel). Provides
     Unpause (moved from CDasherInterface) to implement these two.
    
    Old CDynamicFilter (subclass of new) renamed to CDynamicButtons

 Src/DasherCore/ButtonMultiPress.cpp         |   12 +-
 Src/DasherCore/ButtonMultiPress.h           |    8 +-
 Src/DasherCore/CircleStartHandler.cpp       |   10 +-
 Src/DasherCore/CircleStartHandler.h         |    5 +-
 Src/DasherCore/ClickFilter.cpp              |    2 +-
 Src/DasherCore/DashIntfScreenMsgs.cpp       |   28 +++--
 Src/DasherCore/DashIntfScreenMsgs.h         |    5 +-
 Src/DasherCore/DasherButtons.cpp            |    2 +-
 Src/DasherCore/DasherInterfaceBase.cpp      |   45 +++-----
 Src/DasherCore/DasherInterfaceBase.h        |   18 +---
 Src/DasherCore/DasherModel.cpp              |   88 +++++----------
 Src/DasherCore/DasherModel.h                |   66 ++++--------
 Src/DasherCore/DefaultFilter.cpp            |   14 ++--
 Src/DasherCore/DefaultFilter.h              |   10 +-
 Src/DasherCore/DynamicButtons.cpp           |  156 +++++++++++++++++++++++++++
 Src/DasherCore/DynamicButtons.h             |   67 ++++++++++++
 Src/DasherCore/DynamicFilter.cpp            |  141 ++++--------------------
 Src/DasherCore/DynamicFilter.h              |   54 ++++------
 Src/DasherCore/FrameRate.cpp                |    2 +-
 Src/DasherCore/FrameRate.h                  |   23 ++---
 Src/DasherCore/Makefile.am                  |    4 +-
 Src/DasherCore/OneButtonDynamicFilter.cpp   |    6 +-
 Src/DasherCore/OneButtonDynamicFilter.h     |    2 +-
 Src/DasherCore/OneButtonFilter.cpp          |    2 +-
 Src/DasherCore/OneDimensionalFilter.cpp     |   10 +-
 Src/DasherCore/OneDimensionalFilter.h       |    2 +-
 Src/DasherCore/StartHandler.h               |    8 +-
 Src/DasherCore/StylusFilter.cpp             |    8 +-
 Src/DasherCore/StylusFilter.h               |    2 +-
 Src/DasherCore/TwoBoxStartHandler.cpp       |    9 +-
 Src/DasherCore/TwoBoxStartHandler.h         |    3 +-
 Src/DasherCore/TwoButtonDynamicFilter.cpp   |    8 +-
 Src/DasherCore/TwoButtonDynamicFilter.h     |    2 +-
 Src/DasherCore/TwoPushDynamicFilter.cpp     |   14 ++--
 Src/DasherCore/TwoPushDynamicFilter.h       |    6 +-
 Src/DasherCore/UserLog.cpp                  |    5 +-
 Src/DasherCore/UserLog.h                    |    2 +-
 Src/DasherCore/UserLogBase.cpp              |   15 +++-
 Src/DasherCore/UserLogBase.h                |    4 +-
 Src/MacOSX/Dasher.xcodeproj/project.pbxproj |    8 ++
 Src/MacOSX/DasherApp.h                      |    3 -
 Src/MacOSX/DasherApp.mm                     |   12 --
 42 files changed, 469 insertions(+), 422 deletions(-)
---
diff --git a/Src/DasherCore/ButtonMultiPress.cpp b/Src/DasherCore/ButtonMultiPress.cpp
index ce597d4..fdb4028 100644
--- a/Src/DasherCore/ButtonMultiPress.cpp
+++ b/Src/DasherCore/ButtonMultiPress.cpp
@@ -23,8 +23,8 @@
 
 using namespace Dasher;
 
-CButtonMultiPress::CButtonMultiPress(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName)
-  : CDynamicFilter(pCreator, pInterface, iID, szName) {
+CButtonMultiPress::CButtonMultiPress(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID, const char *szName)
+  : CDynamicButtons(pCreator, pInterface, pFramerate, iID, szName) {
 }
 
 void CButtonMultiPress::KeyDown(int iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CUserLogBase *pUserLog) {
@@ -57,23 +57,23 @@ void CButtonMultiPress::KeyDown(int iTime, int iId, CDasherView *pView, CDasherI
   // Record press...
   m_deQueueTimes.push_back(iTime);
   // ... and process normally; if it changes the state, pause()/reverse()'ll clear the queue
-  CDynamicFilter::KeyDown(iTime, iId, pView, pInput, pModel, pUserLog);
+  CDynamicButtons::KeyDown(iTime, iId, pView, pInput, pModel, pUserLog);
 }
 
 void CButtonMultiPress::pause()
 {
-  CDynamicFilter::pause();
+  CDynamicButtons::pause();
   m_deQueueTimes.clear();
 }
 
 void CButtonMultiPress::reverse()
 {
-  CDynamicFilter::reverse();
+  CDynamicButtons::reverse();
   m_deQueueTimes.clear();
 }
 
 void CButtonMultiPress::run()
 {
   if (!isRunning()) m_deQueueTimes.clear();
-  CDynamicFilter::run();
+  CDynamicButtons::run();
 }
diff --git a/Src/DasherCore/ButtonMultiPress.h b/Src/DasherCore/ButtonMultiPress.h
index 9c2cb4d..d783429 100644
--- a/Src/DasherCore/ButtonMultiPress.h
+++ b/Src/DasherCore/ButtonMultiPress.h
@@ -21,17 +21,17 @@
 #ifndef __ButtonMultiPress_h__
 #define __ButtonMultiPress_h__
 
-#include "DynamicFilter.h"
+#include "DynamicButtons.h"
 
 namespace Dasher {
 /// \ingroup InputFilter
 /// @{
-///DynamicFilter which detects multiple presses of the same button occurring in a short space of time
+/// DynamicButtons filter which detects multiple presses of the same button occurring in a short space of time
 /// (such multi-presses are then passed onto the standard 'ActionButton' method, with iType equal
 /// to the number of presses, for subclasses to handle/decide how to respond.)
-class CButtonMultiPress : public CDynamicFilter {
+class CButtonMultiPress : public CDynamicButtons {
  public:
-  CButtonMultiPress(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName);
+  CButtonMultiPress(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID, const char *szName);
 
   virtual void KeyDown(int iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CUserLogBase *pUserLog);
 
diff --git a/Src/DasherCore/CircleStartHandler.cpp b/Src/DasherCore/CircleStartHandler.cpp
index d5ed313..e6e1009 100644
--- a/Src/DasherCore/CircleStartHandler.cpp
+++ b/Src/DasherCore/CircleStartHandler.cpp
@@ -21,13 +21,15 @@
 #include "../Common/Common.h"
 
 #include "CircleStartHandler.h"
+#include "DefaultFilter.h"
+#include "DasherInterfaceBase.h"
 #include "Event.h"
 #include "DasherInput.h"
 
 using namespace Dasher;
 
-CCircleStartHandler::CCircleStartHandler(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface)
-: CStartHandler(pInterface), CSettingsUserObserver(pCreator), m_iEnterTime(std::numeric_limits<long>::max()), m_iScreenRadius(-1), m_pView(NULL) {
+CCircleStartHandler::CCircleStartHandler(CDefaultFilter *pCreator)
+: CStartHandler(pCreator), CSettingsUserObserver(pCreator), m_iEnterTime(std::numeric_limits<long>::max()), m_iScreenRadius(-1), m_pView(NULL) {
 }
 
 CCircleStartHandler::~CCircleStartHandler() {
@@ -88,9 +90,9 @@ void CCircleStartHandler::Timer(int iTime, dasherint mouseX, dasherint mouseY,CD
       if (m_iEnterTime != std::numeric_limits<long>::max() && iTime - m_iEnterTime > 1000) {
         //activate!
         if (GetBoolParameter(BP_DASHER_PAUSED))
-          m_pInterface->Unpause(iTime);
+          m_pFilter->Unpause(iTime);
         else
-          m_pInterface->Stop();
+          m_pFilter->m_pInterface->Stop();
         //note our HandleEvent method will then set
         //   m_iEnterTime = std::numeric_limits<long>::max()
         // thus preventing us from firing until user leaves circle and enters again
diff --git a/Src/DasherCore/CircleStartHandler.h b/Src/DasherCore/CircleStartHandler.h
index 32ab086..34e3bd0 100644
--- a/Src/DasherCore/CircleStartHandler.h
+++ b/Src/DasherCore/CircleStartHandler.h
@@ -2,12 +2,15 @@
 #define __CIRCLE_START_HANDLER_H__
 
 #include "StartHandler.h"
+#include "SettingsStore.h"
+#include "DasherScreen.h"
+
 /// \ingroup Start
 /// @{
 namespace Dasher {
 class CCircleStartHandler : public CStartHandler, public CSettingsUserObserver, public Observer<CDasherView *> {
 public:
-  CCircleStartHandler(CSettingsUser *pCreateFrom, CDasherInterfaceBase *pInterface);
+  CCircleStartHandler(CDefaultFilter *pCreator);
   ~CCircleStartHandler();
   virtual bool DecorateView(CDasherView *pView);
   virtual void Timer(int iTime, dasherint iX, dasherint iY, CDasherView *pView);
diff --git a/Src/DasherCore/ClickFilter.cpp b/Src/DasherCore/ClickFilter.cpp
index 7e10905..f359299 100644
--- a/Src/DasherCore/ClickFilter.cpp
+++ b/Src/DasherCore/ClickFilter.cpp
@@ -80,7 +80,7 @@ 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(Time);
+  return pModel->NextScheduledStep();
 }
 
 void CClickFilter::KeyDown(int iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CUserLogBase *pUserLog, bool bPos, int iX, int iY) {
diff --git a/Src/DasherCore/DashIntfScreenMsgs.cpp b/Src/DasherCore/DashIntfScreenMsgs.cpp
index 125f928..00d35ca 100644
--- a/Src/DasherCore/DashIntfScreenMsgs.cpp
+++ b/Src/DasherCore/DashIntfScreenMsgs.cpp
@@ -81,18 +81,24 @@ void CDashIntfScreenMsgs::ChangeScreen(CDasherScreen *pNewScreen) {
   }
 }
 
-void CDashIntfScreenMsgs::Unpause(unsigned long lTime) {
-  if (!GetBoolParameter(BP_DASHER_PAUSED)) return;
-  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 return; //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.
+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.
+        SetBoolParameter(BP_DASHER_PAUSED,true);
+        return;
+      }
+    }
   }
-  CDasherInterfaceBase::Unpause(lTime);
 }
 
 CGameModule *CDashIntfScreenMsgs::CreateGameModule(CDasherView *pView, CDasherModel *pModel) {
diff --git a/Src/DasherCore/DashIntfScreenMsgs.h b/Src/DasherCore/DashIntfScreenMsgs.h
index 1b7b76c..bd7d6d5 100644
--- a/Src/DasherCore/DashIntfScreenMsgs.h
+++ b/Src/DasherCore/DashIntfScreenMsgs.h
@@ -55,8 +55,9 @@ public:
   ///Override to re-MakeLabel any messages.
   void ChangeScreen(CDasherScreen *pNewScreen);
   
-  ///Override to clear any modal messages currently being displayed before resuming.
-  void Unpause(unsigned long lTime);
+  ///Listen for BP_DASHER_PAUSED being cleared to flush any modal messages that
+  /// have been displayed before resuming.
+  void HandleEvent(int iParameter);
   
   ///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 fe70d89..47a8339 100644
--- a/Src/DasherCore/DasherButtons.cpp
+++ b/Src/DasherCore/DasherButtons.cpp
@@ -95,7 +95,7 @@ bool CDasherButtons::Timer(unsigned long Time, CDasherView *pView, CDasherInput
   pInput->GetDasherCoords(iDasherX, iDasherY, pView);
   // ----
 
-  return pModel->NextScheduledStep(Time);
+  return pModel->NextScheduledStep();
 }
 
 void CDasherButtons::NewDrawGoTo(CDasherView *pView, myint iDasherMin, myint iDasherMax, bool bActive) {
diff --git a/Src/DasherCore/DasherInterfaceBase.cpp b/Src/DasherCore/DasherInterfaceBase.cpp
index c6a2d3d..ff4b980 100644
--- a/Src/DasherCore/DasherInterfaceBase.cpp
+++ b/Src/DasherCore/DasherInterfaceBase.cpp
@@ -86,7 +86,7 @@ static char THIS_FILE[] = __FILE__;
 #endif
 #endif
 
-CDasherInterfaceBase::CDasherInterfaceBase(CSettingsStore *pSettingsStore) : CSettingsUser(pSettingsStore), m_pSettingsStore(pSettingsStore), m_pLockLabel(NULL) {
+CDasherInterfaceBase::CDasherInterfaceBase(CSettingsStore *pSettingsStore) : CSettingsUser(pSettingsStore), m_pSettingsStore(pSettingsStore), m_pFramerate(new CFrameRate(this)), m_pLockLabel(NULL) {
   
   pSettingsStore->Register(this, true);
   
@@ -127,8 +127,8 @@ void CDasherInterfaceBase::Realize(unsigned long ulTime) {
   srand(ulTime);
   
   //create the model... (no nodes just yet)
-  m_pDasherModel = new CDasherModel(this, this);
-
+  m_pDasherModel = new CDasherModel(this);
+  
   SetupUI();
   SetupPaths();
 
@@ -207,6 +207,7 @@ CDasherInterfaceBase::~CDasherInterfaceBase() {
   }
 #endif
 
+  delete m_pFramerate;
 
   for (std::vector<CActionButton *>::iterator it=m_vLeftButtons.begin(); it != m_vLeftButtons.end(); ++it)
     delete *it;
@@ -331,7 +332,6 @@ void CDasherInterfaceBase::HandleEvent(int iParameter) {
     // This may move the canvas around a bit, but at least manages to keep/reuse the
     // existing AlphabetManager, NCManager, etc. objects...
     SetOffset(m_pDasherModel->GetOffset(), true);
-    ScheduleRedraw();
     break;      
   default:
     break;
@@ -418,6 +418,7 @@ void CDasherInterfaceBase::CreateNCManager() {
     // this will be a sensible default of 0 if no nodes previously existed).
     // This deletes the old tree of nodes...
     m_pDasherModel->SetOffset(m_pDasherModel->GetOffset(), m_pNCManager->GetAlphabetManager(), m_pDasherView, true);
+    ScheduleRedraw();
   } //else, if there is no screen, the model should not contain any nodes from the old NCManager. (Assert, somehow?)
 
   //...so now we can delete the old manager
@@ -477,20 +478,6 @@ void CDasherInterfaceBase::Stop() {
   }
 }
 
-void CDasherInterfaceBase::Unpause(unsigned long Time) {
-  if (!GetBoolParameter(BP_DASHER_PAUSED)) return; //already running, no need to do anything
-  
-  SetBoolParameter(BP_DASHER_PAUSED, false);
-
-  if(m_pDasherModel != 0)
-    m_pDasherModel->Reset_framerate(Time);
-
-#ifndef _WIN32_WCE
-  if (m_pUserLog != NULL)
-    m_pUserLog->StartWriting();
-#endif
-}
-
 void CDasherInterfaceBase::CreateInput() {
   if(m_pInput) {
     m_pInput->Deactivate();
@@ -569,11 +556,6 @@ void CDasherInterfaceBase::NewFrame(unsigned long iTime, bool bForceRedraw) {
         // (previously DashIntf gathered the info, and then passed it to the logger here).
         m_pUserLog->FrameEnded();
       }
-
-      // This just passes the time through to the framerate tracker, so we
-      // know how often new frames are being drawn.
-      if(m_pDasherModel != 0)
-        m_pDasherModel->RecordFrame(iTime);
     }
     if (FinishRender(iTime)) bBlit = true;
     if (bBlit) m_DasherScreen->Display();
@@ -661,6 +643,7 @@ void CDasherInterfaceBase::ChangeScreen(CDasherScreen *NewScreen) {
     m_pNCManager->ChangeScreen(m_DasherScreen);
     if (m_pDasherModel)
       m_pDasherModel->SetOffset(m_pDasherModel->GetOffset(), m_pNCManager->GetAlphabetManager(), m_pDasherView, true);
+      //Redraw already scheduled by ChangeView / ScreenResized
   }
 }
 
@@ -685,8 +668,8 @@ void CDasherInterfaceBase::ChangeView() {
     delete m_pDasherView;
 
     m_pDasherView = pNewView;
-
   }
+  ScheduleRedraw();
 }
 
 double CDasherInterfaceBase::GetCurCPM() {
@@ -801,10 +784,10 @@ void CDasherInterfaceBase::SetDefaultInputMethod(CInputFilter *pModule) {
 }
 
 void CDasherInterfaceBase::CreateModules() {
-  CInputFilter *defFil = new CDefaultFilter(this, this, 3, _("Normal Control"));
+  CInputFilter *defFil = new CDefaultFilter(this, this, m_pFramerate, 3, _("Normal Control"));
   RegisterModule(defFil);
   SetDefaultInputMethod(defFil);
-  RegisterModule(new COneDimensionalFilter(this, this));
+  RegisterModule(new COneDimensionalFilter(this, this, m_pFramerate));
 #ifndef _WIN32_WCE
   RegisterModule(new CClickFilter(this, this));
 #else
@@ -813,16 +796,16 @@ void CDasherInterfaceBase::CreateModules() {
   );
 #endif
   RegisterModule(new COneButtonFilter(this, this));
-  RegisterModule(new COneButtonDynamicFilter(this, this));
-  RegisterModule(new CTwoButtonDynamicFilter(this, this));
-  RegisterModule(new CTwoPushDynamicFilter(this, this));
+  RegisterModule(new COneButtonDynamicFilter(this, this, m_pFramerate));
+  RegisterModule(new CTwoButtonDynamicFilter(this, this, m_pFramerate));
+  RegisterModule(new CTwoPushDynamicFilter(this, this, m_pFramerate));
   // TODO: specialist factory for button mode
   RegisterModule(new CButtonMode(this, this, true, 8, _("Menu Mode")));
   RegisterModule(new CButtonMode(this, this, false,10, _("Direct Mode")));
   //  RegisterModule(new CDasherButtons(this, this, 4, 0, false,11, "Buttons 3"));
   RegisterModule(new CAlternatingDirectMode(this, this));
   RegisterModule(new CCompassMode(this, this));
-  RegisterModule(new CStylusFilter(this, this, 15, _("Stylus Control")));
+  RegisterModule(new CStylusFilter(this, this, m_pFramerate));
 }
 
 void CDasherInterfaceBase::GetPermittedValues(int iParameter, std::vector<std::string> &vList) {
@@ -948,6 +931,8 @@ void CDasherInterfaceBase::SetOffset(int iOffset, bool bForce) {
   for (set<TextAction *>::iterator it = m_vTextActions.begin(); it!=m_vTextActions.end(); it++) {
     (*it)->NotifyOffset(iOffset);
   }
+  
+  ScheduleRedraw();
 }
 
 // Returns 0 on success, an error string on failure.
diff --git a/Src/DasherCore/DasherInterfaceBase.h b/Src/DasherCore/DasherInterfaceBase.h
index 8c89da0..b9845a7 100644
--- a/Src/DasherCore/DasherInterfaceBase.h
+++ b/Src/DasherCore/DasherInterfaceBase.h
@@ -41,7 +41,7 @@
 #include "InputFilter.h"
 #include "ModuleManager.h"
 #include "ControlManager.h"
-
+#include "FrameRate.h"
 #include <set>
 #include <algorithm>
 
@@ -204,11 +204,6 @@ public:
   /// (But does nothing if BP_DASHER_PAUSED is not set)
   virtual void Stop();
 
-  /// Unpause Dasher. Clears BP_DASHER_PAUSED.
-  /// (But does nothing if BP_DASHER_PAUSED is currently set).
-  /// \param Time Time in ms, used to keep a constant frame rate
-  virtual void Unpause(unsigned long Time);
-
   ///Whether any actions are currently setup to occur when Dasher 'stop's.
   /// Default is to return TRUE iff we support speech and BP_SPEAK_ON_STOP is set,
   /// and/or if we support clipboard and BP_COPY_ALL_ON_STOP is set; subclasses may
@@ -423,15 +418,8 @@ protected:
 
   CDasherScreen *m_DasherScreen;
 
-  /// Asynchronous (non-modal) messages to be displayed to the user, longest-ago
-  /// at the front, along with the timestamp of the frame at which each was first
-  /// displayed to the user - 0 if not yet displayed.
-  std::deque<pair<CDasherScreen::Label*, unsigned long> > m_dqAsyncMessages;
-  
-  /// Modal messages being or waiting to be displayed to the user, longest-ago
-  /// at the front, along with the timestamp when each was first displayed to the
-  /// user (0 if not yet displayed).
-  std::deque<pair<CDasherScreen::Label*, unsigned long> > m_dqModalMessages;
+  ///Framerate monitor; created in constructor, req'd for DynamicFilter subclasses
+  CFrameRate * const m_pFramerate;
   
  private:
   
diff --git a/Src/DasherCore/DasherModel.cpp b/Src/DasherCore/DasherModel.cpp
index c1cdd76..fab05df 100644
--- a/Src/DasherCore/DasherModel.cpp
+++ b/Src/DasherCore/DasherModel.cpp
@@ -30,7 +30,6 @@
 #include "Parameters.h"
 
 #include "Event.h"
-#include "DasherInterfaceBase.h"
 #include "NodeCreationManager.h"
 #include "AlphabetManager.h"
 
@@ -51,12 +50,9 @@ static char THIS_FILE[] = __FILE__;
 
 // CDasherModel
 
-CDasherModel::CDasherModel(CSettingsUser *pCreateFrom,
-			   CDasherInterfaceBase *pDasherInterface)
-  : CFrameRate(pCreateFrom), m_pDasherInterface(pDasherInterface) {
-
-  DASHER_ASSERT(m_pDasherInterface != NULL);
-
+CDasherModel::CDasherModel(CSettingsUser *pCreateFrom)
+: CSettingsUserObserver(pCreateFrom) {
+  
   m_pLastOutput = m_Root = NULL;
 
   m_Rootmin = 0;
@@ -94,8 +90,6 @@ CDasherModel::~CDasherModel() {
 }
 
 void CDasherModel::HandleEvent(int iParameter) {
-  CFrameRate::HandleEvent(iParameter);
-
   switch (iParameter) {
   case BP_SMOOTH_OFFSET:
     if (!GetBoolParameter(BP_SMOOTH_OFFSET))
@@ -103,11 +97,6 @@ void CDasherModel::HandleEvent(int iParameter) {
       // in progress at it's current point
       AbortOffset();
     break;
-  case BP_DASHER_PAUSED:
-    if(GetBoolParameter(BP_SLOW_START))
-      m_iStartTime = 0;
-    //else, leave m_iStartTime as is - will result in no slow start
-    break;
   default:
     break;
   }
@@ -269,31 +258,14 @@ void CDasherModel::SetOffset(int iOffset, CAlphabetManager *pMgr, CDasherView *p
   m_Rootmax = GetLongParameter(LP_MAX_Y) / 2 + iWidth / 2;
 
   m_iDisplayOffset = 0;
-
-  // TODO: See if this is better positioned elsewhere
-  m_pDasherInterface->ScheduleRedraw();
 }
 
 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, unsigned long iTime) {
+void CDasherModel::Get_new_root_coords(dasherint X, dasherint Y, dasherint &r1, dasherint &r2, int iSteps, dasherint iMinSize) {
   DASHER_ASSERT(m_Root != NULL);
-
-  int iSteps = Steps();
-
-  double dFactor(1.0);
-
-  if(GetBoolParameter(BP_SLOW_START)) {
-    if(m_iStartTime == 0)
-      m_iStartTime = iTime;
-    if((iTime - m_iStartTime) < GetLongParameter(LP_SLOW_START_TIME))
-      dFactor = 0.1 * (1 + 9 * ((iTime - m_iStartTime) / static_cast<double>(GetLongParameter(LP_SLOW_START_TIME))));
-  }
-
-  iSteps = static_cast<int>(iSteps / dFactor);
-
   // Avoid X == 0, as this corresponds to infinite zoom
   if (X <= 0) X = 1;
 
@@ -302,15 +274,11 @@ void CDasherModel::Get_new_root_coords(dasherint X, dasherint Y, dasherint &r1,
   if (X > iMaxX) X = iMaxX;
 
   // Mouse coords X, Y
-  // new root{min,max} r1,r2, old root{min,max} R1,R2
-  const dasherint R1 = m_Rootmin;
-  const dasherint R2 = m_Rootmax;
   // const dasherint Y1 = 0;
   dasherint Y2(GetLongParameter(LP_MAX_Y));
   dasherint iOX(GetLongParameter(LP_OX));
   dasherint iOY(GetLongParameter(LP_OY));
 
-
   // 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
@@ -326,28 +294,32 @@ void CDasherModel::Get_new_root_coords(dasherint X, dasherint Y, dasherint &r1,
 
   // 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;
 
-  dasherint newy1, newy2, denom;
-
-  denom = Y2 + (iSteps - 1) * (y2 - y1);
-  newy1 = y1 * Y2 / denom;
-  newy2 = ((y2 * iSteps - y1 * (iSteps - 1)) * Y2) / denom;
-
-  y1 = newy1;
-  y2 = newy2;
+    y1 = newy1;
+    y2 = newy2;
+  }
 
   // Calculate the minimum size of the viewport corresponding to the
   // maximum zoom.
 
-  dasherint iMinSize(MinSize(Y2, dFactor));
-
   if((y2 - y1) < iMinSize) {
-    newy1 = y1 * (Y2 - iMinSize) / (Y2 - (y2 - y1));
-    newy2 = newy1 + 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)
@@ -381,7 +353,7 @@ void CDasherModel::Get_new_root_coords(dasherint X, dasherint Y, dasherint &r1,
   r2 = ((R2 - C) * Y2) / (y2 - y1) + C;
 }
 
-bool CDasherModel::NextScheduledStep(unsigned long iTime)
+bool CDasherModel::NextScheduledStep()
 {
   DASHER_ASSERT (!GetBoolParameter(BP_DASHER_PAUSED) || m_deGotoQueue.size()==0);
   if (m_deGotoQueue.size() == 0) return false;
@@ -390,22 +362,22 @@ bool CDasherModel::NextScheduledStep(unsigned long iTime)
   iNewMax = m_deGotoQueue.front().iN2;
   m_deGotoQueue.pop_front();
 
-  UpdateBounds(iNewMin, iNewMax, iTime);
+  UpdateBounds(iNewMin, iNewMax);
   if (m_deGotoQueue.size() == 0) SetBoolParameter(BP_DASHER_PAUSED, true);
   return true;
 }
 
-void CDasherModel::OneStepTowards(myint miMousex, myint miMousey, unsigned long iTime) {
+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, iTime);
-
-  UpdateBounds(iNewMin, iNewMax, iTime);
+  Get_new_root_coords(miMousex, miMousey, iNewMin, iNewMax, iSteps, iMinSize);
+  
+  UpdateBounds(iNewMin, iNewMax);
 }
 
-void CDasherModel::UpdateBounds(myint newRootmin, myint newRootmax, unsigned long iTime) {
+void CDasherModel::UpdateBounds(myint newRootmin, myint newRootmax) {
 
   m_dTotalNats += log((newRootmax - newRootmin) / static_cast<double>(m_Rootmax - m_Rootmin));
 
@@ -472,10 +444,6 @@ void CDasherModel::UpdateBounds(myint newRootmin, myint newRootmax, unsigned lon
   //outside the root node (when we can't generate an older root).
 }
 
-void CDasherModel::RecordFrame(unsigned long Time) {
-  CFrameRate::RecordFrame(Time);
-}
-
 void CDasherModel::OutputTo(CDasherNode *pNewNode) {
   //first, recurse back up to last seen node (must be processed ancestor-first)
   if (pNewNode && !pNewNode->GetFlag(NF_SEEN)) {
@@ -612,7 +580,7 @@ void CDasherModel::ScheduleZoom(long time, dasherint y1, dasherint y2) {
       m_deGotoQueue.push_back(sNewItem);
   }
   //steps having been scheduled, we're gonna start moving accordingly...
-  m_pDasherInterface->Unpause(time);
+  SetBoolParameter(BP_DASHER_PAUSED, false);
 }
 
 void CDasherModel::ClearScheduledSteps() {
diff --git a/Src/DasherCore/DasherModel.h b/Src/DasherCore/DasherModel.h
index e45df0d..23d2038 100644
--- a/Src/DasherCore/DasherModel.h
+++ b/Src/DasherCore/DasherModel.h
@@ -33,13 +33,12 @@
 #include "../Common/NoClones.h"
 #include "DasherNode.h"
 #include "DasherTypes.h"
-#include "FrameRate.h"
-#include "NodeCreationManager.h"
 #include "ExpansionPolicy.h"
+#include "SettingsStore.h"
+#include "AlphabetManager.h"
 
 namespace Dasher {
   class CDasherModel;
-  class CDasherInterfaceBase;
   class CDasherView;
 
   struct SLockData;
@@ -50,23 +49,27 @@ namespace Dasher {
 
 /// \brief Dasher 'world' data structures and dynamics.
 ///
-/// The DasherModel represents the current state of Dasher
-/// It contains a tree of DasherNodes
-///             knows the current viewpoint
-///             knows how to evolve the viewpoint
+/// The DasherModel implements arithmetic coding for Dasher.
+/// It contains a tree of DasherNodes and the current viewpoint, 
+/// and evolves the tree by expanding leaves (somewhat in response to DasherView) and
+/// (eventually) deleting ancestors/parents. It has two methods for moving around the tree:
+/// OneStepTowards implements steady motion towards a given point, one frame at a time;
+/// ScheduleZoom sets up movement to arrive at a particular point in a number of frames.
+/// Clients are responsible for monitoring framerate (if required) and using such
+/// information to decide how far to tell the DasherModel to move.
 ///
-/// It does not care what the nodes in the tree are or mean, tho the model does handle
+/// DasherModel does not care what the nodes in the tree are or mean, tho it does handle
 /// calling CDasherNode::Output() / Undo() on nodes falling under/leaving the crosshair
 /// (However, determining which nodes are under the crosshair, is done by the CDasherView).
 ///
 /// The class is Observable in that it broadcasts a pointer to a CDasherNode when the node's
 /// children are created.
-class Dasher::CDasherModel:public Dasher::CFrameRate, public Observable<CDasherNode*>, private NoClones
+class Dasher::CDasherModel: private CSettingsUserObserver, public Observable<CDasherNode*>, private NoClones
 {
  public:
   /// Constructs a new CDasherModel. Note, must be followed by a call to
   /// SetOffset() before the model can be used.
-  CDasherModel(CSettingsUser *pCreateFrom, CDasherInterfaceBase *pDashIface);
+  CDasherModel(CSettingsUser *pCreateFrom);
   ~CDasherModel();
 
   ///
@@ -82,13 +85,7 @@ class Dasher::CDasherModel:public Dasher::CFrameRate, public Observable<CDasherN
   ///
   /// Update the root location with *one step* towards the specified
   /// co-ordinates - used by timer callbacks (for non-button modes)
-  void OneStepTowards(myint, myint, unsigned long iTime);
-
-  ///
-  /// Notify the framerate class that a new frame has occurred
-  /// Called from CDasherInterfaceBase::NewFrame
-  ///
-  void RecordFrame(unsigned long Time);
+  void OneStepTowards(myint, myint, int iSteps, dasherint iMinSize);
 
   ///
   /// Apply an offset to the 'target' coordinates - implements the jumps in
@@ -156,7 +153,7 @@ class Dasher::CDasherModel:public Dasher::CFrameRate, public Observable<CDasherN
   /// still-in-progress zoom scheduled by ScheduleZoom (does nothing
   /// if no steps remaining / no zoom scheduled).
   ///
-  bool NextScheduledStep(unsigned long iTime);
+  bool NextScheduledStep();
 
   /// @}
 
@@ -194,7 +191,7 @@ class Dasher::CDasherModel:public Dasher::CFrameRate, public Observable<CDasherN
 
   /// Common portion of OneStepTowards / NextScheduledStep, taking
   /// bounds for the root node in the next frame.
-  void UpdateBounds(myint iNewMin, myint iNewMax, unsigned long iTime);
+  void UpdateBounds(myint iNewMin, myint iNewMax);
 
   /// Struct representing intermediate stages in the goto queue
   ///
@@ -203,9 +200,6 @@ class Dasher::CDasherModel:public Dasher::CFrameRate, public Observable<CDasherN
     myint iN2;
   };
 
-  // Pointers to various auxilliary objects
-  CDasherInterfaceBase *m_pDasherInterface;
-
   // The root of the Dasher tree
   CDasherNode *m_Root;
 
@@ -247,31 +241,10 @@ class Dasher::CDasherModel:public Dasher::CFrameRate, public Observable<CDasherN
   // require conversion.
   // TODO: Need to rethink this at some point.
   bool m_bRequireConversion;
-  
-  /**
-   * The string a user must type if game mode is active.
-   */
-  std::string m_strGameTarget;
-
-  // Model status...
-
-
-  // Time at which the model was started (ie last unpaused, used for gradual speed up)
-  // TODO: Implementation is very hacky at the moment
-  // TODO: Duplicates functionality previously implemented elsewhere...
-  //   ...ACL 22/5/09 does it? There was an even hackier implementation, of resetting the
-  //   framerate, used for control mode (ControlManager.cpp), but that's all I could find
-  //   - and that seemed even worse, so I've removed it in favour of this here....?
-  unsigned long m_iStartTime;
-
-  // Debug/performance information...
 
   // Information entered so far in this model
   double m_dTotalNats;
 
-  ///
-  /// CDasherModel::Get_new_root_coords( myint Mousex,myint Mousey )
-  ///
   /// Calculate the new co-ordinates for the root node after a single
   /// update step. For further information, see Doc/geometry.tex.
   ///
@@ -279,9 +252,10 @@ class Dasher::CDasherModel:public Dasher::CFrameRate, public Observable<CDasherN
   /// \param mousey y mouse co-ordinate measured top to bottom.
   /// \param iNewMin New root min
   /// \param iNewMax New root max
-  /// \param iTime Current timestamp
-  ///
-  void Get_new_root_coords(myint mousex, myint mousey, myint &iNewMin, myint &iNewMax, unsigned long iTime);
+  /// \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 4fb1f79..7194a92 100644
--- a/Src/DasherCore/DefaultFilter.cpp
+++ b/Src/DasherCore/DefaultFilter.cpp
@@ -24,8 +24,8 @@ bool CDefaultFilter::GetSettings(SModuleSettings **sets, int *iCount) {
   return true;
 }
 
-CDefaultFilter::CDefaultFilter(CSettingsUser *pCreateFrom, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName)
-  : CInputFilter(pInterface, iID, szName), CSettingsUserObserver(pCreateFrom) {
+CDefaultFilter::CDefaultFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID, const char *szName)
+  : CDynamicFilter(pCreator, pInterface, pFramerate, iID, szName), CSettingsObserver(pCreator) {
   m_pStartHandler = 0;
   m_pAutoSpeedControl = new CAutoSpeedControl(this, pInterface);
 
@@ -127,7 +127,7 @@ bool CDefaultFilter::Timer(unsigned long Time, CDasherView *pView, CDasherInput
       }
     }
 
-    m_pDasherModel->OneStepTowards(m_iLastX,m_iLastY, Time);
+    OneStepTowards(m_pDasherModel, m_iLastX,m_iLastY, Time, SlowStartSpeedMul(Time));
     bDidSomething = true;
 
     if (GetLongParameter(LP_BOOSTFACTOR)==100)
@@ -147,7 +147,7 @@ void CDefaultFilter::KeyDown(int iTime, int iId, CDasherView *pDasherView, CDash
     // 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))
-	m_pInterface->Unpause(iTime);
+        Unpause(iTime);
       else
 	m_pInterface->Stop();
     }
@@ -155,7 +155,7 @@ void CDefaultFilter::KeyDown(int iTime, int iId, CDasherView *pDasherView, CDash
   case 100: // Start on mouse
     if(GetBoolParameter(BP_START_MOUSE)) {
       if(GetBoolParameter(BP_DASHER_PAUSED))
-	m_pInterface->Unpause(iTime);
+        Unpause(iTime);
       else
 	m_pInterface->Stop();
     }
@@ -191,9 +191,9 @@ void CDefaultFilter::Deactivate() {
 
 CStartHandler *CDefaultFilter::MakeStartHandler() {
   if(GetBoolParameter(BP_CIRCLE_START))
-    return new CCircleStartHandler(this, m_pInterface);
+    return new CCircleStartHandler(this);
   if(GetBoolParameter(BP_MOUSEPOS_MODE))
-    return new CTwoBoxStartHandler(this, m_pInterface);
+    return new CTwoBoxStartHandler(this);
   return NULL;
 }
 
diff --git a/Src/DasherCore/DefaultFilter.h b/Src/DasherCore/DefaultFilter.h
index 53e06f1..6b9536f 100644
--- a/Src/DasherCore/DefaultFilter.h
+++ b/Src/DasherCore/DefaultFilter.h
@@ -1,19 +1,19 @@
 #ifndef __DEFAULT_FILTER_H__
 #define __DEFAULT_FILTER_H__
 
-#include "InputFilter.h"
+#include "DynamicFilter.h"
 #include "AutoSpeedControl.h"
 #include "StartHandler.h"
 
 namespace Dasher {
 /// \ingroup InputFilter
 /// @{
-class CDefaultFilter : public CInputFilter, public CSettingsUserObserver {
+class CDefaultFilter : public CDynamicFilter, public CSettingsObserver {
  public:
-  CDefaultFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName);
+  CDefaultFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID, const char *szName);
   ~CDefaultFilter();
-  virtual bool supportsPause() {return true;}
 
+  /// Responds to changes in BP_START_MOUSE / BP_MOUSEPOS_MODE to create StartHandler
   virtual void HandleEvent(int iParameter);
 
   virtual bool DecorateView(CDasherView *pView, CDasherInput *pInput);
@@ -32,6 +32,8 @@ class CDefaultFilter : public CInputFilter, public CSettingsUserObserver {
   myint m_iLastX, m_iLastY;
   bool m_bGotMouseCoords;
 private:
+  friend class CCircleStartHandler;
+  friend class CTwoBoxStartHandler;
   CAutoSpeedControl *m_pAutoSpeedControl;
   myint m_iSum;
   CStartHandler *m_pStartHandler;
diff --git a/Src/DasherCore/DynamicButtons.cpp b/Src/DasherCore/DynamicButtons.cpp
new file mode 100644
index 0000000..06dcf37
--- /dev/null
+++ b/Src/DasherCore/DynamicButtons.cpp
@@ -0,0 +1,156 @@
+// DynamicButtons.cpp
+//
+// Copyright (c) 2007 The Dasher Team
+//
+// This file is part of Dasher.
+//
+// Dasher is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// Dasher is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Dasher; if not, write to the Free Software 
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+#include "DasherInterfaceBase.h"
+#include "DynamicButtons.h"
+
+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_bDecorationChanged = true;
+  m_bKeyDown = false;
+  pause();
+}
+
+bool 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))) {
+    Event(iTime, m_iHeldId, 1, m_pDasherModel, m_pUserLog);
+    m_bKeyHandled = true;
+    //return true; //ACL although that's what old DynamicButtons did, surely we should progress normally?
+  }
+  if (isPaused()) return false;
+  if (isReversing()) {
+    OneStepTowards(m_pDasherModel, 41943,2048, iTime, SlowStartSpeedMul(iTime));
+    return true;
+  }
+  //moving forwards. Check auto speed control...
+  unsigned int uTime = static_cast<unsigned int>(iTime);
+  if (GetBoolParameter(BP_AUTO_SPEEDCONTROL) && m_uSpeedControlTime < uTime)
+  {
+	  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 = uTime + 1000*GetLongParameter(LP_DYNAMIC_SPEED_FREQ);
+  }
+  return TimerImpl(iTime, pDasherView, m_pDasherModel, pol);
+}
+
+void CDynamicButtons::KeyDown(int iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CUserLogBase *pUserLog) {
+  
+  m_pUserLog = pUserLog;
+  
+  if(((iId == 0) || (iId == 1) || (iId == 100)) && !GetBoolParameter(BP_BACKOFF_BUTTON))
+    return;
+
+  if(m_bKeyDown)
+    return;
+
+  // Pass the basic key down event to the handler
+  Event(iTime, iId, 0, pModel, pUserLog);
+    
+  // Store the key down time so that long presses can be determined
+  // TODO: This is going to cause problems if multiple buttons are
+  // held down at once
+  m_iKeyDownTime = iTime;
+
+  m_iHeldId = iId;
+  m_bKeyDown = true;
+  m_bKeyHandled = false;
+}
+
+void CDynamicButtons::KeyUp(int iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel) {
+  if (iId == m_iHeldId) m_bKeyDown = false;
+}
+
+void CDynamicButtons::Event(int iTime, int iButton, int iType, CDasherModel *pModel, CUserLogBase *pUserLog) {
+  // Types known at this point in inheritance hierarchy:
+  // 0 = ordinary click
+  // 1 = long click
+  
+  // TODO: Check that state diagram implemented here is what we
+  // decided upon
+
+  // What happens next depends on the state:
+  if (isPaused()) {
+    //Any button causes a restart
+    if(pUserLog)
+      pUserLog->KeyDown(iButton, iType, 1);
+    run();
+    Unpause(iTime);
+  } else if (isReversing()) {
+    //Any button pauses
+    if(pUserLog)
+      pUserLog->KeyDown(iButton, iType, 2);
+    
+    m_pInterface->Stop();
+    //change in BP_DASHER_PAUSED calls pause().
+  } else {
+    //running; examine event/button-press type
+    switch(iType) {
+    case 0: //single press
+      if((iButton == 0) || (iButton == 100)) {
+        //dedicated pause button
+        if(pUserLog)
+          pUserLog->KeyDown(iButton, iType, 2);
+        m_pInterface->Stop();
+        break;
+      }
+      else if(iButton == 1) {
+        //dedicated reverse button
+        if(pUserLog)
+          pUserLog->KeyDown(iButton, iType, 6);
+        reverse();
+        break;
+      }
+      //else - any non-special button - fall through
+    default: //or, Any special kind of event - long, double, triple, ... 
+      ActionButton(iTime, iButton, iType, pModel, pUserLog);
+    }
+  }
+}
+
+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::reverse()
+{
+  m_iState = 1;
+  if (GetBoolParameter(BP_AUTO_SPEEDCONTROL)) {
+    //treat reversing as a sign of distress --> slow down!
+    SetLongParameter(LP_MAX_BITRATE, GetLongParameter(LP_MAX_BITRATE) *
+					 (1.0 - GetLongParameter(LP_DYNAMIC_SPEED_DEC)/100.0));
+  }
+}
+
+void CDynamicButtons::run()
+{
+  if (m_iState<2) //wasn't running previously
+    m_uSpeedControlTime = 0; //will be set in Timer()
+  m_iState = 2;
+}
+
diff --git a/Src/DasherCore/DynamicButtons.h b/Src/DasherCore/DynamicButtons.h
new file mode 100644
index 0000000..9a4ce70
--- /dev/null
+++ b/Src/DasherCore/DynamicButtons.h
@@ -0,0 +1,67 @@
+// DynamicFilter.h
+//
+// Copyright (c) 2007 The Dasher Team
+//
+// This file is part of Dasher.
+//
+// Dasher is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// Dasher is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Dasher; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+#ifndef __DynamicButtons_h__
+#define __DynamicButtons_h__
+
+#include "DynamicFilter.h"
+
+/// \ingroup InputFilter
+/// @{
+namespace Dasher {
+///filter with three states: paused, reversing, running. Hold any button down to reverse.
+class CDynamicButtons : public CDynamicFilter, public CSettingsObserver {
+ 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 KeyDown(int iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CUserLogBase *pUserLog);
+  virtual void KeyUp(int 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:
+  virtual void ActionButton(int iTime, int iButton, int iType, CDasherModel *pModel, CUserLogBase *pUserLog) = 0;
+  virtual void Event(int iTime, int iButton, int iType, CDasherModel *pModel, CUserLogBase *pUserLog);
+
+  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;}
+  virtual void pause() {m_iState = 0;}
+  virtual void reverse();
+  virtual void run();
+
+  virtual bool TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) = 0;
+
+  private:
+    int m_iState; // 0 = paused, 1 = reversing, >=2 = running (extensible by subclasses)
+    int m_iHeldId;
+    int m_iKeyDownTime;
+    unsigned int m_uSpeedControlTime;
+
+    CUserLogBase *m_pUserLog;
+};
+}
+#endif
diff --git a/Src/DasherCore/DynamicFilter.cpp b/Src/DasherCore/DynamicFilter.cpp
index 0de03c8..8f8b0ef 100644
--- a/Src/DasherCore/DynamicFilter.cpp
+++ b/Src/DasherCore/DynamicFilter.cpp
@@ -23,134 +23,35 @@
 
 using namespace Dasher;
 
-CDynamicFilter::CDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName)
-  : CInputFilter(pInterface, iID, szName), CSettingsUserObserver(pCreator) {
-  m_bDecorationChanged = true;
-  m_bKeyDown = false;
-  pause();
+CDynamicFilter::CDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID, const char *szName) : CInputFilter(pInterface, iID, szName), CSettingsUser(pCreator), m_pFramerate(pFramerate) {
 }
 
-bool CDynamicFilter::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))) {
-    Event(iTime, m_iHeldId, 1, m_pDasherModel, m_pUserLog);
-    m_bKeyHandled = true;
-    //return true; //ACL although that's what old DynamicFilter did, surely we should progress normally?
-  }
-  if (isPaused()) return false;
-  if (isReversing()) {
-    m_pDasherModel->OneStepTowards(41943,2048, iTime);
-    return true;
-  }
-  //moving forwards. Check auto speed control...
-  unsigned int uTime = static_cast<unsigned int>(iTime);
-  if (GetBoolParameter(BP_AUTO_SPEEDCONTROL) && m_uSpeedControlTime < uTime)
-  {
-	  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 = uTime + 1000*GetLongParameter(LP_DYNAMIC_SPEED_FREQ);
-  }
-  return TimerImpl(iTime, pDasherView, m_pDasherModel, pol);
-}
+bool CDynamicFilter::OneStepTowards(CDasherModel *pModel, myint y1, myint y2, unsigned long iTime, double dSpeedMul) {
+  if (dSpeedMul<=0.0) return false; //going nowhere
+  m_pFramerate->RecordFrame(iTime); //Hmmm, even if we don't do anything else?
 
-void CDynamicFilter::KeyDown(int iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CUserLogBase *pUserLog) {
-  
-  m_pUserLog = pUserLog;
+  double dRXMax = m_pFramerate->GetMaxZoomFactor();
+  // 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);
   
-  if(((iId == 0) || (iId == 1) || (iId == 100)) && !GetBoolParameter(BP_BACKOFF_BUTTON))
-    return;
-
-  if(m_bKeyDown)
-    return;
-
-  // Pass the basic key down event to the handler
-  Event(iTime, iId, 0, pModel, pUserLog);
-    
-  // Store the key down time so that long presses can be determined
-  // TODO: This is going to cause problems if multiple buttons are
-  // held down at once
-  m_iKeyDownTime = iTime;
-
-  m_iHeldId = iId;
-  m_bKeyDown = true;
-  m_bKeyHandled = false;
-}
-
-void CDynamicFilter::KeyUp(int iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel) {
-  if (iId == m_iHeldId) m_bKeyDown = false;
+  pModel->OneStepTowards(y1, y2, static_cast<int>(m_pFramerate->Steps() / dSpeedMul), static_cast<myint>(GetLongParameter(LP_MAX_Y)/dRXMax));
+  return true;
 }
 
-void CDynamicFilter::Event(int iTime, int iButton, int iType, CDasherModel *pModel, CUserLogBase *pUserLog) {
-  // Types known at this point in inheritance hierarchy:
-  // 0 = ordinary click
-  // 1 = long click
-  
-  // TODO: Check that state diagram implemented here is what we
-  // decided upon
-
-  // What happens next depends on the state:
-  if (isPaused()) {
-    //Any button causes a restart
-    if(pUserLog)
-      pUserLog->KeyDown(iButton, iType, 1);
-    run();
-    m_pInterface->Unpause(iTime);
-  } else if (isReversing()) {
-    //Any button pauses
-    if(pUserLog)
-      pUserLog->KeyDown(iButton, iType, 2);
-    
-    m_pInterface->Stop();
-    //change in BP_DASHER_PAUSED calls pause().
-  } else {
-    //running; examine event/button-press type
-    switch(iType) {
-    case 0: //single press
-      if((iButton == 0) || (iButton == 100)) {
-        //dedicated pause button
-        if(pUserLog)
-          pUserLog->KeyDown(iButton, iType, 2);
-        m_pInterface->Stop();
-        break;
-      }
-      else if(iButton == 1) {
-        //dedicated reverse button
-        if(pUserLog)
-          pUserLog->KeyDown(iButton, iType, 6);
-        reverse();
-        break;
-      }
-      //else - any non-special button - fall through
-    default: //or, Any special kind of event - long, double, triple, ... 
-      ActionButton(iTime, iButton, iType, pModel, pUserLog);
-    }
-  }
-}
-
-void CDynamicFilter::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);
+double CDynamicFilter::SlowStartSpeedMul(unsigned long iTime) {
+  if(GetBoolParameter(BP_SLOW_START)) {
+    if ((iTime - m_iStartTime) < GetLongParameter(LP_SLOW_START_TIME))
+      return 0.1 * (1 + 9 * ((iTime - m_iStartTime) / static_cast<double>(GetLongParameter(LP_SLOW_START_TIME))));
   }
+  //no slow start, or finished.
+  return 1.0;
 }
 
-void CDynamicFilter::reverse()
-{
-  m_iState = 1;
-  if (GetBoolParameter(BP_AUTO_SPEEDCONTROL)) {
-    //treat reversing as a sign of distress --> slow down!
-    SetLongParameter(LP_MAX_BITRATE, GetLongParameter(LP_MAX_BITRATE) *
-					 (1.0 - GetLongParameter(LP_DYNAMIC_SPEED_DEC)/100.0));
-  }
-}
+void CDynamicFilter::Unpause(unsigned long Time) {
+  if (!GetBoolParameter(BP_DASHER_PAUSED)) return; //already running, no need to / can't really do anything
+  
+  SetBoolParameter(BP_DASHER_PAUSED, false);
 
-void CDynamicFilter::run()
-{
-  if (m_iState<2) //wasn't running previously
-    m_uSpeedControlTime = 0; //will be set in Timer()
-  m_iState = 2;
+  m_pFramerate->Reset_framerate(Time);
+  m_iStartTime = Time;
 }
-
diff --git a/Src/DasherCore/DynamicFilter.h b/Src/DasherCore/DynamicFilter.h
index 035afbb..33791e9 100644
--- a/Src/DasherCore/DynamicFilter.h
+++ b/Src/DasherCore/DynamicFilter.h
@@ -22,48 +22,36 @@
 #define __DynamicFilter_h__
 
 #include "InputFilter.h"
+#include "SettingsStore.h"
+#include "FrameRate.h"
 
 /// \ingroup InputFilter
 /// @{
 namespace Dasher {
-///filter with three states: paused, reversing, running. Hold any button down to reverse.
-class CDynamicFilter : public CInputFilter, public CSettingsUserObserver {
+///Abstract superclass for filters which produce continuous movement
+/// i.e. steadily towards a target (perhaps moving). These filters
+/// need to monitor the framerate (using CFrameRate) to maintain a steady
+/// speed of movement. Also implements Slow Start following a call to the
+/// Unpause method.
+class CDynamicFilter : public CInputFilter, public CSettingsUser {
  public:
-  CDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName);
+  CDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID, const char *szName);
 
   virtual bool supportsPause() {return true;}
 
-  ///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 KeyDown(int iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CUserLogBase *pUserLog);
-  virtual void KeyUp(int 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:
-  virtual void ActionButton(int iTime, int iButton, int iType, CDasherModel *pModel, CUserLogBase *pUserLog) = 0;
-  virtual void Event(int iTime, int iButton, int iType, CDasherModel *pModel, CUserLogBase *pUserLog);
-
-  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;}
-  virtual void pause() {m_iState = 0;}
-  virtual void reverse();
-  virtual void run();
-
-  virtual bool TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) = 0;
-
-  private:
-    int m_iState; // 0 = paused, 1 = reversing, >=2 = running (extensible by subclasses)
-    int m_iHeldId;
-    int m_iKeyDownTime;
-    unsigned int m_uSpeedControlTime;
-
-    CUserLogBase *m_pUserLog;
+  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).
+  /// \param Time Time in ms, used to keep a constant frame rate and
+  /// initialize slow start.
+  void Unpause(unsigned long iTime);
+ private:
+  CFrameRate *m_pFramerate;
+  //Time at which Unpause() was called, used for Slow Start.
+  unsigned long m_iStartTime;
 };
 }
 #endif
diff --git a/Src/DasherCore/FrameRate.cpp b/Src/DasherCore/FrameRate.cpp
index 50d78ec..7a91457 100644
--- a/Src/DasherCore/FrameRate.cpp
+++ b/Src/DasherCore/FrameRate.cpp
@@ -8,7 +8,7 @@ CFrameRate::CFrameRate(CSettingsUser *pCreator) :
   //Sampling parameters...
   m_iFrames = 0;
   m_iSamples = 1;
-  m_iTime = 0;                  // Hmmm, User must reset framerate before starting.
+  m_iTime = 0;
 
   //try and carry on from where we left off at last run
   HandleEvent(LP_FRAMERATE);
diff --git a/Src/DasherCore/FrameRate.h b/Src/DasherCore/FrameRate.h
index a8ba27d..09cb05f 100644
--- a/Src/DasherCore/FrameRate.h
+++ b/Src/DasherCore/FrameRate.h
@@ -11,9 +11,8 @@
 
 #include <cmath>
 #include "../Common/Common.h"
-#include "Event.h"
-#include "Parameters.h"
 #include "SettingsStore.h"
+#include "DasherModel.h"
 
 namespace Dasher {
 /// \ingroup Model
@@ -28,17 +27,12 @@ public:
   
   virtual void HandleEvent(int iParameter);
 
-  /// Get the minimum size of the target viewport
-  ////// TODO: Eventually fix this so that it uses integer maths internally. 
-
-  // dFactor is a temporary change to the frame rate, allowing for
-  // slow start and the like
-  myint MinSize(myint t, double dFactor = 1.0) const {
-    if(dFactor == 1.0)
-      return static_cast < myint > (t / m_dRXmax);
-    else
-      return static_cast < myint > (t / pow(m_dRXmax, dFactor));
-  };
+  ///The maximum amount by which one frame may zoom in. Used as a hard
+  /// upper-bound on the approximate one-step calculation done in DasherModel
+  /// to ensure we never exceed the set LP_MAX_BITRATE.
+  double GetMaxZoomFactor() {
+    return m_dRXmax;
+  }
 
   int Steps() const {
     return m_iSteps;
@@ -59,6 +53,9 @@ public:
   }
 
   void RecordFrame(unsigned long Time);
+
+  bool OneStepTowards(CDasherModel *pModel, myint y1, myint y2, unsigned long iTime, double dSpeedMul);
+  double SlowStartSpeedMul(unsigned long iTime);
   
 private:
   double m_dFrDecay;            // current frame rate (cache of LP_FRAMERATE/100.0)
diff --git a/Src/DasherCore/Makefile.am b/Src/DasherCore/Makefile.am
index 1be761a..b0137a2 100644
--- a/Src/DasherCore/Makefile.am
+++ b/Src/DasherCore/Makefile.am
@@ -72,8 +72,10 @@ libdashercore_a_SOURCES = \
 		DasherViewSquare.inl \
 		DefaultFilter.cpp \
 		DefaultFilter.h \
-		DynamicFilter.h \
+		DynamicButtons.cpp \
+		DynamicButtons.h \
 		DynamicFilter.cpp \
+		DynamicFilter.h \
 		Event.h \
 		FileLogger.cpp \
 		FileLogger.h \
diff --git a/Src/DasherCore/OneButtonDynamicFilter.cpp b/Src/DasherCore/OneButtonDynamicFilter.cpp
index b636e7f..f4dbbaa 100644
--- a/Src/DasherCore/OneButtonDynamicFilter.cpp
+++ b/Src/DasherCore/OneButtonDynamicFilter.cpp
@@ -40,8 +40,8 @@ static SModuleSettings sSettings[] = {
   {LP_DYNAMIC_SPEED_DEC, T_LONG, 1, 99, 1, 1, _("Percentage by which to decrease speed upon reverse")}
 };
 
-COneButtonDynamicFilter::COneButtonDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface)
-  : CButtonMultiPress(pCreator, pInterface, 6, _("One Button Dynamic Mode")) {
+COneButtonDynamicFilter::COneButtonDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate)
+  : CButtonMultiPress(pCreator, pInterface, pFramerate, 6, _("One Button Dynamic Mode")) {
   m_iTarget = 0;
 
   m_iTargetX = new int[2];
@@ -116,7 +116,7 @@ void COneButtonDynamicFilter::KeyUp(int Time, int iId, CDasherView *pDasherView,
 }
 
 bool COneButtonDynamicFilter::TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) {
-  m_pDasherModel->OneStepTowards(m_iTargetX[m_iTarget], m_iTargetY[m_iTarget], Time);
+  OneStepTowards(m_pDasherModel, m_iTargetX[m_iTarget], m_iTargetY[m_iTarget], Time, SlowStartSpeedMul(Time));
   return true;
 }
 
diff --git a/Src/DasherCore/OneButtonDynamicFilter.h b/Src/DasherCore/OneButtonDynamicFilter.h
index 768b4fd..bd6ab3a 100644
--- a/Src/DasherCore/OneButtonDynamicFilter.h
+++ b/Src/DasherCore/OneButtonDynamicFilter.h
@@ -28,7 +28,7 @@
 namespace Dasher {
 class COneButtonDynamicFilter : public CButtonMultiPress {
  public:
-  COneButtonDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface);
+  COneButtonDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate);
   ~COneButtonDynamicFilter();
 
   virtual bool DecorateView(CDasherView *pView, CDasherInput *pInput);
diff --git a/Src/DasherCore/OneButtonFilter.cpp b/Src/DasherCore/OneButtonFilter.cpp
index a3bde6d..f8305d1 100644
--- a/Src/DasherCore/OneButtonFilter.cpp
+++ b/Src/DasherCore/OneButtonFilter.cpp
@@ -67,7 +67,7 @@ bool COneButtonFilter::Timer(unsigned long Time, CDasherView *pView, CDasherInpu
     }
   }
 
-  return m_pDasherModel->NextScheduledStep(Time);
+  return m_pDasherModel->NextScheduledStep();
 }
 
 void COneButtonFilter::KeyDown(int iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CUserLogBase *pUserLog) {
diff --git a/Src/DasherCore/OneDimensionalFilter.cpp b/Src/DasherCore/OneDimensionalFilter.cpp
index 18f3392..86b8d48 100644
--- a/Src/DasherCore/OneDimensionalFilter.cpp
+++ b/Src/DasherCore/OneDimensionalFilter.cpp
@@ -8,8 +8,8 @@ using namespace Dasher;
   : COneDimensionalFilter(pSettingsStore, pInterface, m_pDasherModel, 4, _("One Dimensional Mode")) {
 }*/
 
-COneDimensionalFilter::COneDimensionalFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName)
-  : CDefaultFilter(pCreator, pInterface, iID, szName), forwardmax(GetLongParameter(LP_MAX_Y)/2.5) {
+COneDimensionalFilter::COneDimensionalFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID, const char *szName)
+  : CDefaultFilter(pCreator, pInterface, pFramerate, iID, szName), forwardmax(GetLongParameter(LP_MAX_Y)/2.5) {
 }
 
 void COneDimensionalFilter::ApplyTransform(myint &iDasherX, myint &iDasherY, CDasherView *pView) {
@@ -78,7 +78,7 @@ CStartHandler *COneDimensionalFilter::MakeStartHandler() {
   if (GetBoolParameter(BP_CIRCLE_START)) {
     class C1DCircleStartHandler : public CCircleStartHandler {
     public:
-      C1DCircleStartHandler(COneDimensionalFilter *f) : CCircleStartHandler(f, f->m_pInterface), filter(f) {
+      C1DCircleStartHandler(COneDimensionalFilter *f) : CCircleStartHandler(f) {
       }
       void ComputeScreenLoc(CDasherView *pView) {
         if (m_iScreenRadius!=-1) return;
@@ -87,7 +87,7 @@ CStartHandler *COneDimensionalFilter::MakeStartHandler() {
           //put start circle at center of 1D transform, rather than center of screen
           // (leave m_iScreenRadius, in pixels, as computed by above)
           const myint rad(GetLongParameter(LP_CIRCLE_PERCENT) * GetLongParameter(LP_OY) / 100); //~~rad/2 in dasher-coords
-          pView->Dasher2Screen(GetLongParameter(LP_OX)-filter->forwardmax+rad, GetLongParameter(LP_OY),m_screenCircleCenter.x, m_screenCircleCenter.y);
+          pView->Dasher2Screen(GetLongParameter(LP_OX)-static_cast<COneDimensionalFilter*>(m_pFilter)->forwardmax+rad, GetLongParameter(LP_OY),m_screenCircleCenter.x, m_screenCircleCenter.y);
         } 
       }
       void HandleEvent(int iParameter) {
@@ -98,8 +98,6 @@ CStartHandler *COneDimensionalFilter::MakeStartHandler() {
         }
         CCircleStartHandler::HandleEvent(iParameter);
       }
-    private:
-      const COneDimensionalFilter *filter;
     };
     return new C1DCircleStartHandler(this);
   }
diff --git a/Src/DasherCore/OneDimensionalFilter.h b/Src/DasherCore/OneDimensionalFilter.h
index 1567690..5d516c0 100644
--- a/Src/DasherCore/OneDimensionalFilter.h
+++ b/Src/DasherCore/OneDimensionalFilter.h
@@ -9,7 +9,7 @@ namespace Dasher {
 class COneDimensionalFilter : public CDefaultFilter {
  public:
 //  COneDimensionalFilter(CSettingsStore *pSettingsStore, CDasherInterfaceBase *pInterface, CDasherModel *m_pDasherModel);
-  COneDimensionalFilter(CSettingsUser *pCreateFrom, CDasherInterfaceBase *pInterface,  ModuleID_t iID = 4, const char *szName = _("One Dimensional Mode"));
+  COneDimensionalFilter(CSettingsUser *pCreateFrom, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID = 4, const char *szName = _("One Dimensional Mode"));
   ///Override to remove DefaultFilters BP_REMAP_XTREME, BP_AUTOCALIBRATE, LP_OFFSET
   bool GetSettings(SModuleSettings **pSettings, int *iCount);
  protected:
diff --git a/Src/DasherCore/StartHandler.h b/Src/DasherCore/StartHandler.h
index e309bac..e71f760 100644
--- a/Src/DasherCore/StartHandler.h
+++ b/Src/DasherCore/StartHandler.h
@@ -1,14 +1,16 @@
 #ifndef __START_HANDLER_H__
 #define __START_HANDLER_H__
 
-#include "DasherInterfaceBase.h"
+#include "DasherTypes.h"
 
 namespace Dasher {
+  class CDefaultFilter;
+  class CDasherView;
 /// \defgroup Start Start handlers
 /// @{
 class CStartHandler {
 public:
-  CStartHandler(CDasherInterfaceBase *pInterface) : m_pInterface(pInterface) {
+  CStartHandler(CDefaultFilter *pFilter) : m_pFilter(pFilter) {
   };
   virtual ~CStartHandler() {
   }
@@ -17,7 +19,7 @@ public:
   virtual void Timer(int iTime, dasherint iX, dasherint iY, CDasherView *pView) = 0;
 
 protected:
-  CDasherInterfaceBase *m_pInterface;
+  CDefaultFilter * const m_pFilter;
 };
 }
 /// @}
diff --git a/Src/DasherCore/StylusFilter.cpp b/Src/DasherCore/StylusFilter.cpp
index 9f5d9e7..a1ff5ac 100644
--- a/Src/DasherCore/StylusFilter.cpp
+++ b/Src/DasherCore/StylusFilter.cpp
@@ -6,14 +6,14 @@
 
 using namespace Dasher;
 
-CStylusFilter::CStylusFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName)
-  : CDefaultFilter(pCreator, pInterface, iID, szName) {
+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(iTime)) {
+  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.
@@ -25,7 +25,7 @@ bool CStylusFilter::Timer(unsigned long iTime, CDasherView *pView, CDasherInput
 void CStylusFilter::KeyDown(int iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CUserLogBase *pUserLog) {
   if(iId == 100) {
     pModel->ClearScheduledSteps();
-    m_pInterface->Unpause(iTime);
+    Unpause(iTime);
     m_iKeyDownTime = iTime;
   }
 }
diff --git a/Src/DasherCore/StylusFilter.h b/Src/DasherCore/StylusFilter.h
index adb7f6e..a77e7d0 100644
--- a/Src/DasherCore/StylusFilter.h
+++ b/Src/DasherCore/StylusFilter.h
@@ -9,7 +9,7 @@
 namespace Dasher {
 class CStylusFilter : public CDefaultFilter {
  public:
-  CStylusFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName);
+  CStylusFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate, ModuleID_t iID=15, const char *szName=_("Stylus Control"));
   ///Override DefaultFilter (which supports pause), as we don't
   /// - motion requires continually holding stylus against screen
   virtual bool supportsPause() {return false;}
diff --git a/Src/DasherCore/TwoBoxStartHandler.cpp b/Src/DasherCore/TwoBoxStartHandler.cpp
index 21f9d9a..6b9c96a 100644
--- a/Src/DasherCore/TwoBoxStartHandler.cpp
+++ b/Src/DasherCore/TwoBoxStartHandler.cpp
@@ -1,10 +1,11 @@
 #include "TwoBoxStartHandler.h"
-#include "Event.h"
+#include "DefaultFilter.h"
+#include "DasherView.h"
 
 using namespace Dasher;
 
-CTwoBoxStartHandler::CTwoBoxStartHandler(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface)
-: CStartHandler(pInterface), CSettingsUserObserver(pCreator), m_bFirstBox(true), m_iBoxEntered(std::numeric_limits<long>::max()) {
+CTwoBoxStartHandler::CTwoBoxStartHandler(CDefaultFilter *pCreator)
+: CStartHandler(pCreator), CSettingsUserObserver(pCreator), m_bFirstBox(true), m_iBoxEntered(std::numeric_limits<long>::max()) {
 }
 
 bool CTwoBoxStartHandler::DecorateView(CDasherView *pView) {
@@ -51,7 +52,7 @@ void CTwoBoxStartHandler::Timer(int iTime, dasherint iDasherX, dasherint iDasher
       if(m_bFirstBox)
         m_bFirstBox=false;
       else
-        m_pInterface->Unpause(iTime);
+        m_pFilter->Unpause(iTime);
       m_iBoxEntered = std::numeric_limits<long>::max();
     }
   } else {
diff --git a/Src/DasherCore/TwoBoxStartHandler.h b/Src/DasherCore/TwoBoxStartHandler.h
index ae04886..6ba6a55 100644
--- a/Src/DasherCore/TwoBoxStartHandler.h
+++ b/Src/DasherCore/TwoBoxStartHandler.h
@@ -2,13 +2,14 @@
 #define __TWO_BOX_START_HANDLER_H__
 
 #include "StartHandler.h"
+#include "SettingsStore.h"
 
 namespace Dasher {
 /// \ingroup Start
 /// @{
 class CTwoBoxStartHandler : public CStartHandler, public CSettingsUserObserver {
 public:
-  CTwoBoxStartHandler(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface);
+  CTwoBoxStartHandler(CDefaultFilter *pCreator);
 
   virtual bool DecorateView(CDasherView *pView);
   virtual void Timer(int iTime, dasherint iX, dasherint iY, CDasherView *pView);
diff --git a/Src/DasherCore/TwoButtonDynamicFilter.cpp b/Src/DasherCore/TwoButtonDynamicFilter.cpp
index 1fc830d..e13c7a2 100644
--- a/Src/DasherCore/TwoButtonDynamicFilter.cpp
+++ b/Src/DasherCore/TwoButtonDynamicFilter.cpp
@@ -48,8 +48,8 @@ static SModuleSettings sSettings[] = {
   {LP_DYNAMIC_SPEED_DEC, T_LONG, 1, 99, 1, 1, _("Percentage by which to decrease speed upon reverse")}
 };
 
-CTwoButtonDynamicFilter::CTwoButtonDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface)
-  : CButtonMultiPress(pCreator, pInterface, 14, _("Two Button Dynamic Mode"))
+CTwoButtonDynamicFilter::CTwoButtonDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate)
+  : CButtonMultiPress(pCreator, pInterface, pFramerate, 14, _("Two Button Dynamic Mode"))
 {
   //ensure that m_dLagMul is properly initialised
   HandleEvent(LP_DYNAMIC_BUTTON_LAG);
@@ -111,7 +111,7 @@ void CTwoButtonDynamicFilter::KeyUp(int Time, int iId, CDasherView *pView, CDash
 }
 
 bool CTwoButtonDynamicFilter::TimerImpl(unsigned long Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, CExpansionPolicy **pol) {
-  m_pDasherModel->OneStepTowards(100,2048, Time);
+  OneStepTowards(m_pDasherModel, 100,2048, Time, SlowStartSpeedMul(Time));
   return true;
 }
 
@@ -205,5 +205,5 @@ void CTwoButtonDynamicFilter::HandleEvent(int iParameter)
   case LP_TWO_BUTTON_OFFSET:
       m_bDecorationChanged = true;
   }
-  CDynamicFilter::HandleEvent(iParameter);
+  CButtonMultiPress::HandleEvent(iParameter);
 }
diff --git a/Src/DasherCore/TwoButtonDynamicFilter.h b/Src/DasherCore/TwoButtonDynamicFilter.h
index b16762e..4953fbf 100644
--- a/Src/DasherCore/TwoButtonDynamicFilter.h
+++ b/Src/DasherCore/TwoButtonDynamicFilter.h
@@ -30,7 +30,7 @@ namespace Dasher {
 /// @{
 class CTwoButtonDynamicFilter : public CButtonMultiPress {
  public:
-  CTwoButtonDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface);
+  CTwoButtonDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate);
 
   // Inherited methods
   virtual bool DecorateView(CDasherView *pView, CDasherInput *pInput);
diff --git a/Src/DasherCore/TwoPushDynamicFilter.cpp b/Src/DasherCore/TwoPushDynamicFilter.cpp
index d0de1de..74c4d5b 100644
--- a/Src/DasherCore/TwoPushDynamicFilter.cpp
+++ b/Src/DasherCore/TwoPushDynamicFilter.cpp
@@ -43,8 +43,8 @@ static SModuleSettings sSettings[] = {
   {LP_DYNAMIC_BUTTON_LAG, T_LONG, 0, 1000, 1, 25, _("Lag before user actually pushes button (ms)")}, 
 };
 
-CTwoPushDynamicFilter::CTwoPushDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface)
-  : CDynamicFilter(pCreator, pInterface, 14, _("Two-push Dynamic Mode (New One Button)")), m_dNatsSinceFirstPush(-std::numeric_limits<double>::infinity()) {
+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()) {
   
   HandleEvent(LP_TWO_PUSH_OUTER);//and all the others too!
 }
@@ -101,7 +101,7 @@ bool CTwoPushDynamicFilter::DecorateView(CDasherView *pView, CDasherInput *pInpu
 
 void CTwoPushDynamicFilter::HandleEvent(int iParameter)
 {
-  CDynamicFilter::HandleEvent(iParameter);
+  CDynamicButtons::HandleEvent(iParameter);
   switch (iParameter)
   {
     case LP_TWO_PUSH_OUTER:
@@ -171,7 +171,7 @@ void CTwoPushDynamicFilter::KeyDown(int Time, int iId, CDasherView *pView, CDash
   if (iId == 100 && !GetBoolParameter(BP_BACKOFF_BUTTON))
     //mouse click - will be ignored by superclass method.
     //simulate press of button 2...
-    CDynamicFilter::KeyDown(Time, 2, pView, pInput, pModel, pUserLog);
+    CDynamicButtons::KeyDown(Time, 2, pView, pInput, pModel, pUserLog);
   else
     CInputFilter::KeyDown(Time, iId, pView, pInput, pModel, pUserLog, bPos, iX, iY);
 }
@@ -180,7 +180,7 @@ void CTwoPushDynamicFilter::KeyUp(int Time, int iId, CDasherView *pView, CDasher
   if (iId == 100 && !GetBoolParameter(BP_BACKOFF_BUTTON))
   //mouse click - will be ignored by superclass method.
   //simulate press of button 2...
-    CDynamicFilter::KeyUp(Time, 2, pView, pInput, pModel);
+    CDynamicButtons::KeyUp(Time, 2, pView, pInput, pModel);
   else
     CInputFilter::KeyUp(Time, iId, pView, pInput, pModel, bPos, iX, iY);
 }
@@ -258,13 +258,13 @@ bool CTwoPushDynamicFilter::TimerImpl(unsigned long iTime, CDasherView *m_pDashe
       m_bDecorationChanged |= doSet(m_iActiveMarker, 1 /*down*/);
     else m_bDecorationChanged |= doSet(m_iActiveMarker, -1 /*in middle (neither/both) or too short*/);
   }
-  m_pDasherModel->OneStepTowards(100, 2048, iTime);
+  OneStepTowards(m_pDasherModel, 100, 2048, iTime, SlowStartSpeedMul(iTime));
   return true;
 }
 
 void CTwoPushDynamicFilter::run() {
   m_dNatsSinceFirstPush = -std::numeric_limits<double>::infinity();
-  CDynamicFilter::run();
+  CDynamicButtons::run();
 }
 
 bool CTwoPushDynamicFilter::GetSettings(SModuleSettings **pSettings, int *iCount) {
diff --git a/Src/DasherCore/TwoPushDynamicFilter.h b/Src/DasherCore/TwoPushDynamicFilter.h
index 31f6545..51b7ccf 100644
--- a/Src/DasherCore/TwoPushDynamicFilter.h
+++ b/Src/DasherCore/TwoPushDynamicFilter.h
@@ -21,13 +21,13 @@
 #ifndef __TWO_PUSH_DYNAMIC_FILTER_H__
 #define __TWO_PUSH_DYNAMIC_FILTER_H__
 
-#include "DynamicFilter.h"
+#include "DynamicButtons.h"
 namespace Dasher {
 /// \ingroup InputFilter
 /// @{
-class CTwoPushDynamicFilter : public CDynamicFilter /*long push, but do our own "multi-push" detection*/ {
+class CTwoPushDynamicFilter : public CDynamicButtons /*long push, but do our own "multi-push" detection*/ {
  public:
-  CTwoPushDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface);
+  CTwoPushDynamicFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, CFrameRate *pFramerate);
   
   // Inherited methods
   virtual bool DecorateView(CDasherView *pView, CDasherInput *pInput);
diff --git a/Src/DasherCore/UserLog.cpp b/Src/DasherCore/UserLog.cpp
index fdc5861..1be4d83 100644
--- a/Src/DasherCore/UserLog.cpp
+++ b/Src/DasherCore/UserLog.cpp
@@ -47,7 +47,7 @@ static UserLogParamMask s_UserLogParamMaskTable [] = {
 
 CUserLog::CUserLog(CSettingsUser *pCreateFrom,
                    Observable<const CEditEvent *> *pObsv,
-                   int iLogTypeMask) : CUserLogBase(pCreateFrom, pObsv), CSettingsObserver(pCreateFrom) {
+                   int iLogTypeMask) : CUserLogBase(pCreateFrom, pObsv) {
   //CFunctionLogger f1("CUserLog::CUserLog", g_pLogger);
 
   InitMemberVars();
@@ -1134,8 +1134,7 @@ 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), CSettingsObserver(NULL)
-{
+CUserLog::CUserLog(string strXMLFilename) : CUserLogBase(NULL, NULL) {
   //CFunctionLogger f1("CUserLog::CUserLog(XML)", g_pLogger);
 
   InitMemberVars();
diff --git a/Src/DasherCore/UserLog.h b/Src/DasherCore/UserLog.h
index a859417..df5441c 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, so we'll
 // watch for <int> events from the SettingsStore too.
-class CUserLog : public CUserLogBase, public Dasher::CSettingsObserver {
+class CUserLog : public CUserLogBase {
 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 5a1c170..2dd9f4b 100644
--- a/Src/DasherCore/UserLogBase.cpp
+++ b/Src/DasherCore/UserLogBase.cpp
@@ -14,7 +14,7 @@
 
 using namespace Dasher;
 
-CUserLogBase::CUserLogBase(CSettingsUser *pCreateFrom, Observable<const CEditEvent *> *pHandler) : CSettingsUser(pCreateFrom), TransientObserver<const CEditEvent *>(pHandler), m_iNumDeleted(0) {
+CUserLogBase::CUserLogBase(CSettingsUser *pCreateFrom, Observable<const CEditEvent *> *pHandler) : CSettingsUserObserver(pCreateFrom), TransientObserver<const CEditEvent *>(pHandler), m_iNumDeleted(0) {
 };
 
 void CUserLogBase::HandleEvent(const CEditEvent *evt) {
@@ -27,6 +27,17 @@ 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) {
@@ -37,4 +48,4 @@ void CUserLogBase::FrameEnded() {
    AddSymbols(&m_vAdded);
     m_vAdded.clear();
   }
-}
\ No newline at end of file
+}
diff --git a/Src/DasherCore/UserLogBase.h b/Src/DasherCore/UserLogBase.h
index c63e82c..414340b 100644
--- a/Src/DasherCore/UserLogBase.h
+++ b/Src/DasherCore/UserLogBase.h
@@ -16,7 +16,7 @@ namespace Dasher {
 
 /// \defgroup Logging Logging routines
 /// @{
-class CUserLogBase : protected Dasher::CSettingsUser, protected TransientObserver<const Dasher::CEditEvent *> {
+class CUserLogBase : protected Dasher::CSettingsUserObserver, protected TransientObserver<const Dasher::CEditEvent *> {
  public:
   CUserLogBase(Dasher::CSettingsUser *pCreateFrom, Observable<const Dasher::CEditEvent*> *pHandler);
 
@@ -36,6 +36,8 @@ class CUserLogBase : protected Dasher::CSettingsUser, protected TransientObserve
   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/MacOSX/Dasher.xcodeproj/project.pbxproj b/Src/MacOSX/Dasher.xcodeproj/project.pbxproj
index 71d39c9..30c8d88 100755
--- a/Src/MacOSX/Dasher.xcodeproj/project.pbxproj
+++ b/Src/MacOSX/Dasher.xcodeproj/project.pbxproj
@@ -350,6 +350,8 @@
 		33C88E201201D0A900727492 /* GameModule.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33C88E1C1201D0A900727492 /* GameModule.cpp */; };
 		33C88E211201D0A900727492 /* GameModule.h in Headers */ = {isa = PBXBuildFile; fileRef = 33C88E1D1201D0A900727492 /* GameModule.h */; };
 		33CB6E80125E2C7A002DB8AD /* WordGeneratorBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33CB6E7F125E2C7A002DB8AD /* WordGeneratorBase.cpp */; };
+		33DDB9E013B8AF360001C52D /* DynamicButtons.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33DDB9DE13B8AF360001C52D /* DynamicButtons.cpp */; };
+		33DDB9E113B8AF360001C52D /* DynamicButtons.h in Headers */ = {isa = PBXBuildFile; fileRef = 33DDB9DF13B8AF360001C52D /* DynamicButtons.h */; };
 		33E173C70F3E0B6400D19B38 /* Makefile.am in Resources */ = {isa = PBXBuildFile; fileRef = 33E173A70F3E0B6400D19B38 /* Makefile.am */; };
 		33E173C80F3E0B6400D19B38 /* training_albanian_SQ.txt in Resources */ = {isa = PBXBuildFile; fileRef = 33E173A80F3E0B6400D19B38 /* training_albanian_SQ.txt */; };
 		33E173CB0F3E0B6400D19B38 /* training_bengali_BD.txt in Resources */ = {isa = PBXBuildFile; fileRef = 33E173AB0F3E0B6400D19B38 /* training_bengali_BD.txt */; };
@@ -756,6 +758,8 @@
 		33C88E1C1201D0A900727492 /* GameModule.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GameModule.cpp; sourceTree = "<group>"; };
 		33C88E1D1201D0A900727492 /* GameModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GameModule.h; sourceTree = "<group>"; };
 		33CB6E7F125E2C7A002DB8AD /* WordGeneratorBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WordGeneratorBase.cpp; sourceTree = "<group>"; };
+		33DDB9DE13B8AF360001C52D /* DynamicButtons.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DynamicButtons.cpp; sourceTree = "<group>"; };
+		33DDB9DF13B8AF360001C52D /* DynamicButtons.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DynamicButtons.h; sourceTree = "<group>"; };
 		33E173A70F3E0B6400D19B38 /* Makefile.am */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Makefile.am; sourceTree = "<group>"; };
 		33E173A80F3E0B6400D19B38 /* training_albanian_SQ.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = training_albanian_SQ.txt; sourceTree = "<group>"; };
 		33E173AB0F3E0B6400D19B38 /* training_bengali_BD.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = training_bengali_BD.txt; sourceTree = "<group>"; };
@@ -961,6 +965,8 @@
 				1948BE3A0C226CFD001DFA32 /* DasherViewSquare.inl */,
 				1948BE3B0C226CFD001DFA32 /* DefaultFilter.cpp */,
 				1948BE3C0C226CFD001DFA32 /* DefaultFilter.h */,
+				33DDB9DE13B8AF360001C52D /* DynamicButtons.cpp */,
+				33DDB9DF13B8AF360001C52D /* DynamicButtons.h */,
 				3300115010A2EA7700D31B1D /* ExpansionPolicy.cpp */,
 				3300115110A2EA7700D31B1D /* ExpansionPolicy.h */,
 				1948BE3E0C226CFD001DFA32 /* DynamicFilter.cpp */,
@@ -1467,6 +1473,7 @@
 				33E756A41202D6180012A0E9 /* WordGeneratorBase.h in Headers */,
 				33FC1D2C13ACE7E7007642CD /* ScreenGameModule.h in Headers */,
 				33C3BDD213854CC000C768E0 /* DasherTextView.h in Headers */,
+				33DDB9E113B8AF360001C52D /* DynamicButtons.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1821,6 +1828,7 @@
 				33CB6E80125E2C7A002DB8AD /* WordGeneratorBase.cpp in Sources */,
 				33FC1D2B13ACE7E7007642CD /* ScreenGameModule.cpp in Sources */,
 				33C3BDD313854CC000C768E0 /* DasherTextView.mm in Sources */,
+				33DDB9E013B8AF360001C52D /* DynamicButtons.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/Src/MacOSX/DasherApp.h b/Src/MacOSX/DasherApp.h
index cc6e873..9f4a48b 100644
--- a/Src/MacOSX/DasherApp.h
+++ b/Src/MacOSX/DasherApp.h
@@ -41,11 +41,8 @@
 @property (readonly,retain) NSString *filename;
 @property BOOL modified;
 
-- (void)start;
 - (void)redraw;
 - (void)changeScreen:(CDasherScreen *)aScreen;
-- (void)pause;
-- (void)unpause:(unsigned long int)time;
 - (NSDictionary *)parameterDictionary;
 - (NSArray *)permittedValuesForParameter:(int)aParameter;
 - (id)getParameterValueForKey:(NSString *)aKey;
diff --git a/Src/MacOSX/DasherApp.mm b/Src/MacOSX/DasherApp.mm
index 7bb2a9e..7e82256 100644
--- a/Src/MacOSX/DasherApp.mm
+++ b/Src/MacOSX/DasherApp.mm
@@ -55,10 +55,6 @@ static NSString *FilenameToUntitledName = @"NilToUntitled";
   return [[NSValueTransformer valueTransformerForName:FilenameToUntitledName] transformedValue:self.filename];
 }
 
-- (void)start {
-//  aquaDasherControl->Start();
-}
-
 - (void)redraw {
   aquaDasherControl->ScheduleRedraw();
 }
@@ -67,14 +63,6 @@ static NSString *FilenameToUntitledName = @"NilToUntitled";
   aquaDasherControl->ChangeScreen( aScreen );
 }
 
-- (void)pause {
-  aquaDasherControl->Stop();
-}
-
-- (void)unpause:(unsigned long int)time {
-  aquaDasherControl->Unpause( time );
-}
-
 - (NSDictionary *)parameterDictionary {
   return aquaDasherControl->ParameterDictionary();
 }



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