Code

Prevent localized doubles from being written into filter matrices
[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 <juca@members.fsf.org>
8  *   Jon A. Cruz <jon@joncruz.org>
9  *   Abhishek Sharma
10  *
11  * Copyright (C) 2007 Authors
12  *
13  * Released under GNU GPL.  Read the file 'COPYING' for more information.
14  */
16 #ifdef HAVE_CONFIG_H
17 # include <config.h>
18 #endif
20 #include <gtk/gtk.h>
21 #include <gtkmm/cellrenderertext.h>
22 #include <gtkmm/colorbutton.h>
23 #include <gtkmm/messagedialog.h>
24 #include <gtkmm/paned.h>
25 #include <gtkmm/scale.h>
26 #include <gtkmm/scrolledwindow.h>
27 #include <gtkmm/spinbutton.h>
28 #include <gtkmm/stock.h>
29 #include <gtkmm/tooltips.h>
30 #include <glibmm/i18n.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 "svg/stringstream.h"
62 #include "ui/dialog/filedialog.h"
63 #include "verbs.h"
64 #include "xml/node.h"
65 #include "xml/node-observer.h"
66 #include "xml/repr.h"
67 #include <sstream>
69 #include "io/sys.h"
70 #include <iostream>
72 using namespace Inkscape::Filters;
74 namespace Inkscape {
75 namespace UI {
76 namespace Dialog {
78 using Inkscape::UI::Widget::AttrWidget;
79 using Inkscape::UI::Widget::ComboBoxEnum;
80 using Inkscape::UI::Widget::DualSpinSlider;
81 using Inkscape::UI::Widget::SpinSlider;
84 // Returns the number of inputs available for the filter primitive type
85 int input_count(const SPFilterPrimitive* prim)
86 {
87     if(!prim)
88         return 0;
89     else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
90         return 2;
91     else if(SP_IS_FEMERGE(prim)) {
92         // Return the number of feMergeNode connections plus an extra
93         int count = 1;
94         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count){};
95         return count;
96     }
97     else
98         return 1;
99 }
101 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
103 public:
104     CheckButtonAttr(bool def, const Glib::ustring& label,
105                     const Glib::ustring& tv, const Glib::ustring& fv,
106                     const SPAttributeEnum a, char* tip_text)
107         : Gtk::CheckButton(label),
108           AttrWidget(a, def),
109           _true_val(tv), _false_val(fv)
110     {
111         signal_toggled().connect(signal_attr_changed().make_slot());
112         if (tip_text) _tt.set_tip(*this, tip_text);
113     }
115     Glib::ustring get_as_attribute() const
116     {
117         return get_active() ? _true_val : _false_val;
118     }
120     void set_from_attribute(SPObject* o)
121     {
122         const gchar* val = attribute_value(o);
123         if(val) {
124             if(_true_val == val)
125                 set_active(true);
126             else if(_false_val == val)
127                 set_active(false);
128         } else {
129             set_active(get_default()->as_bool());
130         }
131     }
132 private:
133     const Glib::ustring _true_val, _false_val;
134 };
136 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
138 public:
139     SpinButtonAttr(double lower, double upper, double step_inc,
140                    double climb_rate, int digits, const SPAttributeEnum a, double def, char* tip_text)
141         : Gtk::SpinButton(climb_rate, digits),
142           AttrWidget(a, def)
143     {
144         if (tip_text) _tt.set_tip(*this, tip_text);
145         set_range(lower, upper);
146         set_increments(step_inc, 0);
148         signal_value_changed().connect(signal_attr_changed().make_slot());
149     }
151     Glib::ustring get_as_attribute() const
152     {
153         const double val = get_value();
155         if(get_digits() == 0)
156             return Glib::Ascii::dtostr((int)val);
157         else
158             return Glib::Ascii::dtostr(val);
159     }
161     void set_from_attribute(SPObject* o)
162     {
163         const gchar* val = attribute_value(o);
164         if(val){
165             set_value(Glib::Ascii::strtod(val));
166         } else {
167             set_value(get_default()->as_double());
168         }
169     }
170 };
172 template< typename T> class ComboWithTooltip : public Gtk::EventBox
174 public:
175     ComboWithTooltip<T>(T default_value, const Util::EnumDataConverter<T>& c, const SPAttributeEnum a = SP_ATTR_INVALID, char* tip_text = NULL)
176     {
177         if (tip_text) {
178             _tt.set_tip(*this, tip_text);
179         }
180         combo = new ComboBoxEnum<T>(default_value, c, a, false);
181         add(*combo);
182         show_all();
183     }
185     ~ComboWithTooltip()
186     {
187         delete combo;
188     }
190     ComboBoxEnum<T>* get_attrwidget()
191     {
192         return combo;
193     }
194 private:
195     Gtk::Tooltips _tt;
196     ComboBoxEnum<T>* combo;
197 };
199 // Contains an arbitrary number of spin buttons that use seperate attributes
200 class MultiSpinButton : public Gtk::HBox
202 public:
203     MultiSpinButton(double lower, double upper, double step_inc,
204                     double climb_rate, int digits, std::vector<SPAttributeEnum> attrs, std::vector<double> default_values, std::vector<char*> tip_text)
205     {
206         g_assert(attrs.size()==default_values.size());
207         g_assert(attrs.size()==tip_text.size());
208         for(unsigned i = 0; i < attrs.size(); ++i) {
209             _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i], default_values[i], tip_text[i]));
210             pack_start(*_spins.back(), false, false);
211         }
212     }
214     ~MultiSpinButton()
215     {
216         for(unsigned i = 0; i < _spins.size(); ++i)
217             delete _spins[i];
218     }
220     std::vector<SpinButtonAttr*>& get_spinbuttons()
221     {
222         return _spins;
223     }
224 private:
225     std::vector<SpinButtonAttr*> _spins;
226 };
228 // Contains two spinbuttons that describe a NumberOptNumber
229 class DualSpinButton : public Gtk::HBox, public AttrWidget
231 public:
232     DualSpinButton(char* def, double lower, double upper, double step_inc,
233                    double climb_rate, int digits, const SPAttributeEnum a, char* tt1, char* tt2)
234         : AttrWidget(a, def), //TO-DO: receive default num-opt-num as parameter in the constructor
235           _s1(climb_rate, digits), _s2(climb_rate, digits)
236     {
237         if (tt1) _tt.set_tip(_s1, tt1);
238         if (tt2) _tt.set_tip(_s2, tt2);
239         _s1.set_range(lower, upper);
240         _s2.set_range(lower, upper);
241         _s1.set_increments(step_inc, 0);
242         _s2.set_increments(step_inc, 0);
244         _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
245         _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
247         pack_start(_s1, false, false);
248         pack_start(_s2, false, false);
249     }
251     Gtk::SpinButton& get_spinbutton1()
252     {
253         return _s1;
254     }
256     Gtk::SpinButton& get_spinbutton2()
257     {
258         return _s2;
259     }
261     virtual Glib::ustring get_as_attribute() const
262     {
263         double v1 = _s1.get_value();
264         double v2 = _s2.get_value();
266         if(_s1.get_digits() == 0) {
267             v1 = (int)v1;
268             v2 = (int)v2;
269         }
271         return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
272     }
274     virtual void set_from_attribute(SPObject* o)
275     {
276         const gchar* val = attribute_value(o);
277         NumberOptNumber n;
278         if(val) {
279             n.set(val);
280         } else {
281             n.set(get_default()->as_charptr());
282         }
283         _s1.set_value(n.getNumber());
284         _s2.set_value(n.getOptNumber());
286     }
287 private:
288     Gtk::SpinButton _s1, _s2;
289 };
291 class ColorButton : public Gtk::ColorButton, public AttrWidget
293 public:
294     ColorButton(unsigned int def, const SPAttributeEnum a, char* tip_text)
295         : AttrWidget(a, def)
296     {
297         signal_color_set().connect(signal_attr_changed().make_slot());
298         if (tip_text) _tt.set_tip(*this, tip_text);
300         Gdk::Color col;
301         col.set_rgb(65535, 65535, 65535);
302         set_color(col);
303     }
305     // Returns the color in 'rgb(r,g,b)' form.
306     Glib::ustring get_as_attribute() const
307     {
308         // no doubles here, so we can use the standard string stream.
309         std::ostringstream os;
310         const Gdk::Color c = get_color();
311         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?
312         os << "rgb(" << r << "," << g << "," << b << ")";
313         return os.str();
314     }
317     void set_from_attribute(SPObject* o)
318     {
319         const gchar* val = attribute_value(o);
320         guint32 i = 0;
321         if(val) {
322             i = sp_svg_read_color(val, 0xFFFFFFFF);
323         } else {
324             i = (guint32) get_default()->as_uint();
325         }
326         const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
327         Gdk::Color col;
328         col.set_rgb(r * 256, g * 256, b * 256);
329         set_color(col);
330     }
331 };
333 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
334 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
336 public:
337     MatrixAttr(const SPAttributeEnum a, char* tip_text = NULL)
338         : AttrWidget(a), _locked(false)
339     {
340         _model = Gtk::ListStore::create(_columns);
341         _tree.set_model(_model);
342         _tree.set_headers_visible(false);
343         _tree.show();
344         add(_tree);
345         set_shadow_type(Gtk::SHADOW_IN);
346         if (tip_text) _tt.set_tip(_tree, tip_text);
347     }
349     std::vector<double> get_values() const
350     {
351         std::vector<double> vec;
352         for(Gtk::TreeIter iter = _model->children().begin();
353             iter != _model->children().end(); ++iter) {
354             for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
355                 vec.push_back((*iter)[_columns.cols[c]]);
356         }
357         return vec;
358     }
360     void set_values(const std::vector<double>& v)
361     {
362         unsigned i = 0;
363         for(Gtk::TreeIter iter = _model->children().begin();
364             iter != _model->children().end(); ++iter) {
365             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
366                 if(i >= v.size())
367                     return;
368                 (*iter)[_columns.cols[c]] = v[i];
369                 ++i;
370             }
371         }
372     }
374     Glib::ustring get_as_attribute() const
375     {
376         // use SVGOStringStream to output SVG-compatible doubles
377         Inkscape::SVGOStringStream os;
379         for(Gtk::TreeIter iter = _model->children().begin();
380             iter != _model->children().end(); ++iter) {
381             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
382                 os << (*iter)[_columns.cols[c]] << " ";
383             }
384         }
386         return os.str();
387     }
389     void set_from_attribute(SPObject* o)
390     {
391         if(o) {
392             if(SP_IS_FECONVOLVEMATRIX(o)) {
393                 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
394                 int cols, rows;
395                 cols = (int)conv->order.getNumber();
396                 if(cols > 5)
397                     cols = 5;
398                 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
399                 update(o, rows, cols);
400             }
401             else if(SP_IS_FECOLORMATRIX(o))
402                 update(o, 4, 5);
403         }
404     }
405 private:
406     class MatrixColumns : public Gtk::TreeModel::ColumnRecord
407     {
408     public:
409         MatrixColumns()
410         {
411             cols.resize(5);
412             for(unsigned i = 0; i < cols.size(); ++i)
413                 add(cols[i]);
414         }
415         std::vector<Gtk::TreeModelColumn<double> > cols;
416     };
418     void update(SPObject* o, const int rows, const int cols)
419     {
420         if(_locked)
421             return;
423         _model->clear();
425         _tree.remove_all_columns();
427         std::vector<gdouble>* values = NULL;
428         if(SP_IS_FECOLORMATRIX(o))
429             values = &SP_FECOLORMATRIX(o)->values;
430         else if(SP_IS_FECONVOLVEMATRIX(o))
431             values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
432         else
433             return;
435         if(o) {
436             int ndx = 0;
438             for(int i = 0; i < cols; ++i) {
439                 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
440                 dynamic_cast<Gtk::CellRendererText*>(
441                     _tree.get_column_cell_renderer(i))->signal_edited().connect(
442                         sigc::mem_fun(*this, &MatrixAttr::rebind));
443             }
445             for(int r = 0; r < rows; ++r) {
446                 Gtk::TreeRow row = *(_model->append());
447                 // Default to identity matrix
448                 for(int c = 0; c < cols; ++c, ++ndx)
449                     row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
450             }
451         }
452     }
454     void rebind(const Glib::ustring&, const Glib::ustring&)
455     {
456         _locked = true;
457         signal_attr_changed()();
458         _locked = false;
459     }
461     bool _locked;
462     Gtk::TreeView _tree;
463     Glib::RefPtr<Gtk::ListStore> _model;
464     MatrixColumns _columns;
465 };
467 // Displays a matrix or a slider for feColorMatrix
468 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
470 public:
471     ColorMatrixValues()
472         : AttrWidget(SP_ATTR_VALUES),
473           // TRANSLATORS: this dialog is accessible via menu Filters - Filter editor
474           _matrix(SP_ATTR_VALUES, _("This matrix determines a linear transform on color 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.")),
475           _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
476           _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
477           _label(_("None"), Gtk::ALIGN_LEFT),
478           _use_stored(false),
479           _saturation_store(0),
480           _angle_store(0)
481     {
482         _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
483         _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
484         _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
485         signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
487         _matrix.show();
488         _saturation.show();
489         _angle.show();
490         _label.show();
491         _label.set_sensitive(false);
493         set_shadow_type(Gtk::SHADOW_NONE);
494     }
496     virtual void set_from_attribute(SPObject* o)
497     {
498         if(SP_IS_FECOLORMATRIX(o)) {
499             SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
500             remove();
501             switch(col->type) {
502                 case COLORMATRIX_SATURATE:
503                     add(_saturation);
504                     if(_use_stored)
505                         _saturation.set_value(_saturation_store);
506                     else
507                         _saturation.set_from_attribute(o);
508                     break;
509                 case COLORMATRIX_HUEROTATE:
510                     add(_angle);
511                     if(_use_stored)
512                         _angle.set_value(_angle_store);
513                     else
514                         _angle.set_from_attribute(o);
515                     break;
516                 case COLORMATRIX_LUMINANCETOALPHA:
517                     add(_label);
518                     break;
519                 case COLORMATRIX_MATRIX:
520                 default:
521                     add(_matrix);
522                     if(_use_stored)
523                         _matrix.set_values(_matrix_store);
524                     else
525                         _matrix.set_from_attribute(o);
526                     break;
527             }
528             _use_stored = true;
529         }
530     }
532     virtual Glib::ustring get_as_attribute() const
533     {
534         const Widget* w = get_child();
535         if(w == &_label)
536             return "";
537         else
538             return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
539     }
541     void clear_store()
542     {
543         _use_stored = false;
544     }
545 private:
546     void update_store()
547     {
548         const Widget* w = get_child();
549         if(w == &_matrix)
550             _matrix_store = _matrix.get_values();
551         else if(w == &_saturation)
552             _saturation_store = _saturation.get_value();
553         else if(w == &_angle)
554             _angle_store = _angle.get_value();
555     }
557     MatrixAttr _matrix;
558     SpinSlider _saturation;
559     SpinSlider _angle;
560     Gtk::Label _label;
562     // Store separate values for the different color modes
563     bool _use_stored;
564     std::vector<double> _matrix_store;
565     double _saturation_store;
566     double _angle_store;
567 };
569 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
571 //Displays a chooser for feImage input
572 //It may be a filename or the id for an SVG Element
573 //described in xlink:href syntax
574 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
576 public:
577     FileOrElementChooser(const SPAttributeEnum a)
578         : AttrWidget(a)
579     {
580         pack_start(_entry, false, false);
581         pack_start(_fromFile, false, false);
582         pack_start(_fromSVGElement, false, false);
584         _fromFile.set_label(_("Image File"));
585         _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
587         _fromSVGElement.set_label(_("Selected SVG Element"));
588         _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
590         _entry.signal_changed().connect(signal_attr_changed().make_slot());
592         show_all();
594     }
596     // Returns the element in xlink:href form.
597     Glib::ustring get_as_attribute() const
598     {
599         return _entry.get_text();
600     }
603     void set_from_attribute(SPObject* o)
604     {
605         const gchar* val = attribute_value(o);
606         if(val) {
607             _entry.set_text(val);
608         } else {
609             _entry.set_text("");
610         }
611     }
613     void set_desktop(SPDesktop* d){
614         _desktop = d;
615     }
617 private:
618     void select_svg_element(){
619         Inkscape::Selection* sel = sp_desktop_selection(_desktop);
620         if (sel->isEmpty()) return;
621         Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
622         if (!node || !node->matchAttributeName("id")) return;
624         std::ostringstream xlikhref;
625         xlikhref << "#" << node->attribute("id");
626         _entry.set_text(xlikhref.str());
627     }
629     void select_file(){
631         //# Get the current directory for finding files
632         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
633         Glib::ustring open_path;
634         Glib::ustring attr = prefs->getString("/dialogs/open/path");
635         if (!attr.empty())
636             open_path = attr;
638         //# Test if the open_path directory exists
639         if (!Inkscape::IO::file_test(open_path.c_str(),
640                   (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
641             open_path = "";
643         //# If no open path, default to our home directory
644         if (open_path.size() < 1)
645             {
646             open_path = g_get_home_dir();
647             open_path.append(G_DIR_SEPARATOR_S);
648             }
650         //# Create a dialog if we don't already have one
651         if (!selectFeImageFileInstance) {
652             selectFeImageFileInstance =
653                   Inkscape::UI::Dialog::FileOpenDialog::create(
654                      *_desktop->getToplevel(),
655                      open_path,
656                      Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not just svg*/
657                      (char const *)_("Select an image to be used as feImage input"));
658         }
660         //# Show the dialog
661         bool const success = selectFeImageFileInstance->show();
662         if (!success)
663             return;
665         //# User selected something.  Get name and type
666         Glib::ustring fileName = selectFeImageFileInstance->getFilename();
668         if (fileName.size() > 0) {
670             Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
672             if ( newFileName.size() > 0)
673                 fileName = newFileName;
674             else
675                 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
677             open_path = fileName;
678             open_path.append(G_DIR_SEPARATOR_S);
679             prefs->setString("/dialogs/open/path", open_path);
681             _entry.set_text(fileName);
682         }
683         return;
684     }
686     Gtk::Entry _entry;
687     Gtk::Button _fromFile;
688     Gtk::Button _fromSVGElement;
689     SPDesktop* _desktop;
690 };
692 class FilterEffectsDialog::Settings
694 public:
695     typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
697     Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
698         : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
699     {
700         _groups.resize(_max_types);
701         _attrwidgets.resize(_max_types);
702         _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
704         for(int i = 0; i < _max_types; ++i) {
705             _groups[i] = new Gtk::VBox;
706             b.pack_start(*_groups[i], false, false);
707         }
708         _current_type = 0;
709     }
711     ~Settings()
712     {
713         for(int i = 0; i < _max_types; ++i) {
714             delete _groups[i];
715             for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
716                 delete _attrwidgets[i][j];
717         }
718     }
720     // Show the active settings group and update all the AttrWidgets with new values
721     void show_and_update(const int t, SPObject* ob)
722     {
723         if(t != _current_type) {
724             type(t);
725             for(unsigned i = 0; i < _groups.size(); ++i)
726                 _groups[i]->hide();
727         }
728         if(t >= 0)
729             _groups[t]->show_all();
731         _dialog.set_attrs_locked(true);
732         for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
733             _attrwidgets[_current_type][i]->set_from_attribute(ob);
734         _dialog.set_attrs_locked(false);
735     }
737     int get_current_type() const
738     {
739         return _current_type;
740     }
742     void type(const int t)
743     {
744         _current_type = t;
745     }
747     void add_no_params()
748     {
749         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
750         add_widget(lbl, "");
751     }
753     void add_notimplemented()
754     {
755         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
756         add_widget(lbl, "");
757     }
759     // LightSource
760     LightSourceControl* add_lightsource();
762     // CheckBox
763     CheckButtonAttr* add_checkbutton(bool def, const SPAttributeEnum attr, const Glib::ustring& label,
764                                      const Glib::ustring& tv, const Glib::ustring& fv, char* tip_text = NULL)
765     {
766         CheckButtonAttr* cb = new CheckButtonAttr(def, label, tv, fv, attr, tip_text);
767         add_widget(cb, "");
768         add_attr_widget(cb);
769         return cb;
770     }
772     // ColorButton
773     ColorButton* add_color(unsigned int def, const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text = NULL)
774     {
775         ColorButton* col = new ColorButton(def, attr, tip_text);
776         add_widget(col, label);
777         add_attr_widget(col);
778         return col;
779     }
781     // Matrix
782     MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text)
783     {
784         MatrixAttr* conv = new MatrixAttr(attr, tip_text);
785         add_widget(conv, label);
786         add_attr_widget(conv);
787         return conv;
788     }
790     // ColorMatrixValues
791     ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
792     {
793         ColorMatrixValues* cmv = new ColorMatrixValues();
794         add_widget(cmv, label);
795         add_attr_widget(cmv);
796         return cmv;
797     }
799     // SpinSlider
800     SpinSlider* add_spinslider(double def, const SPAttributeEnum attr, const Glib::ustring& label,
801                          const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
802     {
803         SpinSlider* spinslider = new SpinSlider(def, lo, hi, step_inc, climb, digits, attr, tip_text);
804         add_widget(spinslider, label);
805         add_attr_widget(spinslider);
806         return spinslider;
807     }
809     // DualSpinSlider
810     DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
811                                        const double lo, const double hi, const double step_inc,
812                                        const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
813     {
814         DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
815         add_widget(dss, label);
816         add_attr_widget(dss);
817         return dss;
818     }
820     // DualSpinButton
821     DualSpinButton* add_dualspinbutton(char* defalt_value, const SPAttributeEnum attr, const Glib::ustring& label,
822                                        const double lo, const double hi, const double step_inc,
823                                        const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
824     {
825         DualSpinButton* dsb = new DualSpinButton(defalt_value, lo, hi, step_inc, climb, digits, attr, tip1, tip2);
826         add_widget(dsb, label);
827         add_attr_widget(dsb);
828         return dsb;
829     }
831     // MultiSpinButton
832     MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
833                                          const Glib::ustring& label, const double lo, const double hi,
834                                          const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
835     {
836         std::vector<SPAttributeEnum> attrs;
837         attrs.push_back(attr1);
838         attrs.push_back(attr2);
840         std::vector<double> default_values;
841         default_values.push_back(def1);
842         default_values.push_back(def2);
844         std::vector<char*> tips;
845         tips.push_back(tip1);
846         tips.push_back(tip2);
848         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
849         add_widget(msb, label);
850         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
851             add_attr_widget(msb->get_spinbuttons()[i]);
852         return msb;
853     }
854     MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
855                                          const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
856                                          const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
857     {
858         std::vector<SPAttributeEnum> attrs;
859         attrs.push_back(attr1);
860         attrs.push_back(attr2);
861         attrs.push_back(attr3);
863         std::vector<double> default_values;
864         default_values.push_back(def1);
865         default_values.push_back(def2);
866         default_values.push_back(def3);
868         std::vector<char*> tips;
869         tips.push_back(tip1);
870         tips.push_back(tip2);
871         tips.push_back(tip3);
873         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
874         add_widget(msb, label);
875         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
876             add_attr_widget(msb->get_spinbuttons()[i]);
877         return msb;
878     }
880     // FileOrElementChooser
881     FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
882     {
883         FileOrElementChooser* foech = new FileOrElementChooser(attr);
884         foech->set_desktop(_dialog.getDesktop());
885         add_widget(foech, label);
886         add_attr_widget(foech);
887         return foech;
888     }
890     // ComboBoxEnum
891     template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
892                                   const Glib::ustring& label,
893                                   const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
894     {
895         ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
896         add_widget(combo, label);
897         add_attr_widget(combo->get_attrwidget());
898         return combo->get_attrwidget();
899     }
900 private:
901     Gtk::Tooltips _tt;
903     void add_attr_widget(AttrWidget* a)
904     {
905         _attrwidgets[_current_type].push_back(a);
906         a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
907     }
909     /* Adds a new settings widget using the specified label. The label will be formatted with a colon
910        and all widgets within the setting group are aligned automatically. */
911     void add_widget(Gtk::Widget* w, const Glib::ustring& label)
912     {
913         Gtk::Label *lbl = 0;
914         Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
915         hb->set_spacing(12);
917         if(label != "") {
918             //lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT)); colon now in label (LP #358921)
919             lbl = Gtk::manage(new Gtk::Label(label, Gtk::ALIGN_LEFT));
920             hb->pack_start(*lbl, false, false);
921             _size_group->add_widget(*lbl);
922             lbl->show();
923         }
925         hb->pack_start(*w);
926         _groups[_current_type]->pack_start(*hb);
927         hb->show();
928         w->show();
929     }
931     std::vector<Gtk::VBox*> _groups;
932     Glib::RefPtr<Gtk::SizeGroup> _size_group;
933     FilterEffectsDialog& _dialog;
934     SetAttrSlot _set_attr_slot;
935     std::vector<std::vector< AttrWidget*> > _attrwidgets;
936     int _current_type, _max_types;
937 };
939 // Settings for the three light source objects
940 class FilterEffectsDialog::LightSourceControl : public AttrWidget
942 public:
943     LightSourceControl(FilterEffectsDialog& d)
944         : AttrWidget(SP_ATTR_INVALID),
945           _dialog(d),
946           _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
947           _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
948           _light_source(LightSourceConverter),
949           _locked(false)
950     {
951         _light_box.pack_start(_light_label, false, false);
952         _light_box.pack_start(_light_source);
953         _light_box.show_all();
954         _light_box.set_spacing(12);
955         _dialog._sizegroup->add_widget(_light_label);
957         _box.add(_light_box);
958         _box.reorder_child(_light_box, 0);
959         _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
961         // FIXME: these range values are complete crap
963         _settings.type(LIGHT_DISTANT);
964         _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"));
965         _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"));
967         _settings.type(LIGHT_POINT);
968         _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"));
970         _settings.type(LIGHT_SPOT);
971         _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"));
972         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
973                                       SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
974                                       _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
975         _settings.add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
976         //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.
977         _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."));
978     }
980     Gtk::VBox& get_box()
981     {
982         return _box;
983     }
984 protected:
985     Glib::ustring get_as_attribute() const
986     {
987         return "";
988     }
989     void set_from_attribute(SPObject* o)
990     {
991         if(_locked)
992             return;
994         _locked = true;
996         SPObject* child = o->children;
998         if(SP_IS_FEDISTANTLIGHT(child))
999             _light_source.set_active(0);
1000         else if(SP_IS_FEPOINTLIGHT(child))
1001             _light_source.set_active(1);
1002         else if(SP_IS_FESPOTLIGHT(child))
1003             _light_source.set_active(2);
1004         else
1005             _light_source.set_active(-1);
1007         update();
1009         _locked = false;
1010     }
1011 private:
1012     void on_source_changed()
1013     {
1014         if(_locked)
1015             return;
1017         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1018         if(prim) {
1019             _locked = true;
1021             SPObject* child = prim->children;
1022             const int ls = _light_source.get_active_row_number();
1023             // Check if the light source type has changed
1024             if(!(ls == -1 && !child) &&
1025                !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1026                !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1027                !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1028                 if(child)
1029                     //XML Tree being used directly here while it shouldn't be.
1030                     sp_repr_unparent(child->getRepr());
1032                 if(ls != -1) {
1033                     Inkscape::XML::Document *xml_doc = prim->document->getReprDoc();
1034                     Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1035                     //XML Tree being used directly here while it shouldn't be.
1036                     prim->getRepr()->appendChild(repr);
1037                     Inkscape::GC::release(repr);
1038                 }
1040                 DocumentUndo::done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1041                 update();
1042             }
1044             _locked = false;
1045         }
1046     }
1048     void update()
1049     {
1050         _box.hide_all();
1051         _box.show();
1052         _light_box.show_all();
1054         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1055         if(prim && prim->children)
1056             _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1057     }
1059     FilterEffectsDialog& _dialog;
1060     Gtk::VBox _box;
1061     Settings _settings;
1062     Gtk::HBox _light_box;
1063     Gtk::Label _light_label;
1064     ComboBoxEnum<LightSource> _light_source;
1065     bool _locked;
1066 };
1068 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1070     LightSourceControl* ls = new LightSourceControl(_dialog);
1071     add_attr_widget(ls);
1072     add_widget(&ls->get_box(), "");
1073     return ls;
1076 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1077                                           sigc::slot<void> rem)
1079     Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1081     menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1082     Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1083     menu->append(*mi);
1084     mi->signal_activate().connect(rem);
1085     mi->show();
1086     menu->accelerate(parent);
1088     return menu;
1091 /*** FilterModifier ***/
1092 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1093     : _dialog(d), _add(Gtk::Stock::NEW), _observer(new Inkscape::XML::SignalObserver)
1095     Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1096     pack_start(*sw);
1097     pack_start(_add, false, false);
1098     sw->add(_list);
1100     _model = Gtk::ListStore::create(_columns);
1101     _list.set_model(_model);
1102     _cell_toggle.set_active(true);
1103     const int selcol = _list.append_column("", _cell_toggle);
1104     Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1105     if(col)
1106        col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1107     _list.append_column_editable(_("_Filter"), _columns.label);
1108     ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1109         signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1111     sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1112     sw->set_shadow_type(Gtk::SHADOW_IN);
1113     show_all_children();
1114     _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1115     _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1116     _list.signal_button_release_event().connect_notify(
1117         sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1118     _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1119                               sigc::mem_fun(*this, &FilterModifier::remove_filter));
1120     _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1121                                  _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1122     _menu->accelerate(*this);
1124     _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1125     _observer->signal_changed().connect(signal_filter_changed().make_slot());
1126     g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1127                      G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1129     g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1130                      G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1131     g_signal_connect(G_OBJECT(INKSCAPE), "deactivate_desktop",
1132                      G_CALLBACK(&FilterModifier::on_deactivate_desktop), this);
1134     on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1135     update_filters();
1138 FilterEffectsDialog::FilterModifier::~FilterModifier()
1140    _resource_changed.disconnect();
1141    _doc_replaced.disconnect();
1144 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1146     me->_doc_replaced.disconnect();
1147     me->_doc_replaced = desktop->connectDocumentReplaced(
1148         sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1150     me->_resource_changed.disconnect();
1151     me->_resource_changed =
1152         sp_desktop_document(desktop)->connectResourcesChanged("filter",sigc::mem_fun(me, &FilterModifier::update_filters));
1154     me->_dialog.setDesktop(desktop);
1156     me->update_filters();
1159 void FilterEffectsDialog::FilterModifier::on_deactivate_desktop(Application*, SPDesktop* /*desktop*/, FilterModifier* me)
1161     me->_doc_replaced.disconnect();
1162     me->_resource_changed.disconnect();
1163     me->_dialog.setDesktop(NULL);
1167 // When the selection changes, show the active filter(s) in the dialog
1168 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1169                                                                        Selection *sel,
1170                                                                        FilterModifier* fm)
1172     if(fm && sel)
1173         fm->update_selection(sel);
1176 // Update each filter's sel property based on the current object selection;
1177 //  If the filter is not used by any selected object, sel = 0,
1178 //  otherwise sel is set to the total number of filters in use by selected objects
1179 //  If only one filter is in use, it is selected
1180 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1182     std::set<SPObject*> used;
1184     for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1185         SPObject *obj = SP_OBJECT (i->data);
1186         SPStyle *style = SP_OBJECT_STYLE (obj);
1187         if(!style || !SP_IS_ITEM(obj)) continue;
1189         if(style->filter.set && style->getFilter())
1190             used.insert(style->getFilter());
1191         else
1192             used.insert(0);
1193     }
1195     const int size = used.size();
1197     for(Gtk::TreeIter iter = _model->children().begin();
1198         iter != _model->children().end(); ++iter) {
1199         if(used.find((*iter)[_columns.filter]) != used.end()) {
1200             // If only one filter is in use by the selection, select it
1201             if(size == 1)
1202                 _list.get_selection()->select(iter);
1203             (*iter)[_columns.sel] = size;
1204         }
1205         else
1206             (*iter)[_columns.sel] = 0;
1207     }
1210 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1212     _observer->set(get_selected_filter());
1213     signal_filter_changed()();
1216 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1218     Gtk::TreeModel::iterator iter = _model->get_iter(path);
1220     if(iter) {
1221         SPFilter* filter = (*iter)[_columns.filter];
1222         filter->setLabel(text.c_str());
1223         DocumentUndo::done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1224         if(iter)
1225             (*iter)[_columns.label] = text;
1226     }
1229 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1231     Gtk::TreeIter iter = _model->get_iter(path);
1233     if(iter) {
1234         SPDesktop *desktop = _dialog.getDesktop();
1235         SPDocument *doc = sp_desktop_document(desktop);
1236         SPFilter* filter = (*iter)[_columns.filter];
1237         Inkscape::Selection *sel = sp_desktop_selection(desktop);
1239         /* If this filter is the only one used in the selection, unset it */
1240         if((*iter)[_columns.sel] == 1)
1241             filter = 0;
1243         GSList const *items = sel->itemList();
1245         for (GSList const *i = items; i != NULL; i = i->next) {
1246             SPItem * item = SP_ITEM(i->data);
1247             SPStyle *style = SP_OBJECT_STYLE(item);
1248             g_assert(style != NULL);
1250             if(filter)
1251                 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1252             else
1253                 ::remove_filter(item, false);
1255             SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1256         }
1258         update_selection(sel);
1259         DocumentUndo::done(doc, SP_VERB_DIALOG_FILTER_EFFECTS,  _("Apply filter"));
1260     }
1263 /* Add all filters in the document to the combobox.
1264    Keeps the same selection if possible, otherwise selects the first element */
1265 void FilterEffectsDialog::FilterModifier::update_filters()
1267     SPDesktop* desktop = _dialog.getDesktop();
1268     SPDocument* document = sp_desktop_document(desktop);
1269     const GSList* filters = document->getResourceList("filter");
1271     _model->clear();
1273     for(const GSList *l = filters; l; l = l->next) {
1274         Gtk::TreeModel::Row row = *_model->append();
1275         SPFilter* f = (SPFilter*)l->data;
1276         row[_columns.filter] = f;
1277         const gchar* lbl = f->label();
1278         const gchar* id = f->getId();
1279         row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1280     }
1282     update_selection(desktop->selection);
1283     _dialog.update_filter_general_settings_view();
1286 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1288     if(_list.get_selection()) {
1289         Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1291         if(i)
1292             return (*i)[_columns.filter];
1293     }
1295     return 0;
1298 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1300     if(filter) {
1301         for(Gtk::TreeModel::iterator i = _model->children().begin();
1302             i != _model->children().end(); ++i) {
1303             if((*i)[_columns.filter] == filter) {
1304                 _list.get_selection()->select(i);
1305                 break;
1306             }
1307         }
1308     }
1311 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1313     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1314         const bool sensitive = get_selected_filter() != NULL;
1315         _menu->items()[0].set_sensitive(sensitive);
1316         _menu->items()[1].set_sensitive(sensitive);
1317         _menu->popup(event->button, event->time);
1318     }
1321 void FilterEffectsDialog::FilterModifier::add_filter()
1323     SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1324     SPFilter* filter = new_filter(doc);
1326     const int count = _model->children().size();
1327     std::ostringstream os;
1328     os << _("filter") << count;
1329     filter->setLabel(os.str().c_str());
1331     update_filters();
1333     select_filter(filter);
1335     DocumentUndo::done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1338 void FilterEffectsDialog::FilterModifier::remove_filter()
1340     SPFilter *filter = get_selected_filter();
1342     if(filter) {
1343         SPDocument* doc = filter->document;
1345         //XML Tree being used directly here while it shouldn't be.
1346         sp_repr_unparent(filter->getRepr());
1348         DocumentUndo::done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1350         update_filters();
1351     }
1354 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1356     SPFilter* filter = get_selected_filter();
1358     if(filter) {
1359         Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1360         repr = repr->duplicate(repr->document());
1361         parent->appendChild(repr);
1363         DocumentUndo::done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1365         update_filters();
1366     }
1369 void FilterEffectsDialog::FilterModifier::rename_filter()
1371     _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1374 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1375     : Glib::ObjectBase(typeid(CellRendererConnection)),
1376       _primitive(*this, "primitive", 0)
1377 {}
1379 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1381     return _primitive.get_proxy();
1384 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1386     _text_width = w;
1389 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1391     return _text_width;
1394 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1395     Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1396     int* x_offset, int* y_offset, int* width, int* height) const
1398     PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1400     if(x_offset)
1401         (*x_offset) = 0;
1402     if(y_offset)
1403         (*y_offset) = 0;
1404     if(width)
1405         (*width) = size * primlist.primitive_count() + _text_width * 7;
1406     if(height) {
1407         // Scale the height depending on the number of inputs, unless it's
1408         // the first primitive, in which case there are no connections
1409         SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1410         (*height) = size * input_count(prim);
1411     }
1414 /*** PrimitiveList ***/
1415 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1416     : _dialog(d),
1417       _in_drag(0),
1418       _observer(new Inkscape::XML::SignalObserver)
1420     d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1422     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1423     signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1425     _model = Gtk::ListStore::create(_columns);
1427     set_reorderable(true);
1429     set_model(_model);
1430     append_column(_("_Effect"), _columns.type);
1432     _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1433     get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1434     signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1436     _connection_cell.set_text_width(init_text());
1438     int cols_count = append_column(_("Connections"), _connection_cell);
1439     Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1440     if(col)
1441        col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1444 // Sets up a vertical Pango context/layout, and returns the largest
1445 // width needed to render the FilterPrimitiveInput labels.
1446 int FilterEffectsDialog::PrimitiveList::init_text()
1448     // Set up a vertical context+layout
1449     Glib::RefPtr<Pango::Context> context = create_pango_context();
1450     const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1451     context->set_matrix(matrix);
1452     _vertical_layout = Pango::Layout::create(context);
1454     int maxfont = 0;
1455     for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1456         _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1457         int fontw, fonth;
1458         _vertical_layout->get_pixel_size(fontw, fonth);
1459         if(fonth > maxfont)
1460             maxfont = fonth;
1461     }
1463     return maxfont;
1466 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1468     return _signal_primitive_changed;
1471 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1473     _observer->set(get_selected());
1474     signal_primitive_changed()();
1475     _dialog._color_matrix_values->clear_store();
1478 /* Add all filter primitives in the current to the list.
1479    Keeps the same selection if possible, otherwise selects the first element */
1480 void FilterEffectsDialog::PrimitiveList::update()
1482     SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1483     const SPFilterPrimitive* active_prim = get_selected();
1484     bool active_found = false;
1486     _model->clear();
1488     if(f) {
1489         _dialog._primitive_box.set_sensitive(true);
1490         _dialog.update_filter_general_settings_view();
1491         for(SPObject *prim_obj = f->children;
1492                 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1493                 prim_obj = prim_obj->next) {
1494             SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1495             if(prim) {
1496                 Gtk::TreeModel::Row row = *_model->append();
1497                 row[_columns.primitive] = prim;
1499                 //XML Tree being used directly here while it shouldn't be.
1500                 row[_columns.type_id] = FPConverter.get_id_from_key(prim->getRepr()->name());
1501                 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1502                 row[_columns.id] = prim->getId();
1504                 if(prim == active_prim) {
1505                     get_selection()->select(row);
1506                     active_found = true;
1507                 }
1508             }
1509         }
1511         if(!active_found && _model->children().begin())
1512             get_selection()->select(_model->children().begin());
1514         columns_autosize();
1515     }
1516     else {
1517         _dialog._primitive_box.set_sensitive(false);
1518     }
1521 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1523     _primitive_menu = menu;
1526 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1528     if(_dialog._filter_modifier.get_selected_filter()) {
1529         Gtk::TreeModel::iterator i = get_selection()->get_selected();
1530         if(i)
1531             return (*i)[_columns.primitive];
1532     }
1534     return 0;
1537 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1539     for(Gtk::TreeIter i = _model->children().begin();
1540         i != _model->children().end(); ++i) {
1541         if((*i)[_columns.primitive] == prim)
1542             get_selection()->select(i);
1543     }
1546 void FilterEffectsDialog::PrimitiveList::remove_selected()
1548     SPFilterPrimitive* prim = get_selected();
1550     if(prim) {
1551         _observer->set(0);
1553         //XML Tree being used directly here while it shouldn't be.
1554         sp_repr_unparent(prim->getRepr());
1556         DocumentUndo::done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1557                            _("Remove filter primitive"));
1559         update();
1560     }
1563 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1565     Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1566     Glib::RefPtr<Gdk::Window> win = get_bin_window();
1567     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1569     SPFilterPrimitive* prim = get_selected();
1570     int row_count = get_model()->children().size();
1572     int fheight = CellRendererConnection::size;
1573     Gdk::Rectangle rct, vis;
1574     Gtk::TreeIter row = get_model()->children().begin();
1575     int text_start_x = 0;
1576     if(row) {
1577         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1578         get_visible_rect(vis);
1579         int vis_x, vis_y;
1580         tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1582         text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter._length + 1) + 1;
1583         for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1584             _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1585             const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1586             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());
1587             get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1588             get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1589         }
1590     }
1592     int row_index = 0;
1593     for(; row != get_model()->children().end(); ++row, ++row_index) {
1594         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1595         const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1597         // Check mouse state
1598         int mx, my;
1599         Gdk::ModifierType mask;
1600         get_bin_window()->get_pointer(mx, my, mask);
1602         // Outline the bottom of the connection area
1603         const int outline_x = x + fheight * (row_count - row_index);
1604         get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1606         // Side outline
1607         get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1609         std::vector<Gdk::Point> con_poly;
1610         int con_drag_y = 0;
1611         bool inside;
1612         const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1613         const int inputs = input_count(row_prim);
1615         if(SP_IS_FEMERGE(row_prim)) {
1616             for(int i = 0; i < inputs; ++i) {
1617                 inside = do_connection_node(row, i, con_poly, mx, my);
1618                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1619                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1620                                                inside, con_poly);
1622                 if(_in_drag == (i + 1))
1623                     con_drag_y = con_poly[2].get_y();
1625                 if(_in_drag != (i + 1) || row_prim != prim)
1626                     draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1627             }
1628         }
1629         else {
1630             // Draw "in" shape
1631             inside = do_connection_node(row, 0, con_poly, mx, my);
1632             con_drag_y = con_poly[2].get_y();
1633             get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1634                                            darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1635                                            inside, con_poly);
1637             // Draw "in" connection
1638             if(_in_drag != 1 || row_prim != prim)
1639                 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1641             if(inputs == 2) {
1642                 // Draw "in2" shape
1643                 inside = do_connection_node(row, 1, con_poly, mx, my);
1644                 if(_in_drag == 2)
1645                     con_drag_y = con_poly[2].get_y();
1646                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1647                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1648                                                inside, con_poly);
1649                 // Draw "in2" connection
1650                 if(_in_drag != 2 || row_prim != prim)
1651                     draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1652             }
1653         }
1655         // Draw drag connection
1656         if(row_prim == prim && _in_drag) {
1657             get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1658                                         mx, con_drag_y);
1659             get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1660         }
1661     }
1663     return true;
1666 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1667                                                          const int text_start_x, const int x1, const int y1,
1668                                                          const int row_count)
1670     int src_id = 0;
1671     Gtk::TreeIter res = find_result(input, attr, src_id);
1672     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1673     Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1674     Glib::RefPtr<Gdk::GC> gc;
1676     const bool is_first = input == get_model()->children().begin();
1677     const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1678     const bool use_default = !res && !is_merge;
1680     if(res == input || (use_default && is_first)) {
1681         // Draw straight connection to a standard input
1682         // Draw a lighter line for an implicit connection to a standard input
1683         const int tw = _connection_cell.get_text_width();
1684         gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1685         gc = (use_default && is_first) ? lightgc : darkgc;
1686         get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1687         get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1688     }
1689     else {
1690         // Draw an 'L'-shaped connection to another filter primitive
1691         // If no connection is specified, draw a light connection to the previous primitive
1692         gc = use_default ? lightgc : darkgc;
1694         if(use_default) {
1695             res = input;
1696             --res;
1697         }
1699         if(res) {
1700             Gdk::Rectangle rct;
1702             get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1703             const int fheight = CellRendererConnection::size;
1705             get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1706             const int row_index = find_index(res);
1707             const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1708             const int y2 = rct.get_y() + rct.get_height();
1710             // Draw a bevelled 'L'-shaped connection
1711             get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1712             get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1713             get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1714         }
1715     }
1718 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1719 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1720                                                             std::vector<Gdk::Point>& points,
1721                                                             const int ix, const int iy)
1723     Gdk::Rectangle rct;
1724     const int icnt = input_count((*row)[_columns.primitive]);
1726     get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1727     const int fheight = CellRendererConnection::size;
1729     get_cell_area(_model->get_path(row), *get_column(1), rct);
1730     const float h = rct.get_height() / icnt;
1732     const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1733     const int con_w = (int)(fheight * 0.35f);
1734     const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1735     points.clear();
1736     points.push_back(Gdk::Point(x, con_y));
1737     points.push_back(Gdk::Point(x, con_y + con_w * 2));
1738     points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1740     return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1743 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1744                                                                     const int attr, int& src_id)
1746     SPFilterPrimitive* prim = (*start)[_columns.primitive];
1747     Gtk::TreeIter target = _model->children().end();
1748     int image = 0;
1750     if(SP_IS_FEMERGE(prim)) {
1751         int c = 0;
1752         bool found = false;
1753         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1754             if(c == attr && SP_IS_FEMERGENODE(o)) {
1755                 image = SP_FEMERGENODE(o)->input;
1756                 found = true;
1757             }
1758         }
1759         if(!found)
1760             return target;
1761     }
1762     else {
1763         if(attr == SP_ATTR_IN)
1764             image = prim->image_in;
1765         else if(attr == SP_ATTR_IN2) {
1766             if(SP_IS_FEBLEND(prim))
1767                 image = SP_FEBLEND(prim)->in2;
1768             else if(SP_IS_FECOMPOSITE(prim))
1769                 image = SP_FECOMPOSITE(prim)->in2;
1770             else if(SP_IS_FEDISPLACEMENTMAP(prim))
1771                 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1772             else
1773                 return target;
1774         }
1775         else
1776             return target;
1777     }
1779     if(image >= 0) {
1780         for(Gtk::TreeIter i = _model->children().begin();
1781             i != start; ++i) {
1782             if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1783                 target = i;
1784         }
1785         return target;
1786     }
1787     else if(image < -1) {
1788         src_id = -(image + 2);
1789         return start;
1790     }
1792     return target;
1795 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1797     int i = 0;
1798     for(Gtk::TreeIter iter = _model->children().begin();
1799         iter != target; ++iter, ++i){};
1800     return i;
1803 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1805     Gtk::TreePath path;
1806     Gtk::TreeViewColumn* col;
1807     const int x = (int)e->x, y = (int)e->y;
1808     int cx, cy;
1810     _drag_prim = 0;
1812     if(get_path_at_pos(x, y, path, col, cx, cy)) {
1813         Gtk::TreeIter iter = _model->get_iter(path);
1814         std::vector<Gdk::Point> points;
1816         _drag_prim = (*iter)[_columns.primitive];
1817         const int icnt = input_count(_drag_prim);
1819         for(int i = 0; i < icnt; ++i) {
1820             if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1821                 _in_drag = i + 1;
1822                 break;
1823             }
1824         }
1826         queue_draw();
1827     }
1829     if(_in_drag) {
1830         _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1831         _autoscroll = 0;
1832         get_selection()->select(path);
1833         return true;
1834     }
1835     else
1836         return Gtk::TreeView::on_button_press_event(e);
1839 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1841     const int speed = 10;
1842     const int limit = 15;
1844     Gdk::Rectangle vis;
1845     get_visible_rect(vis);
1846     int vis_x, vis_y;
1847     tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1848     const int top = vis_y + vis.get_height();
1850     // When autoscrolling during a connection drag, set the speed based on
1851     // where the mouse is in relation to the edges.
1852     if(e->y < vis_y)
1853         _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1854     else if(e->y < vis_y + limit)
1855         _autoscroll = -speed;
1856     else if(e->y > top)
1857         _autoscroll = (int)(speed + (e->y - top) / 5);
1858     else if(e->y > top - limit)
1859         _autoscroll = speed;
1860     else
1861         _autoscroll = 0;
1863     queue_draw();
1865     return Gtk::TreeView::on_motion_notify_event(e);
1868 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1870     SPFilterPrimitive *prim = get_selected(), *target;
1872     _scroll_connection.disconnect();
1874     if(_in_drag && prim) {
1875         Gtk::TreePath path;
1876         Gtk::TreeViewColumn* col;
1877         int cx, cy;
1879         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1880             const gchar *in_val = 0;
1881             Glib::ustring result;
1882             Gtk::TreeIter target_iter = _model->get_iter(path);
1883             target = (*target_iter)[_columns.primitive];
1884             col = get_column(1);
1886             Gdk::Rectangle rct;
1887             get_cell_area(path, *col, rct);
1888             const int twidth = _connection_cell.get_text_width();
1889             const int sources_x = rct.get_width() - twidth * FPInputConverter._length;
1890             if(cx > sources_x) {
1891                 int src = (cx - sources_x) / twidth;
1892                 if (src < 0) {
1893                     src = 0;
1894                 } else if(src >= static_cast<int>(FPInputConverter._length)) {
1895                     src = FPInputConverter._length - 1;
1896                 }
1897                 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1898                 in_val = result.c_str();
1899             }
1900             else {
1901                 // Ensure that the target comes before the selected primitive
1902                 for(Gtk::TreeIter iter = _model->children().begin();
1903                     iter != get_selection()->get_selected(); ++iter) {
1904                     if(iter == target_iter) {
1905                         Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1906                         // Make sure the target has a result
1907                         const gchar *gres = repr->attribute("result");
1908                         if(!gres) {
1909                             result = sp_filter_get_new_result_name(SP_FILTER(prim->parent));
1910                             repr->setAttribute("result", result.c_str());
1911                             in_val = result.c_str();
1912                         }
1913                         else
1914                             in_val = gres;
1915                         break;
1916                     }
1917                 }
1918             }
1920             if(SP_IS_FEMERGE(prim)) {
1921                 int c = 1;
1922                 bool handled = false;
1923                 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1924                     if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1925                         // If input is null, delete it
1926                         if(!in_val) {
1928                             //XML Tree being used directly here while it shouldn't be.
1929                             sp_repr_unparent(o->getRepr());
1930                             DocumentUndo::done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1931                                                _("Remove merge node"));
1932                             (*get_selection()->get_selected())[_columns.primitive] = prim;
1933                         }
1934                         else
1935                             _dialog.set_attr(o, SP_ATTR_IN, in_val);
1936                         handled = true;
1937                     }
1938                 }
1939                 // Add new input?
1940                 if(!handled && c == _in_drag && in_val) {
1941                     Inkscape::XML::Document *xml_doc = prim->document->getReprDoc();
1942                     Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1943                     repr->setAttribute("inkscape:collect", "always");
1945                     //XML Tree being used directly here while it shouldn't be.
1946                     prim->getRepr()->appendChild(repr);
1947                     SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1948                     Inkscape::GC::release(repr);
1949                     _dialog.set_attr(node, SP_ATTR_IN, in_val);
1950                     (*get_selection()->get_selected())[_columns.primitive] = prim;
1951                 }
1952             }
1953             else {
1954                 if(_in_drag == 1)
1955                     _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1956                 else if(_in_drag == 2)
1957                     _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1958             }
1959         }
1961         _in_drag = 0;
1962         queue_draw();
1964         _dialog.update_settings_view();
1965     }
1967     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1968         const bool sensitive = get_selected() != NULL;
1969         _primitive_menu->items()[0].set_sensitive(sensitive);
1970         _primitive_menu->items()[1].set_sensitive(sensitive);
1971         _primitive_menu->popup(e->button, e->time);
1973         return true;
1974     }
1975     else
1976         return Gtk::TreeView::on_button_release_event(e);
1979 // Checks all of prim's inputs, removes any that use result
1980 void check_single_connection(SPFilterPrimitive* prim, const int result)
1982     if(prim && result >= 0) {
1984         if(prim->image_in == result)
1985             SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1987         if(SP_IS_FEBLEND(prim)) {
1988             if(SP_FEBLEND(prim)->in2 == result)
1989                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1990         }
1991         else if(SP_IS_FECOMPOSITE(prim)) {
1992             if(SP_FECOMPOSITE(prim)->in2 == result)
1993                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1994         }
1995         else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
1996             if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
1997                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1998         }
1999     }
2002 // Remove any connections going to/from prim_iter that forward-reference other primitives
2003 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
2005     SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
2006     bool before = true;
2008     for(Gtk::TreeIter iter = _model->children().begin();
2009         iter != _model->children().end(); ++iter) {
2010         if(iter == prim_iter)
2011             before = false;
2012         else {
2013             SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
2014             if(before)
2015                 check_single_connection(cur_prim, prim->image_out);
2016             else
2017                 check_single_connection(prim, cur_prim->image_out);
2018         }
2019     }
2022 // Reorder the filter primitives to match the list order
2023 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2025     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2026     int ndx = 0;
2028     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2029         iter != _model->children().end(); ++iter, ++ndx) {
2030         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2031         if(prim && prim == _drag_prim) {
2032             SP_OBJECT_REPR(prim)->setPosition(ndx);
2033             break;
2034         }
2035     }
2037     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2038         iter != _model->children().end(); ++iter, ++ndx) {
2039         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2040         if(prim && prim == _drag_prim) {
2041             sanitize_connections(iter);
2042             get_selection()->select(iter);
2043             break;
2044         }
2045     }
2047     filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2049     DocumentUndo::done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2052 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2053 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2055     if(_autoscroll) {
2056         Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2057         double v;
2059         v = a.get_value() + _autoscroll;
2060         if(v < 0)
2061             v = 0;
2062         if(v > a.get_upper() - a.get_page_size())
2063             v = a.get_upper() - a.get_page_size();
2065         a.set_value(v);
2067         queue_draw();
2068     }
2070     return true;
2073 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2075     return _model->children().size();
2078 /*** FilterEffectsDialog ***/
2080 FilterEffectsDialog::FilterEffectsDialog()
2081     : UI::Widget::Panel("", "/dialogs/filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2082       _add_primitive_type(FPConverter),
2083       _add_primitive(_("Add Effect:")),
2084       _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2085       _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2086       _settings_initialized(false),
2087       _locked(false),
2088       _attr_lock(false),
2089       _filter_modifier(*this),
2090       _primitive_list(*this)
2092     _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2093                              NR_FILTER_ENDPRIMITIVETYPE);
2094     _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2095                              1);
2096     _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2097     _sizegroup->set_ignore_hidden();
2099     _add_primitive_type.remove_row(NR_FILTER_TILE);
2100     _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2102     // Initialize widget hierarchy
2103     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2104     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2105     Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox(/*homogeneous:*/false, /*spacing:*/4));
2106     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2108     _getContents()->add(*hpaned);
2109     hpaned->pack1(_filter_modifier);
2110     hpaned->pack2(_primitive_box);
2111     _primitive_box.pack_start(*sw_prims);
2112     _primitive_box.pack_start(*hb_prims, false, false);
2113     _primitive_box.pack_start(*infobox,false, false);
2114     sw_prims->add(_primitive_list);
2115     infobox->pack_start(_infobox_icon, false, false);
2116     infobox->pack_start(_infobox_desc, false, false);
2117     _infobox_desc.set_line_wrap(true);
2118     _infobox_desc.set_size_request(200, -1);
2120     hb_prims->pack_start(_add_primitive, false, false);
2121     hb_prims->pack_start(_add_primitive_type, false, false);
2122     _getContents()->pack_start(_settings_tabs, false, false);
2123     _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2124     _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2126     _primitive_list.signal_primitive_changed().connect(
2127         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2128     _filter_modifier.signal_filter_changed().connect(
2129         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2131     _add_primitive_type.signal_changed().connect(
2132         sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2134     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2135     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2136 //    al_settings->set_padding(0, 0, 12, 0);
2137 //    fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2138 //    ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2139     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2140     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2141                                                sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2143     show_all_children();
2144     init_settings_widgets();
2145     _primitive_list.update();
2146     update_primitive_infobox();
2149 FilterEffectsDialog::~FilterEffectsDialog()
2151     delete _settings;
2152     delete _filter_general_settings;
2155 void FilterEffectsDialog::set_attrs_locked(const bool l)
2157     _locked = l;
2160 void FilterEffectsDialog::show_all_vfunc()
2162     UI::Widget::Panel::show_all_vfunc();
2164     update_settings_view();
2167 void FilterEffectsDialog::init_settings_widgets()
2169     // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2170     //       most of the current values are complete guesses!
2172     _empty_settings.set_sensitive(false);
2173     _settings_tab1.pack_start(_empty_settings);
2175     _no_filter_selected.set_sensitive(false);
2176     _settings_tab2.pack_start(_no_filter_selected);
2177     _settings_initialized = true;
2179     _filter_general_settings->type(0);
2180     _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"));
2181     _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"));
2183     _settings->type(NR_FILTER_BLEND);
2184     _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode:"), BlendModeConverter);
2186     _settings->type(NR_FILTER_COLORMATRIX);
2187     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."));
2188     _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s):"));
2189     colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2191     _settings->type(NR_FILTER_COMPONENTTRANSFER);
2192     _settings->add_notimplemented();
2193     /*
2194     //TRANSLATORS: for info on "Slope" and "Intercept", see http://id.mind.net/~zona/mmts/functionInstitute/linearFunctions/lsif.html
2195     _settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2196     _ct_slope = _settings->add_spinslider(1, SP_ATTR_SLOPE, _("Slope"), -10, 10, 0.1, 0.01, 2);
2197     _ct_intercept = _settings->add_spinslider(0, SP_ATTR_INTERCEPT, _("Intercept"), -10, 10, 0.1, 0.01, 2);
2198     _ct_amplitude = _settings->add_spinslider(1, SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 10, 0.1, 0.01, 2);
2199     _ct_exponent = _settings->add_spinslider(1, SP_ATTR_EXPONENT, _("Exponent"), 0, 10, 0.1, 0.01, 2);
2200     _ct_offset = _settings->add_spinslider(0, SP_ATTR_OFFSET, _("Offset"), -10, 10, 0.1, 0.01, 2);*/
2202     _settings->type(NR_FILTER_COMPOSITE);
2203     _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator:"), CompositeOperatorConverter);
2204     _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."));
2205     _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."));
2206     _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."));
2207     _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."));
2209     _settings->type(NR_FILTER_CONVOLVEMATRIX);
2210     _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"));
2211     _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."));
2212     //TRANSLATORS: for info on "Kernel", see http://en.wikipedia.org/wiki/Kernel_(matrix)
2213     _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."));
2214     _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2215     _settings->add_spinslider(0, SP_ATTR_DIVISOR, _("Divisor:"), 0, 1000, 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."));
2216     _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."));
2217     _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."));
2218     _settings->add_checkbutton(false, SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive."));
2220     _settings->type(NR_FILTER_DIFFUSELIGHTING);
2221     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Diffuse Color:"), _("Defines the color of the light source"));
2222     _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale:"), -5, 5, 0.01, 0.001, 3, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2223     _settings->add_spinslider(1, SP_ATTR_DIFFUSECONSTANT, _("Constant:"), 0, 5, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2224     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2225     _settings->add_lightsource();
2227     _settings->type(NR_FILTER_DISPLACEMENTMAP);
2228     _settings->add_spinslider(0, SP_ATTR_SCALE, _("Scale:"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2229     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2230     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2232     _settings->type(NR_FILTER_FLOOD);
2233     _settings->add_color(/*default: black*/ 0, SP_PROP_FLOOD_COLOR, _("Flood Color:"), _("The whole filter region will be filled with this color."));
2234     _settings->add_spinslider(1, SP_PROP_FLOOD_OPACITY, _("Opacity:"), 0, 1, 0.1, 0.01, 2);
2236     _settings->type(NR_FILTER_GAUSSIANBLUR);
2237     _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation:"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2239     _settings->type(NR_FILTER_MERGE);
2240     _settings->add_no_params();
2242     _settings->type(NR_FILTER_MORPHOLOGY);
2243     _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator:"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2244     _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius:"), 0, 100, 1, 0.01, 1);
2246     _settings->type(NR_FILTER_IMAGE);
2247     _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image:"));
2249     _settings->type(NR_FILTER_OFFSET);
2250     _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"));
2251     _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"));
2253     _settings->type(NR_FILTER_SPECULARLIGHTING);
2254     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Specular Color:"), _("Defines the color of the light source"));
2255     _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale:"), -5, 5, 0.1, 0.01, 2, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2256     _settings->add_spinslider(1, SP_ATTR_SPECULARCONSTANT, _("Constant:"), 0, 5, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2257     _settings->add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Exponent:"), 1, 50, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2258     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2259     _settings->add_lightsource();
2261     _settings->type(NR_FILTER_TILE);
2262     _settings->add_notimplemented();
2264     _settings->type(NR_FILTER_TURBULENCE);
2265 //    _settings->add_checkbutton(false, SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2266     _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type:"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2267     _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency:"), 0, 0.4, 0.001, 0.01, 3);
2268     _settings->add_spinslider(1, SP_ATTR_NUMOCTAVES, _("Octaves:"), 1, 10, 1, 1, 0);
2269     _settings->add_spinslider(0, SP_ATTR_SEED, _("Seed:"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2272 void FilterEffectsDialog::add_primitive()
2274     SPFilter* filter = _filter_modifier.get_selected_filter();
2276     if(filter) {
2277         SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2279         _primitive_list.select(prim);
2281         DocumentUndo::done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2282     }
2285 void FilterEffectsDialog::update_primitive_infobox()
2287     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2288     if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2289         _infobox_icon.show();
2290         _infobox_desc.show();
2291     } else {
2292         _infobox_icon.hide();
2293         _infobox_desc.hide();
2294     }
2295     switch(_add_primitive_type.get_active_data()->id){
2296         case(NR_FILTER_BLEND):
2297             _infobox_icon.set_from_icon_name("feBlend-icon", Gtk::ICON_SIZE_DIALOG);
2298             _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2299             break;
2300         case(NR_FILTER_COLORMATRIX):
2301             _infobox_icon.set_from_icon_name("feColorMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2302             _infobox_desc.set_markup(_("The <b>feColorMatrix</b> filter primitive applies a matrix transformation to color of each rendered pixel. This allows for effects like turning object to grayscale, modifying color saturation and changing color hue."));
2303             break;
2304         case(NR_FILTER_COMPONENTTRANSFER):
2305             _infobox_icon.set_from_icon_name("feComponentTransfer-icon", Gtk::ICON_SIZE_DIALOG);
2306             _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."));
2307             break;
2308         case(NR_FILTER_COMPOSITE):
2309             _infobox_icon.set_from_icon_name("feComposite-icon", Gtk::ICON_SIZE_DIALOG);
2310             _infobox_desc.set_markup(_("The <b>feComposite</b> filter primitive composites two images using one of the Porter-Duff blending modes or the arithmetic mode described in SVG standard. Porter-Duff blending modes are essentially logical operations between the corresponding pixel values of the images."));
2311             break;
2312         case(NR_FILTER_CONVOLVEMATRIX):
2313             _infobox_icon.set_from_icon_name("feConvolveMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2314             _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."));
2315             break;
2316         case(NR_FILTER_DIFFUSELIGHTING):
2317             _infobox_icon.set_from_icon_name("feDiffuseLighting-icon", Gtk::ICON_SIZE_DIALOG);
2318             _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."));
2319             break;
2320         case(NR_FILTER_DISPLACEMENTMAP):
2321             _infobox_icon.set_from_icon_name("feDisplacementMap-icon", Gtk::ICON_SIZE_DIALOG);
2322             _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."));
2323             break;
2324         case(NR_FILTER_FLOOD):
2325             _infobox_icon.set_from_icon_name("feFlood-icon", Gtk::ICON_SIZE_DIALOG);
2326             _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."));
2327             break;
2328         case(NR_FILTER_GAUSSIANBLUR):
2329             _infobox_icon.set_from_icon_name("feGaussianBlur-icon", Gtk::ICON_SIZE_DIALOG);
2330             _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."));
2331             break;
2332         case(NR_FILTER_IMAGE):
2333             _infobox_icon.set_from_icon_name("feImage-icon", Gtk::ICON_SIZE_DIALOG);
2334             _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2335             break;
2336         case(NR_FILTER_MERGE):
2337             _infobox_icon.set_from_icon_name("feMerge-icon", Gtk::ICON_SIZE_DIALOG);
2338             _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."));
2339             break;
2340         case(NR_FILTER_MORPHOLOGY):
2341             _infobox_icon.set_from_icon_name("feMorphology-icon", Gtk::ICON_SIZE_DIALOG);
2342             _infobox_desc.set_markup(_("The <b>feMorphology</b> filter primitive provides erode and dilate effects. For single-color objects erode makes the object thinner and dilate makes it thicker."));
2343             break;
2344         case(NR_FILTER_OFFSET):
2345             _infobox_icon.set_from_icon_name("feOffset-icon", Gtk::ICON_SIZE_DIALOG);
2346             _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."));
2347             break;
2348         case(NR_FILTER_SPECULARLIGHTING):
2349             _infobox_icon.set_from_icon_name("feSpecularLighting-icon", Gtk::ICON_SIZE_DIALOG);
2350             _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."));
2351             break;
2352         case(NR_FILTER_TILE):
2353             _infobox_icon.set_from_icon_name("feTile-icon", Gtk::ICON_SIZE_DIALOG);
2354             _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2355             break;
2356         case(NR_FILTER_TURBULENCE):
2357             _infobox_icon.set_from_icon_name("feTurbulence-icon", Gtk::ICON_SIZE_DIALOG);
2358             _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."));
2359             break;
2360         default:
2361             g_assert(false);
2362             break;
2363     }
2364     _infobox_icon.set_pixel_size(96);
2367 void FilterEffectsDialog::duplicate_primitive()
2369     SPFilter* filter = _filter_modifier.get_selected_filter();
2370     SPFilterPrimitive* origprim = _primitive_list.get_selected();
2372     if(filter && origprim) {
2373         Inkscape::XML::Node *repr;
2374         repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2375         SP_OBJECT_REPR(filter)->appendChild(repr);
2377         DocumentUndo::done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2379         _primitive_list.update();
2380     }
2383 void FilterEffectsDialog::convolve_order_changed()
2385     _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2386     _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2387     _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2390 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2392     set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2395 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2397     if(!_locked) {
2398         _attr_lock = true;
2399         SPFilter *filter = _filter_modifier.get_selected_filter();
2400         const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2401         if (filter && name && SP_OBJECT_REPR(filter)){
2402             SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2403             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2404         }
2405         _attr_lock = false;
2406     }
2409 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2411     set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2414 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2416     if(!_locked) {
2417         _attr_lock = true;
2419         SPFilter *filter = _filter_modifier.get_selected_filter();
2420         const gchar* name = (const gchar*)sp_attribute_name(attr);
2421         if(filter && name && o) {
2422             update_settings_sensitivity();
2424             SP_OBJECT_REPR(o)->setAttribute(name, val);
2425             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2427             Glib::ustring undokey = "filtereffects:";
2428             undokey += name;
2429             DocumentUndo::maybeDone(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2430                                     _("Set filter primitive attribute"));
2431         }
2433         _attr_lock = false;
2434     }
2437 void FilterEffectsDialog::update_filter_general_settings_view()
2439     if(_settings_initialized != true) return;
2441     if(!_locked) {
2442         _attr_lock = true;
2444         SPFilter* filter = _filter_modifier.get_selected_filter();
2446         if(filter) {
2447             _filter_general_settings->show_and_update(0, filter);
2448             _no_filter_selected.hide();
2449         }
2450         else {
2451             std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2452             vect[0]->hide_all();
2453             _no_filter_selected.show();
2454         }
2456         _attr_lock = false;
2457     }
2460 void FilterEffectsDialog::update_settings_view()
2462     update_settings_sensitivity();
2464     if(_attr_lock)
2465         return;
2467 //First Tab
2469     std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2470     for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2471     _empty_settings.show();
2473     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2474     if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2475         _infobox_icon.show();
2476         _infobox_desc.show();
2477     } else {
2478         _infobox_icon.hide();
2479         _infobox_desc.hide();
2480     }
2482     SPFilterPrimitive* prim = _primitive_list.get_selected();
2484     if(prim) {
2486         //XML Tree being used directly here while it shouldn't be.
2487         _settings->show_and_update(FPConverter.get_id_from_key(prim->getRepr()->name()), prim);
2488         _empty_settings.hide();
2489     }
2491 //Second Tab
2493     std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2494     vect2[0]->hide_all();
2495     _no_filter_selected.show();
2497     SPFilter* filter = _filter_modifier.get_selected_filter();
2499     if(filter) {
2500         _filter_general_settings->show_and_update(0, filter);
2501         _no_filter_selected.hide();
2502     }
2506 void FilterEffectsDialog::update_settings_sensitivity()
2508     SPFilterPrimitive* prim = _primitive_list.get_selected();
2509     const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2510     _k1->set_sensitive(use_k);
2511     _k2->set_sensitive(use_k);
2512     _k3->set_sensitive(use_k);
2513     _k4->set_sensitive(use_k);
2515 // Component transfer not yet implemented
2516 /*
2517     if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2518         SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2519         const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2520         const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2522         _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2523         _ct_slope->set_sensitive(linear);
2524         _ct_intercept->set_sensitive(linear);
2525         _ct_amplitude->set_sensitive(gamma);
2526         _ct_exponent->set_sensitive(gamma);
2527         _ct_offset->set_sensitive(gamma);
2528     }
2529 */
2532 void FilterEffectsDialog::update_color_matrix()
2534     _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2537 } // namespace Dialog
2538 } // namespace UI
2539 } // namespace Inkscape
2541 /*
2542   Local Variables:
2543   mode:c++
2544   c-file-style:"stroustrup"
2545   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2546   indent-tabs-mode:nil
2547   fill-column:99
2548   End:
2549 */
2550 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :