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