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