Prevent unsafe thread change of local settings using synfig::ChangeLocale class
[synfig.git] / synfig-studio / src / gui / states / state_width.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file state_width.cpp
3 **      \brief Template File
4 **
5 **      $Id$
6 **
7 **      \legal
8 **      Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **  Copyright (c) 2008 Chris Moore
10 **
11 **      This package is free software; you can redistribute it and/or
12 **      modify it under the terms of the GNU General Public License as
13 **      published by the Free Software Foundation; either version 2 of
14 **      the License, or (at your option) any later version.
15 **
16 **      This package is distributed in the hope that it will be useful,
17 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
18 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 **      General Public License for more details.
20 **      \endlegal
21 */
22 /* ========================================================================= */
23
24 /* === H E A D E R S ======================================================= */
25
26 #ifdef USING_PCH
27 #       include "pch.h"
28 #else
29 #ifdef HAVE_CONFIG_H
30 #       include <config.h>
31 #endif
32
33 #include <gtkmm/dialog.h>
34 #include <gtkmm/entry.h>
35
36 #include <ETL/bezier>
37
38 #include <synfig/valuenode_dynamiclist.h>
39 #include <synfigapp/action_system.h>
40
41 #include "state_width.h"
42 #include "state_normal.h"
43 #include "canvasview.h"
44 #include "workarea.h"
45 #include "app.h"
46
47 #include <synfigapp/action.h>
48 #include "event_mouse.h"
49 #include "event_layerclick.h"
50 #include "toolbox.h"
51 #include "docks/dialog_tooloptions.h"
52 #include <gtkmm/optionmenu.h>
53 #include "duck.h"
54
55 //#include <synfigapp/value_desc.h>
56 #include <synfigapp/main.h>
57
58 #include <ETL/clock>
59
60 #include "general.h"
61
62 #endif
63
64 /* === U S I N G =========================================================== */
65
66 using namespace std;
67 using namespace etl;
68 using namespace synfig;
69 using namespace synfigapp;
70 using namespace studio;
71
72 /* === M A C R O S ========================================================= */
73
74 /* === G L O B A L S ======================================================= */
75
76 StateWidth studio::state_width;
77
78 /* === C L A S S E S & S T R U C T S ======================================= */
79
80 class studio::StateWidth_Context : public sigc::trackable
81 {
82         etl::handle<CanvasView> canvas_view_;
83         CanvasView::IsWorking is_working;
84
85         //Point mouse_pos;
86
87         handle<Duck> center;
88         handle<Duck> radius;
89         handle<Duck> closestpoint;
90
91         map<handle<Duck>,Real>  changetable;
92
93         etl::clock      clocktime;
94         Real            lastt;
95
96         bool added;
97
98         void refresh_ducks();
99
100         bool prev_workarea_layer_clicking;
101         bool prev_workarea_duck_clicking;
102         Duckmatic::Type old_duckmask;
103
104         //Toolbox settings
105         synfigapp::Settings& settings;
106
107         //Toolbox display
108         Gtk::Table options_table;
109
110         Gtk::Adjustment adj_delta;
111         Gtk::SpinButton spin_delta;
112
113         Gtk::Adjustment adj_radius;
114         Gtk::SpinButton spin_radius;
115
116         Gtk::CheckButton check_relative;
117
118         void AdjustWidth(handle<Duckmatic::Bezier> c, float t, Real mult, bool invert);
119
120 public:
121
122         Real get_delta()const { return adj_delta.get_value(); }
123         void set_delta(Real f) { adj_delta.set_value(f); }
124
125         Real get_radius()const { return adj_radius.get_value(); }
126         void set_radius(Real f) { adj_radius.set_value(f); }
127
128         bool get_relative() const { return check_relative.get_active(); }
129         void set_relative(bool r) { check_relative.set_active(r); }
130
131         void refresh_tool_options(); //to refresh the toolbox
132
133         //events
134         Smach::event_result event_stop_handler(const Smach::event& x);
135         Smach::event_result event_refresh_handler(const Smach::event& x);
136         Smach::event_result event_mouse_handler(const Smach::event& x);
137         Smach::event_result event_refresh_tool_options(const Smach::event& x);
138
139         //constructor destructor
140         StateWidth_Context(CanvasView* canvas_view);
141         ~StateWidth_Context();
142
143         //Canvas interaction
144         const etl::handle<CanvasView>& get_canvas_view()const{return canvas_view_;}
145         etl::handle<synfigapp::CanvasInterface> get_canvas_interface()const{return canvas_view_->canvas_interface();}
146         synfig::Canvas::Handle get_canvas()const{return canvas_view_->get_canvas();}
147         WorkArea * get_work_area()const{return canvas_view_->get_work_area();}
148
149         //Modifying settings etc.
150         void load_settings();
151         void save_settings();
152         void reset();
153
154 };      // END of class StateWidth_Context
155
156 /* === M E T H O D S ======================================================= */
157
158 StateWidth::StateWidth():
159         Smach::state<StateWidth_Context>("width")
160 {
161         insert(event_def(EVENT_STOP,&StateWidth_Context::event_stop_handler));
162         insert(event_def(EVENT_REFRESH,&StateWidth_Context::event_refresh_handler));
163         insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_DOWN,&StateWidth_Context::event_mouse_handler));
164         insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_DRAG,&StateWidth_Context::event_mouse_handler));
165         insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_UP,&StateWidth_Context::event_mouse_handler));
166         insert(event_def(EVENT_REFRESH_TOOL_OPTIONS,&StateWidth_Context::event_refresh_tool_options));
167 }
168
169 StateWidth::~StateWidth()
170 {
171 }
172
173 void
174 StateWidth_Context::load_settings()
175 {
176         try
177         {
178                 synfig::ChangeLocale change_locale(LC_NUMERIC, "C");
179                 String value;
180
181                 //parse the arguments yargh!
182                 if(settings.get_value("width.delta",value))
183                         set_delta(atof(value.c_str()));
184                 else
185                         set_delta(6);
186
187                 if(settings.get_value("width.radius",value))
188                         set_radius(atof(value.c_str()));
189                 else
190                         set_radius(15);
191
192                 //defaults to false
193                 if(settings.get_value("width.relative",value) && value == "1")
194                         set_relative(true);
195                 else
196                         set_relative(false);
197         }
198         catch(...)
199         {
200                 synfig::warning("State Width: Caught exception when attempting to load settings.");
201         }
202 }
203
204 void
205 StateWidth_Context::save_settings()
206 {
207         try
208         {
209                 synfig::ChangeLocale change_locale(LC_NUMERIC, "C");
210                 settings.set_value("width.delta",strprintf("%f",get_delta()));
211                 settings.set_value("width.radius",strprintf("%f",get_radius()));
212                 settings.set_value("width.relative",get_relative()?"1":"0");
213         }
214         catch(...)
215         {
216                 synfig::warning("State Width: Caught exception when attempting to save settings.");
217         }
218 }
219
220 void
221 StateWidth_Context::reset()
222 {
223         refresh_ducks();
224 }
225
226 StateWidth_Context::StateWidth_Context(CanvasView* canvas_view):
227         canvas_view_(canvas_view),
228         is_working(*canvas_view),
229         prev_workarea_layer_clicking(get_work_area()->get_allow_layer_clicks()),
230         prev_workarea_duck_clicking(get_work_area()->get_allow_duck_clicks()),
231         old_duckmask(get_work_area()->get_type_mask()),
232
233         settings(synfigapp::Main::get_selected_input_device()->settings()),
234
235         adj_delta(6,0,20,0.01,0.1),
236         spin_delta(adj_delta,0.01,3),
237
238         adj_radius(200,0,1e50,1,10),
239         spin_radius(adj_radius,1,1),
240
241         check_relative(_("Relative Growth"))
242 {
243         load_settings();
244
245         // Set up the tool options dialog
246         options_table.attach(*manage(new Gtk::Label(_("Width Tool"))),  0, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
247
248         //expand stuff
249         options_table.attach(*manage(new Gtk::Label(_("Growth:"))),             0, 1, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
250         options_table.attach(spin_delta,                                                                1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
251
252         options_table.attach(*manage(new Gtk::Label(_("Radius:"))),             0, 1, 2, 3, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
253         options_table.attach(spin_radius,                                                               1, 2, 2, 3, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
254
255         options_table.attach(check_relative,                                                    0, 2, 3, 4, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
256
257         options_table.show_all();
258
259         refresh_tool_options();
260         App::dialog_tool_options->present();
261
262         // Turn off layer clicking
263         get_work_area()->set_allow_layer_clicks(false);
264
265         // clear out the ducks
266         //get_work_area()->clear_ducks();
267
268         // Refresh the work area
269         get_work_area()->queue_draw();
270
271         //Create the new ducks
272         added = false;
273
274         if(!center)
275         {
276                 center = new Duck();
277                 center->set_name("p1");
278                 center->set_type(Duck::TYPE_POSITION);
279         }
280
281         if(!radius)
282         {
283                 radius = new Duck();
284                 radius->set_origin(center);
285                 radius->set_radius(true);
286                 radius->set_type(Duck::TYPE_RADIUS);
287                 radius->set_name("radius");
288         }
289
290         if(!closestpoint)
291         {
292                 closestpoint = new Duck();
293                 closestpoint->set_name("closest");
294                 closestpoint->set_type(Duck::TYPE_POSITION);
295         }
296
297         //Disable duck clicking for the maximum coolness :)
298         get_work_area()->set_allow_duck_clicks(false);
299         get_work_area()->set_type_mask((Duck::Type)((int)Duck::TYPE_WIDTH + (int)Duck::TYPE_RADIUS));
300
301         // Turn the mouse pointer to crosshairs
302         get_work_area()->set_cursor(Gdk::CROSSHAIR);
303
304         // Hide the tables if they are showing
305         //prev_table_status=get_canvas_view()->tables_are_visible();
306         //if(prev_table_status)get_canvas_view()->hide_tables();
307
308         // Disable the time bar
309         //get_canvas_view()->set_sensitive_timebar(false);
310
311         // Connect a signal
312         //get_work_area()->signal_user_click().connect(sigc::mem_fun(*this,&studio::StateWidth_Context::on_user_click));
313
314         App::toolbox->refresh();
315 }
316
317 void
318 StateWidth_Context::refresh_tool_options()
319 {
320         App::dialog_tool_options->clear();
321         App::dialog_tool_options->set_widget(options_table);
322         App::dialog_tool_options->set_local_name(_("Width Tool"));
323         App::dialog_tool_options->set_name("width");
324 }
325
326 Smach::event_result
327 StateWidth_Context::event_refresh_tool_options(const Smach::event& /*x*/)
328 {
329         refresh_tool_options();
330         return Smach::RESULT_ACCEPT;
331 }
332
333 StateWidth_Context::~StateWidth_Context()
334 {
335         save_settings();
336
337         //remove ducks if need be
338         if(added)
339         {
340                 get_work_area()->erase_duck(center);
341                 get_work_area()->erase_duck(radius);
342                 get_work_area()->erase_duck(closestpoint);
343                 added = false;
344         }
345
346         // Restore Duck clicking
347         get_work_area()->set_allow_duck_clicks(prev_workarea_duck_clicking);
348
349         // Restore layer clicking
350         get_work_area()->set_allow_layer_clicks(prev_workarea_layer_clicking);
351
352         // Restore the mouse pointer
353         get_work_area()->reset_cursor();
354
355         // Restore duck masking
356         get_work_area()->set_type_mask(old_duckmask);
357
358         // Tool options be rid of ye!!
359         App::dialog_tool_options->clear();
360
361         // Enable the time bar
362         //get_canvas_view()->set_sensitive_timebar(true);
363
364         // Bring back the tables if they were out before
365         //if(prev_table_status)get_canvas_view()->show_tables();
366
367         // Refresh the work area
368         get_work_area()->queue_draw();
369
370         App::toolbox->refresh();
371 }
372
373 Smach::event_result
374 StateWidth_Context::event_stop_handler(const Smach::event& /*x*/)
375 {
376         //throw Smach::egress_exception();
377         throw &state_normal;
378         return Smach::RESULT_OK;
379 }
380
381 Smach::event_result
382 StateWidth_Context::event_refresh_handler(const Smach::event& /*x*/)
383 {
384         refresh_ducks();
385         return Smach::RESULT_ACCEPT;
386 }
387
388 void
389 StateWidth_Context::AdjustWidth(handle<Duckmatic::Bezier> c, float t, Real mult, bool invert)
390 {
391         //Leave the function if there is no curve
392         if(!c)return;
393
394         Real amount1=0,amount2=0;
395
396         //decide how much to change each width
397         /*
398         t \in [0,1]
399
400         both pressure and multiply amount are in mult
401                 (may want to change this to allow different types of falloff)
402
403         rsq is the squared distance from the point on the curve (also part of the falloff)
404
405
406         */
407         //may want to provide a different falloff function...
408         if(t <= 0.2)
409                 amount1 = mult;
410         else if(t >= 0.8)
411                 amount2 = mult;
412         else
413         {
414                 t = (t-0.2)/0.6;
415                 amount1 = (1-t)*mult;
416                 amount2 = t*mult;
417         }
418
419         if(invert)
420         {
421                 amount1 *= -1;
422                 amount2 *= -1;
423         }
424
425         handle<Duck>    p1 = c->p1;
426         handle<Duck>    p2 = c->p2;
427
428         handle<Duck>    w1,w2;
429
430         //find w1,w2
431         {
432                 const DuckList dl = get_work_area()->get_duck_list();
433
434                 DuckList::const_iterator i = dl.begin();
435
436                 for(;i != dl.end(); ++i)
437                 {
438                         if((*i)->get_type() == Duck::TYPE_WIDTH)
439                         {
440                                 if((*i)->get_origin_duck() == p1)
441                                 {
442                                         w1 = *i;
443                                 }
444
445                                 if((*i)->get_origin_duck() == p2)
446                                 {
447                                         w2 = *i;
448                                 }
449                         }
450                 }
451         }
452
453         if(amount1 != 0 && w1)
454         {
455                 Real width = w1->get_point().mag();
456
457                 width += amount1;
458                 w1->set_point(Vector(width,0));
459
460                 //log in the list of changes...
461                 //to truly be changed after everything is said and done
462                 changetable[w1] = width;
463         }
464
465         if(amount2 != 0 && w2)
466         {
467                 Real width = w2->get_point().mag();
468
469                 width += amount2;
470                 w2->set_point(Vector(width,0));
471
472                 //log in the list of changes...
473                 //to truly be changed after everything is said and done
474                 changetable[w2] = width;
475         }
476 }
477
478 Smach::event_result
479 StateWidth_Context::event_mouse_handler(const Smach::event& x)
480 {
481         const EventMouse& event(*reinterpret_cast<const EventMouse*>(&x));
482
483         //handle the click
484         if( (event.key == EVENT_WORKAREA_MOUSE_BUTTON_DOWN || event.key == EVENT_WORKAREA_MOUSE_BUTTON_DRAG)
485                         && event.button == BUTTON_LEFT )
486         {
487                 const Real pw = get_work_area()->get_pw();
488                 const Real ph = get_work_area()->get_ph();
489                 const Real scale = sqrt(pw*pw+ph*ph);
490                 const Real rad = get_relative() ? scale * get_radius() : get_radius();
491
492                 bool invert = (event.modifier&Gdk::CONTROL_MASK);
493
494                 const Real threshold = 0.08;
495
496                 float t = 0;
497                 Real rsq = 0;
498
499                 Real dtime = 1/60.0;
500
501                 //if we're dragging get the difference in time between now and then
502                 if(event.key == EVENT_WORKAREA_MOUSE_BUTTON_DRAG)
503                 {
504                         dtime = min(1/15.0,clocktime());
505                 }
506                 clocktime.reset();
507
508                 //make way for new ducks
509                 //get_work_area()->clear_ducks();
510
511                 //update positions
512                 //mouse_pos = event.pos;
513
514                 center->set_point(event.pos);
515                 if(!added)get_work_area()->add_duck(center);
516
517                 radius->set_scalar(rad);
518                 if(!added)get_work_area()->add_duck(radius);
519
520                 //the other duck is at the current duck
521                 closestpoint->set_point(event.pos);
522                 if(!added)get_work_area()->add_duck(closestpoint);
523
524                 //get the closest curve...
525                 handle<Duckmatic::Bezier>       c;
526                 if(event.pressure >= threshold)
527                         c = get_work_area()->find_bezier(event.pos,scale*8,rad,&t);
528
529                 //run algorithm on event.pos to get 2nd placement
530                 if(!c.empty())
531                 {
532                         bezier<Point> curve;
533                         Point p;
534
535                         curve[0] = c->p1->get_trans_point();
536                         curve[1] = c->c1->get_trans_point();
537                         curve[2] = c->c2->get_trans_point();
538                         curve[3] = c->p2->get_trans_point();
539
540                         p = curve(t);
541                         rsq = (p-event.pos).mag_squared();
542
543                         const Real r = rad*rad;
544
545                         if(rsq < r)
546                         {
547                                 closestpoint->set_point(curve(t));
548
549                                 //adjust the width...
550                                 //squared falloff for radius... [0,1]
551
552                                 Real ri = (r - rsq)/r;
553                                 AdjustWidth(c,t,ri*event.pressure*get_delta()*dtime,invert);
554                         }
555                 }
556
557                 //the points have been added
558                 added = true;
559
560                 //draw where it is yo!
561                 get_work_area()->queue_draw();
562
563                 return Smach::RESULT_ACCEPT;
564         }
565
566         if(event.key == EVENT_WORKAREA_MOUSE_BUTTON_UP && event.button == BUTTON_LEFT)
567         {
568                 if(added)
569                 {
570                         get_work_area()->erase_duck(center);
571                         get_work_area()->erase_duck(radius);
572                         get_work_area()->erase_duck(closestpoint);
573                         added = false;
574                 }
575
576                 //Affect the width changes here...
577                 map<handle<Duck>,Real>::iterator i = changetable.begin();
578
579                 synfigapp::Action::PassiveGrouper group(get_canvas_interface()->get_instance().get(),_("Sketch Width"));
580                 for(; i != changetable.end(); ++i)
581                 {
582                         //for each duck modify IT!!!
583                         ValueDesc desc = i->first->get_value_desc();
584
585                         if(     desc.get_value_type() == ValueBase::TYPE_REAL )
586                         {
587                                 Action::Handle action(Action::create("ValueDescSet"));
588                                 assert(action);
589
590                                 action->set_param("canvas",get_canvas());
591                                 action->set_param("canvas_interface",get_canvas_interface());
592
593                                 action->set_param("value_desc",desc);
594                                 action->set_param("new_value",ValueBase(i->second));
595                                 action->set_param("time",get_canvas_view()->get_time());
596
597                                 if(!action->is_ready() || !get_canvas_view()->get_instance()->perform_action(action))
598                                 {
599                                         group.cancel();
600                                         synfig::warning("Changing the width action has failed");
601                                         return Smach::RESULT_ERROR;
602                                 }
603                         }
604                 }
605
606                 changetable.clear();
607
608                 get_work_area()->queue_draw();
609
610                 return Smach::RESULT_ACCEPT;
611         }
612
613         return Smach::RESULT_OK;
614 }
615
616
617 void
618 StateWidth_Context::refresh_ducks()
619 {
620         get_work_area()->clear_ducks();
621         get_work_area()->queue_draw();
622 }