Allow PasteCanvas layers with unset canvas parameters to be exported.
[synfig.git] / synfig-studio / trunk / 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 **  Copyright (c) 2008 Chris Moore
10 **
11 **      This package is free software; you can redistribute it and/or
12 **      modify it under the terms of the GNU General Public License as
13 **      published by the Free Software Foundation; either version 2 of
14 **      the License, or (at your option) any later version.
15 **
16 **      This package is distributed in the hope that it will be useful,
17 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
18 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 **      General Public License for more details.
20 **      \endlegal
21 */
22 /* ========================================================================= */
23
24 /* === H E A D E R S ======================================================= */
25
26 #ifdef USING_PCH
27 #       include "pch.h"
28 #else
29 #ifdef HAVE_CONFIG_H
30 #       include <config.h>
31 #endif
32
33 #include <gtkmm/dialog.h>
34 #include <gtkmm/entry.h>
35
36 #include <ETL/bezier>
37
38 #include <synfig/valuenode_dynamiclist.h>
39 #include <synfigapp/action_system.h>
40
41 #include "state_width.h"
42 #include "canvasview.h"
43 #include "workarea.h"
44 #include "app.h"
45
46 #include <synfigapp/action.h>
47 #include "event_mouse.h"
48 #include "event_layerclick.h"
49 #include "toolbox.h"
50 #include "dialog_tooloptions.h"
51 #include <gtkmm/optionmenu.h>
52 #include "duck.h"
53
54 //#include <synfigapp/value_desc.h>
55 #include <synfigapp/main.h>
56
57 #include <ETL/clock>
58
59 #include "general.h"
60
61 #endif
62
63 /* === U S I N G =========================================================== */
64
65 using namespace std;
66 using namespace etl;
67 using namespace synfig;
68 using namespace synfigapp;
69 using namespace studio;
70
71 /* === M A C R O S ========================================================= */
72
73 /* === G L O B A L S ======================================================= */
74
75 StateWidth studio::state_width;
76
77 /* === C L A S S E S & S T R U C T S ======================================= */
78
79 class studio::StateWidth_Context : public sigc::trackable
80 {
81         etl::handle<CanvasView> canvas_view_;
82         CanvasView::IsWorking is_working;
83
84         //Point mouse_pos;
85
86         handle<Duck> center;
87         handle<Duck> radius;
88         handle<Duck> closestpoint;
89
90         map<handle<Duck>,Real>  changetable;
91
92         etl::clock      clocktime;
93         Real            lastt;
94
95         bool added;
96
97         void refresh_ducks();
98
99         bool prev_workarea_layer_clicking;
100         bool prev_workarea_duck_clicking;
101         Duckmatic::Type old_duckmask;
102
103         //Toolbox settings
104         synfigapp::Settings& settings;
105
106         //Toolbox display
107         Gtk::Table options_table;
108
109         Gtk::Adjustment adj_delta;
110         Gtk::SpinButton spin_delta;
111
112         Gtk::Adjustment adj_radius;
113         Gtk::SpinButton spin_radius;
114
115         Gtk::CheckButton check_relative;
116
117         void AdjustWidth(handle<Duckmatic::Bezier> c, float t, Real mult, bool invert);
118
119 public:
120
121         Real get_delta()const { return adj_delta.get_value(); }
122         void set_delta(Real f) { adj_delta.set_value(f); }
123
124         Real get_radius()const { return adj_radius.get_value(); }
125         void set_radius(Real f) { adj_radius.set_value(f); }
126
127         bool get_relative() const { return check_relative.get_active(); }
128         void set_relative(bool r) { check_relative.set_active(r); }
129
130         void refresh_tool_options(); //to refresh the toolbox
131
132         //events
133         Smach::event_result event_stop_handler(const Smach::event& x);
134         Smach::event_result event_refresh_handler(const Smach::event& x);
135         Smach::event_result event_mouse_handler(const Smach::event& x);
136         Smach::event_result event_refresh_tool_options(const Smach::event& x);
137
138         //constructor destructor
139         StateWidth_Context(CanvasView* canvas_view);
140         ~StateWidth_Context();
141
142         //Canvas interaction
143         const etl::handle<CanvasView>& get_canvas_view()const{return canvas_view_;}
144         etl::handle<synfigapp::CanvasInterface> get_canvas_interface()const{return canvas_view_->canvas_interface();}
145         synfig::Canvas::Handle get_canvas()const{return canvas_view_->get_canvas();}
146         WorkArea * get_work_area()const{return canvas_view_->get_work_area();}
147
148         //Modifying settings etc.
149         void load_settings();
150         void save_settings();
151         void reset();
152
153 };      // END of class StateWidth_Context
154
155 /* === M E T H O D S ======================================================= */
156
157 StateWidth::StateWidth():
158         Smach::state<StateWidth_Context>("width")
159 {
160         insert(event_def(EVENT_STOP,&StateWidth_Context::event_stop_handler));
161         insert(event_def(EVENT_REFRESH,&StateWidth_Context::event_refresh_handler));
162         insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_DOWN,&StateWidth_Context::event_mouse_handler));
163         insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_DRAG,&StateWidth_Context::event_mouse_handler));
164         insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_UP,&StateWidth_Context::event_mouse_handler));
165         insert(event_def(EVENT_REFRESH_TOOL_OPTIONS,&StateWidth_Context::event_refresh_tool_options));
166 }
167
168 StateWidth::~StateWidth()
169 {
170 }
171
172 void
173 StateWidth_Context::load_settings()
174 {
175         String value;
176
177         //parse the arguments yargh!
178         if(settings.get_value("width.delta",value))
179                 set_delta(atof(value.c_str()));
180         else
181                 set_delta(6);
182
183         if(settings.get_value("width.radius",value))
184                 set_radius(atof(value.c_str()));
185         else
186                 set_radius(15);
187
188         //defaults to false
189         if(settings.get_value("width.relative",value) && value == "1")
190                 set_relative(true);
191         else
192                 set_relative(false);
193 }
194
195 void
196 StateWidth_Context::save_settings()
197 {
198         settings.set_value("width.delta",strprintf("%f",get_delta()));
199         settings.set_value("width.radius",strprintf("%f",get_radius()));
200         settings.set_value("width.relative",get_relative()?"1":"0");
201 }
202
203 void
204 StateWidth_Context::reset()
205 {
206         refresh_ducks();
207 }
208
209 StateWidth_Context::StateWidth_Context(CanvasView* canvas_view):
210         canvas_view_(canvas_view),
211         is_working(*canvas_view),
212         prev_workarea_layer_clicking(get_work_area()->get_allow_layer_clicks()),
213         prev_workarea_duck_clicking(get_work_area()->get_allow_duck_clicks()),
214         old_duckmask(get_work_area()->get_type_mask()),
215
216         settings(synfigapp::Main::get_selected_input_device()->settings()),
217
218         adj_delta(6,0,20,0.01,0.1),
219         spin_delta(adj_delta,0.01,3),
220
221         adj_radius(200,0,1e50,1,10),
222         spin_radius(adj_radius,1,1),
223
224         check_relative(_("Relative Growth"))
225 {
226         load_settings();
227
228         // Set up the tool options dialog
229         options_table.attach(*manage(new Gtk::Label(_("Width Tool"))),  0, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
230
231         //expand stuff
232         options_table.attach(*manage(new Gtk::Label(_("Growth:"))),             0, 1, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
233         options_table.attach(spin_delta,                                                                1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
234
235         options_table.attach(*manage(new Gtk::Label(_("Radius:"))),             0, 1, 2, 3, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
236         options_table.attach(spin_radius,                                                               1, 2, 2, 3, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
237
238         options_table.attach(check_relative,                                                    0, 2, 3, 4, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
239
240         options_table.show_all();
241
242         refresh_tool_options();
243         App::dialog_tool_options->present();
244
245         // Turn off layer clicking
246         get_work_area()->set_allow_layer_clicks(false);
247
248         // clear out the ducks
249         //get_work_area()->clear_ducks();
250
251         // Refresh the work area
252         get_work_area()->queue_draw();
253
254         //Create the new ducks
255         added = false;
256
257         if(!center)
258         {
259                 center = new Duck();
260                 center->set_name("p1");
261                 center->set_type(Duck::TYPE_POSITION);
262         }
263
264         if(!radius)
265         {
266                 radius = new Duck();
267                 radius->set_origin(center);
268                 radius->set_radius(true);
269                 radius->set_type(Duck::TYPE_RADIUS);
270                 radius->set_name("radius");
271         }
272
273         if(!closestpoint)
274         {
275                 closestpoint = new Duck();
276                 closestpoint->set_name("closest");
277                 closestpoint->set_type(Duck::TYPE_POSITION);
278         }
279
280         //Disable duck clicking for the maximum coolness :)
281         get_work_area()->set_allow_duck_clicks(false);
282         get_work_area()->set_type_mask((Duck::Type)((int)Duck::TYPE_WIDTH + (int)Duck::TYPE_RADIUS));
283
284         // Turn the mouse pointer to crosshairs
285         get_work_area()->set_cursor(Gdk::CROSSHAIR);
286
287         // Hide the tables if they are showing
288         //prev_table_status=get_canvas_view()->tables_are_visible();
289         //if(prev_table_status)get_canvas_view()->hide_tables();
290
291         // Disable the time bar
292         //get_canvas_view()->set_sensitive_timebar(false);
293
294         // Connect a signal
295         //get_work_area()->signal_user_click().connect(sigc::mem_fun(*this,&studio::StateWidth_Context::on_user_click));
296
297         App::toolbox->refresh();
298 }
299
300 void
301 StateWidth_Context::refresh_tool_options()
302 {
303         App::dialog_tool_options->clear();
304         App::dialog_tool_options->set_widget(options_table);
305         App::dialog_tool_options->set_local_name(_("Width Tool"));
306         App::dialog_tool_options->set_name("width");
307 }
308
309 Smach::event_result
310 StateWidth_Context::event_refresh_tool_options(const Smach::event& /*x*/)
311 {
312         refresh_tool_options();
313         return Smach::RESULT_ACCEPT;
314 }
315
316 StateWidth_Context::~StateWidth_Context()
317 {
318         save_settings();
319
320         //remove ducks if need be
321         if(added)
322         {
323                 get_work_area()->erase_duck(center);
324                 get_work_area()->erase_duck(radius);
325                 get_work_area()->erase_duck(closestpoint);
326                 added = false;
327         }
328
329         // Restore Duck clicking
330         get_work_area()->set_allow_duck_clicks(prev_workarea_duck_clicking);
331
332         // Restore layer clicking
333         get_work_area()->set_allow_layer_clicks(prev_workarea_layer_clicking);
334
335         // Restore the mouse pointer
336         get_work_area()->reset_cursor();
337
338         // Restore duck masking
339         get_work_area()->set_type_mask(old_duckmask);
340
341         // Tool options be rid of ye!!
342         App::dialog_tool_options->clear();
343
344         // Enable the time bar
345         //get_canvas_view()->set_sensitive_timebar(true);
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 the 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                 synfigapp::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                                         synfig::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 }