Code

ed7103be3c801a91fc4628d271e403d0e13c38b1
[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 "ui/dialog/filedialog.h"
62 #include "verbs.h"
63 #include "xml/node.h"
64 #include "xml/node-observer.h"
65 #include "xml/repr.h"
66 #include <sstream>
68 #include "io/sys.h"
69 #include <iostream>
71 using namespace Inkscape::Filters;
73 namespace Inkscape {
74 namespace UI {
75 namespace Dialog {
77 using Inkscape::UI::Widget::AttrWidget;
78 using Inkscape::UI::Widget::ComboBoxEnum;
79 using Inkscape::UI::Widget::DualSpinSlider;
80 using Inkscape::UI::Widget::SpinSlider;
83 // Returns the number of inputs available for the filter primitive type
84 int input_count(const SPFilterPrimitive* prim)
85 {
86     if(!prim)
87         return 0;
88     else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
89         return 2;
90     else if(SP_IS_FEMERGE(prim)) {
91         // Return the number of feMergeNode connections plus an extra
92         int count = 1;
93         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count){};
94         return count;
95     }
96     else
97         return 1;
98 }
100 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
102 public:
103     CheckButtonAttr(bool def, const Glib::ustring& label,
104                     const Glib::ustring& tv, const Glib::ustring& fv,
105                     const SPAttributeEnum a, char* tip_text)
106         : Gtk::CheckButton(label),
107           AttrWidget(a, def),
108           _true_val(tv), _false_val(fv)
109     {
110         signal_toggled().connect(signal_attr_changed().make_slot());
111         if (tip_text) _tt.set_tip(*this, tip_text);
112     }
114     Glib::ustring get_as_attribute() const
115     {
116         return get_active() ? _true_val : _false_val;
117     }
119     void set_from_attribute(SPObject* o)
120     {
121         const gchar* val = attribute_value(o);
122         if(val) {
123             if(_true_val == val)
124                 set_active(true);
125             else if(_false_val == val)
126                 set_active(false);
127         } else {
128             set_active(get_default()->as_bool());
129         }
130     }
131 private:
132     const Glib::ustring _true_val, _false_val;
133 };
135 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
137 public:
138     SpinButtonAttr(double lower, double upper, double step_inc,
139                    double climb_rate, int digits, const SPAttributeEnum a, double def, char* tip_text)
140         : Gtk::SpinButton(climb_rate, digits),
141           AttrWidget(a, def)
142     {
143         if (tip_text) _tt.set_tip(*this, tip_text);
144         set_range(lower, upper);
145         set_increments(step_inc, 0);
147         signal_value_changed().connect(signal_attr_changed().make_slot());
148     }
150     Glib::ustring get_as_attribute() const
151     {
152         const double val = get_value();
154         if(get_digits() == 0)
155             return Glib::Ascii::dtostr((int)val);
156         else
157             return Glib::Ascii::dtostr(val);
158     }
160     void set_from_attribute(SPObject* o)
161     {
162         const gchar* val = attribute_value(o);
163         if(val){
164             set_value(Glib::Ascii::strtod(val));
165         } else {
166             set_value(get_default()->as_double());
167         }
168     }
169 };
171 template< typename T> class ComboWithTooltip : public Gtk::EventBox
173 public:
174     ComboWithTooltip<T>(T default_value, const Util::EnumDataConverter<T>& c, const SPAttributeEnum a = SP_ATTR_INVALID, char* tip_text = NULL)
175     {
176         if (tip_text) {
177             _tt.set_tip(*this, tip_text);
178         }
179         combo = new ComboBoxEnum<T>(default_value, c, a, false);
180         add(*combo);
181         show_all();
182     }
184     ~ComboWithTooltip()
185     {
186         delete combo;
187     }
189     ComboBoxEnum<T>* get_attrwidget()
190     {
191         return combo;
192     }
193 private:
194     Gtk::Tooltips _tt;
195     ComboBoxEnum<T>* combo;
196 };
198 // Contains an arbitrary number of spin buttons that use seperate attributes
199 class MultiSpinButton : public Gtk::HBox
201 public:
202     MultiSpinButton(double lower, double upper, double step_inc,
203                     double climb_rate, int digits, std::vector<SPAttributeEnum> attrs, std::vector<double> default_values, std::vector<char*> tip_text)
204     {
205         g_assert(attrs.size()==default_values.size());
206         g_assert(attrs.size()==tip_text.size());
207         for(unsigned i = 0; i < attrs.size(); ++i) {
208             _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i], default_values[i], tip_text[i]));
209             pack_start(*_spins.back(), false, false);
210         }
211     }
213     ~MultiSpinButton()
214     {
215         for(unsigned i = 0; i < _spins.size(); ++i)
216             delete _spins[i];
217     }
219     std::vector<SpinButtonAttr*>& get_spinbuttons()
220     {
221         return _spins;
222     }
223 private:
224     std::vector<SpinButtonAttr*> _spins;
225 };
227 // Contains two spinbuttons that describe a NumberOptNumber
228 class DualSpinButton : public Gtk::HBox, public AttrWidget
230 public:
231     DualSpinButton(char* def, double lower, double upper, double step_inc,
232                    double climb_rate, int digits, const SPAttributeEnum a, char* tt1, char* tt2)
233         : AttrWidget(a, def), //TO-DO: receive default num-opt-num as parameter in the constructor
234           _s1(climb_rate, digits), _s2(climb_rate, digits)
235     {
236         if (tt1) _tt.set_tip(_s1, tt1);
237         if (tt2) _tt.set_tip(_s2, tt2);
238         _s1.set_range(lower, upper);
239         _s2.set_range(lower, upper);
240         _s1.set_increments(step_inc, 0);
241         _s2.set_increments(step_inc, 0);
243         _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
244         _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
246         pack_start(_s1, false, false);
247         pack_start(_s2, false, false);
248     }
250     Gtk::SpinButton& get_spinbutton1()
251     {
252         return _s1;
253     }
255     Gtk::SpinButton& get_spinbutton2()
256     {
257         return _s2;
258     }
260     virtual Glib::ustring get_as_attribute() const
261     {
262         double v1 = _s1.get_value();
263         double v2 = _s2.get_value();
265         if(_s1.get_digits() == 0) {
266             v1 = (int)v1;
267             v2 = (int)v2;
268         }
270         return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
271     }
273     virtual void set_from_attribute(SPObject* o)
274     {
275         const gchar* val = attribute_value(o);
276         NumberOptNumber n;
277         if(val) {
278             n.set(val);
279         } else {
280             n.set(get_default()->as_charptr());
281         }
282         _s1.set_value(n.getNumber());
283         _s2.set_value(n.getOptNumber());
285     }
286 private:
287     Gtk::SpinButton _s1, _s2;
288 };
290 class ColorButton : public Gtk::ColorButton, public AttrWidget
292 public:
293     ColorButton(unsigned int def, const SPAttributeEnum a, char* tip_text)
294         : AttrWidget(a, def)
295     {
296         signal_color_set().connect(signal_attr_changed().make_slot());
297         if (tip_text) _tt.set_tip(*this, tip_text);
299         Gdk::Color col;
300         col.set_rgb(65535, 65535, 65535);
301         set_color(col);
302     }
304     // Returns the color in 'rgb(r,g,b)' form.
305     Glib::ustring get_as_attribute() const
306     {
307         std::ostringstream os;
308         const Gdk::Color c = get_color();
309         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?
310         os << "rgb(" << r << "," << g << "," << b << ")";
311         return os.str();
312     }
315     void set_from_attribute(SPObject* o)
316     {
317         const gchar* val = attribute_value(o);
318         guint32 i = 0;
319         if(val) {
320             i = sp_svg_read_color(val, 0xFFFFFFFF);
321         } else {
322             i = (guint32) get_default()->as_uint();
323         }
324         const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
325         Gdk::Color col;
326         col.set_rgb(r * 256, g * 256, b * 256);
327         set_color(col);
328     }
329 };
331 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
332 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
334 public:
335     MatrixAttr(const SPAttributeEnum a, char* tip_text = NULL)
336         : AttrWidget(a), _locked(false)
337     {
338         _model = Gtk::ListStore::create(_columns);
339         _tree.set_model(_model);
340         _tree.set_headers_visible(false);
341         _tree.show();
342         add(_tree);
343         set_shadow_type(Gtk::SHADOW_IN);
344         if (tip_text) _tt.set_tip(_tree, tip_text);
345     }
347     std::vector<double> get_values() const
348     {
349         std::vector<double> vec;
350         for(Gtk::TreeIter iter = _model->children().begin();
351             iter != _model->children().end(); ++iter) {
352             for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
353                 vec.push_back((*iter)[_columns.cols[c]]);
354         }
355         return vec;
356     }
358     void set_values(const std::vector<double>& v)
359     {
360         unsigned i = 0;
361         for(Gtk::TreeIter iter = _model->children().begin();
362             iter != _model->children().end(); ++iter) {
363             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
364                 if(i >= v.size())
365                     return;
366                 (*iter)[_columns.cols[c]] = v[i];
367                 ++i;
368             }
369         }
370     }
372     Glib::ustring get_as_attribute() const
373     {
374         std::ostringstream os;
376         for(Gtk::TreeIter iter = _model->children().begin();
377             iter != _model->children().end(); ++iter) {
378             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
379                 os << (*iter)[_columns.cols[c]] << " ";
380             }
381         }
383         return os.str();
384     }
386     void set_from_attribute(SPObject* o)
387     {
388         if(o) {
389             if(SP_IS_FECONVOLVEMATRIX(o)) {
390                 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
391                 int cols, rows;
392                 cols = (int)conv->order.getNumber();
393                 if(cols > 5)
394                     cols = 5;
395                 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
396                 update(o, rows, cols);
397             }
398             else if(SP_IS_FECOLORMATRIX(o))
399                 update(o, 4, 5);
400         }
401     }
402 private:
403     class MatrixColumns : public Gtk::TreeModel::ColumnRecord
404     {
405     public:
406         MatrixColumns()
407         {
408             cols.resize(5);
409             for(unsigned i = 0; i < cols.size(); ++i)
410                 add(cols[i]);
411         }
412         std::vector<Gtk::TreeModelColumn<double> > cols;
413     };
415     void update(SPObject* o, const int rows, const int cols)
416     {
417         if(_locked)
418             return;
420         _model->clear();
422         _tree.remove_all_columns();
424         std::vector<gdouble>* values = NULL;
425         if(SP_IS_FECOLORMATRIX(o))
426             values = &SP_FECOLORMATRIX(o)->values;
427         else if(SP_IS_FECONVOLVEMATRIX(o))
428             values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
429         else
430             return;
432         if(o) {
433             int ndx = 0;
435             for(int i = 0; i < cols; ++i) {
436                 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
437                 dynamic_cast<Gtk::CellRendererText*>(
438                     _tree.get_column_cell_renderer(i))->signal_edited().connect(
439                         sigc::mem_fun(*this, &MatrixAttr::rebind));
440             }
442             for(int r = 0; r < rows; ++r) {
443                 Gtk::TreeRow row = *(_model->append());
444                 // Default to identity matrix
445                 for(int c = 0; c < cols; ++c, ++ndx)
446                     row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
447             }
448         }
449     }
451     void rebind(const Glib::ustring&, const Glib::ustring&)
452     {
453         _locked = true;
454         signal_attr_changed()();
455         _locked = false;
456     }
458     bool _locked;
459     Gtk::TreeView _tree;
460     Glib::RefPtr<Gtk::ListStore> _model;
461     MatrixColumns _columns;
462 };
464 // Displays a matrix or a slider for feColorMatrix
465 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
467 public:
468     ColorMatrixValues()
469         : AttrWidget(SP_ATTR_VALUES),
470           // TRANSLATORS: this dialog is accessible via menu Filters - Filter editor
471           _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.")),
472           _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
473           _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
474           _label(_("None"), Gtk::ALIGN_LEFT),
475           _use_stored(false),
476           _saturation_store(0),
477           _angle_store(0)
478     {
479         _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
480         _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
481         _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
482         signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
484         _matrix.show();
485         _saturation.show();
486         _angle.show();
487         _label.show();
488         _label.set_sensitive(false);
490         set_shadow_type(Gtk::SHADOW_NONE);
491     }
493     virtual void set_from_attribute(SPObject* o)
494     {
495         if(SP_IS_FECOLORMATRIX(o)) {
496             SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
497             remove();
498             switch(col->type) {
499                 case COLORMATRIX_SATURATE:
500                     add(_saturation);
501                     if(_use_stored)
502                         _saturation.set_value(_saturation_store);
503                     else
504                         _saturation.set_from_attribute(o);
505                     break;
506                 case COLORMATRIX_HUEROTATE:
507                     add(_angle);
508                     if(_use_stored)
509                         _angle.set_value(_angle_store);
510                     else
511                         _angle.set_from_attribute(o);
512                     break;
513                 case COLORMATRIX_LUMINANCETOALPHA:
514                     add(_label);
515                     break;
516                 case COLORMATRIX_MATRIX:
517                 default:
518                     add(_matrix);
519                     if(_use_stored)
520                         _matrix.set_values(_matrix_store);
521                     else
522                         _matrix.set_from_attribute(o);
523                     break;
524             }
525             _use_stored = true;
526         }
527     }
529     virtual Glib::ustring get_as_attribute() const
530     {
531         const Widget* w = get_child();
532         if(w == &_label)
533             return "";
534         else
535             return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
536     }
538     void clear_store()
539     {
540         _use_stored = false;
541     }
542 private:
543     void update_store()
544     {
545         const Widget* w = get_child();
546         if(w == &_matrix)
547             _matrix_store = _matrix.get_values();
548         else if(w == &_saturation)
549             _saturation_store = _saturation.get_value();
550         else if(w == &_angle)
551             _angle_store = _angle.get_value();
552     }
554     MatrixAttr _matrix;
555     SpinSlider _saturation;
556     SpinSlider _angle;
557     Gtk::Label _label;
559     // Store separate values for the different color modes
560     bool _use_stored;
561     std::vector<double> _matrix_store;
562     double _saturation_store;
563     double _angle_store;
564 };
566 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
568 //Displays a chooser for feImage input
569 //It may be a filename or the id for an SVG Element
570 //described in xlink:href syntax
571 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
573 public:
574     FileOrElementChooser(const SPAttributeEnum a)
575         : AttrWidget(a)
576     {
577         pack_start(_entry, false, false);
578         pack_start(_fromFile, false, false);
579         pack_start(_fromSVGElement, false, false);
581         _fromFile.set_label(_("Image File"));
582         _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
584         _fromSVGElement.set_label(_("Selected SVG Element"));
585         _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
587         _entry.signal_changed().connect(signal_attr_changed().make_slot());
589         show_all();
591     }
593     // Returns the element in xlink:href form.
594     Glib::ustring get_as_attribute() const
595     {
596         return _entry.get_text();
597     }
600     void set_from_attribute(SPObject* o)
601     {
602         const gchar* val = attribute_value(o);
603         if(val) {
604             _entry.set_text(val);
605         } else {
606             _entry.set_text("");
607         }
608     }
610     void set_desktop(SPDesktop* d){
611         _desktop = d;
612     }
614 private:
615     void select_svg_element(){
616         Inkscape::Selection* sel = sp_desktop_selection(_desktop);
617         if (sel->isEmpty()) return;
618         Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
619         if (!node || !node->matchAttributeName("id")) return;
621         std::ostringstream xlikhref;
622         xlikhref << "#" << node->attribute("id");
623         _entry.set_text(xlikhref.str());
624     }
626     void select_file(){
628         //# Get the current directory for finding files
629         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
630         Glib::ustring open_path;
631         Glib::ustring attr = prefs->getString("/dialogs/open/path");
632         if (!attr.empty())
633             open_path = attr;
635         //# Test if the open_path directory exists
636         if (!Inkscape::IO::file_test(open_path.c_str(),
637                   (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
638             open_path = "";
640         //# If no open path, default to our home directory
641         if (open_path.size() < 1)
642             {
643             open_path = g_get_home_dir();
644             open_path.append(G_DIR_SEPARATOR_S);
645             }
647         //# Create a dialog if we don't already have one
648         if (!selectFeImageFileInstance) {
649             selectFeImageFileInstance =
650                   Inkscape::UI::Dialog::FileOpenDialog::create(
651                      *_desktop->getToplevel(),
652                      open_path,
653                      Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not just svg*/
654                      (char const *)_("Select an image to be used as feImage input"));
655         }
657         //# Show the dialog
658         bool const success = selectFeImageFileInstance->show();
659         if (!success)
660             return;
662         //# User selected something.  Get name and type
663         Glib::ustring fileName = selectFeImageFileInstance->getFilename();
665         if (fileName.size() > 0) {
667             Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
669             if ( newFileName.size() > 0)
670                 fileName = newFileName;
671             else
672                 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
674             open_path = fileName;
675             open_path.append(G_DIR_SEPARATOR_S);
676             prefs->setString("/dialogs/open/path", open_path);
678             _entry.set_text(fileName);
679         }
680         return;
681     }
683     Gtk::Entry _entry;
684     Gtk::Button _fromFile;
685     Gtk::Button _fromSVGElement;
686     SPDesktop* _desktop;
687 };
689 class FilterEffectsDialog::Settings
691 public:
692     typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
694     Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
695         : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
696     {
697         _groups.resize(_max_types);
698         _attrwidgets.resize(_max_types);
699         _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
701         for(int i = 0; i < _max_types; ++i) {
702             _groups[i] = new Gtk::VBox;
703             b.pack_start(*_groups[i], false, false);
704         }
705         _current_type = 0;
706     }
708     ~Settings()
709     {
710         for(int i = 0; i < _max_types; ++i) {
711             delete _groups[i];
712             for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
713                 delete _attrwidgets[i][j];
714         }
715     }
717     // Show the active settings group and update all the AttrWidgets with new values
718     void show_and_update(const int t, SPObject* ob)
719     {
720         if(t != _current_type) {
721             type(t);
722             for(unsigned i = 0; i < _groups.size(); ++i)
723                 _groups[i]->hide();
724         }
725         if(t >= 0)
726             _groups[t]->show_all();
728         _dialog.set_attrs_locked(true);
729         for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
730             _attrwidgets[_current_type][i]->set_from_attribute(ob);
731         _dialog.set_attrs_locked(false);
732     }
734     int get_current_type() const
735     {
736         return _current_type;
737     }
739     void type(const int t)
740     {
741         _current_type = t;
742     }
744     void add_no_params()
745     {
746         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
747         add_widget(lbl, "");
748     }
750     void add_notimplemented()
751     {
752         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
753         add_widget(lbl, "");
754     }
756     // LightSource
757     LightSourceControl* add_lightsource();
759     // CheckBox
760     CheckButtonAttr* add_checkbutton(bool def, const SPAttributeEnum attr, const Glib::ustring& label,
761                                      const Glib::ustring& tv, const Glib::ustring& fv, char* tip_text = NULL)
762     {
763         CheckButtonAttr* cb = new CheckButtonAttr(def, label, tv, fv, attr, tip_text);
764         add_widget(cb, "");
765         add_attr_widget(cb);
766         return cb;
767     }
769     // ColorButton
770     ColorButton* add_color(unsigned int def, const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text = NULL)
771     {
772         ColorButton* col = new ColorButton(def, attr, tip_text);
773         add_widget(col, label);
774         add_attr_widget(col);
775         return col;
776     }
778     // Matrix
779     MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text)
780     {
781         MatrixAttr* conv = new MatrixAttr(attr, tip_text);
782         add_widget(conv, label);
783         add_attr_widget(conv);
784         return conv;
785     }
787     // ColorMatrixValues
788     ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
789     {
790         ColorMatrixValues* cmv = new ColorMatrixValues();
791         add_widget(cmv, label);
792         add_attr_widget(cmv);
793         return cmv;
794     }
796     // SpinSlider
797     SpinSlider* add_spinslider(double def, const SPAttributeEnum attr, const Glib::ustring& label,
798                          const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
799     {
800         SpinSlider* spinslider = new SpinSlider(def, lo, hi, step_inc, climb, digits, attr, tip_text);
801         add_widget(spinslider, label);
802         add_attr_widget(spinslider);
803         return spinslider;
804     }
806     // DualSpinSlider
807     DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
808                                        const double lo, const double hi, const double step_inc,
809                                        const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
810     {
811         DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
812         add_widget(dss, label);
813         add_attr_widget(dss);
814         return dss;
815     }
817     // DualSpinButton
818     DualSpinButton* add_dualspinbutton(char* defalt_value, const SPAttributeEnum attr, const Glib::ustring& label,
819                                        const double lo, const double hi, const double step_inc,
820                                        const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
821     {
822         DualSpinButton* dsb = new DualSpinButton(defalt_value, lo, hi, step_inc, climb, digits, attr, tip1, tip2);
823         add_widget(dsb, label);
824         add_attr_widget(dsb);
825         return dsb;
826     }
828     // MultiSpinButton
829     MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
830                                          const Glib::ustring& label, const double lo, const double hi,
831                                          const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
832     {
833         std::vector<SPAttributeEnum> attrs;
834         attrs.push_back(attr1);
835         attrs.push_back(attr2);
837         std::vector<double> default_values;
838         default_values.push_back(def1);
839         default_values.push_back(def2);
841         std::vector<char*> tips;
842         tips.push_back(tip1);
843         tips.push_back(tip2);
845         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
846         add_widget(msb, label);
847         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
848             add_attr_widget(msb->get_spinbuttons()[i]);
849         return msb;
850     }
851     MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
852                                          const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
853                                          const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
854     {
855         std::vector<SPAttributeEnum> attrs;
856         attrs.push_back(attr1);
857         attrs.push_back(attr2);
858         attrs.push_back(attr3);
860         std::vector<double> default_values;
861         default_values.push_back(def1);
862         default_values.push_back(def2);
863         default_values.push_back(def3);
865         std::vector<char*> tips;
866         tips.push_back(tip1);
867         tips.push_back(tip2);
868         tips.push_back(tip3);
870         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
871         add_widget(msb, label);
872         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
873             add_attr_widget(msb->get_spinbuttons()[i]);
874         return msb;
875     }
877     // FileOrElementChooser
878     FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
879     {
880         FileOrElementChooser* foech = new FileOrElementChooser(attr);
881         foech->set_desktop(_dialog.getDesktop());
882         add_widget(foech, label);
883         add_attr_widget(foech);
884         return foech;
885     }
887     // ComboBoxEnum
888     template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
889                                   const Glib::ustring& label,
890                                   const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
891     {
892         ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
893         add_widget(combo, label);
894         add_attr_widget(combo->get_attrwidget());
895         return combo->get_attrwidget();
896     }
897 private:
898     Gtk::Tooltips _tt;
900     void add_attr_widget(AttrWidget* a)
901     {
902         _attrwidgets[_current_type].push_back(a);
903         a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
904     }
906     /* Adds a new settings widget using the specified label. The label will be formatted with a colon
907        and all widgets within the setting group are aligned automatically. */
908     void add_widget(Gtk::Widget* w, const Glib::ustring& label)
909     {
910         Gtk::Label *lbl = 0;
911         Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
912         hb->set_spacing(12);
914         if(label != "") {
915             //lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT)); colon now in label (LP #358921)
916             lbl = Gtk::manage(new Gtk::Label(label, Gtk::ALIGN_LEFT));
917             hb->pack_start(*lbl, false, false);
918             _size_group->add_widget(*lbl);
919             lbl->show();
920         }
922         hb->pack_start(*w);
923         _groups[_current_type]->pack_start(*hb);
924         hb->show();
925         w->show();
926     }
928     std::vector<Gtk::VBox*> _groups;
929     Glib::RefPtr<Gtk::SizeGroup> _size_group;
930     FilterEffectsDialog& _dialog;
931     SetAttrSlot _set_attr_slot;
932     std::vector<std::vector< AttrWidget*> > _attrwidgets;
933     int _current_type, _max_types;
934 };
936 // Settings for the three light source objects
937 class FilterEffectsDialog::LightSourceControl : public AttrWidget
939 public:
940     LightSourceControl(FilterEffectsDialog& d)
941         : AttrWidget(SP_ATTR_INVALID),
942           _dialog(d),
943           _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
944           _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
945           _light_source(LightSourceConverter),
946           _locked(false)
947     {
948         _light_box.pack_start(_light_label, false, false);
949         _light_box.pack_start(_light_source);
950         _light_box.show_all();
951         _light_box.set_spacing(12);
952         _dialog._sizegroup->add_widget(_light_label);
954         _box.add(_light_box);
955         _box.reorder_child(_light_box, 0);
956         _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
958         // FIXME: these range values are complete crap
960         _settings.type(LIGHT_DISTANT);
961         _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"));
962         _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"));
964         _settings.type(LIGHT_POINT);
965         _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"));
967         _settings.type(LIGHT_SPOT);
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"));
969         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
970                                       SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
971                                       _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
972         _settings.add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
973         //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.
974         _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."));
975     }
977     Gtk::VBox& get_box()
978     {
979         return _box;
980     }
981 protected:
982     Glib::ustring get_as_attribute() const
983     {
984         return "";
985     }
986     void set_from_attribute(SPObject* o)
987     {
988         if(_locked)
989             return;
991         _locked = true;
993         SPObject* child = o->children;
995         if(SP_IS_FEDISTANTLIGHT(child))
996             _light_source.set_active(0);
997         else if(SP_IS_FEPOINTLIGHT(child))
998             _light_source.set_active(1);
999         else if(SP_IS_FESPOTLIGHT(child))
1000             _light_source.set_active(2);
1001         else
1002             _light_source.set_active(-1);
1004         update();
1006         _locked = false;
1007     }
1008 private:
1009     void on_source_changed()
1010     {
1011         if(_locked)
1012             return;
1014         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1015         if(prim) {
1016             _locked = true;
1018             SPObject* child = prim->children;
1019             const int ls = _light_source.get_active_row_number();
1020             // Check if the light source type has changed
1021             if(!(ls == -1 && !child) &&
1022                !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1023                !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1024                !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1025                 if(child)
1026                     //XML Tree being used directly here while it shouldn't be.
1027                     sp_repr_unparent(child->getRepr());
1029                 if(ls != -1) {
1030                     Inkscape::XML::Document *xml_doc = prim->document->getReprDoc();
1031                     Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1032                     //XML Tree being used directly here while it shouldn't be.
1033                     prim->getRepr()->appendChild(repr);
1034                     Inkscape::GC::release(repr);
1035                 }
1037                 DocumentUndo::done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1038                 update();
1039             }
1041             _locked = false;
1042         }
1043     }
1045     void update()
1046     {
1047         _box.hide_all();
1048         _box.show();
1049         _light_box.show_all();
1051         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1052         if(prim && prim->children)
1053             _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1054     }
1056     FilterEffectsDialog& _dialog;
1057     Gtk::VBox _box;
1058     Settings _settings;
1059     Gtk::HBox _light_box;
1060     Gtk::Label _light_label;
1061     ComboBoxEnum<LightSource> _light_source;
1062     bool _locked;
1063 };
1065 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1067     LightSourceControl* ls = new LightSourceControl(_dialog);
1068     add_attr_widget(ls);
1069     add_widget(&ls->get_box(), "");
1070     return ls;
1073 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1074                                           sigc::slot<void> rem)
1076     Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1078     menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1079     Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1080     menu->append(*mi);
1081     mi->signal_activate().connect(rem);
1082     mi->show();
1083     menu->accelerate(parent);
1085     return menu;
1088 /*** FilterModifier ***/
1089 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1090     : _dialog(d), _add(Gtk::Stock::NEW), _observer(new Inkscape::XML::SignalObserver)
1092     Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1093     pack_start(*sw);
1094     pack_start(_add, false, false);
1095     sw->add(_list);
1097     _model = Gtk::ListStore::create(_columns);
1098     _list.set_model(_model);
1099     _cell_toggle.set_active(true);
1100     const int selcol = _list.append_column("", _cell_toggle);
1101     Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1102     if(col)
1103        col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1104     _list.append_column_editable(_("_Filter"), _columns.label);
1105     ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1106         signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1108     sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1109     sw->set_shadow_type(Gtk::SHADOW_IN);
1110     show_all_children();
1111     _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1112     _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1113     _list.signal_button_release_event().connect_notify(
1114         sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1115     _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1116                               sigc::mem_fun(*this, &FilterModifier::remove_filter));
1117     _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1118                                  _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1119     _menu->accelerate(*this);
1121     _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1122     _observer->signal_changed().connect(signal_filter_changed().make_slot());
1123     g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1124                      G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1126     g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1127                      G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1128     g_signal_connect(G_OBJECT(INKSCAPE), "deactivate_desktop",
1129                      G_CALLBACK(&FilterModifier::on_deactivate_desktop), this);
1131     on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1132     update_filters();
1135 FilterEffectsDialog::FilterModifier::~FilterModifier()
1137    _resource_changed.disconnect();
1138    _doc_replaced.disconnect();
1141 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1143     me->_doc_replaced.disconnect();
1144     me->_doc_replaced = desktop->connectDocumentReplaced(
1145         sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1147     me->_resource_changed.disconnect();
1148     me->_resource_changed =
1149         sp_desktop_document(desktop)->connectResourcesChanged("filter",sigc::mem_fun(me, &FilterModifier::update_filters));
1151     me->_dialog.setDesktop(desktop);
1153     me->update_filters();
1156 void FilterEffectsDialog::FilterModifier::on_deactivate_desktop(Application*, SPDesktop* /*desktop*/, FilterModifier* me)
1158     me->_doc_replaced.disconnect();
1159     me->_resource_changed.disconnect();
1160     me->_dialog.setDesktop(NULL);
1164 // When the selection changes, show the active filter(s) in the dialog
1165 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1166                                                                        Selection *sel,
1167                                                                        FilterModifier* fm)
1169     if(fm && sel)
1170         fm->update_selection(sel);
1173 // Update each filter's sel property based on the current object selection;
1174 //  If the filter is not used by any selected object, sel = 0,
1175 //  otherwise sel is set to the total number of filters in use by selected objects
1176 //  If only one filter is in use, it is selected
1177 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1179     std::set<SPObject*> used;
1181     for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1182         SPObject *obj = SP_OBJECT (i->data);
1183         SPStyle *style = SP_OBJECT_STYLE (obj);
1184         if(!style || !SP_IS_ITEM(obj)) continue;
1186         if(style->filter.set && style->getFilter())
1187             used.insert(style->getFilter());
1188         else
1189             used.insert(0);
1190     }
1192     const int size = used.size();
1194     for(Gtk::TreeIter iter = _model->children().begin();
1195         iter != _model->children().end(); ++iter) {
1196         if(used.find((*iter)[_columns.filter]) != used.end()) {
1197             // If only one filter is in use by the selection, select it
1198             if(size == 1)
1199                 _list.get_selection()->select(iter);
1200             (*iter)[_columns.sel] = size;
1201         }
1202         else
1203             (*iter)[_columns.sel] = 0;
1204     }
1207 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1209     _observer->set(get_selected_filter());
1210     signal_filter_changed()();
1213 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1215     Gtk::TreeModel::iterator iter = _model->get_iter(path);
1217     if(iter) {
1218         SPFilter* filter = (*iter)[_columns.filter];
1219         filter->setLabel(text.c_str());
1220         DocumentUndo::done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1221         if(iter)
1222             (*iter)[_columns.label] = text;
1223     }
1226 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1228     Gtk::TreeIter iter = _model->get_iter(path);
1230     if(iter) {
1231         SPDesktop *desktop = _dialog.getDesktop();
1232         SPDocument *doc = sp_desktop_document(desktop);
1233         SPFilter* filter = (*iter)[_columns.filter];
1234         Inkscape::Selection *sel = sp_desktop_selection(desktop);
1236         /* If this filter is the only one used in the selection, unset it */
1237         if((*iter)[_columns.sel] == 1)
1238             filter = 0;
1240         GSList const *items = sel->itemList();
1242         for (GSList const *i = items; i != NULL; i = i->next) {
1243             SPItem * item = SP_ITEM(i->data);
1244             SPStyle *style = SP_OBJECT_STYLE(item);
1245             g_assert(style != NULL);
1247             if(filter)
1248                 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1249             else
1250                 ::remove_filter(item, false);
1252             SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1253         }
1255         update_selection(sel);
1256         DocumentUndo::done(doc, SP_VERB_DIALOG_FILTER_EFFECTS,  _("Apply filter"));
1257     }
1260 /* Add all filters in the document to the combobox.
1261    Keeps the same selection if possible, otherwise selects the first element */
1262 void FilterEffectsDialog::FilterModifier::update_filters()
1264     SPDesktop* desktop = _dialog.getDesktop();
1265     SPDocument* document = sp_desktop_document(desktop);
1266     const GSList* filters = document->getResourceList("filter");
1268     _model->clear();
1270     for(const GSList *l = filters; l; l = l->next) {
1271         Gtk::TreeModel::Row row = *_model->append();
1272         SPFilter* f = (SPFilter*)l->data;
1273         row[_columns.filter] = f;
1274         const gchar* lbl = f->label();
1275         const gchar* id = f->getId();
1276         row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1277     }
1279     update_selection(desktop->selection);
1280     _dialog.update_filter_general_settings_view();
1283 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1285     if(_list.get_selection()) {
1286         Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1288         if(i)
1289             return (*i)[_columns.filter];
1290     }
1292     return 0;
1295 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1297     if(filter) {
1298         for(Gtk::TreeModel::iterator i = _model->children().begin();
1299             i != _model->children().end(); ++i) {
1300             if((*i)[_columns.filter] == filter) {
1301                 _list.get_selection()->select(i);
1302                 break;
1303             }
1304         }
1305     }
1308 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1310     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1311         const bool sensitive = get_selected_filter() != NULL;
1312         _menu->items()[0].set_sensitive(sensitive);
1313         _menu->items()[1].set_sensitive(sensitive);
1314         _menu->popup(event->button, event->time);
1315     }
1318 void FilterEffectsDialog::FilterModifier::add_filter()
1320     SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1321     SPFilter* filter = new_filter(doc);
1323     const int count = _model->children().size();
1324     std::ostringstream os;
1325     os << _("filter") << count;
1326     filter->setLabel(os.str().c_str());
1328     update_filters();
1330     select_filter(filter);
1332     DocumentUndo::done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1335 void FilterEffectsDialog::FilterModifier::remove_filter()
1337     SPFilter *filter = get_selected_filter();
1339     if(filter) {
1340         SPDocument* doc = filter->document;
1342         //XML Tree being used directly here while it shouldn't be.
1343         sp_repr_unparent(filter->getRepr());
1345         DocumentUndo::done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1347         update_filters();
1348     }
1351 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1353     SPFilter* filter = get_selected_filter();
1355     if(filter) {
1356         Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1357         repr = repr->duplicate(repr->document());
1358         parent->appendChild(repr);
1360         DocumentUndo::done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1362         update_filters();
1363     }
1366 void FilterEffectsDialog::FilterModifier::rename_filter()
1368     _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1371 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1372     : Glib::ObjectBase(typeid(CellRendererConnection)),
1373       _primitive(*this, "primitive", 0)
1374 {}
1376 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1378     return _primitive.get_proxy();
1381 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1383     _text_width = w;
1386 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1388     return _text_width;
1391 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1392     Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1393     int* x_offset, int* y_offset, int* width, int* height) const
1395     PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1397     if(x_offset)
1398         (*x_offset) = 0;
1399     if(y_offset)
1400         (*y_offset) = 0;
1401     if(width)
1402         (*width) = size * primlist.primitive_count() + _text_width * 7;
1403     if(height) {
1404         // Scale the height depending on the number of inputs, unless it's
1405         // the first primitive, in which case there are no connections
1406         SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1407         (*height) = size * input_count(prim);
1408     }
1411 /*** PrimitiveList ***/
1412 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1413     : _dialog(d),
1414       _in_drag(0),
1415       _observer(new Inkscape::XML::SignalObserver)
1417     d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1419     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1420     signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1422     _model = Gtk::ListStore::create(_columns);
1424     set_reorderable(true);
1426     set_model(_model);
1427     append_column(_("_Effect"), _columns.type);
1429     _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1430     get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1431     signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1433     _connection_cell.set_text_width(init_text());
1435     int cols_count = append_column(_("Connections"), _connection_cell);
1436     Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1437     if(col)
1438        col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1441 // Sets up a vertical Pango context/layout, and returns the largest
1442 // width needed to render the FilterPrimitiveInput labels.
1443 int FilterEffectsDialog::PrimitiveList::init_text()
1445     // Set up a vertical context+layout
1446     Glib::RefPtr<Pango::Context> context = create_pango_context();
1447     const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1448     context->set_matrix(matrix);
1449     _vertical_layout = Pango::Layout::create(context);
1451     int maxfont = 0;
1452     for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1453         _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1454         int fontw, fonth;
1455         _vertical_layout->get_pixel_size(fontw, fonth);
1456         if(fonth > maxfont)
1457             maxfont = fonth;
1458     }
1460     return maxfont;
1463 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1465     return _signal_primitive_changed;
1468 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1470     _observer->set(get_selected());
1471     signal_primitive_changed()();
1472     _dialog._color_matrix_values->clear_store();
1475 /* Add all filter primitives in the current to the list.
1476    Keeps the same selection if possible, otherwise selects the first element */
1477 void FilterEffectsDialog::PrimitiveList::update()
1479     SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1480     const SPFilterPrimitive* active_prim = get_selected();
1481     bool active_found = false;
1483     _model->clear();
1485     if(f) {
1486         _dialog._primitive_box.set_sensitive(true);
1487         _dialog.update_filter_general_settings_view();
1488         for(SPObject *prim_obj = f->children;
1489                 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1490                 prim_obj = prim_obj->next) {
1491             SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1492             if(prim) {
1493                 Gtk::TreeModel::Row row = *_model->append();
1494                 row[_columns.primitive] = prim;
1496                 //XML Tree being used directly here while it shouldn't be.
1497                 row[_columns.type_id] = FPConverter.get_id_from_key(prim->getRepr()->name());
1498                 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1499                 row[_columns.id] = prim->getId();
1501                 if(prim == active_prim) {
1502                     get_selection()->select(row);
1503                     active_found = true;
1504                 }
1505             }
1506         }
1508         if(!active_found && _model->children().begin())
1509             get_selection()->select(_model->children().begin());
1511         columns_autosize();
1512     }
1513     else {
1514         _dialog._primitive_box.set_sensitive(false);
1515     }
1518 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1520     _primitive_menu = menu;
1523 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1525     if(_dialog._filter_modifier.get_selected_filter()) {
1526         Gtk::TreeModel::iterator i = get_selection()->get_selected();
1527         if(i)
1528             return (*i)[_columns.primitive];
1529     }
1531     return 0;
1534 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1536     for(Gtk::TreeIter i = _model->children().begin();
1537         i != _model->children().end(); ++i) {
1538         if((*i)[_columns.primitive] == prim)
1539             get_selection()->select(i);
1540     }
1543 void FilterEffectsDialog::PrimitiveList::remove_selected()
1545     SPFilterPrimitive* prim = get_selected();
1547     if(prim) {
1548         _observer->set(0);
1550         //XML Tree being used directly here while it shouldn't be.
1551         sp_repr_unparent(prim->getRepr());
1553         DocumentUndo::done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1554                            _("Remove filter primitive"));
1556         update();
1557     }
1560 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1562     Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1563     Glib::RefPtr<Gdk::Window> win = get_bin_window();
1564     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1566     SPFilterPrimitive* prim = get_selected();
1567     int row_count = get_model()->children().size();
1569     int fheight = CellRendererConnection::size;
1570     Gdk::Rectangle rct, vis;
1571     Gtk::TreeIter row = get_model()->children().begin();
1572     int text_start_x = 0;
1573     if(row) {
1574         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1575         get_visible_rect(vis);
1576         int vis_x, vis_y;
1577         tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1579         text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter._length + 1) + 1;
1580         for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1581             _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1582             const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1583             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());
1584             get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1585             get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1586         }
1587     }
1589     int row_index = 0;
1590     for(; row != get_model()->children().end(); ++row, ++row_index) {
1591         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1592         const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1594         // Check mouse state
1595         int mx, my;
1596         Gdk::ModifierType mask;
1597         get_bin_window()->get_pointer(mx, my, mask);
1599         // Outline the bottom of the connection area
1600         const int outline_x = x + fheight * (row_count - row_index);
1601         get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1603         // Side outline
1604         get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1606         std::vector<Gdk::Point> con_poly;
1607         int con_drag_y = 0;
1608         bool inside;
1609         const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1610         const int inputs = input_count(row_prim);
1612         if(SP_IS_FEMERGE(row_prim)) {
1613             for(int i = 0; i < inputs; ++i) {
1614                 inside = do_connection_node(row, i, con_poly, mx, my);
1615                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1616                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1617                                                inside, con_poly);
1619                 if(_in_drag == (i + 1))
1620                     con_drag_y = con_poly[2].get_y();
1622                 if(_in_drag != (i + 1) || row_prim != prim)
1623                     draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1624             }
1625         }
1626         else {
1627             // Draw "in" shape
1628             inside = do_connection_node(row, 0, con_poly, mx, my);
1629             con_drag_y = con_poly[2].get_y();
1630             get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1631                                            darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1632                                            inside, con_poly);
1634             // Draw "in" connection
1635             if(_in_drag != 1 || row_prim != prim)
1636                 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1638             if(inputs == 2) {
1639                 // Draw "in2" shape
1640                 inside = do_connection_node(row, 1, con_poly, mx, my);
1641                 if(_in_drag == 2)
1642                     con_drag_y = con_poly[2].get_y();
1643                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1644                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1645                                                inside, con_poly);
1646                 // Draw "in2" connection
1647                 if(_in_drag != 2 || row_prim != prim)
1648                     draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1649             }
1650         }
1652         // Draw drag connection
1653         if(row_prim == prim && _in_drag) {
1654             get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1655                                         mx, con_drag_y);
1656             get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1657         }
1658     }
1660     return true;
1663 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1664                                                          const int text_start_x, const int x1, const int y1,
1665                                                          const int row_count)
1667     int src_id = 0;
1668     Gtk::TreeIter res = find_result(input, attr, src_id);
1669     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1670     Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1671     Glib::RefPtr<Gdk::GC> gc;
1673     const bool is_first = input == get_model()->children().begin();
1674     const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1675     const bool use_default = !res && !is_merge;
1677     if(res == input || (use_default && is_first)) {
1678         // Draw straight connection to a standard input
1679         // Draw a lighter line for an implicit connection to a standard input
1680         const int tw = _connection_cell.get_text_width();
1681         gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1682         gc = (use_default && is_first) ? lightgc : darkgc;
1683         get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1684         get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1685     }
1686     else {
1687         // Draw an 'L'-shaped connection to another filter primitive
1688         // If no connection is specified, draw a light connection to the previous primitive
1689         gc = use_default ? lightgc : darkgc;
1691         if(use_default) {
1692             res = input;
1693             --res;
1694         }
1696         if(res) {
1697             Gdk::Rectangle rct;
1699             get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1700             const int fheight = CellRendererConnection::size;
1702             get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1703             const int row_index = find_index(res);
1704             const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1705             const int y2 = rct.get_y() + rct.get_height();
1707             // Draw a bevelled 'L'-shaped connection
1708             get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1709             get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1710             get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1711         }
1712     }
1715 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1716 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1717                                                             std::vector<Gdk::Point>& points,
1718                                                             const int ix, const int iy)
1720     Gdk::Rectangle rct;
1721     const int icnt = input_count((*row)[_columns.primitive]);
1723     get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1724     const int fheight = CellRendererConnection::size;
1726     get_cell_area(_model->get_path(row), *get_column(1), rct);
1727     const float h = rct.get_height() / icnt;
1729     const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1730     const int con_w = (int)(fheight * 0.35f);
1731     const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1732     points.clear();
1733     points.push_back(Gdk::Point(x, con_y));
1734     points.push_back(Gdk::Point(x, con_y + con_w * 2));
1735     points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1737     return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1740 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1741                                                                     const int attr, int& src_id)
1743     SPFilterPrimitive* prim = (*start)[_columns.primitive];
1744     Gtk::TreeIter target = _model->children().end();
1745     int image = 0;
1747     if(SP_IS_FEMERGE(prim)) {
1748         int c = 0;
1749         bool found = false;
1750         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1751             if(c == attr && SP_IS_FEMERGENODE(o)) {
1752                 image = SP_FEMERGENODE(o)->input;
1753                 found = true;
1754             }
1755         }
1756         if(!found)
1757             return target;
1758     }
1759     else {
1760         if(attr == SP_ATTR_IN)
1761             image = prim->image_in;
1762         else if(attr == SP_ATTR_IN2) {
1763             if(SP_IS_FEBLEND(prim))
1764                 image = SP_FEBLEND(prim)->in2;
1765             else if(SP_IS_FECOMPOSITE(prim))
1766                 image = SP_FECOMPOSITE(prim)->in2;
1767             else if(SP_IS_FEDISPLACEMENTMAP(prim))
1768                 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1769             else
1770                 return target;
1771         }
1772         else
1773             return target;
1774     }
1776     if(image >= 0) {
1777         for(Gtk::TreeIter i = _model->children().begin();
1778             i != start; ++i) {
1779             if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1780                 target = i;
1781         }
1782         return target;
1783     }
1784     else if(image < -1) {
1785         src_id = -(image + 2);
1786         return start;
1787     }
1789     return target;
1792 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1794     int i = 0;
1795     for(Gtk::TreeIter iter = _model->children().begin();
1796         iter != target; ++iter, ++i){};
1797     return i;
1800 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1802     Gtk::TreePath path;
1803     Gtk::TreeViewColumn* col;
1804     const int x = (int)e->x, y = (int)e->y;
1805     int cx, cy;
1807     _drag_prim = 0;
1809     if(get_path_at_pos(x, y, path, col, cx, cy)) {
1810         Gtk::TreeIter iter = _model->get_iter(path);
1811         std::vector<Gdk::Point> points;
1813         _drag_prim = (*iter)[_columns.primitive];
1814         const int icnt = input_count(_drag_prim);
1816         for(int i = 0; i < icnt; ++i) {
1817             if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1818                 _in_drag = i + 1;
1819                 break;
1820             }
1821         }
1823         queue_draw();
1824     }
1826     if(_in_drag) {
1827         _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1828         _autoscroll = 0;
1829         get_selection()->select(path);
1830         return true;
1831     }
1832     else
1833         return Gtk::TreeView::on_button_press_event(e);
1836 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1838     const int speed = 10;
1839     const int limit = 15;
1841     Gdk::Rectangle vis;
1842     get_visible_rect(vis);
1843     int vis_x, vis_y;
1844     tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1845     const int top = vis_y + vis.get_height();
1847     // When autoscrolling during a connection drag, set the speed based on
1848     // where the mouse is in relation to the edges.
1849     if(e->y < vis_y)
1850         _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1851     else if(e->y < vis_y + limit)
1852         _autoscroll = -speed;
1853     else if(e->y > top)
1854         _autoscroll = (int)(speed + (e->y - top) / 5);
1855     else if(e->y > top - limit)
1856         _autoscroll = speed;
1857     else
1858         _autoscroll = 0;
1860     queue_draw();
1862     return Gtk::TreeView::on_motion_notify_event(e);
1865 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1867     SPFilterPrimitive *prim = get_selected(), *target;
1869     _scroll_connection.disconnect();
1871     if(_in_drag && prim) {
1872         Gtk::TreePath path;
1873         Gtk::TreeViewColumn* col;
1874         int cx, cy;
1876         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1877             const gchar *in_val = 0;
1878             Glib::ustring result;
1879             Gtk::TreeIter target_iter = _model->get_iter(path);
1880             target = (*target_iter)[_columns.primitive];
1881             col = get_column(1);
1883             Gdk::Rectangle rct;
1884             get_cell_area(path, *col, rct);
1885             const int twidth = _connection_cell.get_text_width();
1886             const int sources_x = rct.get_width() - twidth * FPInputConverter._length;
1887             if(cx > sources_x) {
1888                 int src = (cx - sources_x) / twidth;
1889                 if (src < 0) {
1890                     src = 0;
1891                 } else if(src >= static_cast<int>(FPInputConverter._length)) {
1892                     src = FPInputConverter._length - 1;
1893                 }
1894                 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1895                 in_val = result.c_str();
1896             }
1897             else {
1898                 // Ensure that the target comes before the selected primitive
1899                 for(Gtk::TreeIter iter = _model->children().begin();
1900                     iter != get_selection()->get_selected(); ++iter) {
1901                     if(iter == target_iter) {
1902                         Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1903                         // Make sure the target has a result
1904                         const gchar *gres = repr->attribute("result");
1905                         if(!gres) {
1906                             result = sp_filter_get_new_result_name(SP_FILTER(prim->parent));
1907                             repr->setAttribute("result", result.c_str());
1908                             in_val = result.c_str();
1909                         }
1910                         else
1911                             in_val = gres;
1912                         break;
1913                     }
1914                 }
1915             }
1917             if(SP_IS_FEMERGE(prim)) {
1918                 int c = 1;
1919                 bool handled = false;
1920                 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1921                     if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1922                         // If input is null, delete it
1923                         if(!in_val) {
1925                             //XML Tree being used directly here while it shouldn't be.
1926                             sp_repr_unparent(o->getRepr());
1927                             DocumentUndo::done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1928                                                _("Remove merge node"));
1929                             (*get_selection()->get_selected())[_columns.primitive] = prim;
1930                         }
1931                         else
1932                             _dialog.set_attr(o, SP_ATTR_IN, in_val);
1933                         handled = true;
1934                     }
1935                 }
1936                 // Add new input?
1937                 if(!handled && c == _in_drag && in_val) {
1938                     Inkscape::XML::Document *xml_doc = prim->document->getReprDoc();
1939                     Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1940                     repr->setAttribute("inkscape:collect", "always");
1942                     //XML Tree being used directly here while it shouldn't be.
1943                     prim->getRepr()->appendChild(repr);
1944                     SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1945                     Inkscape::GC::release(repr);
1946                     _dialog.set_attr(node, SP_ATTR_IN, in_val);
1947                     (*get_selection()->get_selected())[_columns.primitive] = prim;
1948                 }
1949             }
1950             else {
1951                 if(_in_drag == 1)
1952                     _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1953                 else if(_in_drag == 2)
1954                     _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1955             }
1956         }
1958         _in_drag = 0;
1959         queue_draw();
1961         _dialog.update_settings_view();
1962     }
1964     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1965         const bool sensitive = get_selected() != NULL;
1966         _primitive_menu->items()[0].set_sensitive(sensitive);
1967         _primitive_menu->items()[1].set_sensitive(sensitive);
1968         _primitive_menu->popup(e->button, e->time);
1970         return true;
1971     }
1972     else
1973         return Gtk::TreeView::on_button_release_event(e);
1976 // Checks all of prim's inputs, removes any that use result
1977 void check_single_connection(SPFilterPrimitive* prim, const int result)
1979     if(prim && result >= 0) {
1981         if(prim->image_in == result)
1982             SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1984         if(SP_IS_FEBLEND(prim)) {
1985             if(SP_FEBLEND(prim)->in2 == result)
1986                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1987         }
1988         else if(SP_IS_FECOMPOSITE(prim)) {
1989             if(SP_FECOMPOSITE(prim)->in2 == result)
1990                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1991         }
1992         else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
1993             if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
1994                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1995         }
1996     }
1999 // Remove any connections going to/from prim_iter that forward-reference other primitives
2000 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
2002     SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
2003     bool before = true;
2005     for(Gtk::TreeIter iter = _model->children().begin();
2006         iter != _model->children().end(); ++iter) {
2007         if(iter == prim_iter)
2008             before = false;
2009         else {
2010             SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
2011             if(before)
2012                 check_single_connection(cur_prim, prim->image_out);
2013             else
2014                 check_single_connection(prim, cur_prim->image_out);
2015         }
2016     }
2019 // Reorder the filter primitives to match the list order
2020 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2022     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2023     int ndx = 0;
2025     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2026         iter != _model->children().end(); ++iter, ++ndx) {
2027         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2028         if(prim && prim == _drag_prim) {
2029             SP_OBJECT_REPR(prim)->setPosition(ndx);
2030             break;
2031         }
2032     }
2034     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2035         iter != _model->children().end(); ++iter, ++ndx) {
2036         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2037         if(prim && prim == _drag_prim) {
2038             sanitize_connections(iter);
2039             get_selection()->select(iter);
2040             break;
2041         }
2042     }
2044     filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2046     DocumentUndo::done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2049 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2050 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2052     if(_autoscroll) {
2053         Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2054         double v;
2056         v = a.get_value() + _autoscroll;
2057         if(v < 0)
2058             v = 0;
2059         if(v > a.get_upper() - a.get_page_size())
2060             v = a.get_upper() - a.get_page_size();
2062         a.set_value(v);
2064         queue_draw();
2065     }
2067     return true;
2070 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2072     return _model->children().size();
2075 /*** FilterEffectsDialog ***/
2077 FilterEffectsDialog::FilterEffectsDialog()
2078     : UI::Widget::Panel("", "/dialogs/filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2079       _add_primitive_type(FPConverter),
2080       _add_primitive(_("Add Effect:")),
2081       _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2082       _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2083       _settings_initialized(false),
2084       _locked(false),
2085       _attr_lock(false),
2086       _filter_modifier(*this),
2087       _primitive_list(*this)
2089     _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2090                              NR_FILTER_ENDPRIMITIVETYPE);
2091     _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2092                              1);
2093     _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2094     _sizegroup->set_ignore_hidden();
2096     _add_primitive_type.remove_row(NR_FILTER_TILE);
2097     _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2099     // Initialize widget hierarchy
2100     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2101     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2102     Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox(/*homogeneous:*/false, /*spacing:*/4));
2103     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2105     _getContents()->add(*hpaned);
2106     hpaned->pack1(_filter_modifier);
2107     hpaned->pack2(_primitive_box);
2108     _primitive_box.pack_start(*sw_prims);
2109     _primitive_box.pack_start(*hb_prims, false, false);
2110     _primitive_box.pack_start(*infobox,false, false);
2111     sw_prims->add(_primitive_list);
2112     infobox->pack_start(_infobox_icon, false, false);
2113     infobox->pack_start(_infobox_desc, false, false);
2114     _infobox_desc.set_line_wrap(true);
2115     _infobox_desc.set_size_request(200, -1);
2117     hb_prims->pack_start(_add_primitive, false, false);
2118     hb_prims->pack_start(_add_primitive_type, false, false);
2119     _getContents()->pack_start(_settings_tabs, false, false);
2120     _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2121     _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2123     _primitive_list.signal_primitive_changed().connect(
2124         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2125     _filter_modifier.signal_filter_changed().connect(
2126         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2128     _add_primitive_type.signal_changed().connect(
2129         sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2131     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2132     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2133 //    al_settings->set_padding(0, 0, 12, 0);
2134 //    fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2135 //    ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2136     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2137     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2138                                                sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2140     show_all_children();
2141     init_settings_widgets();
2142     _primitive_list.update();
2143     update_primitive_infobox();
2146 FilterEffectsDialog::~FilterEffectsDialog()
2148     delete _settings;
2149     delete _filter_general_settings;
2152 void FilterEffectsDialog::set_attrs_locked(const bool l)
2154     _locked = l;
2157 void FilterEffectsDialog::show_all_vfunc()
2159     UI::Widget::Panel::show_all_vfunc();
2161     update_settings_view();
2164 void FilterEffectsDialog::init_settings_widgets()
2166     // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2167     //       most of the current values are complete guesses!
2169     _empty_settings.set_sensitive(false);
2170     _settings_tab1.pack_start(_empty_settings);
2172     _no_filter_selected.set_sensitive(false);
2173     _settings_tab2.pack_start(_no_filter_selected);
2174     _settings_initialized = true;
2176     _filter_general_settings->type(0);
2177     _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"));
2178     _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"));
2180     _settings->type(NR_FILTER_BLEND);
2181     _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode:"), BlendModeConverter);
2183     _settings->type(NR_FILTER_COLORMATRIX);
2184     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."));
2185     _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s):"));
2186     colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2188     _settings->type(NR_FILTER_COMPONENTTRANSFER);
2189     _settings->add_notimplemented();
2190     /*
2191     //TRANSLATORS: for info on "Slope" and "Intercept", see http://id.mind.net/~zona/mmts/functionInstitute/linearFunctions/lsif.html
2192     _settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2193     _ct_slope = _settings->add_spinslider(1, SP_ATTR_SLOPE, _("Slope"), -10, 10, 0.1, 0.01, 2);
2194     _ct_intercept = _settings->add_spinslider(0, SP_ATTR_INTERCEPT, _("Intercept"), -10, 10, 0.1, 0.01, 2);
2195     _ct_amplitude = _settings->add_spinslider(1, SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 10, 0.1, 0.01, 2);
2196     _ct_exponent = _settings->add_spinslider(1, SP_ATTR_EXPONENT, _("Exponent"), 0, 10, 0.1, 0.01, 2);
2197     _ct_offset = _settings->add_spinslider(0, SP_ATTR_OFFSET, _("Offset"), -10, 10, 0.1, 0.01, 2);*/
2199     _settings->type(NR_FILTER_COMPOSITE);
2200     _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator:"), CompositeOperatorConverter);
2201     _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."));
2202     _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."));
2203     _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."));
2204     _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."));
2206     _settings->type(NR_FILTER_CONVOLVEMATRIX);
2207     _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"));
2208     _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."));
2209     //TRANSLATORS: for info on "Kernel", see http://en.wikipedia.org/wiki/Kernel_(matrix)
2210     _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."));
2211     _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2212     _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."));
2213     _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."));
2214     _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."));
2215     _settings->add_checkbutton(false, SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive."));
2217     _settings->type(NR_FILTER_DIFFUSELIGHTING);
2218     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Diffuse Color:"), _("Defines the color of the light source"));
2219     _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"));
2220     _settings->add_spinslider(1, SP_ATTR_DIFFUSECONSTANT, _("Constant:"), 0, 5, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2221     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2222     _settings->add_lightsource();
2224     _settings->type(NR_FILTER_DISPLACEMENTMAP);
2225     _settings->add_spinslider(0, SP_ATTR_SCALE, _("Scale:"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2226     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2227     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2229     _settings->type(NR_FILTER_FLOOD);
2230     _settings->add_color(/*default: black*/ 0, SP_PROP_FLOOD_COLOR, _("Flood Color:"), _("The whole filter region will be filled with this color."));
2231     _settings->add_spinslider(1, SP_PROP_FLOOD_OPACITY, _("Opacity:"), 0, 1, 0.1, 0.01, 2);
2233     _settings->type(NR_FILTER_GAUSSIANBLUR);
2234     _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation:"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2236     _settings->type(NR_FILTER_MERGE);
2237     _settings->add_no_params();
2239     _settings->type(NR_FILTER_MORPHOLOGY);
2240     _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator:"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2241     _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius:"), 0, 100, 1, 0.01, 1);
2243     _settings->type(NR_FILTER_IMAGE);
2244     _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image:"));
2246     _settings->type(NR_FILTER_OFFSET);
2247     _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"));
2248     _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"));
2250     _settings->type(NR_FILTER_SPECULARLIGHTING);
2251     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Specular Color:"), _("Defines the color of the light source"));
2252     _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"));
2253     _settings->add_spinslider(1, SP_ATTR_SPECULARCONSTANT, _("Constant:"), 0, 5, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2254     _settings->add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Exponent:"), 1, 50, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2255     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2256     _settings->add_lightsource();
2258     _settings->type(NR_FILTER_TILE);
2259     _settings->add_notimplemented();
2261     _settings->type(NR_FILTER_TURBULENCE);
2262 //    _settings->add_checkbutton(false, SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2263     _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type:"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2264     _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency:"), 0, 0.4, 0.001, 0.01, 3);
2265     _settings->add_spinslider(1, SP_ATTR_NUMOCTAVES, _("Octaves:"), 1, 10, 1, 1, 0);
2266     _settings->add_spinslider(0, SP_ATTR_SEED, _("Seed:"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2269 void FilterEffectsDialog::add_primitive()
2271     SPFilter* filter = _filter_modifier.get_selected_filter();
2273     if(filter) {
2274         SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2276         _primitive_list.select(prim);
2278         DocumentUndo::done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2279     }
2282 void FilterEffectsDialog::update_primitive_infobox()
2284     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2285     if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2286         _infobox_icon.show();
2287         _infobox_desc.show();
2288     } else {
2289         _infobox_icon.hide();
2290         _infobox_desc.hide();
2291     }
2292     switch(_add_primitive_type.get_active_data()->id){
2293         case(NR_FILTER_BLEND):
2294             _infobox_icon.set_from_icon_name("feBlend-icon", Gtk::ICON_SIZE_DIALOG);
2295             _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2296             break;
2297         case(NR_FILTER_COLORMATRIX):
2298             _infobox_icon.set_from_icon_name("feColorMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2299             _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."));
2300             break;
2301         case(NR_FILTER_COMPONENTTRANSFER):
2302             _infobox_icon.set_from_icon_name("feComponentTransfer-icon", Gtk::ICON_SIZE_DIALOG);
2303             _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."));
2304             break;
2305         case(NR_FILTER_COMPOSITE):
2306             _infobox_icon.set_from_icon_name("feComposite-icon", Gtk::ICON_SIZE_DIALOG);
2307             _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."));
2308             break;
2309         case(NR_FILTER_CONVOLVEMATRIX):
2310             _infobox_icon.set_from_icon_name("feConvolveMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2311             _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."));
2312             break;
2313         case(NR_FILTER_DIFFUSELIGHTING):
2314             _infobox_icon.set_from_icon_name("feDiffuseLighting-icon", Gtk::ICON_SIZE_DIALOG);
2315             _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."));
2316             break;
2317         case(NR_FILTER_DISPLACEMENTMAP):
2318             _infobox_icon.set_from_icon_name("feDisplacementMap-icon", Gtk::ICON_SIZE_DIALOG);
2319             _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."));
2320             break;
2321         case(NR_FILTER_FLOOD):
2322             _infobox_icon.set_from_icon_name("feFlood-icon", Gtk::ICON_SIZE_DIALOG);
2323             _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."));
2324             break;
2325         case(NR_FILTER_GAUSSIANBLUR):
2326             _infobox_icon.set_from_icon_name("feGaussianBlur-icon", Gtk::ICON_SIZE_DIALOG);
2327             _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."));
2328             break;
2329         case(NR_FILTER_IMAGE):
2330             _infobox_icon.set_from_icon_name("feImage-icon", Gtk::ICON_SIZE_DIALOG);
2331             _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2332             break;
2333         case(NR_FILTER_MERGE):
2334             _infobox_icon.set_from_icon_name("feMerge-icon", Gtk::ICON_SIZE_DIALOG);
2335             _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."));
2336             break;
2337         case(NR_FILTER_MORPHOLOGY):
2338             _infobox_icon.set_from_icon_name("feMorphology-icon", Gtk::ICON_SIZE_DIALOG);
2339             _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."));
2340             break;
2341         case(NR_FILTER_OFFSET):
2342             _infobox_icon.set_from_icon_name("feOffset-icon", Gtk::ICON_SIZE_DIALOG);
2343             _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."));
2344             break;
2345         case(NR_FILTER_SPECULARLIGHTING):
2346             _infobox_icon.set_from_icon_name("feSpecularLighting-icon", Gtk::ICON_SIZE_DIALOG);
2347             _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."));
2348             break;
2349         case(NR_FILTER_TILE):
2350             _infobox_icon.set_from_icon_name("feTile-icon", Gtk::ICON_SIZE_DIALOG);
2351             _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2352             break;
2353         case(NR_FILTER_TURBULENCE):
2354             _infobox_icon.set_from_icon_name("feTurbulence-icon", Gtk::ICON_SIZE_DIALOG);
2355             _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."));
2356             break;
2357         default:
2358             g_assert(false);
2359             break;
2360     }
2361     _infobox_icon.set_pixel_size(96);
2364 void FilterEffectsDialog::duplicate_primitive()
2366     SPFilter* filter = _filter_modifier.get_selected_filter();
2367     SPFilterPrimitive* origprim = _primitive_list.get_selected();
2369     if(filter && origprim) {
2370         Inkscape::XML::Node *repr;
2371         repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2372         SP_OBJECT_REPR(filter)->appendChild(repr);
2374         DocumentUndo::done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2376         _primitive_list.update();
2377     }
2380 void FilterEffectsDialog::convolve_order_changed()
2382     _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2383     _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2384     _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2387 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2389     set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2392 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2394     if(!_locked) {
2395         _attr_lock = true;
2396         SPFilter *filter = _filter_modifier.get_selected_filter();
2397         const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2398         if (filter && name && SP_OBJECT_REPR(filter)){
2399             SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2400             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2401         }
2402         _attr_lock = false;
2403     }
2406 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2408     set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2411 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2413     if(!_locked) {
2414         _attr_lock = true;
2416         SPFilter *filter = _filter_modifier.get_selected_filter();
2417         const gchar* name = (const gchar*)sp_attribute_name(attr);
2418         if(filter && name && o) {
2419             update_settings_sensitivity();
2421             SP_OBJECT_REPR(o)->setAttribute(name, val);
2422             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2424             Glib::ustring undokey = "filtereffects:";
2425             undokey += name;
2426             DocumentUndo::maybeDone(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2427                                     _("Set filter primitive attribute"));
2428         }
2430         _attr_lock = false;
2431     }
2434 void FilterEffectsDialog::update_filter_general_settings_view()
2436     if(_settings_initialized != true) return;
2438     if(!_locked) {
2439         _attr_lock = true;
2441         SPFilter* filter = _filter_modifier.get_selected_filter();
2443         if(filter) {
2444             _filter_general_settings->show_and_update(0, filter);
2445             _no_filter_selected.hide();
2446         }
2447         else {
2448             std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2449             vect[0]->hide_all();
2450             _no_filter_selected.show();
2451         }
2453         _attr_lock = false;
2454     }
2457 void FilterEffectsDialog::update_settings_view()
2459     update_settings_sensitivity();
2461     if(_attr_lock)
2462         return;
2464 //First Tab
2466     std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2467     for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2468     _empty_settings.show();
2470     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2471     if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2472         _infobox_icon.show();
2473         _infobox_desc.show();
2474     } else {
2475         _infobox_icon.hide();
2476         _infobox_desc.hide();
2477     }
2479     SPFilterPrimitive* prim = _primitive_list.get_selected();
2481     if(prim) {
2483         //XML Tree being used directly here while it shouldn't be.
2484         _settings->show_and_update(FPConverter.get_id_from_key(prim->getRepr()->name()), prim);
2485         _empty_settings.hide();
2486     }
2488 //Second Tab
2490     std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2491     vect2[0]->hide_all();
2492     _no_filter_selected.show();
2494     SPFilter* filter = _filter_modifier.get_selected_filter();
2496     if(filter) {
2497         _filter_general_settings->show_and_update(0, filter);
2498         _no_filter_selected.hide();
2499     }
2503 void FilterEffectsDialog::update_settings_sensitivity()
2505     SPFilterPrimitive* prim = _primitive_list.get_selected();
2506     const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2507     _k1->set_sensitive(use_k);
2508     _k2->set_sensitive(use_k);
2509     _k3->set_sensitive(use_k);
2510     _k4->set_sensitive(use_k);
2512 // Component transfer not yet implemented
2513 /*
2514     if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2515         SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2516         const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2517         const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2519         _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2520         _ct_slope->set_sensitive(linear);
2521         _ct_intercept->set_sensitive(linear);
2522         _ct_amplitude->set_sensitive(gamma);
2523         _ct_exponent->set_sensitive(gamma);
2524         _ct_offset->set_sensitive(gamma);
2525     }
2526 */
2529 void FilterEffectsDialog::update_color_matrix()
2531     _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2534 } // namespace Dialog
2535 } // namespace UI
2536 } // namespace Inkscape
2538 /*
2539   Local Variables:
2540   mode:c++
2541   c-file-style:"stroustrup"
2542   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2543   indent-tabs-mode:nil
2544   fill-column:99
2545   End:
2546 */
2547 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :