Fixed UTF-8 file save bug Update to circle start handler Started two
[dasher.git] / Src / DasherCore / DasherButtons.cpp
blob1ac1bebeaa3151558eaf439674f0a4049acc310f
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"
9 #include "DasherButtons.h"
10 #include <valarray>
11 #include <iostream>
13 // Track memory leaks on Windows to the line that new'd the memory
14 #ifdef _WIN32
15 #ifdef _DEBUG
16 #define DEBUG_NEW new( _NORMAL_BLOCK, THIS_FILE, __LINE__ )
17 #define new DEBUG_NEW
18 #undef THIS_FILE
19 static char THIS_FILE[] = __FILE__;
20 #endif
21 #endif
23 static SModuleSettings sSettings[] = {
24 {LP_ZOOMSTEPS, T_LONG, 1, 63, 1, 1, "Zoom steps"},
25 {LP_RIGHTZOOM, T_LONG, 1024, 10240, 1024, 1024, "Right zoom"},
26 {LP_B, T_LONG, 2, 10, 1, 1, "Number of boxes"},
27 {LP_S, T_LONG, 0, 256, 1, 1, "Safety margin"},
28 {LP_R, T_LONG, -400, 400, 1, 10, "Box non-uniformity"},
29 {BP_GLOBAL_KEYBOARD, T_BOOL, -1, -1, -1, -1, "Global keyboard grab"}
32 // FIXME - should compass mode be made a separate class?
34 CDasherButtons::CDasherButtons(Dasher::CEventHandler * pEventHandler, CSettingsStore *pSettingsStore, CDasherInterfaceBase *pInterface, int iNumBoxes, int iStyle, bool bMenu, long long int iID, const char *szName)
35 : CInputFilter(pEventHandler, pSettingsStore, pInterface, iID, 1, szName) {
37 m_pBoxes = 0;
39 m_iNumBoxes = iNumBoxes;
40 m_iStyle = iStyle;
41 m_bMenu = bMenu;
43 m_pSettingsStore = pSettingsStore;
45 m_bDecorationChanged = true;
47 SetupBoxes();
50 CDasherButtons::~CDasherButtons()
52 delete[] m_pBoxes;
55 void CDasherButtons::SetupBoxes()
58 int iDasherY(GetLongParameter(LP_MAX_Y));
61 if(m_iStyle == 3) {
62 // Alternating direct mode
64 m_pBoxes = new SBoxInfo[5];
66 // Fast boxes
68 m_pBoxes[0].iTop = 0;
69 m_pBoxes[0].iBottom = 1000;
70 m_pBoxes[1].iTop = 3096;
71 m_pBoxes[1].iBottom = 4096;
73 // Slow boxes
75 m_pBoxes[2].iTop = 0;
76 m_pBoxes[2].iBottom = 3096;
77 m_pBoxes[3].iTop = 1000;
78 m_pBoxes[3].iBottom = 4096;
80 m_pBoxes[0].iDisplayTop = m_pBoxes[0].iTop;
81 m_pBoxes[0].iDisplayBottom = m_pBoxes[0].iBottom;
82 m_pBoxes[1].iDisplayTop = m_pBoxes[1].iTop;
83 m_pBoxes[1].iDisplayBottom = m_pBoxes[1].iBottom;
84 m_pBoxes[2].iDisplayTop = m_pBoxes[2].iTop;
85 m_pBoxes[2].iDisplayBottom = m_pBoxes[2].iBottom;
86 m_pBoxes[3].iDisplayTop = m_pBoxes[3].iTop;
87 m_pBoxes[3].iDisplayBottom = m_pBoxes[3].iBottom;
89 m_iNumBoxes = 5;
90 m_pBoxes[m_iNumBoxes-1].iDisplayTop = 0;
91 m_pBoxes[m_iNumBoxes-1].iDisplayBottom = iDasherY;
93 m_pBoxes[m_iNumBoxes-1].iTop = int(- iDasherY / 2);
94 m_pBoxes[m_iNumBoxes-1].iBottom = int(iDasherY * 1.5);
96 m_iLastBox = -1;
99 else if(m_iStyle == 2) { // Compass mode
100 m_pBoxes = new SBoxInfo[4];
102 iTargetWidth = iDasherY * 1024 / GetLongParameter(LP_RIGHTZOOM);
104 // FIXME - need to relate these to cross-hair position as stored in the parameters
106 // Not sure whether this is at all the right algorithm here - need to check
108 m_pBoxes[1].iTop = (2048 - iTargetWidth / 2);
109 m_pBoxes[1].iBottom = 4096 - m_pBoxes[1].iTop;
111 // Make this the inverse of the right zoom option
113 m_pBoxes[3].iTop = -2048 * m_pBoxes[1].iTop / (2048 - m_pBoxes[1].iTop);
114 m_pBoxes[3].iBottom = 4096 - m_pBoxes[3].iTop;
116 m_pBoxes[0].iTop = -iTargetWidth;
117 m_pBoxes[0].iBottom = iDasherY - iTargetWidth;
118 m_pBoxes[2].iTop = iTargetWidth;
119 m_pBoxes[2].iBottom = iDasherY + iTargetWidth;
121 m_pBoxes[0].iDisplayTop = m_pBoxes[0].iTop;
122 m_pBoxes[0].iDisplayBottom = m_pBoxes[0].iBottom;
123 m_pBoxes[1].iDisplayTop = m_pBoxes[1].iTop;
124 m_pBoxes[1].iDisplayBottom = m_pBoxes[1].iBottom;
125 m_pBoxes[2].iDisplayTop = m_pBoxes[2].iTop;
126 m_pBoxes[2].iDisplayBottom = m_pBoxes[2].iBottom;
127 m_pBoxes[3].iDisplayTop = m_pBoxes[3].iTop;
128 m_pBoxes[3].iDisplayBottom = m_pBoxes[3].iBottom;
131 else {
133 if((m_iStyle == 1) || (m_iStyle == 0))
134 m_iNumBoxes = GetLongParameter(LP_B) + 1; // One extra box for backoff
136 if(m_pBoxes) {
137 delete[] m_pBoxes;
138 m_pBoxes = 0;
141 m_pBoxes = new SBoxInfo[m_iNumBoxes];
142 int iForwardBoxes(m_iNumBoxes - 1);
144 // Calculate the sizes of non-uniform boxes using standard
145 // geometric progression results
147 double dRatio;
148 double dNorm;
150 // FIXME - implement this using DJCM's integer method?
151 // See ~mackay/dasher/buttons/
152 dRatio = pow(129/127.0, -static_cast<double>(GetLongParameter(LP_R)));
154 if(m_bMenu) {
156 double dMaxSize;
157 if(dRatio == 1.0)
158 dMaxSize = iDasherY / static_cast<double>(iForwardBoxes);
159 else
160 dMaxSize = ((dRatio - 1)/(pow(dRatio, iForwardBoxes) - 1)) * iDasherY;
162 double dMin(0.0);
163 double dMax;
165 for(int i(0); i < m_iNumBoxes - 1; ++i) { // One button reserved for backoff
166 dMax = dMin + dMaxSize * pow(dRatio, i);
168 // m_pBoxes[i].iDisplayTop = (i * iDasherY) / (m_iNumBoxes - 1);
169 // m_pBoxes[i].iDisplayBottom = ((i+1) * iDasherY) / (m_iNumBoxes - 1);
171 m_pBoxes[i].iDisplayTop = static_cast<int>(dMin);
172 m_pBoxes[i].iDisplayBottom = static_cast<int>(dMax);
174 m_pBoxes[i].iTop = m_pBoxes[i].iDisplayTop - GetLongParameter(LP_S);
175 m_pBoxes[i].iBottom = m_pBoxes[i].iDisplayBottom + GetLongParameter(LP_S);
177 dMin = dMax;
181 else {
182 if(m_iNumBoxes == 2+1) { // Special case for two forwards buttons
183 dNorm = 1+dRatio;
185 m_pBoxes[0].iDisplayTop = 0;
186 m_pBoxes[0].iDisplayBottom = int( (1 / dNorm) * iDasherY );
188 m_pBoxes[1].iDisplayTop = int( (1 / dNorm) * iDasherY );
189 m_pBoxes[1].iDisplayBottom = iDasherY;
191 else {
192 int iForwardsButtons(m_iNumBoxes - 1);
193 bool bEven(iForwardsButtons % 2 == 0);
195 int iGeometricTerms;
197 if(bEven)
198 iGeometricTerms = iForwardsButtons / 2;
199 else
200 iGeometricTerms = (1+iForwardsButtons) / 2;
202 double dMaxSize;
204 if(dRatio == 1.0) {
205 dMaxSize = iDasherY / iForwardsButtons;
207 else {
208 if(bEven)
209 dMaxSize = iDasherY * (dRatio - 1) / (2 * (pow(dRatio, iGeometricTerms) - 1));
210 else
211 dMaxSize = iDasherY * (dRatio - 1) / (2 * (pow(dRatio, iGeometricTerms) - 1) - (dRatio - 1));
214 double dMin;
215 double dMax;
217 if(bEven)
218 dMin = iDasherY / 2;
219 else
220 dMin = (iDasherY - dMaxSize)/2;
222 int iUpBase;
223 int iDownBase;
225 if(bEven) {
226 iUpBase = iForwardsButtons / 2;
227 iDownBase = iUpBase - 1;
229 else {
230 iUpBase = (iForwardsButtons - 1)/ 2;
231 iDownBase = iUpBase;
234 for(int i(0); i < iGeometricTerms; ++i) { // One button reserved for backoff
235 dMax = dMin + dMaxSize * pow(dRatio, i);
237 m_pBoxes[iUpBase + i].iDisplayTop = int(dMin);
238 m_pBoxes[iUpBase + i].iDisplayBottom = int(dMax);
240 m_pBoxes[iDownBase - i].iDisplayTop = int(iDasherY - dMax);
241 m_pBoxes[iDownBase - i].iDisplayBottom = int(iDasherY - dMin);
243 dMin = dMax;
248 for(int i(0); i < m_iNumBoxes - 1; ++i) {
249 m_pBoxes[i].iTop = m_pBoxes[i].iDisplayTop - GetLongParameter(LP_S);
250 m_pBoxes[i].iBottom = m_pBoxes[i].iDisplayBottom + GetLongParameter(LP_S);
253 m_pBoxes[m_iNumBoxes-1].iDisplayTop = 0;
254 m_pBoxes[m_iNumBoxes-1].iDisplayBottom = iDasherY;
256 m_pBoxes[m_iNumBoxes-1].iTop = int(- iDasherY / 2);
257 m_pBoxes[m_iNumBoxes-1].iBottom = int(iDasherY * 1.5);
260 iActiveBox = 0;
261 m_bDecorationChanged = true;
264 bool CDasherButtons::DecorateView(CDasherView *pView) {
265 if(m_iStyle == 2) {
266 CDasherScreen *pScreen(pView->Screen());
268 int iPos(2048 - iTargetWidth / 2);
270 bool bFirst(true);
272 while(iPos >= 0) {
273 CDasherScreen::point p[2];
275 myint iDasherX;
276 myint iDasherY;
278 iDasherX = -100;
279 iDasherY = iPos;
281 pView->Dasher2Screen(iDasherX, iDasherY, p[0].x, p[0].y);
283 iDasherX = -1000;
284 iDasherY = iPos;
286 pView->Dasher2Screen(iDasherX, iDasherY, p[1].x, p[1].y);
288 if(bFirst)
289 pScreen->Polyline(p, 2, 1, 1);
290 else
291 pScreen->Polyline(p, 2, 1, 2);
293 iDasherX = -100;
294 iDasherY = 4096 - iPos;
296 pView->Dasher2Screen(iDasherX, iDasherY, p[0].x, p[0].y);
298 iDasherX = -1000;
299 iDasherY = 4096 - iPos;
301 pView->Dasher2Screen(iDasherX, iDasherY, p[1].x, p[1].y);
303 if(bFirst)
304 pScreen->Polyline(p, 2, 1, 1);
305 else
306 pScreen->Polyline(p, 2, 1, 2);
308 iPos -= iTargetWidth;
309 bFirst = false;
312 else if(m_iStyle == 3) {
313 if(m_iLastBox == 1) {
314 pView->NewDrawGoTo(m_pBoxes[2].iDisplayTop, m_pBoxes[2].iDisplayBottom, false);
315 pView->NewDrawGoTo(m_pBoxes[1].iDisplayTop, m_pBoxes[3].iDisplayBottom, false);
316 pView->NewDrawGoTo(m_pBoxes[4].iDisplayTop, m_pBoxes[4].iDisplayBottom, false);
318 else {
319 pView->NewDrawGoTo(m_pBoxes[0].iDisplayTop, m_pBoxes[0].iDisplayBottom, false);
320 pView->NewDrawGoTo(m_pBoxes[3].iDisplayTop, m_pBoxes[1].iDisplayBottom, false);
321 pView->NewDrawGoTo(m_pBoxes[4].iDisplayTop, m_pBoxes[4].iDisplayBottom, false);
324 else {
325 for(int i(0); i < m_iNumBoxes; ++i) {
326 if(i != iActiveBox)
327 pView->NewDrawGoTo(m_pBoxes[i].iDisplayTop, m_pBoxes[i].iDisplayBottom, false);
329 pView->NewDrawGoTo(m_pBoxes[iActiveBox].iDisplayTop, m_pBoxes[iActiveBox].iDisplayBottom, m_bMenu || m_bHighlight);
332 bool bRV(m_bDecorationChanged);
333 m_bDecorationChanged = false;
334 return bRV;
338 void CDasherButtons::KeyDown(int iTime, int iId, CDasherModel *pModel, CUserLog *pUserLog) {
340 if(m_bMenu) {
341 switch(iId) {
342 case 1:
343 case 4:
344 m_bDecorationChanged = true;
345 ++iActiveBox;
346 if(iActiveBox == m_iNumBoxes)
347 iActiveBox = 0;
348 break;
349 case 2:
350 case 3:
351 m_bDecorationChanged = true;
352 pModel->ScheduleZoom((m_pBoxes[iActiveBox].iBottom - m_pBoxes[iActiveBox].iTop)/2, (m_pBoxes[iActiveBox].iBottom + m_pBoxes[iActiveBox].iTop)/2);
353 if(iActiveBox != m_iNumBoxes-1)
354 iActiveBox = 0;
355 break;
358 else {
359 if(m_iStyle == 3) {
360 switch(iId) {
361 case 2:
362 if(m_iLastBox == 1)
363 pModel->ScheduleZoom((m_pBoxes[2].iBottom - m_pBoxes[2].iTop)/2, (m_pBoxes[2].iBottom + m_pBoxes[2].iTop)/2);
364 else
365 pModel->ScheduleZoom((m_pBoxes[0].iBottom - m_pBoxes[0].iTop)/2, (m_pBoxes[0].iBottom + m_pBoxes[0].iTop)/2);
366 m_iLastBox = 1;
367 break;
368 case 3:
369 case 4:
370 if(m_iLastBox == 2)
371 pModel->ScheduleZoom((m_pBoxes[3].iBottom - m_pBoxes[3].iTop)/2, (m_pBoxes[3].iBottom + m_pBoxes[3].iTop)/2);
372 else
373 pModel->ScheduleZoom((m_pBoxes[1].iBottom - m_pBoxes[1].iTop)/2, (m_pBoxes[1].iBottom + m_pBoxes[1].iTop)/2);
374 m_iLastBox = 2;
375 break;
376 case 1:
377 pModel->ScheduleZoom((m_pBoxes[4].iBottom - m_pBoxes[4].iTop)/2, (m_pBoxes[4].iBottom + m_pBoxes[4].iTop)/2);
378 break;
381 else {
382 if(iId == 100) // Ignore mouse events
383 return;
384 if(iId == 1)
385 iActiveBox = m_iNumBoxes - 1;
386 else if(iId <= m_iNumBoxes)
387 iActiveBox = iId-2;
388 else
389 iActiveBox = m_iNumBoxes-2;
391 m_iLastTime = iTime;
393 pModel->ScheduleZoom((m_pBoxes[iActiveBox].iBottom - m_pBoxes[iActiveBox].iTop)/2, (m_pBoxes[iActiveBox].iBottom + m_pBoxes[iActiveBox].iTop)/2);
399 bool CDasherButtons::Timer(int Time, CDasherView *m_pDasherView, CDasherModel *m_pDasherModel, Dasher::VECTOR_SYMBOL_PROB *pAdded, int *pNumDeleted) {
400 bool m_bOldHighlight(m_bHighlight);
401 m_bHighlight = (Time - m_iLastTime < 200);
403 if(m_bOldHighlight != m_bHighlight)
404 m_bDecorationChanged = true;
406 return m_pDasherModel->Tap_on_display(0, 0, Time, pAdded, pNumDeleted);
409 void CDasherButtons::HandleEvent(Dasher::CEvent * pEvent) {
410 if(pEvent->m_iEventType == 1) {
411 Dasher::CParameterNotificationEvent * pEvt(static_cast < Dasher::CParameterNotificationEvent * >(pEvent));
413 switch (pEvt->m_iParameter) {
414 case LP_B:
415 case LP_RIGHTZOOM:
416 case LP_R:
417 // Delibarate fallthrough
418 SetupBoxes();
419 break;
424 bool CDasherButtons::GetSettings(SModuleSettings **pSettings, int *iCount) {
425 *pSettings = sSettings;
426 *iCount = sizeof(sSettings) / sizeof(SModuleSettings);
428 return true;