Add a 'feather' setting to the polygon tool's 'Tool Options' panel.
[synfig.git] / synfig-studio / trunk / src / gtkmm / state_polygon.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file state_polygon.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) 2007, 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 <synfig/valuenode_dynamiclist.h>
37 #include <synfigapp/action_system.h>
38
39 #include "state_polygon.h"
40 #include "canvasview.h"
41 #include "workarea.h"
42 #include "app.h"
43
44 #include <synfigapp/action.h>
45 #include "event_mouse.h"
46 #include "event_layerclick.h"
47 #include "toolbox.h"
48 #include "dialog_tooloptions.h"
49 #include <synfigapp/main.h>
50
51 #include "general.h"
52
53 #endif
54
55 /* === U S I N G =========================================================== */
56
57 using namespace std;
58 using namespace etl;
59 using namespace synfig;
60 using namespace studio;
61
62 /* === M A C R O S ========================================================= */
63
64 /* === G L O B A L S ======================================================= */
65
66 StatePolygon studio::state_polygon;
67
68 /* === C L A S S E S & S T R U C T S ======================================= */
69
70 class studio::StatePolygon_Context : public sigc::trackable
71 {
72         etl::handle<CanvasView> canvas_view_;
73         CanvasView::IsWorking is_working;
74
75         bool prev_table_status;
76         bool prev_workarea_layer_status_;
77
78         Gtk::Menu menu;
79
80         Duckmatic::Push duckmatic_push;
81
82         std::list<synfig::Point> polygon_point_list;
83         synfigapp::Settings& settings;
84
85
86         bool on_polygon_duck_change(const synfig::Point &point, std::list<synfig::Point>::iterator iter);
87
88
89         void popup_handle_menu(synfigapp::ValueDesc value_desc);
90
91
92         void refresh_ducks();
93
94         Gtk::Table options_table;
95         Gtk::CheckButton checkbutton_invert;
96         Gtk::Entry entry_id;
97         Gtk::Button button_make;
98         Gtk::Adjustment  adj_feather;
99         Gtk::SpinButton  spin_feather;
100
101 public:
102         synfig::String get_id()const { return entry_id.get_text(); }
103         void set_id(const synfig::String& x) { return entry_id.set_text(x); }
104
105         bool get_invert()const { return checkbutton_invert.get_active(); }
106         void set_invert(bool i) { checkbutton_invert.set_active(i); }
107
108         Real get_feather() const { return adj_feather.get_value(); }
109         void set_feather(Real x) { return adj_feather.set_value(x); }
110
111         Smach::event_result event_stop_handler(const Smach::event& x);
112
113         Smach::event_result event_refresh_handler(const Smach::event& x);
114
115         Smach::event_result event_mouse_click_handler(const Smach::event& x);
116         Smach::event_result event_refresh_tool_options(const Smach::event& x);
117         void refresh_tool_options();
118
119         StatePolygon_Context(CanvasView* canvas_view);
120
121         ~StatePolygon_Context();
122
123         const etl::handle<CanvasView>& get_canvas_view()const{return canvas_view_;}
124         etl::handle<synfigapp::CanvasInterface> get_canvas_interface()const{return canvas_view_->canvas_interface();}
125         synfig::Canvas::Handle get_canvas()const{return canvas_view_->get_canvas();}
126         WorkArea * get_work_area()const{return canvas_view_->get_work_area();}
127
128         //void on_user_click(synfig::Point point);
129         void load_settings();
130         void save_settings();
131         void reset();
132         void increment_id();
133         bool egress_on_selection_change;
134         Smach::event_result event_layer_selection_changed_handler(const Smach::event& /*x*/)
135         {
136                 if(egress_on_selection_change)
137                         throw Smach::egress_exception();
138                 return Smach::RESULT_OK;
139         }
140
141         void run();
142 };      // END of class StatePolygon_Context
143
144 /* === M E T H O D S ======================================================= */
145
146 StatePolygon::StatePolygon():
147         Smach::state<StatePolygon_Context>("polygon")
148 {
149         insert(event_def(EVENT_LAYER_SELECTION_CHANGED,&StatePolygon_Context::event_layer_selection_changed_handler));
150         insert(event_def(EVENT_STOP,&StatePolygon_Context::event_stop_handler));
151         insert(event_def(EVENT_REFRESH,&StatePolygon_Context::event_refresh_handler));
152         insert(event_def(EVENT_REFRESH_DUCKS,&StatePolygon_Context::event_refresh_handler));
153         insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_DOWN,&StatePolygon_Context::event_mouse_click_handler));
154         insert(event_def(EVENT_REFRESH_TOOL_OPTIONS,&StatePolygon_Context::event_refresh_tool_options));
155 }
156
157 StatePolygon::~StatePolygon()
158 {
159 }
160
161 void
162 StatePolygon_Context::load_settings()
163 {
164         String value;
165
166         if(settings.get_value("polygon.id",value))
167                 set_id(value);
168         else
169                 set_id("Polygon");
170
171         if(settings.get_value("polygon.invert",value) && value != "0")
172                 set_invert(true);
173         else
174                 set_invert(false);
175
176         if(settings.get_value("polygon.feather",value))
177         {
178                 Real n = atof(value.c_str());
179                 set_feather(n);
180         }
181 }
182
183 void
184 StatePolygon_Context::save_settings()
185 {
186         settings.set_value("polygon.id",get_id().c_str());
187         settings.set_value("polygon.invert",get_invert()?"1":"0");
188         settings.set_value("polygon.feather",strprintf("%f",get_feather()));
189 }
190
191 void
192 StatePolygon_Context::reset()
193 {
194         polygon_point_list.clear();
195         refresh_ducks();
196 }
197
198 void
199 StatePolygon_Context::increment_id()
200 {
201         String id(get_id());
202         int number=1;
203         int digits=0;
204
205         if(id.empty())
206                 id="Polygon";
207
208         // If there is a number
209         // already at the end of the
210         // id, then remove it.
211         if(id[id.size()-1]<='9' && id[id.size()-1]>='0')
212         {
213                 // figure out how many digits it is
214                 for (digits = 0;
215                          (int)id.size()-1 >= digits && id[id.size()-1-digits] <= '9' && id[id.size()-1-digits] >= '0';
216                          digits++)
217                         ;
218
219                 String str_number;
220                 str_number=String(id,id.size()-digits,id.size());
221                 id=String(id,0,id.size()-digits);
222
223                 number=atoi(str_number.c_str());
224         }
225         else
226         {
227                 number=1;
228                 digits=3;
229         }
230
231         number++;
232
233         // Add the number back onto the id
234         {
235                 const String format(strprintf("%%0%dd",digits));
236                 id+=strprintf(format.c_str(),number);
237         }
238
239         // Set the ID
240         set_id(id);
241 }
242
243 StatePolygon_Context::StatePolygon_Context(CanvasView* canvas_view):
244         canvas_view_(canvas_view),
245         is_working(*canvas_view),
246         prev_workarea_layer_status_(get_work_area()->get_allow_layer_clicks()),
247         duckmatic_push(get_work_area()),
248         settings(synfigapp::Main::get_selected_input_device()->settings()),
249         entry_id(),
250         checkbutton_invert(_("Invert")),
251         button_make(_("Make")),
252         adj_feather(0,0,10000,0.01,0.1),
253         spin_feather(adj_feather,0.01,4)
254 {
255         egress_on_selection_change=true;
256         load_settings();
257
258         // Set up the tool options dialog
259         options_table.attach(*manage(new Gtk::Label(_("Polygon Tool"))),        0, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
260         options_table.attach(entry_id,                                                                          0, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
261
262         //invert flag
263         options_table.attach(checkbutton_invert,                                                        0, 2, 2, 3, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
264
265         //feather stuff
266         options_table.attach(*manage(new Gtk::Label(_("Feather"))),             0, 1, 3, 4, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
267         options_table.attach(spin_feather,                                                                      1, 2, 3, 4, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
268
269         //options_table.attach(button_make, 0, 2, 4, 5, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0);
270         button_make.signal_pressed().connect(sigc::mem_fun(*this,&StatePolygon_Context::run));
271         options_table.show_all();
272         refresh_tool_options();
273         App::dialog_tool_options->present();
274
275
276         // Turn off layer clicking
277         get_work_area()->set_allow_layer_clicks(false);
278
279         // clear out the ducks
280         get_work_area()->clear_ducks();
281
282         // Refresh the work area
283         get_work_area()->queue_draw();
284
285         get_canvas_view()->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::StatePolygon_Context::on_user_click));
296
297         App::toolbox->refresh();
298 }
299
300 void
301 StatePolygon_Context::refresh_tool_options()
302 {
303         App::dialog_tool_options->clear();
304         App::dialog_tool_options->set_widget(options_table);
305
306         App::dialog_tool_options->set_local_name(_("Polygon Tool"));
307         App::dialog_tool_options->set_name("polygon");
308
309         App::dialog_tool_options->add_button(
310                 Gtk::StockID("gtk-execute"),
311                 _("Make Polygon")
312         )->signal_clicked().connect(
313                 sigc::mem_fun(
314                         *this,
315                         &StatePolygon_Context::run
316                 )
317         );
318
319         App::dialog_tool_options->add_button(
320                 Gtk::StockID("gtk-clear"),
321                 _("Clear current Polygon")
322         )->signal_clicked().connect(
323                 sigc::mem_fun(
324                         *this,
325                         &StatePolygon_Context::reset
326                 )
327         );
328 }
329
330 Smach::event_result
331 StatePolygon_Context::event_refresh_tool_options(const Smach::event& /*x*/)
332 {
333         refresh_tool_options();
334         return Smach::RESULT_ACCEPT;
335 }
336
337 StatePolygon_Context::~StatePolygon_Context()
338 {
339         run();
340
341         save_settings();
342         // Restore layer clicking
343         get_work_area()->set_allow_layer_clicks(prev_workarea_layer_status_);
344
345         App::dialog_tool_options->clear();
346
347         get_canvas_view()->work_area->reset_cursor();
348
349         // Enable the time bar
350         get_canvas_view()->set_sensitive_timebar(true);
351
352         // Bring back the tables if they were out before
353         if(prev_table_status)get_canvas_view()->show_tables();
354
355         // Refresh the work area
356         get_work_area()->queue_draw();
357
358         App::toolbox->refresh();
359 }
360
361 Smach::event_result
362 StatePolygon_Context::event_stop_handler(const Smach::event& /*x*/)
363 {
364         synfig::info("STATE RotoPolygon: Received Stop Event");
365         //throw Smach::egress_exception();
366         reset();
367         return Smach::RESULT_ACCEPT;
368
369 }
370
371 Smach::event_result
372 StatePolygon_Context::event_refresh_handler(const Smach::event& /*x*/)
373 {
374         synfig::info("STATE RotoPolygon: Received Refresh Event");
375         refresh_ducks();
376         return Smach::RESULT_ACCEPT;
377 }
378
379 void
380 StatePolygon_Context::run()
381 {
382         if(polygon_point_list.empty())
383                 return;
384
385         if(polygon_point_list.size()<3)
386         {
387                 get_canvas_view()->get_ui_interface()->error("You need at least 3 points to create a polygon");
388                 return;
389         }
390                 Layer::Handle layer;
391                 Canvas::Handle canvas(get_canvas_view()->get_canvas());
392                 int depth(0);
393
394                 // we are temporarily using the layer to hold something
395                 layer=get_canvas_view()->get_selection_manager()->get_selected_layer();
396                 if(layer)
397                 {
398                         depth=layer->get_depth();
399                         canvas=layer->get_canvas();
400                 }
401
402                 {
403                         synfigapp::Action::PassiveGrouper group(get_canvas_interface()->get_instance().get(),_("New Polygon"));
404                         synfigapp::PushMode push_mode(get_canvas_interface(),synfigapp::MODE_NORMAL);
405
406                         Layer::Handle layer(get_canvas_interface()->add_layer_to("polygon",canvas,depth));
407
408                         layer->set_param("invert",get_invert());
409                         get_canvas_interface()->signal_layer_param_changed()(layer,"invert");
410
411                         if(get_feather())
412                         {
413                                 layer->set_param("feather",get_feather());
414                                 get_canvas_interface()->signal_layer_param_changed()(layer,"feather");
415                         }
416
417                         layer->set_description(get_id());
418                         get_canvas_interface()->signal_layer_new_description()(layer,layer->get_description());
419
420                         layer->disconnect_dynamic_param("vector_list");
421                         if(!layer->set_param("vector_list",polygon_point_list))
422                         {
423                                 group.cancel();
424                                 get_canvas_view()->get_ui_interface()->error("Unable to set layer parameter");
425                                 return;
426                         }
427
428                         {
429                                 synfigapp::Action::Handle action(synfigapp::Action::create("value_desc_convert"));
430                                 synfigapp::ValueDesc value_desc(layer,"vector_list");
431                                 action->set_param("canvas",get_canvas());
432                                 action->set_param("canvas_interface",get_canvas_interface());
433                                 action->set_param("value_desc",value_desc);
434                                 action->set_param("type","dynamic_list");
435                                 action->set_param("time",get_canvas_interface()->get_time());
436                                 if(!get_canvas_interface()->get_instance()->perform_action(action))
437                                 {
438                                         group.cancel();
439                                         get_canvas_view()->get_ui_interface()->error("Unable to execute action \"value_desc_convert\"");
440                                         return;
441                                 }
442                         }
443                         egress_on_selection_change=false;
444                         synfigapp::SelectionManager::LayerList layer_selection;
445                         if (!getenv("SYNFIG_TOOLS_CLEAR_SELECTION"))
446                                 layer_selection = get_canvas_view()->get_selection_manager()->get_selected_layers();
447                         get_canvas_interface()->get_selection_manager()->clear_selected_layers();
448                         layer_selection.push_back(layer);
449                         get_canvas_interface()->get_selection_manager()->set_selected_layers(layer_selection);
450                         egress_on_selection_change=true;
451                         //get_canvas_interface()->signal_dirty_preview()();
452                 }
453 /*
454                 else
455                 {
456                         ValueNode::Handle value_node=(ValueNode_Const::create(polygon_point_list));
457                         std::string valuenode_name="Poly";
458                         while(studio::App::dialog_entry("New Polygon", "Please enter the new ID for this value_node",valuenode_name))
459                                 if(get_canvas_interface()->add_value_node(value_node,valuenode_name))
460                                         return true;
461                 }
462 */
463         reset();
464         increment_id();
465 }
466
467 Smach::event_result
468 StatePolygon_Context::event_mouse_click_handler(const Smach::event& x)
469 {
470         synfig::info("STATE ROTOPOLYGON: Received mouse button down Event");
471         const EventMouse& event(*reinterpret_cast<const EventMouse*>(&x));
472         switch(event.button)
473         {
474         case BUTTON_LEFT:
475                 polygon_point_list.push_back(get_work_area()->snap_point_to_grid(event.pos));
476                 refresh_ducks();
477                 return Smach::RESULT_ACCEPT;
478
479         case BUTTON_RIGHT: // Intercept the right-button click to short-circuit the pop-up menu
480                 if (!getenv("SYNFIG_ENABLE_POPUP_MENU_IN_ALL_TOOLS"))
481                         return Smach::RESULT_ACCEPT;
482
483         default:
484                 return Smach::RESULT_OK;
485         }
486 }
487
488
489 void
490 StatePolygon_Context::refresh_ducks()
491 {
492         get_work_area()->clear_ducks();
493
494         if(polygon_point_list.empty()) return;
495
496         std::list<synfig::Point>::iterator iter=polygon_point_list.begin();
497
498         etl::handle<WorkArea::Duck> duck;
499         duck=new WorkArea::Duck(*iter);
500         duck->set_editable(true);
501         duck->signal_edited().connect(
502                 sigc::bind(sigc::mem_fun(*this,&studio::StatePolygon_Context::on_polygon_duck_change),iter)
503         );
504         duck->signal_user_click(0).connect(sigc::mem_fun(*this,&StatePolygon_Context::run));
505
506         get_work_area()->add_duck(duck);
507
508         for(++iter;iter!=polygon_point_list.end();++iter)
509         {
510                 etl::handle<WorkArea::Bezier> bezier(new WorkArea::Bezier());
511                 bezier->p1=bezier->c1=duck;
512
513                 duck=new WorkArea::Duck(*iter);
514                 duck->set_editable(true);
515                 duck->set_name(strprintf("%x",&*iter));
516                 duck->signal_edited().connect(
517                         sigc::bind(sigc::mem_fun(*this,&studio::StatePolygon_Context::on_polygon_duck_change),iter)
518                 );
519
520                 get_work_area()->add_duck(duck);
521
522                 bezier->p2=bezier->c2=duck;
523                 get_work_area()->add_bezier(bezier);
524         }
525         get_work_area()->queue_draw();
526 }
527
528
529 bool
530 StatePolygon_Context::on_polygon_duck_change(const synfig::Point &point, std::list<synfig::Point>::iterator iter)
531 {
532         *iter=point;
533         return true;
534 }