6e212ea4012d0cecc2bf0ea51ae4112e9fca6c5f
[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                 SETTINGS_LOCALE_SAFE_AND_BACKUP
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                 SETTINGS_LOCALE_RESTORE
198         }
199         catch(...)
200         {
201                 synfig::warning("State Width: Caught exception when attempting to load settings.");
202         }
203 }
204
205 void
206 StateWidth_Context::save_settings()
207 {
208         try
209         {
210                 SETTINGS_LOCALE_SAFE_AND_BACKUP
211                 settings.set_value("width.delta",strprintf("%f",get_delta()));
212                 settings.set_value("width.radius",strprintf("%f",get_radius()));
213                 settings.set_value("width.relative",get_relative()?"1":"0");
214                 SETTINGS_LOCALE_RESTORE
215         }
216         catch(...)
217         {
218                 synfig::warning("State Width: Caught exception when attempting to save settings.");
219         }
220 }
221
222 void
223 StateWidth_Context::reset()
224 {
225         refresh_ducks();
226 }
227
228 StateWidth_Context::StateWidth_Context(CanvasView* canvas_view):
229         canvas_view_(canvas_view),
230         is_working(*canvas_view),
231         prev_workarea_layer_clicking(get_work_area()->get_allow_layer_clicks()),
232         prev_workarea_duck_clicking(get_work_area()->get_allow_duck_clicks()),
233         old_duckmask(get_work_area()->get_type_mask()),
234
235         settings(synfigapp::Main::get_selected_input_device()->settings()),
236
237         adj_delta(6,0,20,0.01,0.1),
238         spin_delta(adj_delta,0.01,3),
239
240         adj_radius(200,0,1e50,1,10),
241         spin_radius(adj_radius,1,1),
242
243         check_relative(_("Relative Growth"))
244 {
245         load_settings();
246
247         // Set up the tool options dialog
248         options_table.attach(*manage(new Gtk::Label(_("Width Tool"))),  0, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
249
250         //expand stuff
251         options_table.attach(*manage(new Gtk::Label(_("Growth:"))),             0, 1, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
252         options_table.attach(spin_delta,                                                                1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
253
254         options_table.attach(*manage(new Gtk::Label(_("Radius:"))),             0, 1, 2, 3, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
255         options_table.attach(spin_radius,                                                               1, 2, 2, 3, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
256
257         options_table.attach(check_relative,                                                    0, 2, 3, 4, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
258
259         options_table.show_all();
260
261         refresh_tool_options();
262         App::dialog_tool_options->present();
263
264         // Turn off layer clicking
265         get_work_area()->set_allow_layer_clicks(false);
266
267         // clear out the ducks
268         //get_work_area()->clear_ducks();
269
270         // Refresh the work area
271         get_work_area()->queue_draw();
272
273         //Create the new ducks
274         added = false;
275
276         if(!center)
277         {
278                 center = new Duck();
279                 center->set_name("p1");
280                 center->set_type(Duck::TYPE_POSITION);
281         }
282
283         if(!radius)
284         {
285                 radius = new Duck();
286                 radius->set_origin(center);
287                 radius->set_radius(true);
288                 radius->set_type(Duck::TYPE_RADIUS);
289                 radius->set_name("radius");
290         }
291
292         if(!closestpoint)
293         {
294                 closestpoint = new Duck();
295                 closestpoint->set_name("closest");
296                 closestpoint->set_type(Duck::TYPE_POSITION);
297         }
298
299         //Disable duck clicking for the maximum coolness :)
300         get_work_area()->set_allow_duck_clicks(false);
301         get_work_area()->set_type_mask((Duck::Type)((int)Duck::TYPE_WIDTH + (int)Duck::TYPE_RADIUS));
302
303         // Turn the mouse pointer to crosshairs
304         get_work_area()->set_cursor(Gdk::CROSSHAIR);
305
306         // Hide the tables if they are showing
307         //prev_table_status=get_canvas_view()->tables_are_visible();
308         //if(prev_table_status)get_canvas_view()->hide_tables();
309
310         // Disable the time bar
311         //get_canvas_view()->set_sensitive_timebar(false);
312
313         // Connect a signal
314         //get_work_area()->signal_user_click().connect(sigc::mem_fun(*this,&studio::StateWidth_Context::on_user_click));
315
316         App::toolbox->refresh();
317 }
318
319 void
320 StateWidth_Context::refresh_tool_options()
321 {
322         App::dialog_tool_options->clear();
323         App::dialog_tool_options->set_widget(options_table);
324         App::dialog_tool_options->set_local_name(_("Width Tool"));
325         App::dialog_tool_options->set_name("width");
326 }
327
328 Smach::event_result
329 StateWidth_Context::event_refresh_tool_options(const Smach::event& /*x*/)
330 {
331         refresh_tool_options();
332         return Smach::RESULT_ACCEPT;
333 }
334
335 StateWidth_Context::~StateWidth_Context()
336 {
337         save_settings();
338
339         //remove ducks if need be
340         if(added)
341         {
342                 get_work_area()->erase_duck(center);
343                 get_work_area()->erase_duck(radius);
344                 get_work_area()->erase_duck(closestpoint);
345                 added = false;
346         }
347
348         // Restore Duck clicking
349         get_work_area()->set_allow_duck_clicks(prev_workarea_duck_clicking);
350
351         // Restore layer clicking
352         get_work_area()->set_allow_layer_clicks(prev_workarea_layer_clicking);
353
354         // Restore the mouse pointer
355         get_work_area()->reset_cursor();
356
357         // Restore duck masking
358         get_work_area()->set_type_mask(old_duckmask);
359
360         // Tool options be rid of ye!!
361         App::dialog_tool_options->clear();
362
363         // Enable the time bar
364         //get_canvas_view()->set_sensitive_timebar(true);
365
366         // Bring back the tables if they were out before
367         //if(prev_table_status)get_canvas_view()->show_tables();
368
369         // Refresh the work area
370         get_work_area()->queue_draw();
371
372         App::toolbox->refresh();
373 }
374
375 Smach::event_result
376 StateWidth_Context::event_stop_handler(const Smach::event& /*x*/)
377 {
378         //throw Smach::egress_exception();
379         throw &state_normal;
380         return Smach::RESULT_OK;
381 }
382
383 Smach::event_result
384 StateWidth_Context::event_refresh_handler(const Smach::event& /*x*/)
385 {
386         refresh_ducks();
387         return Smach::RESULT_ACCEPT;
388 }
389
390 void
391 StateWidth_Context::AdjustWidth(handle<Duckmatic::Bezier> c, float t, Real mult, bool invert)
392 {
393         //Leave the function if there is no curve
394         if(!c)return;
395
396         Real amount1=0,amount2=0;
397
398         //decide how much to change each width
399         /*
400         t \in [0,1]
401
402         both pressure and multiply amount are in mult
403                 (may want to change this to allow different types of falloff)
404
405         rsq is the squared distance from the point on the curve (also part of the falloff)
406
407
408         */
409         //may want to provide a different falloff function...
410         if(t <= 0.2)
411                 amount1 = mult;
412         else if(t >= 0.8)
413                 amount2 = mult;
414         else
415         {
416                 t = (t-0.2)/0.6;
417                 amount1 = (1-t)*mult;
418                 amount2 = t*mult;
419         }
420
421         if(invert)
422         {
423                 amount1 *= -1;
424                 amount2 *= -1;
425         }
426
427         handle<Duck>    p1 = c->p1;
428         handle<Duck>    p2 = c->p2;
429
430         handle<Duck>    w1,w2;
431
432         //find w1,w2
433         {
434                 const DuckList dl = get_work_area()->get_duck_list();
435
436                 DuckList::const_iterator i = dl.begin();
437
438                 for(;i != dl.end(); ++i)
439                 {
440                         if((*i)->get_type() == Duck::TYPE_WIDTH)
441                         {
442                                 if((*i)->get_origin_duck() == p1)
443                                 {
444                                         w1 = *i;
445                                 }
446
447                                 if((*i)->get_origin_duck() == p2)
448                                 {
449                                         w2 = *i;
450                                 }
451                         }
452                 }
453         }
454
455         if(amount1 != 0 && w1)
456         {
457                 Real width = w1->get_point().mag();
458
459                 width += amount1;
460                 w1->set_point(Vector(width,0));
461
462                 //log in the list of changes...
463                 //to truly be changed after everything is said and done
464                 changetable[w1] = width;
465         }
466
467         if(amount2 != 0 && w2)
468         {
469                 Real width = w2->get_point().mag();
470
471                 width += amount2;
472                 w2->set_point(Vector(width,0));
473
474                 //log in the list of changes...
475                 //to truly be changed after everything is said and done
476                 changetable[w2] = width;
477         }
478 }
479
480 Smach::event_result
481 StateWidth_Context::event_mouse_handler(const Smach::event& x)
482 {
483         const EventMouse& event(*reinterpret_cast<const EventMouse*>(&x));
484
485         //handle the click
486         if( (event.key == EVENT_WORKAREA_MOUSE_BUTTON_DOWN || event.key == EVENT_WORKAREA_MOUSE_BUTTON_DRAG)
487                         && event.button == BUTTON_LEFT )
488         {
489                 const Real pw = get_work_area()->get_pw();
490                 const Real ph = get_work_area()->get_ph();
491                 const Real scale = sqrt(pw*pw+ph*ph);
492                 const Real rad = get_relative() ? scale * get_radius() : get_radius();
493
494                 bool invert = (event.modifier&Gdk::CONTROL_MASK);
495
496                 const Real threshold = 0.08;
497
498                 float t = 0;
499                 Real rsq = 0;
500
501                 Real dtime = 1/60.0;
502
503                 //if we're dragging get the difference in time between now and then
504                 if(event.key == EVENT_WORKAREA_MOUSE_BUTTON_DRAG)
505                 {
506                         dtime = min(1/15.0,clocktime());
507                 }
508                 clocktime.reset();
509
510                 //make way for new ducks
511                 //get_work_area()->clear_ducks();
512
513                 //update positions
514                 //mouse_pos = event.pos;
515
516                 center->set_point(event.pos);
517                 if(!added)get_work_area()->add_duck(center);
518
519                 radius->set_scalar(rad);
520                 if(!added)get_work_area()->add_duck(radius);
521
522                 //the other duck is at the current duck
523                 closestpoint->set_point(event.pos);
524                 if(!added)get_work_area()->add_duck(closestpoint);
525
526                 //get the closest curve...
527                 handle<Duckmatic::Bezier>       c;
528                 if(event.pressure >= threshold)
529                         c = get_work_area()->find_bezier(event.pos,scale*8,rad,&t);
530
531                 //run algorithm on event.pos to get 2nd placement
532                 if(!c.empty())
533                 {
534                         bezier<Point> curve;
535                         Point p;
536
537                         curve[0] = c->p1->get_trans_point();
538                         curve[1] = c->c1->get_trans_point();
539                         curve[2] = c->c2->get_trans_point();
540                         curve[3] = c->p2->get_trans_point();
541
542                         p = curve(t);
543                         rsq = (p-event.pos).mag_squared();
544
545                         const Real r = rad*rad;
546
547                         if(rsq < r)
548                         {
549                                 closestpoint->set_point(curve(t));
550
551                                 //adjust the width...
552                                 //squared falloff for radius... [0,1]
553
554                                 Real ri = (r - rsq)/r;
555                                 AdjustWidth(c,t,ri*event.pressure*get_delta()*dtime,invert);
556                         }
557                 }
558
559                 //the points have been added
560                 added = true;
561
562                 //draw where it is yo!
563                 get_work_area()->queue_draw();
564
565                 return Smach::RESULT_ACCEPT;
566         }
567
568         if(event.key == EVENT_WORKAREA_MOUSE_BUTTON_UP && event.button == BUTTON_LEFT)
569         {
570                 if(added)
571                 {
572                         get_work_area()->erase_duck(center);
573                         get_work_area()->erase_duck(radius);
574                         get_work_area()->erase_duck(closestpoint);
575                         added = false;
576                 }
577
578                 //Affect the width changes here...
579                 map<handle<Duck>,Real>::iterator i = changetable.begin();
580
581                 synfigapp::Action::PassiveGrouper group(get_canvas_interface()->get_instance().get(),_("Sketch Width"));
582                 for(; i != changetable.end(); ++i)
583                 {
584                         //for each duck modify IT!!!
585                         ValueDesc desc = i->first->get_value_desc();
586
587                         if(     desc.get_value_type() == ValueBase::TYPE_REAL )
588                         {
589                                 Action::Handle action(Action::create("ValueDescSet"));
590                                 assert(action);
591
592                                 action->set_param("canvas",get_canvas());
593                                 action->set_param("canvas_interface",get_canvas_interface());
594
595                                 action->set_param("value_desc",desc);
596                                 action->set_param("new_value",ValueBase(i->second));
597                                 action->set_param("time",get_canvas_view()->get_time());
598
599                                 if(!action->is_ready() || !get_canvas_view()->get_instance()->perform_action(action))
600                                 {
601                                         group.cancel();
602                                         synfig::warning("Changing the width action has failed");
603                                         return Smach::RESULT_ERROR;
604                                 }
605                         }
606                 }
607
608                 changetable.clear();
609
610                 get_work_area()->queue_draw();
611
612                 return Smach::RESULT_ACCEPT;
613         }
614
615         return Smach::RESULT_OK;
616 }
617
618
619 void
620 StateWidth_Context::refresh_ducks()
621 {
622         get_work_area()->clear_ducks();
623         get_work_area()->queue_draw();
624 }