Update Turkish translation
[dasher.git] / Src / DasherCore / DasherViewSquare.cpp
blobbd919943b6a88b566635ee13edba2e0b9fe12144
1 // DasherViewSquare.cpp
2 //
3 // Copyright (c) 2008 The Dasher Team
4 //
5 // This file is part of Dasher.
6 //
7 // Dasher is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 2 of the License, or
10 // (at your option) any later version.
12 // Dasher is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with Dasher; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #include "../Common/Common.h"
23 #ifdef _WIN32
24 #include "..\Win32\Common\WinCommon.h"
25 #endif
27 #include "DasherViewSquare.h"
28 #include "DasherView.h"
29 #include "DasherTypes.h"
30 #include "Event.h"
31 #include "Observable.h"
33 #include <algorithm>
34 #include <iostream>
35 #include <limits>
36 #include <stdlib.h>
38 using namespace Dasher;
39 using namespace Opts;
41 // Track memory leaks on Windows to the line that new'd the memory
42 #ifdef _WIN32
43 #ifdef _DEBUG_MEMLEAKS
44 #define DEBUG_NEW new( _NORMAL_BLOCK, THIS_FILE, __LINE__ )
45 #define new DEBUG_NEW
46 #undef THIS_FILE
47 static char THIS_FILE[] = __FILE__;
48 #endif
49 #endif
51 // FIXME - quite a lot of the code here probably should be moved to
52 // the parent class (DasherView). I think we really should make the
53 // parent class less general - we're probably not going to implement
54 // anything which uses radically different co-ordinate transforms, and
55 // we can always override if necessary.
57 // FIXME - duplicated 'mode' code throught - needs to be fixed (actually, mode related stuff, Input2Dasher etc should probably be at least partially in some other class)
59 CDasherViewSquare::CDasherViewSquare(CSettingsUser *pCreateFrom, CDasherScreen *DasherScreen, Opts::ScreenOrientations orient)
60 : CDasherView(DasherScreen,orient), CSettingsUserObserver(pCreateFrom), m_Y1(4), m_Y2(0.95 * CDasherModel::MAX_Y), m_Y3(0.05 * CDasherModel::MAX_Y), m_bVisibleRegionValid(false) {
62 //Note, nonlinearity parameters set in SetScaleFactor
63 ScreenResized(DasherScreen);
66 CDasherViewSquare::~CDasherViewSquare() {}
68 void CDasherViewSquare::SetOrientation(Opts::ScreenOrientations newOrient) {
69 if (newOrient == GetOrientation()) return;
70 CDasherView::SetOrientation(newOrient);
71 m_bVisibleRegionValid=false;
72 SetScaleFactor();
75 void CDasherViewSquare::HandleEvent(int iParameter) {
76 switch (iParameter) {
77 case LP_MARGIN_WIDTH:
78 case BP_NONLINEAR_Y:
79 case LP_NONLINEAR_X:
80 case LP_GEOMETRY:
81 m_bVisibleRegionValid = false;
82 SetScaleFactor();
86 CDasherNode *CDasherViewSquare::Render(CDasherNode *pRoot, myint iRootMin, myint iRootMax,
87 CExpansionPolicy &policy) {
88 DASHER_ASSERT(pRoot != 0);
89 myint iDasherMinX;
90 myint iDasherMinY;
91 myint iDasherMaxX;
92 myint iDasherMaxY;
93 VisibleRegion(iDasherMinX, iDasherMinY, iDasherMaxX, iDasherMaxY);
96 m_iRenderCount = 0;
98 CDasherNode *pOutput = pRoot->Parent();
100 // Blank the region around the root node:
101 if (GetLongParameter(LP_SHAPE_TYPE)==0) { //disjoint rects, so go round root
102 if(iRootMin > iDasherMinY)
103 DasherDrawRectangle(iDasherMaxX, iDasherMinY, iDasherMinX, iRootMin, 0, -1, 0);
105 if(iRootMax < iDasherMaxY)
106 DasherDrawRectangle(iDasherMaxX, iRootMax, iDasherMinX, iDasherMaxY, 0, -1, 0);
108 //to left (greater Dasher X)
109 if (iRootMax - iRootMin < iDasherMaxX)
110 DasherDrawRectangle(iDasherMaxX, std::max(iRootMin,iDasherMinY), iRootMax-iRootMin, std::min(iRootMax,iDasherMaxY), 0, -1, 0);
112 //to right (margin)
113 DasherDrawRectangle(0, iDasherMinY, iDasherMinX, iDasherMaxY, 0, -1, 0);
115 //and render root.
116 DisjointRender(pRoot, iRootMin, iRootMax, NULL, policy, std::numeric_limits<double>::infinity(), pOutput);
117 } else {
118 //overlapping rects/shapes
119 if (pOutput) {
120 //LEFT of Y axis, would be entirely covered by the root node parent (before we render root)
121 // (getColour() gives the right colour, even if pOutput is invisible - in that case it gives
122 // the colour of its parent)
123 DasherDrawRectangle(iDasherMaxX, iDasherMinY, 0, iDasherMaxY, pOutput->getColour(), -1, 0);
124 //RIGHT of Y axis, should be white.
125 DasherDrawRectangle(0, iDasherMinY, iDasherMinX, iDasherMaxY, 0, -1, 0);
126 } else //easy case, whole screen is white (outside root node, e.g. when starting)
127 Screen()->DrawRectangle(0, 0, Screen()->GetWidth(), Screen()->GetHeight(), 0, -1, 0);
128 NewRender(pRoot, iRootMin, iRootMax, NULL, policy, std::numeric_limits<double>::infinity(), pOutput);
131 // Labels are drawn in a second parse to get the overlapping right
132 for (vector<CTextString *>::iterator it=m_DelayedTexts.begin(), E=m_DelayedTexts.end(); it!=E; it++)
133 DoDelayedText(*it);
134 m_DelayedTexts.clear();
136 // Finally decorate the view
137 Crosshair();
138 return pOutput;
141 /// Draw text specified in Dasher co-ordinates. The position is
142 /// specified as two co-ordinates, intended to the be the corners of
143 /// the leading edge of the containing box.
145 CDasherViewSquare::CTextString *CDasherViewSquare::DasherDrawText(myint iDasherMaxX, myint iDasherMidY, CDasherScreen::Label *pLabel, CTextString *pParent, int iColor) {
147 screenint x,y;
148 Dasher2Screen(iDasherMaxX, iDasherMidY, x, y);
150 //compute font size...
151 int iSize = GetLongParameter(LP_DASHER_FONTSIZE);
153 const myint iMaxY(CDasherModel::MAX_Y);
154 if (Screen()->MultiSizeFonts() && iSize>4) {
155 //font size maxes out at ((iMaxY*3)/2)+iMaxY)/iMaxY = 3/2*smallest
156 // which is reached when iDasherMaxX == iMaxY/2, i.e. the crosshair
157 iSize = ((min(iDasherMaxX*3,(iMaxY*3)/2) + iMaxY) * iSize) / iMaxY;
158 } else {
159 //old style fonts; ignore iSize passed-in.
160 myint iLeftTimesFontSize = (iMaxY - iDasherMaxX )*iSize;
161 if(iLeftTimesFontSize < iMaxY * 19/ 20)
162 iSize *= 20;
163 else if(iLeftTimesFontSize < iMaxY * 159 / 160)
164 iSize *= 14;
165 else
166 iSize *= 11;
170 CTextString *pRet = new CTextString(pLabel, x, y, iSize, iColor);
171 vector<CTextString *> &dest(pParent ? pParent->m_children : m_DelayedTexts);
172 dest.push_back(pRet);
173 return pRet;
176 void CDasherViewSquare::DoDelayedText(CTextString *pText) {
178 //note that it'd be better to compute old-style font sizes here, or even after shunting
179 // across according to the aiMax array, but this needs Dasher co-ordinates, which were
180 // more easily available at CTextString creation time. If it really doesn't look as good,
181 // can put in extra calls to Screen2Dasher....
182 screenint x(pText->m_ix), y(pText->m_iy);
183 pair<screenint,screenint> textDims=Screen()->TextSize(pText->m_pLabel, pText->m_iSize);
184 switch (GetOrientation()) {
185 case Dasher::Opts::LeftToRight: {
186 screenint iRight = x + textDims.first;
187 if (iRight < Screen()->GetWidth()) {
188 Screen()->DrawString(pText->m_pLabel, x, y-textDims.second/2, pText->m_iSize, pText->m_iColor);
189 for (vector<CTextString *>::iterator it = pText->m_children.begin(); it!=pText->m_children.end(); it++) {
190 CTextString *pChild=*it;
191 pChild->m_ix = max(pChild->m_ix, iRight);
192 DoDelayedText(pChild);
194 pText->m_children.clear();
196 break;
198 case Dasher::Opts::RightToLeft: {
199 screenint iLeft = x-textDims.first;
200 if (iLeft>=0) {
201 Screen()->DrawString(pText->m_pLabel, iLeft, y-textDims.second/2, pText->m_iSize, pText->m_iColor);
202 for (vector<CTextString *>::iterator it = pText->m_children.begin(); it!=pText->m_children.end(); it++) {
203 CTextString *pChild=*it;
204 pChild->m_ix = min(pChild->m_ix, iLeft);
205 DoDelayedText(*it);
207 pText->m_children.clear();
209 break;
211 case Dasher::Opts::TopToBottom: {
212 screenint iBottom = y + textDims.second;
213 if (iBottom < Screen()->GetHeight()) {
214 Screen()->DrawString(pText->m_pLabel, x-textDims.first/2, y, pText->m_iSize, pText->m_iColor);
215 for (vector<CTextString *>::iterator it = pText->m_children.begin(); it!=pText->m_children.end(); it++) {
216 CTextString *pChild=*it;
217 pChild->m_iy = max(pChild->m_iy, iBottom);
218 DoDelayedText(*it);
220 pText->m_children.clear();
222 break;
224 case Dasher::Opts::BottomToTop: {
225 screenint iTop = y - textDims.second;
226 if (y>=0) {
227 Screen()->DrawString(pText->m_pLabel, x-textDims.first/2, iTop, pText->m_iSize, pText->m_iColor);
228 for (vector<CTextString *>::iterator it = pText->m_children.begin(); it!=pText->m_children.end(); it++) {
229 CTextString *pChild=*it;
230 pChild->m_iy = min(pChild->m_iy, iTop);
231 DoDelayedText(*it);
233 pText->m_children.clear();
235 break;
237 default:
238 break;
240 delete pText;
243 CDasherViewSquare::CTextString::~CTextString() {
244 for (vector<CTextString *>::iterator it = m_children.begin(); it!=m_children.end(); it++)
245 delete *it;
248 void CDasherViewSquare::TruncateTri(myint x, myint y1, myint y2, myint midy1, myint midy2, int fillColor, int outlineColor, int lineWidth) {
249 DASHER_ASSERT (y1<=midy1 && midy1<=midy2 && midy2<=y2);
250 myint iVisibleMinX, iVisibleMaxX, iVisibleMinY, iVisibleMaxY;
251 VisibleRegion(iVisibleMinX, iVisibleMinY, iVisibleMaxX, iVisibleMaxY);
253 myint x1(x), x2(x); //(max)x-coords of the two lines
254 myint tempx1(0),tempx2(0); //& min x-coords
255 //intersect y1's diagonal with screen
256 if (!ClipLineToVisible(x1,midy1,tempx1,y1)) {
257 //entirely offscreen....i.e. off top/bottom
258 //DASHER_ASSERT (midy1 < iVisibleMinY);//no, args undefined if returns false!
259 midy1 = y1 = iVisibleMinY;
260 x1 = min(x1, iVisibleMaxX);
261 tempx1=0;
263 //intersect y2's diagonal with screen
264 if (!ClipLineToVisible(tempx2, y2, x2, midy2)) {
265 //entirely offscreen again, i.e. off bottom/top
266 midy2 = y2 = iVisibleMaxY;
267 x2 = min(x2, iVisibleMaxX);
268 tempx2=0;
270 if (x1!=x2) {
271 //both will be clipped to be <= iVisibleMaxX by above. If they are still
272 // unequal, one must have been further clipped by passing off top/bottom
273 // (i.e., the point of max x is off the top/off the bottom), in which case
274 // the other line is entirely offscreen:
275 DASHER_ASSERT(midy1 == midy2); //point/line of max x has been removed
277 if (x1<x2) {
278 //(0,y1) - (x1,midy1) hit max-y edge of screen
279 //(0,y2) - (x2,midy2) entirely offscreen
280 DASHER_ASSERT(midy1==iVisibleMaxY && y2 == midy2);
281 x2=x1;
282 } else {
283 // (0,y2) - (x2, midy2) hit min-y edge of screen
284 // (0,y1) - (x1,midy1) entirely offscreen
285 DASHER_ASSERT(midy2 == iVisibleMinY && y1 == midy1);
286 x1=x2;
289 // midy1,x1 is now start point
290 vector<CDasherScreen::point> pts(1);
291 Dasher2Screen(x1, midy1, pts[0].x, pts[0].y);
292 DasherLine2Screen(x1, midy1, tempx1, y1, pts);
293 if (tempx1) {
294 //did not reach y axis
295 DASHER_ASSERT(y1 == iVisibleMinY);
296 pts.push_back(CDasherScreen::point());
297 Dasher2Screen(0, iVisibleMinY, pts.back().x, pts.back().y);
299 //that gets us to the min-y (y1) end of the line along the y-axis
301 //add line along y-axis...
302 pts.push_back(CDasherScreen::point());
303 Dasher2Screen(0, y2, pts.back().x, pts.back().y);
305 if (tempx2) {
306 //y2's diagonal did not reach y-axis
307 DASHER_ASSERT(y2 == iVisibleMaxY);
308 pts.push_back(CDasherScreen::point());
309 Dasher2Screen(tempx2, iVisibleMaxY, pts.back().x, pts.back().y);
311 //and the diagonal part...
312 DasherLine2Screen(tempx2, y2, x2, midy2, pts);
314 if (midy1 != midy2) {
315 //is the max-x extent a line, after cropping - i.e. handles both
316 // normal triangle (orig midy1==orig midy2) being cropped to screen edge,
317 // and trunc tri (orig midy1 < orig midy2, possibly cropped) cases
318 DASHER_ASSERT(x1 == x2);
319 pts.push_back(CDasherScreen::point());
320 Dasher2Screen(x1, midy1, pts.back().x, pts.back().y);
321 } else DASHER_ASSERT(pts.back().x == pts[0].x && pts.back().y == pts[0].y);
323 CDasherScreen::point *p_array=new CDasherScreen::point[pts.size()];
324 for (unsigned int i = 0; i<pts.size(); i++) p_array[i] = pts[i];
325 Screen()->Polygon(p_array, pts.size(), fillColor, outlineColor, lineWidth);
326 delete[] p_array;
329 #define sq(X) ((X)*(X))
330 void CDasherViewSquare::Circle(myint Range, myint y1, myint y2, int fCol, int oCol, int lWidth) {
331 std::vector<CDasherScreen::point> pts;
332 myint cy((y1+y2)/2),r(Range/2), x1, x2;
333 myint iDasherMinX, iDasherMinY, iDasherMaxX, iDasherMaxY;
334 VisibleRegion(iDasherMinX, iDasherMinY, iDasherMaxX, iDasherMaxY);
336 CDasherScreen::point p;
337 //run along bottom edge...
338 if (y1 < iDasherMinY) {
339 Dasher2Screen(0, iDasherMinY, p.x, p.y);
340 pts.push_back(p);
341 //intersect with bottom edge
342 x1 = min(iDasherMaxX, myint(sqrt(double(r*r - sq(cy-iDasherMinY)))));
343 y1 = iDasherMinY;
344 } else {
345 x1=0;
347 Dasher2Screen(x1,y1,p.x,p.y);
348 pts.push_back(p);
350 //and along top...
351 if (y2 > iDasherMaxY) {
352 //intersect...
353 x2 = min(iDasherMaxX, myint(sqrt(double(r*r - sq(iDasherMaxY-cy)))));
354 Dasher2Screen(x2, y2=iDasherMaxY, p.x, p.y);
355 //that's target point for end of curved section.
356 if (x2==iDasherMaxX && x1==iDasherMaxX) {
357 //circle entirely covers screen
358 DASHER_ASSERT(y1==iDasherMinY);
359 DasherDrawRectangle(iDasherMaxX, iDasherMinY, 0, iDasherMaxY, fCol, oCol, lWidth);
360 return;
362 //will also need final point at top-right (0,y2 in dasher coords)....
363 } else {
364 Dasher2Screen(x2=0,y2,p.x,p.y);
366 CircleTo(cy,r,y1,x1,y2,x2,p,pts, 2.0);
367 if (iDasherMaxY == y2) {
368 Dasher2Screen(0, iDasherMaxX, p.x, p.y);
369 pts.push_back(p);
371 CDasherScreen::point *p_array = new CDasherScreen::point[pts.size()];
372 for (unsigned int i=0; i<pts.size(); i++) p_array[i] = pts[i];
373 Screen()->Polygon(p_array, pts.size(), fCol, oCol, lWidth);
374 delete[] p_array;
377 void CDasherViewSquare::CircleTo(myint cy, myint r, myint y1, myint x1, myint y3, myint x3, CDasherScreen::point dest, vector<CDasherScreen::point> &pts, double dXMul) {
378 myint y2((y1+y3)/2);
379 myint x2(sqrt(double(sq(r)-sq(cy-y2)))*dXMul);
380 CDasherScreen::point mid; //where midpoint of circle/arc should be
381 Dasher2Screen(x2, y2, mid.x, mid.y); //(except "midpoint" measured along y axis)
382 int lmx=(pts.back().x + dest.x)/2, lmy = (pts.back().y + dest.y)/2; //midpoint of straight line
383 if (abs(dest.y-pts.back().y)<2 || abs(mid.x-lmx) + abs(mid.y-lmy)<2) {
384 //okay, use straight line
385 pts.push_back(dest);
386 } else {
387 CircleTo(cy, r, y1, x1, y2, x2, mid, pts, dXMul);
388 CircleTo(cy, r, y2, x2, y3, x3, dest, pts, dXMul);
391 #undef sq
393 void CDasherViewSquare::DasherSpaceArc(myint cy, myint r, myint x1, myint y1, myint x2, myint y2, int iColour, int iLineWidth) {
394 CDasherScreen::point p;
395 //start point
396 Dasher2Screen(x1, y1, p.x, p.y);
397 vector<CDasherScreen::point> pts;
398 pts.push_back(p);
399 //if circle goes behind crosshair and we want the point of max-x, force division into two sections with that point as boundary
400 if (r>CDasherModel::ORIGIN_X && ((y1 < cy) ^ (y2 < cy))) {
401 Dasher2Screen(r, cy, p.x, p.y);
402 CircleTo(cy, r, y1, x1, cy, r, p, pts, 1.0);
403 x1=r; y1=cy;
405 Dasher2Screen(x2, y2, p.x, p.y);
406 CircleTo(cy, r, y1, x1, y2, x2, p, pts, 1.0);
407 CDasherScreen::point *p_array = new CDasherScreen::point[pts.size()];
408 for (unsigned int i=0; i<pts.size(); i++) p_array[i] = pts[i];
409 Screen()->Polyline(p_array, pts.size(), iLineWidth, iColour);
412 void CDasherViewSquare::Quadric(myint Range, myint lowY, myint highY, int fillColor, int outlineColour, int lineWidth) {
413 static const double RR2=1.0/sqrt(2.0);
414 const int midY=(lowY+highY)/2;
415 #define NUM_STEPS 40
416 CDasherScreen::point p_array[2*NUM_STEPS+2];
417 myint minX,maxX,minY,maxY;
418 VisibleRegion(minX, minY, maxX, maxY);
420 myint x1(0), y1(highY), x2(Range*RR2),y2(highY*RR2 + midY*(1.0-RR2)), x3(Range), y3(midY);
421 for (int i=0; i<=NUM_STEPS; i++) {
422 double f=i/(double)NUM_STEPS, of = 1.0-f;
423 Dasher2Screen(min(maxX,myint(of*of*x1 + 2.0*of*f*x2 + f*f*x3)), max(minY,min(maxY,myint(of*of*y1 + 2.0*of*f*y2 + f*f*y3))), p_array[i].x, p_array[i].y);
427 myint x1(Range), y1(midY), x2(Range*RR2), y2(lowY*RR2 + midY*(1.0-RR2)), x3(0), y3(lowY);
428 for (int i=0; i<=NUM_STEPS; i++) {
429 double f=i/(double)NUM_STEPS, of = 1.0-f;
430 Dasher2Screen(min(maxX,myint(of*of*x1 + 2.0*of*f*x2 + f*f*x3)),max(minY,min(maxY,myint(of*of*y1 + 2.0*of*f*y2 + f*f*y3))), p_array[i+NUM_STEPS+1].x, p_array[i+NUM_STEPS+1].y);
434 Screen()->Polygon(p_array, 2*NUM_STEPS+2, fillColor, outlineColour, lineWidth);
435 #undef NUM_STEPS
438 bool CDasherViewSquare::IsSpaceAroundNode(myint y1, myint y2) {
439 myint iVisibleMinX;
440 myint iVisibleMinY;
441 myint iVisibleMaxX;
442 myint iVisibleMaxY;
444 VisibleRegion(iVisibleMinX, iVisibleMinY, iVisibleMaxX, iVisibleMaxY);
445 const myint maxX(y2-y1);
446 if ((maxX < iVisibleMaxX) || (y1 > iVisibleMinY) || (y2 < iVisibleMaxY))
447 return true; //space around sq => space around anything smaller!
449 //in theory, even if the crosshair is off-screen (!), anything spanning y1-y2 should cover it...
450 DASHER_ASSERT (CoversCrosshair(y2-y1, y1, y2));
452 switch (GetLongParameter(LP_SHAPE_TYPE)) {
453 case 0: //non-overlapping rects
454 case 1: //overlapping rects
455 return false;
456 case 2: { //simple triangles
457 const myint iMidY((y1+y2)/2);
458 return (iMidY < iVisibleMaxY && (y2-iVisibleMaxY)*maxX < iVisibleMaxX*(y2-iMidY))
459 || (iMidY > iVisibleMinY && (iVisibleMinY-y1)*maxX < iVisibleMaxX*(iMidY-y1));
461 case 3: { //truncated triangles
462 const myint y113((y1+y1+y2)/3), y123((y1+y2+y2)/3);
463 return (y123 < iVisibleMaxY && (y2-iVisibleMaxY)*maxX < iVisibleMaxX*(y2-y123))
464 || (y113 > iVisibleMinY && (iVisibleMinY-y1)*maxX < iVisibleMaxX*(y123-y1));
466 case 4: //quadric.
467 //erm. seems hard. fall-through to circle, as it isn't far out -
468 // unfortunately it's not a conservative approximation, the circle
469 // covers the quadric not the other way around, so we'll say the
470 // circle covers the screen when the quadric doesn't :-(. However
471 // atm circles seem better generally so fixing quadrics is a low priority!
472 case 5: { //circle - or rather ellipse, x diameter is twice y diam, hence the *2 to normalize
473 const myint iMidY((y1+y2)/2); //centerX=0, radius = maxX
474 const myint maxYDiff(max(iVisibleMaxY-iMidY,iMidY-iVisibleMinY)*2);
475 return maxYDiff*maxYDiff + iVisibleMaxX*iVisibleMaxX > maxX*maxX;
478 /* NOTREACHED */
479 return false;
482 void CDasherViewSquare::DisjointRender(CDasherNode *pRender, myint y1, myint y2,
483 CTextString *pPrevText, CExpansionPolicy &policy, double dMaxCost,
484 CDasherNode *&pOutput)
486 DASHER_ASSERT_VALIDPTR_RW(pRender);
488 ++m_iRenderCount;
490 // Set the NF_SUPER flag if this node entirely frames the visual
491 // area.
493 myint iDasherMinX;
494 myint iDasherMinY;
495 myint iDasherMaxX;
496 myint iDasherMaxY;
497 VisibleRegion(iDasherMinX, iDasherMinY, iDasherMaxX, iDasherMaxY);
498 pRender->SetFlag(NF_SUPER, (y2-y1 >= iDasherMaxX) && (y1 <= iDasherMinY) && (y2 >= iDasherMaxY));
500 const int myColor = pRender->getColour();
502 if( pRender->getLabel() )
504 const int textColor = GetLongParameter(LP_OUTLINE_WIDTH)<0 ? myColor : 4;
505 myint ny1 = std::min(iDasherMaxY, std::max(iDasherMinY, y1)),
506 ny2 = std::min(iDasherMaxY, std::max(iDasherMinY, y2));
507 CTextString *pText = DasherDrawText(y2-y1, (ny1+ny2)/2, pRender->getLabel(), pPrevText, textColor);
508 if (pRender->bShove()) pPrevText = pText;
511 const myint Range(y2-y1);
513 //Does node cover crosshair?
514 if (pOutput == pRender->Parent() && Range > CDasherModel::ORIGIN_X && y1 < CDasherModel::ORIGIN_Y && y2 > CDasherModel::ORIGIN_Y) {
515 pOutput=pRender;
516 if (pRender->ChildCount()==0) {
517 //covers crosshair! forcibly populate, now!
518 policy.ExpandNode(pRender);
521 if (pRender->ChildCount() == 0) {
522 //allow empty node to be expanded, it's big enough.
523 policy.pushNode(pRender, y1, y2, true, dMaxCost);
524 //and render whole node in one go
525 DasherDrawRectangle(std::min(Range,iDasherMaxX), std::max(y1,iDasherMinY),0, std::min(y2,iDasherMaxY), myColor, -1, 0);
526 //fall through to draw outline
527 } else {
528 //Node has children. It can therefore be collapsed...however,
529 // we don't allow a node covering the crosshair to be collapsed
530 // (at best this'll mean there's nowhere useful to go forwards;
531 // at worst, all kinds of crashes trying to do text output!)
533 //No reason why we can't collapse a game mode node that's too small/offscreen
534 // - we've got its coordinates, and can recreate its children and set their
535 // NF_GAME flags appropriately when it becomes renderable again...
536 if (pRender != pOutput)
537 dMaxCost = policy.pushNode(pRender, y1, y2, false, dMaxCost);
539 // Render children
541 int id=-1;
543 if (CDasherNode *pChild = pRender->onlyChildRendered)
545 //if child still covers screen, render _just_ it and return
546 myint newy1 = y1 + (Range * pChild->Lbnd()) / CDasherModel::NORMALIZATION;
547 myint newy2 = y1 + (Range * pChild->Hbnd()) / CDasherModel::NORMALIZATION;
548 if (newy1 < iDasherMinY && newy2 > iDasherMaxY) {
549 //still covers entire screen. Parent should too...
550 DASHER_ASSERT(dMaxCost == std::numeric_limits<double>::infinity());
552 if (newy2-newy1 < iDasherMaxX) //fill in to it's left...
553 DasherDrawRectangle(std::min(Range,iDasherMaxX), std::max(y1,iDasherMinY), newy2-newy1, std::min(y2,iDasherMaxY), myColor, -1, 0);
554 DisjointRender(pChild, newy1, newy2, pPrevText,
555 policy, dMaxCost, pOutput);
556 //leave pRender->onlyChildRendered set, so remaining children are skipped
558 else
559 pRender->onlyChildRendered = NULL;
562 if (!pRender->onlyChildRendered) {
563 //render all children...
564 myint lasty=y1;
565 for(CDasherNode::ChildMap::const_iterator i = pRender->GetChildren().begin();
566 i != pRender->GetChildren().end(); i++) {
567 id++;
568 CDasherNode *pChild = *i;
570 myint newy1 = y1 + (Range * pChild->Lbnd()) / CDasherModel::NORMALIZATION;
571 myint newy2 = y1 + (Range * pChild->Hbnd()) / CDasherModel::NORMALIZATION;
573 if (pChild->GetFlag(NF_GAME)) {
574 CGameNodeDrawEvent evt(pChild, newy1, newy2);
575 Observable<CGameNodeDrawEvent*>::DispatchEvent(&evt);
577 //switch to "render just one child" mode if all others are off the screen,
578 //and if this _won't_ cause us to avoid rendering a game node...
579 if (newy1 < iDasherMinY && newy2 > iDasherMaxY && (!pRender->GetFlag(NF_GAME) || pChild->GetFlag(NF_GAME))) {
580 DASHER_ASSERT(dMaxCost == std::numeric_limits<double>::infinity());
581 pRender->onlyChildRendered = pChild;
582 if (newy2-newy1 < iDasherMaxX)
583 DasherDrawRectangle(std::min(Range,iDasherMaxX), std::max(y1,iDasherMinY), newy2-newy1, std::min(y2,iDasherMaxY), myColor, -1, 0);
584 DisjointRender(pChild, newy1, newy2, pPrevText, policy, dMaxCost, pOutput);
585 //ensure we don't blank over this child in "finishing off" the parent (!)
586 lasty=newy2;
587 //all remaining children are offscreen. quickly delete, avoid recomputing ranges...
588 while ((++i)!=pRender->GetChildren().end())
589 if (!(*i)->GetFlag(NF_SEEN)) (*i)->Delete_children();
590 break;
591 } else if (newy2-newy1 >= GetLongParameter(LP_MIN_NODE_SIZE) //simple test if big enough
592 && newy1 <= iDasherMaxY && newy2 >= iDasherMinY) //at least partly on screen
594 //child should be rendered!
595 //fill in to its left
596 DasherDrawRectangle(std::min(y2-y1,iDasherMaxX), std::max(newy1,iDasherMinY), std::min(newy2-newy1,iDasherMaxX), std::min(newy2,iDasherMaxY), myColor, -1, 0);
598 if (std::max(lasty,iDasherMinY)<newy1) //fill in interval above child up to the last drawn child
599 DasherDrawRectangle(std::min(Range,iDasherMaxX), std::max(lasty,iDasherMinY),0, std::min(newy1,iDasherMaxY), myColor, -1, 0);
600 lasty = newy2;
601 DisjointRender(pChild, newy1, newy2, pPrevText, policy, dMaxCost, pOutput);
602 } else {
603 // We get here if the node is too small to render or is off-screen.
604 // So, collapse it immediately.
605 if(!pChild->GetFlag(NF_SEEN))
606 pChild->Delete_children();
609 //all children rendered.
610 if (lasty<min(y2,iDasherMaxY)) {
611 // Finish off the drawing process, filling in any part of the parent below the last-rendered child
612 DasherDrawRectangle(std::min(Range,iDasherMaxX), std::max(lasty, iDasherMinY), 0, std::min(y2, iDasherMaxY), myColor, -1, 0);
615 //end rendering children, fall through to outline
617 // Lastly, draw the outline
618 if(GetLongParameter(LP_OUTLINE_WIDTH) && pRender->GetFlag(NF_VISIBLE)) {
619 DasherDrawRectangle(std::min(Range,iDasherMaxX), std::max(y1,iDasherMinY),0, std::min(y2,iDasherMaxY), -1, -1, abs(GetLongParameter(LP_OUTLINE_WIDTH)));
623 bool CDasherViewSquare::CoversCrosshair(myint Range, myint y1, myint y2) {
624 if (Range > CDasherModel::ORIGIN_X && y1 < CDasherModel::ORIGIN_Y && y2 > CDasherModel::ORIGIN_Y) {
625 switch (GetLongParameter(LP_SHAPE_TYPE)) {
626 case 0: //Disjoint rectangles
627 case 1: //Rectangles
628 return true;
629 case 2: { //Triangles
630 myint iMidY((y1+y2)/2);
631 return (iMidY > CDasherModel::ORIGIN_Y)
632 ? ((CDasherModel::ORIGIN_Y-y1)*Range) > (iMidY - y1) * CDasherModel::ORIGIN_X
633 : ((y2-CDasherModel::ORIGIN_Y)*Range) > (y2 - iMidY) * CDasherModel::ORIGIN_X;
635 case 3: { //Truncated tris
636 myint midy1((y1+y1+y2)/3), midy2((y1+y2+y2)/3);
637 if (midy1 > CDasherModel::ORIGIN_Y) //(0,y1) - (Range,midy1)
638 return (CDasherModel::ORIGIN_Y-y1)*Range > (midy1 - y1) * CDasherModel::ORIGIN_X;
639 if (midy2 > CDasherModel::ORIGIN_Y) // (Range,midy1) - (Range,midy2)
640 return true;
641 return (y2 - CDasherModel::ORIGIN_Y)*Range > (y2 - midy2) * CDasherModel::ORIGIN_X;
642 break;
644 case 4: //quadrics. We'll approximate with circles, as they're easier...
645 // however, note that the circle is bigger, so this'll output things
646 // too soon/aggressively :-(.
647 // (hence, fallthrough to:)
648 case 5: { //circles - actually ellipses, as x diameter is twice y diameter, hence the *4
649 const myint y_dist(CDasherModel::ORIGIN_Y - (y1+y2)/2);
650 return CDasherModel::ORIGIN_X * CDasherModel::ORIGIN_X + y_dist*y_dist*4 < Range*Range;
654 return false;
657 void CDasherViewSquare::NewRender(CDasherNode *pRender, myint y1, myint y2,
658 CTextString *pPrevText, CExpansionPolicy &policy, double dMaxCost,
659 CDasherNode *&pOutput)
661 //when we have only one child node to render, which'll be the last thing we
662 // do before returning, we make a tail call by jumping here, rather than
663 // pushing another stack frame:
664 beginning:
665 DASHER_ASSERT_VALIDPTR_RW(pRender);
667 ++m_iRenderCount;
669 // Set the NF_SUPER flag if this node entirely frames the visual
670 // area.
672 myint iDasherMinX;
673 myint iDasherMinY;
674 myint iDasherMaxX;
675 myint iDasherMaxY;
676 VisibleRegion(iDasherMinX, iDasherMinY, iDasherMaxX, iDasherMaxY);
677 pRender->SetFlag(NF_SUPER, !IsSpaceAroundNode(y1, y2));
679 const int myColor = pRender->getColour();
681 if( pRender->getLabel() )
683 const int textColor = GetLongParameter(LP_OUTLINE_WIDTH)<0 ? myColor : 4;
684 myint ny1 = std::min(iDasherMaxY, std::max(iDasherMinY, y1)),
685 ny2 = std::min(iDasherMaxY, std::max(iDasherMinY, y2));
686 CTextString *pText = DasherDrawText(y2-y1, (ny1+ny2)/2, pRender->getLabel(), pPrevText, textColor);
687 if (pRender->bShove()) pPrevText = pText;
690 const myint Range(y2-y1);
691 // Draw node...we can both fill & outline in one go, since
692 // we're drawing the whole node at once (unlike disjoint-rects),
693 // as any part of the outline which is obscured by a child node,
694 // will have the outline of the child node painted over it,
695 // and all outlines are the same colour.
697 //"invisible" nodes are given same colour as parent, so we neither fill
698 // nor outline them. TODO this isn't quite right, as nodes that are
699 // _supposed_ to be the same colour as their parent, will have no outlines...
700 // (thankfully having 2 "phases" means this doesn't happen in standard
701 // colour schemes)
702 if (pRender->GetFlag(NF_VISIBLE)) {
703 //outline width 0 = fill only; >0 = fill + outline; <0 = outline only
704 int fillColour = GetLongParameter(LP_OUTLINE_WIDTH)>=0 ? myColor : -1;
705 int lineWidth = abs(GetLongParameter(LP_OUTLINE_WIDTH));
706 switch (GetLongParameter(LP_SHAPE_TYPE)) {
707 case 1: //overlapping rects
708 DasherDrawRectangle(std::min(Range,iDasherMaxX), std::max(y1,iDasherMinY), 0, std::min(y2,iDasherMaxY), fillColour, -1, lineWidth);
709 break;
710 case 2: //simple triangles
711 TruncateTri(Range, y1, y2, (y1+y2)/2, (y1+y2)/2, fillColour, -1, lineWidth);
712 break;
713 case 3: //truncated triangles
714 TruncateTri(Range, y1, y2, (y1+y1+y2)/3, (y1+y2+y2)/3, fillColour, -1, lineWidth);
715 break;
716 case 4:
717 Quadric(Range, y1, y2, fillColour, -1, lineWidth);
718 break;
719 case 5:
720 Circle(Range, y1, y2, fillColour, -1, lineWidth);
721 break;
725 //Does node cover crosshair?
726 if (pOutput == pRender->Parent() && CoversCrosshair(Range, y1, y2))
727 pOutput = pRender;
729 if (pRender->ChildCount() == 0) {
730 if (pOutput==pRender) {
731 //covers crosshair! forcibly populate, now!
732 policy.ExpandNode(pRender);
733 } else {
734 //allow empty node to be expanded, it's big enough.
735 policy.pushNode(pRender, y1, y2, true, dMaxCost);
736 return; //no children atm => nothing more to do
738 } else {
739 //Node has children. It can therefore be collapsed...however,
740 // we don't allow a node covering the crosshair to be collapsed
741 // (at best this'll mean there's nowhere useful to go forwards;
742 // at worst, all kinds of crashes trying to do text output!)
744 //No reason why we can't collapse a game mode node that's too small/offscreen
745 // - we've got its coordinates, and can recreate its children and set their
746 // NF_GAME flags appropriately when it becomes renderable again...
747 if (pRender != pOutput)
748 dMaxCost = policy.pushNode(pRender, y1, y2, false, dMaxCost);
750 //Node has children - either it already did, or else it covers the crosshair,
751 // and we've just made them...so render them.
753 //first check if there's only one child we need to render
754 if (CDasherNode *pChild = pRender->onlyChildRendered) {
755 //if child still covers screen, render _just_ it and return
756 myint newy1 = y1 + (Range * pChild->Lbnd()) / CDasherModel::NORMALIZATION;
757 myint newy2 = y1 + (Range * pChild->Hbnd()) / CDasherModel::NORMALIZATION;
758 if (
759 (newy1 < iDasherMinY && newy2 > iDasherMaxY)) { //covers entire y-axis!
760 //render just that child; nothing more to do for this node => tail call to beginning
761 pRender = pChild; y1=newy1; y2=newy2;
762 goto beginning;
764 pRender->onlyChildRendered = NULL;
767 //ok, need to render all children...
768 myint newy1=y1,newy2;
769 CDasherNode::ChildMap::const_iterator I = pRender->GetChildren().begin(), E = pRender->GetChildren().end();
770 while (I!=E) {
771 CDasherNode *pChild(*I);
773 newy2 = y1 + (Range * pChild->Hbnd()) / CDasherModel::NORMALIZATION;
774 if (pChild->GetFlag(NF_GAME)) {
775 CGameNodeDrawEvent evt(pChild, newy1, newy2);
776 Observable<CGameNodeDrawEvent*>::DispatchEvent(&evt);
778 if (newy1<=iDasherMaxY && newy2 >= iDasherMinY) { //onscreen
779 if (newy2-newy1 > GetLongParameter(LP_MIN_NODE_SIZE)) {
780 //definitely big enough to render.
781 NewRender(pChild, newy1, newy2, pPrevText, policy, dMaxCost, pOutput);
782 } else if (!pChild->GetFlag(NF_SEEN)) pChild->Delete_children();
783 if (newy2>iDasherMaxY && !pRender->GetFlag(NF_GAME)) {
784 //remaining children offscreen and no game-mode child we might skip
785 // (among the remainder, or any previous off the top of the screen)
786 if (newy1 < iDasherMinY) pRender->onlyChildRendered = pChild; //previous children also offscreen!
787 break; //skip remaining children
790 I++;
791 newy1=newy2;
793 if (I!=E) {
794 //broke out of loop. Possibly more to delete...
795 while (++I!=E) if (!(*I)->GetFlag(NF_SEEN)) (*I)->Delete_children();
797 //all children rendered.
800 /// Convert screen co-ordinates to dasher co-ordinates. This doesn't
801 /// include the nonlinear mapping for eyetracking mode etc - it is
802 /// just the inverse of the mapping used to calculate the screen
803 /// positions of boxes etc.
805 void CDasherViewSquare::Screen2Dasher(screenint iInputX, screenint iInputY, myint &iDasherX, myint &iDasherY) {
807 // Things we're likely to need:
809 screenint iScreenWidth = Screen()->GetWidth();
810 screenint iScreenHeight = Screen()->GetHeight();
812 switch(GetOrientation()) {
813 case Dasher::Opts::LeftToRight:
814 iDasherX = ( iScreenWidth - iInputX ) * SCALE_FACTOR / iScaleFactorX;
815 iDasherY = CDasherModel::MAX_Y / 2 + ( iInputY - iScreenHeight / 2 ) * SCALE_FACTOR / iScaleFactorY;
816 break;
817 case Dasher::Opts::RightToLeft:
818 iDasherX = myint( ( iInputX ) * SCALE_FACTOR / iScaleFactorX);
819 iDasherY = myint(CDasherModel::MAX_Y / 2 + ( iInputY - iScreenHeight / 2 ) * SCALE_FACTOR / iScaleFactorY);
820 break;
821 case Dasher::Opts::TopToBottom:
822 iDasherX = myint( ( iScreenHeight - iInputY ) * SCALE_FACTOR / iScaleFactorX);
823 iDasherY = myint(CDasherModel::MAX_Y / 2 + ( iInputX - iScreenWidth / 2 ) * SCALE_FACTOR / iScaleFactorY);
824 break;
825 case Dasher::Opts::BottomToTop:
826 iDasherX = myint( ( iInputY ) * SCALE_FACTOR / iScaleFactorX);
827 iDasherY = myint(CDasherModel::MAX_Y / 2 + ( iInputX - iScreenWidth / 2 ) * SCALE_FACTOR / iScaleFactorY);
828 break;
829 default:
830 break;
833 iDasherX = ixmap(iDasherX);
834 iDasherY = iymap(iDasherY);
838 void CDasherViewSquare::SetScaleFactor( void )
840 //Parameters for X non-linearity.
841 // Set some defaults here, in case we change(d) them later...
842 m_iXlogThres=CDasherModel::MAX_Y/2; //threshold: DasherX's less than this are linear; those greater are logarithmic
844 //set log scaling coefficient (unused if LP_NONLINEAR_X==0)
845 // note previous value = 1/0.2, i.e. a value of LP_NONLINEAR_X =~= 4.8
846 m_dXlogCoeff = exp(GetLongParameter(LP_NONLINEAR_X)/3.0);
848 const bool bHoriz(GetOrientation() == Dasher::Opts::LeftToRight || GetOrientation() == Dasher::Opts::RightToLeft);
849 const screenint iScreenWidth(Screen()->GetWidth()), iScreenHeight(Screen()->GetHeight());
850 const double dPixelsX(bHoriz ? iScreenWidth : iScreenHeight), dPixelsY(bHoriz ? iScreenHeight : iScreenWidth);
852 //Defaults/starting values, will be modified later according to scheme in use...
853 iMarginWidth = GetLongParameter(LP_MARGIN_WIDTH);
854 double dScaleFactorY(dPixelsY / CDasherModel::MAX_Y );
855 double dScaleFactorX(dPixelsX / static_cast<double>(CDasherModel::MAX_Y + iMarginWidth) );
857 switch (GetLongParameter(LP_GEOMETRY)) {
858 case 0: {
859 //old style
860 if (dScaleFactorX < dScaleFactorY) {
861 //fewer (pixels per dasher coord) in X direction - i.e., X is more compressed.
862 //So, use X scale for Y too...except first, we'll _try_ to reduce the difference
863 // by changing the relative scaling of X and Y (by at most 20%):
864 double dMul = max(0.8, dScaleFactorX / dScaleFactorY);
865 dScaleFactorY = std::max(dScaleFactorX/dMul, dScaleFactorY / 4.0);
866 dScaleFactorX *= 0.9;
867 iMarginWidth = (CDasherModel::MAX_Y/20.0 + iMarginWidth*0.95)/0.9;
868 } else {
869 //X has more room; use Y scale for both -> will get lots history
870 // however, "compensate" by relaxing the default "relative scaling" of X
871 // (normally only 90% of Y) towards 1...
872 double dXmpc = std::min(1.0,0.9 * dScaleFactorX / dScaleFactorY);
873 dScaleFactorX = max(dScaleFactorY, dScaleFactorX / 4.0)*dXmpc;
874 iMarginWidth = (iMarginWidth + dPixelsX/dScaleFactorX - CDasherModel::MAX_Y)/2;
876 break;
878 //all new styles fix the y axis the way we want it (i.e. leave as above),
879 // and just do different things with x...
880 case 1:
881 //square with x-hair possibly offscreen
882 dScaleFactorX = dScaleFactorY;
883 break;
884 case 2:
885 case 3: {
886 //2 or 3 => squish x (so xhair always visible)
887 const double dDesiredXPerPixel( (CDasherModel::MAX_Y + iMarginWidth) / dPixelsX), dMinXPerPixel((CDasherModel::ORIGIN_X+iMarginWidth)/dPixelsX);
888 const double dAspect(1.0/dScaleFactorY/dDesiredXPerPixel);
889 double dDasherXPerPixel( (dAspect<1.0)
890 ? (dMinXPerPixel+pow(dAspect,3.0)*(dDesiredXPerPixel-dMinXPerPixel)) //tall+thin
891 : (1.0/dScaleFactorY)); //square or wide+low
892 iMarginWidth /= 0.9; //this comes from the old scaling by m_dXmpc=0.9. Drop in new scheme?
893 if (GetLongParameter(LP_GEOMETRY)==3) {
894 //make whole screen logarithmic (but keep xhair in same place)
895 myint crosshair(xmap(2048)); //should be 2048...
896 m_iXlogThres=0;
897 dDasherXPerPixel *= xmap(2048)/static_cast<double>(crosshair);
899 dScaleFactorX = 0.9 / dDasherXPerPixel;
902 iScaleFactorX = myint(dScaleFactorX * SCALE_FACTOR);
903 iScaleFactorY = myint(dScaleFactorY * SCALE_FACTOR);
905 #ifdef DEBUG
906 //test...
907 for (screenint x=0; x<iScreenWidth; x++) {
908 dasherint dx, dy;
909 Screen2Dasher(x, 0, dx, dy);
910 screenint fx, fy;
911 Dasher2Screen(dx, dy, fx, fy);
912 if (fx!=x)
913 std::cout << "ERROR ScreenX " << x << " becomes " << dx << " back to " << fx << std::endl;;
915 for (screenint y=0; y<iScreenHeight; y++) {
916 dasherint dx,dy;
917 Screen2Dasher(0, y, dx, dy);
918 screenint fx,fy;
919 Dasher2Screen(dx, dy, fx, fy);
920 if (fy!=y)
921 std::cout << "ERROR ScreenY " << y << " becomes " << dy << " back to " << fy << std::endl;
923 #endif
925 //notify listeners that coordinates have changed...
926 Observable<CDasherView*>::DispatchEvent(this);
930 inline myint CDasherViewSquare::CustomIDivScaleFactor(myint iNumerator) {
931 // Integer division rounding away from zero
933 long long int num, denom, quot, rem;
934 myint res;
936 num = iNumerator;
937 denom = SCALE_FACTOR;
939 DASHER_ASSERT(denom != 0);
941 #ifdef HAVE_LLDIV
942 lldiv_t ans = ::lldiv(num, denom);
944 quot = ans.quot;
945 rem = ans.rem;
946 #else
947 quot = num / denom;
948 rem = num % denom;
949 #endif
951 if (rem < 0)
952 res = quot - 1;
953 else if (rem > 0)
954 res = quot + 1;
955 else
956 res = quot;
958 return res;
960 // return (iNumerator + iDenominator - 1) / iDenominator;
963 void CDasherViewSquare::Dasher2Screen(myint iDasherX, myint iDasherY, screenint &iScreenX, screenint &iScreenY) {
965 // Apply the nonlinearities
967 iDasherX = xmap(iDasherX);
968 iDasherY = ymap(iDasherY);
970 // Things we're likely to need:
972 screenint iScreenWidth = Screen()->GetWidth();
973 screenint iScreenHeight = Screen()->GetHeight();
975 // Note that integer division is rounded *away* from zero here to
976 // ensure that this really is the inverse of the map the other way
977 // around.
979 switch( GetOrientation() ) {
980 case Dasher::Opts::LeftToRight:
981 iScreenX = screenint(iScreenWidth -
982 CustomIDivScaleFactor(iDasherX * iScaleFactorX));
983 iScreenY = screenint(iScreenHeight / 2 +
984 CustomIDivScaleFactor(( iDasherY - CDasherModel::MAX_Y / 2 ) * iScaleFactorY));
985 break;
986 case Dasher::Opts::RightToLeft:
987 iScreenX = screenint(CustomIDivScaleFactor(iDasherX * iScaleFactorX));
988 iScreenY = screenint(iScreenHeight / 2 +
989 CustomIDivScaleFactor( (iDasherY - CDasherModel::MAX_Y/2) * iScaleFactorY));
990 break;
991 case Dasher::Opts::TopToBottom:
992 iScreenX = screenint(iScreenWidth / 2 +
993 CustomIDivScaleFactor( (iDasherY - CDasherModel::MAX_Y/2) * iScaleFactorY));
994 iScreenY = screenint(iScreenHeight -
995 CustomIDivScaleFactor( iDasherX * iScaleFactorX ));
996 break;
997 case Dasher::Opts::BottomToTop:
998 iScreenX = screenint(iScreenWidth / 2 +
999 CustomIDivScaleFactor(( iDasherY - CDasherModel::MAX_Y/2 ) * iScaleFactorY));
1000 iScreenY = screenint(CustomIDivScaleFactor( iDasherX * iScaleFactorX ));
1001 break;
1002 default:
1003 break;
1007 void CDasherViewSquare::Dasher2Polar(myint iDasherX, myint iDasherY, double &r, double &theta) {
1008 iDasherX = xmap(iDasherX);
1009 iDasherY = ymap(iDasherY);
1011 myint iDasherOX = xmap(CDasherModel::ORIGIN_X);
1012 myint iDasherOY = ymap(CDasherModel::ORIGIN_Y);
1014 double x = -(iDasherX - iDasherOX) / double(iDasherOX); //Use normalised coords so min r works
1015 double y = -(iDasherY - iDasherOY) / double(iDasherOY);
1016 theta = atan2(y, x);
1017 r = sqrt(x * x + y * y);
1020 void CDasherViewSquare::DasherLine2Screen(myint x1, myint y1, myint x2, myint y2, vector<CDasherScreen::point> &vPoints) {
1021 if (x1!=x2 && y1!=y2) { //only diagonal lines ever get changed...
1022 if (GetBoolParameter(BP_NONLINEAR_Y)) {
1023 if ((y1 < m_Y3 && y2 > m_Y3) ||(y2 < m_Y3 && y1 > m_Y3)) {
1024 //crosses bottom non-linearity border
1025 myint x_mid = x1+(x2-x1) * (m_Y3-y1)/(y2-y1);
1026 DasherLine2Screen(x1, y1, x_mid, m_Y3, vPoints);
1027 x1=x_mid; y1=m_Y3;
1028 }//else //no, a single line might cross _both_ borders!
1029 if ((y1 > m_Y2 && y2 < m_Y2) || (y2 > m_Y2 && y1 < m_Y2)) {
1030 //crosses top non-linearity border
1031 myint x_mid = x1 + (x2-x1) * (m_Y2-y1)/(y2-y1);
1032 DasherLine2Screen(x1, y1, x_mid, m_Y2, vPoints);
1033 x1=x_mid; y1=m_Y2;
1036 if (GetLongParameter(LP_NONLINEAR_X) && (x1 > m_iXlogThres || x2 > m_iXlogThres)) {
1037 //into logarithmic section
1038 CDasherScreen::point pStart, pScreenMid, pEnd;
1039 Dasher2Screen(x2, y2, pEnd.x, pEnd.y);
1040 for(;;) {
1041 Dasher2Screen(x1, y1, pStart.x, pStart.y);
1042 //a straight line on the screen between pStart and pEnd passes through pScreenMid:
1043 pScreenMid.x = (pStart.x + pEnd.x)/2;
1044 pScreenMid.y = (pStart.y + pEnd.y)/2;
1045 //whereas a straight line _in_Dasher_space_ passes through pDasherMid:
1046 myint xMid=(x1+x2)/2, yMid=(y1+y2)/2;
1047 CDasherScreen::point pDasherMid;
1048 Dasher2Screen(xMid, yMid, pDasherMid.x, pDasherMid.y);
1050 //since we know both endpoints are in the same section of the screen wrt. Y nonlinearity,
1051 //the midpoint along the DasherY axis of both lines should be the same.
1052 if (GetOrientation()==Dasher::Opts::LeftToRight || GetOrientation()==Dasher::Opts::RightToLeft) {
1053 DASHER_ASSERT(abs(pDasherMid.y - pScreenMid.y)<=1);//allow for rounding error
1054 if (abs(pDasherMid.x - pScreenMid.x)<=1) break; //call a straight line accurate enough
1055 } else {
1056 DASHER_ASSERT(abs(pDasherMid.x - pScreenMid.x)<=1);
1057 if (abs(pDasherMid.y - pScreenMid.y)<=1) break;
1059 //line should appear bent. Subdivide!
1060 DasherLine2Screen(x1,y1,xMid,yMid,vPoints); //recurse for first half (to Dasher-space midpoint)
1061 if (x1==xMid || y1 == yMid) break; // as test on entry, only diagonal lines need to be bent...
1062 x1=xMid; y1=yMid; //& loop round for second half
1064 //broke out of loop. a straight line (x1,y1)-(x2,y2) on the screen is an accurate portrayal of a straight line in Dasher-space.
1065 vPoints.push_back(pEnd);
1066 return;
1068 //ok, not in x nonlinear section; fall through.
1070 #ifdef DEBUG
1071 CDasherScreen::point pTest;
1072 Dasher2Screen(x1, y1, pTest.x, pTest.y);
1073 DASHER_ASSERT(vPoints.back().x == pTest.x && vPoints.back().y == pTest.y);
1074 #endif
1075 CDasherScreen::point p;
1076 Dasher2Screen(x2, y2, p.x, p.y);
1077 vPoints.push_back(p);
1080 void CDasherViewSquare::VisibleRegion( myint &iDasherMinX, myint &iDasherMinY, myint &iDasherMaxX, myint &iDasherMaxY ) {
1081 // TODO: Change output parameters to pointers and allow NULL to mean
1082 // 'I don't care'. Need to be slightly careful about this as it will
1083 // require a slightly more sophisticated caching mechanism
1085 if(!m_bVisibleRegionValid) {
1087 switch( GetOrientation() ) {
1088 case Dasher::Opts::LeftToRight:
1089 Screen2Dasher(Screen()->GetWidth(),0,m_iDasherMinX,m_iDasherMinY);
1090 Screen2Dasher(0,Screen()->GetHeight(),m_iDasherMaxX,m_iDasherMaxY);
1091 break;
1092 case Dasher::Opts::RightToLeft:
1093 Screen2Dasher(0,0,m_iDasherMinX,m_iDasherMinY);
1094 Screen2Dasher(Screen()->GetWidth(),Screen()->GetHeight(),m_iDasherMaxX,m_iDasherMaxY);
1095 break;
1096 case Dasher::Opts::TopToBottom:
1097 Screen2Dasher(0,Screen()->GetHeight(),m_iDasherMinX,m_iDasherMinY);
1098 Screen2Dasher(Screen()->GetWidth(),0,m_iDasherMaxX,m_iDasherMaxY);
1099 break;
1100 case Dasher::Opts::BottomToTop:
1101 Screen2Dasher(0,0,m_iDasherMinX,m_iDasherMinY);
1102 Screen2Dasher(Screen()->GetWidth(),Screen()->GetHeight(),m_iDasherMaxX,m_iDasherMaxY);
1103 break;
1104 default:
1105 break;
1108 m_bVisibleRegionValid = true;
1111 iDasherMinX = m_iDasherMinX;
1112 iDasherMaxX = m_iDasherMaxX;
1113 iDasherMinY = m_iDasherMinY;
1114 iDasherMaxY = m_iDasherMaxY;
1117 // void CDasherViewSquare::NewDrawGoTo(myint iDasherMin, myint iDasherMax, bool bActive) {
1118 // myint iHeight(iDasherMax - iDasherMin);
1120 // int iColour;
1121 // int iWidth;
1123 // if(bActive) {
1124 // iColour = 1;
1125 // iWidth = 3;
1126 // }
1127 // else {
1128 // iColour = 2;
1129 // iWidth = 1;
1130 // }
1132 // CDasherScreen::point p[4];
1134 // Dasher2Screen( 0, iDasherMin, p[0].x, p[0].y);
1135 // Dasher2Screen( iHeight, iDasherMin, p[1].x, p[1].y);
1136 // Dasher2Screen( iHeight, iDasherMax, p[2].x, p[2].y);
1137 // Dasher2Screen( 0, iDasherMax, p[3].x, p[3].y);
1139 // Screen()->Polyline(p, 4, iWidth, iColour);
1140 // }
1142 void CDasherViewSquare::ScreenResized(CDasherScreen *NewScreen) {
1143 m_bVisibleRegionValid = false;
1144 SetScaleFactor();