Code

Pot and Dutch translation update
[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 "desktop.h"
31 #include "desktop-handles.h"
32 #include "dialog-manager.h"
33 #include "dir-util.h"
34 #include "document.h"
35 #include "filter-chemistry.h"
36 #include "filter-effects-dialog.h"
37 #include "filter-enums.h"
38 #include "inkscape.h"
39 #include "path-prefix.h"
40 #include "preferences.h"
41 #include "selection.h"
42 #include "filters/blend.h"
43 #include "filters/colormatrix.h"
44 #include "filters/componenttransfer.h"
45 #include "filters/composite.h"
46 #include "filters/convolvematrix.h"
47 #include "filters/displacementmap.h"
48 #include "filters/distantlight.h"
49 #include "filters/merge.h"
50 #include "filters/mergenode.h"
51 #include "filters/offset.h"
52 #include "filters/pointlight.h"
53 #include "filters/spotlight.h"
54 #include "sp-filter-primitive.h"
55 #include "sp-gaussian-blur.h"
57 #include "style.h"
58 #include "svg/svg-color.h"
59 #include "ui/dialog/filedialog.h"
60 #include "verbs.h"
61 #include "xml/node.h"
62 #include "xml/node-observer.h"
63 #include "xml/repr.h"
64 #include <sstream>
66 #include "io/sys.h"
67 #include <iostream>
69 using namespace Inkscape::Filters;
71 namespace Inkscape {
72 namespace UI {
73 namespace Dialog {
75 using Inkscape::UI::Widget::AttrWidget;
76 using Inkscape::UI::Widget::ComboBoxEnum;
77 using Inkscape::UI::Widget::DualSpinSlider;
78 using Inkscape::UI::Widget::SpinSlider;
81 // Returns the number of inputs available for the filter primitive type
82 int input_count(const SPFilterPrimitive* prim)
83 {
84     if(!prim)
85         return 0;
86     else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
87         return 2;
88     else if(SP_IS_FEMERGE(prim)) {
89         // Return the number of feMergeNode connections plus an extra
90         int count = 1;
91         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count){};
92         return count;
93     }
94     else
95         return 1;
96 }
98 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
99 {
100 public:
101     CheckButtonAttr(bool def, const Glib::ustring& label,
102                     const Glib::ustring& tv, const Glib::ustring& fv,
103                     const SPAttributeEnum a, char* tip_text)
104         : Gtk::CheckButton(label),
105           AttrWidget(a, def),
106           _true_val(tv), _false_val(fv)
107     {
108         signal_toggled().connect(signal_attr_changed().make_slot());
109         if (tip_text) _tt.set_tip(*this, tip_text);
110     }
112     Glib::ustring get_as_attribute() const
113     {
114         return get_active() ? _true_val : _false_val;
115     }
117     void set_from_attribute(SPObject* o)
118     {
119         const gchar* val = attribute_value(o);
120         if(val) {
121             if(_true_val == val)
122                 set_active(true);
123             else if(_false_val == val)
124                 set_active(false);
125         } else {
126             set_active(get_default()->as_bool());
127         }
128     }
129 private:
130     const Glib::ustring _true_val, _false_val;
131 };
133 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
135 public:
136     SpinButtonAttr(double lower, double upper, double step_inc,
137                    double climb_rate, int digits, const SPAttributeEnum a, double def, char* tip_text)
138         : Gtk::SpinButton(climb_rate, digits),
139           AttrWidget(a, def)
140     {
141         if (tip_text) _tt.set_tip(*this, tip_text);
142         set_range(lower, upper);
143         set_increments(step_inc, 0);
145         signal_value_changed().connect(signal_attr_changed().make_slot());
146     }
148     Glib::ustring get_as_attribute() const
149     {
150         const double val = get_value();
152         if(get_digits() == 0)
153             return Glib::Ascii::dtostr((int)val);
154         else
155             return Glib::Ascii::dtostr(val);
156     }
158     void set_from_attribute(SPObject* o)
159     {
160         const gchar* val = attribute_value(o);
161         if(val){
162             set_value(Glib::Ascii::strtod(val));
163         } else {
164             set_value(get_default()->as_double());
165         }
166     }
167 };
169 template< typename T> class ComboWithTooltip : public Gtk::EventBox
171 public:
172     ComboWithTooltip<T>(T default_value, const Util::EnumDataConverter<T>& c, const SPAttributeEnum a = SP_ATTR_INVALID, char* tip_text = NULL)
173     {
174         if (tip_text) {
175             _tt.set_tip(*this, tip_text);
176         }
177         combo = new ComboBoxEnum<T>(default_value, c, a, false);
178         add(*combo);
179         show_all();
180     }
182     ~ComboWithTooltip()
183     {
184         delete combo;
185     }
187     ComboBoxEnum<T>* get_attrwidget()
188     {
189         return combo;
190     }
191 private:
192     Gtk::Tooltips _tt;
193     ComboBoxEnum<T>* combo;
194 };
196 // Contains an arbitrary number of spin buttons that use seperate attributes
197 class MultiSpinButton : public Gtk::HBox
199 public:
200     MultiSpinButton(double lower, double upper, double step_inc,
201                     double climb_rate, int digits, std::vector<SPAttributeEnum> attrs, std::vector<double> default_values, std::vector<char*> tip_text)
202     {
203         g_assert(attrs.size()==default_values.size());
204         g_assert(attrs.size()==tip_text.size());
205         for(unsigned i = 0; i < attrs.size(); ++i) {
206             _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i], default_values[i], tip_text[i]));
207             pack_start(*_spins.back(), false, false);
208         }
209     }
211     ~MultiSpinButton()
212     {
213         for(unsigned i = 0; i < _spins.size(); ++i)
214             delete _spins[i];
215     }
217     std::vector<SpinButtonAttr*>& get_spinbuttons()
218     {
219         return _spins;
220     }
221 private:
222     std::vector<SpinButtonAttr*> _spins;
223 };
225 // Contains two spinbuttons that describe a NumberOptNumber
226 class DualSpinButton : public Gtk::HBox, public AttrWidget
228 public:
229     DualSpinButton(char* def, double lower, double upper, double step_inc,
230                    double climb_rate, int digits, const SPAttributeEnum a, char* tt1, char* tt2)
231         : AttrWidget(a, def), //TO-DO: receive default num-opt-num as parameter in the constructor
232           _s1(climb_rate, digits), _s2(climb_rate, digits)
233     {
234         if (tt1) _tt.set_tip(_s1, tt1);
235         if (tt2) _tt.set_tip(_s2, tt2);
236         _s1.set_range(lower, upper);
237         _s2.set_range(lower, upper);
238         _s1.set_increments(step_inc, 0);
239         _s2.set_increments(step_inc, 0);
241         _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
242         _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
244         pack_start(_s1, false, false);
245         pack_start(_s2, false, false);
246     }
248     Gtk::SpinButton& get_spinbutton1()
249     {
250         return _s1;
251     }
253     Gtk::SpinButton& get_spinbutton2()
254     {
255         return _s2;
256     }
258     virtual Glib::ustring get_as_attribute() const
259     {
260         double v1 = _s1.get_value();
261         double v2 = _s2.get_value();
263         if(_s1.get_digits() == 0) {
264             v1 = (int)v1;
265             v2 = (int)v2;
266         }
268         return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
269     }
271     virtual void set_from_attribute(SPObject* o)
272     {
273         const gchar* val = attribute_value(o);
274         NumberOptNumber n;
275         if(val) {
276             n.set(val);
277         } else {
278             n.set(get_default()->as_charptr());
279         }
280         _s1.set_value(n.getNumber());
281         _s2.set_value(n.getOptNumber());
283     }
284 private:
285     Gtk::SpinButton _s1, _s2;
286 };
288 class ColorButton : public Gtk::ColorButton, public AttrWidget
290 public:
291     ColorButton(unsigned int def, const SPAttributeEnum a, char* tip_text)
292         : AttrWidget(a, def)
293     {
294         signal_color_set().connect(signal_attr_changed().make_slot());
295         if (tip_text) _tt.set_tip(*this, tip_text);
297         Gdk::Color col;
298         col.set_rgb(65535, 65535, 65535);
299         set_color(col);
300     }
302     // Returns the color in 'rgb(r,g,b)' form.
303     Glib::ustring get_as_attribute() const
304     {
305         std::ostringstream os;
306         const Gdk::Color c = get_color();
307         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?
308         os << "rgb(" << r << "," << g << "," << b << ")";
309         return os.str();
310     }
313     void set_from_attribute(SPObject* o)
314     {
315         const gchar* val = attribute_value(o);
316         guint32 i = 0;
317         if(val) {
318             i = sp_svg_read_color(val, 0xFFFFFFFF);
319         } else {
320             i = (guint32) get_default()->as_uint();
321         }
322         const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
323         Gdk::Color col;
324         col.set_rgb(r * 256, g * 256, b * 256);
325         set_color(col);
326     }
327 };
329 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
330 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
332 public:
333     MatrixAttr(const SPAttributeEnum a, char* tip_text = NULL)
334         : AttrWidget(a), _locked(false)
335     {
336         _model = Gtk::ListStore::create(_columns);
337         _tree.set_model(_model);
338         _tree.set_headers_visible(false);
339         _tree.show();
340         add(_tree);
341         set_shadow_type(Gtk::SHADOW_IN);
342         if (tip_text) _tt.set_tip(_tree, tip_text);
343     }
345     std::vector<double> get_values() const
346     {
347         std::vector<double> vec;
348         for(Gtk::TreeIter iter = _model->children().begin();
349             iter != _model->children().end(); ++iter) {
350             for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
351                 vec.push_back((*iter)[_columns.cols[c]]);
352         }
353         return vec;
354     }
356     void set_values(const std::vector<double>& v)
357     {
358         unsigned i = 0;
359         for(Gtk::TreeIter iter = _model->children().begin();
360             iter != _model->children().end(); ++iter) {
361             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
362                 if(i >= v.size())
363                     return;
364                 (*iter)[_columns.cols[c]] = v[i];
365                 ++i;
366             }
367         }
368     }
370     Glib::ustring get_as_attribute() const
371     {
372         std::ostringstream os;
374         for(Gtk::TreeIter iter = _model->children().begin();
375             iter != _model->children().end(); ++iter) {
376             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
377                 os << (*iter)[_columns.cols[c]] << " ";
378             }
379         }
381         return os.str();
382     }
384     void set_from_attribute(SPObject* o)
385     {
386         if(o) {
387             if(SP_IS_FECONVOLVEMATRIX(o)) {
388                 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
389                 int cols, rows;
390                 cols = (int)conv->order.getNumber();
391                 if(cols > 5)
392                     cols = 5;
393                 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
394                 update(o, rows, cols);
395             }
396             else if(SP_IS_FECOLORMATRIX(o))
397                 update(o, 4, 5);
398         }
399     }
400 private:
401     class MatrixColumns : public Gtk::TreeModel::ColumnRecord
402     {
403     public:
404         MatrixColumns()
405         {
406             cols.resize(5);
407             for(unsigned i = 0; i < cols.size(); ++i)
408                 add(cols[i]);
409         }
410         std::vector<Gtk::TreeModelColumn<double> > cols;
411     };
413     void update(SPObject* o, const int rows, const int cols)
414     {
415         if(_locked)
416             return;
418         _model->clear();
420         _tree.remove_all_columns();
422         std::vector<gdouble>* values = NULL;
423         if(SP_IS_FECOLORMATRIX(o))
424             values = &SP_FECOLORMATRIX(o)->values;
425         else if(SP_IS_FECONVOLVEMATRIX(o))
426             values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
427         else
428             return;
430         if(o) {
431             int ndx = 0;
433             for(int i = 0; i < cols; ++i) {
434                 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
435                 dynamic_cast<Gtk::CellRendererText*>(
436                     _tree.get_column_cell_renderer(i))->signal_edited().connect(
437                         sigc::mem_fun(*this, &MatrixAttr::rebind));
438             }
440             for(int r = 0; r < rows; ++r) {
441                 Gtk::TreeRow row = *(_model->append());
442                 // Default to identity matrix
443                 for(int c = 0; c < cols; ++c, ++ndx)
444                     row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
445             }
446         }
447     }
449     void rebind(const Glib::ustring&, const Glib::ustring&)
450     {
451         _locked = true;
452         signal_attr_changed()();
453         _locked = false;
454     }
456     bool _locked;
457     Gtk::TreeView _tree;
458     Glib::RefPtr<Gtk::ListStore> _model;
459     MatrixColumns _columns;
460 };
462 // Displays a matrix or a slider for feColorMatrix
463 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
465 public:
466     ColorMatrixValues()
467         : AttrWidget(SP_ATTR_VALUES),
468           // TRANSLATORS: this dialog is accessible via menu Filters - Filter editor
469           _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.")),
470           _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
471           _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
472           _label(_("None"), Gtk::ALIGN_LEFT),
473           _use_stored(false),
474           _saturation_store(0),
475           _angle_store(0)
476     {
477         _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
478         _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
479         _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
480         signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
482         _matrix.show();
483         _saturation.show();
484         _angle.show();
485         _label.show();
486         _label.set_sensitive(false);
488         set_shadow_type(Gtk::SHADOW_NONE);
489     }
491     virtual void set_from_attribute(SPObject* o)
492     {
493         if(SP_IS_FECOLORMATRIX(o)) {
494             SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
495             remove();
496             switch(col->type) {
497                 case COLORMATRIX_SATURATE:
498                     add(_saturation);
499                     if(_use_stored)
500                         _saturation.set_value(_saturation_store);
501                     else
502                         _saturation.set_from_attribute(o);
503                     break;
504                 case COLORMATRIX_HUEROTATE:
505                     add(_angle);
506                     if(_use_stored)
507                         _angle.set_value(_angle_store);
508                     else
509                         _angle.set_from_attribute(o);
510                     break;
511                 case COLORMATRIX_LUMINANCETOALPHA:
512                     add(_label);
513                     break;
514                 case COLORMATRIX_MATRIX:
515                 default:
516                     add(_matrix);
517                     if(_use_stored)
518                         _matrix.set_values(_matrix_store);
519                     else
520                         _matrix.set_from_attribute(o);
521                     break;
522             }
523             _use_stored = true;
524         }
525     }
527     virtual Glib::ustring get_as_attribute() const
528     {
529         const Widget* w = get_child();
530         if(w == &_label)
531             return "";
532         else
533             return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
534     }
536     void clear_store()
537     {
538         _use_stored = false;
539     }
540 private:
541     void update_store()
542     {
543         const Widget* w = get_child();
544         if(w == &_matrix)
545             _matrix_store = _matrix.get_values();
546         else if(w == &_saturation)
547             _saturation_store = _saturation.get_value();
548         else if(w == &_angle)
549             _angle_store = _angle.get_value();
550     }
552     MatrixAttr _matrix;
553     SpinSlider _saturation;
554     SpinSlider _angle;
555     Gtk::Label _label;
557     // Store separate values for the different color modes
558     bool _use_stored;
559     std::vector<double> _matrix_store;
560     double _saturation_store;
561     double _angle_store;
562 };
564 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
566 //Displays a chooser for feImage input
567 //It may be a filename or the id for an SVG Element
568 //described in xlink:href syntax
569 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
571 public:
572     FileOrElementChooser(const SPAttributeEnum a)
573         : AttrWidget(a)
574     {
575         pack_start(_entry, false, false);
576         pack_start(_fromFile, false, false);
577         pack_start(_fromSVGElement, false, false);
579         _fromFile.set_label(_("Image File"));
580         _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
582         _fromSVGElement.set_label(_("Selected SVG Element"));
583         _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
585         _entry.signal_changed().connect(signal_attr_changed().make_slot());
587         show_all();
589     }
591     // Returns the element in xlink:href form.
592     Glib::ustring get_as_attribute() const
593     {
594         return _entry.get_text();
595     }
598     void set_from_attribute(SPObject* o)
599     {
600         const gchar* val = attribute_value(o);
601         if(val) {
602             _entry.set_text(val);
603         } else {
604             _entry.set_text("");
605         }
606     }
608     void set_desktop(SPDesktop* d){
609         _desktop = d;
610     }
612 private:
613     void select_svg_element(){
614         Inkscape::Selection* sel = sp_desktop_selection(_desktop);
615         if (sel->isEmpty()) return;
616         Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
617         if (!node || !node->matchAttributeName("id")) return;
619         std::ostringstream xlikhref;
620         xlikhref << "#" << node->attribute("id");
621         _entry.set_text(xlikhref.str());
622     }
624     void select_file(){
626         //# Get the current directory for finding files
627         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
628         Glib::ustring open_path;
629         Glib::ustring attr = prefs->getString("/dialogs/open/path");
630         if (!attr.empty())
631             open_path = attr;
633         //# Test if the open_path directory exists
634         if (!Inkscape::IO::file_test(open_path.c_str(),
635                   (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
636             open_path = "";
638         //# If no open path, default to our home directory
639         if (open_path.size() < 1)
640             {
641             open_path = g_get_home_dir();
642             open_path.append(G_DIR_SEPARATOR_S);
643             }
645         //# Create a dialog if we don't already have one
646         if (!selectFeImageFileInstance) {
647             selectFeImageFileInstance =
648                   Inkscape::UI::Dialog::FileOpenDialog::create(
649                      *_desktop->getToplevel(),
650                      open_path,
651                      Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not just svg*/
652                      (char const *)_("Select an image to be used as feImage input"));
653         }
655         //# Show the dialog
656         bool const success = selectFeImageFileInstance->show();
657         if (!success)
658             return;
660         //# User selected something.  Get name and type
661         Glib::ustring fileName = selectFeImageFileInstance->getFilename();
663         if (fileName.size() > 0) {
665             Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
667             if ( newFileName.size() > 0)
668                 fileName = newFileName;
669             else
670                 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
672             open_path = fileName;
673             open_path.append(G_DIR_SEPARATOR_S);
674             prefs->setString("/dialogs/open/path", open_path);
676             _entry.set_text(fileName);
677         }
678         return;
679     }
681     Gtk::Entry _entry;
682     Gtk::Button _fromFile;
683     Gtk::Button _fromSVGElement;
684     SPDesktop* _desktop;
685 };
687 class FilterEffectsDialog::Settings
689 public:
690     typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
692     Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
693         : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
694     {
695         _groups.resize(_max_types);
696         _attrwidgets.resize(_max_types);
697         _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
699         for(int i = 0; i < _max_types; ++i) {
700             _groups[i] = new Gtk::VBox;
701             b.pack_start(*_groups[i], false, false);
702         }
703         _current_type = 0;
704     }
706     ~Settings()
707     {
708         for(int i = 0; i < _max_types; ++i) {
709             delete _groups[i];
710             for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
711                 delete _attrwidgets[i][j];
712         }
713     }
715     // Show the active settings group and update all the AttrWidgets with new values
716     void show_and_update(const int t, SPObject* ob)
717     {
718         if(t != _current_type) {
719             type(t);
720             for(unsigned i = 0; i < _groups.size(); ++i)
721                 _groups[i]->hide();
722         }
723         if(t >= 0)
724             _groups[t]->show_all();
726         _dialog.set_attrs_locked(true);
727         for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
728             _attrwidgets[_current_type][i]->set_from_attribute(ob);
729         _dialog.set_attrs_locked(false);
730     }
732     int get_current_type() const
733     {
734         return _current_type;
735     }
737     void type(const int t)
738     {
739         _current_type = t;
740     }
742     void add_no_params()
743     {
744         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
745         add_widget(lbl, "");
746     }
748     void add_notimplemented()
749     {
750         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
751         add_widget(lbl, "");
752     }
754     // LightSource
755     LightSourceControl* add_lightsource();
757     // CheckBox
758     CheckButtonAttr* add_checkbutton(bool def, const SPAttributeEnum attr, const Glib::ustring& label,
759                                      const Glib::ustring& tv, const Glib::ustring& fv, char* tip_text = NULL)
760     {
761         CheckButtonAttr* cb = new CheckButtonAttr(def, label, tv, fv, attr, tip_text);
762         add_widget(cb, "");
763         add_attr_widget(cb);
764         return cb;
765     }
767     // ColorButton
768     ColorButton* add_color(unsigned int def, const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text = NULL)
769     {
770         ColorButton* col = new ColorButton(def, attr, tip_text);
771         add_widget(col, label);
772         add_attr_widget(col);
773         return col;
774     }
776     // Matrix
777     MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text)
778     {
779         MatrixAttr* conv = new MatrixAttr(attr, tip_text);
780         add_widget(conv, label);
781         add_attr_widget(conv);
782         return conv;
783     }
785     // ColorMatrixValues
786     ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
787     {
788         ColorMatrixValues* cmv = new ColorMatrixValues();
789         add_widget(cmv, label);
790         add_attr_widget(cmv);
791         return cmv;
792     }
794     // SpinSlider
795     SpinSlider* add_spinslider(double def, const SPAttributeEnum attr, const Glib::ustring& label,
796                          const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
797     {
798         SpinSlider* spinslider = new SpinSlider(def, lo, hi, step_inc, climb, digits, attr, tip_text);
799         add_widget(spinslider, label);
800         add_attr_widget(spinslider);
801         return spinslider;
802     }
804     // DualSpinSlider
805     DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
806                                        const double lo, const double hi, const double step_inc,
807                                        const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
808     {
809         DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
810         add_widget(dss, label);
811         add_attr_widget(dss);
812         return dss;
813     }
815     // DualSpinButton
816     DualSpinButton* add_dualspinbutton(char* defalt_value, const SPAttributeEnum attr, const Glib::ustring& label,
817                                        const double lo, const double hi, const double step_inc,
818                                        const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
819     {
820         DualSpinButton* dsb = new DualSpinButton(defalt_value, lo, hi, step_inc, climb, digits, attr, tip1, tip2);
821         add_widget(dsb, label);
822         add_attr_widget(dsb);
823         return dsb;
824     }
826     // MultiSpinButton
827     MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
828                                          const Glib::ustring& label, const double lo, const double hi,
829                                          const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
830     {
831         std::vector<SPAttributeEnum> attrs;
832         attrs.push_back(attr1);
833         attrs.push_back(attr2);
835         std::vector<double> default_values;
836         default_values.push_back(def1);
837         default_values.push_back(def2);
839         std::vector<char*> tips;
840         tips.push_back(tip1);
841         tips.push_back(tip2);
843         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
844         add_widget(msb, label);
845         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
846             add_attr_widget(msb->get_spinbuttons()[i]);
847         return msb;
848     }
849     MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
850                                          const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
851                                          const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
852     {
853         std::vector<SPAttributeEnum> attrs;
854         attrs.push_back(attr1);
855         attrs.push_back(attr2);
856         attrs.push_back(attr3);
858         std::vector<double> default_values;
859         default_values.push_back(def1);
860         default_values.push_back(def2);
861         default_values.push_back(def3);
863         std::vector<char*> tips;
864         tips.push_back(tip1);
865         tips.push_back(tip2);
866         tips.push_back(tip3);
868         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
869         add_widget(msb, label);
870         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
871             add_attr_widget(msb->get_spinbuttons()[i]);
872         return msb;
873     }
875     // FileOrElementChooser
876     FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
877     {
878         FileOrElementChooser* foech = new FileOrElementChooser(attr);
879         foech->set_desktop(_dialog.getDesktop());
880         add_widget(foech, label);
881         add_attr_widget(foech);
882         return foech;
883     }
885     // ComboBoxEnum
886     template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
887                                   const Glib::ustring& label,
888                                   const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
889     {
890         ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
891         add_widget(combo, label);
892         add_attr_widget(combo->get_attrwidget());
893         return combo->get_attrwidget();
894     }
895 private:
896     Gtk::Tooltips _tt;
898     void add_attr_widget(AttrWidget* a)
899     {
900         _attrwidgets[_current_type].push_back(a);
901         a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
902     }
904     /* Adds a new settings widget using the specified label. The label will be formatted with a colon
905        and all widgets within the setting group are aligned automatically. */
906     void add_widget(Gtk::Widget* w, const Glib::ustring& label)
907     {
908         Gtk::Label *lbl = 0;
909         Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
910         hb->set_spacing(12);
912         if(label != "") {
913             //lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT)); colon now in label (LP #358921)
914             lbl = Gtk::manage(new Gtk::Label(label, Gtk::ALIGN_LEFT));
915             hb->pack_start(*lbl, false, false);
916             _size_group->add_widget(*lbl);
917             lbl->show();
918         }
920         hb->pack_start(*w);
921         _groups[_current_type]->pack_start(*hb);
922         hb->show();
923         w->show();
924     }
926     std::vector<Gtk::VBox*> _groups;
927     Glib::RefPtr<Gtk::SizeGroup> _size_group;
928     FilterEffectsDialog& _dialog;
929     SetAttrSlot _set_attr_slot;
930     std::vector<std::vector< AttrWidget*> > _attrwidgets;
931     int _current_type, _max_types;
932 };
934 // Settings for the three light source objects
935 class FilterEffectsDialog::LightSourceControl : public AttrWidget
937 public:
938     LightSourceControl(FilterEffectsDialog& d)
939         : AttrWidget(SP_ATTR_INVALID),
940           _dialog(d),
941           _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
942           _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
943           _light_source(LightSourceConverter),
944           _locked(false)
945     {
946         _light_box.pack_start(_light_label, false, false);
947         _light_box.pack_start(_light_source);
948         _light_box.show_all();
949         _light_box.set_spacing(12);
950         _dialog._sizegroup->add_widget(_light_label);
952         _box.add(_light_box);
953         _box.reorder_child(_light_box, 0);
954         _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
956         // FIXME: these range values are complete crap
958         _settings.type(LIGHT_DISTANT);
959         _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"));
960         _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"));
962         _settings.type(LIGHT_POINT);
963         _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"));
965         _settings.type(LIGHT_SPOT);
966         _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.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
968                                       SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
969                                       _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
970         _settings.add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
971         //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.
972         _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."));
973     }
975     Gtk::VBox& get_box()
976     {
977         return _box;
978     }
979 protected:
980     Glib::ustring get_as_attribute() const
981     {
982         return "";
983     }
984     void set_from_attribute(SPObject* o)
985     {
986         if(_locked)
987             return;
989         _locked = true;
991         SPObject* child = o->children;
993         if(SP_IS_FEDISTANTLIGHT(child))
994             _light_source.set_active(0);
995         else if(SP_IS_FEPOINTLIGHT(child))
996             _light_source.set_active(1);
997         else if(SP_IS_FESPOTLIGHT(child))
998             _light_source.set_active(2);
999         else
1000             _light_source.set_active(-1);
1002         update();
1004         _locked = false;
1005     }
1006 private:
1007     void on_source_changed()
1008     {
1009         if(_locked)
1010             return;
1012         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1013         if(prim) {
1014             _locked = true;
1016             SPObject* child = prim->children;
1017             const int ls = _light_source.get_active_row_number();
1018             // Check if the light source type has changed
1019             if(!(ls == -1 && !child) &&
1020                !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1021                !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1022                !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1023                 if(child)
1024                     sp_repr_unparent(child->repr);
1026                 if(ls != -1) {
1027                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1028                     Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1029                     prim->repr->appendChild(repr);
1030                     Inkscape::GC::release(repr);
1031                 }
1033                 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1034                 update();
1035             }
1037             _locked = false;
1038         }
1039     }
1041     void update()
1042     {
1043         _box.hide_all();
1044         _box.show();
1045         _light_box.show_all();
1047         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1048         if(prim && prim->children)
1049             _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1050     }
1052     FilterEffectsDialog& _dialog;
1053     Gtk::VBox _box;
1054     Settings _settings;
1055     Gtk::HBox _light_box;
1056     Gtk::Label _light_label;
1057     ComboBoxEnum<LightSource> _light_source;
1058     bool _locked;
1059 };
1061 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1063     LightSourceControl* ls = new LightSourceControl(_dialog);
1064     add_attr_widget(ls);
1065     add_widget(&ls->get_box(), "");
1066     return ls;
1069 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1070                                           sigc::slot<void> rem)
1072     Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1074     menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1075     Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1076     menu->append(*mi);
1077     mi->signal_activate().connect(rem);
1078     mi->show();
1079     menu->accelerate(parent);
1081     return menu;
1084 /*** FilterModifier ***/
1085 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1086     : _dialog(d), _add(Gtk::Stock::NEW), _observer(new Inkscape::XML::SignalObserver)
1088     Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1089     pack_start(*sw);
1090     pack_start(_add, false, false);
1091     sw->add(_list);
1093     _model = Gtk::ListStore::create(_columns);
1094     _list.set_model(_model);
1095     _cell_toggle.set_active(true);
1096     const int selcol = _list.append_column("", _cell_toggle);
1097     Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1098     if(col)
1099        col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1100     _list.append_column_editable(_("_Filter"), _columns.label);
1101     ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1102         signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1104     sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1105     sw->set_shadow_type(Gtk::SHADOW_IN);
1106     show_all_children();
1107     _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1108     _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1109     _list.signal_button_release_event().connect_notify(
1110         sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1111     _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1112                               sigc::mem_fun(*this, &FilterModifier::remove_filter));
1113     _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1114                                  _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1115     _menu->accelerate(*this);
1117     _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1118     _observer->signal_changed().connect(signal_filter_changed().make_slot());
1119     g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1120                      G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1122     g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1123                      G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1124     g_signal_connect(G_OBJECT(INKSCAPE), "deactivate_desktop",
1125                      G_CALLBACK(&FilterModifier::on_deactivate_desktop), this);
1127     on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1128     update_filters();
1131 FilterEffectsDialog::FilterModifier::~FilterModifier()
1133    _resource_changed.disconnect();
1134    _doc_replaced.disconnect();
1137 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1139     me->_doc_replaced.disconnect();
1140     me->_doc_replaced = desktop->connectDocumentReplaced(
1141         sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1143     me->_resource_changed.disconnect();
1144     me->_resource_changed =
1145         sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1146                                               sigc::mem_fun(me, &FilterModifier::update_filters));
1148     me->_dialog.setDesktop(desktop);
1150     me->update_filters();
1153 void FilterEffectsDialog::FilterModifier::on_deactivate_desktop(Application*, SPDesktop* /*desktop*/, FilterModifier* me)
1155     me->_doc_replaced.disconnect();
1156     me->_resource_changed.disconnect();
1157     me->_dialog.setDesktop(NULL);
1161 // When the selection changes, show the active filter(s) in the dialog
1162 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1163                                                                        Selection *sel,
1164                                                                        FilterModifier* fm)
1166     if(fm && sel)
1167         fm->update_selection(sel);
1170 // Update each filter's sel property based on the current object selection;
1171 //  If the filter is not used by any selected object, sel = 0,
1172 //  otherwise sel is set to the total number of filters in use by selected objects
1173 //  If only one filter is in use, it is selected
1174 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1176     std::set<SPObject*> used;
1178     for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1179         SPObject *obj = SP_OBJECT (i->data);
1180         SPStyle *style = SP_OBJECT_STYLE (obj);
1181         if(!style || !SP_IS_ITEM(obj)) continue;
1183         if(style->filter.set && style->getFilter())
1184             used.insert(style->getFilter());
1185         else
1186             used.insert(0);
1187     }
1189     const int size = used.size();
1191     for(Gtk::TreeIter iter = _model->children().begin();
1192         iter != _model->children().end(); ++iter) {
1193         if(used.find((*iter)[_columns.filter]) != used.end()) {
1194             // If only one filter is in use by the selection, select it
1195             if(size == 1)
1196                 _list.get_selection()->select(iter);
1197             (*iter)[_columns.sel] = size;
1198         }
1199         else
1200             (*iter)[_columns.sel] = 0;
1201     }
1204 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1206     _observer->set(get_selected_filter());
1207     signal_filter_changed()();
1210 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1212     Gtk::TreeModel::iterator iter = _model->get_iter(path);
1214     if(iter) {
1215         SPFilter* filter = (*iter)[_columns.filter];
1216         filter->setLabel(text.c_str());
1217         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1218         if(iter)
1219             (*iter)[_columns.label] = text;
1220     }
1223 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1225     Gtk::TreeIter iter = _model->get_iter(path);
1227     if(iter) {
1228         SPDesktop *desktop = _dialog.getDesktop();
1229         SPDocument *doc = sp_desktop_document(desktop);
1230         SPFilter* filter = (*iter)[_columns.filter];
1231         Inkscape::Selection *sel = sp_desktop_selection(desktop);
1233         /* If this filter is the only one used in the selection, unset it */
1234         if((*iter)[_columns.sel] == 1)
1235             filter = 0;
1237         GSList const *items = sel->itemList();
1239         for (GSList const *i = items; i != NULL; i = i->next) {
1240             SPItem * item = SP_ITEM(i->data);
1241             SPStyle *style = SP_OBJECT_STYLE(item);
1242             g_assert(style != NULL);
1244             if(filter)
1245                 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1246             else
1247                 ::remove_filter(item, false);
1249             SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1250         }
1252         update_selection(sel);
1253         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS,  _("Apply filter"));
1254     }
1257 /* Add all filters in the document to the combobox.
1258    Keeps the same selection if possible, otherwise selects the first element */
1259 void FilterEffectsDialog::FilterModifier::update_filters()
1261     SPDesktop* desktop = _dialog.getDesktop();
1262     SPDocument* document = sp_desktop_document(desktop);
1263     const GSList* filters = sp_document_get_resource_list(document, "filter");
1265     _model->clear();
1267     for(const GSList *l = filters; l; l = l->next) {
1268         Gtk::TreeModel::Row row = *_model->append();
1269         SPFilter* f = (SPFilter*)l->data;
1270         row[_columns.filter] = f;
1271         const gchar* lbl = f->label();
1272         const gchar* id = f->getId();
1273         row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1274     }
1276     update_selection(desktop->selection);
1277     _dialog.update_filter_general_settings_view();
1280 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1282     if(_list.get_selection()) {
1283         Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1285         if(i)
1286             return (*i)[_columns.filter];
1287     }
1289     return 0;
1292 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1294     if(filter) {
1295         for(Gtk::TreeModel::iterator i = _model->children().begin();
1296             i != _model->children().end(); ++i) {
1297             if((*i)[_columns.filter] == filter) {
1298                 _list.get_selection()->select(i);
1299                 break;
1300             }
1301         }
1302     }
1305 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1307     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1308         const bool sensitive = get_selected_filter() != NULL;
1309         _menu->items()[0].set_sensitive(sensitive);
1310         _menu->items()[1].set_sensitive(sensitive);
1311         _menu->popup(event->button, event->time);
1312     }
1315 void FilterEffectsDialog::FilterModifier::add_filter()
1317     SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1318     SPFilter* filter = new_filter(doc);
1320     const int count = _model->children().size();
1321     std::ostringstream os;
1322     os << _("filter") << count;
1323     filter->setLabel(os.str().c_str());
1325     update_filters();
1327     select_filter(filter);
1329     sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1332 void FilterEffectsDialog::FilterModifier::remove_filter()
1334     SPFilter *filter = get_selected_filter();
1336     if(filter) {
1337         SPDocument* doc = filter->document;
1338         sp_repr_unparent(filter->repr);
1340         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1342         update_filters();
1343     }
1346 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1348     SPFilter* filter = get_selected_filter();
1350     if(filter) {
1351         Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1352         repr = repr->duplicate(repr->document());
1353         parent->appendChild(repr);
1355         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1357         update_filters();
1358     }
1361 void FilterEffectsDialog::FilterModifier::rename_filter()
1363     _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1366 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1367     : Glib::ObjectBase(typeid(CellRendererConnection)),
1368       _primitive(*this, "primitive", 0)
1369 {}
1371 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1373     return _primitive.get_proxy();
1376 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1378     _text_width = w;
1381 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1383     return _text_width;
1386 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1387     Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1388     int* x_offset, int* y_offset, int* width, int* height) const
1390     PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1392     if(x_offset)
1393         (*x_offset) = 0;
1394     if(y_offset)
1395         (*y_offset) = 0;
1396     if(width)
1397         (*width) = size * primlist.primitive_count() + _text_width * 7;
1398     if(height) {
1399         // Scale the height depending on the number of inputs, unless it's
1400         // the first primitive, in which case there are no connections
1401         SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1402         (*height) = size * input_count(prim);
1403     }
1406 /*** PrimitiveList ***/
1407 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1408     : _dialog(d),
1409       _in_drag(0),
1410       _observer(new Inkscape::XML::SignalObserver)
1412     d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1414     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1415     signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1417     _model = Gtk::ListStore::create(_columns);
1419     set_reorderable(true);
1421     set_model(_model);
1422     append_column(_("_Effect"), _columns.type);
1424     _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1425     get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1426     signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1428     _connection_cell.set_text_width(init_text());
1430     int cols_count = append_column(_("Connections"), _connection_cell);
1431     Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1432     if(col)
1433        col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1436 // Sets up a vertical Pango context/layout, and returns the largest
1437 // width needed to render the FilterPrimitiveInput labels.
1438 int FilterEffectsDialog::PrimitiveList::init_text()
1440     // Set up a vertical context+layout
1441     Glib::RefPtr<Pango::Context> context = create_pango_context();
1442     const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1443     context->set_matrix(matrix);
1444     _vertical_layout = Pango::Layout::create(context);
1446     int maxfont = 0;
1447     for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1448         _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1449         int fontw, fonth;
1450         _vertical_layout->get_pixel_size(fontw, fonth);
1451         if(fonth > maxfont)
1452             maxfont = fonth;
1453     }
1455     return maxfont;
1458 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1460     return _signal_primitive_changed;
1463 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1465     _observer->set(get_selected());
1466     signal_primitive_changed()();
1467     _dialog._color_matrix_values->clear_store();
1470 /* Add all filter primitives in the current to the list.
1471    Keeps the same selection if possible, otherwise selects the first element */
1472 void FilterEffectsDialog::PrimitiveList::update()
1474     SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1475     const SPFilterPrimitive* active_prim = get_selected();
1476     bool active_found = false;
1478     _model->clear();
1480     if(f) {
1481         _dialog._primitive_box.set_sensitive(true);
1482         _dialog.update_filter_general_settings_view();
1483         for(SPObject *prim_obj = f->children;
1484                 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1485                 prim_obj = prim_obj->next) {
1486             SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1487             if(prim) {
1488                 Gtk::TreeModel::Row row = *_model->append();
1489                 row[_columns.primitive] = prim;
1490                 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1491                 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1492                 row[_columns.id] = prim->getId();
1494                 if(prim == active_prim) {
1495                     get_selection()->select(row);
1496                     active_found = true;
1497                 }
1498             }
1499         }
1501         if(!active_found && _model->children().begin())
1502             get_selection()->select(_model->children().begin());
1504         columns_autosize();
1505     }
1506     else {
1507         _dialog._primitive_box.set_sensitive(false);
1508     }
1511 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1513     _primitive_menu = menu;
1516 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1518     if(_dialog._filter_modifier.get_selected_filter()) {
1519         Gtk::TreeModel::iterator i = get_selection()->get_selected();
1520         if(i)
1521             return (*i)[_columns.primitive];
1522     }
1524     return 0;
1527 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1529     for(Gtk::TreeIter i = _model->children().begin();
1530         i != _model->children().end(); ++i) {
1531         if((*i)[_columns.primitive] == prim)
1532             get_selection()->select(i);
1533     }
1536 void FilterEffectsDialog::PrimitiveList::remove_selected()
1538     SPFilterPrimitive* prim = get_selected();
1540     if(prim) {
1541         _observer->set(0);
1543         sp_repr_unparent(prim->repr);
1545         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1546                          _("Remove filter primitive"));
1548         update();
1549     }
1552 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1554     Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1555     Glib::RefPtr<Gdk::Window> win = get_bin_window();
1556     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1558     SPFilterPrimitive* prim = get_selected();
1559     int row_count = get_model()->children().size();
1561     int fheight = CellRendererConnection::size;
1562     Gdk::Rectangle rct, vis;
1563     Gtk::TreeIter row = get_model()->children().begin();
1564     int text_start_x = 0;
1565     if(row) {
1566         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1567         get_visible_rect(vis);
1568         int vis_x, vis_y;
1569         tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1571         text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter._length + 1) + 1;
1572         for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1573             _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1574             const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1575             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());
1576             get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1577             get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1578         }
1579     }
1581     int row_index = 0;
1582     for(; row != get_model()->children().end(); ++row, ++row_index) {
1583         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1584         const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1586         // Check mouse state
1587         int mx, my;
1588         Gdk::ModifierType mask;
1589         get_bin_window()->get_pointer(mx, my, mask);
1591         // Outline the bottom of the connection area
1592         const int outline_x = x + fheight * (row_count - row_index);
1593         get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1595         // Side outline
1596         get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1598         std::vector<Gdk::Point> con_poly;
1599         int con_drag_y = 0;
1600         bool inside;
1601         const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1602         const int inputs = input_count(row_prim);
1604         if(SP_IS_FEMERGE(row_prim)) {
1605             for(int i = 0; i < inputs; ++i) {
1606                 inside = do_connection_node(row, i, con_poly, mx, my);
1607                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1608                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1609                                                inside, con_poly);
1611                 if(_in_drag == (i + 1))
1612                     con_drag_y = con_poly[2].get_y();
1614                 if(_in_drag != (i + 1) || row_prim != prim)
1615                     draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1616             }
1617         }
1618         else {
1619             // Draw "in" shape
1620             inside = do_connection_node(row, 0, con_poly, mx, my);
1621             con_drag_y = con_poly[2].get_y();
1622             get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1623                                            darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1624                                            inside, con_poly);
1626             // Draw "in" connection
1627             if(_in_drag != 1 || row_prim != prim)
1628                 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1630             if(inputs == 2) {
1631                 // Draw "in2" shape
1632                 inside = do_connection_node(row, 1, con_poly, mx, my);
1633                 if(_in_drag == 2)
1634                     con_drag_y = con_poly[2].get_y();
1635                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1636                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1637                                                inside, con_poly);
1638                 // Draw "in2" connection
1639                 if(_in_drag != 2 || row_prim != prim)
1640                     draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1641             }
1642         }
1644         // Draw drag connection
1645         if(row_prim == prim && _in_drag) {
1646             get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1647                                         mx, con_drag_y);
1648             get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1649         }
1650     }
1652     return true;
1655 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1656                                                          const int text_start_x, const int x1, const int y1,
1657                                                          const int row_count)
1659     int src_id = 0;
1660     Gtk::TreeIter res = find_result(input, attr, src_id);
1661     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1662     Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1663     Glib::RefPtr<Gdk::GC> gc;
1665     const bool is_first = input == get_model()->children().begin();
1666     const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1667     const bool use_default = !res && !is_merge;
1669     if(res == input || (use_default && is_first)) {
1670         // Draw straight connection to a standard input
1671         // Draw a lighter line for an implicit connection to a standard input
1672         const int tw = _connection_cell.get_text_width();
1673         gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1674         gc = (use_default && is_first) ? lightgc : darkgc;
1675         get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1676         get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1677     }
1678     else {
1679         // Draw an 'L'-shaped connection to another filter primitive
1680         // If no connection is specified, draw a light connection to the previous primitive
1681         gc = use_default ? lightgc : darkgc;
1683         if(use_default) {
1684             res = input;
1685             --res;
1686         }
1688         if(res) {
1689             Gdk::Rectangle rct;
1691             get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1692             const int fheight = CellRendererConnection::size;
1694             get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1695             const int row_index = find_index(res);
1696             const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1697             const int y2 = rct.get_y() + rct.get_height();
1699             // Draw a bevelled 'L'-shaped connection
1700             get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1701             get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1702             get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1703         }
1704     }
1707 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1708 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1709                                                             std::vector<Gdk::Point>& points,
1710                                                             const int ix, const int iy)
1712     Gdk::Rectangle rct;
1713     const int icnt = input_count((*row)[_columns.primitive]);
1715     get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1716     const int fheight = CellRendererConnection::size;
1718     get_cell_area(_model->get_path(row), *get_column(1), rct);
1719     const float h = rct.get_height() / icnt;
1721     const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1722     const int con_w = (int)(fheight * 0.35f);
1723     const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1724     points.clear();
1725     points.push_back(Gdk::Point(x, con_y));
1726     points.push_back(Gdk::Point(x, con_y + con_w * 2));
1727     points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1729     return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1732 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1733                                                                     const int attr, int& src_id)
1735     SPFilterPrimitive* prim = (*start)[_columns.primitive];
1736     Gtk::TreeIter target = _model->children().end();
1737     int image = 0;
1739     if(SP_IS_FEMERGE(prim)) {
1740         int c = 0;
1741         bool found = false;
1742         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1743             if(c == attr && SP_IS_FEMERGENODE(o)) {
1744                 image = SP_FEMERGENODE(o)->input;
1745                 found = true;
1746             }
1747         }
1748         if(!found)
1749             return target;
1750     }
1751     else {
1752         if(attr == SP_ATTR_IN)
1753             image = prim->image_in;
1754         else if(attr == SP_ATTR_IN2) {
1755             if(SP_IS_FEBLEND(prim))
1756                 image = SP_FEBLEND(prim)->in2;
1757             else if(SP_IS_FECOMPOSITE(prim))
1758                 image = SP_FECOMPOSITE(prim)->in2;
1759             else if(SP_IS_FEDISPLACEMENTMAP(prim))
1760                 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1761             else
1762                 return target;
1763         }
1764         else
1765             return target;
1766     }
1768     if(image >= 0) {
1769         for(Gtk::TreeIter i = _model->children().begin();
1770             i != start; ++i) {
1771             if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1772                 target = i;
1773         }
1774         return target;
1775     }
1776     else if(image < -1) {
1777         src_id = -(image + 2);
1778         return start;
1779     }
1781     return target;
1784 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1786     int i = 0;
1787     for(Gtk::TreeIter iter = _model->children().begin();
1788         iter != target; ++iter, ++i){};
1789     return i;
1792 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1794     Gtk::TreePath path;
1795     Gtk::TreeViewColumn* col;
1796     const int x = (int)e->x, y = (int)e->y;
1797     int cx, cy;
1799     _drag_prim = 0;
1801     if(get_path_at_pos(x, y, path, col, cx, cy)) {
1802         Gtk::TreeIter iter = _model->get_iter(path);
1803         std::vector<Gdk::Point> points;
1805         _drag_prim = (*iter)[_columns.primitive];
1806         const int icnt = input_count(_drag_prim);
1808         for(int i = 0; i < icnt; ++i) {
1809             if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1810                 _in_drag = i + 1;
1811                 break;
1812             }
1813         }
1815         queue_draw();
1816     }
1818     if(_in_drag) {
1819         _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1820         _autoscroll = 0;
1821         get_selection()->select(path);
1822         return true;
1823     }
1824     else
1825         return Gtk::TreeView::on_button_press_event(e);
1828 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1830     const int speed = 10;
1831     const int limit = 15;
1833     Gdk::Rectangle vis;
1834     get_visible_rect(vis);
1835     int vis_x, vis_y;
1836     tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1837     const int top = vis_y + vis.get_height();
1839     // When autoscrolling during a connection drag, set the speed based on
1840     // where the mouse is in relation to the edges.
1841     if(e->y < vis_y)
1842         _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1843     else if(e->y < vis_y + limit)
1844         _autoscroll = -speed;
1845     else if(e->y > top)
1846         _autoscroll = (int)(speed + (e->y - top) / 5);
1847     else if(e->y > top - limit)
1848         _autoscroll = speed;
1849     else
1850         _autoscroll = 0;
1852     queue_draw();
1854     return Gtk::TreeView::on_motion_notify_event(e);
1857 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1859     SPFilterPrimitive *prim = get_selected(), *target;
1861     _scroll_connection.disconnect();
1863     if(_in_drag && prim) {
1864         Gtk::TreePath path;
1865         Gtk::TreeViewColumn* col;
1866         int cx, cy;
1868         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1869             const gchar *in_val = 0;
1870             Glib::ustring result;
1871             Gtk::TreeIter target_iter = _model->get_iter(path);
1872             target = (*target_iter)[_columns.primitive];
1873             col = get_column(1);
1875             Gdk::Rectangle rct;
1876             get_cell_area(path, *col, rct);
1877             const int twidth = _connection_cell.get_text_width();
1878             const int sources_x = rct.get_width() - twidth * FPInputConverter._length;
1879             if(cx > sources_x) {
1880                 int src = (cx - sources_x) / twidth;
1881                 if (src < 0) {
1882                     src = 0;
1883                 } else if(src >= static_cast<int>(FPInputConverter._length)) {
1884                     src = FPInputConverter._length - 1;
1885                 }
1886                 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1887                 in_val = result.c_str();
1888             }
1889             else {
1890                 // Ensure that the target comes before the selected primitive
1891                 for(Gtk::TreeIter iter = _model->children().begin();
1892                     iter != get_selection()->get_selected(); ++iter) {
1893                     if(iter == target_iter) {
1894                         Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1895                         // Make sure the target has a result
1896                         const gchar *gres = repr->attribute("result");
1897                         if(!gres) {
1898                             result = sp_filter_get_new_result_name(SP_FILTER(prim->parent));
1899                             repr->setAttribute("result", result.c_str());
1900                             in_val = result.c_str();
1901                         }
1902                         else
1903                             in_val = gres;
1904                         break;
1905                     }
1906                 }
1907             }
1909             if(SP_IS_FEMERGE(prim)) {
1910                 int c = 1;
1911                 bool handled = false;
1912                 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1913                     if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1914                         // If input is null, delete it
1915                         if(!in_val) {
1916                             sp_repr_unparent(o->repr);
1917                             sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1918                                              _("Remove merge node"));
1919                             (*get_selection()->get_selected())[_columns.primitive] = prim;
1920                         }
1921                         else
1922                             _dialog.set_attr(o, SP_ATTR_IN, in_val);
1923                         handled = true;
1924                     }
1925                 }
1926                 // Add new input?
1927                 if(!handled && c == _in_drag && in_val) {
1928                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1929                     Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1930                     repr->setAttribute("inkscape:collect", "always");
1931                     prim->repr->appendChild(repr);
1932                     SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1933                     Inkscape::GC::release(repr);
1934                     _dialog.set_attr(node, SP_ATTR_IN, in_val);
1935                     (*get_selection()->get_selected())[_columns.primitive] = prim;
1936                 }
1937             }
1938             else {
1939                 if(_in_drag == 1)
1940                     _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1941                 else if(_in_drag == 2)
1942                     _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1943             }
1944         }
1946         _in_drag = 0;
1947         queue_draw();
1949         _dialog.update_settings_view();
1950     }
1952     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1953         const bool sensitive = get_selected() != NULL;
1954         _primitive_menu->items()[0].set_sensitive(sensitive);
1955         _primitive_menu->items()[1].set_sensitive(sensitive);
1956         _primitive_menu->popup(e->button, e->time);
1958         return true;
1959     }
1960     else
1961         return Gtk::TreeView::on_button_release_event(e);
1964 // Checks all of prim's inputs, removes any that use result
1965 void check_single_connection(SPFilterPrimitive* prim, const int result)
1967     if(prim && result >= 0) {
1969         if(prim->image_in == result)
1970             SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1972         if(SP_IS_FEBLEND(prim)) {
1973             if(SP_FEBLEND(prim)->in2 == result)
1974                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1975         }
1976         else if(SP_IS_FECOMPOSITE(prim)) {
1977             if(SP_FECOMPOSITE(prim)->in2 == result)
1978                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1979         }
1980         else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
1981             if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
1982                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1983         }
1984     }
1987 // Remove any connections going to/from prim_iter that forward-reference other primitives
1988 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
1990     SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
1991     bool before = true;
1993     for(Gtk::TreeIter iter = _model->children().begin();
1994         iter != _model->children().end(); ++iter) {
1995         if(iter == prim_iter)
1996             before = false;
1997         else {
1998             SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
1999             if(before)
2000                 check_single_connection(cur_prim, prim->image_out);
2001             else
2002                 check_single_connection(prim, cur_prim->image_out);
2003         }
2004     }
2007 // Reorder the filter primitives to match the list order
2008 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2010     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2011     int ndx = 0;
2013     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2014         iter != _model->children().end(); ++iter, ++ndx) {
2015         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2016         if(prim && prim == _drag_prim) {
2017             SP_OBJECT_REPR(prim)->setPosition(ndx);
2018             break;
2019         }
2020     }
2022     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2023         iter != _model->children().end(); ++iter, ++ndx) {
2024         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2025         if(prim && prim == _drag_prim) {
2026             sanitize_connections(iter);
2027             get_selection()->select(iter);
2028             break;
2029         }
2030     }
2032     filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2034     sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2037 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2038 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2040     if(_autoscroll) {
2041         Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2042         double v;
2044         v = a.get_value() + _autoscroll;
2045         if(v < 0)
2046             v = 0;
2047         if(v > a.get_upper() - a.get_page_size())
2048             v = a.get_upper() - a.get_page_size();
2050         a.set_value(v);
2052         queue_draw();
2053     }
2055     return true;
2058 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2060     return _model->children().size();
2063 /*** FilterEffectsDialog ***/
2065 FilterEffectsDialog::FilterEffectsDialog()
2066     : UI::Widget::Panel("", "/dialogs/filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2067       _add_primitive_type(FPConverter),
2068       _add_primitive(_("Add Effect:")),
2069       _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2070       _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2071       _settings_initialized(false),
2072       _locked(false),
2073       _attr_lock(false),
2074       _filter_modifier(*this),
2075       _primitive_list(*this)
2077     _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2078                              NR_FILTER_ENDPRIMITIVETYPE);
2079     _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2080                              1);
2081     _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2082     _sizegroup->set_ignore_hidden();
2084     _add_primitive_type.remove_row(NR_FILTER_TILE);
2085     _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2087     // Initialize widget hierarchy
2088     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2089     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2090     Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox(/*homogeneous:*/false, /*spacing:*/4));
2091     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2093     _getContents()->add(*hpaned);
2094     hpaned->pack1(_filter_modifier);
2095     hpaned->pack2(_primitive_box);
2096     _primitive_box.pack_start(*sw_prims);
2097     _primitive_box.pack_start(*hb_prims, false, false);
2098     _primitive_box.pack_start(*infobox,false, false);
2099     sw_prims->add(_primitive_list);
2100     infobox->pack_start(_infobox_icon, false, false);
2101     infobox->pack_start(_infobox_desc, false, false);
2102     _infobox_desc.set_line_wrap(true);
2103     _infobox_desc.set_size_request(200, -1);
2105     hb_prims->pack_start(_add_primitive, false, false);
2106     hb_prims->pack_start(_add_primitive_type, false, false);
2107     _getContents()->pack_start(_settings_tabs, false, false);
2108     _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2109     _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2111     _primitive_list.signal_primitive_changed().connect(
2112         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2113     _filter_modifier.signal_filter_changed().connect(
2114         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2116     _add_primitive_type.signal_changed().connect(
2117         sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2119     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2120     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2121 //    al_settings->set_padding(0, 0, 12, 0);
2122 //    fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2123 //    ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2124     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2125     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2126                                                sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2128     show_all_children();
2129     init_settings_widgets();
2130     _primitive_list.update();
2131     update_primitive_infobox();
2134 FilterEffectsDialog::~FilterEffectsDialog()
2136     delete _settings;
2137     delete _filter_general_settings;
2140 void FilterEffectsDialog::set_attrs_locked(const bool l)
2142     _locked = l;
2145 void FilterEffectsDialog::show_all_vfunc()
2147     UI::Widget::Panel::show_all_vfunc();
2149     update_settings_view();
2152 void FilterEffectsDialog::init_settings_widgets()
2154     // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2155     //       most of the current values are complete guesses!
2157     _empty_settings.set_sensitive(false);
2158     _settings_tab1.pack_start(_empty_settings);
2160     _no_filter_selected.set_sensitive(false);
2161     _settings_tab2.pack_start(_no_filter_selected);
2162     _settings_initialized = true;
2164     _filter_general_settings->type(0);
2165     _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"));
2166     _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"));
2168     _settings->type(NR_FILTER_BLEND);
2169     _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode:"), BlendModeConverter);
2171     _settings->type(NR_FILTER_COLORMATRIX);
2172     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."));
2173     _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s):"));
2174     colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2176     _settings->type(NR_FILTER_COMPONENTTRANSFER);
2177     _settings->add_notimplemented();
2178     /*
2179     //TRANSLATORS: for info on "Slope" and "Intercept", see http://id.mind.net/~zona/mmts/functionInstitute/linearFunctions/lsif.html
2180     _settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2181     _ct_slope = _settings->add_spinslider(1, SP_ATTR_SLOPE, _("Slope"), -10, 10, 0.1, 0.01, 2);
2182     _ct_intercept = _settings->add_spinslider(0, SP_ATTR_INTERCEPT, _("Intercept"), -10, 10, 0.1, 0.01, 2);
2183     _ct_amplitude = _settings->add_spinslider(1, SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 10, 0.1, 0.01, 2);
2184     _ct_exponent = _settings->add_spinslider(1, SP_ATTR_EXPONENT, _("Exponent"), 0, 10, 0.1, 0.01, 2);
2185     _ct_offset = _settings->add_spinslider(0, SP_ATTR_OFFSET, _("Offset"), -10, 10, 0.1, 0.01, 2);*/
2187     _settings->type(NR_FILTER_COMPOSITE);
2188     _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator:"), CompositeOperatorConverter);
2189     _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."));
2190     _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."));
2191     _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."));
2192     _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."));
2194     _settings->type(NR_FILTER_CONVOLVEMATRIX);
2195     _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"));
2196     _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."));
2197     //TRANSLATORS: for info on "Kernel", see http://en.wikipedia.org/wiki/Kernel_(matrix)
2198     _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."));
2199     _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2200     _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."));
2201     _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."));
2202     _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."));
2203     _settings->add_checkbutton(false, SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive."));
2205     _settings->type(NR_FILTER_DIFFUSELIGHTING);
2206     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Diffuse Color:"), _("Defines the color of the light source"));
2207     _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale:"), -5, 5, 0.01, 0.001, 3, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2208     _settings->add_spinslider(1, SP_ATTR_DIFFUSECONSTANT, _("Constant:"), 0, 5, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2209     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2210     _settings->add_lightsource();
2212     _settings->type(NR_FILTER_DISPLACEMENTMAP);
2213     _settings->add_spinslider(0, SP_ATTR_SCALE, _("Scale:"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2214     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2215     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2217     _settings->type(NR_FILTER_FLOOD);
2218     _settings->add_color(/*default: black*/ 0, SP_PROP_FLOOD_COLOR, _("Flood Color:"), _("The whole filter region will be filled with this color."));
2219     _settings->add_spinslider(1, SP_PROP_FLOOD_OPACITY, _("Opacity:"), 0, 1, 0.1, 0.01, 2);
2221     _settings->type(NR_FILTER_GAUSSIANBLUR);
2222     _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation:"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2224     _settings->type(NR_FILTER_MERGE);
2225     _settings->add_no_params();
2227     _settings->type(NR_FILTER_MORPHOLOGY);
2228     _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator:"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2229     _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius:"), 0, 100, 1, 0.01, 1);
2231     _settings->type(NR_FILTER_IMAGE);
2232     _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image:"));
2234     _settings->type(NR_FILTER_OFFSET);
2235     _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"));
2236     _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"));
2238     _settings->type(NR_FILTER_SPECULARLIGHTING);
2239     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Specular Color:"), _("Defines the color of the light source"));
2240     _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale:"), -5, 5, 0.1, 0.01, 2, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2241     _settings->add_spinslider(1, SP_ATTR_SPECULARCONSTANT, _("Constant:"), 0, 5, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2242     _settings->add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Exponent:"), 1, 50, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2243     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2244     _settings->add_lightsource();
2246     _settings->type(NR_FILTER_TILE);
2247     _settings->add_notimplemented();
2249     _settings->type(NR_FILTER_TURBULENCE);
2250 //    _settings->add_checkbutton(false, SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2251     _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type:"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2252     _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency:"), 0, 0.4, 0.001, 0.01, 3);
2253     _settings->add_spinslider(1, SP_ATTR_NUMOCTAVES, _("Octaves:"), 1, 10, 1, 1, 0);
2254     _settings->add_spinslider(0, SP_ATTR_SEED, _("Seed:"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2257 void FilterEffectsDialog::add_primitive()
2259     SPFilter* filter = _filter_modifier.get_selected_filter();
2261     if(filter) {
2262         SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2264         _primitive_list.select(prim);
2266         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2267     }
2270 void FilterEffectsDialog::update_primitive_infobox()
2272     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2273     if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2274         _infobox_icon.show();
2275         _infobox_desc.show();
2276     } else {
2277         _infobox_icon.hide();
2278         _infobox_desc.hide();
2279     }
2280     switch(_add_primitive_type.get_active_data()->id){
2281         case(NR_FILTER_BLEND):
2282             _infobox_icon.set_from_icon_name("feBlend-icon", Gtk::ICON_SIZE_DIALOG);
2283             _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2284             break;
2285         case(NR_FILTER_COLORMATRIX):
2286             _infobox_icon.set_from_icon_name("feColorMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2287             _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."));
2288             break;
2289         case(NR_FILTER_COMPONENTTRANSFER):
2290             _infobox_icon.set_from_icon_name("feComponentTransfer-icon", Gtk::ICON_SIZE_DIALOG);
2291             _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."));
2292             break;
2293         case(NR_FILTER_COMPOSITE):
2294             _infobox_icon.set_from_icon_name("feComposite-icon", Gtk::ICON_SIZE_DIALOG);
2295             _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."));
2296             break;
2297         case(NR_FILTER_CONVOLVEMATRIX):
2298             _infobox_icon.set_from_icon_name("feConvolveMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2299             _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."));
2300             break;
2301         case(NR_FILTER_DIFFUSELIGHTING):
2302             _infobox_icon.set_from_icon_name("feDiffuseLighting-icon", Gtk::ICON_SIZE_DIALOG);
2303             _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."));
2304             break;
2305         case(NR_FILTER_DISPLACEMENTMAP):
2306             _infobox_icon.set_from_icon_name("feDisplacementMap-icon", Gtk::ICON_SIZE_DIALOG);
2307             _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."));
2308             break;
2309         case(NR_FILTER_FLOOD):
2310             _infobox_icon.set_from_icon_name("feFlood-icon", Gtk::ICON_SIZE_DIALOG);
2311             _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."));
2312             break;
2313         case(NR_FILTER_GAUSSIANBLUR):
2314             _infobox_icon.set_from_icon_name("feGaussianBlur-icon", Gtk::ICON_SIZE_DIALOG);
2315             _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."));
2316             break;
2317         case(NR_FILTER_IMAGE):
2318             _infobox_icon.set_from_icon_name("feImage-icon", Gtk::ICON_SIZE_DIALOG);
2319             _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2320             break;
2321         case(NR_FILTER_MERGE):
2322             _infobox_icon.set_from_icon_name("feMerge-icon", Gtk::ICON_SIZE_DIALOG);
2323             _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."));
2324             break;
2325         case(NR_FILTER_MORPHOLOGY):
2326             _infobox_icon.set_from_icon_name("feMorphology-icon", Gtk::ICON_SIZE_DIALOG);
2327             _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."));
2328             break;
2329         case(NR_FILTER_OFFSET):
2330             _infobox_icon.set_from_icon_name("feOffset-icon", Gtk::ICON_SIZE_DIALOG);
2331             _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."));
2332             break;
2333         case(NR_FILTER_SPECULARLIGHTING):
2334             _infobox_icon.set_from_icon_name("feSpecularLighting-icon", Gtk::ICON_SIZE_DIALOG);
2335             _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."));
2336             break;
2337         case(NR_FILTER_TILE):
2338             _infobox_icon.set_from_icon_name("feTile-icon", Gtk::ICON_SIZE_DIALOG);
2339             _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2340             break;
2341         case(NR_FILTER_TURBULENCE):
2342             _infobox_icon.set_from_icon_name("feTurbulence-icon", Gtk::ICON_SIZE_DIALOG);
2343             _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."));
2344             break;
2345         default:
2346             g_assert(false);
2347             break;
2348     }
2349     _infobox_icon.set_pixel_size(96);
2352 void FilterEffectsDialog::duplicate_primitive()
2354     SPFilter* filter = _filter_modifier.get_selected_filter();
2355     SPFilterPrimitive* origprim = _primitive_list.get_selected();
2357     if(filter && origprim) {
2358         Inkscape::XML::Node *repr;
2359         repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2360         SP_OBJECT_REPR(filter)->appendChild(repr);
2362         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2364         _primitive_list.update();
2365     }
2368 void FilterEffectsDialog::convolve_order_changed()
2370     _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2371     _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2372     _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2375 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2377     set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2380 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2382     if(!_locked) {
2383         _attr_lock = true;
2384         SPFilter *filter = _filter_modifier.get_selected_filter();
2385         const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2386         if (filter && name && SP_OBJECT_REPR(filter)){
2387             SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2388             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2389         }
2390         _attr_lock = false;
2391     }
2394 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2396     set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2399 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2401     if(!_locked) {
2402         _attr_lock = true;
2404         SPFilter *filter = _filter_modifier.get_selected_filter();
2405         const gchar* name = (const gchar*)sp_attribute_name(attr);
2406         if(filter && name && o) {
2407             update_settings_sensitivity();
2409             SP_OBJECT_REPR(o)->setAttribute(name, val);
2410             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2412             Glib::ustring undokey = "filtereffects:";
2413             undokey += name;
2414             sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2415                                    _("Set filter primitive attribute"));
2416         }
2418         _attr_lock = false;
2419     }
2422 void FilterEffectsDialog::update_filter_general_settings_view()
2424     if(_settings_initialized != true) return;
2426     if(!_locked) {
2427         _attr_lock = true;
2429         SPFilter* filter = _filter_modifier.get_selected_filter();
2431         if(filter) {
2432             _filter_general_settings->show_and_update(0, filter);
2433             _no_filter_selected.hide();
2434         }
2435         else {
2436             std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2437             vect[0]->hide_all();
2438             _no_filter_selected.show();
2439         }
2441         _attr_lock = false;
2442     }
2445 void FilterEffectsDialog::update_settings_view()
2447     update_settings_sensitivity();
2449     if(_attr_lock)
2450         return;
2452 //First Tab
2454     std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2455     for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2456     _empty_settings.show();
2458     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2459     if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2460         _infobox_icon.show();
2461         _infobox_desc.show();
2462     } else {
2463         _infobox_icon.hide();
2464         _infobox_desc.hide();
2465     }
2467     SPFilterPrimitive* prim = _primitive_list.get_selected();
2469     if(prim) {
2470         _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2471         _empty_settings.hide();
2472     }
2474 //Second Tab
2476     std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2477     vect2[0]->hide_all();
2478     _no_filter_selected.show();
2480     SPFilter* filter = _filter_modifier.get_selected_filter();
2482     if(filter) {
2483         _filter_general_settings->show_and_update(0, filter);
2484         _no_filter_selected.hide();
2485     }
2489 void FilterEffectsDialog::update_settings_sensitivity()
2491     SPFilterPrimitive* prim = _primitive_list.get_selected();
2492     const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2493     _k1->set_sensitive(use_k);
2494     _k2->set_sensitive(use_k);
2495     _k3->set_sensitive(use_k);
2496     _k4->set_sensitive(use_k);
2498 // Component transfer not yet implemented
2499 /*
2500     if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2501         SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2502         const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2503         const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2505         _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2506         _ct_slope->set_sensitive(linear);
2507         _ct_intercept->set_sensitive(linear);
2508         _ct_amplitude->set_sensitive(gamma);
2509         _ct_exponent->set_sensitive(gamma);
2510         _ct_offset->set_sensitive(gamma);
2511     }
2512 */
2515 void FilterEffectsDialog::update_color_matrix()
2517     _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2520 } // namespace Dialog
2521 } // namespace UI
2522 } // namespace Inkscape
2524 /*
2525   Local Variables:
2526   mode:c++
2527   c-file-style:"stroustrup"
2528   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2529   indent-tabs-mode:nil
2530   fill-column:99
2531   End:
2532 */
2533 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :