Default the width tool's "relative growth" to OFF.
[synfig.git] / synfig-studio / trunk / src / gtkmm / state_width.cpp
blob7c7b1bc58cce1a7d23473ccb4d93b2760cb38df4
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.
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
21 /* ========================================================================= */
23 /* === H E A D E R S ======================================================= */
25 #ifdef USING_PCH
26 # include "pch.h"
27 #else
28 #ifdef HAVE_CONFIG_H
29 # include <config.h>
30 #endif
32 #include <gtkmm/dialog.h>
33 #include <gtkmm/entry.h>
35 #include <ETL/bezier>
37 #include <synfig/valuenode_dynamiclist.h>
38 #include <synfigapp/action_system.h>
40 #include "state_width.h"
41 #include "canvasview.h"
42 #include "workarea.h"
43 #include "app.h"
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"
53 //#include <synfigapp/value_desc.h>
54 #include <synfigapp/main.h>
56 #include <ETL/clock>
58 #include "general.h"
60 #endif
62 /* === U S I N G =========================================================== */
64 using namespace std;
65 using namespace etl;
66 using namespace synfig;
67 using namespace synfigapp;
68 using namespace studio;
70 /* === M A C R O S ========================================================= */
72 /* === G L O B A L S ======================================================= */
74 StateWidth studio::state_width;
76 /* === C L A S S E S & S T R U C T S ======================================= */
78 class studio::StateWidth_Context : public sigc::trackable
80 etl::handle<CanvasView> canvas_view_;
81 CanvasView::IsWorking is_working;
83 //Point mouse_pos;
85 handle<Duck> center;
86 handle<Duck> radius;
87 handle<Duck> closestpoint;
89 map<handle<Duck>,Real> changetable;
91 etl::clock clocktime;
92 Real lastt;
94 bool added;
96 void refresh_ducks();
98 bool prev_workarea_layer_clicking;
99 bool prev_workarea_duck_clicking;
100 Duckmatic::Type old_duckmask;
102 //Toolbox settings
103 synfigapp::Settings& settings;
105 //Toolbox display
106 Gtk::Table options_table;
108 Gtk::Adjustment adj_delta;
109 Gtk::SpinButton spin_delta;
111 Gtk::Adjustment adj_radius;
112 Gtk::SpinButton spin_radius;
114 Gtk::CheckButton check_relative;
116 void AdjustWidth(handle<Duckmatic::Bezier> c, float t, Real mult, bool invert);
118 public:
120 Real get_delta()const { return adj_delta.get_value(); }
121 void set_delta(Real f) { adj_delta.set_value(f); }
123 Real get_radius()const { return adj_radius.get_value(); }
124 void set_radius(Real f) { adj_radius.set_value(f); }
126 bool get_relative() const { return check_relative.get_active(); }
127 void set_relative(bool r) { check_relative.set_active(r); }
129 void refresh_tool_options(); //to refresh the toolbox
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);
137 //constructor destructor
138 StateWidth_Context(CanvasView* canvas_view);
139 ~StateWidth_Context();
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();}
147 //Modifying settings etc.
148 void load_settings();
149 void save_settings();
150 void reset();
152 }; // END of class StateWidth_Context
154 /* === M E T H O D S ======================================================= */
156 StateWidth::StateWidth():
157 Smach::state<StateWidth_Context>("width")
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));
167 StateWidth::~StateWidth()
171 void
172 StateWidth_Context::load_settings()
174 String value;
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);
182 if(settings.get_value("width.radius",value))
183 set_radius(atof(value.c_str()));
184 else
185 set_radius(15);
187 //defaults to false
188 if(settings.get_value("width.relative",value) && value == "1")
189 set_relative(true);
190 else
191 set_relative(false);
194 void
195 StateWidth_Context::save_settings()
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");
202 void
203 StateWidth_Context::reset()
205 refresh_ducks();
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()),
215 settings(synfigapp::Main::get_selected_input_device()->settings()),
217 adj_delta(6,0,1,0.001,0.01),
218 spin_delta(adj_delta,0.01,3),
220 adj_radius(0,0,1e50,1,10),
221 spin_radius(adj_radius,1,1),
223 check_relative(_("Relative Growth"))
225 load_settings();
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);
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);
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);
237 options_table.attach(check_relative, 0, 2, 3, 4, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
239 options_table.show_all();
241 refresh_tool_options();
242 App::dialog_tool_options->present();
244 // Turn off layer clicking
245 get_work_area()->set_allow_layer_clicks(false);
247 // clear out the ducks
248 //get_work_area()->clear_ducks();
250 // Refresh the work area
251 get_work_area()->queue_draw();
253 //Create the new ducks
254 added = false;
256 if(!center)
258 center = new Duck();
259 center->set_name("p1");
260 center->set_type(Duck::TYPE_POSITION);
263 if(!radius)
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");
272 if(!closestpoint)
274 closestpoint = new Duck();
275 closestpoint->set_name("closest");
276 closestpoint->set_type(Duck::TYPE_POSITION);
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));
283 // Turn the mouse pointer to crosshairs
284 get_work_area()->set_cursor(Gdk::CROSSHAIR);
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();
290 // Disable the time bar
291 //get_canvas_view()->set_sensitive_timebar(false);
293 // Connect a signal
294 //get_work_area()->signal_user_click().connect(sigc::mem_fun(*this,&studio::StateWidth_Context::on_user_click));
296 App::toolbox->refresh();
299 void
300 StateWidth_Context::refresh_tool_options()
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");
308 Smach::event_result
309 StateWidth_Context::event_refresh_tool_options(const Smach::event& /*x*/)
311 refresh_tool_options();
312 return Smach::RESULT_ACCEPT;
315 StateWidth_Context::~StateWidth_Context()
317 save_settings();
319 //remove ducks if need be
320 if(added)
322 get_work_area()->erase_duck(center);
323 get_work_area()->erase_duck(radius);
324 get_work_area()->erase_duck(closestpoint);
325 added = false;
328 // Restore Duck clicking
329 get_work_area()->set_allow_duck_clicks(prev_workarea_duck_clicking);
331 // Restore layer clicking
332 get_work_area()->set_allow_layer_clicks(prev_workarea_layer_clicking);
334 // Restore the mouse pointer
335 get_work_area()->reset_cursor();
337 // Restore duck masking
338 get_work_area()->set_type_mask(old_duckmask);
340 // Tool options be rid of ye!!
341 App::dialog_tool_options->clear();
343 // Enable the time bar
344 //get_canvas_view()->set_sensitive_timebar(true);
346 // Bring back the tables if they were out before
347 //if(prev_table_status)get_canvas_view()->show_tables();
349 // Refresh the work area
350 get_work_area()->queue_draw();
352 App::toolbox->refresh();
355 Smach::event_result
356 StateWidth_Context::event_stop_handler(const Smach::event& /*x*/)
358 throw Smach::egress_exception();
361 Smach::event_result
362 StateWidth_Context::event_refresh_handler(const Smach::event& /*x*/)
364 refresh_ducks();
365 return Smach::RESULT_ACCEPT;
368 void
369 StateWidth_Context::AdjustWidth(handle<Duckmatic::Bezier> c, float t, Real mult, bool invert)
371 //Leave the function if there is no curve
372 if(!c)return;
374 Real amount1=0,amount2=0;
376 //decide how much to change each width
378 t \in [0,1]
380 both pressure and multiply amount are in mult
381 (may want to change this to allow different types of falloff)
383 rsq is the squared distance from the point on the curve (also part of the falloff)
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
394 t = (t-0.2)/0.6;
395 amount1 = (1-t)*mult;
396 amount2 = t*mult;
399 if(invert)
401 amount1 *= -1;
402 amount2 *= -1;
405 handle<Duck> p1 = c->p1;
406 handle<Duck> p2 = c->p2;
408 handle<Duck> w1,w2;
410 //find w1,w2
412 const DuckList dl = get_work_area()->get_duck_list();
414 DuckList::const_iterator i = dl.begin();
416 for(;i != dl.end(); ++i)
418 if((*i)->get_type() == Duck::TYPE_WIDTH)
420 if((*i)->get_origin_duck() == p1)
422 w1 = *i;
425 if((*i)->get_origin_duck() == p2)
427 w2 = *i;
433 if(amount1 != 0 && w1)
435 Real width = w1->get_point().mag();
437 width += amount1;
438 w1->set_point(Vector(width,0));
440 //log in the list of changes...
441 //to truly be changed after everything is said and done
442 changetable[w1] = width;
445 if(amount2 != 0 && w2)
447 Real width = w2->get_point().mag();
449 width += amount2;
450 w2->set_point(Vector(width,0));
452 //log in the list of changes...
453 //to truly be changed after everything is said and done
454 changetable[w2] = width;
458 Smach::event_result
459 StateWidth_Context::event_mouse_handler(const Smach::event& x)
461 const EventMouse& event(*reinterpret_cast<const EventMouse*>(&x));
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 )
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();
472 bool invert = (event.modifier&Gdk::CONTROL_MASK);
474 const Real threshold = 0.08;
476 float t = 0;
477 Real rsq = 0;
479 Real dtime = 1/60.0;
481 //if we're dragging get the difference in time between now and then
482 if(event.key == EVENT_WORKAREA_MOUSE_BUTTON_DRAG)
484 dtime = min(1/15.0,clocktime());
486 clocktime.reset();
488 //make way for new ducks
489 //get_work_area()->clear_ducks();
491 //update positions
492 //mouse_pos = event.pos;
494 center->set_point(event.pos);
495 if(!added)get_work_area()->add_duck(center);
497 radius->set_scalar(rad);
498 if(!added)get_work_area()->add_duck(radius);
500 //the other duck is at the current duck
501 closestpoint->set_point(event.pos);
502 if(!added)get_work_area()->add_duck(closestpoint);
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);
509 //run algorithm on event.pos to get 2nd placement
510 if(!c.empty())
512 bezier<Point> curve;
513 Point p;
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();
520 p = curve(t);
521 rsq = (p-event.pos).mag_squared();
523 const Real r = rad*rad;
525 if(rsq < r)
527 closestpoint->set_point(curve(t));
529 //adjust the width...
530 //squared falloff for radius... [0,1]
532 Real ri = (r - rsq)/r;
533 AdjustWidth(c,t,ri*event.pressure*get_delta()*dtime,invert);
537 //the points have been added
538 added = true;
540 //draw where it is yo!
541 get_work_area()->queue_draw();
543 return Smach::RESULT_ACCEPT;
546 if(event.key == EVENT_WORKAREA_MOUSE_BUTTON_UP && event.button == BUTTON_LEFT)
548 if(added)
550 get_work_area()->erase_duck(center);
551 get_work_area()->erase_duck(radius);
552 get_work_area()->erase_duck(closestpoint);
553 added = false;
556 //Affect the width changes here...
557 map<handle<Duck>,Real>::iterator i = changetable.begin();
559 synfigapp::Action::PassiveGrouper group(get_canvas_interface()->get_instance().get(),_("Sketch Width"));
560 for(; i != changetable.end(); ++i)
562 //for each duck modify IT!!!
563 ValueDesc desc = i->first->get_value_desc();
565 if( desc.get_value_type() == ValueBase::TYPE_REAL )
567 Action::Handle action(Action::create("value_desc_set"));
568 assert(action);
570 action->set_param("canvas",get_canvas());
571 action->set_param("canvas_interface",get_canvas_interface());
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());
577 if(!action->is_ready() || !get_canvas_view()->get_instance()->perform_action(action))
579 group.cancel();
580 synfig::warning("Changing the width action has failed");
581 return Smach::RESULT_ERROR;
586 changetable.clear();
588 get_work_area()->queue_draw();
590 return Smach::RESULT_ACCEPT;
593 return Smach::RESULT_OK;
597 void
598 StateWidth_Context::refresh_ducks()
600 get_work_area()->clear_ducks();
601 get_work_area()->queue_draw();