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