Code

Warning cleanup
[inkscape.git] / src / ui / dialog / filter-effects-dialog.cpp
1 /**
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 "prefs-utils.h"
43 #include "selection.h"
44 #include "sp-feblend.h"
45 #include "sp-fecolormatrix.h"
46 #include "sp-fecomponenttransfer.h"
47 #include "sp-fecomposite.h"
48 #include "sp-feconvolvematrix.h"
49 #include "sp-fedisplacementmap.h"
50 #include "sp-fedistantlight.h"
51 #include "sp-femerge.h"
52 #include "sp-femergenode.h"
53 #include "sp-feoffset.h"
54 #include "sp-fepointlight.h"
55 #include "sp-fespotlight.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         Glib::ustring open_path;
665         char *attr = (char *)prefs_get_string_attribute("dialogs.open", "path");
666         if (attr)
667             open_path = attr;
669         //# Test if the open_path directory exists
670         if (!Inkscape::IO::file_test(open_path.c_str(),
671                   (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
672             open_path = "";
674         //# If no open path, default to our home directory
675         if (open_path.size() < 1)
676             {
677             open_path = g_get_home_dir();
678             open_path.append(G_DIR_SEPARATOR_S);
679             }
681         //# Create a dialog if we don't already have one
682         if (!selectFeImageFileInstance) {
683             selectFeImageFileInstance =
684                   Inkscape::UI::Dialog::FileOpenDialog::create(
685                      *_desktop->getToplevel(),
686                      open_path,
687                      Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not justy svg*/
688                      (char const *)_("Select an image to be used as feImage input"));
689         }
691         //# Show the dialog
692         bool const success = selectFeImageFileInstance->show();
693         if (!success)
694             return;
696         //# User selected something.  Get name and type
697         Glib::ustring fileName = selectFeImageFileInstance->getFilename();
699         if (fileName.size() > 0) {
701             Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
703             if ( newFileName.size() > 0)
704                 fileName = newFileName;
705             else
706                 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
708             open_path = fileName;
709             open_path.append(G_DIR_SEPARATOR_S);
710             prefs_set_string_attribute("dialogs.open", "path", open_path.c_str());
712             _entry.set_text(fileName);
713         }
714         return;
715     }
717     Gtk::Entry _entry;
718     Gtk::Button _fromFile;
719     Gtk::Button _fromSVGElement;
720     SPDesktop* _desktop;
721 };
723 class FilterEffectsDialog::Settings
725 public:
726     typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
728     Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
729         : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
730     {
731         _groups.resize(_max_types);
732         _attrwidgets.resize(_max_types);
733         _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
735         for(int i = 0; i < _max_types; ++i) {
736             _groups[i] = new Gtk::VBox;
737             b.pack_start(*_groups[i], false, false);
738         }
739         _current_type = 0;
740     }
742     ~Settings()
743     {
744         for(int i = 0; i < _max_types; ++i) {
745             delete _groups[i];
746             for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
747                 delete _attrwidgets[i][j];
748         }
749     }
751     // Show the active settings group and update all the AttrWidgets with new values
752     void show_and_update(const int t, SPObject* ob)
753     {
754         if(t != _current_type) {
755             type(t);
756             for(unsigned i = 0; i < _groups.size(); ++i)
757                 _groups[i]->hide();
758         }
759         if(t >= 0)
760             _groups[t]->show_all();
762         _dialog.set_attrs_locked(true);
763         for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
764             _attrwidgets[_current_type][i]->set_from_attribute(ob);
765         _dialog.set_attrs_locked(false);
766     }
768     int get_current_type() const
769     {
770         return _current_type;
771     }
773     void type(const int t)
774     {
775         _current_type = t;
776     }
778     void add_no_params()
779     {
780         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
781         add_widget(lbl, "");
782     }
784     void add_notimplemented()
785     {
786         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
787         add_widget(lbl, "");
788     }
790     // LightSource
791     LightSourceControl* add_lightsource();
793     // CheckBox
794     CheckButtonAttr* add_checkbutton(bool def, const SPAttributeEnum attr, const Glib::ustring& label,
795                                      const Glib::ustring& tv, const Glib::ustring& fv, char* tip_text = NULL)
796     {
797         CheckButtonAttr* cb = new CheckButtonAttr(def, label, tv, fv, attr, tip_text);
798         add_widget(cb, "");
799         add_attr_widget(cb);
800         return cb;
801     }
803     // ColorButton
804     ColorButton* add_color(unsigned int def, const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text = NULL)
805     {
806         ColorButton* col = new ColorButton(def, attr, tip_text);
807         add_widget(col, label);
808         add_attr_widget(col);
809         return col;
810     }
812     // Matrix
813     MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text)
814     {
815         MatrixAttr* conv = new MatrixAttr(attr, tip_text);
816         add_widget(conv, label);
817         add_attr_widget(conv);
818         return conv;
819     }
821     // ColorMatrixValues
822     ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
823     {
824         ColorMatrixValues* cmv = new ColorMatrixValues();
825         add_widget(cmv, label);
826         add_attr_widget(cmv);
827         return cmv;
828     }
830     // SpinSlider
831     SpinSlider* add_spinslider(double def, const SPAttributeEnum attr, const Glib::ustring& label,
832                          const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
833     {
834         SpinSlider* spinslider = new SpinSlider(def, lo, hi, step_inc, climb, digits, attr, tip_text);
835         add_widget(spinslider, label);
836         add_attr_widget(spinslider);
837         return spinslider;
838     }
840     // DualSpinSlider
841     DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
842                                        const double lo, const double hi, const double step_inc,
843                                        const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
844     {
845         DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
846         add_widget(dss, label);
847         add_attr_widget(dss);
848         return dss;
849     }
851     // DualSpinButton
852     DualSpinButton* add_dualspinbutton(char* defalt_value, const SPAttributeEnum attr, const Glib::ustring& label,
853                                        const double lo, const double hi, const double step_inc,
854                                        const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
855     {
856         DualSpinButton* dsb = new DualSpinButton(defalt_value, lo, hi, step_inc, climb, digits, attr, tip1, tip2);
857         add_widget(dsb, label);
858         add_attr_widget(dsb);
859         return dsb;
860     }
862     // MultiSpinButton
863     MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
864                                          const Glib::ustring& label, const double lo, const double hi,
865                                          const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
866     {
867         std::vector<SPAttributeEnum> attrs;
868         attrs.push_back(attr1);
869         attrs.push_back(attr2);
871         std::vector<double> default_values;
872         default_values.push_back(def1);
873         default_values.push_back(def2);
875         std::vector<char*> tips;
876         tips.push_back(tip1);
877         tips.push_back(tip2);
879         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
880         add_widget(msb, label);
881         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
882             add_attr_widget(msb->get_spinbuttons()[i]);
883         return msb;
884     }
885     MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
886                                          const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
887                                          const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
888     {
889         std::vector<SPAttributeEnum> attrs;
890         attrs.push_back(attr1);
891         attrs.push_back(attr2);
892         attrs.push_back(attr3);
894         std::vector<double> default_values;
895         default_values.push_back(def1);
896         default_values.push_back(def2);
897         default_values.push_back(def3);
899         std::vector<char*> tips;
900         tips.push_back(tip1);
901         tips.push_back(tip2);
902         tips.push_back(tip3);
904         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
905         add_widget(msb, label);
906         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
907             add_attr_widget(msb->get_spinbuttons()[i]);
908         return msb;
909     }
911     // FileOrElementChooser
912     FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
913     {
914         FileOrElementChooser* foech = new FileOrElementChooser(attr);
915         foech->set_desktop(_dialog.getDesktop());
916         add_widget(foech, label);
917         add_attr_widget(foech);
918         return foech;
919     }
921     // ComboBoxEnum
922     template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
923                                   const Glib::ustring& label,
924                                   const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
925     {
926         ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
927         add_widget(combo, label);
928         add_attr_widget(combo->get_attrwidget());
929         return combo->get_attrwidget();
930     }
931 private:
932     Gtk::Tooltips _tt;
934     void add_attr_widget(AttrWidget* a)
935     {
936         _attrwidgets[_current_type].push_back(a);
937         a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
938     }
940     /* Adds a new settings widget using the specified label. The label will be formatted with a colon
941        and all widgets within the setting group are aligned automatically. */
942     void add_widget(Gtk::Widget* w, const Glib::ustring& label)
943     {
944         Gtk::Label *lbl = 0;
945         Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
946         hb->set_spacing(12);
948         if(label != "") {
949             lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
950             hb->pack_start(*lbl, false, false);
951             _size_group->add_widget(*lbl);
952             lbl->show();
953         }
955         hb->pack_start(*w);
956         _groups[_current_type]->pack_start(*hb);
957         hb->show();
958         w->show();
959     }
961     std::vector<Gtk::VBox*> _groups;
962     Glib::RefPtr<Gtk::SizeGroup> _size_group;
963     FilterEffectsDialog& _dialog;
964     SetAttrSlot _set_attr_slot;
965     std::vector<std::vector< AttrWidget*> > _attrwidgets;
966     int _current_type, _max_types;
967 };
969 // Settings for the three light source objects
970 class FilterEffectsDialog::LightSourceControl : public AttrWidget
972 public:
973     LightSourceControl(FilterEffectsDialog& d)
974         : AttrWidget(SP_ATTR_INVALID),
975           _dialog(d),
976           _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
977           _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
978           _light_source(LightSourceConverter),
979           _locked(false)
980     {
981         _light_box.pack_start(_light_label, false, false);
982         _light_box.pack_start(_light_source);
983         _light_box.show_all();
984         _light_box.set_spacing(12);
985         _dialog._sizegroup->add_widget(_light_label);
987         _box.add(_light_box);
988         _box.reorder_child(_light_box, 0);
989         _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
991         // FIXME: these range values are complete crap
993         _settings.type(LIGHT_DISTANT);
994         _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"));
995         _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"));
997         _settings.type(LIGHT_POINT);
998         _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"));
1000         _settings.type(LIGHT_SPOT);
1001         _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"));
1002         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
1003                                       SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
1004                                       _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
1005         _settings.add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
1006         //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.
1007         _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."));
1008     }
1010     Gtk::VBox& get_box()
1011     {
1012         return _box;
1013     }
1014 protected:
1015     Glib::ustring get_as_attribute() const
1016     {
1017         return "";
1018     }
1019     void set_from_attribute(SPObject* o)
1020     {
1021         if(_locked)
1022             return;
1024         _locked = true;
1026         SPObject* child = o->children;
1028         if(SP_IS_FEDISTANTLIGHT(child))
1029             _light_source.set_active(0);
1030         else if(SP_IS_FEPOINTLIGHT(child))
1031             _light_source.set_active(1);
1032         else if(SP_IS_FESPOTLIGHT(child))
1033             _light_source.set_active(2);
1034         else
1035             _light_source.set_active(-1);
1037         update();
1039         _locked = false;
1040     }
1041 private:
1042     void on_source_changed()
1043     {
1044         if(_locked)
1045             return;
1047         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1048         if(prim) {
1049             _locked = true;
1051             SPObject* child = prim->children;
1052             const int ls = _light_source.get_active_row_number();
1053             // Check if the light source type has changed
1054             if(!(ls == -1 && !child) &&
1055                !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1056                !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1057                !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1058                 if(child)
1059                     sp_repr_unparent(child->repr);
1061                 if(ls != -1) {
1062                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1063                     Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1064                     prim->repr->appendChild(repr);
1065                 }
1067                 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1068                 update();
1069             }
1071             _locked = false;
1072         }
1073     }
1075     void update()
1076     {
1077         _box.hide_all();
1078         _box.show();
1079         _light_box.show_all();
1081         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1082         if(prim && prim->children)
1083             _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1084     }
1086     FilterEffectsDialog& _dialog;
1087     Gtk::VBox _box;
1088     Settings _settings;
1089     Gtk::HBox _light_box;
1090     Gtk::Label _light_label;
1091     ComboBoxEnum<LightSource> _light_source;
1092     bool _locked;
1093 };
1095 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1097     LightSourceControl* ls = new LightSourceControl(_dialog);
1098     add_attr_widget(ls);
1099     add_widget(&ls->get_box(), "");
1100     return ls;
1103 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1104                                           sigc::slot<void> rem)
1106     Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1108     menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1109     Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1110     menu->append(*mi);
1111     mi->signal_activate().connect(rem);
1112     mi->show();
1113     menu->accelerate(parent);
1115     return menu;
1118 /*** FilterModifier ***/
1119 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1120     : _dialog(d), _add(Gtk::Stock::NEW), _observer(new SignalObserver)
1122     Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1123     pack_start(*sw);
1124     pack_start(_add, false, false);
1125     sw->add(_list);
1127     _model = Gtk::ListStore::create(_columns);
1128     _list.set_model(_model);
1129     _cell_toggle.set_active(true);
1130     const int selcol = _list.append_column("", _cell_toggle);
1131     Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1132     if(col)
1133        col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1134     _list.append_column_editable(_("_Filter"), _columns.label);
1135     ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1136         signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1138     sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1139     sw->set_shadow_type(Gtk::SHADOW_IN);
1140     show_all_children();
1141     _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1142     _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1143     _list.signal_button_release_event().connect_notify(
1144         sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1145     _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1146                               sigc::mem_fun(*this, &FilterModifier::remove_filter));
1147     _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1148                                  _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1149     _menu->accelerate(*this);
1151     _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1152     _observer->signal_changed().connect(signal_filter_changed().make_slot());
1153     g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1154                      G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1156     g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1157                      G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1159     on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1160     update_filters();
1163 FilterEffectsDialog::FilterModifier::~FilterModifier()
1165    _resource_changed.disconnect();
1166    _doc_replaced.disconnect();
1169 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1171     me->_doc_replaced.disconnect();
1172     me->_doc_replaced = desktop->connectDocumentReplaced(
1173         sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1175     me->_resource_changed.disconnect();
1176     me->_resource_changed =
1177         sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1178                                               sigc::mem_fun(me, &FilterModifier::update_filters));
1180     me->_dialog.setDesktop(desktop);
1182     me->update_filters();
1186 // When the selection changes, show the active filter(s) in the dialog
1187 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1188                                                                        Selection *sel,
1189                                                                        FilterModifier* fm)
1191     if(fm && sel)
1192         fm->update_selection(sel);
1195 // Update each filter's sel property based on the current object selection;
1196 //  If the filter is not used by any selected object, sel = 0,
1197 //  otherwise sel is set to the total number of filters in use by selected objects
1198 //  If only one filter is in use, it is selected
1199 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1201     std::set<SPObject*> used;
1203     for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1204         SPObject *obj = SP_OBJECT (i->data);
1205         SPStyle *style = SP_OBJECT_STYLE (obj);
1206         if(!style || !SP_IS_ITEM(obj)) continue;
1208         if(style->filter.set && style->getFilter())
1209             used.insert(style->getFilter());
1210         else
1211             used.insert(0);
1212     }
1214     const int size = used.size();
1216     for(Gtk::TreeIter iter = _model->children().begin();
1217         iter != _model->children().end(); ++iter) {
1218         if(used.find((*iter)[_columns.filter]) != used.end()) {
1219             // If only one filter is in use by the selection, select it
1220             if(size == 1)
1221                 _list.get_selection()->select(iter);
1222             (*iter)[_columns.sel] = size;
1223         }
1224         else
1225             (*iter)[_columns.sel] = 0;
1226     }
1229 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1231     _observer->set(get_selected_filter());
1232     signal_filter_changed()();
1235 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1237     Gtk::TreeModel::iterator iter = _model->get_iter(path);
1239     if(iter) {
1240         SPFilter* filter = (*iter)[_columns.filter];
1241         filter->setLabel(text.c_str());
1242         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1243         if(iter)
1244             (*iter)[_columns.label] = text;
1245     }
1248 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1250     Gtk::TreeIter iter = _model->get_iter(path);
1252     if(iter) {
1253         SPDesktop *desktop = _dialog.getDesktop();
1254         SPDocument *doc = sp_desktop_document(desktop);
1255         SPFilter* filter = (*iter)[_columns.filter];
1256         Inkscape::Selection *sel = sp_desktop_selection(desktop);
1258         /* If this filter is the only one used in the selection, unset it */
1259         if((*iter)[_columns.sel] == 1)
1260             filter = 0;
1262         GSList const *items = sel->itemList();
1264         for (GSList const *i = items; i != NULL; i = i->next) {
1265             SPItem * item = SP_ITEM(i->data);
1266             SPStyle *style = SP_OBJECT_STYLE(item);
1267             g_assert(style != NULL);
1269             if(filter)
1270                 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1271             else
1272                 ::remove_filter(item, false);
1274             SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1275         }
1277         update_selection(sel);
1278         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS,  _("Apply filter"));
1279     }
1282 /* Add all filters in the document to the combobox.
1283    Keeps the same selection if possible, otherwise selects the first element */
1284 void FilterEffectsDialog::FilterModifier::update_filters()
1286     SPDesktop* desktop = _dialog.getDesktop();
1287     SPDocument* document = sp_desktop_document(desktop);
1288     const GSList* filters = sp_document_get_resource_list(document, "filter");
1290     _model->clear();
1292     for(const GSList *l = filters; l; l = l->next) {
1293         Gtk::TreeModel::Row row = *_model->append();
1294         SPFilter* f = (SPFilter*)l->data;
1295         row[_columns.filter] = f;
1296         const gchar* lbl = f->label();
1297         const gchar* id = SP_OBJECT_ID(f);
1298         row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1299     }
1301     update_selection(desktop->selection);
1302     _dialog.update_filter_general_settings_view();
1305 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1307     if(_list.get_selection()) {
1308         Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1310         if(i)
1311             return (*i)[_columns.filter];
1312     }
1314     return 0;
1317 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1319     if(filter) {
1320         for(Gtk::TreeModel::iterator i = _model->children().begin();
1321             i != _model->children().end(); ++i) {
1322             if((*i)[_columns.filter] == filter) {
1323                 _list.get_selection()->select(i);
1324                 break;
1325             }
1326         }
1327     }
1330 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1332     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1333         const bool sensitive = get_selected_filter() != NULL;
1334         _menu->items()[0].set_sensitive(sensitive);
1335         _menu->items()[1].set_sensitive(sensitive);
1336         _menu->popup(event->button, event->time);
1337     }
1340 void FilterEffectsDialog::FilterModifier::add_filter()
1342     SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1343     SPFilter* filter = new_filter(doc);
1345     const int count = _model->children().size();
1346     std::ostringstream os;
1347     os << "filter" << count;
1348     filter->setLabel(os.str().c_str());
1350     update_filters();
1352     select_filter(filter);
1354     sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1357 void FilterEffectsDialog::FilterModifier::remove_filter()
1359     SPFilter *filter = get_selected_filter();
1361     if(filter) {
1362         SPDocument* doc = filter->document;
1363         sp_repr_unparent(filter->repr);
1365         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1367         update_filters();
1368     }
1371 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1373     SPFilter* filter = get_selected_filter();
1375     if(filter) {
1376         Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1377         repr = repr->duplicate(repr->document());
1378         parent->appendChild(repr);
1380         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1382         update_filters();
1383     }
1386 void FilterEffectsDialog::FilterModifier::rename_filter()
1388     _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1391 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1392     : Glib::ObjectBase(typeid(CellRendererConnection)),
1393       _primitive(*this, "primitive", 0)
1394 {}
1396 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1398     return _primitive.get_proxy();
1401 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1403     _text_width = w;
1406 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1408     return _text_width;
1411 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1412     Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1413     int* x_offset, int* y_offset, int* width, int* height) const
1415     PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1417     if(x_offset)
1418         (*x_offset) = 0;
1419     if(y_offset)
1420         (*y_offset) = 0;
1421     if(width)
1422         (*width) = size * primlist.primitive_count() + _text_width * 7;
1423     if(height) {
1424         // Scale the height depending on the number of inputs, unless it's
1425         // the first primitive, in which case there are no connections
1426         SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1427         (*height) = size * input_count(prim);
1428     }
1431 /*** PrimitiveList ***/
1432 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1433     : _dialog(d),
1434       _in_drag(0),
1435       _observer(new SignalObserver)
1437     d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1439     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1440     signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1442     _model = Gtk::ListStore::create(_columns);
1444     set_reorderable(true);
1446     set_model(_model);
1447     append_column(_("_Effect"), _columns.type);
1449     _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1450     get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1451     signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1453     _connection_cell.set_text_width(init_text());
1455     int cols_count = append_column(_("Connections"), _connection_cell);
1456     Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1457     if(col)
1458        col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1461 // Sets up a vertical Pango context/layout, and returns the largest
1462 // width needed to render the FilterPrimitiveInput labels.
1463 int FilterEffectsDialog::PrimitiveList::init_text()
1465     // Set up a vertical context+layout
1466     Glib::RefPtr<Pango::Context> context = create_pango_context();
1467     const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1468     context->set_matrix(matrix);
1469     _vertical_layout = Pango::Layout::create(context);
1471     int maxfont = 0;
1472     for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1473         _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1474         int fontw, fonth;
1475         _vertical_layout->get_pixel_size(fontw, fonth);
1476         if(fonth > maxfont)
1477             maxfont = fonth;
1478     }
1480     return maxfont;
1483 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1485     return _signal_primitive_changed;
1488 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1490     _observer->set(get_selected());
1491     signal_primitive_changed()();
1492     _dialog._color_matrix_values->clear_store();
1495 /* Add all filter primitives in the current to the list.
1496    Keeps the same selection if possible, otherwise selects the first element */
1497 void FilterEffectsDialog::PrimitiveList::update()
1499     SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1500     const SPFilterPrimitive* active_prim = get_selected();
1501     bool active_found = false;
1503     _model->clear();
1505     if(f) {
1506         _dialog._primitive_box.set_sensitive(true);
1507         _dialog.update_filter_general_settings_view();
1508         for(SPObject *prim_obj = f->children;
1509                 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1510                 prim_obj = prim_obj->next) {
1511             SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1512             if(prim) {
1513                 Gtk::TreeModel::Row row = *_model->append();
1514                 row[_columns.primitive] = prim;
1515                 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1516                 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1517                 row[_columns.id] = SP_OBJECT_ID(prim);
1519                 if(prim == active_prim) {
1520                     get_selection()->select(row);
1521                     active_found = true;
1522                 }
1523             }
1524         }
1526         if(!active_found && _model->children().begin())
1527             get_selection()->select(_model->children().begin());
1529         columns_autosize();
1530     }
1531     else {
1532         _dialog._primitive_box.set_sensitive(false);
1533     }
1536 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1538     _primitive_menu = menu;
1541 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1543     if(_dialog._filter_modifier.get_selected_filter()) {
1544         Gtk::TreeModel::iterator i = get_selection()->get_selected();
1545         if(i)
1546             return (*i)[_columns.primitive];
1547     }
1549     return 0;
1552 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1554     for(Gtk::TreeIter i = _model->children().begin();
1555         i != _model->children().end(); ++i) {
1556         if((*i)[_columns.primitive] == prim)
1557             get_selection()->select(i);
1558     }
1561 void FilterEffectsDialog::PrimitiveList::remove_selected()
1563     SPFilterPrimitive* prim = get_selected();
1565     if(prim) {
1566         _observer->set(0);
1568         sp_repr_unparent(prim->repr);
1570         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1571                          _("Remove filter primitive"));
1573         update();
1574     }
1577 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1579     Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1580     Glib::RefPtr<Gdk::Window> win = get_bin_window();
1581     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1583     SPFilterPrimitive* prim = get_selected();
1584     int row_count = get_model()->children().size();
1586     int fheight = CellRendererConnection::size;
1587     Gdk::Rectangle rct, vis;
1588     Gtk::TreeIter row = get_model()->children().begin();
1589     int text_start_x = 0;
1590     if(row) {
1591         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1592         get_visible_rect(vis);
1593         int vis_x, vis_y;
1594         tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1596         text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter._length + 1) + 1;
1597         for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1598             _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1599             const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1600             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());
1601             get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1602             get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1603         }
1604     }
1606     int row_index = 0;
1607     for(; row != get_model()->children().end(); ++row, ++row_index) {
1608         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1609         const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1611         // Check mouse state
1612         int mx, my;
1613         Gdk::ModifierType mask;
1614         get_bin_window()->get_pointer(mx, my, mask);
1616         // Outline the bottom of the connection area
1617         const int outline_x = x + fheight * (row_count - row_index);
1618         get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1620         // Side outline
1621         get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1623         std::vector<Gdk::Point> con_poly;
1624         int con_drag_y = 0;
1625         bool inside;
1626         const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1627         const int inputs = input_count(row_prim);
1629         if(SP_IS_FEMERGE(row_prim)) {
1630             for(int i = 0; i < inputs; ++i) {
1631                 inside = do_connection_node(row, i, con_poly, mx, my);
1632                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1633                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1634                                                inside, con_poly);
1636                 if(_in_drag == (i + 1))
1637                     con_drag_y = con_poly[2].get_y();
1639                 if(_in_drag != (i + 1) || row_prim != prim)
1640                     draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1641             }
1642         }
1643         else {
1644             // Draw "in" shape
1645             inside = do_connection_node(row, 0, con_poly, mx, my);
1646             con_drag_y = con_poly[2].get_y();
1647             get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1648                                            darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1649                                            inside, con_poly);
1651             // Draw "in" connection
1652             if(_in_drag != 1 || row_prim != prim)
1653                 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1655             if(inputs == 2) {
1656                 // Draw "in2" shape
1657                 inside = do_connection_node(row, 1, con_poly, mx, my);
1658                 if(_in_drag == 2)
1659                     con_drag_y = con_poly[2].get_y();
1660                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1661                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1662                                                inside, con_poly);
1663                 // Draw "in2" connection
1664                 if(_in_drag != 2 || row_prim != prim)
1665                     draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1666             }
1667         }
1669         // Draw drag connection
1670         if(row_prim == prim && _in_drag) {
1671             get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1672                                         mx, con_drag_y);
1673             get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1674         }
1675     }
1677     return true;
1680 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1681                                                          const int text_start_x, const int x1, const int y1,
1682                                                          const int row_count)
1684     int src_id = 0;
1685     Gtk::TreeIter res = find_result(input, attr, src_id);
1686     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1687     Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1688     Glib::RefPtr<Gdk::GC> gc;
1690     const bool is_first = input == get_model()->children().begin();
1691     const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1692     const bool use_default = !res && !is_merge;
1694     if(res == input || (use_default && is_first)) {
1695         // Draw straight connection to a standard input
1696         // Draw a lighter line for an implicit connection to a standard input
1697         const int tw = _connection_cell.get_text_width();
1698         gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1699         gc = (use_default && is_first) ? lightgc : darkgc;
1700         get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1701         get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1702     }
1703     else {
1704         // Draw an 'L'-shaped connection to another filter primitive
1705         // If no connection is specified, draw a light connection to the previous primitive
1706         gc = use_default ? lightgc : darkgc;
1708         if(use_default) {
1709             res = input;
1710             --res;
1711         }
1713         if(res) {
1714             Gdk::Rectangle rct;
1716             get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1717             const int fheight = CellRendererConnection::size;
1719             get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1720             const int row_index = find_index(res);
1721             const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1722             const int y2 = rct.get_y() + rct.get_height();
1724             // Draw a bevelled 'L'-shaped connection
1725             get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1726             get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1727             get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1728         }
1729     }
1732 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1733 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1734                                                             std::vector<Gdk::Point>& points,
1735                                                             const int ix, const int iy)
1737     Gdk::Rectangle rct;
1738     const int icnt = input_count((*row)[_columns.primitive]);
1740     get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1741     const int fheight = CellRendererConnection::size;
1743     get_cell_area(_model->get_path(row), *get_column(1), rct);
1744     const float h = rct.get_height() / icnt;
1746     const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1747     const int con_w = (int)(fheight * 0.35f);
1748     const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1749     points.clear();
1750     points.push_back(Gdk::Point(x, con_y));
1751     points.push_back(Gdk::Point(x, con_y + con_w * 2));
1752     points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1754     return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1757 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1758                                                                     const int attr, int& src_id)
1760     SPFilterPrimitive* prim = (*start)[_columns.primitive];
1761     Gtk::TreeIter target = _model->children().end();
1762     int image = 0;
1764     if(SP_IS_FEMERGE(prim)) {
1765         int c = 0;
1766         bool found = false;
1767         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1768             if(c == attr && SP_IS_FEMERGENODE(o)) {
1769                 image = SP_FEMERGENODE(o)->input;
1770                 found = true;
1771             }
1772         }
1773         if(!found)
1774             return target;
1775     }
1776     else {
1777         if(attr == SP_ATTR_IN)
1778             image = prim->image_in;
1779         else if(attr == SP_ATTR_IN2) {
1780             if(SP_IS_FEBLEND(prim))
1781                 image = SP_FEBLEND(prim)->in2;
1782             else if(SP_IS_FECOMPOSITE(prim))
1783                 image = SP_FECOMPOSITE(prim)->in2;
1784             else if(SP_IS_FEDISPLACEMENTMAP(prim))
1785                 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1786             else
1787                 return target;
1788         }
1789         else
1790             return target;
1791     }
1793     if(image >= 0) {
1794         for(Gtk::TreeIter i = _model->children().begin();
1795             i != start; ++i) {
1796             if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1797                 target = i;
1798         }
1799         return target;
1800     }
1801     else if(image < -1) {
1802         src_id = -(image + 2);
1803         return start;
1804     }
1806     return target;
1809 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1811     int i = 0;
1812     for(Gtk::TreeIter iter = _model->children().begin();
1813         iter != target; ++iter, ++i);
1814     return i;
1817 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1819     Gtk::TreePath path;
1820     Gtk::TreeViewColumn* col;
1821     const int x = (int)e->x, y = (int)e->y;
1822     int cx, cy;
1824     _drag_prim = 0;
1826     if(get_path_at_pos(x, y, path, col, cx, cy)) {
1827         Gtk::TreeIter iter = _model->get_iter(path);
1828         std::vector<Gdk::Point> points;
1830         _drag_prim = (*iter)[_columns.primitive];
1831         const int icnt = input_count(_drag_prim);
1833         for(int i = 0; i < icnt; ++i) {
1834             if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1835                 _in_drag = i + 1;
1836                 break;
1837             }
1838         }
1840         queue_draw();
1841     }
1843     if(_in_drag) {
1844         _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1845         _autoscroll = 0;
1846         get_selection()->select(path);
1847         return true;
1848     }
1849     else
1850         return Gtk::TreeView::on_button_press_event(e);
1853 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1855     const int speed = 10;
1856     const int limit = 15;
1858     Gdk::Rectangle vis;
1859     get_visible_rect(vis);
1860     int vis_x, vis_y;
1861     tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1862     const int top = vis_y + vis.get_height();
1864     // When autoscrolling during a connection drag, set the speed based on
1865     // where the mouse is in relation to the edges.
1866     if(e->y < vis_y)
1867         _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1868     else if(e->y < vis_y + limit)
1869         _autoscroll = -speed;
1870     else if(e->y > top)
1871         _autoscroll = (int)(speed + (e->y - top) / 5);
1872     else if(e->y > top - limit)
1873         _autoscroll = speed;
1874     else
1875         _autoscroll = 0;
1877     queue_draw();
1879     return Gtk::TreeView::on_motion_notify_event(e);
1882 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1884     SPFilterPrimitive *prim = get_selected(), *target;
1886     _scroll_connection.disconnect();
1888     if(_in_drag && prim) {
1889         Gtk::TreePath path;
1890         Gtk::TreeViewColumn* col;
1891         int cx, cy;
1893         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1894             const gchar *in_val = 0;
1895             Glib::ustring result;
1896             Gtk::TreeIter target_iter = _model->get_iter(path);
1897             target = (*target_iter)[_columns.primitive];
1898             col = get_column(1);
1900             Gdk::Rectangle rct;
1901             get_cell_area(path, *col, rct);
1902             const int twidth = _connection_cell.get_text_width();
1903             const int sources_x = rct.get_width() - twidth * FPInputConverter._length;
1904             if(cx > sources_x) {
1905                 int src = (cx - sources_x) / twidth;
1906                 if (src < 0) {
1907                     src = 0;
1908                 } else if(src >= static_cast<int>(FPInputConverter._length)) {
1909                     src = FPInputConverter._length - 1;
1910                 }
1911                 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1912                 in_val = result.c_str();
1913             }
1914             else {
1915                 // Ensure that the target comes before the selected primitive
1916                 for(Gtk::TreeIter iter = _model->children().begin();
1917                     iter != get_selection()->get_selected(); ++iter) {
1918                     if(iter == target_iter) {
1919                         Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1920                         // Make sure the target has a result
1921                         const gchar *gres = repr->attribute("result");
1922                         if(!gres) {
1923                             result = sp_filter_get_new_result_name(SP_FILTER(prim->parent));
1924                             repr->setAttribute("result", result.c_str());
1925                             in_val = result.c_str();
1926                         }
1927                         else
1928                             in_val = gres;
1929                         break;
1930                     }
1931                 }
1932             }
1934             if(SP_IS_FEMERGE(prim)) {
1935                 int c = 1;
1936                 bool handled = false;
1937                 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1938                     if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1939                         // If input is null, delete it
1940                         if(!in_val) {
1941                             sp_repr_unparent(o->repr);
1942                             sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1943                                              _("Remove merge node"));
1944                             (*get_selection()->get_selected())[_columns.primitive] = prim;
1945                         }
1946                         else
1947                             _dialog.set_attr(o, SP_ATTR_IN, in_val);
1948                         handled = true;
1949                     }
1950                 }
1951                 // Add new input?
1952                 if(!handled && c == _in_drag && in_val) {
1953                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1954                     Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1955                     repr->setAttribute("inkscape:collect", "always");
1956                     prim->repr->appendChild(repr);
1957                     SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1958                     Inkscape::GC::release(repr);
1959                     _dialog.set_attr(node, SP_ATTR_IN, in_val);
1960                     (*get_selection()->get_selected())[_columns.primitive] = prim;
1961                 }
1962             }
1963             else {
1964                 if(_in_drag == 1)
1965                     _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1966                 else if(_in_drag == 2)
1967                     _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1968             }
1969         }
1971         _in_drag = 0;
1972         queue_draw();
1974         _dialog.update_settings_view();
1975     }
1977     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1978         const bool sensitive = get_selected() != NULL;
1979         _primitive_menu->items()[0].set_sensitive(sensitive);
1980         _primitive_menu->items()[1].set_sensitive(sensitive);
1981         _primitive_menu->popup(e->button, e->time);
1983         return true;
1984     }
1985     else
1986         return Gtk::TreeView::on_button_release_event(e);
1989 // Checks all of prim's inputs, removes any that use result
1990 void check_single_connection(SPFilterPrimitive* prim, const int result)
1992     if(prim && result >= 0) {
1994         if(prim->image_in == result)
1995             SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1997         if(SP_IS_FEBLEND(prim)) {
1998             if(SP_FEBLEND(prim)->in2 == result)
1999                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2000         }
2001         else if(SP_IS_FECOMPOSITE(prim)) {
2002             if(SP_FECOMPOSITE(prim)->in2 == result)
2003                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2004         }
2005         else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
2006             if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
2007                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2008         }
2009     }
2012 // Remove any connections going to/from prim_iter that forward-reference other primitives
2013 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
2015     SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
2016     bool before = true;
2018     for(Gtk::TreeIter iter = _model->children().begin();
2019         iter != _model->children().end(); ++iter) {
2020         if(iter == prim_iter)
2021             before = false;
2022         else {
2023             SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
2024             if(before)
2025                 check_single_connection(cur_prim, prim->image_out);
2026             else
2027                 check_single_connection(prim, cur_prim->image_out);
2028         }
2029     }
2032 // Reorder the filter primitives to match the list order
2033 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2035     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2036     int ndx = 0;
2038     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2039         iter != _model->children().end(); ++iter, ++ndx) {
2040         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2041         if(prim && prim == _drag_prim) {
2042             SP_OBJECT_REPR(prim)->setPosition(ndx);
2043             break;
2044         }
2045     }
2047     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2048         iter != _model->children().end(); ++iter, ++ndx) {
2049         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2050         if(prim && prim == _drag_prim) {
2051             sanitize_connections(iter);
2052             get_selection()->select(iter);
2053             break;
2054         }
2055     }
2057     filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2059     sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2062 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2063 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2065     if(_autoscroll) {
2066         Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2067         double v;
2069         v = a.get_value() + _autoscroll;
2070         if(v < 0)
2071             v = 0;
2072         if(v > a.get_upper() - a.get_page_size())
2073             v = a.get_upper() - a.get_page_size();
2075         a.set_value(v);
2077         queue_draw();
2078     }
2080     return true;
2083 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2085     return _model->children().size();
2088 /*** FilterEffectsDialog ***/
2090 FilterEffectsDialog::FilterEffectsDialog()
2091     : UI::Widget::Panel("", "dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2092       _add_primitive_type(FPConverter),
2093       _add_primitive(_("Add Effect:")),
2094       _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2095       _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2096       _settings_initialized(false),
2097       _locked(false),
2098       _attr_lock(false),
2099       _filter_modifier(*this),
2100       _primitive_list(*this)
2102     _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2103                              NR_FILTER_ENDPRIMITIVETYPE);
2104     _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2105                              1);
2106     _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2107     _sizegroup->set_ignore_hidden();
2109     _add_primitive_type.remove_row(NR_FILTER_TILE);
2110     _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2112     // Initialize widget hierarchy
2113     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2114     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2115     Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox(/*homogeneous:*/false, /*spacing:*/4));
2116     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2118     _getContents()->add(*hpaned);
2119     hpaned->pack1(_filter_modifier);
2120     hpaned->pack2(_primitive_box);
2121     _primitive_box.pack_start(*sw_prims);
2122     _primitive_box.pack_start(*hb_prims, false, false);
2123     _primitive_box.pack_start(*infobox,false, false);
2124     sw_prims->add(_primitive_list);
2125     infobox->pack_start(_infobox_icon, false, false);
2126     infobox->pack_start(_infobox_desc, false, false);
2127     _infobox_desc.set_line_wrap(true);
2128     _infobox_desc.set_size_request(200, -1);
2130     hb_prims->pack_start(_add_primitive, false, false);
2131     hb_prims->pack_start(_add_primitive_type, false, false);
2132     _getContents()->pack_start(_settings_tabs, false, false);
2133     _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2134     _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2136     _primitive_list.signal_primitive_changed().connect(
2137         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2138     _filter_modifier.signal_filter_changed().connect(
2139         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2141     _add_primitive_type.signal_changed().connect(
2142         sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2144     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2145     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2146 //    al_settings->set_padding(0, 0, 12, 0);
2147 //    fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2148 //    ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2149     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2150     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2151                                                sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2153     show_all_children();
2154     init_settings_widgets();
2155     _primitive_list.update();
2156     update_primitive_infobox();
2159 FilterEffectsDialog::~FilterEffectsDialog()
2161     delete _settings;
2162     delete _filter_general_settings;
2165 void FilterEffectsDialog::set_attrs_locked(const bool l)
2167     _locked = l;
2170 void FilterEffectsDialog::show_all_vfunc()
2172     UI::Widget::Panel::show_all_vfunc();
2174     update_settings_view();
2177 void FilterEffectsDialog::init_settings_widgets()
2179     // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2180     //       most of the current values are complete guesses!
2182     _empty_settings.set_sensitive(false);
2183     _settings_tab1.pack_start(_empty_settings);
2185     _no_filter_selected.set_sensitive(false);
2186     _settings_tab2.pack_start(_no_filter_selected);
2187     _settings_initialized = true;
2189     _filter_general_settings->type(0);
2190     _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"));
2191     _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"));
2193     _settings->type(NR_FILTER_BLEND);
2194     _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode"), BlendModeConverter);
2196     _settings->type(NR_FILTER_COLORMATRIX);
2197     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."));
2198     _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s)"));
2199     colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2201     _settings->type(NR_FILTER_COMPONENTTRANSFER);
2202     _settings->add_notimplemented();
2203     //TRANSLATORS: for info on "Slope" and "Intercept", see http://id.mind.net/~zona/mmts/functionInstitute/linearFunctions/lsif.html
2204     /*_settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2205     _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2206     _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2207     _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2208     _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2209     _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2211     _settings->type(NR_FILTER_COMPOSITE);
2212     _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
2213     _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."));
2214     _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."));
2215     _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."));
2216     _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."));
2218     _settings->type(NR_FILTER_CONVOLVEMATRIX);
2219     _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"));
2220     _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."));
2221     //TRANSLATORS: for info on "Kernel", see http://en.wikipedia.org/wiki/Kernel_(matrix)
2222     _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."));
2223     _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2224     //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.
2225     _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."));
2226     _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."));
2227     _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."));
2228     _settings->add_checkbutton(false, SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive."));
2230     _settings->type(NR_FILTER_DIFFUSELIGHTING);
2231     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Diffuse Color"), _("Defines the color of the light source"));
2232     _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"));
2233     _settings->add_spinslider(1, SP_ATTR_DIFFUSECONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2234     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2235     _settings->add_lightsource();
2237     _settings->type(NR_FILTER_DISPLACEMENTMAP);
2238     _settings->add_spinslider(0, SP_ATTR_SCALE, _("Scale"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2239     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2240     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2242     _settings->type(NR_FILTER_FLOOD);
2243     _settings->add_color(/*default: black*/ 0, SP_PROP_FLOOD_COLOR, _("Flood Color"), _("The whole filter region will be filled with this color."));
2244     _settings->add_spinslider(1, SP_PROP_FLOOD_OPACITY, _("Opacity"), 0, 1, 0.1, 0.01, 2);
2246     _settings->type(NR_FILTER_GAUSSIANBLUR);
2247     _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2249     _settings->type(NR_FILTER_MERGE);
2250     _settings->add_no_params();
2252     _settings->type(NR_FILTER_MORPHOLOGY);
2253     _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2254     _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius"), 0, 100, 1, 0.01, 1);
2256     _settings->type(NR_FILTER_IMAGE);
2257     _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image"));
2259     _settings->type(NR_FILTER_OFFSET);
2260     _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"));
2261     _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"));
2263     _settings->type(NR_FILTER_SPECULARLIGHTING);
2264     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Specular Color"), _("Defines the color of the light source"));
2265     _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"));
2266     _settings->add_spinslider(1, SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2267     _settings->add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2268     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2269     _settings->add_lightsource();
2271     _settings->type(NR_FILTER_TILE);
2272     _settings->add_notimplemented();
2274     _settings->type(NR_FILTER_TURBULENCE);
2275 //    _settings->add_checkbutton(false, SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2276     _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2277     _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency"), 0, 0.4, 0.001, 0.01, 3);
2278     _settings->add_spinslider(1, SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2279     _settings->add_spinslider(0, SP_ATTR_SEED, _("Seed"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2282 void FilterEffectsDialog::add_primitive()
2284     SPFilter* filter = _filter_modifier.get_selected_filter();
2286     if(filter) {
2287         SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2289         _primitive_list.select(prim);
2291         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2292     }
2295 void FilterEffectsDialog::update_primitive_infobox()
2297     if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2298         _infobox_icon.show();
2299         _infobox_desc.show();
2300     } else {
2301         _infobox_icon.hide();
2302         _infobox_desc.hide();
2303     }
2304     switch(_add_primitive_type.get_active_data()->id){
2305         case(NR::NR_FILTER_BLEND):
2306             _infobox_icon.set(g_strdup_printf("%s/feBlend-icon.png", INKSCAPE_PIXMAPDIR));
2307             _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2308             break;
2309         case(NR::NR_FILTER_COLORMATRIX):
2310             _infobox_icon.set(g_strdup_printf("%s/feColorMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2311             _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."));
2312             break;
2313         case(NR::NR_FILTER_COMPONENTTRANSFER):
2314             _infobox_icon.set(g_strdup_printf("%s/feComponentTransfer-icon.png", INKSCAPE_PIXMAPDIR));
2315             _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."));
2316             break;
2317         case(NR::NR_FILTER_COMPOSITE):
2318             _infobox_icon.set(g_strdup_printf("%s/feComposite-icon.png", INKSCAPE_PIXMAPDIR));
2319             _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."));
2320             break;
2321         case(NR::NR_FILTER_CONVOLVEMATRIX):
2322             _infobox_icon.set(g_strdup_printf("%s/feConvolveMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2323             _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."));
2324             break;
2325         case(NR::NR_FILTER_DIFFUSELIGHTING):
2326             _infobox_icon.set(g_strdup_printf("%s/feDiffuseLighting-icon.png", INKSCAPE_PIXMAPDIR));
2327             _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."));
2328             break;
2329         case(NR::NR_FILTER_DISPLACEMENTMAP):
2330             _infobox_icon.set(g_strdup_printf("%s/feDisplacementMap-icon.png", INKSCAPE_PIXMAPDIR));
2331             _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."));
2332             break;
2333         case(NR::NR_FILTER_FLOOD):
2334             _infobox_icon.set(g_strdup_printf("%s/feFlood-icon.png", INKSCAPE_PIXMAPDIR));
2335             _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."));
2336             break;
2337         case(NR::NR_FILTER_GAUSSIANBLUR):
2338             _infobox_icon.set(g_strdup_printf("%s/feGaussianBlur-icon.png", INKSCAPE_PIXMAPDIR));
2339             _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."));
2340             break;
2341         case(NR::NR_FILTER_IMAGE):
2342             _infobox_icon.set(g_strdup_printf("%s/feImage-icon.png", INKSCAPE_PIXMAPDIR));
2343             _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2344             break;
2345         case(NR::NR_FILTER_MERGE):
2346             _infobox_icon.set(g_strdup_printf("%s/feMerge-icon.png", INKSCAPE_PIXMAPDIR));
2347             _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."));
2348             break;
2349         case(NR::NR_FILTER_MORPHOLOGY):
2350             _infobox_icon.set(g_strdup_printf("%s/feMorphology-icon.png", INKSCAPE_PIXMAPDIR));
2351             _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."));
2352             break;
2353         case(NR::NR_FILTER_OFFSET):
2354             _infobox_icon.set(g_strdup_printf("%s/feOffset-icon.png", INKSCAPE_PIXMAPDIR));
2355             _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."));
2356             break;
2357         case(NR::NR_FILTER_SPECULARLIGHTING):
2358             _infobox_icon.set(g_strdup_printf("%s/feSpecularLighting-icon.png", INKSCAPE_PIXMAPDIR));
2359             _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."));
2360             break;
2361         case(NR::NR_FILTER_TILE):
2362             _infobox_icon.set(g_strdup_printf("%s/feTile-icon.png", INKSCAPE_PIXMAPDIR));
2363             _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2364             break;
2365         case(NR::NR_FILTER_TURBULENCE):
2366             _infobox_icon.set(g_strdup_printf("%s/feTurbulence-icon.png", INKSCAPE_PIXMAPDIR));
2367             _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."));
2368             break;
2369         default:
2370             g_assert(false);
2371             break;
2372     }
2375 void FilterEffectsDialog::duplicate_primitive()
2377     SPFilter* filter = _filter_modifier.get_selected_filter();
2378     SPFilterPrimitive* origprim = _primitive_list.get_selected();
2380     if(filter && origprim) {
2381         Inkscape::XML::Node *repr;
2382         repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2383         SP_OBJECT_REPR(filter)->appendChild(repr);
2385         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2387         _primitive_list.update();
2388     }
2391 void FilterEffectsDialog::convolve_order_changed()
2393     _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2394     _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2395     _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2398 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2400     set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2403 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2405     if(!_locked) {
2406         _attr_lock = true;
2407         SPFilter *filter = _filter_modifier.get_selected_filter();
2408         const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2409         if (filter && name && SP_OBJECT_REPR(filter)){
2410             SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2411             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2412         }
2413         _attr_lock = false;
2414     }
2417 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2419     set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2422 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2424     if(!_locked) {
2425         _attr_lock = true;
2427         SPFilter *filter = _filter_modifier.get_selected_filter();
2428         const gchar* name = (const gchar*)sp_attribute_name(attr);
2429         if(filter && name && o) {
2430             update_settings_sensitivity();
2432             SP_OBJECT_REPR(o)->setAttribute(name, val);
2433             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2435             Glib::ustring undokey = "filtereffects:";
2436             undokey += name;
2437             sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2438                                    _("Set filter primitive attribute"));
2439         }
2441         _attr_lock = false;
2442     }
2445 void FilterEffectsDialog::update_filter_general_settings_view()
2447     if(_settings_initialized != true) return;
2449     if(!_locked) {
2450         _attr_lock = true;
2452         SPFilter* filter = _filter_modifier.get_selected_filter();
2454         if(filter) {
2455             _filter_general_settings->show_and_update(0, filter);
2456             _no_filter_selected.hide();
2457         }
2458         else {
2459             std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2460             vect[0]->hide_all();
2461             _no_filter_selected.show();
2462         }
2464         _attr_lock = false;
2465     }
2468 void FilterEffectsDialog::update_settings_view()
2470     update_settings_sensitivity();
2472     if(_attr_lock)
2473         return;
2475 //First Tab
2477     std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2478     for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2479     _empty_settings.show();
2481     if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2482         _infobox_icon.show();
2483         _infobox_desc.show();
2484     } else {
2485         _infobox_icon.hide();
2486         _infobox_desc.hide();
2487     }
2489     SPFilterPrimitive* prim = _primitive_list.get_selected();
2491     if(prim) {
2492         _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2493         _empty_settings.hide();
2494     }
2496 //Second Tab
2498     std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2499     vect2[0]->hide_all();
2500     _no_filter_selected.show();
2502     SPFilter* filter = _filter_modifier.get_selected_filter();
2504     if(filter) {
2505         _filter_general_settings->show_and_update(0, filter);
2506         _no_filter_selected.hide();
2507     }
2511 void FilterEffectsDialog::update_settings_sensitivity()
2513     SPFilterPrimitive* prim = _primitive_list.get_selected();
2514     const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2515     _k1->set_sensitive(use_k);
2516     _k2->set_sensitive(use_k);
2517     _k3->set_sensitive(use_k);
2518     _k4->set_sensitive(use_k);
2520 // Component transfer not yet implemented
2521 /*
2522     if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2523         SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2524         const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2525         const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2527         _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2528         _ct_slope->set_sensitive(linear);
2529         _ct_intercept->set_sensitive(linear);
2530         _ct_amplitude->set_sensitive(gamma);
2531         _ct_exponent->set_sensitive(gamma);
2532         _ct_offset->set_sensitive(gamma);
2533     }
2534 */
2537 void FilterEffectsDialog::update_color_matrix()
2539     _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2542 } // namespace Dialog
2543 } // namespace UI
2544 } // namespace Inkscape
2546 /*
2547   Local Variables:
2548   mode:c++
2549   c-file-style:"stroustrup"
2550   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2551   indent-tabs-mode:nil
2552   fill-column:99
2553   End:
2554 */
2555 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :