/* === 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 "state_normal.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();
throw &state_normal;
return Smach::RESULT_OK;
}
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();
}