Code

Merge from fe-moved
[inkscape.git] / src / ui / dialog / filter-effects-dialog.cpp
1 /** @file
2  * @brief Filter Effects dialog
3  */
4 /* Authors:
5  *   Nicholas Bishop <nicholasbishop@gmail.org>
6  *   Rodrigo Kumpera <kumpera@gmail.com>
7  *   Felipe C. da S. Sanches <felipe.sanches@gmail.com>
8  *
9  * Copyright (C) 2007 Authors
10  *
11  * Released under GNU GPL.  Read the file 'COPYING' for more information.
12  */
14 #ifdef HAVE_CONFIG_H
15 # include <config.h>
16 #endif
18 #include <gtk/gtktreeview.h>
19 #include <gtkmm/cellrenderertext.h>
20 #include <gtkmm/colorbutton.h>
21 #include <gtkmm/messagedialog.h>
22 #include <gtkmm/paned.h>
23 #include <gtkmm/scale.h>
24 #include <gtkmm/scrolledwindow.h>
25 #include <gtkmm/spinbutton.h>
26 #include <gtkmm/stock.h>
27 #include <gtkmm/tooltips.h>
28 #include <glibmm/i18n.h>
30 #include "application/application.h"
31 #include "application/editor.h"
32 #include "desktop.h"
33 #include "desktop-handles.h"
34 #include "dialog-manager.h"
35 #include "dir-util.h"
36 #include "document.h"
37 #include "filter-chemistry.h"
38 #include "filter-effects-dialog.h"
39 #include "filter-enums.h"
40 #include "inkscape.h"
41 #include "path-prefix.h"
42 #include "preferences.h"
43 #include "selection.h"
44 #include "filters/blend.h"
45 #include "filters/colormatrix.h"
46 #include "filters/componenttransfer.h"
47 #include "filters/composite.h"
48 #include "filters/convolvematrix.h"
49 #include "filters/displacementmap.h"
50 #include "filters/distantlight.h"
51 #include "filters/merge.h"
52 #include "filters/mergenode.h"
53 #include "filters/offset.h"
54 #include "filters/pointlight.h"
55 #include "filters/spotlight.h"
56 #include "sp-filter-primitive.h"
57 #include "sp-gaussian-blur.h"
59 #include "style.h"
60 #include "svg/svg-color.h"
61 #include "ui/dialog/filedialog.h"
62 #include "verbs.h"
63 #include "xml/node.h"
64 #include "xml/node-observer.h"
65 #include "xml/repr.h"
66 #include <sstream>
68 #include "io/sys.h"
69 #include <iostream>
71 using namespace NR;
73 namespace Inkscape {
74 namespace UI {
75 namespace Dialog {
77 // Returns the number of inputs available for the filter primitive type
78 int input_count(const SPFilterPrimitive* prim)
79 {
80     if(!prim)
81         return 0;
82     else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
83         return 2;
84     else if(SP_IS_FEMERGE(prim)) {
85         // Return the number of feMergeNode connections plus an extra
86         int count = 1;
87         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count){};
88         return count;
89     }
90     else
91         return 1;
92 }
94 // Very simple observer that just emits a signal if anything happens to a node
95 class FilterEffectsDialog::SignalObserver : public XML::NodeObserver
96 {
97 public:
98     SignalObserver()
99         : _oldsel(0)
100     {}
102     // Add this observer to the SPObject and remove it from any previous object
103     void set(SPObject* o)
104     {
105         if(_oldsel && _oldsel->repr)
106             _oldsel->repr->removeObserver(*this);
107         if(o && o->repr)
108             o->repr->addObserver(*this);
109         _oldsel = o;
110     }
112     void notifyChildAdded(XML::Node&, XML::Node&, XML::Node*)
113     { signal_changed()(); }
115     void notifyChildRemoved(XML::Node&, XML::Node&, XML::Node*)
116     { signal_changed()(); }
118     void notifyChildOrderChanged(XML::Node&, XML::Node&, XML::Node*, XML::Node*)
119     { signal_changed()(); }
121     void notifyContentChanged(XML::Node&, Util::ptr_shared<char>, Util::ptr_shared<char>)
122     {}
124     void notifyAttributeChanged(XML::Node&, GQuark, Util::ptr_shared<char>, Util::ptr_shared<char>)
125     { signal_changed()(); }
127     sigc::signal<void>& signal_changed()
128     {
129         return _signal_changed;
130     }
131 private:
132     sigc::signal<void> _signal_changed;
133     SPObject* _oldsel;
134 };
136 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
138 public:
139     CheckButtonAttr(bool def, const Glib::ustring& label,
140                     const Glib::ustring& tv, const Glib::ustring& fv,
141                     const SPAttributeEnum a, char* tip_text)
142         : Gtk::CheckButton(label),
143           AttrWidget(a, def),
144           _true_val(tv), _false_val(fv)
145     {
146         signal_toggled().connect(signal_attr_changed().make_slot());
147         if (tip_text) _tt.set_tip(*this, tip_text);
148     }
150     Glib::ustring get_as_attribute() const
151     {
152         return get_active() ? _true_val : _false_val;
153     }
155     void set_from_attribute(SPObject* o)
156     {
157         const gchar* val = attribute_value(o);
158         if(val) {
159             if(_true_val == val)
160                 set_active(true);
161             else if(_false_val == val)
162                 set_active(false);
163         } else {
164             set_active(get_default()->as_bool());
165         }
166     }
167 private:
168     const Glib::ustring _true_val, _false_val;
169 };
171 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
173 public:
174     SpinButtonAttr(double lower, double upper, double step_inc,
175                    double climb_rate, int digits, const SPAttributeEnum a, double def, char* tip_text)
176         : Gtk::SpinButton(climb_rate, digits),
177           AttrWidget(a, def)
178     {
179         if (tip_text) _tt.set_tip(*this, tip_text);
180         set_range(lower, upper);
181         set_increments(step_inc, step_inc * 5);
183         signal_value_changed().connect(signal_attr_changed().make_slot());
184     }
186     Glib::ustring get_as_attribute() const
187     {
188         const double val = get_value();
190         if(get_digits() == 0)
191             return Glib::Ascii::dtostr((int)val);
192         else
193             return Glib::Ascii::dtostr(val);
194     }
196     void set_from_attribute(SPObject* o)
197     {
198         const gchar* val = attribute_value(o);
199         if(val){
200             set_value(Glib::Ascii::strtod(val));
201         } else {
202             set_value(get_default()->as_double());
203         }
204     }
205 };
207 template< typename T> class ComboWithTooltip : public Gtk::EventBox
209 public:
210     ComboWithTooltip<T>(T default_value, const Util::EnumDataConverter<T>& c, const SPAttributeEnum a = SP_ATTR_INVALID, char* tip_text = NULL)
211     {
212         if (tip_text) {
213             _tt.set_tip(*this, tip_text);
214         }
215         combo = new ComboBoxEnum<T>(default_value, c, a);
216         add(*combo);
217         show_all();
218     }
220     ~ComboWithTooltip()
221     {
222         delete combo;
223     }
225     ComboBoxEnum<T>* get_attrwidget()
226     {
227         return combo;
228     }
229 private:
230     Gtk::Tooltips _tt;
231     ComboBoxEnum<T>* combo;
232 };
234 // Contains an arbitrary number of spin buttons that use seperate attributes
235 class MultiSpinButton : public Gtk::HBox
237 public:
238     MultiSpinButton(double lower, double upper, double step_inc,
239                     double climb_rate, int digits, std::vector<SPAttributeEnum> attrs, std::vector<double> default_values, std::vector<char*> tip_text)
240     {
241         g_assert(attrs.size()==default_values.size());
242         g_assert(attrs.size()==tip_text.size());
243         for(unsigned i = 0; i < attrs.size(); ++i) {
244             _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i], default_values[i], tip_text[i]));
245             pack_start(*_spins.back(), false, false);
246         }
247     }
249     ~MultiSpinButton()
250     {
251         for(unsigned i = 0; i < _spins.size(); ++i)
252             delete _spins[i];
253     }
255     std::vector<SpinButtonAttr*>& get_spinbuttons()
256     {
257         return _spins;
258     }
259 private:
260     std::vector<SpinButtonAttr*> _spins;
261 };
263 // Contains two spinbuttons that describe a NumberOptNumber
264 class DualSpinButton : public Gtk::HBox, public AttrWidget
266 public:
267     DualSpinButton(char* def, double lower, double upper, double step_inc,
268                    double climb_rate, int digits, const SPAttributeEnum a, char* tt1, char* tt2)
269         : AttrWidget(a, def), //TO-DO: receive default num-opt-num as parameter in the constructor
270           _s1(climb_rate, digits), _s2(climb_rate, digits)
271     {
272         if (tt1) _tt.set_tip(_s1, tt1);
273         if (tt2) _tt.set_tip(_s2, tt2);
274         _s1.set_range(lower, upper);
275         _s2.set_range(lower, upper);
276         _s1.set_increments(step_inc, step_inc * 5);
277         _s2.set_increments(step_inc, step_inc * 5);
279         _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
280         _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
282         pack_start(_s1, false, false);
283         pack_start(_s2, false, false);
284     }
286     Gtk::SpinButton& get_spinbutton1()
287     {
288         return _s1;
289     }
291     Gtk::SpinButton& get_spinbutton2()
292     {
293         return _s2;
294     }
296     virtual Glib::ustring get_as_attribute() const
297     {
298         double v1 = _s1.get_value();
299         double v2 = _s2.get_value();
301         if(_s1.get_digits() == 0) {
302             v1 = (int)v1;
303             v2 = (int)v2;
304         }
306         return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
307     }
309     virtual void set_from_attribute(SPObject* o)
310     {
311         const gchar* val = attribute_value(o);
312         NumberOptNumber n;
313         if(val) {
314             n.set(val);
315         } else {
316             n.set(get_default()->as_charptr());
317         }
318         _s1.set_value(n.getNumber());
319         _s2.set_value(n.getOptNumber());
321     }
322 private:
323     Gtk::SpinButton _s1, _s2;
324 };
326 class ColorButton : public Gtk::ColorButton, public AttrWidget
328 public:
329     ColorButton(unsigned int def, const SPAttributeEnum a, char* tip_text)
330         : AttrWidget(a, def)
331     {
332         signal_color_set().connect(signal_attr_changed().make_slot());
333         if (tip_text) _tt.set_tip(*this, tip_text);
335         Gdk::Color col;
336         col.set_rgb(65535, 65535, 65535);
337         set_color(col);
338     }
340     // Returns the color in 'rgb(r,g,b)' form.
341     Glib::ustring get_as_attribute() const
342     {
343         std::ostringstream os;
344         const Gdk::Color c = get_color();
345         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?
346         os << "rgb(" << r << "," << g << "," << b << ")";
347         return os.str();
348     }
351     void set_from_attribute(SPObject* o)
352     {
353         const gchar* val = attribute_value(o);
354         guint32 i = 0;
355         if(val) {
356             i = sp_svg_read_color(val, 0xFFFFFFFF);
357         } else {
358             i = (guint32) get_default()->as_uint();
359         }
360         const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
361         Gdk::Color col;
362         col.set_rgb(r * 256, g * 256, b * 256);
363         set_color(col);
364     }
365 };
367 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
368 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
370 public:
371     MatrixAttr(const SPAttributeEnum a, char* tip_text = NULL)
372         : AttrWidget(a), _locked(false)
373     {
374         _model = Gtk::ListStore::create(_columns);
375         _tree.set_model(_model);
376         _tree.set_headers_visible(false);
377         _tree.show();
378         add(_tree);
379         set_shadow_type(Gtk::SHADOW_IN);
380         if (tip_text) _tt.set_tip(_tree, tip_text);
381     }
383     std::vector<double> get_values() const
384     {
385         std::vector<double> vec;
386         for(Gtk::TreeIter iter = _model->children().begin();
387             iter != _model->children().end(); ++iter) {
388             for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
389                 vec.push_back((*iter)[_columns.cols[c]]);
390         }
391         return vec;
392     }
394     void set_values(const std::vector<double>& v)
395     {
396         unsigned i = 0;
397         for(Gtk::TreeIter iter = _model->children().begin();
398             iter != _model->children().end(); ++iter) {
399             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
400                 if(i >= v.size())
401                     return;
402                 (*iter)[_columns.cols[c]] = v[i];
403                 ++i;
404             }
405         }
406     }
408     Glib::ustring get_as_attribute() const
409     {
410         std::ostringstream os;
412         for(Gtk::TreeIter iter = _model->children().begin();
413             iter != _model->children().end(); ++iter) {
414             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
415                 os << (*iter)[_columns.cols[c]] << " ";
416             }
417         }
419         return os.str();
420     }
422     void set_from_attribute(SPObject* o)
423     {
424         if(o) {
425             if(SP_IS_FECONVOLVEMATRIX(o)) {
426                 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
427                 int cols, rows;
428                 cols = (int)conv->order.getNumber();
429                 if(cols > 5)
430                     cols = 5;
431                 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
432                 update(o, rows, cols);
433             }
434             else if(SP_IS_FECOLORMATRIX(o))
435                 update(o, 4, 5);
436         }
437     }
438 private:
439     class MatrixColumns : public Gtk::TreeModel::ColumnRecord
440     {
441     public:
442         MatrixColumns()
443         {
444             cols.resize(5);
445             for(unsigned i = 0; i < cols.size(); ++i)
446                 add(cols[i]);
447         }
448         std::vector<Gtk::TreeModelColumn<double> > cols;
449     };
451     void update(SPObject* o, const int rows, const int cols)
452     {
453         if(_locked)
454             return;
456         _model->clear();
458         _tree.remove_all_columns();
460         std::vector<gdouble>* values = NULL;
461         if(SP_IS_FECOLORMATRIX(o))
462             values = &SP_FECOLORMATRIX(o)->values;
463         else if(SP_IS_FECONVOLVEMATRIX(o))
464             values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
465         else
466             return;
468         if(o) {
469             int ndx = 0;
471             for(int i = 0; i < cols; ++i) {
472                 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
473                 dynamic_cast<Gtk::CellRendererText*>(
474                     _tree.get_column_cell_renderer(i))->signal_edited().connect(
475                         sigc::mem_fun(*this, &MatrixAttr::rebind));
476             }
478             for(int r = 0; r < rows; ++r) {
479                 Gtk::TreeRow row = *(_model->append());
480                 // Default to identity matrix
481                 for(int c = 0; c < cols; ++c, ++ndx)
482                     row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
483             }
484         }
485     }
487     void rebind(const Glib::ustring&, const Glib::ustring&)
488     {
489         _locked = true;
490         signal_attr_changed()();
491         _locked = false;
492     }
494     bool _locked;
495     Gtk::TreeView _tree;
496     Glib::RefPtr<Gtk::ListStore> _model;
497     MatrixColumns _columns;
498 };
500 // Displays a matrix or a slider for feColorMatrix
501 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
503 public:
504     ColorMatrixValues()
505         : AttrWidget(SP_ATTR_VALUES),
506           _matrix(SP_ATTR_VALUES, _("This matrix determines a linear transform on colour space. Each line affects one of the color components. Each column determines how much of each color component from the input is passed to the output. The last column does not depend on input colors, so can be used to adjust a constant component value.")),
507           _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
508           _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
509           _label(_("None"), Gtk::ALIGN_LEFT),
510           _use_stored(false),
511           _saturation_store(0),
512           _angle_store(0)
513     {
514         _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
515         _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
516         _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
517         signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
519         _matrix.show();
520         _saturation.show();
521         _angle.show();
522         _label.show();
523         _label.set_sensitive(false);
525         set_shadow_type(Gtk::SHADOW_NONE);
526     }
528     virtual void set_from_attribute(SPObject* o)
529     {
530         if(SP_IS_FECOLORMATRIX(o)) {
531             SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
532             remove();
533             switch(col->type) {
534                 case COLORMATRIX_SATURATE:
535                     add(_saturation);
536                     if(_use_stored)
537                         _saturation.set_value(_saturation_store);
538                     else
539                         _saturation.set_from_attribute(o);
540                     break;
541                 case COLORMATRIX_HUEROTATE:
542                     add(_angle);
543                     if(_use_stored)
544                         _angle.set_value(_angle_store);
545                     else
546                         _angle.set_from_attribute(o);
547                     break;
548                 case COLORMATRIX_LUMINANCETOALPHA:
549                     add(_label);
550                     break;
551                 case COLORMATRIX_MATRIX:
552                 default:
553                     add(_matrix);
554                     if(_use_stored)
555                         _matrix.set_values(_matrix_store);
556                     else
557                         _matrix.set_from_attribute(o);
558                     break;
559             }
560             _use_stored = true;
561         }
562     }
564     virtual Glib::ustring get_as_attribute() const
565     {
566         const Widget* w = get_child();
567         if(w == &_label)
568             return "";
569         else
570             return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
571     }
573     void clear_store()
574     {
575         _use_stored = false;
576     }
577 private:
578     void update_store()
579     {
580         const Widget* w = get_child();
581         if(w == &_matrix)
582             _matrix_store = _matrix.get_values();
583         else if(w == &_saturation)
584             _saturation_store = _saturation.get_value();
585         else if(w == &_angle)
586             _angle_store = _angle.get_value();
587     }
589     MatrixAttr _matrix;
590     SpinSlider _saturation;
591     SpinSlider _angle;
592     Gtk::Label _label;
594     // Store separate values for the different color modes
595     bool _use_stored;
596     std::vector<double> _matrix_store;
597     double _saturation_store;
598     double _angle_store;
599 };
601 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
603 //Displays a chooser for feImage input
604 //It may be a filename or the id for an SVG Element
605 //described in xlink:href syntax
606 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
608 public:
609     FileOrElementChooser(const SPAttributeEnum a)
610         : AttrWidget(a)
611     {
612         pack_start(_entry, false, false);
613         pack_start(_fromFile, false, false);
614         pack_start(_fromSVGElement, false, false);
616         _fromFile.set_label(_("Image File"));
617         _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
619         _fromSVGElement.set_label(_("Selected SVG Element"));
620         _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
622         _entry.signal_changed().connect(signal_attr_changed().make_slot());
624         show_all();
626     }
628     // Returns the element in xlink:href form.
629     Glib::ustring get_as_attribute() const
630     {
631         return _entry.get_text();
632     }
635     void set_from_attribute(SPObject* o)
636     {
637         const gchar* val = attribute_value(o);
638         if(val) {
639             _entry.set_text(val);
640         } else {
641             _entry.set_text("");
642         }
643     }
645     void set_desktop(SPDesktop* d){
646         _desktop = d;
647     }
649 private:
650     void select_svg_element(){
651         Inkscape::Selection* sel = sp_desktop_selection(_desktop);
652         if (sel->isEmpty()) return;
653         Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
654         if (!node || !node->matchAttributeName("id")) return;
656         std::ostringstream xlikhref;
657         xlikhref << "#" << node->attribute("id");
658         _entry.set_text(xlikhref.str());
659     }
661     void select_file(){
663         //# Get the current directory for finding files
664         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
665         Glib::ustring open_path;
666         Glib::ustring attr = prefs->getString("/dialogs/open/path");
667         if (!attr.empty())
668             open_path = attr;
670         //# Test if the open_path directory exists
671         if (!Inkscape::IO::file_test(open_path.c_str(),
672                   (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
673             open_path = "";
675         //# If no open path, default to our home directory
676         if (open_path.size() < 1)
677             {
678             open_path = g_get_home_dir();
679             open_path.append(G_DIR_SEPARATOR_S);
680             }
682         //# Create a dialog if we don't already have one
683         if (!selectFeImageFileInstance) {
684             selectFeImageFileInstance =
685                   Inkscape::UI::Dialog::FileOpenDialog::create(
686                      *_desktop->getToplevel(),
687                      open_path,
688                      Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not justy svg*/
689                      (char const *)_("Select an image to be used as feImage input"));
690         }
692         //# Show the dialog
693         bool const success = selectFeImageFileInstance->show();
694         if (!success)
695             return;
697         //# User selected something.  Get name and type
698         Glib::ustring fileName = selectFeImageFileInstance->getFilename();
700         if (fileName.size() > 0) {
702             Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
704             if ( newFileName.size() > 0)
705                 fileName = newFileName;
706             else
707                 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
709             open_path = fileName;
710             open_path.append(G_DIR_SEPARATOR_S);
711             prefs->setString("/dialogs/open/path", open_path);
713             _entry.set_text(fileName);
714         }
715         return;
716     }
718     Gtk::Entry _entry;
719     Gtk::Button _fromFile;
720     Gtk::Button _fromSVGElement;
721     SPDesktop* _desktop;
722 };
724 class FilterEffectsDialog::Settings
726 public:
727     typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
729     Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
730         : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
731     {
732         _groups.resize(_max_types);
733         _attrwidgets.resize(_max_types);
734         _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
736         for(int i = 0; i < _max_types; ++i) {
737             _groups[i] = new Gtk::VBox;
738             b.pack_start(*_groups[i], false, false);
739         }
740         _current_type = 0;
741     }
743     ~Settings()
744     {
745         for(int i = 0; i < _max_types; ++i) {
746             delete _groups[i];
747             for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
748                 delete _attrwidgets[i][j];
749         }
750     }
752     // Show the active settings group and update all the AttrWidgets with new values
753     void show_and_update(const int t, SPObject* ob)
754     {
755         if(t != _current_type) {
756             type(t);
757             for(unsigned i = 0; i < _groups.size(); ++i)
758                 _groups[i]->hide();
759         }
760         if(t >= 0)
761             _groups[t]->show_all();
763         _dialog.set_attrs_locked(true);
764         for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
765             _attrwidgets[_current_type][i]->set_from_attribute(ob);
766         _dialog.set_attrs_locked(false);
767     }
769     int get_current_type() const
770     {
771         return _current_type;
772     }
774     void type(const int t)
775     {
776         _current_type = t;
777     }
779     void add_no_params()
780     {
781         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
782         add_widget(lbl, "");
783     }
785     void add_notimplemented()
786     {
787         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
788         add_widget(lbl, "");
789     }
791     // LightSource
792     LightSourceControl* add_lightsource();
794     // CheckBox
795     CheckButtonAttr* add_checkbutton(bool def, const SPAttributeEnum attr, const Glib::ustring& label,
796                                      const Glib::ustring& tv, const Glib::ustring& fv, char* tip_text = NULL)
797     {
798         CheckButtonAttr* cb = new CheckButtonAttr(def, label, tv, fv, attr, tip_text);
799         add_widget(cb, "");
800         add_attr_widget(cb);
801         return cb;
802     }
804     // ColorButton
805     ColorButton* add_color(unsigned int def, const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text = NULL)
806     {
807         ColorButton* col = new ColorButton(def, attr, tip_text);
808         add_widget(col, label);
809         add_attr_widget(col);
810         return col;
811     }
813     // Matrix
814     MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text)
815     {
816         MatrixAttr* conv = new MatrixAttr(attr, tip_text);
817         add_widget(conv, label);
818         add_attr_widget(conv);
819         return conv;
820     }
822     // ColorMatrixValues
823     ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
824     {
825         ColorMatrixValues* cmv = new ColorMatrixValues();
826         add_widget(cmv, label);
827         add_attr_widget(cmv);
828         return cmv;
829     }
831     // SpinSlider
832     SpinSlider* add_spinslider(double def, const SPAttributeEnum attr, const Glib::ustring& label,
833                          const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
834     {
835         SpinSlider* spinslider = new SpinSlider(def, lo, hi, step_inc, climb, digits, attr, tip_text);
836         add_widget(spinslider, label);
837         add_attr_widget(spinslider);
838         return spinslider;
839     }
841     // DualSpinSlider
842     DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
843                                        const double lo, const double hi, const double step_inc,
844                                        const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
845     {
846         DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
847         add_widget(dss, label);
848         add_attr_widget(dss);
849         return dss;
850     }
852     // DualSpinButton
853     DualSpinButton* add_dualspinbutton(char* defalt_value, const SPAttributeEnum attr, const Glib::ustring& label,
854                                        const double lo, const double hi, const double step_inc,
855                                        const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
856     {
857         DualSpinButton* dsb = new DualSpinButton(defalt_value, lo, hi, step_inc, climb, digits, attr, tip1, tip2);
858         add_widget(dsb, label);
859         add_attr_widget(dsb);
860         return dsb;
861     }
863     // MultiSpinButton
864     MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
865                                          const Glib::ustring& label, const double lo, const double hi,
866                                          const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
867     {
868         std::vector<SPAttributeEnum> attrs;
869         attrs.push_back(attr1);
870         attrs.push_back(attr2);
872         std::vector<double> default_values;
873         default_values.push_back(def1);
874         default_values.push_back(def2);
876         std::vector<char*> tips;
877         tips.push_back(tip1);
878         tips.push_back(tip2);
880         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
881         add_widget(msb, label);
882         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
883             add_attr_widget(msb->get_spinbuttons()[i]);
884         return msb;
885     }
886     MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
887                                          const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
888                                          const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
889     {
890         std::vector<SPAttributeEnum> attrs;
891         attrs.push_back(attr1);
892         attrs.push_back(attr2);
893         attrs.push_back(attr3);
895         std::vector<double> default_values;
896         default_values.push_back(def1);
897         default_values.push_back(def2);
898         default_values.push_back(def3);
900         std::vector<char*> tips;
901         tips.push_back(tip1);
902         tips.push_back(tip2);
903         tips.push_back(tip3);
905         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
906         add_widget(msb, label);
907         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
908             add_attr_widget(msb->get_spinbuttons()[i]);
909         return msb;
910     }
912     // FileOrElementChooser
913     FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
914     {
915         FileOrElementChooser* foech = new FileOrElementChooser(attr);
916         foech->set_desktop(_dialog.getDesktop());
917         add_widget(foech, label);
918         add_attr_widget(foech);
919         return foech;
920     }
922     // ComboBoxEnum
923     template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
924                                   const Glib::ustring& label,
925                                   const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
926     {
927         ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
928         add_widget(combo, label);
929         add_attr_widget(combo->get_attrwidget());
930         return combo->get_attrwidget();
931     }
932 private:
933     Gtk::Tooltips _tt;
935     void add_attr_widget(AttrWidget* a)
936     {
937         _attrwidgets[_current_type].push_back(a);
938         a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
939     }
941     /* Adds a new settings widget using the specified label. The label will be formatted with a colon
942        and all widgets within the setting group are aligned automatically. */
943     void add_widget(Gtk::Widget* w, const Glib::ustring& label)
944     {
945         Gtk::Label *lbl = 0;
946         Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
947         hb->set_spacing(12);
949         if(label != "") {
950             lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
951             hb->pack_start(*lbl, false, false);
952             _size_group->add_widget(*lbl);
953             lbl->show();
954         }
956         hb->pack_start(*w);
957         _groups[_current_type]->pack_start(*hb);
958         hb->show();
959         w->show();
960     }
962     std::vector<Gtk::VBox*> _groups;
963     Glib::RefPtr<Gtk::SizeGroup> _size_group;
964     FilterEffectsDialog& _dialog;
965     SetAttrSlot _set_attr_slot;
966     std::vector<std::vector< AttrWidget*> > _attrwidgets;
967     int _current_type, _max_types;
968 };
970 // Settings for the three light source objects
971 class FilterEffectsDialog::LightSourceControl : public AttrWidget
973 public:
974     LightSourceControl(FilterEffectsDialog& d)
975         : AttrWidget(SP_ATTR_INVALID),
976           _dialog(d),
977           _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
978           _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
979           _light_source(LightSourceConverter),
980           _locked(false)
981     {
982         _light_box.pack_start(_light_label, false, false);
983         _light_box.pack_start(_light_source);
984         _light_box.show_all();
985         _light_box.set_spacing(12);
986         _dialog._sizegroup->add_widget(_light_label);
988         _box.add(_light_box);
989         _box.reorder_child(_light_box, 0);
990         _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
992         // FIXME: these range values are complete crap
994         _settings.type(LIGHT_DISTANT);
995         _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"));
996         _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"));
998         _settings.type(LIGHT_POINT);
999         _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"));
1001         _settings.type(LIGHT_SPOT);
1002         _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"));
1003         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
1004                                       SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
1005                                       _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
1006         _settings.add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
1007         //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.
1008         _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."));
1009     }
1011     Gtk::VBox& get_box()
1012     {
1013         return _box;
1014     }
1015 protected:
1016     Glib::ustring get_as_attribute() const
1017     {
1018         return "";
1019     }
1020     void set_from_attribute(SPObject* o)
1021     {
1022         if(_locked)
1023             return;
1025         _locked = true;
1027         SPObject* child = o->children;
1029         if(SP_IS_FEDISTANTLIGHT(child))
1030             _light_source.set_active(0);
1031         else if(SP_IS_FEPOINTLIGHT(child))
1032             _light_source.set_active(1);
1033         else if(SP_IS_FESPOTLIGHT(child))
1034             _light_source.set_active(2);
1035         else
1036             _light_source.set_active(-1);
1038         update();
1040         _locked = false;
1041     }
1042 private:
1043     void on_source_changed()
1044     {
1045         if(_locked)
1046             return;
1048         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1049         if(prim) {
1050             _locked = true;
1052             SPObject* child = prim->children;
1053             const int ls = _light_source.get_active_row_number();
1054             // Check if the light source type has changed
1055             if(!(ls == -1 && !child) &&
1056                !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1057                !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1058                !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1059                 if(child)
1060                     sp_repr_unparent(child->repr);
1062                 if(ls != -1) {
1063                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1064                     Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1065                     prim->repr->appendChild(repr);
1066                     Inkscape::GC::release(repr);
1067                 }
1069                 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1070                 update();
1071             }
1073             _locked = false;
1074         }
1075     }
1077     void update()
1078     {
1079         _box.hide_all();
1080         _box.show();
1081         _light_box.show_all();
1083         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1084         if(prim && prim->children)
1085             _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1086     }
1088     FilterEffectsDialog& _dialog;
1089     Gtk::VBox _box;
1090     Settings _settings;
1091     Gtk::HBox _light_box;
1092     Gtk::Label _light_label;
1093     ComboBoxEnum<LightSource> _light_source;
1094     bool _locked;
1095 };
1097 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1099     LightSourceControl* ls = new LightSourceControl(_dialog);
1100     add_attr_widget(ls);
1101     add_widget(&ls->get_box(), "");
1102     return ls;
1105 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1106                                           sigc::slot<void> rem)
1108     Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1110     menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1111     Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1112     menu->append(*mi);
1113     mi->signal_activate().connect(rem);
1114     mi->show();
1115     menu->accelerate(parent);
1117     return menu;
1120 /*** FilterModifier ***/
1121 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1122     : _dialog(d), _add(Gtk::Stock::NEW), _observer(new SignalObserver)
1124     Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1125     pack_start(*sw);
1126     pack_start(_add, false, false);
1127     sw->add(_list);
1129     _model = Gtk::ListStore::create(_columns);
1130     _list.set_model(_model);
1131     _cell_toggle.set_active(true);
1132     const int selcol = _list.append_column("", _cell_toggle);
1133     Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1134     if(col)
1135        col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1136     _list.append_column_editable(_("_Filter"), _columns.label);
1137     ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1138         signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1140     sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1141     sw->set_shadow_type(Gtk::SHADOW_IN);
1142     show_all_children();
1143     _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1144     _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1145     _list.signal_button_release_event().connect_notify(
1146         sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1147     _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1148                               sigc::mem_fun(*this, &FilterModifier::remove_filter));
1149     _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1150                                  _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1151     _menu->accelerate(*this);
1153     _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1154     _observer->signal_changed().connect(signal_filter_changed().make_slot());
1155     g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1156                      G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1158     g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1159                      G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1160     g_signal_connect(G_OBJECT(INKSCAPE), "deactivate_desktop",
1161                      G_CALLBACK(&FilterModifier::on_deactivate_desktop), this);
1163     on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1164     update_filters();
1167 FilterEffectsDialog::FilterModifier::~FilterModifier()
1169    _resource_changed.disconnect();
1170    _doc_replaced.disconnect();
1173 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1175     me->_doc_replaced.disconnect();
1176     me->_doc_replaced = desktop->connectDocumentReplaced(
1177         sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1179     me->_resource_changed.disconnect();
1180     me->_resource_changed =
1181         sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1182                                               sigc::mem_fun(me, &FilterModifier::update_filters));
1184     me->_dialog.setDesktop(desktop);
1186     me->update_filters();
1189 void FilterEffectsDialog::FilterModifier::on_deactivate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1191     me->_doc_replaced.disconnect();
1192     me->_resource_changed.disconnect();
1193     me->_dialog.setDesktop(NULL);
1197 // When the selection changes, show the active filter(s) in the dialog
1198 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1199                                                                        Selection *sel,
1200                                                                        FilterModifier* fm)
1202     if(fm && sel)
1203         fm->update_selection(sel);
1206 // Update each filter's sel property based on the current object selection;
1207 //  If the filter is not used by any selected object, sel = 0,
1208 //  otherwise sel is set to the total number of filters in use by selected objects
1209 //  If only one filter is in use, it is selected
1210 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1212     std::set<SPObject*> used;
1214     for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1215         SPObject *obj = SP_OBJECT (i->data);
1216         SPStyle *style = SP_OBJECT_STYLE (obj);
1217         if(!style || !SP_IS_ITEM(obj)) continue;
1219         if(style->filter.set && style->getFilter())
1220             used.insert(style->getFilter());
1221         else
1222             used.insert(0);
1223     }
1225     const int size = used.size();
1227     for(Gtk::TreeIter iter = _model->children().begin();
1228         iter != _model->children().end(); ++iter) {
1229         if(used.find((*iter)[_columns.filter]) != used.end()) {
1230             // If only one filter is in use by the selection, select it
1231             if(size == 1)
1232                 _list.get_selection()->select(iter);
1233             (*iter)[_columns.sel] = size;
1234         }
1235         else
1236             (*iter)[_columns.sel] = 0;
1237     }
1240 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1242     _observer->set(get_selected_filter());
1243     signal_filter_changed()();
1246 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1248     Gtk::TreeModel::iterator iter = _model->get_iter(path);
1250     if(iter) {
1251         SPFilter* filter = (*iter)[_columns.filter];
1252         filter->setLabel(text.c_str());
1253         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1254         if(iter)
1255             (*iter)[_columns.label] = text;
1256     }
1259 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1261     Gtk::TreeIter iter = _model->get_iter(path);
1263     if(iter) {
1264         SPDesktop *desktop = _dialog.getDesktop();
1265         SPDocument *doc = sp_desktop_document(desktop);
1266         SPFilter* filter = (*iter)[_columns.filter];
1267         Inkscape::Selection *sel = sp_desktop_selection(desktop);
1269         /* If this filter is the only one used in the selection, unset it */
1270         if((*iter)[_columns.sel] == 1)
1271             filter = 0;
1273         GSList const *items = sel->itemList();
1275         for (GSList const *i = items; i != NULL; i = i->next) {
1276             SPItem * item = SP_ITEM(i->data);
1277             SPStyle *style = SP_OBJECT_STYLE(item);
1278             g_assert(style != NULL);
1280             if(filter)
1281                 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1282             else
1283                 ::remove_filter(item, false);
1285             SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1286         }
1288         update_selection(sel);
1289         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS,  _("Apply filter"));
1290     }
1293 /* Add all filters in the document to the combobox.
1294    Keeps the same selection if possible, otherwise selects the first element */
1295 void FilterEffectsDialog::FilterModifier::update_filters()
1297     SPDesktop* desktop = _dialog.getDesktop();
1298     SPDocument* document = sp_desktop_document(desktop);
1299     const GSList* filters = sp_document_get_resource_list(document, "filter");
1301     _model->clear();
1303     for(const GSList *l = filters; l; l = l->next) {
1304         Gtk::TreeModel::Row row = *_model->append();
1305         SPFilter* f = (SPFilter*)l->data;
1306         row[_columns.filter] = f;
1307         const gchar* lbl = f->label();
1308         const gchar* id = SP_OBJECT_ID(f);
1309         row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1310     }
1312     update_selection(desktop->selection);
1313     _dialog.update_filter_general_settings_view();
1316 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1318     if(_list.get_selection()) {
1319         Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1321         if(i)
1322             return (*i)[_columns.filter];
1323     }
1325     return 0;
1328 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1330     if(filter) {
1331         for(Gtk::TreeModel::iterator i = _model->children().begin();
1332             i != _model->children().end(); ++i) {
1333             if((*i)[_columns.filter] == filter) {
1334                 _list.get_selection()->select(i);
1335                 break;
1336             }
1337         }
1338     }
1341 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1343     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1344         const bool sensitive = get_selected_filter() != NULL;
1345         _menu->items()[0].set_sensitive(sensitive);
1346         _menu->items()[1].set_sensitive(sensitive);
1347         _menu->popup(event->button, event->time);
1348     }
1351 void FilterEffectsDialog::FilterModifier::add_filter()
1353     SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1354     SPFilter* filter = new_filter(doc);
1356     const int count = _model->children().size();
1357     std::ostringstream os;
1358     os << "filter" << count;
1359     filter->setLabel(os.str().c_str());
1361     update_filters();
1363     select_filter(filter);
1365     sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1368 void FilterEffectsDialog::FilterModifier::remove_filter()
1370     SPFilter *filter = get_selected_filter();
1372     if(filter) {
1373         SPDocument* doc = filter->document;
1374         sp_repr_unparent(filter->repr);
1376         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1378         update_filters();
1379     }
1382 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1384     SPFilter* filter = get_selected_filter();
1386     if(filter) {
1387         Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1388         repr = repr->duplicate(repr->document());
1389         parent->appendChild(repr);
1391         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1393         update_filters();
1394     }
1397 void FilterEffectsDialog::FilterModifier::rename_filter()
1399     _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1402 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1403     : Glib::ObjectBase(typeid(CellRendererConnection)),
1404       _primitive(*this, "primitive", 0)
1405 {}
1407 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1409     return _primitive.get_proxy();
1412 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1414     _text_width = w;
1417 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1419     return _text_width;
1422 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1423     Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1424     int* x_offset, int* y_offset, int* width, int* height) const
1426     PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1428     if(x_offset)
1429         (*x_offset) = 0;
1430     if(y_offset)
1431         (*y_offset) = 0;
1432     if(width)
1433         (*width) = size * primlist.primitive_count() + _text_width * 7;
1434     if(height) {
1435         // Scale the height depending on the number of inputs, unless it's
1436         // the first primitive, in which case there are no connections
1437         SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1438         (*height) = size * input_count(prim);
1439     }
1442 /*** PrimitiveList ***/
1443 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1444     : _dialog(d),
1445       _in_drag(0),
1446       _observer(new SignalObserver)
1448     d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1450     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1451     signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1453     _model = Gtk::ListStore::create(_columns);
1455     set_reorderable(true);
1457     set_model(_model);
1458     append_column(_("_Effect"), _columns.type);
1460     _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1461     get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1462     signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1464     _connection_cell.set_text_width(init_text());
1466     int cols_count = append_column(_("Connections"), _connection_cell);
1467     Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1468     if(col)
1469        col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1472 // Sets up a vertical Pango context/layout, and returns the largest
1473 // width needed to render the FilterPrimitiveInput labels.
1474 int FilterEffectsDialog::PrimitiveList::init_text()
1476     // Set up a vertical context+layout
1477     Glib::RefPtr<Pango::Context> context = create_pango_context();
1478     const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1479     context->set_matrix(matrix);
1480     _vertical_layout = Pango::Layout::create(context);
1482     int maxfont = 0;
1483     for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1484         _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1485         int fontw, fonth;
1486         _vertical_layout->get_pixel_size(fontw, fonth);
1487         if(fonth > maxfont)
1488             maxfont = fonth;
1489     }
1491     return maxfont;
1494 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1496     return _signal_primitive_changed;
1499 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1501     _observer->set(get_selected());
1502     signal_primitive_changed()();
1503     _dialog._color_matrix_values->clear_store();
1506 /* Add all filter primitives in the current to the list.
1507    Keeps the same selection if possible, otherwise selects the first element */
1508 void FilterEffectsDialog::PrimitiveList::update()
1510     SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1511     const SPFilterPrimitive* active_prim = get_selected();
1512     bool active_found = false;
1514     _model->clear();
1516     if(f) {
1517         _dialog._primitive_box.set_sensitive(true);
1518         _dialog.update_filter_general_settings_view();
1519         for(SPObject *prim_obj = f->children;
1520                 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1521                 prim_obj = prim_obj->next) {
1522             SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1523             if(prim) {
1524                 Gtk::TreeModel::Row row = *_model->append();
1525                 row[_columns.primitive] = prim;
1526                 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1527                 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1528                 row[_columns.id] = SP_OBJECT_ID(prim);
1530                 if(prim == active_prim) {
1531                     get_selection()->select(row);
1532                     active_found = true;
1533                 }
1534             }
1535         }
1537         if(!active_found && _model->children().begin())
1538             get_selection()->select(_model->children().begin());
1540         columns_autosize();
1541     }
1542     else {
1543         _dialog._primitive_box.set_sensitive(false);
1544     }
1547 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1549     _primitive_menu = menu;
1552 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1554     if(_dialog._filter_modifier.get_selected_filter()) {
1555         Gtk::TreeModel::iterator i = get_selection()->get_selected();
1556         if(i)
1557             return (*i)[_columns.primitive];
1558     }
1560     return 0;
1563 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1565     for(Gtk::TreeIter i = _model->children().begin();
1566         i != _model->children().end(); ++i) {
1567         if((*i)[_columns.primitive] == prim)
1568             get_selection()->select(i);
1569     }
1572 void FilterEffectsDialog::PrimitiveList::remove_selected()
1574     SPFilterPrimitive* prim = get_selected();
1576     if(prim) {
1577         _observer->set(0);
1579         sp_repr_unparent(prim->repr);
1581         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1582                          _("Remove filter primitive"));
1584         update();
1585     }
1588 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1590     Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1591     Glib::RefPtr<Gdk::Window> win = get_bin_window();
1592     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1594     SPFilterPrimitive* prim = get_selected();
1595     int row_count = get_model()->children().size();
1597     int fheight = CellRendererConnection::size;
1598     Gdk::Rectangle rct, vis;
1599     Gtk::TreeIter row = get_model()->children().begin();
1600     int text_start_x = 0;
1601     if(row) {
1602         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1603         get_visible_rect(vis);
1604         int vis_x, vis_y;
1605         tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1607         text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter._length + 1) + 1;
1608         for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1609             _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1610             const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1611             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());
1612             get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1613             get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1614         }
1615     }
1617     int row_index = 0;
1618     for(; row != get_model()->children().end(); ++row, ++row_index) {
1619         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1620         const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1622         // Check mouse state
1623         int mx, my;
1624         Gdk::ModifierType mask;
1625         get_bin_window()->get_pointer(mx, my, mask);
1627         // Outline the bottom of the connection area
1628         const int outline_x = x + fheight * (row_count - row_index);
1629         get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1631         // Side outline
1632         get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1634         std::vector<Gdk::Point> con_poly;
1635         int con_drag_y = 0;
1636         bool inside;
1637         const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1638         const int inputs = input_count(row_prim);
1640         if(SP_IS_FEMERGE(row_prim)) {
1641             for(int i = 0; i < inputs; ++i) {
1642                 inside = do_connection_node(row, i, con_poly, mx, my);
1643                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1644                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1645                                                inside, con_poly);
1647                 if(_in_drag == (i + 1))
1648                     con_drag_y = con_poly[2].get_y();
1650                 if(_in_drag != (i + 1) || row_prim != prim)
1651                     draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1652             }
1653         }
1654         else {
1655             // Draw "in" shape
1656             inside = do_connection_node(row, 0, con_poly, mx, my);
1657             con_drag_y = con_poly[2].get_y();
1658             get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1659                                            darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1660                                            inside, con_poly);
1662             // Draw "in" connection
1663             if(_in_drag != 1 || row_prim != prim)
1664                 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1666             if(inputs == 2) {
1667                 // Draw "in2" shape
1668                 inside = do_connection_node(row, 1, con_poly, mx, my);
1669                 if(_in_drag == 2)
1670                     con_drag_y = con_poly[2].get_y();
1671                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1672                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1673                                                inside, con_poly);
1674                 // Draw "in2" connection
1675                 if(_in_drag != 2 || row_prim != prim)
1676                     draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1677             }
1678         }
1680         // Draw drag connection
1681         if(row_prim == prim && _in_drag) {
1682             get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1683                                         mx, con_drag_y);
1684             get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1685         }
1686     }
1688     return true;
1691 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1692                                                          const int text_start_x, const int x1, const int y1,
1693                                                          const int row_count)
1695     int src_id = 0;
1696     Gtk::TreeIter res = find_result(input, attr, src_id);
1697     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1698     Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1699     Glib::RefPtr<Gdk::GC> gc;
1701     const bool is_first = input == get_model()->children().begin();
1702     const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1703     const bool use_default = !res && !is_merge;
1705     if(res == input || (use_default && is_first)) {
1706         // Draw straight connection to a standard input
1707         // Draw a lighter line for an implicit connection to a standard input
1708         const int tw = _connection_cell.get_text_width();
1709         gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1710         gc = (use_default && is_first) ? lightgc : darkgc;
1711         get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1712         get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1713     }
1714     else {
1715         // Draw an 'L'-shaped connection to another filter primitive
1716         // If no connection is specified, draw a light connection to the previous primitive
1717         gc = use_default ? lightgc : darkgc;
1719         if(use_default) {
1720             res = input;
1721             --res;
1722         }
1724         if(res) {
1725             Gdk::Rectangle rct;
1727             get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1728             const int fheight = CellRendererConnection::size;
1730             get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1731             const int row_index = find_index(res);
1732             const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1733             const int y2 = rct.get_y() + rct.get_height();
1735             // Draw a bevelled 'L'-shaped connection
1736             get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1737             get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1738             get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1739         }
1740     }
1743 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1744 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1745                                                             std::vector<Gdk::Point>& points,
1746                                                             const int ix, const int iy)
1748     Gdk::Rectangle rct;
1749     const int icnt = input_count((*row)[_columns.primitive]);
1751     get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1752     const int fheight = CellRendererConnection::size;
1754     get_cell_area(_model->get_path(row), *get_column(1), rct);
1755     const float h = rct.get_height() / icnt;
1757     const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1758     const int con_w = (int)(fheight * 0.35f);
1759     const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1760     points.clear();
1761     points.push_back(Gdk::Point(x, con_y));
1762     points.push_back(Gdk::Point(x, con_y + con_w * 2));
1763     points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1765     return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1768 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1769                                                                     const int attr, int& src_id)
1771     SPFilterPrimitive* prim = (*start)[_columns.primitive];
1772     Gtk::TreeIter target = _model->children().end();
1773     int image = 0;
1775     if(SP_IS_FEMERGE(prim)) {
1776         int c = 0;
1777         bool found = false;
1778         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1779             if(c == attr && SP_IS_FEMERGENODE(o)) {
1780                 image = SP_FEMERGENODE(o)->input;
1781                 found = true;
1782             }
1783         }
1784         if(!found)
1785             return target;
1786     }
1787     else {
1788         if(attr == SP_ATTR_IN)
1789             image = prim->image_in;
1790         else if(attr == SP_ATTR_IN2) {
1791             if(SP_IS_FEBLEND(prim))
1792                 image = SP_FEBLEND(prim)->in2;
1793             else if(SP_IS_FECOMPOSITE(prim))
1794                 image = SP_FECOMPOSITE(prim)->in2;
1795             else if(SP_IS_FEDISPLACEMENTMAP(prim))
1796                 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1797             else
1798                 return target;
1799         }
1800         else
1801             return target;
1802     }
1804     if(image >= 0) {
1805         for(Gtk::TreeIter i = _model->children().begin();
1806             i != start; ++i) {
1807             if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1808                 target = i;
1809         }
1810         return target;
1811     }
1812     else if(image < -1) {
1813         src_id = -(image + 2);
1814         return start;
1815     }
1817     return target;
1820 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1822     int i = 0;
1823     for(Gtk::TreeIter iter = _model->children().begin();
1824         iter != target; ++iter, ++i){};
1825     return i;
1828 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1830     Gtk::TreePath path;
1831     Gtk::TreeViewColumn* col;
1832     const int x = (int)e->x, y = (int)e->y;
1833     int cx, cy;
1835     _drag_prim = 0;
1837     if(get_path_at_pos(x, y, path, col, cx, cy)) {
1838         Gtk::TreeIter iter = _model->get_iter(path);
1839         std::vector<Gdk::Point> points;
1841         _drag_prim = (*iter)[_columns.primitive];
1842         const int icnt = input_count(_drag_prim);
1844         for(int i = 0; i < icnt; ++i) {
1845             if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1846                 _in_drag = i + 1;
1847                 break;
1848             }
1849         }
1851         queue_draw();
1852     }
1854     if(_in_drag) {
1855         _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1856         _autoscroll = 0;
1857         get_selection()->select(path);
1858         return true;
1859     }
1860     else
1861         return Gtk::TreeView::on_button_press_event(e);
1864 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1866     const int speed = 10;
1867     const int limit = 15;
1869     Gdk::Rectangle vis;
1870     get_visible_rect(vis);
1871     int vis_x, vis_y;
1872     tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1873     const int top = vis_y + vis.get_height();
1875     // When autoscrolling during a connection drag, set the speed based on
1876     // where the mouse is in relation to the edges.
1877     if(e->y < vis_y)
1878         _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1879     else if(e->y < vis_y + limit)
1880         _autoscroll = -speed;
1881     else if(e->y > top)
1882         _autoscroll = (int)(speed + (e->y - top) / 5);
1883     else if(e->y > top - limit)
1884         _autoscroll = speed;
1885     else
1886         _autoscroll = 0;
1888     queue_draw();
1890     return Gtk::TreeView::on_motion_notify_event(e);
1893 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1895     SPFilterPrimitive *prim = get_selected(), *target;
1897     _scroll_connection.disconnect();
1899     if(_in_drag && prim) {
1900         Gtk::TreePath path;
1901         Gtk::TreeViewColumn* col;
1902         int cx, cy;
1904         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1905             const gchar *in_val = 0;
1906             Glib::ustring result;
1907             Gtk::TreeIter target_iter = _model->get_iter(path);
1908             target = (*target_iter)[_columns.primitive];
1909             col = get_column(1);
1911             Gdk::Rectangle rct;
1912             get_cell_area(path, *col, rct);
1913             const int twidth = _connection_cell.get_text_width();
1914             const int sources_x = rct.get_width() - twidth * FPInputConverter._length;
1915             if(cx > sources_x) {
1916                 int src = (cx - sources_x) / twidth;
1917                 if (src < 0) {
1918                     src = 0;
1919                 } else if(src >= static_cast<int>(FPInputConverter._length)) {
1920                     src = FPInputConverter._length - 1;
1921                 }
1922                 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1923                 in_val = result.c_str();
1924             }
1925             else {
1926                 // Ensure that the target comes before the selected primitive
1927                 for(Gtk::TreeIter iter = _model->children().begin();
1928                     iter != get_selection()->get_selected(); ++iter) {
1929                     if(iter == target_iter) {
1930                         Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1931                         // Make sure the target has a result
1932                         const gchar *gres = repr->attribute("result");
1933                         if(!gres) {
1934                             result = sp_filter_get_new_result_name(SP_FILTER(prim->parent));
1935                             repr->setAttribute("result", result.c_str());
1936                             in_val = result.c_str();
1937                         }
1938                         else
1939                             in_val = gres;
1940                         break;
1941                     }
1942                 }
1943             }
1945             if(SP_IS_FEMERGE(prim)) {
1946                 int c = 1;
1947                 bool handled = false;
1948                 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1949                     if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1950                         // If input is null, delete it
1951                         if(!in_val) {
1952                             sp_repr_unparent(o->repr);
1953                             sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1954                                              _("Remove merge node"));
1955                             (*get_selection()->get_selected())[_columns.primitive] = prim;
1956                         }
1957                         else
1958                             _dialog.set_attr(o, SP_ATTR_IN, in_val);
1959                         handled = true;
1960                     }
1961                 }
1962                 // Add new input?
1963                 if(!handled && c == _in_drag && in_val) {
1964                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1965                     Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1966                     repr->setAttribute("inkscape:collect", "always");
1967                     prim->repr->appendChild(repr);
1968                     SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1969                     Inkscape::GC::release(repr);
1970                     _dialog.set_attr(node, SP_ATTR_IN, in_val);
1971                     (*get_selection()->get_selected())[_columns.primitive] = prim;
1972                 }
1973             }
1974             else {
1975                 if(_in_drag == 1)
1976                     _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1977                 else if(_in_drag == 2)
1978                     _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1979             }
1980         }
1982         _in_drag = 0;
1983         queue_draw();
1985         _dialog.update_settings_view();
1986     }
1988     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1989         const bool sensitive = get_selected() != NULL;
1990         _primitive_menu->items()[0].set_sensitive(sensitive);
1991         _primitive_menu->items()[1].set_sensitive(sensitive);
1992         _primitive_menu->popup(e->button, e->time);
1994         return true;
1995     }
1996     else
1997         return Gtk::TreeView::on_button_release_event(e);
2000 // Checks all of prim's inputs, removes any that use result
2001 void check_single_connection(SPFilterPrimitive* prim, const int result)
2003     if(prim && result >= 0) {
2005         if(prim->image_in == result)
2006             SP_OBJECT_REPR(prim)->setAttribute("in", 0);
2008         if(SP_IS_FEBLEND(prim)) {
2009             if(SP_FEBLEND(prim)->in2 == result)
2010                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2011         }
2012         else if(SP_IS_FECOMPOSITE(prim)) {
2013             if(SP_FECOMPOSITE(prim)->in2 == result)
2014                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2015         }
2016         else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
2017             if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
2018                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2019         }
2020     }
2023 // Remove any connections going to/from prim_iter that forward-reference other primitives
2024 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
2026     SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
2027     bool before = true;
2029     for(Gtk::TreeIter iter = _model->children().begin();
2030         iter != _model->children().end(); ++iter) {
2031         if(iter == prim_iter)
2032             before = false;
2033         else {
2034             SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
2035             if(before)
2036                 check_single_connection(cur_prim, prim->image_out);
2037             else
2038                 check_single_connection(prim, cur_prim->image_out);
2039         }
2040     }
2043 // Reorder the filter primitives to match the list order
2044 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2046     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2047     int ndx = 0;
2049     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2050         iter != _model->children().end(); ++iter, ++ndx) {
2051         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2052         if(prim && prim == _drag_prim) {
2053             SP_OBJECT_REPR(prim)->setPosition(ndx);
2054             break;
2055         }
2056     }
2058     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2059         iter != _model->children().end(); ++iter, ++ndx) {
2060         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2061         if(prim && prim == _drag_prim) {
2062             sanitize_connections(iter);
2063             get_selection()->select(iter);
2064             break;
2065         }
2066     }
2068     filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2070     sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2073 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2074 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2076     if(_autoscroll) {
2077         Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2078         double v;
2080         v = a.get_value() + _autoscroll;
2081         if(v < 0)
2082             v = 0;
2083         if(v > a.get_upper() - a.get_page_size())
2084             v = a.get_upper() - a.get_page_size();
2086         a.set_value(v);
2088         queue_draw();
2089     }
2091     return true;
2094 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2096     return _model->children().size();
2099 /*** FilterEffectsDialog ***/
2101 FilterEffectsDialog::FilterEffectsDialog()
2102     : UI::Widget::Panel("", "/dialogs/filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2103       _add_primitive_type(FPConverter),
2104       _add_primitive(_("Add Effect:")),
2105       _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2106       _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2107       _settings_initialized(false),
2108       _locked(false),
2109       _attr_lock(false),
2110       _filter_modifier(*this),
2111       _primitive_list(*this)
2113     _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2114                              NR_FILTER_ENDPRIMITIVETYPE);
2115     _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2116                              1);
2117     _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2118     _sizegroup->set_ignore_hidden();
2120     _add_primitive_type.remove_row(NR_FILTER_TILE);
2121     _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2123     // Initialize widget hierarchy
2124     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2125     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2126     Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox(/*homogeneous:*/false, /*spacing:*/4));
2127     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2129     _getContents()->add(*hpaned);
2130     hpaned->pack1(_filter_modifier);
2131     hpaned->pack2(_primitive_box);
2132     _primitive_box.pack_start(*sw_prims);
2133     _primitive_box.pack_start(*hb_prims, false, false);
2134     _primitive_box.pack_start(*infobox,false, false);
2135     sw_prims->add(_primitive_list);
2136     infobox->pack_start(_infobox_icon, false, false);
2137     infobox->pack_start(_infobox_desc, false, false);
2138     _infobox_desc.set_line_wrap(true);
2139     _infobox_desc.set_size_request(200, -1);
2141     hb_prims->pack_start(_add_primitive, false, false);
2142     hb_prims->pack_start(_add_primitive_type, false, false);
2143     _getContents()->pack_start(_settings_tabs, false, false);
2144     _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2145     _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2147     _primitive_list.signal_primitive_changed().connect(
2148         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2149     _filter_modifier.signal_filter_changed().connect(
2150         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2152     _add_primitive_type.signal_changed().connect(
2153         sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2155     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2156     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2157 //    al_settings->set_padding(0, 0, 12, 0);
2158 //    fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2159 //    ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2160     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2161     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2162                                                sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2164     show_all_children();
2165     init_settings_widgets();
2166     _primitive_list.update();
2167     update_primitive_infobox();
2170 FilterEffectsDialog::~FilterEffectsDialog()
2172     delete _settings;
2173     delete _filter_general_settings;
2176 void FilterEffectsDialog::set_attrs_locked(const bool l)
2178     _locked = l;
2181 void FilterEffectsDialog::show_all_vfunc()
2183     UI::Widget::Panel::show_all_vfunc();
2185     update_settings_view();
2188 void FilterEffectsDialog::init_settings_widgets()
2190     // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2191     //       most of the current values are complete guesses!
2193     _empty_settings.set_sensitive(false);
2194     _settings_tab1.pack_start(_empty_settings);
2196     _no_filter_selected.set_sensitive(false);
2197     _settings_tab2.pack_start(_no_filter_selected);
2198     _settings_initialized = true;
2200     _filter_general_settings->type(0);
2201     _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"));
2202     _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"));
2204     _settings->type(NR_FILTER_BLEND);
2205     _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode"), BlendModeConverter);
2207     _settings->type(NR_FILTER_COLORMATRIX);
2208     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."));
2209     _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s)"));
2210     colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2212     _settings->type(NR_FILTER_COMPONENTTRANSFER);
2213     _settings->add_notimplemented();
2214     //TRANSLATORS: for info on "Slope" and "Intercept", see http://id.mind.net/~zona/mmts/functionInstitute/linearFunctions/lsif.html
2215     /*_settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2216     _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2217     _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2218     _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2219     _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2220     _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2222     _settings->type(NR_FILTER_COMPOSITE);
2223     _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
2224     _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."));
2225     _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."));
2226     _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."));
2227     _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."));
2229     _settings->type(NR_FILTER_CONVOLVEMATRIX);
2230     _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"));
2231     _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."));
2232     //TRANSLATORS: for info on "Kernel", see http://en.wikipedia.org/wiki/Kernel_(matrix)
2233     _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."));
2234     _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2235     //TODO: svg spec: The default value is the sum of all values in kernelMatrix, with the exception that if the sum is zero, then the divisor is set to 1.
2236     _settings->add_spinslider(1, SP_ATTR_DIVISOR, _("Divisor"), 1, 20, 1, 0.1, 2, _("After applying the kernelMatrix to the input image to yield a number, that number is divided by divisor to yield the final destination color value. A divisor that is the sum of all the matrix values tends to have an evening effect on the overall color intensity of the result."));
2237     _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."));
2238     _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."));
2239     _settings->add_checkbutton(false, SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive."));
2241     _settings->type(NR_FILTER_DIFFUSELIGHTING);
2242     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Diffuse Color"), _("Defines the color of the light source"));
2243     _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2244     _settings->add_spinslider(1, SP_ATTR_DIFFUSECONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2245     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2246     _settings->add_lightsource();
2248     _settings->type(NR_FILTER_DISPLACEMENTMAP);
2249     _settings->add_spinslider(0, SP_ATTR_SCALE, _("Scale"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2250     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2251     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2253     _settings->type(NR_FILTER_FLOOD);
2254     _settings->add_color(/*default: black*/ 0, SP_PROP_FLOOD_COLOR, _("Flood Color"), _("The whole filter region will be filled with this color."));
2255     _settings->add_spinslider(1, SP_PROP_FLOOD_OPACITY, _("Opacity"), 0, 1, 0.1, 0.01, 2);
2257     _settings->type(NR_FILTER_GAUSSIANBLUR);
2258     _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2260     _settings->type(NR_FILTER_MERGE);
2261     _settings->add_no_params();
2263     _settings->type(NR_FILTER_MORPHOLOGY);
2264     _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2265     _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius"), 0, 100, 1, 0.01, 1);
2267     _settings->type(NR_FILTER_IMAGE);
2268     _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image"));
2270     _settings->type(NR_FILTER_OFFSET);
2271     _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"));
2272     _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"));
2274     _settings->type(NR_FILTER_SPECULARLIGHTING);
2275     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Specular Color"), _("Defines the color of the light source"));
2276     _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2277     _settings->add_spinslider(1, SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2278     _settings->add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2279     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2280     _settings->add_lightsource();
2282     _settings->type(NR_FILTER_TILE);
2283     _settings->add_notimplemented();
2285     _settings->type(NR_FILTER_TURBULENCE);
2286 //    _settings->add_checkbutton(false, SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2287     _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2288     _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency"), 0, 0.4, 0.001, 0.01, 3);
2289     _settings->add_spinslider(1, SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2290     _settings->add_spinslider(0, SP_ATTR_SEED, _("Seed"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2293 void FilterEffectsDialog::add_primitive()
2295     SPFilter* filter = _filter_modifier.get_selected_filter();
2297     if(filter) {
2298         SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2300         _primitive_list.select(prim);
2302         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2303     }
2306 void FilterEffectsDialog::update_primitive_infobox()
2308     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2309     if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2310         _infobox_icon.show();
2311         _infobox_desc.show();
2312     } else {
2313         _infobox_icon.hide();
2314         _infobox_desc.hide();
2315     }
2316     switch(_add_primitive_type.get_active_data()->id){
2317         case(NR::NR_FILTER_BLEND):
2318             _infobox_icon.set(g_strdup_printf("%s/feBlend-icon.png", INKSCAPE_PIXMAPDIR));
2319             _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2320             break;
2321         case(NR::NR_FILTER_COLORMATRIX):
2322             _infobox_icon.set(g_strdup_printf("%s/feColorMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2323             _infobox_desc.set_markup(_("The <b>feColorMatrix</b> filter primitive applies a matrix transformation to colour of each rendered pixel. This allows for effects like turning object to grayscale, modifying colour saturation and changing colour hue."));
2324             break;
2325         case(NR::NR_FILTER_COMPONENTTRANSFER):
2326             _infobox_icon.set(g_strdup_printf("%s/feComponentTransfer-icon.png", INKSCAPE_PIXMAPDIR));
2327             _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."));
2328             break;
2329         case(NR::NR_FILTER_COMPOSITE):
2330             _infobox_icon.set(g_strdup_printf("%s/feComposite-icon.png", INKSCAPE_PIXMAPDIR));
2331             _infobox_desc.set_markup(_("The <b>feComposite</b> filter primitive composites two images using one of the Porter-Duff blending modes or the aritmetic mode described in SVG standard. Porter-Duff blending modes are essentially logical operations between the corresponding pixel values of the images."));
2332             break;
2333         case(NR::NR_FILTER_CONVOLVEMATRIX):
2334             _infobox_icon.set(g_strdup_printf("%s/feConvolveMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2335             _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."));
2336             break;
2337         case(NR::NR_FILTER_DIFFUSELIGHTING):
2338             _infobox_icon.set(g_strdup_printf("%s/feDiffuseLighting-icon.png", INKSCAPE_PIXMAPDIR));
2339             _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."));
2340             break;
2341         case(NR::NR_FILTER_DISPLACEMENTMAP):
2342             _infobox_icon.set(g_strdup_printf("%s/feDisplacementMap-icon.png", INKSCAPE_PIXMAPDIR));
2343             _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."));
2344             break;
2345         case(NR::NR_FILTER_FLOOD):
2346             _infobox_icon.set(g_strdup_printf("%s/feFlood-icon.png", INKSCAPE_PIXMAPDIR));
2347             _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."));
2348             break;
2349         case(NR::NR_FILTER_GAUSSIANBLUR):
2350             _infobox_icon.set(g_strdup_printf("%s/feGaussianBlur-icon.png", INKSCAPE_PIXMAPDIR));
2351             _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."));
2352             break;
2353         case(NR::NR_FILTER_IMAGE):
2354             _infobox_icon.set(g_strdup_printf("%s/feImage-icon.png", INKSCAPE_PIXMAPDIR));
2355             _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2356             break;
2357         case(NR::NR_FILTER_MERGE):
2358             _infobox_icon.set(g_strdup_printf("%s/feMerge-icon.png", INKSCAPE_PIXMAPDIR));
2359             _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."));
2360             break;
2361         case(NR::NR_FILTER_MORPHOLOGY):
2362             _infobox_icon.set(g_strdup_printf("%s/feMorphology-icon.png", INKSCAPE_PIXMAPDIR));
2363             _infobox_desc.set_markup(_("The <b>feMorphology</b> filter primitive provides erode and dilate effects. For single-colour objects erode makes the object thinner and dilate makes it thicker."));
2364             break;
2365         case(NR::NR_FILTER_OFFSET):
2366             _infobox_icon.set(g_strdup_printf("%s/feOffset-icon.png", INKSCAPE_PIXMAPDIR));
2367             _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."));
2368             break;
2369         case(NR::NR_FILTER_SPECULARLIGHTING):
2370             _infobox_icon.set(g_strdup_printf("%s/feSpecularLighting-icon.png", INKSCAPE_PIXMAPDIR));
2371             _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."));
2372             break;
2373         case(NR::NR_FILTER_TILE):
2374             _infobox_icon.set(g_strdup_printf("%s/feTile-icon.png", INKSCAPE_PIXMAPDIR));
2375             _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2376             break;
2377         case(NR::NR_FILTER_TURBULENCE):
2378             _infobox_icon.set(g_strdup_printf("%s/feTurbulence-icon.png", INKSCAPE_PIXMAPDIR));
2379             _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."));
2380             break;
2381         default:
2382             g_assert(false);
2383             break;
2384     }
2387 void FilterEffectsDialog::duplicate_primitive()
2389     SPFilter* filter = _filter_modifier.get_selected_filter();
2390     SPFilterPrimitive* origprim = _primitive_list.get_selected();
2392     if(filter && origprim) {
2393         Inkscape::XML::Node *repr;
2394         repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2395         SP_OBJECT_REPR(filter)->appendChild(repr);
2397         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2399         _primitive_list.update();
2400     }
2403 void FilterEffectsDialog::convolve_order_changed()
2405     _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2406     _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2407     _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2410 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2412     set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2415 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2417     if(!_locked) {
2418         _attr_lock = true;
2419         SPFilter *filter = _filter_modifier.get_selected_filter();
2420         const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2421         if (filter && name && SP_OBJECT_REPR(filter)){
2422             SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2423             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2424         }
2425         _attr_lock = false;
2426     }
2429 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2431     set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2434 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2436     if(!_locked) {
2437         _attr_lock = true;
2439         SPFilter *filter = _filter_modifier.get_selected_filter();
2440         const gchar* name = (const gchar*)sp_attribute_name(attr);
2441         if(filter && name && o) {
2442             update_settings_sensitivity();
2444             SP_OBJECT_REPR(o)->setAttribute(name, val);
2445             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2447             Glib::ustring undokey = "filtereffects:";
2448             undokey += name;
2449             sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2450                                    _("Set filter primitive attribute"));
2451         }
2453         _attr_lock = false;
2454     }
2457 void FilterEffectsDialog::update_filter_general_settings_view()
2459     if(_settings_initialized != true) return;
2461     if(!_locked) {
2462         _attr_lock = true;
2464         SPFilter* filter = _filter_modifier.get_selected_filter();
2466         if(filter) {
2467             _filter_general_settings->show_and_update(0, filter);
2468             _no_filter_selected.hide();
2469         }
2470         else {
2471             std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2472             vect[0]->hide_all();
2473             _no_filter_selected.show();
2474         }
2476         _attr_lock = false;
2477     }
2480 void FilterEffectsDialog::update_settings_view()
2482     update_settings_sensitivity();
2484     if(_attr_lock)
2485         return;
2487 //First Tab
2489     std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2490     for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2491     _empty_settings.show();
2493     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2494     if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2495         _infobox_icon.show();
2496         _infobox_desc.show();
2497     } else {
2498         _infobox_icon.hide();
2499         _infobox_desc.hide();
2500     }
2502     SPFilterPrimitive* prim = _primitive_list.get_selected();
2504     if(prim) {
2505         _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2506         _empty_settings.hide();
2507     }
2509 //Second Tab
2511     std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2512     vect2[0]->hide_all();
2513     _no_filter_selected.show();
2515     SPFilter* filter = _filter_modifier.get_selected_filter();
2517     if(filter) {
2518         _filter_general_settings->show_and_update(0, filter);
2519         _no_filter_selected.hide();
2520     }
2524 void FilterEffectsDialog::update_settings_sensitivity()
2526     SPFilterPrimitive* prim = _primitive_list.get_selected();
2527     const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2528     _k1->set_sensitive(use_k);
2529     _k2->set_sensitive(use_k);
2530     _k3->set_sensitive(use_k);
2531     _k4->set_sensitive(use_k);
2533 // Component transfer not yet implemented
2534 /*
2535     if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2536         SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2537         const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2538         const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2540         _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2541         _ct_slope->set_sensitive(linear);
2542         _ct_intercept->set_sensitive(linear);
2543         _ct_amplitude->set_sensitive(gamma);
2544         _ct_exponent->set_sensitive(gamma);
2545         _ct_offset->set_sensitive(gamma);
2546     }
2547 */
2550 void FilterEffectsDialog::update_color_matrix()
2552     _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2555 } // namespace Dialog
2556 } // namespace UI
2557 } // namespace Inkscape
2559 /*
2560   Local Variables:
2561   mode:c++
2562   c-file-style:"stroustrup"
2563   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2564   indent-tabs-mode:nil
2565   fill-column:99
2566   End:
2567 */
2568 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :