1 /*
2  * Copyright (C) 2002 - David W. Durham
3  *
4  * This file is part of ReZound, an audio editing application.
5  *
6  * ReZound is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published
8  * by the Free Software Foundation; either version 2 of the License,
9  * or (at your option) any later version.
10  *
11  * ReZound is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
19  */
20 
21 #include "FXGraphParamValue.h"
22 
23 #include <exception>
24 #include <stdexcept>
25 #include <string>
26 #include <algorithm>
27 
28 #include <istring>
29 
30 #include <CNestedDataFile/CNestedDataFile.h>
31 
32 #include "utils.h"
33 
34 #include "../backend/CSound.h"
35 
36 #include "settings.h"
37 
38 #include "CFOXIcons.h"
39 
40 #define NODE_RADIUS 4
41 
42 /* TODO:
43  *
44  *	- I probably need to limit the minimum size as it could cause divide by zero errors, and really be unusable anyway
45  *
46  * 	- I would have liked to reuse code (without copy/pasting) from FXRezWaveView, but it's currently
47  * 	  too much tied to CLoadedSound for start and stop positions and such...
48  * 	  - At least I pulled draw portion out not to have to rewrite that
49  * 	  - but it would be nice if all the scrolling and zooming capabilities where also there....
50  *
51  * 	- I should also onMouseMove print the time that the cursor is over
52  * 		- clear it onExit
53  *
54  * 	- draw a vertical to the time and a horizontal line to the ruler while dragging the nodes
55  *
56  * 	- still a glitch with the drawing of the bottom tick in the ruler.. I might draw more values in the ticks
57  * 		I think I've fixed this now
58  *
59  *   	- I might ought to draw the play position in there if I can get the event
60  *
61  *	- I might want to avoid redrawing everything.  Alternatively I could:
62  *         - blit only the sections between the nodes involving the change
63  *         	I think I do now ???
64  *
65  */
66 
67 
68 // --- declaration of CHorzRuler -----------------------------------------------
69 
70 class CHorzRuler : public FXHorizontalFrame
71 {
72 	FXDECLARE(CHorzRuler)
73 public:
CHorzRuler(FXComposite * p,FXGraphParamValue * _parent)74 	CHorzRuler(FXComposite *p,FXGraphParamValue *_parent) :
75 		FXHorizontalFrame(p,FRAME_NONE | LAYOUT_FILL_X | LAYOUT_FIX_HEIGHT | LAYOUT_SIDE_TOP, 0,0,0,17),
76 
77 		parent(_parent),
78 
79 		font(getApp()->getNormalFont())
80 	{
81 		enable();
82 		flags|=FLAG_SHOWN; // I have to do this, or it will not show up.. like height is 0 or something
83 
84 		FXFontDesc d;
85 		font->getFontDesc(d);
86 		d.weight=FONTWEIGHT_LIGHT;
87 		d.size=65;
88 		font=new FXFont(getApp(),d);
89 	}
90 
~CHorzRuler()91 	virtual ~CHorzRuler()
92 	{
93 		delete font;
94 	}
95 
96 
create()97 	virtual void create()
98 	{
99 		FXHorizontalFrame::create();
100 		font->create();
101 	}
102 
103 	// events I get by message
104 
onPaint(FXObject * object,FXSelector sel,void * ptr)105 	long onPaint(FXObject *object,FXSelector sel,void *ptr)
106 	{
107 		FXHorizontalFrame::onPaint(object,sel,ptr);
108 
109 		FXEvent *ev=(FXEvent*)ptr;
110 
111 		FXDCWindow dc(this,ev);
112 		dc.setForeground(FXRGB(20,20,20));
113 		dc.compat_setFont(font);
114 
115 		#define H_TICK_FREQ 50
116 
117 		// N is the number of ticks to label
118 		int N=(parent->getGraphPanelWidth()/H_TICK_FREQ); // we lable always an odd number of ticks so there is always a middle tick
119 		if(N%2==0)
120 			N++;
121 
122 		// make sure there's at least 3
123 		if(N<3)
124 			N=3;
125 
126 		const int s=0;
127 		const int e=parent->getGraphPanelWidth()-1;
128 
129 		// x actually goes from the event's min-10 to max+10 incase part of some text needs be be redrawn (and we limit it to 0..height)
130 		const int maxX=min(parent->getGraphPanelWidth(),(ev->rect.w+ev->rect.x)+10);
131 		for(int x=max(0,(ev->rect.x)-10);x<=maxX;x++)
132 		{
133 			// this is, y=s+(((e-s)*t)/(N-1)) [0..N interpolated across s..e] solved for t, and then we get the remainder of the resulting division instead of the actual quotient)
134 			const double t=fmod((double)((x-s)*(N-1)),(double)(e-s));
135 			const int renderX=x+parent->vertRuler->getWidth();
136 
137 			if(t<(N-1))
138 			{ // draw and label this tick
139 				dc.drawLine(renderX,getHeight()-2,renderX,getHeight()-10);
140 				//dc.drawLine(renderX+H_TICK_FREQ/2,getHeight()-2,renderX+H_TICK_FREQ/2,getHeight()-4);
141 
142 				const string sValue=parent->getHorzValueString(parent->screenToNodeHorzValue(x,false));
143 
144 				int offset=font->getTextWidth(sValue.c_str(),sValue.length()); // put text left of the tick mark
145 
146 				dc.drawText(renderX-offset-1,font->getFontHeight()+2,sValue.c_str(),sValue.length());
147 			}
148 			// else, every 2 ticks (i.e. in between labled ticks)
149 				//dc.drawLine(getWidth()-2,renderY,getWidth()-5,renderY); // half way tick between labled ticks
150 		}
151 
152 		return 0;
153 	}
154 
155 protected:
CHorzRuler()156 	CHorzRuler() {}
157 
158 private:
159 	FXGraphParamValue *parent;
160 
161 	FXFont *font;
162 
163 };
164 
165 FXDEFMAP(CHorzRuler) CHorzRulerMap[]=
166 {
167 	//Message_Type				ID				Message_Handler
168 	FXMAPFUNC(SEL_PAINT,			0,				CHorzRuler::onPaint),
169 };
170 
171 FXIMPLEMENT(CHorzRuler,FXHorizontalFrame,CHorzRulerMap,ARRAYNUMBER(CHorzRulerMap))
172 
173 
174 
175 // --- declaration of CVertRuler -----------------------------------------------
176 
177 class CVertRuler : public FXHorizontalFrame
178 {
179 	FXDECLARE(CVertRuler)
180 public:
CVertRuler(FXComposite * p,FXGraphParamValue * _parent)181 	CVertRuler(FXComposite *p,FXGraphParamValue *_parent) :
182 		FXHorizontalFrame(p,FRAME_NONE | LAYOUT_FILL_Y | LAYOUT_FIX_WIDTH | LAYOUT_SIDE_LEFT, 0,0,42),
183 
184 		parent(_parent),
185 
186 		font(getApp()->getNormalFont())
187 	{
188 		enable();
189 		flags|=FLAG_SHOWN; // I have to do this, or it will not show up.. like height is 0 or something
190 
191 		FXFontDesc d;
192 		font->getFontDesc(d);
193 		d.weight=FONTWEIGHT_LIGHT;
194 		d.size=65;
195 		font=new FXFont(getApp(),d);
196 	}
197 
~CVertRuler()198 	virtual ~CVertRuler()
199 	{
200 		delete font;
201 	}
202 
create()203 	virtual void create()
204 	{
205 		FXHorizontalFrame::create();
206 		font->create();
207 	}
208 
209 	// events I get by message
210 
onPaint(FXObject * object,FXSelector sel,void * ptr)211 	long onPaint(FXObject *object,FXSelector sel,void *ptr)
212 	{
213 		FXHorizontalFrame::onPaint(object,sel,ptr);
214 
215 		FXEvent *ev=(FXEvent*)ptr;
216 
217 		FXDCWindow dc(this,ev);
218 		dc.setForeground(FXRGB(20,20,20));
219 		dc.compat_setFont(font);
220 
221 		#define V_TICK_FREQ 20
222 
223 		// N is the number of ticks to label
224 		int N=(parent->getGraphPanelHeight()/V_TICK_FREQ); // we lable always an odd number of ticks so there is always a middle tick
225 		if(N%2==0)
226 			N++;
227 
228 		// make sure there's at least 3
229 		if(N<3)
230 			N=3;
231 
232 		const int s=0;
233 		const int e=parent->getGraphPanelHeight()-1;
234 
235 		// y actually goes from the event's min-10 to max+10 incase part of some text needs be be redrawn (and we limit it to 0..height)
236 		const int maxY=min(parent->getGraphPanelHeight(),(ev->rect.h+ev->rect.y)+10);
237 		for(int y=max(0,(ev->rect.y)-10);y<=maxY;y++)
238 		{
239 			// this is, y=s+(((e-s)*t)/(N-1)) [0..N interpolated across s..e] solved for t, and then we get the remainder of the resulting division instead of the actual quotient)
240 			const double t=fmod((double)((y-s)*(N-1)),(double)(e-s));
241 			const int renderY=y+(((FXPacker *)(parent->graphCanvas->getParent()))->getPadTop());
242 
243 			if(t<(N-1))
244 			{ // draw and label this tick
245 				dc.drawLine(getWidth()-2,renderY,getWidth()-10,renderY);
246 
247 				const string s=parent->getVertValueString(parent->screenToNodeVertValue(y,false));
248 
249 				int yoffset=font->getFontHeight(); // put text below the tick mark
250 				int xoffset=font->getTextWidth(s.c_str(),s.length());
251 				dc.drawText(getWidth()-xoffset-7,renderY+yoffset,s.c_str(),s.length());
252 			}
253 			// else, every 2 ticks (i.e. in between labled ticks)
254 				//dc.drawLine(getWidth()-2,renderY,getWidth()-5,renderY); // half way tick between labled ticks
255 		}
256 
257 		return 0;
258 	}
259 
260 protected:
CVertRuler()261 	CVertRuler() {}
262 
263 private:
264 	FXGraphParamValue *parent;
265 
266 	FXFont *font;
267 
268 };
269 
270 FXDEFMAP(CVertRuler) CVertRulerMap[]=
271 {
272 	//Message_Type				ID				Message_Handler
273 	FXMAPFUNC(SEL_PAINT,			0,				CVertRuler::onPaint),
274 };
275 
276 FXIMPLEMENT(CVertRuler,FXHorizontalFrame,CVertRulerMap,ARRAYNUMBER(CVertRulerMap))
277 
278 
279 
280 // -----------------------------------------------
281 
282 FXDEFMAP(FXGraphParamValue) FXGraphParamValueMap[]=
283 {
284 	//Message_Type				ID					Message_Handler
285 
286 	FXMAPFUNC(SEL_PAINT,			FXGraphParamValue::ID_GRAPH_CANVAS,	FXGraphParamValue::onGraphPanelPaint),
287 
288 	FXMAPFUNC(SEL_CONFIGURE,		FXGraphParamValue::ID_GRAPH_CANVAS,	FXGraphParamValue::onGraphPanelResize),
289 
290 	FXMAPFUNC(SEL_LEFTBUTTONPRESS,		FXGraphParamValue::ID_GRAPH_CANVAS,	FXGraphParamValue::onCreateOrStartDragNode),
291 	FXMAPFUNC(SEL_MOTION,			FXGraphParamValue::ID_GRAPH_CANVAS,	FXGraphParamValue::onDragNode),
292 	FXMAPFUNC(SEL_LEFTBUTTONRELEASE,	FXGraphParamValue::ID_GRAPH_CANVAS,	FXGraphParamValue::onStopDragNode),
293 	FXMAPFUNC(SEL_RIGHTBUTTONRELEASE,	FXGraphParamValue::ID_GRAPH_CANVAS,	FXGraphParamValue::onDestroyNode),
294 
295 	FXMAPFUNC(SEL_COMMAND,			FXGraphParamValue::ID_SCALAR_SPINNER,	FXGraphParamValue::onScalarSpinnerChange),
296 
297 	FXMAPFUNC(SEL_COMMAND,			FXGraphParamValue::ID_CLEAR_BUTTON,	FXGraphParamValue::onPatternButton),
298 	FXMAPFUNC(SEL_COMMAND,			FXGraphParamValue::ID_HORZ_FLIP_BUTTON,	FXGraphParamValue::onPatternButton),
299 	FXMAPFUNC(SEL_COMMAND,			FXGraphParamValue::ID_VERT_FLIP_BUTTON,	FXGraphParamValue::onPatternButton),
300 	FXMAPFUNC(SEL_COMMAND,			FXGraphParamValue::ID_SMOOTH_BUTTON,	FXGraphParamValue::onPatternButton),
301 
302 	FXMAPFUNC(SEL_CHANGED,			FXGraphParamValue::ID_HORZ_DEFORM_SLIDER,FXGraphParamValue::onHorzDeformSliderChange),
303 	FXMAPFUNC(SEL_RIGHTBUTTONRELEASE,	FXGraphParamValue::ID_HORZ_DEFORM_SLIDER,FXGraphParamValue::onHorzDeformSliderReset),
304 	FXMAPFUNC(SEL_CHANGED,			FXGraphParamValue::ID_VERT_DEFORM_SLIDER,FXGraphParamValue::onVertDeformSliderChange),
305 	FXMAPFUNC(SEL_RIGHTBUTTONRELEASE,	FXGraphParamValue::ID_VERT_DEFORM_SLIDER,FXGraphParamValue::onVertDeformSliderReset),
306 };
307 
FXIMPLEMENT(FXGraphParamValue,FXVerticalFrame,FXGraphParamValueMap,ARRAYNUMBER (FXGraphParamValueMap))308 FXIMPLEMENT(FXGraphParamValue,FXVerticalFrame,FXGraphParamValueMap,ARRAYNUMBER(FXGraphParamValueMap))
309 
310 FXGraphParamValue::FXGraphParamValue(const char *_name,FXComposite *p,int opts,int x,int y,int w,int h) :
311 	FXVerticalFrame(p,opts|FRAME_RAISED,x,y,w,h, 0,0,0,0, 0,0),
312 
313 	name(_name),
314 
315 	graphPanel(new FXPacker(this,FRAME_NONE | LAYOUT_FILL_X|LAYOUT_FILL_Y, 0,0,0,0, 0,0,0,0, 0,0)),
316 		vertDeformPanel(new FXVerticalFrame(graphPanel,FRAME_NONE | LAYOUT_SIDE_RIGHT|LAYOUT_FILL_Y, 0,0,0,0, 2,2,0,0, 0,0)),
317 			vertDeformSlider(new FXSlider(vertDeformPanel,this,ID_VERT_DEFORM_SLIDER, FRAME_NONE | LAYOUT_FILL_Y | SLIDER_VERTICAL|SLIDER_TICKS_LEFT|SLIDER_ARROW_LEFT, 0,0,0,0, 0,0,0,0)),
318 		horzRuler(new CHorzRuler(graphPanel,this)),
319 		vertRuler(new CVertRuler(graphPanel,this)),
320 		horzDeformPanel(new FXHorizontalFrame(graphPanel,FRAME_NONE | LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X, 0,0,0,0, 0,0,2,0, 0,0)),
321 			horzDeformSlider(new FXSlider(horzDeformPanel,this,ID_HORZ_DEFORM_SLIDER, FRAME_NONE | LAYOUT_FILL_X | SLIDER_HORIZONTAL|SLIDER_TICKS_TOP|SLIDER_ARROW_UP,0,0,0,0, 0,0,0,0)),
322 		graphCanvas(new FXCanvas(graphPanel,this,ID_GRAPH_CANVAS,LAYOUT_FILL_X|LAYOUT_FILL_Y)),
323 
324 	statusPanel(new FXHorizontalFrame(this,FRAME_NONE | LAYOUT_FILL_X, 0,0,0,0, 4,4,0,0, 4,0)),
325 		horzValueLabel(new FXLabel(statusPanel,": ",NULL,LAYOUT_LEFT)),
326 		vertValueLabel(new FXLabel(statusPanel,": ",NULL,LAYOUT_LEFT)),
327 		pointCountLabel(new FXLabel(statusPanel,"",NULL,LAYOUT_RIGHT)),
328 
329 	buttonPanel(new FXHorizontalFrame(this,FRAME_NONE | LAYOUT_FILL_X, 0,0,0,0, 4,4,2,4)),
330 		scalarLabel(NULL),
331 		scalarSpinner(NULL),
332 
333 	draggingNode(-1),
334 	dragOffsetX(0),dragOffsetY(0),
335 
336 	sound(NULL),
337 	start(0),
338 	stop(0),
339 
340 	horzAxisLabel(""),
341 	horzUnits(""),
342 	horzValueMapper(NULL),
343 
344 	vertAxisLabel(""),
345 	vertUnits(""),
346 	vertValueMapper(NULL),
347 
348 	backBuffer(new FXImage(getApp())),
349 
350 	textFont(getApp()->getNormalFont()),
351 
352 	minWidth(500),
353 	minHeight(300)
354 {
355 	// create a smaller font to use
356         FXFontDesc d;
357         textFont->getFontDesc(d);
358         d.size-=10;
359         textFont=new FXFont(getApp(),d);
360 
361 
362 	const int deformSliderRange=4000;
363 
364 	horzDeformSlider->setRange(-deformSliderRange,deformSliderRange); // symmetry is assumed when the slider is used (and horz and very should be the same
365 #if FOX_MAJOR>=1
366 	horzDeformSlider->setTickDelta((deformSliderRange*2+1)/(11-1)); // 11 tick marks
367 #endif
368 	horzDeformSlider->setValue(0);
369 	horzDeformSlider->setTipText("Horizontally Deform the Nodes Toward One Side or the Other\nRight-Click to Reset to the Middle");
370 
371 	vertDeformSlider->setRange(-deformSliderRange,deformSliderRange); // symmetry is assumed when the slider is used (and horz and very should be the same
372 #if FOX_MAJOR>=1
373 	vertDeformSlider->setTickDelta((deformSliderRange*2+1)/(11-1)); // 11 tick marks
374 #endif
375 	vertDeformSlider->setValue(0);
376 	vertDeformSlider->setTipText("Vertically Deform the Nodes Toward the Top or Bottom\nRight-Click to Reset to the Middle");
377 
378 
379 
380 	new FXButton(buttonPanel,(string("\t")+_("Clear to Normal")).c_str(),FOXIcons->graph_clear,this,ID_CLEAR_BUTTON);
381 	new FXButton(buttonPanel,(string("\t")+_("Flip Graph Horizontally")).c_str(),FOXIcons->graph_horz_flip,this,ID_HORZ_FLIP_BUTTON);
382 	new FXButton(buttonPanel,(string("\t")+_("Flip Graph Vertically")).c_str(),FOXIcons->graph_vert_flip,this,ID_VERT_FLIP_BUTTON);
383 	new FXButton(buttonPanel,(string("\t")+_("Smooth Graph Nodes")).c_str(),FOXIcons->graph_smooth,this,ID_SMOOTH_BUTTON);
384 
385 	nodes.reserve(100);
386 
387 	backBuffer->create();
388 
389 	clearNodes();
390 
391 	setFontOfAllChildren(buttonPanel,textFont);
392 	setFontOfAllChildren(statusPanel,textFont);
393 }
394 
~FXGraphParamValue()395 FXGraphParamValue::~FXGraphParamValue()
396 {
397 	delete textFont;
398 }
399 
400 // return the default w & h of this widget
getDefaultWidth()401 FXint FXGraphParamValue::getDefaultWidth() { return minWidth; }
getDefaultHeight()402 FXint FXGraphParamValue::getDefaultHeight() { return minHeight; }
403 
setMinSize(FXint _minWidth,FXint _minHeight)404 void FXGraphParamValue::setMinSize(FXint _minWidth,FXint _minHeight)
405 {
406 	minWidth=_minWidth;
407 	minHeight=_minHeight;
408 }
409 
setSound(CSound * _sound,sample_pos_t _start,sample_pos_t _stop)410 void FXGraphParamValue::setSound(CSound *_sound,sample_pos_t _start,sample_pos_t _stop)
411 {
412 	sound=_sound;
413 	start=_start;
414 	stop=_stop;
415 
416 	horzAxisLabel="Time";
417 	horzUnits="s";
418 
419 	// make sure that the back buffer re-renders
420 	onGraphPanelResize(NULL,0,NULL);
421 
422 	clearStatus();
423 }
424 
setHorzParameters(const string horzAxisLabel,const string horzUnits,AActionParamMapper * _horzValueMapper)425 void FXGraphParamValue::setHorzParameters(const string horzAxisLabel,const string horzUnits,AActionParamMapper *_horzValueMapper)
426 {
427 	this->horzAxisLabel=horzAxisLabel;
428 	this->horzUnits=horzUnits;
429 	horzValueMapper=_horzValueMapper;
430 
431 	clearStatus();
432 	updateNumbers();
433 }
434 
setVertParameters(const string vertAxisLabel,const string vertUnits,AActionParamMapper * _vertValueMapper)435 void FXGraphParamValue::setVertParameters(const string vertAxisLabel,const string vertUnits,AActionParamMapper *_vertValueMapper)
436 {
437 	this->vertAxisLabel=vertAxisLabel;
438 	this->vertUnits=vertUnits;
439 	vertValueMapper=_vertValueMapper;
440 
441 	delete scalarLabel;
442 	delete scalarSpinner;
443 	if(vertValueMapper->getMinScalar()!=vertValueMapper->getMaxScalar())
444 	{
445 		scalarLabel=new FXLabel(buttonPanel,_("Scalar"),NULL,LABEL_NORMAL|LAYOUT_CENTER_Y);
446 		scalarSpinner=new FXSpinner(buttonPanel,5,this,ID_SCALAR_SPINNER,SPIN_NORMAL|FRAME_SUNKEN|FRAME_THICK|LAYOUT_CENTER_Y);
447 		scalarSpinner->setRange(vertValueMapper->getMinScalar(),vertValueMapper->getMaxScalar());
448 		scalarSpinner->setValue(vertValueMapper->getDefaultScalar());
449 	}
450 
451 	clearStatus();
452 	updateNumbers();
453 }
454 
clearNodes()455 void FXGraphParamValue::clearNodes()
456 {
457 	nodes.clear();
458 
459 	// add endpoints
460 	CGraphParamValueNode first(0.0,vertValueMapper==NULL ? 0.5 : vertValueMapper->uninterpretValue(vertValueMapper->getDefaultValue()));
461 	nodes.push_back(first);
462 
463 	CGraphParamValueNode last(1.0,vertValueMapper==NULL ? 0.5 : vertValueMapper->uninterpretValue(vertValueMapper->getDefaultValue()));
464 	nodes.push_back(last);
465 
466 	graphCanvas->update();
467 }
468 
469 
onScalarSpinnerChange(FXObject * sender,FXSelector sel,void * ptr)470 long FXGraphParamValue::onScalarSpinnerChange(FXObject *sender,FXSelector sel,void *ptr)
471 {
472 	vertValueMapper->setScalar(scalarSpinner->getValue());
473 	scalarSpinner->setValue(vertValueMapper->getScalar());
474 	updateNumbers();
475 	return 1;
476 }
477 
onPatternButton(FXObject * sender,FXSelector sel,void * ptr)478 long FXGraphParamValue::onPatternButton(FXObject *sender,FXSelector sel,void *ptr)
479 {
480 	switch(FXSELID(sel))
481 	{
482 	case ID_CLEAR_BUTTON:
483 		horzDeformSlider->setValue(0);
484 		vertDeformSlider->setValue(0);
485 		clearNodes();
486 		break;
487 
488 	case ID_HORZ_FLIP_BUTTON:
489 		horzDeformSlider->setValue(-horzDeformSlider->getValue());
490 		nodes=flipGraphNodesHorizontally(nodes);
491 		break;
492 
493 	case ID_VERT_FLIP_BUTTON:
494 		vertDeformSlider->setValue(-vertDeformSlider->getValue());
495 		nodes=flipGraphNodesVertically(nodes);
496 		break;
497 
498 	case ID_SMOOTH_BUTTON:
499 		nodes=smoothGraphNodes(nodes);
500 		break;
501 
502 	// ??? need a button that can change bandpass to notch
503 
504 	}
505 
506 	graphCanvas->update();
507 	updateNumbers();
508 	return 1;
509 }
510 
onHorzDeformSliderChange(FXObject * sender,FXSelector sel,void * ptr)511 long FXGraphParamValue::onHorzDeformSliderChange(FXObject *sender,FXSelector sel,void *ptr)
512 {
513 	graphCanvas->update();
514 	return 1;
515 }
516 
onVertDeformSliderChange(FXObject * sender,FXSelector sel,void * ptr)517 long FXGraphParamValue::onVertDeformSliderChange(FXObject *sender,FXSelector sel,void *ptr)
518 {
519 	graphCanvas->update();
520 	return 1;
521 }
522 
onHorzDeformSliderReset(FXObject * sender,FXSelector sel,void * ptr)523 long FXGraphParamValue::onHorzDeformSliderReset(FXObject *sender,FXSelector sel,void *ptr)
524 {
525 	horzDeformSlider->setValue(0);
526 	graphCanvas->update();
527 	return 1;
528 }
529 
onVertDeformSliderReset(FXObject * sender,FXSelector sel,void * ptr)530 long FXGraphParamValue::onVertDeformSliderReset(FXObject *sender,FXSelector sel,void *ptr)
531 {
532 	vertDeformSlider->setValue(0);
533 	graphCanvas->update();
534 	return 1;
535 }
536 
537 
onGraphPanelPaint(FXObject * sender,FXSelector sel,void * ptr)538 long FXGraphParamValue::onGraphPanelPaint(FXObject *sender,FXSelector sel,void *ptr)
539 {
540 	FXDCWindow dc(graphCanvas);
541 
542 	// draw the whole background
543 	dc.drawImage(backBuffer,0,0);
544 
545 	// draw the connection between the nodes
546 	dc.setForeground(FXRGB(255,64,64));
547 	//if(0)
548 	{ // draw lines
549 		for(size_t t=1;t<nodes.size();t++)
550 		{
551 			const CGraphParamValueNode &n1=nodes[t-1];
552 			const CGraphParamValueNode &n2=nodes[t];
553 
554 			const int x1=nodeToScreenX(n1);
555 			const int y1=nodeToScreenY(n1);
556 			const int x2=nodeToScreenX(n2);
557 			const int y2=nodeToScreenY(n2);
558 
559 			dc.drawLine(x1,y1,x2,y2);
560 		}
561 	}
562 /*
563 Okay, here i have coded cubic and cosine based curves to connect the dots instead of or in addition to
564 the straight line segments.  However, after playing around with with them a few issues arise.
565 1) Cosine always has a horizontal tangent at each node
566 2) Cubic curves have a couple of problems.
567 	- The top or bottom of a local maximum can extend above or below the screen
568 	- It's hard to control the concavity of each direction change by adding nodes without affecting the shape
569 A better overall solution may be to have a 'smooth' button for the line segments or to use bezier curves (except they
570 don't pass thru the control points
571 
572 	else
573 	{ // draw a cubic curve (http://astronomy.swin.edu.au/~pbourke/analysis/interpolation/index.html)
574 		int prevX=nodeToScreenX(nodes[0]);
575 		int prevY=nodeToScreenY(nodes[0]);
576 		for(int t=1;t<(int)nodes.size();t++)
577 		{
578 			const CGraphParamValueNode &n0=nodes[max(0,t-2)];
579 			const CGraphParamValueNode &n1=nodes[max(0,t-1)];
580 			const CGraphParamValueNode &n2=nodes[t];
581 			const CGraphParamValueNode &n3=nodes[min((int)nodes.size()-1,t+1)];
582 
583 			//const int x0=nodeToScreenX(n0);
584 			const int y0=nodeToScreenY(n0);
585 			const int x1=nodeToScreenX(n1);
586 			const int y1=nodeToScreenY(n1);
587 			const int x2=nodeToScreenX(n2);
588 			const int y2=nodeToScreenY(n2);
589 			//const int x3=nodeToScreenX(n3);
590 			const int y3=nodeToScreenY(n3);
591 
592 			for(int x=x1;x<x2;x++)
593 			{
594 				const float mu=(float)(x-x1)/(float)(x2-x1);
595 				const float mu2=mu*mu;
596 				const float a0=y3-y2-y0+y1;
597 				const float a1=y0-y1-a0;
598 				const float a2=y2-y0;
599 				const float a3=y1;
600 
601 				const float y=a0*mu*mu2+a1*mu2+a2*mu+a3;
602 
603 				dc.drawLine(prevX,prevY,x,(int)y);
604 				prevX=x;
605 				prevY=(int)y;
606 			}
607 
608 		}
609 	}
610 	else
611 	{ // draw a cosine interpolated curve (http://astronomy.swin.edu.au/~pbourke/analysis/interpolation/index.html)
612 		int prevX=nodeToScreenX(nodes[0]);
613 		int prevY=nodeToScreenY(nodes[0]);
614 		for(size_t t=1;t<nodes.size();t++)
615 		{
616 			const CGraphParamValueNode &n1=nodes[t-1];
617 			const CGraphParamValueNode &n2=nodes[t];
618 
619 			const int x1=nodeToScreenX(n1);
620 			const int y1=nodeToScreenY(n1);
621 			const int x2=nodeToScreenX(n2);
622 			const int y2=nodeToScreenY(n2);
623 
624 			for(int x=x1;x<x2;x++)
625 			{
626 				const float mu=(float)(x-x1)/(float)(x2-x1);
627 				const float mu2=(1.0-cos(mu*M_PI))/2.0;
628 
629 				const float y=y1*(1.0-mu2)+y2*mu2;
630 
631 				dc.drawLine(prevX,prevY,x,(int)y);
632 				prevX=x;
633 				prevY=(int)y;
634 			}
635 		}
636 	}
637 */
638 
639 	// draw the nodes
640 	for(size_t t=0;t<nodes.size();t++)
641 	{
642 		CGraphParamValueNode &n=nodes[t];
643 
644 		const int x=nodeToScreenX(n);
645 		const int y=nodeToScreenY(n);
646 
647 		if(t==0 || t==nodes.size()-1)
648 			dc.setForeground(FXRGB(0,191,255)); // end point node
649 		else
650 			dc.setForeground(FXRGB(0,255,0));
651 
652 		dc.fillArc(x-NODE_RADIUS,y-NODE_RADIUS,NODE_RADIUS*2,NODE_RADIUS*2,0*64,360*64);
653 	}
654 
655 	return 1;
656 }
657 
658 #include "drawPortion.h"
onGraphPanelResize(FXObject * sender,FXSelector sel,void * ptr)659 long FXGraphParamValue::onGraphPanelResize(FXObject *sender,FXSelector sel,void *ptr)
660 {
661 	// render waveform to the backbuffer
662 	backBuffer->resize(graphCanvas->getWidth(),graphCanvas->getHeight());
663 	FXDCWindow dc(backBuffer);
664 	if(sound!=NULL)
665 	{
666 		const int canvasWidth=graphCanvas->getWidth();
667 		const int canvasHeight=graphCanvas->getHeight();
668 		const sample_pos_t length=stop-start+1;
669 		const double hScalar=(double)((sample_fpos_t)length/canvasWidth);
670 		const int hOffset=(int)(start/hScalar);
671 		const float vScalar=MAX_WAVE_HEIGHT/(float)canvasHeight;
672 
673 		drawPortion(0,canvasWidth,&dc,sound,canvasWidth,canvasHeight,-1,-1,hScalar,hOffset,vScalar,0,true);
674 	}
675 	else
676 	{
677 		dc.setForeground(FXRGB(0,0,0));
678 		dc.setFillStyle(FILL_SOLID);
679 		dc.fillRectangle(0,0,graphCanvas->getWidth(),graphCanvas->getHeight());
680 	}
681 
682 	// make the vertical deform slider's top and bottom appear at the top and bottom of the graph canvas
683 	int totalPad=graphCanvas->getParent()->getHeight()-graphCanvas->getHeight();
684 	vertDeformPanel->setPadTop(graphCanvas->getY());
685 	vertDeformPanel->setPadBottom(totalPad-graphCanvas->getY());
686 
687 	return 1;
688 }
689 
690 // always returns an odd value >=1
getGraphPanelWidth() const691 int FXGraphParamValue::getGraphPanelWidth() const
692 {
693 	int w=graphCanvas->getWidth();
694 	if(w%2==0)
695 		w--;
696 	return max(w,1);
697 }
698 
699 // always returns an odd value >=1
getGraphPanelHeight() const700 int FXGraphParamValue::getGraphPanelHeight() const
701 {
702 	int h=graphCanvas->getHeight();
703 	if(h%2==0)
704 		h--;
705 	return max(h,1);
706 }
707 
insertIntoNodes(const CGraphParamValueNode & node)708 int FXGraphParamValue::insertIntoNodes(const CGraphParamValueNode &node)
709 {
710 	// sanity checks
711 	if(nodes.size()<2)
712 		throw runtime_error(string(__func__)+" -- nodes doesn't already contain at least 2 items");
713 	if(node.x<0.0 || node.x>1.0)
714 		throw runtime_error(string(__func__)+" -- node's x is out of range: "+istring(node.x));
715 
716 	int insertedIndex=-1;
717 
718 	// - basically, this function inserts the node into sorted order first by the nodes' postions
719 
720 	// - insert into the first position to keep the node's position in non-desreasing order
721 	for(size_t t1=1;t1<nodes.size();t1++)
722 	{
723 		CGraphParamValueNode &temp=nodes[t1];
724 		if(temp.x>=node.x)
725 		{
726 			nodes.insert(nodes.begin()+t1,node);
727 			insertedIndex=t1;
728 			break;
729 		}
730 	}
731 
732 	return insertedIndex;
733 }
734 
onCreateOrStartDragNode(FXObject * sender,FXSelector sel,void * ptr)735 long FXGraphParamValue::onCreateOrStartDragNode(FXObject *sender,FXSelector sel,void *ptr)
736 {
737 	FXEvent *ev=(FXEvent *)ptr;
738 
739 	int nodeIndex=findNodeAt(ev->win_x,ev->win_y);
740 	if(nodeIndex==-1)
741 	{ // create a new node
742 		CGraphParamValueNode node(screenToNodeHorzValue(ev->win_x),screenToNodeVertValue(ev->win_y));
743 		draggingNode=insertIntoNodes(node);
744 
745 		dragOffsetX=0;
746 		dragOffsetY=0;
747 	}
748 	else
749 	{
750 		dragOffsetX=ev->win_x-nodeToScreenX(nodes[nodeIndex]);
751 		dragOffsetY=ev->win_y-nodeToScreenY(nodes[nodeIndex]);
752 		draggingNode=nodeIndex;
753 	}
754 
755 	updateStatus();
756 	graphCanvas->update();
757 	return 1;
758 }
759 
onDragNode(FXObject * sender,FXSelector sel,void * ptr)760 long FXGraphParamValue::onDragNode(FXObject *sender,FXSelector sel,void *ptr)
761 {
762 	if(draggingNode!=-1)
763 	{
764 		FXEvent *ev=(FXEvent *)ptr;
765 
766 		bool lockX=(draggingNode==0 || draggingNode==(int)nodes.size()-1) ? true : false; // lock X for endpoints
767 		bool lockY=false;
768 
769 		// if shift or control is held, lock the x or y position
770 		if(ev->state&SHIFTMASK)
771 			lockX=true;
772 		else if(ev->state&CONTROLMASK)
773 			lockY=true;
774 
775 
776 		CGraphParamValueNode node=nodes[draggingNode];
777 
778 		// calculate the new positions
779 		node.x=	lockX ? node.x : screenToNodeHorzValue(ev->win_x-dragOffsetX);
780 		node.y=	lockY ? node.y : screenToNodeVertValue(ev->win_y-dragOffsetY);
781 
782 		// update the node within the vector
783 		if(draggingNode!=0 && draggingNode!=(int)nodes.size()-1)
784 		{ // not an end point
785 			// re-insert the node so that the nodes vector will be in the proper order
786 			nodes.erase(nodes.begin()+draggingNode);
787 			draggingNode=insertIntoNodes(node);
788 		}
789 		else
790 			nodes[draggingNode]=node;
791 
792 		// redraw
793 		updateStatus();
794 		graphCanvas->update();
795 	}
796 	return 0;
797 }
798 
onStopDragNode(FXObject * sender,FXSelector sel,void * ptr)799 long FXGraphParamValue::onStopDragNode(FXObject *sender,FXSelector sel,void *ptr)
800 {
801 	draggingNode=-1;
802 	clearStatus();
803 	return 1;
804 }
805 
onDestroyNode(FXObject * sender,FXSelector sel,void * ptr)806 long FXGraphParamValue::onDestroyNode(FXObject *sender,FXSelector sel,void *ptr)
807 {
808 	FXEvent *ev=(FXEvent *)ptr;
809 	int nodeIndex=findNodeAt(ev->win_x,ev->win_y);
810 	if(nodeIndex!=-1 && nodeIndex!=0 && nodeIndex!=(int)(nodes.size()-1)) // not the first or the last node
811 	{
812 		nodes.erase(nodes.begin()+nodeIndex);
813 		draggingNode=-1;
814 		graphCanvas->update();
815 		updateNumbers();
816 	}
817 	return 1;
818 }
819 
findNodeAt(int x,int y)820 int FXGraphParamValue::findNodeAt(int x,int y)
821 {
822 	for(size_t t=0;t<nodes.size();t++)
823 	{
824 		CGraphParamValueNode &n=nodes[t];
825 
826 		const int nx=nodeToScreenX(n);
827 		const int ny=nodeToScreenY(n);
828 
829 		// see if the node's position is <= the NODE_RADIUS from the give x,y position
830 		if( ((nx-x)*(nx-x)+(ny-y)*(ny-y)) <= (NODE_RADIUS*NODE_RADIUS) )
831 			return t;
832 	}
833 
834 	return -1;
835 }
836 
deformNodeX(double x,FXint sliderValue) const837 double FXGraphParamValue::deformNodeX(double x,FXint sliderValue) const
838 {
839 /*
840 	This deformation takes x:[0,1] and translates it to a deformation of x yet still with the range of [0,1]
841 	The deformation is to plug x into one quadrant of the unit circle given by the graph of f(x)=cos(asin(x)) where x:[0,1]
842 	Then I use the rule that cos(asin(x)) == sqrt(1-x^2)
843 	So, f(x) = sqrt(1-x^2)
844 
845 	Then I let the slider define a variable, p, that adjusts how much from a straight line to a more and more taught curve
846 	to deform x by.  This comes from generalizing the f(x) above to:
847 		f(x) = ( 1-x^p ) ^ (1/p)  (that is, p takes the place of '2' in the previous f(x))
848 
849 	So, now p can go from 1 (where f(x) produces the line, y=x) and upwards where elbow of the curve is more and more and more bent toward (1,0)
850 
851 	So, the x coord of the node plugged into this f(x) where s==1 does no deforming and where s is greater, the x is more bent in one direction
852 
853 	So the slider going from [-N,N] is scaled to s: [-1,1] and when s>=0 I let s define p as p=(K*s)+1
854 	But when s<0 I let be defined as p=(K*(-s))+1, but I flip the f(x) equation horizontally and vertically so that the bend tends toward (0,1)
855 	This way, the slider deforms the nodes' x values leftwards or rightwards depending on the slider's value being - or +
856 
857 	And as a side note: I square s (but maintain the sign) so that more values in the middle will be available but s is will [-1,1]
858 
859 */
860 
861 	FXint lo,hi; // assuming symmetry on low and hi and vert and horz are the same
862 	horzDeformSlider->getRange(lo,hi);
863 
864 		// s=[-1,1]
865 	double s=(double)sliderValue/(double)hi;
866 	s=fabs(s)*s;
867 
868 	if(s>=0)
869 	{
870 		double p=(10.0*s)+1.0;
871 		return pow( 1.0-pow(1.0-x,p) , 1.0/p );
872 	}
873 	else
874 	{
875 		double p=(10.0*(-s))+1.0;
876 		return 1.0-pow( 1.0-pow(x,p) , 1.0/p );
877 	}
878 
879 }
880 
undeformNodeX(double x,FXint sliderValue) const881 double FXGraphParamValue::undeformNodeX(double x,FXint sliderValue) const
882 {
883 	/* this computes the inverse of the previous deformNodeX method above */
884 	/* which simplifies to just calling deformNodeX with the negated sliderValue */
885 	return deformNodeX(x,-sliderValue);
886 }
887 
nodeToScreenX(const CGraphParamValueNode & node)888 int FXGraphParamValue::nodeToScreenX(const CGraphParamValueNode &node)
889 {
890 	return (int)(deformNodeX(node.x,horzDeformSlider->getValue())*(graphCanvas->getWidth()-1));
891 }
892 
nodeToScreenY(const CGraphParamValueNode & node)893 int FXGraphParamValue::nodeToScreenY(const CGraphParamValueNode &node)
894 {
895 	return (int)((1.0-deformNodeX(node.y,vertDeformSlider->getValue()))*(getGraphPanelHeight()));
896 }
897 
screenToNodeHorzValue(int x,bool undeform)898 double FXGraphParamValue::screenToNodeHorzValue(int x,bool undeform)
899 {
900 	double v=((double)x/(double)(graphCanvas->getWidth()-1));
901 	if(v<0.0)
902 		v=0.0;
903 	else if(v>1.0)
904 		v=1.0;
905 
906 	if(undeform)
907 		return undeformNodeX(v,horzDeformSlider->getValue());
908 	else
909 		return v;
910 }
911 
screenToNodeVertValue(int y,bool undeform)912 double FXGraphParamValue::screenToNodeVertValue(int y,bool undeform)
913 {
914 	double v=(1.0-((double)y/(double)(getGraphPanelHeight()-1)));
915 	if(v<0.0)
916 		v=0.0;
917 	else if(v>1.0)
918 		v=1.0;
919 
920 	if(undeform)
921 		return undeformNodeX(v,vertDeformSlider->getValue());
922 	else
923 		return v;
924 }
925 
updateNumbers()926 void FXGraphParamValue::updateNumbers()
927 {
928 	horzRuler->update();
929 	vertRuler->update();
930 	updateStatus();
931 }
932 
getNodes() const933 const CGraphParamValueNodeList &FXGraphParamValue::getNodes() const
934 {
935 	retNodes=nodes;
936 	for(size_t t=0;t<retNodes.size();t++)
937 	{
938 		retNodes[t].x=deformNodeX(retNodes[t].x,horzDeformSlider->getValue());
939 		retNodes[t].y=deformNodeX(retNodes[t].y,vertDeformSlider->getValue());
940 
941 		if(horzValueMapper!=NULL)
942 			retNodes[t].x=horzValueMapper->interpretValue(retNodes[t].x);
943 		if(vertValueMapper!=NULL)
944 			retNodes[t].y=vertValueMapper->interpretValue(retNodes[t].y);
945 	}
946 
947 	return retNodes;
948 }
949 
950 /*
951  * only copies nodes and deformation parameters and scalars, but not interpret functions and such
952  */
copyFrom(const FXGraphParamValue * src)953 void FXGraphParamValue::copyFrom(const FXGraphParamValue *src)
954 {
955 	// make undo stack copy when implemented ???
956 
957 	// copy deformation values
958 	horzDeformSlider->setValue(src->horzDeformSlider->getValue());
959 	vertDeformSlider->setValue(src->vertDeformSlider->getValue());
960 
961 	// copy scalar values
962 	setScalar(src->getScalar());
963 
964 	// copy nodes
965 	nodes.clear();
966 	for(size_t t=0;t<src->nodes.size();t++)
967 		nodes.push_back(src->nodes[t]);
968 
969 	// cause everything to refresh
970 	updateNumbers();
971 	update();
972 	graphCanvas->update();
973 }
974 
swapWith(FXGraphParamValue * src)975 void FXGraphParamValue::swapWith(FXGraphParamValue *src)
976 {
977 	int t;
978 
979 	// make undo stack copy (for both) when implemented ???
980 
981 	// swap deformation values
982 	t=horzDeformSlider->getValue();
983 	horzDeformSlider->setValue(src->horzDeformSlider->getValue());
984 	src->horzDeformSlider->setValue(t);
985 
986 	t=vertDeformSlider->getValue();
987 	vertDeformSlider->setValue(src->vertDeformSlider->getValue());
988 	src->vertDeformSlider->setValue(t);
989 
990 
991 	// copy scalar values
992 	t=getScalar();
993 	setScalar(src->getScalar());
994 	src->setScalar(t);
995 
996 	// copy nodes
997 	swap(nodes,src->nodes);
998 
999 	// cause everything to refresh
1000 	updateNumbers();
1001 	update();
1002 	graphCanvas->update();
1003 
1004 	src->updateNumbers();
1005 	src->update();
1006 	src->graphCanvas->update();
1007 }
1008 
getVertValueString(double vertValue) const1009 const string FXGraphParamValue::getVertValueString(double vertValue) const
1010 {
1011 	return istring(vertValueMapper->interpretValue(vertValue),5,3);
1012 }
1013 
getHorzValueString(double horzValue) const1014 const string FXGraphParamValue::getHorzValueString(double horzValue) const
1015 {
1016 	if(sound==NULL)
1017 		return istring(horzValueMapper->interpretValue(horzValue),5,3);
1018 	else
1019 		return sound->getTimePosition((sample_pos_t)((sample_fpos_t)(stop-start+1)*horzValue+start),3,false);
1020 }
1021 
1022 
updateStatus()1023 void FXGraphParamValue::updateStatus()
1024 {
1025 	pointCountLabel->setText((istring(nodes.size())+" "+_("points")).c_str());
1026 
1027 	if(draggingNode==-1)
1028 		return;
1029 
1030 	const CGraphParamValueNode &n=nodes[draggingNode];
1031 
1032 	// draw status
1033 	horzValueLabel->setText((horzAxisLabel+": "+getHorzValueString(deformNodeX(n.x,horzDeformSlider->getValue()))+horzUnits).c_str());
1034 	vertValueLabel->setText((vertAxisLabel+": "+getVertValueString(deformNodeX(n.y,vertDeformSlider->getValue()))+vertUnits).c_str());
1035 }
1036 
clearStatus()1037 void FXGraphParamValue::clearStatus()
1038 {
1039 	horzValueLabel->setText((horzAxisLabel+": #"+horzUnits).c_str());
1040 	vertValueLabel->setText((vertAxisLabel+": #"+vertUnits).c_str());
1041 	pointCountLabel->setText((istring(nodes.size())+" "+_("points")).c_str());
1042 }
1043 
1044 
getScalar() const1045 const int FXGraphParamValue::getScalar() const
1046 {
1047 	return scalarSpinner==NULL ? vertValueMapper->getDefaultScalar() : vertValueMapper->getScalar();
1048 }
1049 
setScalar(const int scalar)1050 void FXGraphParamValue::setScalar(const int scalar)
1051 {
1052 	vertValueMapper->setScalar(scalar);
1053 	if(scalarSpinner!=NULL) scalarSpinner->setValue(vertValueMapper->getScalar());
1054 }
1055 
getMinScalar() const1056 const int FXGraphParamValue::getMinScalar() const
1057 {
1058 	/*
1059 	FXint lo=0,hi=0;
1060 	if(scalarSpinner!=NULL)
1061 		scalarSpinner->getRange(lo,hi);
1062 	return lo;
1063 	*/
1064 	return vertValueMapper->getMinScalar();
1065 }
1066 
getMaxScalar() const1067 const int FXGraphParamValue::getMaxScalar() const
1068 {
1069 	/*
1070 	FXint lo=0,hi=0;
1071 	if(scalarSpinner!=NULL)
1072 		scalarSpinner->getRange(lo,hi);
1073 	return hi;
1074 	*/
1075 	return vertValueMapper->getMaxScalar();
1076 }
1077 
getName() const1078 const string FXGraphParamValue::getName() const
1079 {
1080 	return name;
1081 }
1082 
readFromFile(const string & prefix,CNestedDataFile * f)1083 void FXGraphParamValue::readFromFile(const string &prefix,CNestedDataFile *f)
1084 {
1085 	const string key=prefix DOT getName();
1086 
1087 	if(f->keyExists(key DOT "scalar"))
1088 		setScalar(f->getValue<int>(key DOT "scalar"));
1089 	else
1090 		setScalar(vertValueMapper->getDefaultScalar());
1091 
1092 	nodes.clear();
1093 
1094 	const string k1=key DOT "node_positions";
1095 	const string k2=key DOT "node_values";
1096 	if(f->keyExists(k1)==CNestedDataFile::ktValue && f->keyExists(k2)==CNestedDataFile::ktValue)
1097 	{
1098 		const vector<double> positions=f->getValue<vector<double> >(k1);
1099 		const vector<double> values=f->getValue<vector<double> >(k2);
1100 
1101 		// ??? I could either save the node x and values as [0,1], or I could use save the actual values ( <-- currently)
1102 		for(size_t t=0;t<positions.size();t++)
1103 			nodes.push_back(CGraphParamValueNode(positions[t],values[t]));
1104 
1105 	}
1106 	else
1107 		clearNodes();
1108 
1109 	if(f->keyExists(key DOT "horzDeformSlider")==CNestedDataFile::ktValue)
1110 		horzDeformSlider->setValue(f->getValue<int>(key DOT "horzDeformSlider"));
1111 	if(f->keyExists(key DOT "vertDeformSlider")==CNestedDataFile::ktValue)
1112 		vertDeformSlider->setValue(f->getValue<int>(key DOT "vertDeformSlider"));
1113 
1114 	updateNumbers();
1115 	update();
1116 	graphCanvas->update();
1117 }
1118 
writeToFile(const string & prefix,CNestedDataFile * f) const1119 void FXGraphParamValue::writeToFile(const string &prefix,CNestedDataFile *f) const
1120 {
1121 	const string key=prefix DOT getName();
1122 
1123 	if(getMinScalar()!=getMaxScalar())
1124 		f->setValue<int>(key DOT "scalar",getScalar());
1125 
1126 	vector<double> positions,values;
1127 	for(size_t t=0;t<nodes.size();t++)
1128 	{
1129 		positions.push_back(nodes[t].x);
1130 		values.push_back(nodes[t].y);
1131 	}
1132 
1133 	const string k1=key DOT "node_positions";
1134 	f->setValue<vector<double> >(k1,positions);
1135 
1136 	const string k2=key DOT "node_values";
1137 	f->setValue<vector<double> >(k2,values);
1138 
1139 	if(getMinScalar()!=getMaxScalar())
1140 		f->setValue<int>(key DOT "scalar",getScalar());
1141 
1142 	f->setValue<int>(key DOT "horzDeformSlider",horzDeformSlider->getValue());
1143 	f->setValue<int>(key DOT "vertDeformSlider",vertDeformSlider->getValue());
1144 }
1145 
1146