Remove ancient trunk folder from svn repository
[synfig.git] / synfig-studio / src / gtkmm / state_width.cpp
diff --git a/synfig-studio/src/gtkmm/state_width.cpp b/synfig-studio/src/gtkmm/state_width.cpp
new file mode 100644 (file)
index 0000000..1c7ef4f
--- /dev/null
@@ -0,0 +1,603 @@
+/* === S Y N F I G ========================================================= */
+/*!    \file state_width.cpp
+**     \brief Template File
+**
+**     $Id$
+**
+**     \legal
+**     Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
+**  Copyright (c) 2008 Chris Moore
+**
+**     This package is free software; you can redistribute it and/or
+**     modify it under the terms of the GNU General Public License as
+**     published by the Free Software Foundation; either version 2 of
+**     the License, or (at your option) any later version.
+**
+**     This package is distributed in the hope that it will be useful,
+**     but WITHOUT ANY WARRANTY; without even the implied warranty of
+**     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+**     General Public License for more details.
+**     \endlegal
+*/
+/* ========================================================================= */
+
+/* === H E A D E R S ======================================================= */
+
+#ifdef USING_PCH
+#      include "pch.h"
+#else
+#ifdef HAVE_CONFIG_H
+#      include <config.h>
+#endif
+
+#include <gtkmm/dialog.h>
+#include <gtkmm/entry.h>
+
+#include <ETL/bezier>
+
+#include <synfig/valuenode_dynamiclist.h>
+#include <synfigapp/action_system.h>
+
+#include "state_width.h"
+#include "canvasview.h"
+#include "workarea.h"
+#include "app.h"
+
+#include <synfigapp/action.h>
+#include "event_mouse.h"
+#include "event_layerclick.h"
+#include "toolbox.h"
+#include "dialog_tooloptions.h"
+#include <gtkmm/optionmenu.h>
+#include "duck.h"
+
+//#include <synfigapp/value_desc.h>
+#include <synfigapp/main.h>
+
+#include <ETL/clock>
+
+#include "general.h"
+
+#endif
+
+/* === U S I N G =========================================================== */
+
+using namespace std;
+using namespace etl;
+using namespace synfig;
+using namespace synfigapp;
+using namespace studio;
+
+/* === M A C R O S ========================================================= */
+
+/* === G L O B A L S ======================================================= */
+
+StateWidth studio::state_width;
+
+/* === C L A S S E S & S T R U C T S ======================================= */
+
+class studio::StateWidth_Context : public sigc::trackable
+{
+       etl::handle<CanvasView> canvas_view_;
+       CanvasView::IsWorking is_working;
+
+       //Point mouse_pos;
+
+       handle<Duck> center;
+       handle<Duck> radius;
+       handle<Duck> closestpoint;
+
+       map<handle<Duck>,Real>  changetable;
+
+       etl::clock      clocktime;
+       Real            lastt;
+
+       bool added;
+
+       void refresh_ducks();
+
+       bool prev_workarea_layer_clicking;
+       bool prev_workarea_duck_clicking;
+       Duckmatic::Type old_duckmask;
+
+       //Toolbox settings
+       synfigapp::Settings& settings;
+
+       //Toolbox display
+       Gtk::Table options_table;
+
+       Gtk::Adjustment adj_delta;
+       Gtk::SpinButton spin_delta;
+
+       Gtk::Adjustment adj_radius;
+       Gtk::SpinButton spin_radius;
+
+       Gtk::CheckButton check_relative;
+
+       void AdjustWidth(handle<Duckmatic::Bezier> c, float t, Real mult, bool invert);
+
+public:
+
+       Real get_delta()const { return adj_delta.get_value(); }
+       void set_delta(Real f) { adj_delta.set_value(f); }
+
+       Real get_radius()const { return adj_radius.get_value(); }
+       void set_radius(Real f) { adj_radius.set_value(f); }
+
+       bool get_relative() const { return check_relative.get_active(); }
+       void set_relative(bool r) { check_relative.set_active(r); }
+
+       void refresh_tool_options(); //to refresh the toolbox
+
+       //events
+       Smach::event_result event_stop_handler(const Smach::event& x);
+       Smach::event_result event_refresh_handler(const Smach::event& x);
+       Smach::event_result event_mouse_handler(const Smach::event& x);
+       Smach::event_result event_refresh_tool_options(const Smach::event& x);
+
+       //constructor destructor
+       StateWidth_Context(CanvasView* canvas_view);
+       ~StateWidth_Context();
+
+       //Canvas interaction
+       const etl::handle<CanvasView>& get_canvas_view()const{return canvas_view_;}
+       etl::handle<synfigapp::CanvasInterface> get_canvas_interface()const{return canvas_view_->canvas_interface();}
+       synfig::Canvas::Handle get_canvas()const{return canvas_view_->get_canvas();}
+       WorkArea * get_work_area()const{return canvas_view_->get_work_area();}
+
+       //Modifying settings etc.
+       void load_settings();
+       void save_settings();
+       void reset();
+
+};     // END of class StateWidth_Context
+
+/* === M E T H O D S ======================================================= */
+
+StateWidth::StateWidth():
+       Smach::state<StateWidth_Context>("width")
+{
+       insert(event_def(EVENT_STOP,&StateWidth_Context::event_stop_handler));
+       insert(event_def(EVENT_REFRESH,&StateWidth_Context::event_refresh_handler));
+       insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_DOWN,&StateWidth_Context::event_mouse_handler));
+       insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_DRAG,&StateWidth_Context::event_mouse_handler));
+       insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_UP,&StateWidth_Context::event_mouse_handler));
+       insert(event_def(EVENT_REFRESH_TOOL_OPTIONS,&StateWidth_Context::event_refresh_tool_options));
+}
+
+StateWidth::~StateWidth()
+{
+}
+
+void
+StateWidth_Context::load_settings()
+{
+       String value;
+
+       //parse the arguments yargh!
+       if(settings.get_value("width.delta",value))
+               set_delta(atof(value.c_str()));
+       else
+               set_delta(6);
+
+       if(settings.get_value("width.radius",value))
+               set_radius(atof(value.c_str()));
+       else
+               set_radius(15);
+
+       //defaults to false
+       if(settings.get_value("width.relative",value) && value == "1")
+               set_relative(true);
+       else
+               set_relative(false);
+}
+
+void
+StateWidth_Context::save_settings()
+{
+       settings.set_value("width.delta",strprintf("%f",get_delta()));
+       settings.set_value("width.radius",strprintf("%f",get_radius()));
+       settings.set_value("width.relative",get_relative()?"1":"0");
+}
+
+void
+StateWidth_Context::reset()
+{
+       refresh_ducks();
+}
+
+StateWidth_Context::StateWidth_Context(CanvasView* canvas_view):
+       canvas_view_(canvas_view),
+       is_working(*canvas_view),
+       prev_workarea_layer_clicking(get_work_area()->get_allow_layer_clicks()),
+       prev_workarea_duck_clicking(get_work_area()->get_allow_duck_clicks()),
+       old_duckmask(get_work_area()->get_type_mask()),
+
+       settings(synfigapp::Main::get_selected_input_device()->settings()),
+
+       adj_delta(6,0,20,0.01,0.1),
+       spin_delta(adj_delta,0.01,3),
+
+       adj_radius(200,0,1e50,1,10),
+       spin_radius(adj_radius,1,1),
+
+       check_relative(_("Relative Growth"))
+{
+       load_settings();
+
+       // Set up the tool options dialog
+       options_table.attach(*manage(new Gtk::Label(_("Width Tool"))),  0, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
+
+       //expand stuff
+       options_table.attach(*manage(new Gtk::Label(_("Growth:"))),             0, 1, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
+       options_table.attach(spin_delta,                                                                1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
+
+       options_table.attach(*manage(new Gtk::Label(_("Radius:"))),             0, 1, 2, 3, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
+       options_table.attach(spin_radius,                                                               1, 2, 2, 3, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
+
+       options_table.attach(check_relative,                                                    0, 2, 3, 4, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
+
+       options_table.show_all();
+
+       refresh_tool_options();
+       App::dialog_tool_options->present();
+
+       // Turn off layer clicking
+       get_work_area()->set_allow_layer_clicks(false);
+
+       // clear out the ducks
+       //get_work_area()->clear_ducks();
+
+       // Refresh the work area
+       get_work_area()->queue_draw();
+
+       //Create the new ducks
+       added = false;
+
+       if(!center)
+       {
+               center = new Duck();
+               center->set_name("p1");
+               center->set_type(Duck::TYPE_POSITION);
+       }
+
+       if(!radius)
+       {
+               radius = new Duck();
+               radius->set_origin(center);
+               radius->set_radius(true);
+               radius->set_type(Duck::TYPE_RADIUS);
+               radius->set_name("radius");
+       }
+
+       if(!closestpoint)
+       {
+               closestpoint = new Duck();
+               closestpoint->set_name("closest");
+               closestpoint->set_type(Duck::TYPE_POSITION);
+       }
+
+       //Disable duck clicking for the maximum coolness :)
+       get_work_area()->set_allow_duck_clicks(false);
+       get_work_area()->set_type_mask((Duck::Type)((int)Duck::TYPE_WIDTH + (int)Duck::TYPE_RADIUS));
+
+       // Turn the mouse pointer to crosshairs
+       get_work_area()->set_cursor(Gdk::CROSSHAIR);
+
+       // Hide the tables if they are showing
+       //prev_table_status=get_canvas_view()->tables_are_visible();
+       //if(prev_table_status)get_canvas_view()->hide_tables();
+
+       // Disable the time bar
+       //get_canvas_view()->set_sensitive_timebar(false);
+
+       // Connect a signal
+       //get_work_area()->signal_user_click().connect(sigc::mem_fun(*this,&studio::StateWidth_Context::on_user_click));
+
+       App::toolbox->refresh();
+}
+
+void
+StateWidth_Context::refresh_tool_options()
+{
+       App::dialog_tool_options->clear();
+       App::dialog_tool_options->set_widget(options_table);
+       App::dialog_tool_options->set_local_name(_("Width Tool"));
+       App::dialog_tool_options->set_name("width");
+}
+
+Smach::event_result
+StateWidth_Context::event_refresh_tool_options(const Smach::event& /*x*/)
+{
+       refresh_tool_options();
+       return Smach::RESULT_ACCEPT;
+}
+
+StateWidth_Context::~StateWidth_Context()
+{
+       save_settings();
+
+       //remove ducks if need be
+       if(added)
+       {
+               get_work_area()->erase_duck(center);
+               get_work_area()->erase_duck(radius);
+               get_work_area()->erase_duck(closestpoint);
+               added = false;
+       }
+
+       // Restore Duck clicking
+       get_work_area()->set_allow_duck_clicks(prev_workarea_duck_clicking);
+
+       // Restore layer clicking
+       get_work_area()->set_allow_layer_clicks(prev_workarea_layer_clicking);
+
+       // Restore the mouse pointer
+       get_work_area()->reset_cursor();
+
+       // Restore duck masking
+       get_work_area()->set_type_mask(old_duckmask);
+
+       // Tool options be rid of ye!!
+       App::dialog_tool_options->clear();
+
+       // Enable the time bar
+       //get_canvas_view()->set_sensitive_timebar(true);
+
+       // Bring back the tables if they were out before
+       //if(prev_table_status)get_canvas_view()->show_tables();
+
+       // Refresh the work area
+       get_work_area()->queue_draw();
+
+       App::toolbox->refresh();
+}
+
+Smach::event_result
+StateWidth_Context::event_stop_handler(const Smach::event& /*x*/)
+{
+       throw Smach::egress_exception();
+}
+
+Smach::event_result
+StateWidth_Context::event_refresh_handler(const Smach::event& /*x*/)
+{
+       refresh_ducks();
+       return Smach::RESULT_ACCEPT;
+}
+
+void
+StateWidth_Context::AdjustWidth(handle<Duckmatic::Bezier> c, float t, Real mult, bool invert)
+{
+       //Leave the function if there is no curve
+       if(!c)return;
+
+       Real amount1=0,amount2=0;
+
+       //decide how much to change each width
+       /*
+       t \in [0,1]
+
+       both pressure and multiply amount are in mult
+               (may want to change this to allow different types of falloff)
+
+       rsq is the squared distance from the point on the curve (also part of the falloff)
+
+
+       */
+       //may want to provide a different falloff function...
+       if(t <= 0.2)
+               amount1 = mult;
+       else if(t >= 0.8)
+               amount2 = mult;
+       else
+       {
+               t = (t-0.2)/0.6;
+               amount1 = (1-t)*mult;
+               amount2 = t*mult;
+       }
+
+       if(invert)
+       {
+               amount1 *= -1;
+               amount2 *= -1;
+       }
+
+       handle<Duck>    p1 = c->p1;
+       handle<Duck>    p2 = c->p2;
+
+       handle<Duck>    w1,w2;
+
+       //find w1,w2
+       {
+               const DuckList dl = get_work_area()->get_duck_list();
+
+               DuckList::const_iterator i = dl.begin();
+
+               for(;i != dl.end(); ++i)
+               {
+                       if((*i)->get_type() == Duck::TYPE_WIDTH)
+                       {
+                               if((*i)->get_origin_duck() == p1)
+                               {
+                                       w1 = *i;
+                               }
+
+                               if((*i)->get_origin_duck() == p2)
+                               {
+                                       w2 = *i;
+                               }
+                       }
+               }
+       }
+
+       if(amount1 != 0 && w1)
+       {
+               Real width = w1->get_point().mag();
+
+               width += amount1;
+               w1->set_point(Vector(width,0));
+
+               //log in the list of changes...
+               //to truly be changed after everything is said and done
+               changetable[w1] = width;
+       }
+
+       if(amount2 != 0 && w2)
+       {
+               Real width = w2->get_point().mag();
+
+               width += amount2;
+               w2->set_point(Vector(width,0));
+
+               //log in the list of changes...
+               //to truly be changed after everything is said and done
+               changetable[w2] = width;
+       }
+}
+
+Smach::event_result
+StateWidth_Context::event_mouse_handler(const Smach::event& x)
+{
+       const EventMouse& event(*reinterpret_cast<const EventMouse*>(&x));
+
+       //handle the click
+       if( (event.key == EVENT_WORKAREA_MOUSE_BUTTON_DOWN || event.key == EVENT_WORKAREA_MOUSE_BUTTON_DRAG)
+                       && event.button == BUTTON_LEFT )
+       {
+               const Real pw = get_work_area()->get_pw();
+               const Real ph = get_work_area()->get_ph();
+               const Real scale = sqrt(pw*pw+ph*ph);
+               const Real rad = get_relative() ? scale * get_radius() : get_radius();
+
+               bool invert = (event.modifier&Gdk::CONTROL_MASK);
+
+               const Real threshold = 0.08;
+
+               float t = 0;
+               Real rsq = 0;
+
+               Real dtime = 1/60.0;
+
+               //if we're dragging get the difference in time between now and then
+               if(event.key == EVENT_WORKAREA_MOUSE_BUTTON_DRAG)
+               {
+                       dtime = min(1/15.0,clocktime());
+               }
+               clocktime.reset();
+
+               //make way for new ducks
+               //get_work_area()->clear_ducks();
+
+               //update positions
+               //mouse_pos = event.pos;
+
+               center->set_point(event.pos);
+               if(!added)get_work_area()->add_duck(center);
+
+               radius->set_scalar(rad);
+               if(!added)get_work_area()->add_duck(radius);
+
+               //the other duck is at the current duck
+               closestpoint->set_point(event.pos);
+               if(!added)get_work_area()->add_duck(closestpoint);
+
+               //get the closest curve...
+               handle<Duckmatic::Bezier>       c;
+               if(event.pressure >= threshold)
+                       c = get_work_area()->find_bezier(event.pos,scale*8,rad,&t);
+
+               //run algorithm on event.pos to get 2nd placement
+               if(!c.empty())
+               {
+                       bezier<Point> curve;
+                       Point p;
+
+                       curve[0] = c->p1->get_trans_point();
+                       curve[1] = c->c1->get_trans_point();
+                       curve[2] = c->c2->get_trans_point();
+                       curve[3] = c->p2->get_trans_point();
+
+                       p = curve(t);
+                       rsq = (p-event.pos).mag_squared();
+
+                       const Real r = rad*rad;
+
+                       if(rsq < r)
+                       {
+                               closestpoint->set_point(curve(t));
+
+                               //adjust the width...
+                               //squared falloff for radius... [0,1]
+
+                               Real ri = (r - rsq)/r;
+                               AdjustWidth(c,t,ri*event.pressure*get_delta()*dtime,invert);
+                       }
+               }
+
+               //the points have been added
+               added = true;
+
+               //draw where it is yo!
+               get_work_area()->queue_draw();
+
+               return Smach::RESULT_ACCEPT;
+       }
+
+       if(event.key == EVENT_WORKAREA_MOUSE_BUTTON_UP && event.button == BUTTON_LEFT)
+       {
+               if(added)
+               {
+                       get_work_area()->erase_duck(center);
+                       get_work_area()->erase_duck(radius);
+                       get_work_area()->erase_duck(closestpoint);
+                       added = false;
+               }
+
+               //Affect the width changes here...
+               map<handle<Duck>,Real>::iterator i = changetable.begin();
+
+               synfigapp::Action::PassiveGrouper group(get_canvas_interface()->get_instance().get(),_("Sketch Width"));
+               for(; i != changetable.end(); ++i)
+               {
+                       //for each duck modify IT!!!
+                       ValueDesc desc = i->first->get_value_desc();
+
+                       if(     desc.get_value_type() == ValueBase::TYPE_REAL )
+                       {
+                               Action::Handle action(Action::create("ValueDescSet"));
+                               assert(action);
+
+                               action->set_param("canvas",get_canvas());
+                               action->set_param("canvas_interface",get_canvas_interface());
+
+                               action->set_param("value_desc",desc);
+                               action->set_param("new_value",ValueBase(i->second));
+                               action->set_param("time",get_canvas_view()->get_time());
+
+                               if(!action->is_ready() || !get_canvas_view()->get_instance()->perform_action(action))
+                               {
+                                       group.cancel();
+                                       synfig::warning("Changing the width action has failed");
+                                       return Smach::RESULT_ERROR;
+                               }
+                       }
+               }
+
+               changetable.clear();
+
+               get_work_area()->queue_draw();
+
+               return Smach::RESULT_ACCEPT;
+       }
+
+       return Smach::RESULT_OK;
+}
+
+
+void
+StateWidth_Context::refresh_ducks()
+{
+       get_work_area()->clear_ducks();
+       get_work_area()->queue_draw();
+}