Prevent unsafe thread change of local settings using synfig::ChangeLocale class
[synfig.git] / synfig-studio / src / gui / states / state_gradient.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file state_gradient.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 <synfig/valuenode_dynamiclist.h>
37 #include <synfigapp/action_system.h>
38
39 #include "state_gradient.h"
40 #include "state_normal.h"
41 #include "canvasview.h"
42 #include "workarea.h"
43 #include "app.h"
44
45 #include <synfigapp/action.h>
46 #include "event_mouse.h"
47 #include "event_layerclick.h"
48 #include "toolbox.h"
49 #include "docks/dialog_tooloptions.h"
50 #include <gtkmm/optionmenu.h>
51 #include "duck.h"
52
53 #include "widgets/widget_enum.h"
54 #include <synfigapp/main.h>
55
56 #include "general.h"
57
58 #endif
59
60 /* === U S I N G =========================================================== */
61
62 using namespace std;
63 using namespace etl;
64 using namespace synfig;
65 using namespace studio;
66
67 /* === M A C R O S ========================================================= */
68
69 enum
70 {
71         GRADIENT_INTERPOLATION_LINEAR=0,
72         GRADIENT_RADIAL=1,
73         GRADIENT_CONICAL=2,
74         GRADIENT_SPIRAL=3
75 };
76
77 /* === G L O B A L S ======================================================= */
78
79 StateGradient studio::state_gradient;
80
81 /* === C L A S S E S & S T R U C T S ======================================= */
82
83 class studio::StateGradient_Context : public sigc::trackable
84 {
85         etl::handle<CanvasView> canvas_view_;
86         CanvasView::IsWorking is_working;
87
88         Duckmatic::Push duckmatic_push;
89
90         synfigapp::Settings& settings;
91
92         Point point_holder;
93
94         etl::handle<Duck> point2_duck;
95
96         void refresh_ducks();
97
98         bool prev_workarea_layer_status_;
99
100         Gtk::Table options_table;
101         Gtk::Entry entry_id;
102         Widget_Enum enum_type;
103 #ifdef BLEND_METHOD_IN_TOOL_OPTIONS
104         Widget_Enum     enum_blend;
105 #endif  // BLEND_METHOD_IN_TOOL_OPTIONS
106
107 public:
108         synfig::String get_id()const { return entry_id.get_text(); }
109         void set_id(const synfig::String& x) { return entry_id.set_text(x); }
110
111         int get_type()const { return enum_type.get_value(); }
112         void set_type(int x) { return enum_type.set_value(x); }
113
114 #ifdef BLEND_METHOD_IN_TOOL_OPTIONS
115         int get_blend()const { return enum_blend.get_value(); }
116         void set_blend(int x) { return enum_blend.set_value(x); }
117 #endif  // BLEND_METHOD_IN_TOOL_OPTIONS
118
119         Smach::event_result event_stop_handler(const Smach::event& x);
120
121         Smach::event_result event_refresh_handler(const Smach::event& x);
122
123         Smach::event_result event_mouse_click_handler(const Smach::event& x);
124         Smach::event_result event_refresh_tool_options(const Smach::event& x);
125
126         void refresh_tool_options();
127
128         StateGradient_Context(CanvasView* canvas_view);
129
130         ~StateGradient_Context();
131
132         const etl::handle<CanvasView>& get_canvas_view()const{return canvas_view_;}
133         etl::handle<synfigapp::CanvasInterface> get_canvas_interface()const{return canvas_view_->canvas_interface();}
134         synfig::Canvas::Handle get_canvas()const{return canvas_view_->get_canvas();}
135         WorkArea * get_work_area()const{return canvas_view_->get_work_area();}
136
137         //void on_user_click(synfig::Point point);
138         void load_settings();
139         void save_settings();
140         void reset();
141         void increment_id();
142
143         void make_gradient(const Point& p1, const Point& p2);
144         bool egress_on_selection_change;
145         Smach::event_result event_layer_selection_changed_handler(const Smach::event& /*x*/)
146         {
147                 if(egress_on_selection_change)
148                         throw &state_normal; //throw Smach::egress_exception();
149                 return Smach::RESULT_OK;
150         }
151
152 };      // END of class StateGradient_Context
153
154 /* === M E T H O D S ======================================================= */
155
156 StateGradient::StateGradient():
157         Smach::state<StateGradient_Context>("gradient")
158 {
159         insert(event_def(EVENT_LAYER_SELECTION_CHANGED,&StateGradient_Context::event_layer_selection_changed_handler));
160         insert(event_def(EVENT_STOP,&StateGradient_Context::event_stop_handler));
161         insert(event_def(EVENT_TABLES_SHOW,&StateGradient_Context::event_stop_handler));
162         insert(event_def(EVENT_REFRESH,&StateGradient_Context::event_refresh_handler));
163         insert(event_def(EVENT_REFRESH_DUCKS,&StateGradient_Context::event_refresh_handler));
164         insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_DOWN,&StateGradient_Context::event_mouse_click_handler));
165         insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_DRAG,&StateGradient_Context::event_mouse_click_handler));
166         insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_UP,&StateGradient_Context::event_mouse_click_handler));
167         insert(event_def(EVENT_REFRESH_TOOL_OPTIONS,&StateGradient_Context::event_refresh_tool_options));
168 }
169
170 StateGradient::~StateGradient()
171 {
172 }
173
174 void
175 StateGradient_Context::load_settings()
176 {
177         try
178         {
179                 synfig::ChangeLocale change_locale(LC_NUMERIC, "C");
180                 String value;
181
182                 if(settings.get_value("gradient.id",value))
183                         set_id(value);
184                 else
185                         set_id("Gradient");
186
187                 if(settings.get_value("gradient.type",value))
188                         set_type(atoi(value.c_str()));
189                 else
190                         set_type(GRADIENT_INTERPOLATION_LINEAR);
191
192 #ifdef BLEND_METHOD_IN_TOOL_OPTIONS
193                 if(settings.get_value("gradient.blend",value))
194                         set_blend(atoi(value.c_str()));
195                 else
196                         set_blend(Color::BLEND_COMPOSITE);
197 #endif  // BLEND_METHOD_IN_TOOL_OPTIONS
198         }
199         catch(...)
200         {
201                 synfig::warning("State Gradient: Caught exception when attempting to load settings.");
202         }
203 }
204
205 void
206 StateGradient_Context::save_settings()
207 {
208         try
209         {
210                 synfig::ChangeLocale change_locale(LC_NUMERIC, "C");
211                 settings.set_value("gradient.id",get_id().c_str());
212                 settings.set_value("gradient.type",strprintf("%d",get_type()));
213 #ifdef BLEND_METHOD_IN_TOOL_OPTIONS
214                 settings.set_value("gradient.blend",strprintf("%d",get_blend()));
215 #endif  // BLEND_METHOD_IN_TOOL_OPTIONS
216         }
217         catch(...)
218         {
219                 synfig::warning("State Gradient: Caught exception when attempting to save settings.");
220         }
221 }
222
223 void
224 StateGradient_Context::reset()
225 {
226         refresh_ducks();
227 }
228
229 void
230 StateGradient_Context::increment_id()
231 {
232         String id(get_id());
233         int number=1;
234         int digits=0;
235
236         if(id.empty())
237                 id="Gradient";
238
239         // If there is a number
240         // already at the end of the
241         // id, then remove it.
242         if(id[id.size()-1]<='9' && id[id.size()-1]>='0')
243         {
244                 // figure out how many digits it is
245                 for (digits = 0;
246                          (int)id.size()-1 >= digits && id[id.size()-1-digits] <= '9' && id[id.size()-1-digits] >= '0';
247                          digits++)
248                         ;
249
250                 String str_number;
251                 str_number=String(id,id.size()-digits,id.size());
252                 id=String(id,0,id.size()-digits);
253
254                 number=atoi(str_number.c_str());
255         }
256         else
257         {
258                 number=1;
259                 digits=3;
260         }
261
262         number++;
263
264         // Add the number back onto the id
265         {
266                 const String format(strprintf("%%0%dd",digits));
267                 id+=strprintf(format.c_str(),number);
268         }
269
270         // Set the ID
271         set_id(id);
272 }
273
274 StateGradient_Context::StateGradient_Context(CanvasView* canvas_view):
275         canvas_view_(canvas_view),
276         is_working(*canvas_view),
277         duckmatic_push(get_work_area()),
278         settings(synfigapp::Main::get_selected_input_device()->settings()),
279         prev_workarea_layer_status_(get_work_area()->get_allow_layer_clicks()),
280         entry_id()
281 {
282         egress_on_selection_change=true;
283         // Set up the tool options dialog
284         options_table.attach(*manage(new Gtk::Label(_("Gradient Tool"))),       0, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
285         options_table.attach(entry_id,                                                                          0, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
286
287         enum_type.set_param_desc(ParamDesc("type")
288                 .set_local_name(_("Gradient Type"))
289                 .set_description(_("Determines the type of Gradient used"))
290                 .set_hint("enum")
291                 .add_enum_value(GRADIENT_INTERPOLATION_LINEAR,"linear",_("Linear"))
292                 .add_enum_value(GRADIENT_RADIAL,"radial",_("Radial"))
293                 .add_enum_value(GRADIENT_CONICAL,"conical",_("Conical"))
294                 .add_enum_value(GRADIENT_SPIRAL,"spiral",_("Spiral")));
295
296 #ifdef BLEND_METHOD_IN_TOOL_OPTIONS
297         enum_blend.set_param_desc(ParamDesc(Color::BLEND_COMPOSITE,"blend_method")
298                 .set_local_name(_("Blend Method"))
299                 .set_description(_("The blend method the gradient will use")));
300 #endif  // BLEND_METHOD_IN_TOOL_OPTIONS
301
302         load_settings();
303
304         options_table.attach(enum_type, 0, 2, 2, 3, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
305 #ifdef BLEND_METHOD_IN_TOOL_OPTIONS
306         options_table.attach(enum_blend, 0, 2, 3, 4, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
307 #endif  // BLEND_METHOD_IN_TOOL_OPTIONS
308
309         options_table.show_all();
310         refresh_tool_options();
311         App::dialog_tool_options->present();
312
313
314         // Turn off layer clicking
315         get_work_area()->set_allow_layer_clicks(false);
316
317         get_work_area()->set_cursor(Gdk::CROSSHAIR);
318
319         // clear out the ducks
320         get_work_area()->clear_ducks();
321
322         // Refresh the work area
323         get_work_area()->queue_draw();
324
325         get_work_area()->refresh_cursor();
326
327         // Hide the tables if they are showing
328         get_canvas_view()->hide_tables();
329
330         // Disable the time bar
331         //get_canvas_view()->set_sensitive_timebar(false);
332
333         // Connect a signal
334         //get_work_area()->signal_user_click().connect(sigc::mem_fun(*this,&studio::StateGradient_Context::on_user_click));
335
336         App::toolbox->refresh();
337 }
338
339 void
340 StateGradient_Context::refresh_tool_options()
341 {
342         App::dialog_tool_options->clear();
343         App::dialog_tool_options->set_widget(options_table);
344         App::dialog_tool_options->set_local_name(_("Gradient Tool"));
345         App::dialog_tool_options->set_name("gradient");
346 }
347
348 Smach::event_result
349 StateGradient_Context::event_refresh_tool_options(const Smach::event& /*x*/)
350 {
351         refresh_tool_options();
352         return Smach::RESULT_ACCEPT;
353 }
354
355 StateGradient_Context::~StateGradient_Context()
356 {
357         save_settings();
358
359         // Restore layer clicking
360 //      get_work_area()->set_allow_layer_clicks(prev_workarea_layer_status_);
361         get_work_area()->set_allow_layer_clicks(true);
362         get_work_area()->reset_cursor();
363
364         App::dialog_tool_options->clear();
365
366         // Enable the time bar
367         //get_canvas_view()->set_sensitive_timebar(true);
368
369         // Bring back the tables if they were out before
370         //if(prev_table_status)get_canvas_view()->show_tables();
371
372         // Refresh the work area
373         get_work_area()->queue_draw();
374
375         get_canvas_view()->queue_rebuild_ducks();
376
377         //get_canvas_view()->show_tables();
378
379         get_work_area()->refresh_cursor();
380
381         App::toolbox->refresh();
382 }
383
384 Smach::event_result
385 StateGradient_Context::event_stop_handler(const Smach::event& /*x*/)
386 {
387         //throw Smach::egress_exception();
388         throw &state_normal;
389         return Smach::RESULT_OK;
390 }
391
392 Smach::event_result
393 StateGradient_Context::event_refresh_handler(const Smach::event& /*x*/)
394 {
395         refresh_ducks();
396         return Smach::RESULT_ACCEPT;
397 }
398
399 void
400 StateGradient_Context::make_gradient(const Point& _p1, const Point& _p2)
401 {
402         synfigapp::Action::PassiveGrouper group(get_canvas_interface()->get_instance().get(),_("New Gradient"));
403         synfigapp::PushMode push_mode(get_canvas_interface(),synfigapp::MODE_NORMAL);
404
405         Layer::Handle layer;
406
407         Canvas::Handle canvas(get_canvas_view()->get_canvas());
408         int depth(0);
409
410         // we are temporarily using the layer to hold something
411         layer=get_canvas_view()->get_selection_manager()->get_selected_layer();
412         if(layer)
413         {
414                 depth=layer->get_depth();
415                 canvas=layer->get_canvas();
416         }
417         const synfig::TransformStack& transform(get_canvas_view()->get_curr_transform_stack());
418         const Point p1(transform.unperform(_p1));
419         const Point p2(transform.unperform(_p2));
420
421         switch(get_type())
422         {
423         case GRADIENT_INTERPOLATION_LINEAR:
424
425                 layer=get_canvas_interface()->add_layer_to("linear_gradient",canvas,depth);
426                 if (!layer)
427                 {
428                         get_canvas_view()->get_ui_interface()->error(_("Unable to create layer"));
429                         group.cancel();
430                         return;
431                 }
432                 layer->set_param("p1",p1);
433                 get_canvas_interface()->signal_layer_param_changed()(layer,"p1");
434                 layer->set_param("p2",p2);
435                 get_canvas_interface()->signal_layer_param_changed()(layer,"p2");
436                 break;
437         case GRADIENT_RADIAL:
438                 layer=get_canvas_interface()->add_layer_to("radial_gradient",canvas,depth);
439                 if (!layer)
440                 {
441                         get_canvas_view()->get_ui_interface()->error(_("Unable to create layer"));
442                         group.cancel();
443                         return;
444                 }
445                 layer->set_param("center",p1);
446                 get_canvas_interface()->signal_layer_param_changed()(layer,"center");
447                 layer->set_param("radius",(p2-p1).mag());
448                 get_canvas_interface()->signal_layer_param_changed()(layer,"radius");
449                 break;
450         case GRADIENT_CONICAL:
451                 layer=get_canvas_interface()->add_layer_to("conical_gradient",canvas,depth);
452                 if (!layer)
453                 {
454                         get_canvas_view()->get_ui_interface()->error(_("Unable to create layer"));
455                         group.cancel();
456                         return;
457                 }
458                 layer->set_param("center",p1);
459                 get_canvas_interface()->signal_layer_param_changed()(layer,"center");
460                 {
461                         Vector diff(p2-p1);
462                         layer->set_param("angle",Angle::tan(diff[1],diff[0]));
463                         get_canvas_interface()->signal_layer_param_changed()(layer,"angle");
464                 }
465                 break;
466         case GRADIENT_SPIRAL:
467                 layer=get_canvas_interface()->add_layer_to("spiral_gradient",canvas,depth);
468                 if (!layer)
469                 {
470                         get_canvas_view()->get_ui_interface()->error(_("Unable to create layer"));
471                         group.cancel();
472                         return;
473                 }
474                 layer->set_param("center",p1);
475                 get_canvas_interface()->signal_layer_param_changed()(layer,"center");
476                 layer->set_param("radius",(p2-p1).mag());
477                 get_canvas_interface()->signal_layer_param_changed()(layer,"radius");
478                 {
479                         Vector diff(p2-p1);
480                         layer->set_param("angle",Angle::tan(diff[1],diff[0]));
481                         get_canvas_interface()->signal_layer_param_changed()(layer,"angle");
482                 }
483                 break;
484
485         default:
486                 return;
487         }
488
489 #ifdef BLEND_METHOD_IN_TOOL_OPTIONS
490         layer->set_param("blend_method",get_blend());
491         get_canvas_interface()->signal_layer_param_changed()(layer,"blend_method");
492 #endif  // BLEND_METHOD_IN_TOOL_OPTIONS
493
494         layer->set_description(get_id());
495         get_canvas_interface()->signal_layer_new_description()(layer,layer->get_description());
496
497         egress_on_selection_change=false;
498         synfigapp::SelectionManager::LayerList layer_selection;
499         if (!getenv("SYNFIG_TOOLS_CLEAR_SELECTION"))
500                 layer_selection = get_canvas_view()->get_selection_manager()->get_selected_layers();
501         get_canvas_interface()->get_selection_manager()->clear_selected_layers();
502         layer_selection.push_back(layer);
503         get_canvas_interface()->get_selection_manager()->set_selected_layers(layer_selection);
504         egress_on_selection_change=true;
505
506         reset();
507         increment_id();
508 }
509
510 Smach::event_result
511 StateGradient_Context::event_mouse_click_handler(const Smach::event& x)
512 {
513         const EventMouse& event(*reinterpret_cast<const EventMouse*>(&x));
514
515         if(event.key==EVENT_WORKAREA_MOUSE_BUTTON_DOWN && event.button==BUTTON_LEFT)
516         {
517                 point_holder=get_work_area()->snap_point_to_grid(event.pos);
518                 etl::handle<Duck> duck=new Duck();
519                 duck->set_point(point_holder);
520                 duck->set_name("p1");
521                 duck->set_type(Duck::TYPE_POSITION);
522                 get_work_area()->add_duck(duck);
523
524                 point2_duck=new Duck();
525                 point2_duck->set_point(point_holder);
526                 point2_duck->set_name("p2");
527                 point2_duck->set_type(Duck::TYPE_POSITION);
528                 get_work_area()->add_duck(point2_duck);
529
530                 handle<Duckmatic::Bezier> bezier(new Duckmatic::Bezier());
531                 bezier->p1=bezier->c1=duck;
532                 bezier->p2=bezier->c2=point2_duck;
533                 get_work_area()->add_bezier(bezier);
534
535                 return Smach::RESULT_ACCEPT;
536         }
537
538         if(event.key==EVENT_WORKAREA_MOUSE_BUTTON_DRAG && event.button==BUTTON_LEFT)
539         {
540                 if (!point2_duck) return Smach::RESULT_OK;
541                 point2_duck->set_point(get_work_area()->snap_point_to_grid(event.pos));
542                 get_work_area()->queue_draw();
543                 return Smach::RESULT_ACCEPT;
544         }
545
546         if(event.key==EVENT_WORKAREA_MOUSE_BUTTON_UP && event.button==BUTTON_LEFT)
547         {
548                 make_gradient(point_holder, get_work_area()->snap_point_to_grid(event.pos));
549                 get_work_area()->clear_ducks();
550                 return Smach::RESULT_ACCEPT;
551         }
552
553         return Smach::RESULT_OK;
554 }
555
556
557 void
558 StateGradient_Context::refresh_ducks()
559 {
560         get_work_area()->clear_ducks();
561         get_work_area()->queue_draw();
562 }