Code

Fix initial combo box values in filter effects dialog
[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  *
9  * Copyright (C) 2007 Authors
10  *
11  * Released under GNU GPL.  Read the file 'COPYING' for more information.
12  */
14 #ifdef HAVE_CONFIG_H
15 # include <config.h>
16 #endif
18 #include <gtk/gtk.h>
19 #include <gtkmm/cellrenderertext.h>
20 #include <gtkmm/colorbutton.h>
21 #include <gtkmm/messagedialog.h>
22 #include <gtkmm/paned.h>
23 #include <gtkmm/scale.h>
24 #include <gtkmm/scrolledwindow.h>
25 #include <gtkmm/spinbutton.h>
26 #include <gtkmm/stock.h>
27 #include <gtkmm/tooltips.h>
28 #include <glibmm/i18n.h>
30 #include "application/application.h"
31 #include "application/editor.h"
32 #include "desktop.h"
33 #include "desktop-handles.h"
34 #include "dialog-manager.h"
35 #include "dir-util.h"
36 #include "document.h"
37 #include "filter-chemistry.h"
38 #include "filter-effects-dialog.h"
39 #include "filter-enums.h"
40 #include "inkscape.h"
41 #include "path-prefix.h"
42 #include "preferences.h"
43 #include "selection.h"
44 #include "filters/blend.h"
45 #include "filters/colormatrix.h"
46 #include "filters/componenttransfer.h"
47 #include "filters/composite.h"
48 #include "filters/convolvematrix.h"
49 #include "filters/displacementmap.h"
50 #include "filters/distantlight.h"
51 #include "filters/merge.h"
52 #include "filters/mergenode.h"
53 #include "filters/offset.h"
54 #include "filters/pointlight.h"
55 #include "filters/spotlight.h"
56 #include "sp-filter-primitive.h"
57 #include "sp-gaussian-blur.h"
59 #include "style.h"
60 #include "svg/svg-color.h"
61 #include "ui/dialog/filedialog.h"
62 #include "verbs.h"
63 #include "xml/node.h"
64 #include "xml/node-observer.h"
65 #include "xml/repr.h"
66 #include <sstream>
68 #include "io/sys.h"
69 #include <iostream>
71 using namespace Inkscape::Filters;
73 namespace Inkscape {
74 namespace UI {
75 namespace Dialog {
77 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                     sp_repr_unparent(child->repr);
1028                 if(ls != -1) {
1029                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1030                     Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1031                     prim->repr->appendChild(repr);
1032                     Inkscape::GC::release(repr);
1033                 }
1035                 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1036                 update();
1037             }
1039             _locked = false;
1040         }
1041     }
1043     void update()
1044     {
1045         _box.hide_all();
1046         _box.show();
1047         _light_box.show_all();
1049         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1050         if(prim && prim->children)
1051             _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1052     }
1054     FilterEffectsDialog& _dialog;
1055     Gtk::VBox _box;
1056     Settings _settings;
1057     Gtk::HBox _light_box;
1058     Gtk::Label _light_label;
1059     ComboBoxEnum<LightSource> _light_source;
1060     bool _locked;
1061 };
1063 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1065     LightSourceControl* ls = new LightSourceControl(_dialog);
1066     add_attr_widget(ls);
1067     add_widget(&ls->get_box(), "");
1068     return ls;
1071 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1072                                           sigc::slot<void> rem)
1074     Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1076     menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1077     Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1078     menu->append(*mi);
1079     mi->signal_activate().connect(rem);
1080     mi->show();
1081     menu->accelerate(parent);
1083     return menu;
1086 /*** FilterModifier ***/
1087 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1088     : _dialog(d), _add(Gtk::Stock::NEW), _observer(new Inkscape::XML::SignalObserver)
1090     Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1091     pack_start(*sw);
1092     pack_start(_add, false, false);
1093     sw->add(_list);
1095     _model = Gtk::ListStore::create(_columns);
1096     _list.set_model(_model);
1097     _cell_toggle.set_active(true);
1098     const int selcol = _list.append_column("", _cell_toggle);
1099     Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1100     if(col)
1101        col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1102     _list.append_column_editable(_("_Filter"), _columns.label);
1103     ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1104         signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1106     sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1107     sw->set_shadow_type(Gtk::SHADOW_IN);
1108     show_all_children();
1109     _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1110     _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1111     _list.signal_button_release_event().connect_notify(
1112         sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1113     _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1114                               sigc::mem_fun(*this, &FilterModifier::remove_filter));
1115     _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1116                                  _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1117     _menu->accelerate(*this);
1119     _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1120     _observer->signal_changed().connect(signal_filter_changed().make_slot());
1121     g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1122                      G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1124     g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1125                      G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1126     g_signal_connect(G_OBJECT(INKSCAPE), "deactivate_desktop",
1127                      G_CALLBACK(&FilterModifier::on_deactivate_desktop), this);
1129     on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1130     update_filters();
1133 FilterEffectsDialog::FilterModifier::~FilterModifier()
1135    _resource_changed.disconnect();
1136    _doc_replaced.disconnect();
1139 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1141     me->_doc_replaced.disconnect();
1142     me->_doc_replaced = desktop->connectDocumentReplaced(
1143         sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1145     me->_resource_changed.disconnect();
1146     me->_resource_changed =
1147         sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1148                                               sigc::mem_fun(me, &FilterModifier::update_filters));
1150     me->_dialog.setDesktop(desktop);
1152     me->update_filters();
1155 void FilterEffectsDialog::FilterModifier::on_deactivate_desktop(Application*, SPDesktop* /*desktop*/, FilterModifier* me)
1157     me->_doc_replaced.disconnect();
1158     me->_resource_changed.disconnect();
1159     me->_dialog.setDesktop(NULL);
1163 // When the selection changes, show the active filter(s) in the dialog
1164 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1165                                                                        Selection *sel,
1166                                                                        FilterModifier* fm)
1168     if(fm && sel)
1169         fm->update_selection(sel);
1172 // Update each filter's sel property based on the current object selection;
1173 //  If the filter is not used by any selected object, sel = 0,
1174 //  otherwise sel is set to the total number of filters in use by selected objects
1175 //  If only one filter is in use, it is selected
1176 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1178     std::set<SPObject*> used;
1180     for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1181         SPObject *obj = SP_OBJECT (i->data);
1182         SPStyle *style = SP_OBJECT_STYLE (obj);
1183         if(!style || !SP_IS_ITEM(obj)) continue;
1185         if(style->filter.set && style->getFilter())
1186             used.insert(style->getFilter());
1187         else
1188             used.insert(0);
1189     }
1191     const int size = used.size();
1193     for(Gtk::TreeIter iter = _model->children().begin();
1194         iter != _model->children().end(); ++iter) {
1195         if(used.find((*iter)[_columns.filter]) != used.end()) {
1196             // If only one filter is in use by the selection, select it
1197             if(size == 1)
1198                 _list.get_selection()->select(iter);
1199             (*iter)[_columns.sel] = size;
1200         }
1201         else
1202             (*iter)[_columns.sel] = 0;
1203     }
1206 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1208     _observer->set(get_selected_filter());
1209     signal_filter_changed()();
1212 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1214     Gtk::TreeModel::iterator iter = _model->get_iter(path);
1216     if(iter) {
1217         SPFilter* filter = (*iter)[_columns.filter];
1218         filter->setLabel(text.c_str());
1219         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1220         if(iter)
1221             (*iter)[_columns.label] = text;
1222     }
1225 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1227     Gtk::TreeIter iter = _model->get_iter(path);
1229     if(iter) {
1230         SPDesktop *desktop = _dialog.getDesktop();
1231         SPDocument *doc = sp_desktop_document(desktop);
1232         SPFilter* filter = (*iter)[_columns.filter];
1233         Inkscape::Selection *sel = sp_desktop_selection(desktop);
1235         /* If this filter is the only one used in the selection, unset it */
1236         if((*iter)[_columns.sel] == 1)
1237             filter = 0;
1239         GSList const *items = sel->itemList();
1241         for (GSList const *i = items; i != NULL; i = i->next) {
1242             SPItem * item = SP_ITEM(i->data);
1243             SPStyle *style = SP_OBJECT_STYLE(item);
1244             g_assert(style != NULL);
1246             if(filter)
1247                 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1248             else
1249                 ::remove_filter(item, false);
1251             SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1252         }
1254         update_selection(sel);
1255         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS,  _("Apply filter"));
1256     }
1259 /* Add all filters in the document to the combobox.
1260    Keeps the same selection if possible, otherwise selects the first element */
1261 void FilterEffectsDialog::FilterModifier::update_filters()
1263     SPDesktop* desktop = _dialog.getDesktop();
1264     SPDocument* document = sp_desktop_document(desktop);
1265     const GSList* filters = sp_document_get_resource_list(document, "filter");
1267     _model->clear();
1269     for(const GSList *l = filters; l; l = l->next) {
1270         Gtk::TreeModel::Row row = *_model->append();
1271         SPFilter* f = (SPFilter*)l->data;
1272         row[_columns.filter] = f;
1273         const gchar* lbl = f->label();
1274         const gchar* id = f->getId();
1275         row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1276     }
1278     update_selection(desktop->selection);
1279     _dialog.update_filter_general_settings_view();
1282 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1284     if(_list.get_selection()) {
1285         Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1287         if(i)
1288             return (*i)[_columns.filter];
1289     }
1291     return 0;
1294 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1296     if(filter) {
1297         for(Gtk::TreeModel::iterator i = _model->children().begin();
1298             i != _model->children().end(); ++i) {
1299             if((*i)[_columns.filter] == filter) {
1300                 _list.get_selection()->select(i);
1301                 break;
1302             }
1303         }
1304     }
1307 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1309     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1310         const bool sensitive = get_selected_filter() != NULL;
1311         _menu->items()[0].set_sensitive(sensitive);
1312         _menu->items()[1].set_sensitive(sensitive);
1313         _menu->popup(event->button, event->time);
1314     }
1317 void FilterEffectsDialog::FilterModifier::add_filter()
1319     SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1320     SPFilter* filter = new_filter(doc);
1322     const int count = _model->children().size();
1323     std::ostringstream os;
1324     os << _("filter") << count;
1325     filter->setLabel(os.str().c_str());
1327     update_filters();
1329     select_filter(filter);
1331     sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1334 void FilterEffectsDialog::FilterModifier::remove_filter()
1336     SPFilter *filter = get_selected_filter();
1338     if(filter) {
1339         SPDocument* doc = filter->document;
1340         sp_repr_unparent(filter->repr);
1342         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1344         update_filters();
1345     }
1348 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1350     SPFilter* filter = get_selected_filter();
1352     if(filter) {
1353         Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1354         repr = repr->duplicate(repr->document());
1355         parent->appendChild(repr);
1357         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1359         update_filters();
1360     }
1363 void FilterEffectsDialog::FilterModifier::rename_filter()
1365     _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1368 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1369     : Glib::ObjectBase(typeid(CellRendererConnection)),
1370       _primitive(*this, "primitive", 0)
1371 {}
1373 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1375     return _primitive.get_proxy();
1378 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1380     _text_width = w;
1383 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1385     return _text_width;
1388 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1389     Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1390     int* x_offset, int* y_offset, int* width, int* height) const
1392     PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1394     if(x_offset)
1395         (*x_offset) = 0;
1396     if(y_offset)
1397         (*y_offset) = 0;
1398     if(width)
1399         (*width) = size * primlist.primitive_count() + _text_width * 7;
1400     if(height) {
1401         // Scale the height depending on the number of inputs, unless it's
1402         // the first primitive, in which case there are no connections
1403         SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1404         (*height) = size * input_count(prim);
1405     }
1408 /*** PrimitiveList ***/
1409 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1410     : _dialog(d),
1411       _in_drag(0),
1412       _observer(new Inkscape::XML::SignalObserver)
1414     d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1416     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1417     signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1419     _model = Gtk::ListStore::create(_columns);
1421     set_reorderable(true);
1423     set_model(_model);
1424     append_column(_("_Effect"), _columns.type);
1426     _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1427     get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1428     signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1430     _connection_cell.set_text_width(init_text());
1432     int cols_count = append_column(_("Connections"), _connection_cell);
1433     Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1434     if(col)
1435        col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1438 // Sets up a vertical Pango context/layout, and returns the largest
1439 // width needed to render the FilterPrimitiveInput labels.
1440 int FilterEffectsDialog::PrimitiveList::init_text()
1442     // Set up a vertical context+layout
1443     Glib::RefPtr<Pango::Context> context = create_pango_context();
1444     const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1445     context->set_matrix(matrix);
1446     _vertical_layout = Pango::Layout::create(context);
1448     int maxfont = 0;
1449     for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1450         _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1451         int fontw, fonth;
1452         _vertical_layout->get_pixel_size(fontw, fonth);
1453         if(fonth > maxfont)
1454             maxfont = fonth;
1455     }
1457     return maxfont;
1460 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1462     return _signal_primitive_changed;
1465 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1467     _observer->set(get_selected());
1468     signal_primitive_changed()();
1469     _dialog._color_matrix_values->clear_store();
1472 /* Add all filter primitives in the current to the list.
1473    Keeps the same selection if possible, otherwise selects the first element */
1474 void FilterEffectsDialog::PrimitiveList::update()
1476     SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1477     const SPFilterPrimitive* active_prim = get_selected();
1478     bool active_found = false;
1480     _model->clear();
1482     if(f) {
1483         _dialog._primitive_box.set_sensitive(true);
1484         _dialog.update_filter_general_settings_view();
1485         for(SPObject *prim_obj = f->children;
1486                 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1487                 prim_obj = prim_obj->next) {
1488             SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1489             if(prim) {
1490                 Gtk::TreeModel::Row row = *_model->append();
1491                 row[_columns.primitive] = prim;
1492                 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1493                 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1494                 row[_columns.id] = prim->getId();
1496                 if(prim == active_prim) {
1497                     get_selection()->select(row);
1498                     active_found = true;
1499                 }
1500             }
1501         }
1503         if(!active_found && _model->children().begin())
1504             get_selection()->select(_model->children().begin());
1506         columns_autosize();
1507     }
1508     else {
1509         _dialog._primitive_box.set_sensitive(false);
1510     }
1513 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1515     _primitive_menu = menu;
1518 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1520     if(_dialog._filter_modifier.get_selected_filter()) {
1521         Gtk::TreeModel::iterator i = get_selection()->get_selected();
1522         if(i)
1523             return (*i)[_columns.primitive];
1524     }
1526     return 0;
1529 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1531     for(Gtk::TreeIter i = _model->children().begin();
1532         i != _model->children().end(); ++i) {
1533         if((*i)[_columns.primitive] == prim)
1534             get_selection()->select(i);
1535     }
1538 void FilterEffectsDialog::PrimitiveList::remove_selected()
1540     SPFilterPrimitive* prim = get_selected();
1542     if(prim) {
1543         _observer->set(0);
1545         sp_repr_unparent(prim->repr);
1547         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1548                          _("Remove filter primitive"));
1550         update();
1551     }
1554 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1556     Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1557     Glib::RefPtr<Gdk::Window> win = get_bin_window();
1558     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1560     SPFilterPrimitive* prim = get_selected();
1561     int row_count = get_model()->children().size();
1563     int fheight = CellRendererConnection::size;
1564     Gdk::Rectangle rct, vis;
1565     Gtk::TreeIter row = get_model()->children().begin();
1566     int text_start_x = 0;
1567     if(row) {
1568         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1569         get_visible_rect(vis);
1570         int vis_x, vis_y;
1571         tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1573         text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter._length + 1) + 1;
1574         for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1575             _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1576             const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1577             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());
1578             get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1579             get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1580         }
1581     }
1583     int row_index = 0;
1584     for(; row != get_model()->children().end(); ++row, ++row_index) {
1585         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1586         const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1588         // Check mouse state
1589         int mx, my;
1590         Gdk::ModifierType mask;
1591         get_bin_window()->get_pointer(mx, my, mask);
1593         // Outline the bottom of the connection area
1594         const int outline_x = x + fheight * (row_count - row_index);
1595         get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1597         // Side outline
1598         get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1600         std::vector<Gdk::Point> con_poly;
1601         int con_drag_y = 0;
1602         bool inside;
1603         const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1604         const int inputs = input_count(row_prim);
1606         if(SP_IS_FEMERGE(row_prim)) {
1607             for(int i = 0; i < inputs; ++i) {
1608                 inside = do_connection_node(row, i, con_poly, mx, my);
1609                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1610                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1611                                                inside, con_poly);
1613                 if(_in_drag == (i + 1))
1614                     con_drag_y = con_poly[2].get_y();
1616                 if(_in_drag != (i + 1) || row_prim != prim)
1617                     draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1618             }
1619         }
1620         else {
1621             // Draw "in" shape
1622             inside = do_connection_node(row, 0, con_poly, mx, my);
1623             con_drag_y = con_poly[2].get_y();
1624             get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1625                                            darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1626                                            inside, con_poly);
1628             // Draw "in" connection
1629             if(_in_drag != 1 || row_prim != prim)
1630                 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1632             if(inputs == 2) {
1633                 // Draw "in2" shape
1634                 inside = do_connection_node(row, 1, con_poly, mx, my);
1635                 if(_in_drag == 2)
1636                     con_drag_y = con_poly[2].get_y();
1637                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1638                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1639                                                inside, con_poly);
1640                 // Draw "in2" connection
1641                 if(_in_drag != 2 || row_prim != prim)
1642                     draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1643             }
1644         }
1646         // Draw drag connection
1647         if(row_prim == prim && _in_drag) {
1648             get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1649                                         mx, con_drag_y);
1650             get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1651         }
1652     }
1654     return true;
1657 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1658                                                          const int text_start_x, const int x1, const int y1,
1659                                                          const int row_count)
1661     int src_id = 0;
1662     Gtk::TreeIter res = find_result(input, attr, src_id);
1663     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1664     Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1665     Glib::RefPtr<Gdk::GC> gc;
1667     const bool is_first = input == get_model()->children().begin();
1668     const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1669     const bool use_default = !res && !is_merge;
1671     if(res == input || (use_default && is_first)) {
1672         // Draw straight connection to a standard input
1673         // Draw a lighter line for an implicit connection to a standard input
1674         const int tw = _connection_cell.get_text_width();
1675         gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1676         gc = (use_default && is_first) ? lightgc : darkgc;
1677         get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1678         get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1679     }
1680     else {
1681         // Draw an 'L'-shaped connection to another filter primitive
1682         // If no connection is specified, draw a light connection to the previous primitive
1683         gc = use_default ? lightgc : darkgc;
1685         if(use_default) {
1686             res = input;
1687             --res;
1688         }
1690         if(res) {
1691             Gdk::Rectangle rct;
1693             get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1694             const int fheight = CellRendererConnection::size;
1696             get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1697             const int row_index = find_index(res);
1698             const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1699             const int y2 = rct.get_y() + rct.get_height();
1701             // Draw a bevelled 'L'-shaped connection
1702             get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1703             get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1704             get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1705         }
1706     }
1709 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1710 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1711                                                             std::vector<Gdk::Point>& points,
1712                                                             const int ix, const int iy)
1714     Gdk::Rectangle rct;
1715     const int icnt = input_count((*row)[_columns.primitive]);
1717     get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1718     const int fheight = CellRendererConnection::size;
1720     get_cell_area(_model->get_path(row), *get_column(1), rct);
1721     const float h = rct.get_height() / icnt;
1723     const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1724     const int con_w = (int)(fheight * 0.35f);
1725     const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1726     points.clear();
1727     points.push_back(Gdk::Point(x, con_y));
1728     points.push_back(Gdk::Point(x, con_y + con_w * 2));
1729     points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1731     return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1734 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1735                                                                     const int attr, int& src_id)
1737     SPFilterPrimitive* prim = (*start)[_columns.primitive];
1738     Gtk::TreeIter target = _model->children().end();
1739     int image = 0;
1741     if(SP_IS_FEMERGE(prim)) {
1742         int c = 0;
1743         bool found = false;
1744         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1745             if(c == attr && SP_IS_FEMERGENODE(o)) {
1746                 image = SP_FEMERGENODE(o)->input;
1747                 found = true;
1748             }
1749         }
1750         if(!found)
1751             return target;
1752     }
1753     else {
1754         if(attr == SP_ATTR_IN)
1755             image = prim->image_in;
1756         else if(attr == SP_ATTR_IN2) {
1757             if(SP_IS_FEBLEND(prim))
1758                 image = SP_FEBLEND(prim)->in2;
1759             else if(SP_IS_FECOMPOSITE(prim))
1760                 image = SP_FECOMPOSITE(prim)->in2;
1761             else if(SP_IS_FEDISPLACEMENTMAP(prim))
1762                 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1763             else
1764                 return target;
1765         }
1766         else
1767             return target;
1768     }
1770     if(image >= 0) {
1771         for(Gtk::TreeIter i = _model->children().begin();
1772             i != start; ++i) {
1773             if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1774                 target = i;
1775         }
1776         return target;
1777     }
1778     else if(image < -1) {
1779         src_id = -(image + 2);
1780         return start;
1781     }
1783     return target;
1786 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1788     int i = 0;
1789     for(Gtk::TreeIter iter = _model->children().begin();
1790         iter != target; ++iter, ++i){};
1791     return i;
1794 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1796     Gtk::TreePath path;
1797     Gtk::TreeViewColumn* col;
1798     const int x = (int)e->x, y = (int)e->y;
1799     int cx, cy;
1801     _drag_prim = 0;
1803     if(get_path_at_pos(x, y, path, col, cx, cy)) {
1804         Gtk::TreeIter iter = _model->get_iter(path);
1805         std::vector<Gdk::Point> points;
1807         _drag_prim = (*iter)[_columns.primitive];
1808         const int icnt = input_count(_drag_prim);
1810         for(int i = 0; i < icnt; ++i) {
1811             if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1812                 _in_drag = i + 1;
1813                 break;
1814             }
1815         }
1817         queue_draw();
1818     }
1820     if(_in_drag) {
1821         _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1822         _autoscroll = 0;
1823         get_selection()->select(path);
1824         return true;
1825     }
1826     else
1827         return Gtk::TreeView::on_button_press_event(e);
1830 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1832     const int speed = 10;
1833     const int limit = 15;
1835     Gdk::Rectangle vis;
1836     get_visible_rect(vis);
1837     int vis_x, vis_y;
1838     tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1839     const int top = vis_y + vis.get_height();
1841     // When autoscrolling during a connection drag, set the speed based on
1842     // where the mouse is in relation to the edges.
1843     if(e->y < vis_y)
1844         _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1845     else if(e->y < vis_y + limit)
1846         _autoscroll = -speed;
1847     else if(e->y > top)
1848         _autoscroll = (int)(speed + (e->y - top) / 5);
1849     else if(e->y > top - limit)
1850         _autoscroll = speed;
1851     else
1852         _autoscroll = 0;
1854     queue_draw();
1856     return Gtk::TreeView::on_motion_notify_event(e);
1859 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1861     SPFilterPrimitive *prim = get_selected(), *target;
1863     _scroll_connection.disconnect();
1865     if(_in_drag && prim) {
1866         Gtk::TreePath path;
1867         Gtk::TreeViewColumn* col;
1868         int cx, cy;
1870         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1871             const gchar *in_val = 0;
1872             Glib::ustring result;
1873             Gtk::TreeIter target_iter = _model->get_iter(path);
1874             target = (*target_iter)[_columns.primitive];
1875             col = get_column(1);
1877             Gdk::Rectangle rct;
1878             get_cell_area(path, *col, rct);
1879             const int twidth = _connection_cell.get_text_width();
1880             const int sources_x = rct.get_width() - twidth * FPInputConverter._length;
1881             if(cx > sources_x) {
1882                 int src = (cx - sources_x) / twidth;
1883                 if (src < 0) {
1884                     src = 0;
1885                 } else if(src >= static_cast<int>(FPInputConverter._length)) {
1886                     src = FPInputConverter._length - 1;
1887                 }
1888                 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1889                 in_val = result.c_str();
1890             }
1891             else {
1892                 // Ensure that the target comes before the selected primitive
1893                 for(Gtk::TreeIter iter = _model->children().begin();
1894                     iter != get_selection()->get_selected(); ++iter) {
1895                     if(iter == target_iter) {
1896                         Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1897                         // Make sure the target has a result
1898                         const gchar *gres = repr->attribute("result");
1899                         if(!gres) {
1900                             result = sp_filter_get_new_result_name(SP_FILTER(prim->parent));
1901                             repr->setAttribute("result", result.c_str());
1902                             in_val = result.c_str();
1903                         }
1904                         else
1905                             in_val = gres;
1906                         break;
1907                     }
1908                 }
1909             }
1911             if(SP_IS_FEMERGE(prim)) {
1912                 int c = 1;
1913                 bool handled = false;
1914                 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1915                     if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1916                         // If input is null, delete it
1917                         if(!in_val) {
1918                             sp_repr_unparent(o->repr);
1919                             sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1920                                              _("Remove merge node"));
1921                             (*get_selection()->get_selected())[_columns.primitive] = prim;
1922                         }
1923                         else
1924                             _dialog.set_attr(o, SP_ATTR_IN, in_val);
1925                         handled = true;
1926                     }
1927                 }
1928                 // Add new input?
1929                 if(!handled && c == _in_drag && in_val) {
1930                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1931                     Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1932                     repr->setAttribute("inkscape:collect", "always");
1933                     prim->repr->appendChild(repr);
1934                     SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1935                     Inkscape::GC::release(repr);
1936                     _dialog.set_attr(node, SP_ATTR_IN, in_val);
1937                     (*get_selection()->get_selected())[_columns.primitive] = prim;
1938                 }
1939             }
1940             else {
1941                 if(_in_drag == 1)
1942                     _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1943                 else if(_in_drag == 2)
1944                     _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1945             }
1946         }
1948         _in_drag = 0;
1949         queue_draw();
1951         _dialog.update_settings_view();
1952     }
1954     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1955         const bool sensitive = get_selected() != NULL;
1956         _primitive_menu->items()[0].set_sensitive(sensitive);
1957         _primitive_menu->items()[1].set_sensitive(sensitive);
1958         _primitive_menu->popup(e->button, e->time);
1960         return true;
1961     }
1962     else
1963         return Gtk::TreeView::on_button_release_event(e);
1966 // Checks all of prim's inputs, removes any that use result
1967 void check_single_connection(SPFilterPrimitive* prim, const int result)
1969     if(prim && result >= 0) {
1971         if(prim->image_in == result)
1972             SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1974         if(SP_IS_FEBLEND(prim)) {
1975             if(SP_FEBLEND(prim)->in2 == result)
1976                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1977         }
1978         else if(SP_IS_FECOMPOSITE(prim)) {
1979             if(SP_FECOMPOSITE(prim)->in2 == result)
1980                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1981         }
1982         else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
1983             if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
1984                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1985         }
1986     }
1989 // Remove any connections going to/from prim_iter that forward-reference other primitives
1990 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
1992     SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
1993     bool before = true;
1995     for(Gtk::TreeIter iter = _model->children().begin();
1996         iter != _model->children().end(); ++iter) {
1997         if(iter == prim_iter)
1998             before = false;
1999         else {
2000             SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
2001             if(before)
2002                 check_single_connection(cur_prim, prim->image_out);
2003             else
2004                 check_single_connection(prim, cur_prim->image_out);
2005         }
2006     }
2009 // Reorder the filter primitives to match the list order
2010 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2012     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2013     int ndx = 0;
2015     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2016         iter != _model->children().end(); ++iter, ++ndx) {
2017         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2018         if(prim && prim == _drag_prim) {
2019             SP_OBJECT_REPR(prim)->setPosition(ndx);
2020             break;
2021         }
2022     }
2024     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2025         iter != _model->children().end(); ++iter, ++ndx) {
2026         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2027         if(prim && prim == _drag_prim) {
2028             sanitize_connections(iter);
2029             get_selection()->select(iter);
2030             break;
2031         }
2032     }
2034     filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2036     sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2039 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2040 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2042     if(_autoscroll) {
2043         Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2044         double v;
2046         v = a.get_value() + _autoscroll;
2047         if(v < 0)
2048             v = 0;
2049         if(v > a.get_upper() - a.get_page_size())
2050             v = a.get_upper() - a.get_page_size();
2052         a.set_value(v);
2054         queue_draw();
2055     }
2057     return true;
2060 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2062     return _model->children().size();
2065 /*** FilterEffectsDialog ***/
2067 FilterEffectsDialog::FilterEffectsDialog()
2068     : UI::Widget::Panel("", "/dialogs/filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2069       _add_primitive_type(FPConverter),
2070       _add_primitive(_("Add Effect:")),
2071       _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2072       _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2073       _settings_initialized(false),
2074       _locked(false),
2075       _attr_lock(false),
2076       _filter_modifier(*this),
2077       _primitive_list(*this)
2079     _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2080                              NR_FILTER_ENDPRIMITIVETYPE);
2081     _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2082                              1);
2083     _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2084     _sizegroup->set_ignore_hidden();
2086     _add_primitive_type.remove_row(NR_FILTER_TILE);
2087     _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2089     // Initialize widget hierarchy
2090     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2091     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2092     Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox(/*homogeneous:*/false, /*spacing:*/4));
2093     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2095     _getContents()->add(*hpaned);
2096     hpaned->pack1(_filter_modifier);
2097     hpaned->pack2(_primitive_box);
2098     _primitive_box.pack_start(*sw_prims);
2099     _primitive_box.pack_start(*hb_prims, false, false);
2100     _primitive_box.pack_start(*infobox,false, false);
2101     sw_prims->add(_primitive_list);
2102     infobox->pack_start(_infobox_icon, false, false);
2103     infobox->pack_start(_infobox_desc, false, false);
2104     _infobox_desc.set_line_wrap(true);
2105     _infobox_desc.set_size_request(200, -1);
2107     hb_prims->pack_start(_add_primitive, false, false);
2108     hb_prims->pack_start(_add_primitive_type, false, false);
2109     _getContents()->pack_start(_settings_tabs, false, false);
2110     _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2111     _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2113     _primitive_list.signal_primitive_changed().connect(
2114         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2115     _filter_modifier.signal_filter_changed().connect(
2116         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2118     _add_primitive_type.signal_changed().connect(
2119         sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2121     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2122     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2123 //    al_settings->set_padding(0, 0, 12, 0);
2124 //    fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2125 //    ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2126     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2127     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2128                                                sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2130     show_all_children();
2131     init_settings_widgets();
2132     _primitive_list.update();
2133     update_primitive_infobox();
2136 FilterEffectsDialog::~FilterEffectsDialog()
2138     delete _settings;
2139     delete _filter_general_settings;
2142 void FilterEffectsDialog::set_attrs_locked(const bool l)
2144     _locked = l;
2147 void FilterEffectsDialog::show_all_vfunc()
2149     UI::Widget::Panel::show_all_vfunc();
2151     update_settings_view();
2154 void FilterEffectsDialog::init_settings_widgets()
2156     // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2157     //       most of the current values are complete guesses!
2159     _empty_settings.set_sensitive(false);
2160     _settings_tab1.pack_start(_empty_settings);
2162     _no_filter_selected.set_sensitive(false);
2163     _settings_tab2.pack_start(_no_filter_selected);
2164     _settings_initialized = true;
2166     _filter_general_settings->type(0);
2167     _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"));
2168     _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"));
2170     _settings->type(NR_FILTER_BLEND);
2171     _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode:"), BlendModeConverter);
2173     _settings->type(NR_FILTER_COLORMATRIX);
2174     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."));
2175     _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s):"));
2176     colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2178     _settings->type(NR_FILTER_COMPONENTTRANSFER);
2179     _settings->add_notimplemented();
2180     //TRANSLATORS: for info on "Slope" and "Intercept", see http://id.mind.net/~zona/mmts/functionInstitute/linearFunctions/lsif.html
2181     /*_settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2182     _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2183     _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2184     _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2185     _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2186     _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2188     _settings->type(NR_FILTER_COMPOSITE);
2189     _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator:"), CompositeOperatorConverter);
2190     _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."));
2191     _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."));
2192     _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."));
2193     _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."));
2195     _settings->type(NR_FILTER_CONVOLVEMATRIX);
2196     _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"));
2197     _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."));
2198     //TRANSLATORS: for info on "Kernel", see http://en.wikipedia.org/wiki/Kernel_(matrix)
2199     _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."));
2200     _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2201     _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."));
2202     _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."));
2203     _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."));
2204     _settings->add_checkbutton(false, SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive."));
2206     _settings->type(NR_FILTER_DIFFUSELIGHTING);
2207     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Diffuse Color:"), _("Defines the color of the light source"));
2208     _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale:"), -1000, 1000, 1, 0.01, 1, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2209     _settings->add_spinslider(1, SP_ATTR_DIFFUSECONSTANT, _("Constant:"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2210     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2211     _settings->add_lightsource();
2213     _settings->type(NR_FILTER_DISPLACEMENTMAP);
2214     _settings->add_spinslider(0, SP_ATTR_SCALE, _("Scale:"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2215     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2216     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2218     _settings->type(NR_FILTER_FLOOD);
2219     _settings->add_color(/*default: black*/ 0, SP_PROP_FLOOD_COLOR, _("Flood Color:"), _("The whole filter region will be filled with this color."));
2220     _settings->add_spinslider(1, SP_PROP_FLOOD_OPACITY, _("Opacity:"), 0, 1, 0.1, 0.01, 2);
2222     _settings->type(NR_FILTER_GAUSSIANBLUR);
2223     _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation:"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2225     _settings->type(NR_FILTER_MERGE);
2226     _settings->add_no_params();
2228     _settings->type(NR_FILTER_MORPHOLOGY);
2229     _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator:"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2230     _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius:"), 0, 100, 1, 0.01, 1);
2232     _settings->type(NR_FILTER_IMAGE);
2233     _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image:"));
2235     _settings->type(NR_FILTER_OFFSET);
2236     _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"));
2237     _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"));
2239     _settings->type(NR_FILTER_SPECULARLIGHTING);
2240     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Specular Color:"), _("Defines the color of the light source"));
2241     _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale:"), -1000, 1000, 1, 0.01, 1, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2242     _settings->add_spinslider(1, SP_ATTR_SPECULARCONSTANT, _("Constant:"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2243     _settings->add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Exponent:"), 1, 128, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2244     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2245     _settings->add_lightsource();
2247     _settings->type(NR_FILTER_TILE);
2248     _settings->add_notimplemented();
2250     _settings->type(NR_FILTER_TURBULENCE);
2251 //    _settings->add_checkbutton(false, SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2252     _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type:"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2253     _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency:"), 0, 0.4, 0.001, 0.01, 3);
2254     _settings->add_spinslider(1, SP_ATTR_NUMOCTAVES, _("Octaves:"), 1, 10, 1, 1, 0);
2255     _settings->add_spinslider(0, SP_ATTR_SEED, _("Seed:"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2258 void FilterEffectsDialog::add_primitive()
2260     SPFilter* filter = _filter_modifier.get_selected_filter();
2262     if(filter) {
2263         SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2265         _primitive_list.select(prim);
2267         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2268     }
2271 void FilterEffectsDialog::update_primitive_infobox()
2273     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2274     if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2275         _infobox_icon.show();
2276         _infobox_desc.show();
2277     } else {
2278         _infobox_icon.hide();
2279         _infobox_desc.hide();
2280     }
2281     switch(_add_primitive_type.get_active_data()->id){
2282         case(NR_FILTER_BLEND):
2283             _infobox_icon.set_from_icon_name("feBlend-icon", Gtk::ICON_SIZE_DIALOG);
2284             _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2285             break;
2286         case(NR_FILTER_COLORMATRIX):
2287             _infobox_icon.set_from_icon_name("feColorMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2288             _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."));
2289             break;
2290         case(NR_FILTER_COMPONENTTRANSFER):
2291             _infobox_icon.set_from_icon_name("feComponentTransfer-icon", Gtk::ICON_SIZE_DIALOG);
2292             _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."));
2293             break;
2294         case(NR_FILTER_COMPOSITE):
2295             _infobox_icon.set_from_icon_name("feComposite-icon", Gtk::ICON_SIZE_DIALOG);
2296             _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."));
2297             break;
2298         case(NR_FILTER_CONVOLVEMATRIX):
2299             _infobox_icon.set_from_icon_name("feConvolveMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2300             _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."));
2301             break;
2302         case(NR_FILTER_DIFFUSELIGHTING):
2303             _infobox_icon.set_from_icon_name("feDiffuseLighting-icon", Gtk::ICON_SIZE_DIALOG);
2304             _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."));
2305             break;
2306         case(NR_FILTER_DISPLACEMENTMAP):
2307             _infobox_icon.set_from_icon_name("feDisplacementMap-icon", Gtk::ICON_SIZE_DIALOG);
2308             _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."));
2309             break;
2310         case(NR_FILTER_FLOOD):
2311             _infobox_icon.set_from_icon_name("feFlood-icon", Gtk::ICON_SIZE_DIALOG);
2312             _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."));
2313             break;
2314         case(NR_FILTER_GAUSSIANBLUR):
2315             _infobox_icon.set_from_icon_name("feGaussianBlur-icon", Gtk::ICON_SIZE_DIALOG);
2316             _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."));
2317             break;
2318         case(NR_FILTER_IMAGE):
2319             _infobox_icon.set_from_icon_name("feImage-icon", Gtk::ICON_SIZE_DIALOG);
2320             _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2321             break;
2322         case(NR_FILTER_MERGE):
2323             _infobox_icon.set_from_icon_name("feMerge-icon", Gtk::ICON_SIZE_DIALOG);
2324             _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."));
2325             break;
2326         case(NR_FILTER_MORPHOLOGY):
2327             _infobox_icon.set_from_icon_name("feMorphology-icon", Gtk::ICON_SIZE_DIALOG);
2328             _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."));
2329             break;
2330         case(NR_FILTER_OFFSET):
2331             _infobox_icon.set_from_icon_name("feOffset-icon", Gtk::ICON_SIZE_DIALOG);
2332             _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."));
2333             break;
2334         case(NR_FILTER_SPECULARLIGHTING):
2335             _infobox_icon.set_from_icon_name("feSpecularLighting-icon", Gtk::ICON_SIZE_DIALOG);
2336             _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."));
2337             break;
2338         case(NR_FILTER_TILE):
2339             _infobox_icon.set_from_icon_name("feTile-icon", Gtk::ICON_SIZE_DIALOG);
2340             _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2341             break;
2342         case(NR_FILTER_TURBULENCE):
2343             _infobox_icon.set_from_icon_name("feTurbulence-icon", Gtk::ICON_SIZE_DIALOG);
2344             _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."));
2345             break;
2346         default:
2347             g_assert(false);
2348             break;
2349     }
2350     _infobox_icon.set_pixel_size(96);
2353 void FilterEffectsDialog::duplicate_primitive()
2355     SPFilter* filter = _filter_modifier.get_selected_filter();
2356     SPFilterPrimitive* origprim = _primitive_list.get_selected();
2358     if(filter && origprim) {
2359         Inkscape::XML::Node *repr;
2360         repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2361         SP_OBJECT_REPR(filter)->appendChild(repr);
2363         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2365         _primitive_list.update();
2366     }
2369 void FilterEffectsDialog::convolve_order_changed()
2371     _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2372     _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2373     _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2376 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2378     set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2381 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2383     if(!_locked) {
2384         _attr_lock = true;
2385         SPFilter *filter = _filter_modifier.get_selected_filter();
2386         const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2387         if (filter && name && SP_OBJECT_REPR(filter)){
2388             SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2389             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2390         }
2391         _attr_lock = false;
2392     }
2395 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2397     set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2400 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2402     if(!_locked) {
2403         _attr_lock = true;
2405         SPFilter *filter = _filter_modifier.get_selected_filter();
2406         const gchar* name = (const gchar*)sp_attribute_name(attr);
2407         if(filter && name && o) {
2408             update_settings_sensitivity();
2410             SP_OBJECT_REPR(o)->setAttribute(name, val);
2411             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2413             Glib::ustring undokey = "filtereffects:";
2414             undokey += name;
2415             sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2416                                    _("Set filter primitive attribute"));
2417         }
2419         _attr_lock = false;
2420     }
2423 void FilterEffectsDialog::update_filter_general_settings_view()
2425     if(_settings_initialized != true) return;
2427     if(!_locked) {
2428         _attr_lock = true;
2430         SPFilter* filter = _filter_modifier.get_selected_filter();
2432         if(filter) {
2433             _filter_general_settings->show_and_update(0, filter);
2434             _no_filter_selected.hide();
2435         }
2436         else {
2437             std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2438             vect[0]->hide_all();
2439             _no_filter_selected.show();
2440         }
2442         _attr_lock = false;
2443     }
2446 void FilterEffectsDialog::update_settings_view()
2448     update_settings_sensitivity();
2450     if(_attr_lock)
2451         return;
2453 //First Tab
2455     std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2456     for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2457     _empty_settings.show();
2459     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2460     if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2461         _infobox_icon.show();
2462         _infobox_desc.show();
2463     } else {
2464         _infobox_icon.hide();
2465         _infobox_desc.hide();
2466     }
2468     SPFilterPrimitive* prim = _primitive_list.get_selected();
2470     if(prim) {
2471         _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2472         _empty_settings.hide();
2473     }
2475 //Second Tab
2477     std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2478     vect2[0]->hide_all();
2479     _no_filter_selected.show();
2481     SPFilter* filter = _filter_modifier.get_selected_filter();
2483     if(filter) {
2484         _filter_general_settings->show_and_update(0, filter);
2485         _no_filter_selected.hide();
2486     }
2490 void FilterEffectsDialog::update_settings_sensitivity()
2492     SPFilterPrimitive* prim = _primitive_list.get_selected();
2493     const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2494     _k1->set_sensitive(use_k);
2495     _k2->set_sensitive(use_k);
2496     _k3->set_sensitive(use_k);
2497     _k4->set_sensitive(use_k);
2499 // Component transfer not yet implemented
2500 /*
2501     if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2502         SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2503         const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2504         const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2506         _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2507         _ct_slope->set_sensitive(linear);
2508         _ct_intercept->set_sensitive(linear);
2509         _ct_amplitude->set_sensitive(gamma);
2510         _ct_exponent->set_sensitive(gamma);
2511         _ct_offset->set_sensitive(gamma);
2512     }
2513 */
2516 void FilterEffectsDialog::update_color_matrix()
2518     _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2521 } // namespace Dialog
2522 } // namespace UI
2523 } // namespace Inkscape
2525 /*
2526   Local Variables:
2527   mode:c++
2528   c-file-style:"stroustrup"
2529   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2530   indent-tabs-mode:nil
2531   fill-column:99
2532   End:
2533 */
2534 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :