Changed the "tagrelease" and "tagstable" make targets to use subversion. Also increme...
[synfig.git] / synfig-studio / tags / stable / src / gtkmm / state_width.cpp
1 /* === S I N F 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 Robert B. Quattlebaum Jr.
9 **
10 **      This software and associated documentation
11 **      are CONFIDENTIAL and PROPRIETARY property of
12 **      the above-mentioned copyright holder.
13 **
14 **      You may not copy, print, publish, or in any
15 **      other way distribute this software without
16 **      a prior written agreement with
17 **      the copyright holder.
18 **      \endlegal
19 */
20 /* ========================================================================= */
21
22 /* === H E A D E R S ======================================================= */
23
24 #ifdef USING_PCH
25 #       include "pch.h"
26 #else
27 #ifdef HAVE_CONFIG_H
28 #       include <config.h>
29 #endif
30
31 #include <gtkmm/dialog.h>
32 #include <gtkmm/entry.h>
33
34 #include <ETL/bezier>
35
36 #include <sinfg/valuenode_dynamiclist.h>
37 #include <sinfgapp/action_system.h>
38
39 #include "state_width.h"
40 #include "canvasview.h"
41 #include "workarea.h"
42 #include "app.h"
43
44 #include <sinfgapp/action.h>
45 #include "event_mouse.h"
46 #include "event_layerclick.h"
47 #include "toolbox.h"
48 #include "dialog_tooloptions.h"
49 #include <gtkmm/optionmenu.h>
50 #include "duck.h"
51
52 //#include <sinfgapp/value_desc.h>
53 #include <sinfgapp/main.h>
54
55 #include <ETL/clock>
56
57 #endif
58
59 /* === U S I N G =========================================================== */
60
61 using namespace std;
62 using namespace etl;
63 using namespace sinfg;
64 using namespace sinfgapp;
65 using namespace studio;
66
67 /* === M A C R O S ========================================================= */
68
69 /* === G L O B A L S ======================================================= */
70
71 StateWidth studio::state_width;
72
73 /* === C L A S S E S & S T R U C T S ======================================= */
74
75 class studio::StateWidth_Context : public sigc::trackable
76 {
77         etl::handle<CanvasView> canvas_view_;
78         CanvasView::IsWorking is_working;
79         
80         //Point mouse_pos;
81         
82         handle<Duck> center;
83         handle<Duck> radius;
84         handle<Duck> closestpoint;
85         
86         map<handle<Duck>,Real>  changetable;
87         
88         etl::clock      clocktime;
89         Real            lastt;
90         
91         bool added;
92
93         void refresh_ducks();
94         
95         bool prev_workarea_layer_clicking;
96         bool prev_workarea_duck_clicking;
97         Duckmatic::Type old_duckmask;
98                 
99         //Toolbox settings
100         sinfgapp::Settings& settings;
101         
102         //Toolbox display
103         Gtk::Table options_table;
104         
105         //Gtk::Entry            entry_id; //what to name the layer
106                 
107         Gtk::Adjustment adj_delta;
108         Gtk::SpinButton spin_delta;
109         
110         Gtk::Adjustment adj_radius;
111         Gtk::SpinButton spin_radius;
112         
113         Gtk::CheckButton check_relative;
114                 
115         void AdjustWidth(handle<Duckmatic::Bezier> c, float t, Real mult, bool invert);
116         
117 public:
118
119         Real get_delta()const { return adj_delta.get_value(); }
120         void set_delta(Real f) { adj_delta.set_value(f); }
121         
122         Real get_radius()const { return adj_radius.get_value(); }
123         void set_radius(Real f) { adj_radius.set_value(f); }
124         
125         bool get_relative() const { return check_relative.get_active(); }
126         void set_relative(bool r) { check_relative.set_active(r); }
127         
128         void refresh_tool_options(); //to refresh the toolbox   
129
130         //events
131         Smach::event_result event_stop_handler(const Smach::event& x);
132         Smach::event_result event_refresh_handler(const Smach::event& x);
133         Smach::event_result event_mouse_handler(const Smach::event& x);
134         Smach::event_result event_refresh_tool_options(const Smach::event& x);
135
136         //constructor destructor
137         StateWidth_Context(CanvasView* canvas_view);
138         ~StateWidth_Context();
139
140         //Canvas interaction
141         const etl::handle<CanvasView>& get_canvas_view()const{return canvas_view_;}
142         etl::handle<sinfgapp::CanvasInterface> get_canvas_interface()const{return canvas_view_->canvas_interface();}
143         sinfg::Canvas::Handle get_canvas()const{return canvas_view_->get_canvas();}
144         WorkArea * get_work_area()const{return canvas_view_->get_work_area();}
145         
146         //Modifying settings etc.
147         void load_settings();
148         void save_settings();
149         void reset();
150         
151 };      // END of class StateGradient_Context
152
153 /* === M E T H O D S ======================================================= */
154
155 StateWidth::StateWidth():
156         Smach::state<StateWidth_Context>("width")
157 {
158         insert(event_def(EVENT_STOP,&StateWidth_Context::event_stop_handler));
159         insert(event_def(EVENT_REFRESH,&StateWidth_Context::event_refresh_handler));
160         insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_DOWN,&StateWidth_Context::event_mouse_handler));
161         insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_DRAG,&StateWidth_Context::event_mouse_handler));
162         insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_UP,&StateWidth_Context::event_mouse_handler));
163         insert(event_def(EVENT_REFRESH_TOOL_OPTIONS,&StateWidth_Context::event_refresh_tool_options));
164 }
165
166 StateWidth::~StateWidth()
167 {
168 }
169
170 void
171 StateWidth_Context::load_settings()
172 {       
173         String value;
174         
175         //parse the arguments yargh!
176         if(settings.get_value("width.delta",value))
177                 set_delta(atof(value.c_str()));
178         else
179                 set_delta(6);
180         
181         if(settings.get_value("width.radius",value))
182                 set_radius(atof(value.c_str()));
183         else
184                 set_radius(15);
185         
186         //defaults to true
187         if(settings.get_value("width.relative",value) && value == "0")
188                 set_relative(false);
189         else
190                 set_relative(true);
191 }
192
193 void
194 StateWidth_Context::save_settings()
195 {       
196         settings.set_value("width.delta",strprintf("%f",get_delta()));
197         settings.set_value("width.radius",strprintf("%f",get_radius()));
198         settings.set_value("width.relative",get_relative()?"1":"0");
199 }
200
201 void
202 StateWidth_Context::reset()
203 {
204         refresh_ducks();
205 }
206
207 StateWidth_Context::StateWidth_Context(CanvasView* canvas_view):
208         canvas_view_(canvas_view),
209         is_working(*canvas_view),
210         prev_workarea_layer_clicking(get_work_area()->allow_layer_clicks),
211         prev_workarea_duck_clicking(get_work_area()->allow_duck_clicks),
212         old_duckmask(get_work_area()->get_type_mask()),
213
214         settings(sinfgapp::Main::get_selected_input_device()->settings()),
215         
216         adj_delta(6,0,1,0.001,0.01),
217         spin_delta(adj_delta,0.01,3),
218         
219         adj_radius(0,0,1e50,1,10),
220         spin_radius(adj_radius,1,1),
221
222         check_relative(_("Relative Growth"))
223 {
224         load_settings();
225         
226         // Set up the tool options dialog
227         //options_table.attach(*manage(new Gtk::Label(_("Width Tool"))), 0, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);       
228         //options_table.attach(entry_id, 0, 2, 1, 2, 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()->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()->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         // Hide the time bar
291         //get_canvas_view()->hide_timebar();
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()->allow_duck_clicks = prev_workarea_duck_clicking;
330
331         // Restore layer clicking
332         get_work_area()->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         // Show the time bar
344         if(get_canvas_view()->get_canvas()->rend_desc().get_time_start()!=get_canvas_view()->get_canvas()->rend_desc().get_time_end())
345                 get_canvas_view()->show_timebar();
346
347         // Bring back the tables if they were out before
348         //if(prev_table_status)get_canvas_view()->show_tables();
349                         
350         // Refresh the work area
351         get_work_area()->queue_draw();
352
353         App::toolbox->refresh();
354 }
355
356 Smach::event_result
357 StateWidth_Context::event_stop_handler(const Smach::event& x)
358 {
359         throw Smach::egress_exception();
360 }
361
362 Smach::event_result
363 StateWidth_Context::event_refresh_handler(const Smach::event& x)
364 {
365         refresh_ducks();
366         return Smach::RESULT_ACCEPT;
367 }
368
369 void 
370 StateWidth_Context::AdjustWidth(handle<Duckmatic::Bezier> c, float t, Real mult, bool invert)
371 {
372         //Leave the function if there is no curve
373         if(!c)return;
374         
375         Real amount1=0,amount2=0;
376         
377         //decide how much to change each width
378         /*
379         t \in [0,1]
380         
381         both pressure and multiply amount are in mult
382                 (may want to change this to allow different types of falloff)
383         
384         rsq is the squared distance from the point on the curve (also part of the falloff)
385         
386                 
387         */
388         //may want to provide a different falloff function...
389         if(t <= 0.2)
390                 amount1 = mult;
391         else if(t >= 0.8)
392                 amount2 = mult;
393         else
394         {
395                 t = (t-0.2)/0.6;
396                 amount1 = (1-t)*mult;
397                 amount2 = t*mult;
398         }
399         
400         if(invert)
401         {
402                 amount1 *= -1;
403                 amount2 *= -1;
404         }
405         
406         handle<Duck>    p1 = c->p1;
407         handle<Duck>    p2 = c->p2;
408         
409         handle<Duck>    w1,w2;
410         
411         //find w1,w2
412         {
413                 const DuckList dl = get_work_area()->get_duck_list();
414                 
415                 DuckList::const_iterator i = dl.begin();
416                 
417                 for(;i != dl.end(); ++i)
418                 {
419                         if((*i)->get_type() == Duck::TYPE_WIDTH)
420                         {
421                                 if((*i)->get_origin_duck() == p1)
422                                 {
423                                         w1 = *i;
424                                 }
425                                 
426                                 if((*i)->get_origin_duck() == p2)
427                                 {
428                                         w2 = *i;
429                                 }
430                         }
431                 }
432         }
433         
434         if(amount1 != 0 && w1)
435         {
436                 Real width = w1->get_point().mag();
437                 
438                 width += amount1;
439                 w1->set_point(Vector(width,0));
440                 
441                 //log in the list of changes...
442                 //to truly be changed after everything is said and done
443                 changetable[w1] = width;
444         }
445         
446         if(amount2 != 0 && w2)
447         {
448                 Real width = w2->get_point().mag();
449                 
450                 width += amount2;
451                 w2->set_point(Vector(width,0));
452                 
453                 //log in the list of changes...
454                 //to truly be changed after everything is said and done
455                 changetable[w2] = width;
456         }
457 }
458
459 Smach::event_result
460 StateWidth_Context::event_mouse_handler(const Smach::event& x)
461 {
462         const EventMouse& event(*reinterpret_cast<const EventMouse*>(&x));
463         
464         //handle ze click       
465         if( (event.key == EVENT_WORKAREA_MOUSE_BUTTON_DOWN || event.key == EVENT_WORKAREA_MOUSE_BUTTON_DRAG)
466                         && event.button == BUTTON_LEFT )
467         {
468                 const Real pw = get_work_area()->get_pw();
469                 const Real ph = get_work_area()->get_ph();
470                 const Real scale = sqrt(pw*pw+ph*ph);
471                 const Real rad = get_relative() ? scale * get_radius() : get_radius();
472                 
473                 bool invert = (event.modifier&Gdk::CONTROL_MASK);
474                 
475                 const Real threshold = 0.08;
476                 
477                 float t = 0;
478                 Real rsq = 0;
479                 
480                 Real dtime = 1/60.0;
481                                 
482                 //if we're dragging get the difference in time between now and then
483                 if(event.key == EVENT_WORKAREA_MOUSE_BUTTON_DRAG)
484                 {
485                         dtime = min(1/15.0,clocktime());
486                 }
487                 clocktime.reset();
488                 
489                 //make way for new ducks
490                 //get_work_area()->clear_ducks();
491                 
492                 //update positions
493                 //mouse_pos = event.pos;
494                 
495                 center->set_point(event.pos);
496                 if(!added)get_work_area()->add_duck(center);
497
498                 radius->set_scalar(rad);
499                 if(!added)get_work_area()->add_duck(radius);
500                 
501                 //the other duck is at the current duck
502                 closestpoint->set_point(event.pos);
503                 if(!added)get_work_area()->add_duck(closestpoint);
504                         
505                 //get the closest curve...
506                 handle<Duckmatic::Bezier>       c;
507                 if(event.pressure >= threshold)
508                         c = get_work_area()->find_bezier(event.pos,scale*8,rad,&t);
509                         
510                 //run algorithm on event.pos to get 2nd placement
511                 if(!c.empty())
512                 {
513                         bezier<Point> curve;
514                         Point p;
515                         
516                         curve[0] = c->p1->get_trans_point();
517                         curve[1] = c->c1->get_trans_point();
518                         curve[2] = c->c2->get_trans_point();
519                         curve[3] = c->p2->get_trans_point();
520                         
521                         p = curve(t);
522                         rsq = (p-event.pos).mag_squared();
523                         
524                         const Real r = rad*rad;
525                         
526                         if(rsq < r)
527                         {
528                                 closestpoint->set_point(curve(t));
529                                 
530                                 //adjust the width...
531                                 //squared falloff for radius... [0,1]                           
532                                 
533                                 Real ri = (r - rsq)/r;
534                                 AdjustWidth(c,t,ri*event.pressure*get_delta()*dtime,invert);
535                         }
536                 }
537                 
538                 //the points have been added
539                 added = true;
540                                 
541                 //draw where it is yo!
542                 get_work_area()->queue_draw();
543                                 
544                 return Smach::RESULT_ACCEPT;
545         }
546         
547         if(event.key == EVENT_WORKAREA_MOUSE_BUTTON_UP && event.button == BUTTON_LEFT)
548         {
549                 if(added)
550                 {
551                         get_work_area()->erase_duck(center);
552                         get_work_area()->erase_duck(radius);
553                         get_work_area()->erase_duck(closestpoint);
554                         added = false;
555                 }
556                 
557                 //Affect the width changes here...
558                 map<handle<Duck>,Real>::iterator i = changetable.begin();
559
560                 sinfgapp::Action::PassiveGrouper group(get_canvas_interface()->get_instance().get(),_("Sketch Width")); 
561                 for(; i != changetable.end(); ++i)
562                 {
563                         //for each duck modify IT!!!
564                         ValueDesc desc = i->first->get_value_desc();
565
566                         if(     desc.get_value_type() == ValueBase::TYPE_REAL )
567                         {
568                                 Action::Handle action(Action::create("value_desc_set"));
569                                 assert(action);
570                                 
571                                 action->set_param("canvas",get_canvas());                                       
572                                 action->set_param("canvas_interface",get_canvas_interface());                   
573                                 
574                                 action->set_param("value_desc",desc);                                   
575                                 action->set_param("new_value",ValueBase(i->second));
576                                 action->set_param("time",get_canvas_view()->get_time());
577                                 
578                                 if(!action->is_ready() || !get_canvas_view()->get_instance()->perform_action(action))
579                                 {
580                                         group.cancel();
581                                         sinfg::warning("Changing the width action has failed");
582                                         return Smach::RESULT_ERROR;
583                                 }
584                         }
585                 }                       
586                 
587                 changetable.clear();
588                 
589                 get_work_area()->queue_draw();
590                 
591                 return Smach::RESULT_ACCEPT;
592         }
593
594         return Smach::RESULT_OK;
595 }
596
597
598 void
599 StateWidth_Context::refresh_ducks()
600 {
601         get_work_area()->clear_ducks();
602         get_work_area()->queue_draw();
603 }