Code

Move filters into their own namespace Inkscape::Filters (from NR::)
[inkscape.git] / src / ui / dialog / filter-effects-dialog.cpp
1 /** @file
2  * @brief Filter Effects dialog
3  */
4 /* Authors:
5  *   Nicholas Bishop <nicholasbishop@gmail.org>
6  *   Rodrigo Kumpera <kumpera@gmail.com>
7  *   Felipe C. da S. Sanches <felipe.sanches@gmail.com>
8  *
9  * Copyright (C) 2007 Authors
10  *
11  * Released under GNU GPL.  Read the file 'COPYING' for more information.
12  */
14 #ifdef HAVE_CONFIG_H
15 # include <config.h>
16 #endif
18 #include <gtk/gtktreeview.h>
19 #include <gtkmm/cellrenderertext.h>
20 #include <gtkmm/colorbutton.h>
21 #include <gtkmm/messagedialog.h>
22 #include <gtkmm/paned.h>
23 #include <gtkmm/scale.h>
24 #include <gtkmm/scrolledwindow.h>
25 #include <gtkmm/spinbutton.h>
26 #include <gtkmm/stock.h>
27 #include <gtkmm/tooltips.h>
28 #include <glibmm/i18n.h>
30 #include "application/application.h"
31 #include "application/editor.h"
32 #include "desktop.h"
33 #include "desktop-handles.h"
34 #include "dialog-manager.h"
35 #include "dir-util.h"
36 #include "document.h"
37 #include "filter-chemistry.h"
38 #include "filter-effects-dialog.h"
39 #include "filter-enums.h"
40 #include "inkscape.h"
41 #include "path-prefix.h"
42 #include "preferences.h"
43 #include "selection.h"
44 #include "filters/blend.h"
45 #include "filters/colormatrix.h"
46 #include "filters/componenttransfer.h"
47 #include "filters/composite.h"
48 #include "filters/convolvematrix.h"
49 #include "filters/displacementmap.h"
50 #include "filters/distantlight.h"
51 #include "filters/merge.h"
52 #include "filters/mergenode.h"
53 #include "filters/offset.h"
54 #include "filters/pointlight.h"
55 #include "filters/spotlight.h"
56 #include "sp-filter-primitive.h"
57 #include "sp-gaussian-blur.h"
59 #include "style.h"
60 #include "svg/svg-color.h"
61 #include "ui/dialog/filedialog.h"
62 #include "verbs.h"
63 #include "xml/node.h"
64 #include "xml/node-observer.h"
65 #include "xml/repr.h"
66 #include <sstream>
68 #include "io/sys.h"
69 #include <iostream>
71 using namespace Inkscape::Filters;
73 namespace Inkscape {
74 namespace UI {
75 namespace Dialog {
77 // Returns the number of inputs available for the filter primitive type
78 int input_count(const SPFilterPrimitive* prim)
79 {
80     if(!prim)
81         return 0;
82     else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
83         return 2;
84     else if(SP_IS_FEMERGE(prim)) {
85         // Return the number of feMergeNode connections plus an extra
86         int count = 1;
87         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count){};
88         return count;
89     }
90     else
91         return 1;
92 }
94 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
95 {
96 public:
97     CheckButtonAttr(bool def, const Glib::ustring& label,
98                     const Glib::ustring& tv, const Glib::ustring& fv,
99                     const SPAttributeEnum a, char* tip_text)
100         : Gtk::CheckButton(label),
101           AttrWidget(a, def),
102           _true_val(tv), _false_val(fv)
103     {
104         signal_toggled().connect(signal_attr_changed().make_slot());
105         if (tip_text) _tt.set_tip(*this, tip_text);
106     }
108     Glib::ustring get_as_attribute() const
109     {
110         return get_active() ? _true_val : _false_val;
111     }
113     void set_from_attribute(SPObject* o)
114     {
115         const gchar* val = attribute_value(o);
116         if(val) {
117             if(_true_val == val)
118                 set_active(true);
119             else if(_false_val == val)
120                 set_active(false);
121         } else {
122             set_active(get_default()->as_bool());
123         }
124     }
125 private:
126     const Glib::ustring _true_val, _false_val;
127 };
129 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
131 public:
132     SpinButtonAttr(double lower, double upper, double step_inc,
133                    double climb_rate, int digits, const SPAttributeEnum a, double def, char* tip_text)
134         : Gtk::SpinButton(climb_rate, digits),
135           AttrWidget(a, def)
136     {
137         if (tip_text) _tt.set_tip(*this, tip_text);
138         set_range(lower, upper);
139         set_increments(step_inc, step_inc * 5);
141         signal_value_changed().connect(signal_attr_changed().make_slot());
142     }
144     Glib::ustring get_as_attribute() const
145     {
146         const double val = get_value();
148         if(get_digits() == 0)
149             return Glib::Ascii::dtostr((int)val);
150         else
151             return Glib::Ascii::dtostr(val);
152     }
154     void set_from_attribute(SPObject* o)
155     {
156         const gchar* val = attribute_value(o);
157         if(val){
158             set_value(Glib::Ascii::strtod(val));
159         } else {
160             set_value(get_default()->as_double());
161         }
162     }
163 };
165 template< typename T> class ComboWithTooltip : public Gtk::EventBox
167 public:
168     ComboWithTooltip<T>(T default_value, const Util::EnumDataConverter<T>& c, const SPAttributeEnum a = SP_ATTR_INVALID, char* tip_text = NULL)
169     {
170         if (tip_text) {
171             _tt.set_tip(*this, tip_text);
172         }
173         combo = new ComboBoxEnum<T>(default_value, c, a);
174         add(*combo);
175         show_all();
176     }
178     ~ComboWithTooltip()
179     {
180         delete combo;
181     }
183     ComboBoxEnum<T>* get_attrwidget()
184     {
185         return combo;
186     }
187 private:
188     Gtk::Tooltips _tt;
189     ComboBoxEnum<T>* combo;
190 };
192 // Contains an arbitrary number of spin buttons that use seperate attributes
193 class MultiSpinButton : public Gtk::HBox
195 public:
196     MultiSpinButton(double lower, double upper, double step_inc,
197                     double climb_rate, int digits, std::vector<SPAttributeEnum> attrs, std::vector<double> default_values, std::vector<char*> tip_text)
198     {
199         g_assert(attrs.size()==default_values.size());
200         g_assert(attrs.size()==tip_text.size());
201         for(unsigned i = 0; i < attrs.size(); ++i) {
202             _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i], default_values[i], tip_text[i]));
203             pack_start(*_spins.back(), false, false);
204         }
205     }
207     ~MultiSpinButton()
208     {
209         for(unsigned i = 0; i < _spins.size(); ++i)
210             delete _spins[i];
211     }
213     std::vector<SpinButtonAttr*>& get_spinbuttons()
214     {
215         return _spins;
216     }
217 private:
218     std::vector<SpinButtonAttr*> _spins;
219 };
221 // Contains two spinbuttons that describe a NumberOptNumber
222 class DualSpinButton : public Gtk::HBox, public AttrWidget
224 public:
225     DualSpinButton(char* def, double lower, double upper, double step_inc,
226                    double climb_rate, int digits, const SPAttributeEnum a, char* tt1, char* tt2)
227         : AttrWidget(a, def), //TO-DO: receive default num-opt-num as parameter in the constructor
228           _s1(climb_rate, digits), _s2(climb_rate, digits)
229     {
230         if (tt1) _tt.set_tip(_s1, tt1);
231         if (tt2) _tt.set_tip(_s2, tt2);
232         _s1.set_range(lower, upper);
233         _s2.set_range(lower, upper);
234         _s1.set_increments(step_inc, step_inc * 5);
235         _s2.set_increments(step_inc, step_inc * 5);
237         _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
238         _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
240         pack_start(_s1, false, false);
241         pack_start(_s2, false, false);
242     }
244     Gtk::SpinButton& get_spinbutton1()
245     {
246         return _s1;
247     }
249     Gtk::SpinButton& get_spinbutton2()
250     {
251         return _s2;
252     }
254     virtual Glib::ustring get_as_attribute() const
255     {
256         double v1 = _s1.get_value();
257         double v2 = _s2.get_value();
259         if(_s1.get_digits() == 0) {
260             v1 = (int)v1;
261             v2 = (int)v2;
262         }
264         return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
265     }
267     virtual void set_from_attribute(SPObject* o)
268     {
269         const gchar* val = attribute_value(o);
270         NumberOptNumber n;
271         if(val) {
272             n.set(val);
273         } else {
274             n.set(get_default()->as_charptr());
275         }
276         _s1.set_value(n.getNumber());
277         _s2.set_value(n.getOptNumber());
279     }
280 private:
281     Gtk::SpinButton _s1, _s2;
282 };
284 class ColorButton : public Gtk::ColorButton, public AttrWidget
286 public:
287     ColorButton(unsigned int def, const SPAttributeEnum a, char* tip_text)
288         : AttrWidget(a, def)
289     {
290         signal_color_set().connect(signal_attr_changed().make_slot());
291         if (tip_text) _tt.set_tip(*this, tip_text);
293         Gdk::Color col;
294         col.set_rgb(65535, 65535, 65535);
295         set_color(col);
296     }
298     // Returns the color in 'rgb(r,g,b)' form.
299     Glib::ustring get_as_attribute() const
300     {
301         std::ostringstream os;
302         const Gdk::Color c = get_color();
303         const int r = c.get_red() / 257, g = c.get_green() / 257, b = c.get_blue() / 257;//TO-DO: verify this. This sounds a lot strange! shouldn't it be 256?
304         os << "rgb(" << r << "," << g << "," << b << ")";
305         return os.str();
306     }
309     void set_from_attribute(SPObject* o)
310     {
311         const gchar* val = attribute_value(o);
312         guint32 i = 0;
313         if(val) {
314             i = sp_svg_read_color(val, 0xFFFFFFFF);
315         } else {
316             i = (guint32) get_default()->as_uint();
317         }
318         const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
319         Gdk::Color col;
320         col.set_rgb(r * 256, g * 256, b * 256);
321         set_color(col);
322     }
323 };
325 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
326 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
328 public:
329     MatrixAttr(const SPAttributeEnum a, char* tip_text = NULL)
330         : AttrWidget(a), _locked(false)
331     {
332         _model = Gtk::ListStore::create(_columns);
333         _tree.set_model(_model);
334         _tree.set_headers_visible(false);
335         _tree.show();
336         add(_tree);
337         set_shadow_type(Gtk::SHADOW_IN);
338         if (tip_text) _tt.set_tip(_tree, tip_text);
339     }
341     std::vector<double> get_values() const
342     {
343         std::vector<double> vec;
344         for(Gtk::TreeIter iter = _model->children().begin();
345             iter != _model->children().end(); ++iter) {
346             for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
347                 vec.push_back((*iter)[_columns.cols[c]]);
348         }
349         return vec;
350     }
352     void set_values(const std::vector<double>& v)
353     {
354         unsigned i = 0;
355         for(Gtk::TreeIter iter = _model->children().begin();
356             iter != _model->children().end(); ++iter) {
357             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
358                 if(i >= v.size())
359                     return;
360                 (*iter)[_columns.cols[c]] = v[i];
361                 ++i;
362             }
363         }
364     }
366     Glib::ustring get_as_attribute() const
367     {
368         std::ostringstream os;
370         for(Gtk::TreeIter iter = _model->children().begin();
371             iter != _model->children().end(); ++iter) {
372             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
373                 os << (*iter)[_columns.cols[c]] << " ";
374             }
375         }
377         return os.str();
378     }
380     void set_from_attribute(SPObject* o)
381     {
382         if(o) {
383             if(SP_IS_FECONVOLVEMATRIX(o)) {
384                 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
385                 int cols, rows;
386                 cols = (int)conv->order.getNumber();
387                 if(cols > 5)
388                     cols = 5;
389                 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
390                 update(o, rows, cols);
391             }
392             else if(SP_IS_FECOLORMATRIX(o))
393                 update(o, 4, 5);
394         }
395     }
396 private:
397     class MatrixColumns : public Gtk::TreeModel::ColumnRecord
398     {
399     public:
400         MatrixColumns()
401         {
402             cols.resize(5);
403             for(unsigned i = 0; i < cols.size(); ++i)
404                 add(cols[i]);
405         }
406         std::vector<Gtk::TreeModelColumn<double> > cols;
407     };
409     void update(SPObject* o, const int rows, const int cols)
410     {
411         if(_locked)
412             return;
414         _model->clear();
416         _tree.remove_all_columns();
418         std::vector<gdouble>* values = NULL;
419         if(SP_IS_FECOLORMATRIX(o))
420             values = &SP_FECOLORMATRIX(o)->values;
421         else if(SP_IS_FECONVOLVEMATRIX(o))
422             values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
423         else
424             return;
426         if(o) {
427             int ndx = 0;
429             for(int i = 0; i < cols; ++i) {
430                 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
431                 dynamic_cast<Gtk::CellRendererText*>(
432                     _tree.get_column_cell_renderer(i))->signal_edited().connect(
433                         sigc::mem_fun(*this, &MatrixAttr::rebind));
434             }
436             for(int r = 0; r < rows; ++r) {
437                 Gtk::TreeRow row = *(_model->append());
438                 // Default to identity matrix
439                 for(int c = 0; c < cols; ++c, ++ndx)
440                     row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
441             }
442         }
443     }
445     void rebind(const Glib::ustring&, const Glib::ustring&)
446     {
447         _locked = true;
448         signal_attr_changed()();
449         _locked = false;
450     }
452     bool _locked;
453     Gtk::TreeView _tree;
454     Glib::RefPtr<Gtk::ListStore> _model;
455     MatrixColumns _columns;
456 };
458 // Displays a matrix or a slider for feColorMatrix
459 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
461 public:
462     ColorMatrixValues()
463         : AttrWidget(SP_ATTR_VALUES),
464           _matrix(SP_ATTR_VALUES, _("This matrix determines a linear transform on colour space. Each line affects one of the color components. Each column determines how much of each color component from the input is passed to the output. The last column does not depend on input colors, so can be used to adjust a constant component value.")),
465           _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
466           _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
467           _label(_("None"), Gtk::ALIGN_LEFT),
468           _use_stored(false),
469           _saturation_store(0),
470           _angle_store(0)
471     {
472         _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
473         _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
474         _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
475         signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
477         _matrix.show();
478         _saturation.show();
479         _angle.show();
480         _label.show();
481         _label.set_sensitive(false);
483         set_shadow_type(Gtk::SHADOW_NONE);
484     }
486     virtual void set_from_attribute(SPObject* o)
487     {
488         if(SP_IS_FECOLORMATRIX(o)) {
489             SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
490             remove();
491             switch(col->type) {
492                 case COLORMATRIX_SATURATE:
493                     add(_saturation);
494                     if(_use_stored)
495                         _saturation.set_value(_saturation_store);
496                     else
497                         _saturation.set_from_attribute(o);
498                     break;
499                 case COLORMATRIX_HUEROTATE:
500                     add(_angle);
501                     if(_use_stored)
502                         _angle.set_value(_angle_store);
503                     else
504                         _angle.set_from_attribute(o);
505                     break;
506                 case COLORMATRIX_LUMINANCETOALPHA:
507                     add(_label);
508                     break;
509                 case COLORMATRIX_MATRIX:
510                 default:
511                     add(_matrix);
512                     if(_use_stored)
513                         _matrix.set_values(_matrix_store);
514                     else
515                         _matrix.set_from_attribute(o);
516                     break;
517             }
518             _use_stored = true;
519         }
520     }
522     virtual Glib::ustring get_as_attribute() const
523     {
524         const Widget* w = get_child();
525         if(w == &_label)
526             return "";
527         else
528             return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
529     }
531     void clear_store()
532     {
533         _use_stored = false;
534     }
535 private:
536     void update_store()
537     {
538         const Widget* w = get_child();
539         if(w == &_matrix)
540             _matrix_store = _matrix.get_values();
541         else if(w == &_saturation)
542             _saturation_store = _saturation.get_value();
543         else if(w == &_angle)
544             _angle_store = _angle.get_value();
545     }
547     MatrixAttr _matrix;
548     SpinSlider _saturation;
549     SpinSlider _angle;
550     Gtk::Label _label;
552     // Store separate values for the different color modes
553     bool _use_stored;
554     std::vector<double> _matrix_store;
555     double _saturation_store;
556     double _angle_store;
557 };
559 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
561 //Displays a chooser for feImage input
562 //It may be a filename or the id for an SVG Element
563 //described in xlink:href syntax
564 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
566 public:
567     FileOrElementChooser(const SPAttributeEnum a)
568         : AttrWidget(a)
569     {
570         pack_start(_entry, false, false);
571         pack_start(_fromFile, false, false);
572         pack_start(_fromSVGElement, false, false);
574         _fromFile.set_label(_("Image File"));
575         _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
577         _fromSVGElement.set_label(_("Selected SVG Element"));
578         _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
580         _entry.signal_changed().connect(signal_attr_changed().make_slot());
582         show_all();
584     }
586     // Returns the element in xlink:href form.
587     Glib::ustring get_as_attribute() const
588     {
589         return _entry.get_text();
590     }
593     void set_from_attribute(SPObject* o)
594     {
595         const gchar* val = attribute_value(o);
596         if(val) {
597             _entry.set_text(val);
598         } else {
599             _entry.set_text("");
600         }
601     }
603     void set_desktop(SPDesktop* d){
604         _desktop = d;
605     }
607 private:
608     void select_svg_element(){
609         Inkscape::Selection* sel = sp_desktop_selection(_desktop);
610         if (sel->isEmpty()) return;
611         Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
612         if (!node || !node->matchAttributeName("id")) return;
614         std::ostringstream xlikhref;
615         xlikhref << "#" << node->attribute("id");
616         _entry.set_text(xlikhref.str());
617     }
619     void select_file(){
621         //# Get the current directory for finding files
622         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
623         Glib::ustring open_path;
624         Glib::ustring attr = prefs->getString("/dialogs/open/path");
625         if (!attr.empty())
626             open_path = attr;
628         //# Test if the open_path directory exists
629         if (!Inkscape::IO::file_test(open_path.c_str(),
630                   (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
631             open_path = "";
633         //# If no open path, default to our home directory
634         if (open_path.size() < 1)
635             {
636             open_path = g_get_home_dir();
637             open_path.append(G_DIR_SEPARATOR_S);
638             }
640         //# Create a dialog if we don't already have one
641         if (!selectFeImageFileInstance) {
642             selectFeImageFileInstance =
643                   Inkscape::UI::Dialog::FileOpenDialog::create(
644                      *_desktop->getToplevel(),
645                      open_path,
646                      Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not justy svg*/
647                      (char const *)_("Select an image to be used as feImage input"));
648         }
650         //# Show the dialog
651         bool const success = selectFeImageFileInstance->show();
652         if (!success)
653             return;
655         //# User selected something.  Get name and type
656         Glib::ustring fileName = selectFeImageFileInstance->getFilename();
658         if (fileName.size() > 0) {
660             Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
662             if ( newFileName.size() > 0)
663                 fileName = newFileName;
664             else
665                 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
667             open_path = fileName;
668             open_path.append(G_DIR_SEPARATOR_S);
669             prefs->setString("/dialogs/open/path", open_path);
671             _entry.set_text(fileName);
672         }
673         return;
674     }
676     Gtk::Entry _entry;
677     Gtk::Button _fromFile;
678     Gtk::Button _fromSVGElement;
679     SPDesktop* _desktop;
680 };
682 class FilterEffectsDialog::Settings
684 public:
685     typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
687     Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
688         : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
689     {
690         _groups.resize(_max_types);
691         _attrwidgets.resize(_max_types);
692         _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
694         for(int i = 0; i < _max_types; ++i) {
695             _groups[i] = new Gtk::VBox;
696             b.pack_start(*_groups[i], false, false);
697         }
698         _current_type = 0;
699     }
701     ~Settings()
702     {
703         for(int i = 0; i < _max_types; ++i) {
704             delete _groups[i];
705             for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
706                 delete _attrwidgets[i][j];
707         }
708     }
710     // Show the active settings group and update all the AttrWidgets with new values
711     void show_and_update(const int t, SPObject* ob)
712     {
713         if(t != _current_type) {
714             type(t);
715             for(unsigned i = 0; i < _groups.size(); ++i)
716                 _groups[i]->hide();
717         }
718         if(t >= 0)
719             _groups[t]->show_all();
721         _dialog.set_attrs_locked(true);
722         for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
723             _attrwidgets[_current_type][i]->set_from_attribute(ob);
724         _dialog.set_attrs_locked(false);
725     }
727     int get_current_type() const
728     {
729         return _current_type;
730     }
732     void type(const int t)
733     {
734         _current_type = t;
735     }
737     void add_no_params()
738     {
739         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
740         add_widget(lbl, "");
741     }
743     void add_notimplemented()
744     {
745         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
746         add_widget(lbl, "");
747     }
749     // LightSource
750     LightSourceControl* add_lightsource();
752     // CheckBox
753     CheckButtonAttr* add_checkbutton(bool def, const SPAttributeEnum attr, const Glib::ustring& label,
754                                      const Glib::ustring& tv, const Glib::ustring& fv, char* tip_text = NULL)
755     {
756         CheckButtonAttr* cb = new CheckButtonAttr(def, label, tv, fv, attr, tip_text);
757         add_widget(cb, "");
758         add_attr_widget(cb);
759         return cb;
760     }
762     // ColorButton
763     ColorButton* add_color(unsigned int def, const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text = NULL)
764     {
765         ColorButton* col = new ColorButton(def, attr, tip_text);
766         add_widget(col, label);
767         add_attr_widget(col);
768         return col;
769     }
771     // Matrix
772     MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text)
773     {
774         MatrixAttr* conv = new MatrixAttr(attr, tip_text);
775         add_widget(conv, label);
776         add_attr_widget(conv);
777         return conv;
778     }
780     // ColorMatrixValues
781     ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
782     {
783         ColorMatrixValues* cmv = new ColorMatrixValues();
784         add_widget(cmv, label);
785         add_attr_widget(cmv);
786         return cmv;
787     }
789     // SpinSlider
790     SpinSlider* add_spinslider(double def, const SPAttributeEnum attr, const Glib::ustring& label,
791                          const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
792     {
793         SpinSlider* spinslider = new SpinSlider(def, lo, hi, step_inc, climb, digits, attr, tip_text);
794         add_widget(spinslider, label);
795         add_attr_widget(spinslider);
796         return spinslider;
797     }
799     // DualSpinSlider
800     DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
801                                        const double lo, const double hi, const double step_inc,
802                                        const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
803     {
804         DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
805         add_widget(dss, label);
806         add_attr_widget(dss);
807         return dss;
808     }
810     // DualSpinButton
811     DualSpinButton* add_dualspinbutton(char* defalt_value, const SPAttributeEnum attr, const Glib::ustring& label,
812                                        const double lo, const double hi, const double step_inc,
813                                        const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
814     {
815         DualSpinButton* dsb = new DualSpinButton(defalt_value, lo, hi, step_inc, climb, digits, attr, tip1, tip2);
816         add_widget(dsb, label);
817         add_attr_widget(dsb);
818         return dsb;
819     }
821     // MultiSpinButton
822     MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
823                                          const Glib::ustring& label, const double lo, const double hi,
824                                          const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
825     {
826         std::vector<SPAttributeEnum> attrs;
827         attrs.push_back(attr1);
828         attrs.push_back(attr2);
830         std::vector<double> default_values;
831         default_values.push_back(def1);
832         default_values.push_back(def2);
834         std::vector<char*> tips;
835         tips.push_back(tip1);
836         tips.push_back(tip2);
838         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
839         add_widget(msb, label);
840         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
841             add_attr_widget(msb->get_spinbuttons()[i]);
842         return msb;
843     }
844     MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
845                                          const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
846                                          const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
847     {
848         std::vector<SPAttributeEnum> attrs;
849         attrs.push_back(attr1);
850         attrs.push_back(attr2);
851         attrs.push_back(attr3);
853         std::vector<double> default_values;
854         default_values.push_back(def1);
855         default_values.push_back(def2);
856         default_values.push_back(def3);
858         std::vector<char*> tips;
859         tips.push_back(tip1);
860         tips.push_back(tip2);
861         tips.push_back(tip3);
863         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
864         add_widget(msb, label);
865         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
866             add_attr_widget(msb->get_spinbuttons()[i]);
867         return msb;
868     }
870     // FileOrElementChooser
871     FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
872     {
873         FileOrElementChooser* foech = new FileOrElementChooser(attr);
874         foech->set_desktop(_dialog.getDesktop());
875         add_widget(foech, label);
876         add_attr_widget(foech);
877         return foech;
878     }
880     // ComboBoxEnum
881     template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
882                                   const Glib::ustring& label,
883                                   const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
884     {
885         ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
886         add_widget(combo, label);
887         add_attr_widget(combo->get_attrwidget());
888         return combo->get_attrwidget();
889     }
890 private:
891     Gtk::Tooltips _tt;
893     void add_attr_widget(AttrWidget* a)
894     {
895         _attrwidgets[_current_type].push_back(a);
896         a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
897     }
899     /* Adds a new settings widget using the specified label. The label will be formatted with a colon
900        and all widgets within the setting group are aligned automatically. */
901     void add_widget(Gtk::Widget* w, const Glib::ustring& label)
902     {
903         Gtk::Label *lbl = 0;
904         Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
905         hb->set_spacing(12);
907         if(label != "") {
908             lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
909             hb->pack_start(*lbl, false, false);
910             _size_group->add_widget(*lbl);
911             lbl->show();
912         }
914         hb->pack_start(*w);
915         _groups[_current_type]->pack_start(*hb);
916         hb->show();
917         w->show();
918     }
920     std::vector<Gtk::VBox*> _groups;
921     Glib::RefPtr<Gtk::SizeGroup> _size_group;
922     FilterEffectsDialog& _dialog;
923     SetAttrSlot _set_attr_slot;
924     std::vector<std::vector< AttrWidget*> > _attrwidgets;
925     int _current_type, _max_types;
926 };
928 // Settings for the three light source objects
929 class FilterEffectsDialog::LightSourceControl : public AttrWidget
931 public:
932     LightSourceControl(FilterEffectsDialog& d)
933         : AttrWidget(SP_ATTR_INVALID),
934           _dialog(d),
935           _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
936           _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
937           _light_source(LightSourceConverter),
938           _locked(false)
939     {
940         _light_box.pack_start(_light_label, false, false);
941         _light_box.pack_start(_light_source);
942         _light_box.show_all();
943         _light_box.set_spacing(12);
944         _dialog._sizegroup->add_widget(_light_label);
946         _box.add(_light_box);
947         _box.reorder_child(_light_box, 0);
948         _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
950         // FIXME: these range values are complete crap
952         _settings.type(LIGHT_DISTANT);
953         _settings.add_spinslider(0, SP_ATTR_AZIMUTH, _("Azimuth"), 0, 360, 1, 1, 0, _("Direction angle for the light source on the XY plane, in degrees"));
954         _settings.add_spinslider(0, SP_ATTR_ELEVATION, _("Elevation"), 0, 360, 1, 1, 0, _("Direction angle for the light source on the YZ plane, in degrees"));
956         _settings.type(LIGHT_POINT);
957         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0, SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
959         _settings.type(LIGHT_SPOT);
960         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0, SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
961         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
962                                       SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
963                                       _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
964         _settings.add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
965         //TODO: here I have used 100 degrees as default value. But spec says that if not specified, no limiting cone is applied. So, there should be a way for the user to set a "no limiting cone" option.
966         _settings.add_spinslider(100, SP_ATTR_LIMITINGCONEANGLE, _("Cone Angle"), 1, 100, 1, 1, 0, _("This is the angle between the spot light axis (i.e. the axis between the light source and the point to which it is pointing at) and the spot light cone. No light is projected outside this cone."));
967     }
969     Gtk::VBox& get_box()
970     {
971         return _box;
972     }
973 protected:
974     Glib::ustring get_as_attribute() const
975     {
976         return "";
977     }
978     void set_from_attribute(SPObject* o)
979     {
980         if(_locked)
981             return;
983         _locked = true;
985         SPObject* child = o->children;
987         if(SP_IS_FEDISTANTLIGHT(child))
988             _light_source.set_active(0);
989         else if(SP_IS_FEPOINTLIGHT(child))
990             _light_source.set_active(1);
991         else if(SP_IS_FESPOTLIGHT(child))
992             _light_source.set_active(2);
993         else
994             _light_source.set_active(-1);
996         update();
998         _locked = false;
999     }
1000 private:
1001     void on_source_changed()
1002     {
1003         if(_locked)
1004             return;
1006         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1007         if(prim) {
1008             _locked = true;
1010             SPObject* child = prim->children;
1011             const int ls = _light_source.get_active_row_number();
1012             // Check if the light source type has changed
1013             if(!(ls == -1 && !child) &&
1014                !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1015                !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1016                !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1017                 if(child)
1018                     sp_repr_unparent(child->repr);
1020                 if(ls != -1) {
1021                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1022                     Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1023                     prim->repr->appendChild(repr);
1024                     Inkscape::GC::release(repr);
1025                 }
1027                 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1028                 update();
1029             }
1031             _locked = false;
1032         }
1033     }
1035     void update()
1036     {
1037         _box.hide_all();
1038         _box.show();
1039         _light_box.show_all();
1041         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1042         if(prim && prim->children)
1043             _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1044     }
1046     FilterEffectsDialog& _dialog;
1047     Gtk::VBox _box;
1048     Settings _settings;
1049     Gtk::HBox _light_box;
1050     Gtk::Label _light_label;
1051     ComboBoxEnum<LightSource> _light_source;
1052     bool _locked;
1053 };
1055 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1057     LightSourceControl* ls = new LightSourceControl(_dialog);
1058     add_attr_widget(ls);
1059     add_widget(&ls->get_box(), "");
1060     return ls;
1063 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1064                                           sigc::slot<void> rem)
1066     Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1068     menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1069     Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1070     menu->append(*mi);
1071     mi->signal_activate().connect(rem);
1072     mi->show();
1073     menu->accelerate(parent);
1075     return menu;
1078 /*** FilterModifier ***/
1079 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1080     : _dialog(d), _add(Gtk::Stock::NEW), _observer(new Inkscape::XML::SignalObserver)
1082     Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1083     pack_start(*sw);
1084     pack_start(_add, false, false);
1085     sw->add(_list);
1087     _model = Gtk::ListStore::create(_columns);
1088     _list.set_model(_model);
1089     _cell_toggle.set_active(true);
1090     const int selcol = _list.append_column("", _cell_toggle);
1091     Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1092     if(col)
1093        col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1094     _list.append_column_editable(_("_Filter"), _columns.label);
1095     ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1096         signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1098     sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1099     sw->set_shadow_type(Gtk::SHADOW_IN);
1100     show_all_children();
1101     _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1102     _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1103     _list.signal_button_release_event().connect_notify(
1104         sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1105     _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1106                               sigc::mem_fun(*this, &FilterModifier::remove_filter));
1107     _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1108                                  _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1109     _menu->accelerate(*this);
1111     _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1112     _observer->signal_changed().connect(signal_filter_changed().make_slot());
1113     g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1114                      G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1116     g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1117                      G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1118     g_signal_connect(G_OBJECT(INKSCAPE), "deactivate_desktop",
1119                      G_CALLBACK(&FilterModifier::on_deactivate_desktop), this);
1121     on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1122     update_filters();
1125 FilterEffectsDialog::FilterModifier::~FilterModifier()
1127    _resource_changed.disconnect();
1128    _doc_replaced.disconnect();
1131 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1133     me->_doc_replaced.disconnect();
1134     me->_doc_replaced = desktop->connectDocumentReplaced(
1135         sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1137     me->_resource_changed.disconnect();
1138     me->_resource_changed =
1139         sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1140                                               sigc::mem_fun(me, &FilterModifier::update_filters));
1142     me->_dialog.setDesktop(desktop);
1144     me->update_filters();
1147 void FilterEffectsDialog::FilterModifier::on_deactivate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1149     me->_doc_replaced.disconnect();
1150     me->_resource_changed.disconnect();
1151     me->_dialog.setDesktop(NULL);
1155 // When the selection changes, show the active filter(s) in the dialog
1156 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1157                                                                        Selection *sel,
1158                                                                        FilterModifier* fm)
1160     if(fm && sel)
1161         fm->update_selection(sel);
1164 // Update each filter's sel property based on the current object selection;
1165 //  If the filter is not used by any selected object, sel = 0,
1166 //  otherwise sel is set to the total number of filters in use by selected objects
1167 //  If only one filter is in use, it is selected
1168 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1170     std::set<SPObject*> used;
1172     for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1173         SPObject *obj = SP_OBJECT (i->data);
1174         SPStyle *style = SP_OBJECT_STYLE (obj);
1175         if(!style || !SP_IS_ITEM(obj)) continue;
1177         if(style->filter.set && style->getFilter())
1178             used.insert(style->getFilter());
1179         else
1180             used.insert(0);
1181     }
1183     const int size = used.size();
1185     for(Gtk::TreeIter iter = _model->children().begin();
1186         iter != _model->children().end(); ++iter) {
1187         if(used.find((*iter)[_columns.filter]) != used.end()) {
1188             // If only one filter is in use by the selection, select it
1189             if(size == 1)
1190                 _list.get_selection()->select(iter);
1191             (*iter)[_columns.sel] = size;
1192         }
1193         else
1194             (*iter)[_columns.sel] = 0;
1195     }
1198 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1200     _observer->set(get_selected_filter());
1201     signal_filter_changed()();
1204 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1206     Gtk::TreeModel::iterator iter = _model->get_iter(path);
1208     if(iter) {
1209         SPFilter* filter = (*iter)[_columns.filter];
1210         filter->setLabel(text.c_str());
1211         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1212         if(iter)
1213             (*iter)[_columns.label] = text;
1214     }
1217 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1219     Gtk::TreeIter iter = _model->get_iter(path);
1221     if(iter) {
1222         SPDesktop *desktop = _dialog.getDesktop();
1223         SPDocument *doc = sp_desktop_document(desktop);
1224         SPFilter* filter = (*iter)[_columns.filter];
1225         Inkscape::Selection *sel = sp_desktop_selection(desktop);
1227         /* If this filter is the only one used in the selection, unset it */
1228         if((*iter)[_columns.sel] == 1)
1229             filter = 0;
1231         GSList const *items = sel->itemList();
1233         for (GSList const *i = items; i != NULL; i = i->next) {
1234             SPItem * item = SP_ITEM(i->data);
1235             SPStyle *style = SP_OBJECT_STYLE(item);
1236             g_assert(style != NULL);
1238             if(filter)
1239                 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1240             else
1241                 ::remove_filter(item, false);
1243             SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1244         }
1246         update_selection(sel);
1247         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS,  _("Apply filter"));
1248     }
1251 /* Add all filters in the document to the combobox.
1252    Keeps the same selection if possible, otherwise selects the first element */
1253 void FilterEffectsDialog::FilterModifier::update_filters()
1255     SPDesktop* desktop = _dialog.getDesktop();
1256     SPDocument* document = sp_desktop_document(desktop);
1257     const GSList* filters = sp_document_get_resource_list(document, "filter");
1259     _model->clear();
1261     for(const GSList *l = filters; l; l = l->next) {
1262         Gtk::TreeModel::Row row = *_model->append();
1263         SPFilter* f = (SPFilter*)l->data;
1264         row[_columns.filter] = f;
1265         const gchar* lbl = f->label();
1266         const gchar* id = SP_OBJECT_ID(f);
1267         row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1268     }
1270     update_selection(desktop->selection);
1271     _dialog.update_filter_general_settings_view();
1274 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1276     if(_list.get_selection()) {
1277         Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1279         if(i)
1280             return (*i)[_columns.filter];
1281     }
1283     return 0;
1286 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1288     if(filter) {
1289         for(Gtk::TreeModel::iterator i = _model->children().begin();
1290             i != _model->children().end(); ++i) {
1291             if((*i)[_columns.filter] == filter) {
1292                 _list.get_selection()->select(i);
1293                 break;
1294             }
1295         }
1296     }
1299 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1301     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1302         const bool sensitive = get_selected_filter() != NULL;
1303         _menu->items()[0].set_sensitive(sensitive);
1304         _menu->items()[1].set_sensitive(sensitive);
1305         _menu->popup(event->button, event->time);
1306     }
1309 void FilterEffectsDialog::FilterModifier::add_filter()
1311     SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1312     SPFilter* filter = new_filter(doc);
1314     const int count = _model->children().size();
1315     std::ostringstream os;
1316     os << "filter" << count;
1317     filter->setLabel(os.str().c_str());
1319     update_filters();
1321     select_filter(filter);
1323     sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1326 void FilterEffectsDialog::FilterModifier::remove_filter()
1328     SPFilter *filter = get_selected_filter();
1330     if(filter) {
1331         SPDocument* doc = filter->document;
1332         sp_repr_unparent(filter->repr);
1334         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1336         update_filters();
1337     }
1340 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1342     SPFilter* filter = get_selected_filter();
1344     if(filter) {
1345         Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1346         repr = repr->duplicate(repr->document());
1347         parent->appendChild(repr);
1349         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1351         update_filters();
1352     }
1355 void FilterEffectsDialog::FilterModifier::rename_filter()
1357     _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1360 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1361     : Glib::ObjectBase(typeid(CellRendererConnection)),
1362       _primitive(*this, "primitive", 0)
1363 {}
1365 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1367     return _primitive.get_proxy();
1370 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1372     _text_width = w;
1375 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1377     return _text_width;
1380 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1381     Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1382     int* x_offset, int* y_offset, int* width, int* height) const
1384     PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1386     if(x_offset)
1387         (*x_offset) = 0;
1388     if(y_offset)
1389         (*y_offset) = 0;
1390     if(width)
1391         (*width) = size * primlist.primitive_count() + _text_width * 7;
1392     if(height) {
1393         // Scale the height depending on the number of inputs, unless it's
1394         // the first primitive, in which case there are no connections
1395         SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1396         (*height) = size * input_count(prim);
1397     }
1400 /*** PrimitiveList ***/
1401 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1402     : _dialog(d),
1403       _in_drag(0),
1404       _observer(new Inkscape::XML::SignalObserver)
1406     d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1408     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1409     signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1411     _model = Gtk::ListStore::create(_columns);
1413     set_reorderable(true);
1415     set_model(_model);
1416     append_column(_("_Effect"), _columns.type);
1418     _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1419     get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1420     signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1422     _connection_cell.set_text_width(init_text());
1424     int cols_count = append_column(_("Connections"), _connection_cell);
1425     Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1426     if(col)
1427        col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1430 // Sets up a vertical Pango context/layout, and returns the largest
1431 // width needed to render the FilterPrimitiveInput labels.
1432 int FilterEffectsDialog::PrimitiveList::init_text()
1434     // Set up a vertical context+layout
1435     Glib::RefPtr<Pango::Context> context = create_pango_context();
1436     const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1437     context->set_matrix(matrix);
1438     _vertical_layout = Pango::Layout::create(context);
1440     int maxfont = 0;
1441     for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1442         _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1443         int fontw, fonth;
1444         _vertical_layout->get_pixel_size(fontw, fonth);
1445         if(fonth > maxfont)
1446             maxfont = fonth;
1447     }
1449     return maxfont;
1452 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1454     return _signal_primitive_changed;
1457 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1459     _observer->set(get_selected());
1460     signal_primitive_changed()();
1461     _dialog._color_matrix_values->clear_store();
1464 /* Add all filter primitives in the current to the list.
1465    Keeps the same selection if possible, otherwise selects the first element */
1466 void FilterEffectsDialog::PrimitiveList::update()
1468     SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1469     const SPFilterPrimitive* active_prim = get_selected();
1470     bool active_found = false;
1472     _model->clear();
1474     if(f) {
1475         _dialog._primitive_box.set_sensitive(true);
1476         _dialog.update_filter_general_settings_view();
1477         for(SPObject *prim_obj = f->children;
1478                 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1479                 prim_obj = prim_obj->next) {
1480             SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1481             if(prim) {
1482                 Gtk::TreeModel::Row row = *_model->append();
1483                 row[_columns.primitive] = prim;
1484                 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1485                 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1486                 row[_columns.id] = SP_OBJECT_ID(prim);
1488                 if(prim == active_prim) {
1489                     get_selection()->select(row);
1490                     active_found = true;
1491                 }
1492             }
1493         }
1495         if(!active_found && _model->children().begin())
1496             get_selection()->select(_model->children().begin());
1498         columns_autosize();
1499     }
1500     else {
1501         _dialog._primitive_box.set_sensitive(false);
1502     }
1505 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1507     _primitive_menu = menu;
1510 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1512     if(_dialog._filter_modifier.get_selected_filter()) {
1513         Gtk::TreeModel::iterator i = get_selection()->get_selected();
1514         if(i)
1515             return (*i)[_columns.primitive];
1516     }
1518     return 0;
1521 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1523     for(Gtk::TreeIter i = _model->children().begin();
1524         i != _model->children().end(); ++i) {
1525         if((*i)[_columns.primitive] == prim)
1526             get_selection()->select(i);
1527     }
1530 void FilterEffectsDialog::PrimitiveList::remove_selected()
1532     SPFilterPrimitive* prim = get_selected();
1534     if(prim) {
1535         _observer->set(0);
1537         sp_repr_unparent(prim->repr);
1539         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1540                          _("Remove filter primitive"));
1542         update();
1543     }
1546 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1548     Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1549     Glib::RefPtr<Gdk::Window> win = get_bin_window();
1550     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1552     SPFilterPrimitive* prim = get_selected();
1553     int row_count = get_model()->children().size();
1555     int fheight = CellRendererConnection::size;
1556     Gdk::Rectangle rct, vis;
1557     Gtk::TreeIter row = get_model()->children().begin();
1558     int text_start_x = 0;
1559     if(row) {
1560         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1561         get_visible_rect(vis);
1562         int vis_x, vis_y;
1563         tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1565         text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter._length + 1) + 1;
1566         for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1567             _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1568             const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1569             get_bin_window()->draw_rectangle(get_style()->get_bg_gc(Gtk::STATE_NORMAL), true, x, vis_y, _connection_cell.get_text_width(), vis.get_height());
1570             get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1571             get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1572         }
1573     }
1575     int row_index = 0;
1576     for(; row != get_model()->children().end(); ++row, ++row_index) {
1577         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1578         const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1580         // Check mouse state
1581         int mx, my;
1582         Gdk::ModifierType mask;
1583         get_bin_window()->get_pointer(mx, my, mask);
1585         // Outline the bottom of the connection area
1586         const int outline_x = x + fheight * (row_count - row_index);
1587         get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1589         // Side outline
1590         get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1592         std::vector<Gdk::Point> con_poly;
1593         int con_drag_y = 0;
1594         bool inside;
1595         const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1596         const int inputs = input_count(row_prim);
1598         if(SP_IS_FEMERGE(row_prim)) {
1599             for(int i = 0; i < inputs; ++i) {
1600                 inside = do_connection_node(row, i, con_poly, mx, my);
1601                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1602                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1603                                                inside, con_poly);
1605                 if(_in_drag == (i + 1))
1606                     con_drag_y = con_poly[2].get_y();
1608                 if(_in_drag != (i + 1) || row_prim != prim)
1609                     draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1610             }
1611         }
1612         else {
1613             // Draw "in" shape
1614             inside = do_connection_node(row, 0, con_poly, mx, my);
1615             con_drag_y = con_poly[2].get_y();
1616             get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1617                                            darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1618                                            inside, con_poly);
1620             // Draw "in" connection
1621             if(_in_drag != 1 || row_prim != prim)
1622                 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1624             if(inputs == 2) {
1625                 // Draw "in2" shape
1626                 inside = do_connection_node(row, 1, con_poly, mx, my);
1627                 if(_in_drag == 2)
1628                     con_drag_y = con_poly[2].get_y();
1629                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1630                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1631                                                inside, con_poly);
1632                 // Draw "in2" connection
1633                 if(_in_drag != 2 || row_prim != prim)
1634                     draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1635             }
1636         }
1638         // Draw drag connection
1639         if(row_prim == prim && _in_drag) {
1640             get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1641                                         mx, con_drag_y);
1642             get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1643         }
1644     }
1646     return true;
1649 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1650                                                          const int text_start_x, const int x1, const int y1,
1651                                                          const int row_count)
1653     int src_id = 0;
1654     Gtk::TreeIter res = find_result(input, attr, src_id);
1655     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1656     Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1657     Glib::RefPtr<Gdk::GC> gc;
1659     const bool is_first = input == get_model()->children().begin();
1660     const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1661     const bool use_default = !res && !is_merge;
1663     if(res == input || (use_default && is_first)) {
1664         // Draw straight connection to a standard input
1665         // Draw a lighter line for an implicit connection to a standard input
1666         const int tw = _connection_cell.get_text_width();
1667         gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1668         gc = (use_default && is_first) ? lightgc : darkgc;
1669         get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1670         get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1671     }
1672     else {
1673         // Draw an 'L'-shaped connection to another filter primitive
1674         // If no connection is specified, draw a light connection to the previous primitive
1675         gc = use_default ? lightgc : darkgc;
1677         if(use_default) {
1678             res = input;
1679             --res;
1680         }
1682         if(res) {
1683             Gdk::Rectangle rct;
1685             get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1686             const int fheight = CellRendererConnection::size;
1688             get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1689             const int row_index = find_index(res);
1690             const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1691             const int y2 = rct.get_y() + rct.get_height();
1693             // Draw a bevelled 'L'-shaped connection
1694             get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1695             get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1696             get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1697         }
1698     }
1701 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1702 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1703                                                             std::vector<Gdk::Point>& points,
1704                                                             const int ix, const int iy)
1706     Gdk::Rectangle rct;
1707     const int icnt = input_count((*row)[_columns.primitive]);
1709     get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1710     const int fheight = CellRendererConnection::size;
1712     get_cell_area(_model->get_path(row), *get_column(1), rct);
1713     const float h = rct.get_height() / icnt;
1715     const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1716     const int con_w = (int)(fheight * 0.35f);
1717     const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1718     points.clear();
1719     points.push_back(Gdk::Point(x, con_y));
1720     points.push_back(Gdk::Point(x, con_y + con_w * 2));
1721     points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1723     return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1726 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1727                                                                     const int attr, int& src_id)
1729     SPFilterPrimitive* prim = (*start)[_columns.primitive];
1730     Gtk::TreeIter target = _model->children().end();
1731     int image = 0;
1733     if(SP_IS_FEMERGE(prim)) {
1734         int c = 0;
1735         bool found = false;
1736         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1737             if(c == attr && SP_IS_FEMERGENODE(o)) {
1738                 image = SP_FEMERGENODE(o)->input;
1739                 found = true;
1740             }
1741         }
1742         if(!found)
1743             return target;
1744     }
1745     else {
1746         if(attr == SP_ATTR_IN)
1747             image = prim->image_in;
1748         else if(attr == SP_ATTR_IN2) {
1749             if(SP_IS_FEBLEND(prim))
1750                 image = SP_FEBLEND(prim)->in2;
1751             else if(SP_IS_FECOMPOSITE(prim))
1752                 image = SP_FECOMPOSITE(prim)->in2;
1753             else if(SP_IS_FEDISPLACEMENTMAP(prim))
1754                 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1755             else
1756                 return target;
1757         }
1758         else
1759             return target;
1760     }
1762     if(image >= 0) {
1763         for(Gtk::TreeIter i = _model->children().begin();
1764             i != start; ++i) {
1765             if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1766                 target = i;
1767         }
1768         return target;
1769     }
1770     else if(image < -1) {
1771         src_id = -(image + 2);
1772         return start;
1773     }
1775     return target;
1778 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1780     int i = 0;
1781     for(Gtk::TreeIter iter = _model->children().begin();
1782         iter != target; ++iter, ++i){};
1783     return i;
1786 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1788     Gtk::TreePath path;
1789     Gtk::TreeViewColumn* col;
1790     const int x = (int)e->x, y = (int)e->y;
1791     int cx, cy;
1793     _drag_prim = 0;
1795     if(get_path_at_pos(x, y, path, col, cx, cy)) {
1796         Gtk::TreeIter iter = _model->get_iter(path);
1797         std::vector<Gdk::Point> points;
1799         _drag_prim = (*iter)[_columns.primitive];
1800         const int icnt = input_count(_drag_prim);
1802         for(int i = 0; i < icnt; ++i) {
1803             if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1804                 _in_drag = i + 1;
1805                 break;
1806             }
1807         }
1809         queue_draw();
1810     }
1812     if(_in_drag) {
1813         _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1814         _autoscroll = 0;
1815         get_selection()->select(path);
1816         return true;
1817     }
1818     else
1819         return Gtk::TreeView::on_button_press_event(e);
1822 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1824     const int speed = 10;
1825     const int limit = 15;
1827     Gdk::Rectangle vis;
1828     get_visible_rect(vis);
1829     int vis_x, vis_y;
1830     tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1831     const int top = vis_y + vis.get_height();
1833     // When autoscrolling during a connection drag, set the speed based on
1834     // where the mouse is in relation to the edges.
1835     if(e->y < vis_y)
1836         _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1837     else if(e->y < vis_y + limit)
1838         _autoscroll = -speed;
1839     else if(e->y > top)
1840         _autoscroll = (int)(speed + (e->y - top) / 5);
1841     else if(e->y > top - limit)
1842         _autoscroll = speed;
1843     else
1844         _autoscroll = 0;
1846     queue_draw();
1848     return Gtk::TreeView::on_motion_notify_event(e);
1851 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1853     SPFilterPrimitive *prim = get_selected(), *target;
1855     _scroll_connection.disconnect();
1857     if(_in_drag && prim) {
1858         Gtk::TreePath path;
1859         Gtk::TreeViewColumn* col;
1860         int cx, cy;
1862         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1863             const gchar *in_val = 0;
1864             Glib::ustring result;
1865             Gtk::TreeIter target_iter = _model->get_iter(path);
1866             target = (*target_iter)[_columns.primitive];
1867             col = get_column(1);
1869             Gdk::Rectangle rct;
1870             get_cell_area(path, *col, rct);
1871             const int twidth = _connection_cell.get_text_width();
1872             const int sources_x = rct.get_width() - twidth * FPInputConverter._length;
1873             if(cx > sources_x) {
1874                 int src = (cx - sources_x) / twidth;
1875                 if (src < 0) {
1876                     src = 0;
1877                 } else if(src >= static_cast<int>(FPInputConverter._length)) {
1878                     src = FPInputConverter._length - 1;
1879                 }
1880                 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1881                 in_val = result.c_str();
1882             }
1883             else {
1884                 // Ensure that the target comes before the selected primitive
1885                 for(Gtk::TreeIter iter = _model->children().begin();
1886                     iter != get_selection()->get_selected(); ++iter) {
1887                     if(iter == target_iter) {
1888                         Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1889                         // Make sure the target has a result
1890                         const gchar *gres = repr->attribute("result");
1891                         if(!gres) {
1892                             result = sp_filter_get_new_result_name(SP_FILTER(prim->parent));
1893                             repr->setAttribute("result", result.c_str());
1894                             in_val = result.c_str();
1895                         }
1896                         else
1897                             in_val = gres;
1898                         break;
1899                     }
1900                 }
1901             }
1903             if(SP_IS_FEMERGE(prim)) {
1904                 int c = 1;
1905                 bool handled = false;
1906                 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1907                     if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1908                         // If input is null, delete it
1909                         if(!in_val) {
1910                             sp_repr_unparent(o->repr);
1911                             sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1912                                              _("Remove merge node"));
1913                             (*get_selection()->get_selected())[_columns.primitive] = prim;
1914                         }
1915                         else
1916                             _dialog.set_attr(o, SP_ATTR_IN, in_val);
1917                         handled = true;
1918                     }
1919                 }
1920                 // Add new input?
1921                 if(!handled && c == _in_drag && in_val) {
1922                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1923                     Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1924                     repr->setAttribute("inkscape:collect", "always");
1925                     prim->repr->appendChild(repr);
1926                     SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1927                     Inkscape::GC::release(repr);
1928                     _dialog.set_attr(node, SP_ATTR_IN, in_val);
1929                     (*get_selection()->get_selected())[_columns.primitive] = prim;
1930                 }
1931             }
1932             else {
1933                 if(_in_drag == 1)
1934                     _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1935                 else if(_in_drag == 2)
1936                     _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1937             }
1938         }
1940         _in_drag = 0;
1941         queue_draw();
1943         _dialog.update_settings_view();
1944     }
1946     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1947         const bool sensitive = get_selected() != NULL;
1948         _primitive_menu->items()[0].set_sensitive(sensitive);
1949         _primitive_menu->items()[1].set_sensitive(sensitive);
1950         _primitive_menu->popup(e->button, e->time);
1952         return true;
1953     }
1954     else
1955         return Gtk::TreeView::on_button_release_event(e);
1958 // Checks all of prim's inputs, removes any that use result
1959 void check_single_connection(SPFilterPrimitive* prim, const int result)
1961     if(prim && result >= 0) {
1963         if(prim->image_in == result)
1964             SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1966         if(SP_IS_FEBLEND(prim)) {
1967             if(SP_FEBLEND(prim)->in2 == result)
1968                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1969         }
1970         else if(SP_IS_FECOMPOSITE(prim)) {
1971             if(SP_FECOMPOSITE(prim)->in2 == result)
1972                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1973         }
1974         else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
1975             if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
1976                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1977         }
1978     }
1981 // Remove any connections going to/from prim_iter that forward-reference other primitives
1982 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
1984     SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
1985     bool before = true;
1987     for(Gtk::TreeIter iter = _model->children().begin();
1988         iter != _model->children().end(); ++iter) {
1989         if(iter == prim_iter)
1990             before = false;
1991         else {
1992             SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
1993             if(before)
1994                 check_single_connection(cur_prim, prim->image_out);
1995             else
1996                 check_single_connection(prim, cur_prim->image_out);
1997         }
1998     }
2001 // Reorder the filter primitives to match the list order
2002 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2004     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2005     int ndx = 0;
2007     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2008         iter != _model->children().end(); ++iter, ++ndx) {
2009         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2010         if(prim && prim == _drag_prim) {
2011             SP_OBJECT_REPR(prim)->setPosition(ndx);
2012             break;
2013         }
2014     }
2016     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2017         iter != _model->children().end(); ++iter, ++ndx) {
2018         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2019         if(prim && prim == _drag_prim) {
2020             sanitize_connections(iter);
2021             get_selection()->select(iter);
2022             break;
2023         }
2024     }
2026     filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2028     sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2031 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2032 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2034     if(_autoscroll) {
2035         Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2036         double v;
2038         v = a.get_value() + _autoscroll;
2039         if(v < 0)
2040             v = 0;
2041         if(v > a.get_upper() - a.get_page_size())
2042             v = a.get_upper() - a.get_page_size();
2044         a.set_value(v);
2046         queue_draw();
2047     }
2049     return true;
2052 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2054     return _model->children().size();
2057 /*** FilterEffectsDialog ***/
2059 FilterEffectsDialog::FilterEffectsDialog()
2060     : UI::Widget::Panel("", "/dialogs/filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2061       _add_primitive_type(FPConverter),
2062       _add_primitive(_("Add Effect:")),
2063       _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2064       _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2065       _settings_initialized(false),
2066       _locked(false),
2067       _attr_lock(false),
2068       _filter_modifier(*this),
2069       _primitive_list(*this)
2071     _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2072                              NR_FILTER_ENDPRIMITIVETYPE);
2073     _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2074                              1);
2075     _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2076     _sizegroup->set_ignore_hidden();
2078     _add_primitive_type.remove_row(NR_FILTER_TILE);
2079     _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2081     // Initialize widget hierarchy
2082     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2083     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2084     Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox(/*homogeneous:*/false, /*spacing:*/4));
2085     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2087     _getContents()->add(*hpaned);
2088     hpaned->pack1(_filter_modifier);
2089     hpaned->pack2(_primitive_box);
2090     _primitive_box.pack_start(*sw_prims);
2091     _primitive_box.pack_start(*hb_prims, false, false);
2092     _primitive_box.pack_start(*infobox,false, false);
2093     sw_prims->add(_primitive_list);
2094     infobox->pack_start(_infobox_icon, false, false);
2095     infobox->pack_start(_infobox_desc, false, false);
2096     _infobox_desc.set_line_wrap(true);
2097     _infobox_desc.set_size_request(200, -1);
2099     hb_prims->pack_start(_add_primitive, false, false);
2100     hb_prims->pack_start(_add_primitive_type, false, false);
2101     _getContents()->pack_start(_settings_tabs, false, false);
2102     _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2103     _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2105     _primitive_list.signal_primitive_changed().connect(
2106         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2107     _filter_modifier.signal_filter_changed().connect(
2108         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2110     _add_primitive_type.signal_changed().connect(
2111         sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2113     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2114     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2115 //    al_settings->set_padding(0, 0, 12, 0);
2116 //    fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2117 //    ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2118     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2119     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2120                                                sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2122     show_all_children();
2123     init_settings_widgets();
2124     _primitive_list.update();
2125     update_primitive_infobox();
2128 FilterEffectsDialog::~FilterEffectsDialog()
2130     delete _settings;
2131     delete _filter_general_settings;
2134 void FilterEffectsDialog::set_attrs_locked(const bool l)
2136     _locked = l;
2139 void FilterEffectsDialog::show_all_vfunc()
2141     UI::Widget::Panel::show_all_vfunc();
2143     update_settings_view();
2146 void FilterEffectsDialog::init_settings_widgets()
2148     // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2149     //       most of the current values are complete guesses!
2151     _empty_settings.set_sensitive(false);
2152     _settings_tab1.pack_start(_empty_settings);
2154     _no_filter_selected.set_sensitive(false);
2155     _settings_tab2.pack_start(_no_filter_selected);
2156     _settings_initialized = true;
2158     _filter_general_settings->type(0);
2159     _filter_general_settings->add_multispinbutton(/*default x:*/ (double) -0.1, /*default y:*/ (double) -0.1, SP_ATTR_X, SP_ATTR_Y, _("Coordinates"), -100, 100, 0.01, 0.1, 2, _("X coordinate of the left corners of filter effects region"), _("Y coordinate of the upper corners of filter effects region"));
2160     _filter_general_settings->add_multispinbutton(/*default width:*/ (double) 1.2, /*default height:*/ (double) 1.2, SP_ATTR_WIDTH, SP_ATTR_HEIGHT, _("Dimensions"), 0, 1000, 0.01, 0.1, 2, _("Width of filter effects region"), _("Height of filter effects region"));
2162     _settings->type(NR_FILTER_BLEND);
2163     _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode"), BlendModeConverter);
2165     _settings->type(NR_FILTER_COLORMATRIX);
2166     ComboBoxEnum<FilterColorMatrixType>* colmat = _settings->add_combo(COLORMATRIX_MATRIX, SP_ATTR_TYPE, _("Type"), ColorMatrixTypeConverter, _("Indicates the type of matrix operation. The keyword 'matrix' indicates that a full 5x4 matrix of values will be provided. The other keywords represent convenience shortcuts to allow commonly used color operations to be performed without specifying a complete matrix."));
2167     _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s)"));
2168     colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2170     _settings->type(NR_FILTER_COMPONENTTRANSFER);
2171     _settings->add_notimplemented();
2172     //TRANSLATORS: for info on "Slope" and "Intercept", see http://id.mind.net/~zona/mmts/functionInstitute/linearFunctions/lsif.html
2173     /*_settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2174     _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2175     _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2176     _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2177     _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2178     _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2180     _settings->type(NR_FILTER_COMPOSITE);
2181     _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
2182     _k1 = _settings->add_spinslider(0, SP_ATTR_K1, _("K1"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2183     _k2 = _settings->add_spinslider(0, SP_ATTR_K2, _("K2"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2184     _k3 = _settings->add_spinslider(0, SP_ATTR_K3, _("K3"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2185     _k4 = _settings->add_spinslider(0, SP_ATTR_K4, _("K4"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2187     _settings->type(NR_FILTER_CONVOLVEMATRIX);
2188     _convolve_order = _settings->add_dualspinbutton((char*)"3", SP_ATTR_ORDER, _("Size"), 1, 5, 1, 1, 0, _("width of the convolve matrix"), _("height of the convolve matrix"));
2189     _convolve_target = _settings->add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, SP_ATTR_TARGETX, SP_ATTR_TARGETY, _("Target"), 0, 4, 1, 1, 0, _("X coordinate of the target point in the convolve matrix. The convolution is applied to pixels around this point."), _("Y coordinate of the target point in the convolve matrix. The convolution is applied to pixels around this point."));
2190     //TRANSLATORS: for info on "Kernel", see http://en.wikipedia.org/wiki/Kernel_(matrix)
2191     _convolve_matrix = _settings->add_matrix(SP_ATTR_KERNELMATRIX, _("Kernel"), _("This matrix describes the convolve operation that is applied to the input image in order to calculate the pixel colors at the output. Different arrangements of values in this matrix result in various possible visual effects. An identity matrix would lead to a motion blur effect (parallel to the matrix diagonal) while a matrix filled with a constant non-zero value would lead to a common blur effect."));
2192     _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2193     //TODO: svg spec: The default value is the sum of all values in kernelMatrix, with the exception that if the sum is zero, then the divisor is set to 1.
2194     _settings->add_spinslider(1, SP_ATTR_DIVISOR, _("Divisor"), 1, 20, 1, 0.1, 2, _("After applying the kernelMatrix to the input image to yield a number, that number is divided by divisor to yield the final destination color value. A divisor that is the sum of all the matrix values tends to have an evening effect on the overall color intensity of the result."));
2195     _settings->add_spinslider(0, SP_ATTR_BIAS, _("Bias"), -10, 10, 1, 0.01, 1, _("This value is added to each component. This is useful to define a constant value as the zero response of the filter."));
2196     _settings->add_combo(CONVOLVEMATRIX_EDGEMODE_DUPLICATE, SP_ATTR_EDGEMODE, _("Edge Mode"), ConvolveMatrixEdgeModeConverter, _("Determines how to extend the input image as necessary with color values so that the matrix operations can be applied when the kernel is positioned at or near the edge of the input image."));
2197     _settings->add_checkbutton(false, SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive."));
2199     _settings->type(NR_FILTER_DIFFUSELIGHTING);
2200     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Diffuse Color"), _("Defines the color of the light source"));
2201     _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2202     _settings->add_spinslider(1, SP_ATTR_DIFFUSECONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2203     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2204     _settings->add_lightsource();
2206     _settings->type(NR_FILTER_DISPLACEMENTMAP);
2207     _settings->add_spinslider(0, SP_ATTR_SCALE, _("Scale"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2208     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2209     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2211     _settings->type(NR_FILTER_FLOOD);
2212     _settings->add_color(/*default: black*/ 0, SP_PROP_FLOOD_COLOR, _("Flood Color"), _("The whole filter region will be filled with this color."));
2213     _settings->add_spinslider(1, SP_PROP_FLOOD_OPACITY, _("Opacity"), 0, 1, 0.1, 0.01, 2);
2215     _settings->type(NR_FILTER_GAUSSIANBLUR);
2216     _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2218     _settings->type(NR_FILTER_MERGE);
2219     _settings->add_no_params();
2221     _settings->type(NR_FILTER_MORPHOLOGY);
2222     _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2223     _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius"), 0, 100, 1, 0.01, 1);
2225     _settings->type(NR_FILTER_IMAGE);
2226     _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image"));
2228     _settings->type(NR_FILTER_OFFSET);
2229     _settings->add_spinslider(0, SP_ATTR_DX, _("Delta X"), -100, 100, 1, 0.01, 1, _("This is how far the input image gets shifted to the right"));
2230     _settings->add_spinslider(0, SP_ATTR_DY, _("Delta Y"), -100, 100, 1, 0.01, 1, _("This is how far the input image gets shifted downwards"));
2232     _settings->type(NR_FILTER_SPECULARLIGHTING);
2233     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Specular Color"), _("Defines the color of the light source"));
2234     _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2235     _settings->add_spinslider(1, SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2236     _settings->add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2237     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2238     _settings->add_lightsource();
2240     _settings->type(NR_FILTER_TILE);
2241     _settings->add_notimplemented();
2243     _settings->type(NR_FILTER_TURBULENCE);
2244 //    _settings->add_checkbutton(false, SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2245     _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2246     _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency"), 0, 0.4, 0.001, 0.01, 3);
2247     _settings->add_spinslider(1, SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2248     _settings->add_spinslider(0, SP_ATTR_SEED, _("Seed"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2251 void FilterEffectsDialog::add_primitive()
2253     SPFilter* filter = _filter_modifier.get_selected_filter();
2255     if(filter) {
2256         SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2258         _primitive_list.select(prim);
2260         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2261     }
2264 void FilterEffectsDialog::update_primitive_infobox()
2266     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2267     if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2268         _infobox_icon.show();
2269         _infobox_desc.show();
2270     } else {
2271         _infobox_icon.hide();
2272         _infobox_desc.hide();
2273     }
2274     switch(_add_primitive_type.get_active_data()->id){
2275         case(NR_FILTER_BLEND):
2276             _infobox_icon.set(g_strdup_printf("%s/feBlend-icon.png", INKSCAPE_PIXMAPDIR));
2277             _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2278             break;
2279         case(NR_FILTER_COLORMATRIX):
2280             _infobox_icon.set(g_strdup_printf("%s/feColorMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2281             _infobox_desc.set_markup(_("The <b>feColorMatrix</b> filter primitive applies a matrix transformation to colour of each rendered pixel. This allows for effects like turning object to grayscale, modifying colour saturation and changing colour hue."));
2282             break;
2283         case(NR_FILTER_COMPONENTTRANSFER):
2284             _infobox_icon.set(g_strdup_printf("%s/feComponentTransfer-icon.png", INKSCAPE_PIXMAPDIR));
2285             _infobox_desc.set_markup(_("The <b>feComponentTransfer</b> filter primitive manipulates the input's color components (red, green, blue, and alpha) according to particular transfer functions, allowing operations like brightness and contrast adjustment, color balance, and thresholding."));
2286             break;
2287         case(NR_FILTER_COMPOSITE):
2288             _infobox_icon.set(g_strdup_printf("%s/feComposite-icon.png", INKSCAPE_PIXMAPDIR));
2289             _infobox_desc.set_markup(_("The <b>feComposite</b> filter primitive composites two images using one of the Porter-Duff blending modes or the aritmetic mode described in SVG standard. Porter-Duff blending modes are essentially logical operations between the corresponding pixel values of the images."));
2290             break;
2291         case(NR_FILTER_CONVOLVEMATRIX):
2292             _infobox_icon.set(g_strdup_printf("%s/feConvolveMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2293             _infobox_desc.set_markup(_("The <b>feConvolveMatrix</b> lets you specify a Convolution to be applied on the image. Common effects created using convolution matrices are blur, sharpening, embossing and edge detection. Note that while gaussian blur can be created using this filter primitive, the special gaussian blur primitive is faster and resolution-independent."));
2294             break;
2295         case(NR_FILTER_DIFFUSELIGHTING):
2296             _infobox_icon.set(g_strdup_printf("%s/feDiffuseLighting-icon.png", INKSCAPE_PIXMAPDIR));
2297             _infobox_desc.set_markup(_("The <b>feDiffuseLighting</b> and feSpecularLighting filter primitives create \"embossed\" shadings.  The input's alpha channel is used to provide depth information: higher opacity areas are raised toward the viewer and lower opacity areas recede away from the viewer."));
2298             break;
2299         case(NR_FILTER_DISPLACEMENTMAP):
2300             _infobox_icon.set(g_strdup_printf("%s/feDisplacementMap-icon.png", INKSCAPE_PIXMAPDIR));
2301             _infobox_desc.set_markup(_("The <b>feDisplacementMap</b> filter primitive displaces the pixels in the first input using the second input as a displacement map, that shows from how far the pixel should come from. Classical examples are whirl and pinch effects."));
2302             break;
2303         case(NR_FILTER_FLOOD):
2304             _infobox_icon.set(g_strdup_printf("%s/feFlood-icon.png", INKSCAPE_PIXMAPDIR));
2305             _infobox_desc.set_markup(_("The <b>feFlood</b> filter primitive fills the region with a given color and opacity.  It is usually used as an input to other filters to apply color to a graphic."));
2306             break;
2307         case(NR_FILTER_GAUSSIANBLUR):
2308             _infobox_icon.set(g_strdup_printf("%s/feGaussianBlur-icon.png", INKSCAPE_PIXMAPDIR));
2309             _infobox_desc.set_markup(_("The <b>feGaussianBlur</b> filter primitive uniformly blurs its input.  It is commonly used together with feOffset to create a drop shadow effect."));
2310             break;
2311         case(NR_FILTER_IMAGE):
2312             _infobox_icon.set(g_strdup_printf("%s/feImage-icon.png", INKSCAPE_PIXMAPDIR));
2313             _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2314             break;
2315         case(NR_FILTER_MERGE):
2316             _infobox_icon.set(g_strdup_printf("%s/feMerge-icon.png", INKSCAPE_PIXMAPDIR));
2317             _infobox_desc.set_markup(_("The <b>feMerge</b> filter primitive composites several temporary images inside the filter primitive to a single image. It uses normal alpha compositing for this. This is equivalent to using several feBlend primitives in 'normal' mode or several feComposite primitives in 'over' mode."));
2318             break;
2319         case(NR_FILTER_MORPHOLOGY):
2320             _infobox_icon.set(g_strdup_printf("%s/feMorphology-icon.png", INKSCAPE_PIXMAPDIR));
2321             _infobox_desc.set_markup(_("The <b>feMorphology</b> filter primitive provides erode and dilate effects. For single-colour objects erode makes the object thinner and dilate makes it thicker."));
2322             break;
2323         case(NR_FILTER_OFFSET):
2324             _infobox_icon.set(g_strdup_printf("%s/feOffset-icon.png", INKSCAPE_PIXMAPDIR));
2325             _infobox_desc.set_markup(_("The <b>feOffset</b> filter primitive offsets the image by an user-defined amount. For example, this is useful for drop shadows, where the shadow is in a slightly different position than the actual object."));
2326             break;
2327         case(NR_FILTER_SPECULARLIGHTING):
2328             _infobox_icon.set(g_strdup_printf("%s/feSpecularLighting-icon.png", INKSCAPE_PIXMAPDIR));
2329             _infobox_desc.set_markup(_("The feDiffuseLighting and <b>feSpecularLighting</b> filter primitives create \"embossed\" shadings.  The input's alpha channel is used to provide depth information: higher opacity areas are raised toward the viewer and lower opacity areas recede away from the viewer."));
2330             break;
2331         case(NR_FILTER_TILE):
2332             _infobox_icon.set(g_strdup_printf("%s/feTile-icon.png", INKSCAPE_PIXMAPDIR));
2333             _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2334             break;
2335         case(NR_FILTER_TURBULENCE):
2336             _infobox_icon.set(g_strdup_printf("%s/feTurbulence-icon.png", INKSCAPE_PIXMAPDIR));
2337             _infobox_desc.set_markup(_("The <b>feTurbulence</b> filter primitive renders Perlin noise. This kind of noise is useful in simulating several nature phenomena like clouds, fire and smoke and in generating complex textures like marble or granite."));
2338             break;
2339         default:
2340             g_assert(false);
2341             break;
2342     }
2345 void FilterEffectsDialog::duplicate_primitive()
2347     SPFilter* filter = _filter_modifier.get_selected_filter();
2348     SPFilterPrimitive* origprim = _primitive_list.get_selected();
2350     if(filter && origprim) {
2351         Inkscape::XML::Node *repr;
2352         repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2353         SP_OBJECT_REPR(filter)->appendChild(repr);
2355         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2357         _primitive_list.update();
2358     }
2361 void FilterEffectsDialog::convolve_order_changed()
2363     _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2364     _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2365     _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2368 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2370     set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2373 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2375     if(!_locked) {
2376         _attr_lock = true;
2377         SPFilter *filter = _filter_modifier.get_selected_filter();
2378         const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2379         if (filter && name && SP_OBJECT_REPR(filter)){
2380             SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2381             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2382         }
2383         _attr_lock = false;
2384     }
2387 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2389     set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2392 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2394     if(!_locked) {
2395         _attr_lock = true;
2397         SPFilter *filter = _filter_modifier.get_selected_filter();
2398         const gchar* name = (const gchar*)sp_attribute_name(attr);
2399         if(filter && name && o) {
2400             update_settings_sensitivity();
2402             SP_OBJECT_REPR(o)->setAttribute(name, val);
2403             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2405             Glib::ustring undokey = "filtereffects:";
2406             undokey += name;
2407             sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2408                                    _("Set filter primitive attribute"));
2409         }
2411         _attr_lock = false;
2412     }
2415 void FilterEffectsDialog::update_filter_general_settings_view()
2417     if(_settings_initialized != true) return;
2419     if(!_locked) {
2420         _attr_lock = true;
2422         SPFilter* filter = _filter_modifier.get_selected_filter();
2424         if(filter) {
2425             _filter_general_settings->show_and_update(0, filter);
2426             _no_filter_selected.hide();
2427         }
2428         else {
2429             std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2430             vect[0]->hide_all();
2431             _no_filter_selected.show();
2432         }
2434         _attr_lock = false;
2435     }
2438 void FilterEffectsDialog::update_settings_view()
2440     update_settings_sensitivity();
2442     if(_attr_lock)
2443         return;
2445 //First Tab
2447     std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2448     for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2449     _empty_settings.show();
2451     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2452     if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2453         _infobox_icon.show();
2454         _infobox_desc.show();
2455     } else {
2456         _infobox_icon.hide();
2457         _infobox_desc.hide();
2458     }
2460     SPFilterPrimitive* prim = _primitive_list.get_selected();
2462     if(prim) {
2463         _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2464         _empty_settings.hide();
2465     }
2467 //Second Tab
2469     std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2470     vect2[0]->hide_all();
2471     _no_filter_selected.show();
2473     SPFilter* filter = _filter_modifier.get_selected_filter();
2475     if(filter) {
2476         _filter_general_settings->show_and_update(0, filter);
2477         _no_filter_selected.hide();
2478     }
2482 void FilterEffectsDialog::update_settings_sensitivity()
2484     SPFilterPrimitive* prim = _primitive_list.get_selected();
2485     const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2486     _k1->set_sensitive(use_k);
2487     _k2->set_sensitive(use_k);
2488     _k3->set_sensitive(use_k);
2489     _k4->set_sensitive(use_k);
2491 // Component transfer not yet implemented
2492 /*
2493     if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2494         SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2495         const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2496         const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2498         _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2499         _ct_slope->set_sensitive(linear);
2500         _ct_intercept->set_sensitive(linear);
2501         _ct_amplitude->set_sensitive(gamma);
2502         _ct_exponent->set_sensitive(gamma);
2503         _ct_offset->set_sensitive(gamma);
2504     }
2505 */
2508 void FilterEffectsDialog::update_color_matrix()
2510     _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2513 } // namespace Dialog
2514 } // namespace UI
2515 } // namespace Inkscape
2517 /*
2518   Local Variables:
2519   mode:c++
2520   c-file-style:"stroustrup"
2521   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2522   indent-tabs-mode:nil
2523   fill-column:99
2524   End:
2525 */
2526 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :