1 /* mathgl.cpp is part of UDAV
2  * Copyright (C) 2007-2014 Alexey Balakin <mathgl.abalakin@gmail.ru>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public License
6  * as published by the Free Software Foundation
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16  */
17 #include "mgl2/mgl.h"
18 #include "mgllab.h"
19 #include "../widgets/image.h"
20 //-----------------------------------------------------------------------------
21 mglParse *Parse=0;
22 //-----------------------------------------------------------------------------
udav_delay(void * v)23 mreal MGL_FUNC_PURE udav_delay(void *v)
24 {	return ((Fl_MGL*)v)->delay;	}
udav_reload(void * v)25 void udav_reload(void *v)
26 {	Parse->RestoreOnce();	((Fl_MGL*)v)->update();	}
27 //-----------------------------------------------------------------------------
udav_next(void * v)28 void udav_next(void *v)	{	((Fl_MGL*)v)->next_frame();	}
next_frame()29 void Fl_MGL::next_frame()
30 {
31 	size_t n=anim.size();
32 	if(n==0 && animate_cb(this))	{	gr->toggle_sshow();	return;	}
33 	n=anim.size();
34 	cur = (cur+1)%n;
35 	Parse->AddParam(0,anim[cur].c_str());
36 	update();
37 }
38 //-----------------------------------------------------------------------------
udav_prev(void * v)39 void udav_prev(void *v)	{	((Fl_MGL*)v)->prev_frame();	}
prev_frame()40 void Fl_MGL::prev_frame()
41 {
42 	size_t n=anim.size();
43 	if(n==0 && animate_cb(this))	{	gr->toggle_sshow();	return;	}
44 	n=anim.size();
45 	cur = (cur+n-1)%n;
46 	Parse->AddParam(0,anim[cur].c_str());
47 	update();
48 }
49 //-----------------------------------------------------------------------------
Fl_MGL(Fl_MGLView * GR)50 Fl_MGL::Fl_MGL(Fl_MGLView *GR)
51 {
52 	if(!Parse)	Parse = new mglParse;
53 	Parse->AllowSetSize(true);
54 	gr = GR;	gr->par = this;	e = 0;
55 	gr->next = udav_next;	gr->delay = udav_delay;
56 	gr->prev = udav_prev;	gr->reload = udav_reload;
57 	gr->FMGL->set_draw(this);
58 	gr->FMGL->set_show_warn(false);
59 	a1=a2=0;	da=1;	cur=0;	delay=0.5;
60 }
61 //-----------------------------------------------------------------------------
~Fl_MGL()62 Fl_MGL::~Fl_MGL()	{}
63 //-----------------------------------------------------------------------------
Param(char id,const char * val)64 void Fl_MGL::Param(char id, const char *val)
65 {	Parse->AddParam(id<='9' ? id-'0' : id-'a'+10, val);	}
66 //-----------------------------------------------------------------------------
Reload()67 void Fl_MGL::Reload()
68 {
69 	Parse->RestoreOnce();
70 	e->graph->update();
71 }
72 //-----------------------------------------------------------------------------
Click()73 void Fl_MGL::Click()
74 {
75 	int id = e->graph->FMGL->get_last_id();
76 	if(id>0)
77 	{
78 		Fl_Text_Editor::kf_ctrl_move(FL_Home, e->editor);
79 		for(int i=0;i<id;i++)	Fl_Text_Editor::kf_down(0, e->editor);
80 		Fl_Text_Editor::kf_up(0, e->editor);
81 		Fl::focus(e->editor);
82 	}
83 	e->graph->update();
84 }
85 //-----------------------------------------------------------------------------
Draw(mglGraph * gr)86 int Fl_MGL::Draw(mglGraph *gr)
87 {
88 	if(exec_save)	save_cb(0,e);
89 	Parse->Execute(gr,script.c_str());
90 	if(textbuf)
91 	{
92 		char *text = textbuf->text();
93 		if(highlight)	gr->Highlight(e->graph->FMGL->get_last_id());
94 		Parse->Execute(gr,text);
95 		free(text);
96 	}
97 	// TODO go to line with warning?!!
98 	message_set(gr->Message(), e);
99 	if(e && e->rtab)	e->rtab->value(e->gplot);
100 	return 0;
101 }
102 //-----------------------------------------------------------------------------
update()103 void Fl_MGL::update()
104 {
105 	// NOTE: hint for old style View(). May be I should remove it!
106 	char *text = textbuf->text();
107 	if(script.empty() || !text || !strstr(text,"rotate"))
108 		mgl_rotate(gr->get_graph(),0,0,0);
109 
110 	gr->update();
111 	for(long i=0;i<Parse->GetNumVar();i++)
112 	{
113 		mglDataA *v = Parse->GetVar(i);
114 		if(v && v->o)	((TableWindow *)v->o)->update(v);
115 	}
116 }
117 //-----------------------------------------------------------------------------
118 // void add_suffix(char *fname, const char *ext)
119 // {
120 // 	long n=strlen(fname);
121 // 	if(n>4 && fname[n-4]=='.')
122 // 	{	fname[n-3]=ext[0];	fname[n-2]=ext[1];	fname[n-1]=ext[2];	}
123 // 	else	{	strcat(fname,".");	strcat(fname,ext);	}
124 // }
125 //-----------------------------------------------------------------------------
126 class ArgsDlg : public GeneralDlg
127 {
128 	Fl_Input *arg[10];
129 public:
cb_ok()130 	void cb_ok()
131 	{
132 		for(int i=0;i<10;i++)	Parse->AddParam(i,arg[i]->value());
133 		hide();
134 	}
set(int id,const char * val)135 	void set(int id, const char *val)
136 	{
137 		if(id>=0 && id<10)
138 		{	arg[id]->value(val);	Parse->AddParam(id,val);	}
139 	}
init()140 	void init()
141 	{	// NOTE I'm not sure that I need to get current arguments. So keep function empty.
142 	}
ArgsDlg()143 	ArgsDlg() : GeneralDlg()
144 	{
145 		w = new Fl_Double_Window(290, 320, _("Set script arguments"));
146 		arg[1] = new Fl_Input(5, 20, 135, 30, _("String for $1"));
147 		arg[1]->align(FL_ALIGN_TOP_LEFT);
148 		arg[2] = new Fl_Input(150, 20, 135, 30, _("String for $2"));
149 		arg[2]->align(FL_ALIGN_TOP_LEFT);
150 		arg[3] = new Fl_Input(5, 75, 135, 30, _("String for $3"));
151 		arg[3]->align(FL_ALIGN_TOP_LEFT);
152 		arg[4] = new Fl_Input(150, 75, 135, 30, _("String for $4"));
153 		arg[4]->align(FL_ALIGN_TOP_LEFT);
154 		arg[5] = new Fl_Input(5, 130, 135, 30, _("String for $5"));
155 		arg[5]->align(FL_ALIGN_TOP_LEFT);
156 		arg[6] = new Fl_Input(150, 130, 135, 30, _("String for $6"));
157 		arg[6]->align(FL_ALIGN_TOP_LEFT);
158 		arg[7] = new Fl_Input(5, 185, 135, 30, _("String for $7"));
159 		arg[7]->align(FL_ALIGN_TOP_LEFT);
160 		arg[8] = new Fl_Input(150, 185, 135, 30, _("String for $8"));
161 		arg[8]->align(FL_ALIGN_TOP_LEFT);
162 		arg[9] = new Fl_Input(5, 240, 135, 30, _("String for $9"));
163 		arg[9]->align(FL_ALIGN_TOP_LEFT);
164 		arg[0] = new Fl_Input(150, 240, 135, 30, _("String for $0"));
165 		arg[0]->align(FL_ALIGN_TOP_LEFT);
166 		Fl_Button* o = new Fl_Button(60, 290, 75, 25, _("Cancel"));
167 		o->callback(cb_dlg_cancel, this);
168 		o = new Fl_Return_Button(155, 290, 75, 25, _("Set"));
169 		o->callback(cb_dlg_ok, this);
170 		w->set_modal();	w->end();
171 	}
172 } args_dlg;
173 //-----------------------------------------------------------------------------
args_dlg_cb(Fl_Widget *,void *)174 void args_dlg_cb(Fl_Widget *, void *)	{	args_dlg.show();	}
argument_set(int id,const char * val)175 void argument_set(int id, const char *val)	{	args_dlg.set(id,val);	}
176 //-----------------------------------------------------------------------------
177 void cb_anim_put(Fl_Widget *, void *);
cb_dlg_only(Fl_Widget *,void * v)178 void cb_dlg_only(Fl_Widget*,void *v)	{	((Fl_Round_Button*)v)->setonly();	}
179 class AnimateDlg : public GeneralDlg
180 {
181 public:
182 	Fl_MGL* dr;
AnimateDlg()183 	AnimateDlg() : GeneralDlg()
184 	{
185 		w = new Fl_Double_Window(335, 350, _("Animation"));
186 		new Fl_Box(10, 5, 315, 25, _("Redraw picture for $0 equal to"));
187 		rt = new Fl_Round_Button(10, 30, 200, 25, _("strings"));
188 		rt->callback(cb_dlg_only, rt);
189 		rv = new Fl_Round_Button(220, 30, 105, 25, _("values"));
190 		rv->callback(cb_dlg_only, rv);
191 		txt = new Fl_Multiline_Input(10, 60, 200, 250);
192 		x0 = new Fl_Float_Input(220, 80, 105, 25, _("from"));			x0->align(FL_ALIGN_TOP_LEFT);
193 		x1 = new Fl_Float_Input(220, 130, 105, 25, _("to"));			x1->align(FL_ALIGN_TOP_LEFT);
194 		dx = new Fl_Float_Input(220, 180, 105, 25, _("with step"));	dx->align(FL_ALIGN_TOP_LEFT);
195 
196 		Fl_Button *o;
197 		o = new Fl_Button(230, 215, 80, 25, _("Cancel"));	o->callback(cb_dlg_cancel, this);
198 		o = new Fl_Return_Button(230, 250, 80, 25, _("OK"));o->callback(cb_dlg_ok, this);
199 		save = new Fl_Check_Button(220, 285, 105, 25, _("save slides"));
200 		save->tooltip(_("Keep slides in memory (faster animation but require more memory)"));
201 		save->down_box(FL_DOWN_BOX);	save->deactivate();
202 
203 		o = new Fl_Button(10, 315, 100, 25, _("Put to script"));	o->callback(cb_anim_put,w);
204 		dt = new Fl_Float_Input(220, 315, 105, 25, _("Delay (in sec)"));//	dx->align(FL_ALIGN_TOP_LEFT);
205 		w->end();
206 	}
init()207 	void init()
208 	{
209 		if(e)	dr = e->draw;
210 		if(dr && dr->da*(dr->a2-dr->a1)>0)
211 		{
212 			char buf[128];	rv->setonly();
213 			snprintf(buf,127,"%g",dr->a1);	x0->value(buf);
214 			snprintf(buf,127,"%g",dr->a2);	x1->value(buf);
215 			snprintf(buf,127,"%g",dr->da);	dx->value(buf);
216 		}
217 		else if(dr)
218 		{
219 			rt->setonly();
220 			std::string str;
221 			for(size_t i=0;i<dr->anim.size();i++)
222 				str += dr->anim[i]+'\n';
223 			txt->value(str.c_str());
224 		}
225 	}
prepare()226 	void prepare()
227 	{
228 		result.clear();
229 		if(dr)	dr->anim.clear();
230 		if(rv->value())
231 		{
232 			const char *s1=x0->value(), *s2=x1->value(), *s3=dx->value();
233 			double a1=s1?atof(s1):NAN, a2=s2?atof(s2):NAN, a3=s3?atof(s3):1;
234 			if(a3*(a2-a1)>0)
235 			{
236 				result = result + "##c "+(s1?s1:"nan")+' '+(s2?s2:"nan")+' '+(s3?s3:"1")+'\n';
237 				if(dr)
238 				{
239 					dr->a1=a1;	dr->a2=a2;	dr->da=a3;
240 					for(double a=a1;a3*(a2-a)>=0;a+=a3)
241 					{
242 						char buf[128];	snprintf(buf,128,"%g",a);
243 						dr->anim.push_back(buf);
244 					}
245 				}
246 			}
247 			else	fl_alert(_("Incompatible loop parameters!"));
248 		}
249 		else if(rt->value())
250 		{
251 			const char *s = txt->value();
252 			while(s && *s)
253 			{
254 				const char *j = strchr(s,'\n');
255 				size_t len = j?(j-s):strlen(s);
256 				std::string val(s,len);
257 				if(dr)	dr->anim.push_back(val);
258 				result = result+"##a "+val+'\n';
259 				s=j?j+1:NULL;
260 			}
261 		}
262 		else	fl_message(_("No selection. So nothing to do"));
263 
264 	}
into_script()265 	void into_script()
266 	{
267 		prepare();
268 		if(e)
269 		{
270 			int p = textbuf->line_start(e->editor->insert_position());
271 			textbuf->insert(p, (result+'\n').c_str());
272 		}
273 	}
cb_ok()274 	void cb_ok()
275 	{
276 		if(!dr)	return;
277 		prepare();
278 		const char *s = dt->value();
279 		if(s && *s)	dr->delay = atof(s);
280 		hide();
281 	}
282 protected:
283 	bool swap;
284 	Fl_Round_Button *rt, *rv;
285 	Fl_Multiline_Input *txt;
286 	Fl_Float_Input *x0, *x1, *dx, *dt;
287 	Fl_Check_Button *save;
288 	void create_dlg();
289 } animate_dlg;
290 //-----------------------------------------------------------------------------
animate_dlg_cb(Fl_Widget *,void * v)291 void animate_dlg_cb(Fl_Widget *, void *v)
292 {	animate_dlg.e = (ScriptWindow*)v;	animate_dlg.show();	}
cb_anim_put(Fl_Widget *,void *)293 void cb_anim_put(Fl_Widget *, void *)	{	animate_dlg.into_script();	}
294 //-----------------------------------------------------------------------------
animate_cb(Fl_MGL * d)295 bool animate_cb(Fl_MGL *d)
296 {	animate_dlg.dr = d;	animate_dlg.show();	return animate_dlg.wait();	}
297 //-----------------------------------------------------------------------------
fill_animate(const char * text,Fl_MGL * dr)298 void fill_animate(const char *text, Fl_MGL *dr)
299 {
300 	char tmp[4]="#$0";
301 	for(int i=0;i<10;i++)	// first read script arguments (if one)
302 	{
303 		tmp[2] = '0'+i;
304 		const char *str=strstr(text,tmp);
305 		if(str)
306 		{
307 			str+=3;
308 			while(*str>0 && *str<=' ' && *str!='\n')	str++;
309 			if(*str>' ')
310 			{
311 				size_t j=0;	while(str[j]>' ')	j++;
312 				std::string val(str,j);
313 				argument_set(i,val.c_str());
314 			}
315 		}
316 	}
317 	dr->anim.clear();
318 	std::string ids;
319 	std::vector<std::string> par;
320 	mgl_parse_comments(text, dr->a1, dr->a2, dr->da, dr->anim, ids, par);
321 	if(!ids.empty())	dr->gr->dialog(ids,par);
322 }
323 //-----------------------------------------------------------------------------
324 Fl_Text_Display::Style_Table_Entry stylemess[2] = {	// Style table
325 	{ FL_BLACK,		FL_COURIER,		12, 0 },		// A - Plain
326 	{ FL_RED,		FL_COURIER,		12, 0 } };		// B - Strings
mess_parse(const char * text,char * style,int)327 void mess_parse(const char *text, char *style, int /*length*/)
328 {
329 	size_t n=strlen(text);
330 	// Style letters: A - Plain; B - Error
331 	for(size_t i=0;i<n;i++)	style[i] = 'A';
332 	const char *l1=text, *l2=strchr(l1, '\n');
333 	while(l1)
334 	{
335 		size_t len = l2?l2-l1:strlen(l1), st=l1-text;
336 		const char *p = strstr(l1,"in line");
337 		if(p && size_t(p-l1)<len)
338 			for(size_t i=0;i<len;i++)	style[i+st]='B';
339 		l1=l2?l2+1:NULL;	l2=l1?strchr(l1, '\n'):NULL;
340 	}
341 }
342 //-----------------------------------------------------------------------------
343 static Fl_Text_Buffer *sbuf=0;
mess_update(int pos,int nInserted,int nDeleted,int,const char *,void * cbArg)344 void mess_update(int pos, int nInserted, int nDeleted, int, const char *, void *cbArg)
345 {
346 	Fl_Text_Buffer *mbuf=((Fl_Text_Editor *)cbArg)->buffer();
347 	long	start, end;	// Start and end of text
348 	char last, *style, *text;		// Text data
349 	if (nInserted == 0 && nDeleted == 0) {	sbuf->unselect();	return;  }
350 	if (nInserted > 0)
351 	{
352 		style = new char[nInserted + 1];
353 		memset(style, 'A', nInserted);
354 		style[nInserted] = '\0';
355 		sbuf->replace(pos, pos + nDeleted, style);
356 		delete[] style;
357 	}
358 	else	sbuf->remove(pos, pos + nDeleted);
359 	sbuf->select(pos, pos + nInserted - nDeleted);
360 	start = mbuf->line_start(pos);
361 	end   = mbuf->line_end(pos + nInserted);
362 	text  = mbuf->text_range(start, end);
363 	style = sbuf->text_range(start, end);
364 	if (start==end)	last = 0;
365 	else	last = style[end-start-1];
366 	mess_parse(text, style, end - start);
367 	sbuf->replace(start, end, style);
368 	((Fl_Text_Editor *)cbArg)->redisplay_range(start, end);
369 
370 	if (start==end || last != style[end-start-1])
371 	{
372 		// Either the user deleted some text, or the last character on
373 		// the line changed styles, so reparse the remainder of the buffer...
374 		free(text);	free(style);
375 		end   = mbuf->length();
376 		text  = mbuf->text_range(start, end);
377 		style = sbuf->text_range(start, end);
378 		mess_parse(text, style, end - start);
379 		sbuf->replace(start, end, style);
380 		((Fl_Text_Editor *)cbArg)->redisplay_range(start, end);
381 	}
382 	free(text);	free(style);
383 }
384 //-----------------------------------------------------------------------------
385 void style_unfinished_cb(int, void*);
386 void cb_mess_copy(Fl_Widget*,void *v);
387 void cb_mess_jump(Fl_Widget*,void *v);
388 class MessDlg : public GeneralDlg
389 {
390 	Fl_Text_Display *mess;
391 	Fl_Text_Buffer *mbuf;
392 	int pos, last;
393 public:
MessDlg()394 	MessDlg() : GeneralDlg()
395 	{
396 		Fl_Button *o;
397 		w = new Fl_Double_Window(500, 195, _("MGL messages"));
398 		mess = new Fl_Text_Display(30, 5, 460, 190);
399 		o = new Fl_Return_Button(5,5,25,25);	o->callback(cb_mess_jump,e);
400 		o = new Fl_Button(5,35,25,25);	o->callback(cb_mess_copy,e);	o->image(img_copy);
401 		w->end();	w->resizable(mess);
402 		mbuf = new Fl_Text_Buffer;	sbuf = new Fl_Text_Buffer;
403 		mess->buffer(mbuf);	pos=last=0;
404 		mess->highlight_data(sbuf, stylemess, sizeof(stylemess) / sizeof(stylemess[0]), 'A', style_unfinished_cb, 0);
405 		mbuf->add_modify_callback(mess_update, mess);
406 		mbuf->call_modify_callbacks();
407 	}
copy()408 	void copy()
409 	{
410 		char *s = mbuf->selection_text();
411 		if(s && *s==0)	{	free(s);	s=0;	}
412 		if(!s)	s = mbuf->text();
413 		Fl::copy(s,strlen(s),1);	free(s);
414 	}
jump()415 	void jump()
416 	{
417 		if(!e)	return;
418 		char *s = mbuf->text();
419 		if(*s==0)	{	free(s);	return;	}
420 		int ipos = mess->line_start(mess->insert_position());
421 		if(ipos!=last)	last=pos=ipos;
422 
423 		int id=-1;
424 		const char *p = strstr(s+pos,"in line");
425 		if(p)	{	pos = p-s+7;	id = atoi(p+8);	}
426 		else	// try from beginning
427 		{
428 			p = strstr(s,"in line");
429 			if(p)	{	pos = p-s+7;	id = atoi(p+8);	}
430 		}
431 		free(s);
432 
433 		if(id>=0)
434 		{
435 			Fl_Text_Editor::kf_ctrl_move(FL_Home, e->editor);
436 			for(int i=0;i<id;i++)	Fl_Text_Editor::kf_down(0, e->editor);
437 			Fl_Text_Editor::kf_up(0, e->editor);
438 			Fl::focus(e->editor);
439 		}
440 	}
set(const char * s)441 	void set(const char *s)
442 	{	mbuf->text(s);	show();	}
443 } mess_wnd;
444 //-----------------------------------------------------------------------------
message_cb(Fl_Widget *,void * v)445 void message_cb(Fl_Widget*,void *v)
446 {	animate_dlg.e = (ScriptWindow*)v;	mess_wnd.show();	}
447 //-----------------------------------------------------------------------------
message_set(const char * s,ScriptWindow * e)448 void message_set(const char *s, ScriptWindow *e)
449 {	mess_wnd.e = e;	if(s && *s)	mess_wnd.set(s);	else	mess_wnd.hide();	}
450 //-----------------------------------------------------------------------------
cb_mess_copy(Fl_Widget *,void * v)451 void cb_mess_copy(Fl_Widget*,void *v)	{	mess_wnd.copy();	}
452 //-----------------------------------------------------------------------------
cb_mess_jump(Fl_Widget *,void * v)453 void cb_mess_jump(Fl_Widget*,void *v)	{	mess_wnd.jump();	}
454 //-----------------------------------------------------------------------------
455