1 // DasherButtons.cpp, build a set of boxes for Button Dasher.
2 // Copyright 2005, Chris Ball and David MacKay. GPL.
4 // Idea - should back off button always just undo the previous 'forwards' button?
6 #include "../Common/Common.h"
8 #include "ButtonMode.h"
9 #include "DasherScreen.h"
10 #include "DasherInterfaceBase.h"
14 // Track memory leaks on Windows to the line that new'd the memory
16 #ifdef _DEBUG_MEMLEAKS
17 #define DEBUG_NEW new( _NORMAL_BLOCK, THIS_FILE, __LINE__ )
20 static char THIS_FILE
[] = __FILE__
;
24 using namespace Dasher
;
26 static SModuleSettings sSettings
[] = {
27 /* TRANSLATORS: The number of time steps over which to perform the zooming motion in button mode. */
28 {LP_ZOOMSTEPS
, T_LONG
, 1, 63, 1, 1, _("Frames over which to perform zoom")},
29 {LP_BUTTON_SCAN_TIME
, T_LONG
, 0, 2000, 1, 100, _("Scan time in menu mode (0 to not scan)")},
30 {LP_B
, T_LONG
, 2, 10, 1, 1, _("Number of forward boxes")},
31 {LP_S
, T_LONG
, 0, 256, 1, 1, _("Safety margin")},
32 /* TRANSLATORS: The boxes (zoom targets) in button mode can either be the same size, or different sizes - this is the extent to which the sizes are allowed to differ from each other. */
33 /* XXX PRLW: 128 log(2) = 89, where 2 is the ratio of adjacent boxes
34 * however the code seems to use ratio = (129/127)^-r, instead of
35 * ratio = exp(r/128) used in the design document
37 {LP_R
, T_LONG
, -89, 89, 1, 10, _("Box non-uniformity")},
38 /* TRANSLATORS: Intercept keyboard events for 'special' keys even when the Dasher window doesn't have keyboard focus.*/
39 {BP_GLOBAL_KEYBOARD
, T_BOOL
, -1, -1, -1, -1, _("Global keyboard grab")}
44 CButtonMode::CButtonMode(CSettingsUser
*pCreator
, CDasherInterfaceBase
*pInterface
, bool bMenu
, int iID
, const char *szName
)
45 : CDasherButtons(pCreator
, pInterface
, bMenu
, iID
, szName
), CSettingsObserver(pCreator
) {}
47 void CButtonMode::SetupBoxes()
49 int iDasherY(CDasherModel::MAX_Y
);
51 int iForwardBoxes(GetLongParameter(LP_B
));
52 m_pBoxes
= new SBoxInfo
[m_iNumBoxes
= iForwardBoxes
+1];
54 // Calculate the sizes of non-uniform boxes using standard
55 // geometric progression results
57 // FIXME - implement this using DJCM's integer method?
58 // See ~mackay/dasher/buttons/
59 const double dRatio
= pow(129/127.0, -static_cast<double>(GetLongParameter(LP_R
)));
65 dMaxSize
= iDasherY
/ static_cast<double>(iForwardBoxes
);
67 dMaxSize
= ((dRatio
- 1)/(pow(dRatio
, iForwardBoxes
) - 1)) * iDasherY
;
72 for(int i(0); i
< m_iNumBoxes
- 1; ++i
) { // One button reserved for backoff
73 dMax
= dMin
+ dMaxSize
* pow(dRatio
, i
);
75 // m_pBoxes[i].iDisplayTop = (i * iDasherY) / (m_iNumBoxes - 1);
76 // m_pBoxes[i].iDisplayBottom = ((i+1) * iDasherY) / (m_iNumBoxes - 1);
78 m_pBoxes
[i
].iDisplayTop
= static_cast<int>(dMin
);
79 m_pBoxes
[i
].iDisplayBottom
= static_cast<int>(dMax
);
81 m_pBoxes
[i
].iTop
= m_pBoxes
[i
].iDisplayTop
- GetLongParameter(LP_S
);
82 m_pBoxes
[i
].iBottom
= m_pBoxes
[i
].iDisplayBottom
+ GetLongParameter(LP_S
);
89 if(iForwardBoxes
== 2) { // Special case for two forwards buttons
90 myint iMid
= static_cast<int>(iDasherY
/ (1.0+dRatio
));
92 m_pBoxes
[0].iDisplayTop
= 0;
93 m_pBoxes
[0].iDisplayBottom
= iMid
;
95 m_pBoxes
[1].iDisplayTop
= iMid
;
96 m_pBoxes
[1].iDisplayBottom
= iDasherY
;
99 bool bEven(iForwardBoxes
% 2 == 0);
101 const int iGeometricTerms
= (iForwardBoxes
+1)/2; //int div, round down
106 dMaxSize
= iDasherY
/ iForwardBoxes
;
110 dMaxSize
= iDasherY
* (dRatio
- 1) / (2 * (pow(dRatio
, iGeometricTerms
) - 1));
112 dMaxSize
= iDasherY
* (dRatio
- 1) / (2 * (pow(dRatio
, iGeometricTerms
) - 1) - (dRatio
- 1));
115 double dMin
= (bEven
) ? iDasherY
/2.0 : (iDasherY
-dMaxSize
)/2.0;
117 const int iUpBase
= iForwardBoxes
/2; //int div, round down if !bEven
118 const int iDownBase
= bEven
? iUpBase
-1 : iUpBase
;
121 for(int i(0); i
< iGeometricTerms
; ++i
) { // One button reserved for backoff
122 const double dMax
= dMin
+ dMaxSize
* pow(dRatio
, i
);
124 m_pBoxes
[iUpBase
+ i
].iDisplayTop
= int(dMin
);
125 m_pBoxes
[iUpBase
+ i
].iDisplayBottom
= int(dMax
);
127 m_pBoxes
[iDownBase
- i
].iDisplayTop
= int(iDasherY
- dMax
);
128 m_pBoxes
[iDownBase
- i
].iDisplayBottom
= int(iDasherY
- dMin
);
135 for(int i(0); i
< m_iNumBoxes
- 1; ++i
) {
136 m_pBoxes
[i
].iTop
= m_pBoxes
[i
].iDisplayTop
- GetLongParameter(LP_S
);
137 m_pBoxes
[i
].iBottom
= m_pBoxes
[i
].iDisplayBottom
+ GetLongParameter(LP_S
);
140 m_pBoxes
[m_iNumBoxes
-1].iDisplayTop
= 0;
141 m_pBoxes
[m_iNumBoxes
-1].iDisplayBottom
= iDasherY
;
143 m_pBoxes
[m_iNumBoxes
-1].iTop
= int(- iDasherY
/ 2);
144 m_pBoxes
[m_iNumBoxes
-1].iBottom
= int(iDasherY
* 1.5);
147 bool CButtonMode::DecorateView(CDasherView
*pView
, CDasherInput
*pInput
) {
148 for(int i(0); i
< m_iNumBoxes
; ++i
) {
150 NewDrawGoTo(pView
, m_pBoxes
[i
].iDisplayTop
, m_pBoxes
[i
].iDisplayBottom
, false);
152 NewDrawGoTo(pView
, m_pBoxes
[iActiveBox
].iDisplayTop
, m_pBoxes
[iActiveBox
].iDisplayBottom
, m_bMenu
|| m_bHighlight
);
154 bool bRV(m_bDecorationChanged
);
155 m_bDecorationChanged
= false;
159 void CButtonMode::Timer(unsigned long Time
, CDasherView
*pView
, CDasherInput
*pInput
, CDasherModel
*pModel
, CExpansionPolicy
**pol
) {
160 bool m_bOldHighlight(m_bHighlight
);
161 m_bHighlight
= (Time
- m_iLastTime
< 200);
163 if(m_bOldHighlight
!= m_bHighlight
)
164 m_bDecorationChanged
= true;
166 CDasherButtons::Timer(Time
, pView
, pInput
, pModel
, pol
);
169 void CButtonMode::KeyDown(unsigned long iTime
, int iId
, CDasherView
*pView
, CDasherInput
*pInput
, CDasherModel
*pModel
) {
174 if (GetLongParameter(LP_BUTTON_SCAN_TIME
))
175 bScan
= false; //auto-scan, any click selects
177 //top scans, bottom selects
178 screenint iScreenX
, iScreenY
;
179 pInput
->GetScreenCoords(iScreenX
, iScreenY
, pView
);
180 bScan
= iScreenY
< pView
->Screen()->GetHeight()/2;
182 CDasherButtons::KeyDown(iTime
, bScan
? 1 : 2, pView
, pInput
, pModel
);
185 myint iDasherX
, iDasherY
;
186 pInput
->GetDasherCoords(iDasherX
, iDasherY
, pView
);
187 //look for a click _in_ a box -> activate box
188 for (int i
= 0; i
< m_iNumBoxes
; i
++) {
189 if (iDasherY
< m_pBoxes
[i
].iDisplayBottom
&&
190 iDasherY
> m_pBoxes
[i
].iDisplayTop
&&
191 iDasherX
< (m_pBoxes
[i
].iDisplayBottom
- m_pBoxes
[i
].iDisplayTop
)) {
192 //user has clicked in box! Simulate press of appropriate (direct-mode) button...
193 CDasherButtons::KeyDown(iTime
, (i
==m_iNumBoxes
-1) ? 1 : i
+2, pView
, pInput
, pModel
);
197 //not in any box. Fall through, just to be conservative...
200 CDasherButtons::KeyDown(iTime
, iId
, pView
, pInput
, pModel
);
203 void CButtonMode::DirectKeyDown(unsigned long iTime
, int iId
, CDasherView
*pView
, CDasherModel
*pModel
) {
204 CDasherButtons::DirectKeyDown(iTime
, iId
, pView
, pModel
);
205 if (iId
!=100) m_iLastTime
= iTime
;
208 void CButtonMode::HandleEvent(int iParameter
) {
209 switch (iParameter
) {
212 // Delibarate fallthrough
215 m_pInterface
->ScheduleRedraw();
220 bool CButtonMode::GetSettings(SModuleSettings
**pSettings
, int *iCount
) {
221 *pSettings
= sSettings
;
222 *iCount
= sizeof(sSettings
) / sizeof(SModuleSettings
);