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