Code

reduced range of basefrequency attribute in the feturbulence settings UI
[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) _tt.set_tip(*this, tip_text);
213         combo = new ComboBoxEnum<T>(default_value, c, a);
214         add(*combo);
215         show_all();
216     }
217     
218     ~ComboWithTooltip()
219     {
220         delete combo;
221     }
222     
223     ComboBoxEnum<T>* get_attrwidget()
224     {
225         return combo;
226     }
227 private:
228     Gtk::Tooltips _tt;
229     ComboBoxEnum<T>* combo;
230 };
232 // Contains an arbitrary number of spin buttons that use seperate attributes
233 class MultiSpinButton : public Gtk::HBox
235 public:
236     MultiSpinButton(double lower, double upper, double step_inc,
237                     double climb_rate, int digits, std::vector<SPAttributeEnum> attrs, std::vector<double> default_values, std::vector<char*> tip_text)
238     {
239         g_assert(attrs.size()==default_values.size());
240         g_assert(attrs.size()==tip_text.size());
241         for(unsigned i = 0; i < attrs.size(); ++i) {
242             _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i], default_values[i], tip_text[i]));
243             pack_start(*_spins.back(), false, false);
244         }
245     }
247     ~MultiSpinButton()
248     {
249         for(unsigned i = 0; i < _spins.size(); ++i)
250             delete _spins[i];
251     }
253     std::vector<SpinButtonAttr*>& get_spinbuttons()
254     {
255         return _spins;
256     }
257 private:
258     std::vector<SpinButtonAttr*> _spins;
259 };
261 // Contains two spinbuttons that describe a NumberOptNumber
262 class DualSpinButton : public Gtk::HBox, public AttrWidget
264 public:
265     DualSpinButton(char* def, double lower, double upper, double step_inc,
266                    double climb_rate, int digits, const SPAttributeEnum a, char* tt1, char* tt2)
267         : AttrWidget(a, def), //TO-DO: receive default num-opt-num as parameter in the constructor
268           _s1(climb_rate, digits), _s2(climb_rate, digits)
269     {
270         if (tt1) _tt.set_tip(_s1, tt1);
271         if (tt2) _tt.set_tip(_s2, tt2);
272         _s1.set_range(lower, upper);
273         _s2.set_range(lower, upper);
274         _s1.set_increments(step_inc, step_inc * 5);
275         _s2.set_increments(step_inc, step_inc * 5);
277         _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
278         _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
280         pack_start(_s1, false, false);
281         pack_start(_s2, false, false);
282     }
284     Gtk::SpinButton& get_spinbutton1()
285     {
286         return _s1;
287     }
289     Gtk::SpinButton& get_spinbutton2()
290     {
291         return _s2;
292     }
294     virtual Glib::ustring get_as_attribute() const
295     {
296         double v1 = _s1.get_value();
297         double v2 = _s2.get_value();
299         if(_s1.get_digits() == 0) {
300             v1 = (int)v1;
301             v2 = (int)v2;
302         }
304         return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
305     }
307     virtual void set_from_attribute(SPObject* o)
308     {
309         const gchar* val = attribute_value(o);
310         NumberOptNumber n;
311         if(val) {
312             n.set(val);
313         } else {
314             n.set(get_default()->as_charptr());
315         }
316         _s1.set_value(n.getNumber());
317         _s2.set_value(n.getOptNumber());
318         
319     }
320 private:
321     Gtk::SpinButton _s1, _s2;
322 };
324 class ColorButton : public Gtk::ColorButton, public AttrWidget
326 public:
327     ColorButton(unsigned int def, const SPAttributeEnum a, char* tip_text)
328         : AttrWidget(a, def)
329     {
330         signal_color_set().connect(signal_attr_changed().make_slot());
331         if (tip_text) _tt.set_tip(*this, tip_text);
333         Gdk::Color col;
334         col.set_rgb(65535, 65535, 65535);
335         set_color(col);
336     }
338     // Returns the color in 'rgb(r,g,b)' form.
339     Glib::ustring get_as_attribute() const
340     {
341         std::ostringstream os;
342         const Gdk::Color c = get_color();
343         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?
344         os << "rgb(" << r << "," << g << "," << b << ")";
345         return os.str();
346     }
349     void set_from_attribute(SPObject* o)
350     {
351         const gchar* val = attribute_value(o);
352         guint32 i = 0;
353         if(val) {
354             i = sp_svg_read_color(val, 0xFFFFFFFF);
355         } else {
356             i = (guint32) get_default()->as_uint();
357         }
358         const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
359         Gdk::Color col;
360         col.set_rgb(r * 256, g * 256, b * 256);
361         set_color(col);
362     }
363 };
365 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
366 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
368 public:
369     MatrixAttr(const SPAttributeEnum a, char* tip_text = NULL)
370         : AttrWidget(a), _locked(false)
371     {
372         _model = Gtk::ListStore::create(_columns);
373         _tree.set_model(_model);
374         _tree.set_headers_visible(false);
375         _tree.show();
376         add(_tree);
377         set_shadow_type(Gtk::SHADOW_IN);
378         if (tip_text) _tt.set_tip(_tree, tip_text);
379     }
381     std::vector<double> get_values() const
382     {
383         std::vector<double> vec;
384         for(Gtk::TreeIter iter = _model->children().begin();
385             iter != _model->children().end(); ++iter) {
386             for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
387                 vec.push_back((*iter)[_columns.cols[c]]);
388         }
389         return vec;
390     }
392     void set_values(const std::vector<double>& v)
393     {
394         unsigned i = 0;
395         for(Gtk::TreeIter iter = _model->children().begin();
396             iter != _model->children().end(); ++iter) {
397             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
398                 if(i >= v.size())
399                     return;
400                 (*iter)[_columns.cols[c]] = v[i];
401                 ++i;
402             }
403         }
404     }
406     Glib::ustring get_as_attribute() const
407     {
408         std::ostringstream os;
410         for(Gtk::TreeIter iter = _model->children().begin();
411             iter != _model->children().end(); ++iter) {
412             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
413                 os << (*iter)[_columns.cols[c]] << " ";
414             }
415         }
417         return os.str();
418     }
420     void set_from_attribute(SPObject* o)
421     {
422         if(o) {
423             if(SP_IS_FECONVOLVEMATRIX(o)) {
424                 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
425                 int cols, rows;
426                 cols = (int)conv->order.getNumber();
427                 if(cols > 5)
428                     cols = 5;
429                 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
430                 update(o, rows, cols);
431             }
432             else if(SP_IS_FECOLORMATRIX(o))
433                 update(o, 4, 5);
434         }
435     }
436 private:
437     class MatrixColumns : public Gtk::TreeModel::ColumnRecord
438     {
439     public:
440         MatrixColumns()
441         {
442             cols.resize(5);
443             for(unsigned i = 0; i < cols.size(); ++i)
444                 add(cols[i]);
445         }
446         std::vector<Gtk::TreeModelColumn<double> > cols;
447     };
449     void update(SPObject* o, const int rows, const int cols)
450     {
451         if(_locked)
452             return;
454         _model->clear();
456         _tree.remove_all_columns();
458         std::vector<gdouble>* values = NULL;
459         if(SP_IS_FECOLORMATRIX(o))
460             values = &SP_FECOLORMATRIX(o)->values;
461         else if(SP_IS_FECONVOLVEMATRIX(o))
462             values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
463         else
464             return;
466         if(o) {
467             int ndx = 0;
469             for(int i = 0; i < cols; ++i) {
470                 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
471                 dynamic_cast<Gtk::CellRendererText*>(
472                     _tree.get_column_cell_renderer(i))->signal_edited().connect(
473                         sigc::mem_fun(*this, &MatrixAttr::rebind));
474             }
476             for(int r = 0; r < rows; ++r) {
477                 Gtk::TreeRow row = *(_model->append());
478                 // Default to identity matrix
479                 for(int c = 0; c < cols; ++c, ++ndx)
480                     row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
481             }
482         }
483     }
485     void rebind(const Glib::ustring&, const Glib::ustring&)
486     {
487         _locked = true;
488         signal_attr_changed()();
489         _locked = false;
490     }
492     bool _locked;
493     Gtk::TreeView _tree;
494     Glib::RefPtr<Gtk::ListStore> _model;
495     MatrixColumns _columns;
496 };
498 // Displays a matrix or a slider for feColorMatrix
499 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
501 public:
502     ColorMatrixValues()
503         : AttrWidget(SP_ATTR_VALUES),
504           _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.")),
505           _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
506           _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
507           _label(_("None"), Gtk::ALIGN_LEFT),
508           _use_stored(false),
509           _saturation_store(0),
510           _angle_store(0)
511     {
512         _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
513         _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
514         _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
515         signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
517         _matrix.show();
518         _saturation.show();
519         _angle.show();
520         _label.show();
521         _label.set_sensitive(false);
523         set_shadow_type(Gtk::SHADOW_NONE);
524     }
526     virtual void set_from_attribute(SPObject* o)
527     {
528         if(SP_IS_FECOLORMATRIX(o)) {
529             SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
530             remove();
531             switch(col->type) {
532                 case COLORMATRIX_SATURATE:
533                     add(_saturation);
534                     if(_use_stored)
535                         _saturation.set_value(_saturation_store);
536                     else
537                         _saturation.set_from_attribute(o);
538                     break;
539                 case COLORMATRIX_HUEROTATE:
540                     add(_angle);
541                     if(_use_stored)
542                         _angle.set_value(_angle_store);
543                     else
544                         _angle.set_from_attribute(o);
545                     break;
546                 case COLORMATRIX_LUMINANCETOALPHA:
547                     add(_label);
548                     break;
549                 case COLORMATRIX_MATRIX:
550                 default:
551                     add(_matrix);
552                     if(_use_stored)
553                         _matrix.set_values(_matrix_store);
554                     else
555                         _matrix.set_from_attribute(o);
556                     break;
557             }
558             _use_stored = true;
559         }
560     }
562     virtual Glib::ustring get_as_attribute() const
563     {
564         const Widget* w = get_child();
565         if(w == &_label)
566             return "";
567         else
568             return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
569     }
571     void clear_store()
572     {
573         _use_stored = false;
574     }
575 private:
576     void update_store()
577     {
578         const Widget* w = get_child();
579         if(w == &_matrix)
580             _matrix_store = _matrix.get_values();
581         else if(w == &_saturation)
582             _saturation_store = _saturation.get_value();
583         else if(w == &_angle)
584             _angle_store = _angle.get_value();
585     }
587     MatrixAttr _matrix;
588     SpinSlider _saturation;
589     SpinSlider _angle;
590     Gtk::Label _label;
592     // Store separate values for the different color modes
593     bool _use_stored;
594     std::vector<double> _matrix_store;
595     double _saturation_store;
596     double _angle_store;
597 };
599 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
601 //Displays a chooser for feImage input
602 //It may be a filename or the id for an SVG Element
603 //described in xlink:href syntax
604 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
606 public:
607     FileOrElementChooser(const SPAttributeEnum a)
608         : AttrWidget(a)
609     {
610         pack_start(_entry, false, false);
611         pack_start(_fromFile, false, false);
612         //pack_start(_fromSVGElement, false, false);
614         _fromFile.set_label(_("Image File"));
615         _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
617         _fromSVGElement.set_label(_("Selected SVG Element"));
618         _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
620         _entry.signal_changed().connect(signal_attr_changed().make_slot());
622         show_all();
624     }
626     // Returns the element in xlink:href form.
627     Glib::ustring get_as_attribute() const
628     {
629         return _entry.get_text();
630     }
633     void set_from_attribute(SPObject* o)
634     {
635         const gchar* val = attribute_value(o);
636         if(val) {
637             _entry.set_text(val);
638         } else {
639             _entry.set_text("");
640         }
641     }
643     void set_desktop(SPDesktop* d){
644         _desktop = d;
645     }
647 private:
648     void select_svg_element(){
649         Inkscape::Selection* sel = sp_desktop_selection(_desktop);
650         if (sel->isEmpty()) return;
651         Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
652         if (!node || !node->matchAttributeName("id")) return;
654         std::ostringstream xlikhref;
655         xlikhref << "#" << node->attribute("id");
656         _entry.set_text(xlikhref.str());
657     }
659     void select_file(){
661         //# Get the current directory for finding files
662         Glib::ustring open_path;
663         char *attr = (char *)prefs_get_string_attribute("dialogs.open", "path");
664         if (attr)
665             open_path = attr;
667         //# Test if the open_path directory exists
668         if (!Inkscape::IO::file_test(open_path.c_str(),
669                   (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
670             open_path = "";
672         //# If no open path, default to our home directory
673         if (open_path.size() < 1)
674             {
675             open_path = g_get_home_dir();
676             open_path.append(G_DIR_SEPARATOR_S);
677             }
679         //# Create a dialog if we don't already have one
680         if (!selectFeImageFileInstance) {
681             selectFeImageFileInstance =
682                   Inkscape::UI::Dialog::FileOpenDialog::create(
683                      *_desktop->getToplevel(),
684                      open_path,
685                      Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not justy svg*/
686                      (char const *)_("Select an image to be used as feImage input"));
687         }
689         //# Show the dialog
690         bool const success = selectFeImageFileInstance->show();
691         if (!success)
692             return;
694         //# User selected something.  Get name and type
695         Glib::ustring fileName = selectFeImageFileInstance->getFilename();
697         if (fileName.size() > 0) {
699             Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
701             if ( newFileName.size() > 0)
702                 fileName = newFileName;
703             else
704                 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
706             open_path = fileName;
707             open_path.append(G_DIR_SEPARATOR_S);
708             prefs_set_string_attribute("dialogs.open", "path", open_path.c_str());
710             _entry.set_text(fileName);
711         }
712         return;
713     }
715     Gtk::Entry _entry;
716     Gtk::Button _fromFile;
717     Gtk::Button _fromSVGElement;
718     SPDesktop* _desktop;
719 };
721 class FilterEffectsDialog::Settings
723 public:
724     typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
726     Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
727         : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
728     {
729         _groups.resize(_max_types);
730         _attrwidgets.resize(_max_types);
731         _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
733         for(int i = 0; i < _max_types; ++i) {
734             _groups[i] = new Gtk::VBox;
735             b.pack_start(*_groups[i], false, false);
736         }
737         _current_type = 0;
738     }
740     ~Settings()
741     {
742         for(int i = 0; i < _max_types; ++i) {
743             delete _groups[i];
744             for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
745                 delete _attrwidgets[i][j];
746         }
747     }
749     // Show the active settings group and update all the AttrWidgets with new values
750     void show_and_update(const int t, SPObject* ob)
751     {
752         if(t != _current_type) {
753             type(t);
754             for(unsigned i = 0; i < _groups.size(); ++i)
755                 _groups[i]->hide();
756         }
757         if(t >= 0)
758             _groups[t]->show_all();
760         _dialog.set_attrs_locked(true);
761         for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
762             _attrwidgets[_current_type][i]->set_from_attribute(ob);
763         _dialog.set_attrs_locked(false);
764     }
766     int get_current_type() const
767     {
768         return _current_type;
769     }
771     void type(const int t)
772     {
773         _current_type = t;
774     }
776     void add_no_params()
777     {
778         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
779         add_widget(lbl, "");
780     }
782     void add_notimplemented()
783     {
784         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
785         add_widget(lbl, "");
786     }
788     // LightSource
789     LightSourceControl* add_lightsource();
791     // CheckBox
792     CheckButtonAttr* add_checkbutton(bool def, const SPAttributeEnum attr, const Glib::ustring& label,
793                                      const Glib::ustring& tv, const Glib::ustring& fv, char* tip_text = NULL)
794     {
795         CheckButtonAttr* cb = new CheckButtonAttr(def, label, tv, fv, attr, tip_text);
796         add_widget(cb, "");
797         add_attr_widget(cb);
798         return cb;
799     }
801     // ColorButton
802     ColorButton* add_color(unsigned int def, const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text = NULL)
803     {
804         ColorButton* col = new ColorButton(def, attr, tip_text);
805         add_widget(col, label);
806         add_attr_widget(col);
807         return col;
808     }
810     // Matrix
811     MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text)
812     {
813         MatrixAttr* conv = new MatrixAttr(attr, tip_text);
814         add_widget(conv, label);
815         add_attr_widget(conv);
816         return conv;
817     }
819     // ColorMatrixValues
820     ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
821     {
822         ColorMatrixValues* cmv = new ColorMatrixValues();
823         add_widget(cmv, label);
824         add_attr_widget(cmv);
825         return cmv;
826     }
828     // SpinSlider
829     SpinSlider* add_spinslider(double def, const SPAttributeEnum attr, const Glib::ustring& label,
830                          const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
831     {
832         SpinSlider* spinslider = new SpinSlider(def, lo, hi, step_inc, climb, digits, attr, tip_text);
833         add_widget(spinslider, label);
834         add_attr_widget(spinslider);
835         return spinslider;
836     }
838     // DualSpinSlider
839     DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
840                                        const double lo, const double hi, const double step_inc,
841                                        const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
842     {
843         DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
844         add_widget(dss, label);
845         add_attr_widget(dss);
846         return dss;
847     }
849     // DualSpinButton
850     DualSpinButton* add_dualspinbutton(char* defalt_value, const SPAttributeEnum attr, const Glib::ustring& label,
851                                        const double lo, const double hi, const double step_inc,
852                                        const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
853     {
854         DualSpinButton* dsb = new DualSpinButton(defalt_value, lo, hi, step_inc, climb, digits, attr, tip1, tip2);
855         add_widget(dsb, label);
856         add_attr_widget(dsb);
857         return dsb;
858     }
860     // MultiSpinButton
861     MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
862                                          const Glib::ustring& label, const double lo, const double hi,
863                                          const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
864     {
865         std::vector<SPAttributeEnum> attrs;
866         attrs.push_back(attr1);
867         attrs.push_back(attr2);
869         std::vector<double> default_values;
870         default_values.push_back(def1);
871         default_values.push_back(def2);
872         
873         std::vector<char*> tips;
874         tips.push_back(tip1);
875         tips.push_back(tip2);
877         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
878         add_widget(msb, label);
879         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
880             add_attr_widget(msb->get_spinbuttons()[i]);
881         return msb;
882     }
883     MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
884                                          const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
885                                          const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
886     {
887         std::vector<SPAttributeEnum> attrs;
888         attrs.push_back(attr1);
889         attrs.push_back(attr2);
890         attrs.push_back(attr3);
892         std::vector<double> default_values;
893         default_values.push_back(def1);
894         default_values.push_back(def2);
895         default_values.push_back(def3);
897         std::vector<char*> tips;
898         tips.push_back(tip1);
899         tips.push_back(tip2);
900         tips.push_back(tip3);
902         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
903         add_widget(msb, label);
904         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
905             add_attr_widget(msb->get_spinbuttons()[i]);
906         return msb;
907     }
909     // FileOrElementChooser
910     FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
911     {
912         FileOrElementChooser* foech = new FileOrElementChooser(attr);
913         foech->set_desktop(_dialog.getDesktop());
914         add_widget(foech, label);
915         add_attr_widget(foech);
916         return foech;
917     }
919     // ComboBoxEnum
920     template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
921                                   const Glib::ustring& label,
922                                   const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
923     {
924         ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
925         add_widget(combo, label);
926         add_attr_widget(combo->get_attrwidget());
927         return combo->get_attrwidget();
928     }
929 private:
930     Gtk::Tooltips _tt;
932     void add_attr_widget(AttrWidget* a)
933     {
934         _attrwidgets[_current_type].push_back(a);
935         a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
936     }
938     /* Adds a new settings widget using the specified label. The label will be formatted with a colon
939        and all widgets within the setting group are aligned automatically. */
940     void add_widget(Gtk::Widget* w, const Glib::ustring& label)
941     {
942         Gtk::Label *lbl = 0;
943         Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
944         hb->set_spacing(12);
946         if(label != "") {
947             lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
948             hb->pack_start(*lbl, false, false);
949             _size_group->add_widget(*lbl);
950             lbl->show();
951         }
953         hb->pack_start(*w);
954         _groups[_current_type]->pack_start(*hb);
955         hb->show();
956         w->show();
957     }
959     std::vector<Gtk::VBox*> _groups;
960     Glib::RefPtr<Gtk::SizeGroup> _size_group;
961     FilterEffectsDialog& _dialog;
962     SetAttrSlot _set_attr_slot;
963     std::vector<std::vector< AttrWidget*> > _attrwidgets;
964     int _current_type, _max_types;
965 };
967 // Settings for the three light source objects
968 class FilterEffectsDialog::LightSourceControl : public AttrWidget
970 public:
971     LightSourceControl(FilterEffectsDialog& d)
972         : AttrWidget(SP_ATTR_INVALID),
973           _dialog(d),
974           _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
975           _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
976           _light_source(LightSourceConverter),
977           _locked(false)
978     {
979         _light_box.pack_start(_light_label, false, false);
980         _light_box.pack_start(_light_source);
981         _light_box.show_all();
982         _light_box.set_spacing(12);
983         _dialog._sizegroup->add_widget(_light_label);
985         _box.add(_light_box);
986         _box.reorder_child(_light_box, 0);
987         _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
989         // FIXME: these range values are complete crap
991         _settings.type(LIGHT_DISTANT);
992         _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"));
993         _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"));
995         _settings.type(LIGHT_POINT);
996         _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"));
998         _settings.type(LIGHT_SPOT);
999         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0, SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
1000         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
1001                                       SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
1002                                       _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
1003         _settings.add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
1004         //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.
1005         _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."));
1006     }
1008     Gtk::VBox& get_box()
1009     {
1010         return _box;
1011     }
1012 protected:
1013     Glib::ustring get_as_attribute() const
1014     {
1015         return "";
1016     }
1017     void set_from_attribute(SPObject* o)
1018     {
1019         if(_locked)
1020             return;
1022         _locked = true;
1024         SPObject* child = o->children;
1026         if(SP_IS_FEDISTANTLIGHT(child))
1027             _light_source.set_active(0);
1028         else if(SP_IS_FEPOINTLIGHT(child))
1029             _light_source.set_active(1);
1030         else if(SP_IS_FESPOTLIGHT(child))
1031             _light_source.set_active(2);
1032         else
1033             _light_source.set_active(-1);
1035         update();
1037         _locked = false;
1038     }
1039 private:
1040     void on_source_changed()
1041     {
1042         if(_locked)
1043             return;
1045         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1046         if(prim) {
1047             _locked = true;
1049             SPObject* child = prim->children;
1050             const int ls = _light_source.get_active_row_number();
1051             // Check if the light source type has changed
1052             if(!(ls == -1 && !child) &&
1053                !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1054                !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1055                !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1056                 if(child)
1057                     sp_repr_unparent(child->repr);
1059                 if(ls != -1) {
1060                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1061                     Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1062                     prim->repr->appendChild(repr);
1063                 }
1065                 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1066                 update();
1067             }
1069             _locked = false;
1070         }
1071     }
1073     void update()
1074     {
1075         _box.hide_all();
1076         _box.show();
1077         _light_box.show_all();
1079         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1080         if(prim && prim->children)
1081             _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1082     }
1084     FilterEffectsDialog& _dialog;
1085     Gtk::VBox _box;
1086     Settings _settings;
1087     Gtk::HBox _light_box;
1088     Gtk::Label _light_label;
1089     ComboBoxEnum<LightSource> _light_source;
1090     bool _locked;
1091 };
1093 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1095     LightSourceControl* ls = new LightSourceControl(_dialog);
1096     add_attr_widget(ls);
1097     add_widget(&ls->get_box(), "");
1098     return ls;
1101 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1102                                           sigc::slot<void> rem)
1104     Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1106     menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1107     Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1108     menu->append(*mi);
1109     mi->signal_activate().connect(rem);
1110     mi->show();
1111     menu->accelerate(parent);
1113     return menu;
1116 /*** FilterModifier ***/
1117 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1118     : _dialog(d), _add(Gtk::Stock::NEW), _observer(new SignalObserver)
1120     Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1121     pack_start(*sw);
1122     pack_start(_add, false, false);
1123     sw->add(_list);
1125     _model = Gtk::ListStore::create(_columns);
1126     _list.set_model(_model);
1127     _cell_toggle.set_active(true);
1128     const int selcol = _list.append_column("", _cell_toggle);
1129     Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1130     if(col)
1131        col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1132     _list.append_column_editable(_("_Filter"), _columns.label);
1133     ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1134         signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1136     sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1137     sw->set_shadow_type(Gtk::SHADOW_IN);
1138     show_all_children();
1139     _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1140     _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1141     _list.signal_button_release_event().connect_notify(
1142         sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1143     _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1144                               sigc::mem_fun(*this, &FilterModifier::remove_filter));
1145     _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1146                                  _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1147     _menu->accelerate(*this);
1149     _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1150     _observer->signal_changed().connect(signal_filter_changed().make_slot());
1151     g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1152                      G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1154     g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1155                      G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1157     on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1158     update_filters();
1161 FilterEffectsDialog::FilterModifier::~FilterModifier()
1163    _resource_changed.disconnect();
1164    _doc_replaced.disconnect();
1167 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1169     me->_doc_replaced.disconnect();
1170     me->_doc_replaced = desktop->connectDocumentReplaced(
1171         sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1173     me->_resource_changed.disconnect();
1174     me->_resource_changed =
1175         sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1176                                               sigc::mem_fun(me, &FilterModifier::update_filters));
1178     me->_dialog.setDesktop(desktop);
1180     me->update_filters();
1184 // When the selection changes, show the active filter(s) in the dialog
1185 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1186                                                                        Selection *sel,
1187                                                                        FilterModifier* fm)
1189     if(fm && sel)
1190         fm->update_selection(sel);
1193 // Update each filter's sel property based on the current object selection;
1194 //  If the filter is not used by any selected object, sel = 0,
1195 //  otherwise sel is set to the total number of filters in use by selected objects
1196 //  If only one filter is in use, it is selected
1197 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1199     std::set<SPObject*> used;
1201     for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1202         SPObject *obj = SP_OBJECT (i->data);
1203         SPStyle *style = SP_OBJECT_STYLE (obj);
1204         if(!style || !SP_IS_ITEM(obj)) continue;
1206         if(style->filter.set && style->getFilter())
1207             used.insert(style->getFilter());
1208         else
1209             used.insert(0);
1210     }
1212     const int size = used.size();
1214     for(Gtk::TreeIter iter = _model->children().begin();
1215         iter != _model->children().end(); ++iter) {
1216         if(used.find((*iter)[_columns.filter]) != used.end()) {
1217             // If only one filter is in use by the selection, select it
1218             if(size == 1)
1219                 _list.get_selection()->select(iter);
1220             (*iter)[_columns.sel] = size;
1221         }
1222         else
1223             (*iter)[_columns.sel] = 0;
1224     }
1227 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1229     _observer->set(get_selected_filter());
1230     signal_filter_changed()();
1233 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1235     Gtk::TreeModel::iterator iter = _model->get_iter(path);
1237     if(iter) {
1238         SPFilter* filter = (*iter)[_columns.filter];
1239         filter->setLabel(text.c_str());
1240         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1241         if(iter)
1242             (*iter)[_columns.label] = text;
1243     }
1246 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1248     Gtk::TreeIter iter = _model->get_iter(path);
1250     if(iter) {
1251         SPDesktop *desktop = _dialog.getDesktop();
1252         SPDocument *doc = sp_desktop_document(desktop);
1253         SPFilter* filter = (*iter)[_columns.filter];
1254         Inkscape::Selection *sel = sp_desktop_selection(desktop);
1256         /* If this filter is the only one used in the selection, unset it */
1257         if((*iter)[_columns.sel] == 1)
1258             filter = 0;
1260         GSList const *items = sel->itemList();
1262         for (GSList const *i = items; i != NULL; i = i->next) {
1263             SPItem * item = SP_ITEM(i->data);
1264             SPStyle *style = SP_OBJECT_STYLE(item);
1265             g_assert(style != NULL);
1267             if(filter)
1268                 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1269             else
1270                 ::remove_filter(item, false);
1272             SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1273         }
1275         update_selection(sel);
1276         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS,  _("Apply filter"));
1277     }
1280 /* Add all filters in the document to the combobox.
1281    Keeps the same selection if possible, otherwise selects the first element */
1282 void FilterEffectsDialog::FilterModifier::update_filters()
1284     SPDesktop* desktop = _dialog.getDesktop();
1285     SPDocument* document = sp_desktop_document(desktop);
1286     const GSList* filters = sp_document_get_resource_list(document, "filter");
1288     _model->clear();
1290     for(const GSList *l = filters; l; l = l->next) {
1291         Gtk::TreeModel::Row row = *_model->append();
1292         SPFilter* f = (SPFilter*)l->data;
1293         row[_columns.filter] = f;
1294         const gchar* lbl = f->label();
1295         const gchar* id = SP_OBJECT_ID(f);
1296         row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1297     }
1299     update_selection(desktop->selection);
1300     _dialog.update_filter_general_settings_view();
1303 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1305     if(_list.get_selection()) {
1306         Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1308         if(i)
1309             return (*i)[_columns.filter];
1310     }
1312     return 0;
1315 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1317     if(filter) {
1318         for(Gtk::TreeModel::iterator i = _model->children().begin();
1319             i != _model->children().end(); ++i) {
1320             if((*i)[_columns.filter] == filter) {
1321                 _list.get_selection()->select(i);
1322                 break;
1323             }
1324         }
1325     }
1328 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1330     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1331         const bool sensitive = get_selected_filter() != NULL;
1332         _menu->items()[0].set_sensitive(sensitive);
1333         _menu->items()[1].set_sensitive(sensitive);
1334         _menu->popup(event->button, event->time);
1335     }
1338 void FilterEffectsDialog::FilterModifier::add_filter()
1340     SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1341     SPFilter* filter = new_filter(doc);
1343     const int count = _model->children().size();
1344     std::ostringstream os;
1345     os << "filter" << count;
1346     filter->setLabel(os.str().c_str());
1348     update_filters();
1350     select_filter(filter);
1352     sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1355 void FilterEffectsDialog::FilterModifier::remove_filter()
1357     SPFilter *filter = get_selected_filter();
1359     if(filter) {
1360         SPDocument* doc = filter->document;
1361         sp_repr_unparent(filter->repr);
1363         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1365         update_filters();
1366     }
1369 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1371     SPFilter* filter = get_selected_filter();
1373     if(filter) {
1374         Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1375         repr = repr->duplicate(repr->document());
1376         parent->appendChild(repr);
1378         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1380         update_filters();
1381     }
1384 void FilterEffectsDialog::FilterModifier::rename_filter()
1386     _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1389 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1390     : Glib::ObjectBase(typeid(CellRendererConnection)),
1391       _primitive(*this, "primitive", 0)
1392 {}
1394 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1396     return _primitive.get_proxy();
1399 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1401     _text_width = w;
1404 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1406     return _text_width;
1409 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1410     Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1411     int* x_offset, int* y_offset, int* width, int* height) const
1413     PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1415     if(x_offset)
1416         (*x_offset) = 0;
1417     if(y_offset)
1418         (*y_offset) = 0;
1419     if(width)
1420         (*width) = size * primlist.primitive_count() + _text_width * 7;
1421     if(height) {
1422         // Scale the height depending on the number of inputs, unless it's
1423         // the first primitive, in which case there are no connections
1424         SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1425         (*height) = size * input_count(prim);
1426     }
1429 /*** PrimitiveList ***/
1430 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1431     : _dialog(d),
1432       _in_drag(0),
1433       _observer(new SignalObserver)
1435     d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1437     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1438     signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1440     _model = Gtk::ListStore::create(_columns);
1442     set_reorderable(true);
1444     set_model(_model);
1445     append_column(_("_Effect"), _columns.type);
1447     _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1448     get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1449     signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1451     _connection_cell.set_text_width(init_text());
1453     int cols_count = append_column(_("Connections"), _connection_cell);
1454     Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1455     if(col)
1456        col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1459 // Sets up a vertical Pango context/layout, and returns the largest
1460 // width needed to render the FilterPrimitiveInput labels.
1461 int FilterEffectsDialog::PrimitiveList::init_text()
1463     // Set up a vertical context+layout
1464     Glib::RefPtr<Pango::Context> context = create_pango_context();
1465     const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1466     context->set_matrix(matrix);
1467     _vertical_layout = Pango::Layout::create(context);
1469     int maxfont = 0;
1470     for(int i = 0; i < FPInputConverter.end; ++i) {
1471         _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1472         int fontw, fonth;
1473         _vertical_layout->get_pixel_size(fontw, fonth);
1474         if(fonth > maxfont)
1475             maxfont = fonth;
1476     }
1478     return maxfont;
1481 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1483     return _signal_primitive_changed;
1486 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1488     _observer->set(get_selected());
1489     signal_primitive_changed()();
1490     _dialog._color_matrix_values->clear_store();
1493 /* Add all filter primitives in the current to the list.
1494    Keeps the same selection if possible, otherwise selects the first element */
1495 void FilterEffectsDialog::PrimitiveList::update()
1497     SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1498     const SPFilterPrimitive* active_prim = get_selected();
1499     bool active_found = false;
1501     _model->clear();
1503     if(f) {
1504         _dialog._primitive_box.set_sensitive(true);
1505         _dialog.update_filter_general_settings_view();
1506         for(SPObject *prim_obj = f->children;
1507                 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1508                 prim_obj = prim_obj->next) {
1509             SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1510             if(prim) {
1511                 Gtk::TreeModel::Row row = *_model->append();
1512                 row[_columns.primitive] = prim;
1513                 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1514                 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1515                 row[_columns.id] = SP_OBJECT_ID(prim);
1517                 if(prim == active_prim) {
1518                     get_selection()->select(row);
1519                     active_found = true;
1520                 }
1521             }
1522         }
1524         if(!active_found && _model->children().begin())
1525             get_selection()->select(_model->children().begin());
1527         columns_autosize();
1528     }
1529     else {
1530         _dialog._primitive_box.set_sensitive(false);
1531     }
1534 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1536     _primitive_menu = menu;
1539 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1541     if(_dialog._filter_modifier.get_selected_filter()) {
1542         Gtk::TreeModel::iterator i = get_selection()->get_selected();
1543         if(i)
1544             return (*i)[_columns.primitive];
1545     }
1547     return 0;
1550 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1552     for(Gtk::TreeIter i = _model->children().begin();
1553         i != _model->children().end(); ++i) {
1554         if((*i)[_columns.primitive] == prim)
1555             get_selection()->select(i);
1556     }
1559 void FilterEffectsDialog::PrimitiveList::remove_selected()
1561     SPFilterPrimitive* prim = get_selected();
1563     if(prim) {
1564         _observer->set(0);
1566         sp_repr_unparent(prim->repr);
1568         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1569                          _("Remove filter primitive"));
1571         update();
1572     }
1575 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1577     Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1578     Glib::RefPtr<Gdk::Window> win = get_bin_window();
1579     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1581     SPFilterPrimitive* prim = get_selected();
1582     int row_count = get_model()->children().size();
1584     int fheight = CellRendererConnection::size;
1585     Gdk::Rectangle rct, vis;
1586     Gtk::TreeIter row = get_model()->children().begin();
1587     int text_start_x = 0;
1588     if(row) {
1589         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1590         get_visible_rect(vis);
1591         int vis_x, vis_y;
1592         tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1594         text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter.end + 1) + 1;
1595         for(int i = 0; i < FPInputConverter.end; ++i) {
1596             _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1597             const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1598             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());
1599             get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1600             get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1601         }
1602     }
1604     int row_index = 0;
1605     for(; row != get_model()->children().end(); ++row, ++row_index) {
1606         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1607         const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1609         // Check mouse state
1610         int mx, my;
1611         Gdk::ModifierType mask;
1612         get_bin_window()->get_pointer(mx, my, mask);
1614         // Outline the bottom of the connection area
1615         const int outline_x = x + fheight * (row_count - row_index);
1616         get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1618         // Side outline
1619         get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1621         std::vector<Gdk::Point> con_poly;
1622         int con_drag_y = 0;
1623         bool inside;
1624         const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1625         const int inputs = input_count(row_prim);
1627         if(SP_IS_FEMERGE(row_prim)) {
1628             for(int i = 0; i < inputs; ++i) {
1629                 inside = do_connection_node(row, i, con_poly, mx, my);
1630                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1631                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1632                                                inside, con_poly);
1634                 if(_in_drag == (i + 1))
1635                     con_drag_y = con_poly[2].get_y();
1637                 if(_in_drag != (i + 1) || row_prim != prim)
1638                     draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1639             }
1640         }
1641         else {
1642             // Draw "in" shape
1643             inside = do_connection_node(row, 0, con_poly, mx, my);
1644             con_drag_y = con_poly[2].get_y();
1645             get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1646                                            darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1647                                            inside, con_poly);
1649             // Draw "in" connection
1650             if(_in_drag != 1 || row_prim != prim)
1651                 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1653             if(inputs == 2) {
1654                 // Draw "in2" shape
1655                 inside = do_connection_node(row, 1, con_poly, mx, my);
1656                 if(_in_drag == 2)
1657                     con_drag_y = con_poly[2].get_y();
1658                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1659                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1660                                                inside, con_poly);
1661                 // Draw "in2" connection
1662                 if(_in_drag != 2 || row_prim != prim)
1663                     draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1664             }
1665         }
1667         // Draw drag connection
1668         if(row_prim == prim && _in_drag) {
1669             get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1670                                         mx, con_drag_y);
1671             get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1672         }
1673     }
1675     return true;
1678 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1679                                                          const int text_start_x, const int x1, const int y1,
1680                                                          const int row_count)
1682     int src_id = 0;
1683     Gtk::TreeIter res = find_result(input, attr, src_id);
1684     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1685     Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1686     Glib::RefPtr<Gdk::GC> gc;
1688     const bool is_first = input == get_model()->children().begin();
1689     const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1690     const bool use_default = !res && !is_merge;
1692     if(res == input || (use_default && is_first)) {
1693         // Draw straight connection to a standard input
1694         // Draw a lighter line for an implicit connection to a standard input
1695         const int tw = _connection_cell.get_text_width();
1696         gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1697         gc = (use_default && is_first) ? lightgc : darkgc;
1698         get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1699         get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1700     }
1701     else {
1702         // Draw an 'L'-shaped connection to another filter primitive
1703         // If no connection is specified, draw a light connection to the previous primitive
1704         gc = use_default ? lightgc : darkgc;
1706         if(use_default) {
1707             res = input;
1708             --res;
1709         }
1711         if(res) {
1712             Gdk::Rectangle rct;
1714             get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1715             const int fheight = CellRendererConnection::size;
1717             get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1718             const int row_index = find_index(res);
1719             const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1720             const int y2 = rct.get_y() + rct.get_height();
1722             // Draw a bevelled 'L'-shaped connection
1723             get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1724             get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1725             get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1726         }
1727     }
1730 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1731 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1732                                                             std::vector<Gdk::Point>& points,
1733                                                             const int ix, const int iy)
1735     Gdk::Rectangle rct;
1736     const int icnt = input_count((*row)[_columns.primitive]);
1738     get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1739     const int fheight = CellRendererConnection::size;
1741     get_cell_area(_model->get_path(row), *get_column(1), rct);
1742     const float h = rct.get_height() / icnt;
1744     const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1745     const int con_w = (int)(fheight * 0.35f);
1746     const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1747     points.clear();
1748     points.push_back(Gdk::Point(x, con_y));
1749     points.push_back(Gdk::Point(x, con_y + con_w * 2));
1750     points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1752     return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1755 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1756                                                                     const int attr, int& src_id)
1758     SPFilterPrimitive* prim = (*start)[_columns.primitive];
1759     Gtk::TreeIter target = _model->children().end();
1760     int image = 0;
1762     if(SP_IS_FEMERGE(prim)) {
1763         int c = 0;
1764         bool found = false;
1765         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1766             if(c == attr && SP_IS_FEMERGENODE(o)) {
1767                 image = SP_FEMERGENODE(o)->input;
1768                 found = true;
1769             }
1770         }
1771         if(!found)
1772             return target;
1773     }
1774     else {
1775         if(attr == SP_ATTR_IN)
1776             image = prim->image_in;
1777         else if(attr == SP_ATTR_IN2) {
1778             if(SP_IS_FEBLEND(prim))
1779                 image = SP_FEBLEND(prim)->in2;
1780             else if(SP_IS_FECOMPOSITE(prim))
1781                 image = SP_FECOMPOSITE(prim)->in2;
1782             else if(SP_IS_FEDISPLACEMENTMAP(prim))
1783                 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1784             else
1785                 return target;
1786         }
1787         else
1788             return target;
1789     }
1791     if(image >= 0) {
1792         for(Gtk::TreeIter i = _model->children().begin();
1793             i != start; ++i) {
1794             if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1795                 target = i;
1796         }
1797         return target;
1798     }
1799     else if(image < -1) {
1800         src_id = -(image + 2);
1801         return start;
1802     }
1804     return target;
1807 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1809     int i = 0;
1810     for(Gtk::TreeIter iter = _model->children().begin();
1811         iter != target; ++iter, ++i);
1812     return i;
1815 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1817     Gtk::TreePath path;
1818     Gtk::TreeViewColumn* col;
1819     const int x = (int)e->x, y = (int)e->y;
1820     int cx, cy;
1822     _drag_prim = 0;
1824     if(get_path_at_pos(x, y, path, col, cx, cy)) {
1825         Gtk::TreeIter iter = _model->get_iter(path);
1826         std::vector<Gdk::Point> points;
1828         _drag_prim = (*iter)[_columns.primitive];
1829         const int icnt = input_count(_drag_prim);
1831         for(int i = 0; i < icnt; ++i) {
1832             if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1833                 _in_drag = i + 1;
1834                 break;
1835             }
1836         }
1838         queue_draw();
1839     }
1841     if(_in_drag) {
1842         _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1843         _autoscroll = 0;
1844         get_selection()->select(path);
1845         return true;
1846     }
1847     else
1848         return Gtk::TreeView::on_button_press_event(e);
1851 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1853     const int speed = 10;
1854     const int limit = 15;
1856     Gdk::Rectangle vis;
1857     get_visible_rect(vis);
1858     int vis_x, vis_y;
1859     tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1860     const int top = vis_y + vis.get_height();
1862     // When autoscrolling during a connection drag, set the speed based on
1863     // where the mouse is in relation to the edges.
1864     if(e->y < vis_y)
1865         _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1866     else if(e->y < vis_y + limit)
1867         _autoscroll = -speed;
1868     else if(e->y > top)
1869         _autoscroll = (int)(speed + (e->y - top) / 5);
1870     else if(e->y > top - limit)
1871         _autoscroll = speed;
1872     else
1873         _autoscroll = 0;
1875     queue_draw();
1877     return Gtk::TreeView::on_motion_notify_event(e);
1880 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1882     SPFilterPrimitive *prim = get_selected(), *target;
1884     _scroll_connection.disconnect();
1886     if(_in_drag && prim) {
1887         Gtk::TreePath path;
1888         Gtk::TreeViewColumn* col;
1889         int cx, cy;
1891         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1892             const gchar *in_val = 0;
1893             Glib::ustring result;
1894             Gtk::TreeIter target_iter = _model->get_iter(path);
1895             target = (*target_iter)[_columns.primitive];
1896             col = get_column(1);
1898             Gdk::Rectangle rct;
1899             get_cell_area(path, *col, rct);
1900             const int twidth = _connection_cell.get_text_width();
1901             const int sources_x = rct.get_width() - twidth * FPInputConverter.end;
1902             if(cx > sources_x) {
1903                 int src = (cx - sources_x) / twidth;
1904                 if(src < 0)
1905                     src = 0;
1906                 else if(src >= FPInputConverter.end)
1907                     src = FPInputConverter.end - 1;
1908                 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1909                 in_val = result.c_str();
1910             }
1911             else {
1912                 // Ensure that the target comes before the selected primitive
1913                 for(Gtk::TreeIter iter = _model->children().begin();
1914                     iter != get_selection()->get_selected(); ++iter) {
1915                     if(iter == target_iter) {
1916                         Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1917                         // Make sure the target has a result
1918                         const gchar *gres = repr->attribute("result");
1919                         if(!gres) {
1920                             result = "result" + Glib::Ascii::dtostr(SP_FILTER(prim->parent)->_image_number_next);
1921                             repr->setAttribute("result", result.c_str());
1922                             in_val = result.c_str();
1923                         }
1924                         else
1925                             in_val = gres;
1926                         break;
1927                     }
1928                 }
1929             }
1931             if(SP_IS_FEMERGE(prim)) {
1932                 int c = 1;
1933                 bool handled = false;
1934                 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1935                     if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1936                         // If input is null, delete it
1937                         if(!in_val) {
1938                             sp_repr_unparent(o->repr);
1939                             sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1940                                              _("Remove merge node"));
1941                             (*get_selection()->get_selected())[_columns.primitive] = prim;
1942                         }
1943                         else
1944                             _dialog.set_attr(o, SP_ATTR_IN, in_val);
1945                         handled = true;
1946                     }
1947                 }
1948                 // Add new input?
1949                 if(!handled && c == _in_drag && in_val) {
1950                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1951                     Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1952                     repr->setAttribute("inkscape:collect", "always");
1953                     prim->repr->appendChild(repr);
1954                     SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1955                     Inkscape::GC::release(repr);
1956                     _dialog.set_attr(node, SP_ATTR_IN, in_val);
1957                     (*get_selection()->get_selected())[_columns.primitive] = prim;
1958                 }
1959             }
1960             else {
1961                 if(_in_drag == 1)
1962                     _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1963                 else if(_in_drag == 2)
1964                     _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1965             }
1966         }
1968         _in_drag = 0;
1969         queue_draw();
1971         _dialog.update_settings_view();
1972     }
1974     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1975         const bool sensitive = get_selected() != NULL;
1976         _primitive_menu->items()[0].set_sensitive(sensitive);
1977         _primitive_menu->items()[1].set_sensitive(sensitive);
1978         _primitive_menu->popup(e->button, e->time);
1980         return true;
1981     }
1982     else
1983         return Gtk::TreeView::on_button_release_event(e);
1986 // Checks all of prim's inputs, removes any that use result
1987 void check_single_connection(SPFilterPrimitive* prim, const int result)
1989     if(prim && result >= 0) {
1991         if(prim->image_in == result)
1992             SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1994         if(SP_IS_FEBLEND(prim)) {
1995             if(SP_FEBLEND(prim)->in2 == result)
1996                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1997         }
1998         else if(SP_IS_FECOMPOSITE(prim)) {
1999             if(SP_FECOMPOSITE(prim)->in2 == result)
2000                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2001         }
2002         else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
2003             if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
2004                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2005         }
2006     }
2009 // Remove any connections going to/from prim_iter that forward-reference other primitives
2010 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
2012     SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
2013     bool before = true;
2015     for(Gtk::TreeIter iter = _model->children().begin();
2016         iter != _model->children().end(); ++iter) {
2017         if(iter == prim_iter)
2018             before = false;
2019         else {
2020             SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
2021             if(before)
2022                 check_single_connection(cur_prim, prim->image_out);
2023             else
2024                 check_single_connection(prim, cur_prim->image_out);
2025         }
2026     }
2029 // Reorder the filter primitives to match the list order
2030 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2032     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2033     int ndx = 0;
2035     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2036         iter != _model->children().end(); ++iter, ++ndx) {
2037         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2038         if(prim && prim == _drag_prim) {
2039             SP_OBJECT_REPR(prim)->setPosition(ndx);
2040             break;
2041         }
2042     }
2044     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2045         iter != _model->children().end(); ++iter, ++ndx) {
2046         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2047         if(prim && prim == _drag_prim) {
2048             sanitize_connections(iter);
2049             get_selection()->select(iter);
2050             break;
2051         }
2052     }
2054     filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2056     sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2059 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2060 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2062     if(_autoscroll) {
2063         Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2064         double v;
2066         v = a.get_value() + _autoscroll;
2067         if(v < 0)
2068             v = 0;
2069         if(v > a.get_upper() - a.get_page_size())
2070             v = a.get_upper() - a.get_page_size();
2072         a.set_value(v);
2074         queue_draw();
2075     }
2077     return true;
2080 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2082     return _model->children().size();
2085 /*** FilterEffectsDialog ***/
2087 FilterEffectsDialog::FilterEffectsDialog()
2088     : UI::Widget::Panel("", "dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2089       _filter_modifier(*this),
2090       _primitive_list(*this),
2091       _add_primitive_type(FPConverter),
2092       _add_primitive(_("Add Effect:")),
2093       _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2094       _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2095       _settings_initialized(false),
2096       _locked(false),
2097       _attr_lock(false)
2099     _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2100                              NR_FILTER_ENDPRIMITIVETYPE);
2101     _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2102                              1);
2103     _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2104     _sizegroup->set_ignore_hidden();
2106     _add_primitive_type.remove_row(NR_FILTER_TILE);
2107     _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2109     // Initialize widget hierarchy
2110     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2111     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2112     Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox);
2113     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2115     _getContents()->add(*hpaned);
2116     hpaned->pack1(_filter_modifier);
2117     hpaned->pack2(_primitive_box);
2118     _primitive_box.pack_start(*sw_prims);
2119     _primitive_box.pack_start(*infobox,false, false);
2120     _primitive_box.pack_start(*hb_prims, false, false);
2121     sw_prims->add(_primitive_list);
2122     infobox->pack_start(_infobox_icon, false, false);
2123     infobox->pack_start(_infobox_desc, false, false);
2124     _infobox_desc.set_line_wrap(true);
2126     hb_prims->pack_end(_add_primitive_type, false, false);
2127     hb_prims->pack_end(_add_primitive, false, false);
2128     _getContents()->pack_start(_settings_tabs, false, false);
2129     _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2130     _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2132     _primitive_list.signal_primitive_changed().connect(
2133         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2134     _filter_modifier.signal_filter_changed().connect(
2135         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2137     _add_primitive_type.signal_changed().connect(
2138         sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2140     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2141     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2142 //    al_settings->set_padding(0, 0, 12, 0);
2143 //    fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2144 //    ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2145     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2146     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2147                                                sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2149     show_all_children();
2150     init_settings_widgets();
2151     _primitive_list.update();
2152     update_primitive_infobox();
2155 FilterEffectsDialog::~FilterEffectsDialog()
2157     delete _settings;
2158     delete _filter_general_settings;
2161 void FilterEffectsDialog::set_attrs_locked(const bool l)
2163     _locked = l;
2166 void FilterEffectsDialog::show_all_vfunc()
2168     UI::Widget::Panel::show_all_vfunc();
2170     update_settings_view();
2173 void FilterEffectsDialog::init_settings_widgets()
2175     // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2176     //       most of the current values are complete guesses!
2178     _empty_settings.set_sensitive(false);
2179     _settings_tab1.pack_start(_empty_settings);
2180     
2181     _no_filter_selected.set_sensitive(false);
2182     _settings_tab2.pack_start(_no_filter_selected);
2183     _settings_initialized = true;
2185     _filter_general_settings->type(0);
2186     _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"));
2187     _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"));
2189     _settings->type(NR_FILTER_BLEND);
2190     _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode"), BlendModeConverter);
2192     _settings->type(NR_FILTER_COLORMATRIX);
2193     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."));
2194     _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s)"));
2195     colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2197     _settings->type(NR_FILTER_COMPONENTTRANSFER);
2198     _settings->add_notimplemented();
2199     /*_settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2200     _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2201     _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2202     _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2203     _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2204     _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2206     _settings->type(NR_FILTER_COMPOSITE);
2207     _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
2208     _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."));
2209     _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."));
2210     _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."));
2211     _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."));
2213     _settings->type(NR_FILTER_CONVOLVEMATRIX);
2214     _convolve_order = _settings->add_dualspinbutton("3", SP_ATTR_ORDER, _("Size"), 1, 5, 1, 1, 0, _("width of the convolve matrix"), _("height of the convolve matrix"));
2215     _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."));
2216     _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."));
2217     _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2218     //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.
2219     _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."));
2220     _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."));
2221     _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."));
2222     _settings->add_checkbutton(false, SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive."));
2224     _settings->type(NR_FILTER_DIFFUSELIGHTING);
2225     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Diffuse Color"), _("Defines the color of the light source"));
2226     _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"));
2227     _settings->add_spinslider(1, SP_ATTR_DIFFUSECONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2228     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2229     _settings->add_lightsource();
2231     _settings->type(NR_FILTER_DISPLACEMENTMAP);
2232     _settings->add_spinslider(0, SP_ATTR_SCALE, _("Scale"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2233     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2234     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2236     _settings->type(NR_FILTER_FLOOD);
2237     _settings->add_color(/*default: black*/ 0, SP_PROP_FLOOD_COLOR, _("Flood Color"), _("The whole filter region will be filled with this color."));
2238     _settings->add_spinslider(1, SP_PROP_FLOOD_OPACITY, _("Opacity"), 0, 1, 0.1, 0.01, 2);
2240     _settings->type(NR_FILTER_GAUSSIANBLUR);
2241     _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2243     _settings->type(NR_FILTER_MERGE);
2244     _settings->add_no_params();
2246     _settings->type(NR_FILTER_MORPHOLOGY);
2247     _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2248     _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius"), 0, 100, 1, 0.01, 1);
2250     _settings->type(NR_FILTER_IMAGE);
2251     _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image"));
2253     _settings->type(NR_FILTER_OFFSET);
2254     _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"));
2255     _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"));
2257     _settings->type(NR_FILTER_SPECULARLIGHTING);
2258     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Specular Color"), _("Defines the color of the light source"));
2259     _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"));
2260     _settings->add_spinslider(1, SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2261     _settings->add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2262     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2263     _settings->add_lightsource();
2265     _settings->type(NR_FILTER_TILE);
2266     _settings->add_notimplemented();
2268     _settings->type(NR_FILTER_TURBULENCE);
2269 //    _settings->add_checkbutton(false, SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2270     _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2271     _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency"), 0, 0.4, 0.001, 0.01, 3);
2272     _settings->add_spinslider(1, SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2273     _settings->add_spinslider(0, SP_ATTR_SEED, _("Seed"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2276 void FilterEffectsDialog::add_primitive()
2278     SPFilter* filter = _filter_modifier.get_selected_filter();
2280     if(filter) {
2281         SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2283         _primitive_list.select(prim);
2285         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2286     }
2289 void FilterEffectsDialog::update_primitive_infobox()
2291     if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2292         _infobox_icon.show();
2293         _infobox_desc.show();
2294     } else {
2295         _infobox_icon.hide();
2296         _infobox_desc.hide();
2297     }
2298     switch(_add_primitive_type.get_active_data()->id){
2299         case(NR::NR_FILTER_BLEND):
2300             _infobox_icon.set(g_strdup_printf("%s/feBlend-icon.png", INKSCAPE_PIXMAPDIR));
2301             _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2302             break;
2303         case(NR::NR_FILTER_COLORMATRIX):
2304             _infobox_icon.set(g_strdup_printf("%s/feColorMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2305             _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."));
2306             break;
2307         case(NR::NR_FILTER_COMPONENTTRANSFER):
2308             _infobox_icon.set(g_strdup_printf("%s/feComponentTransfer-icon.png", INKSCAPE_PIXMAPDIR));
2309             _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."));
2310             break;
2311         case(NR::NR_FILTER_COMPOSITE):
2312             _infobox_icon.set(g_strdup_printf("%s/feComposite-icon.png", INKSCAPE_PIXMAPDIR));
2313             _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."));
2314             break;
2315         case(NR::NR_FILTER_CONVOLVEMATRIX):
2316             _infobox_icon.set(g_strdup_printf("%s/feConvolveMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2317             _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."));
2318             break;
2319         case(NR::NR_FILTER_DIFFUSELIGHTING):
2320             _infobox_icon.set(g_strdup_printf("%s/feDiffuseLighting-icon.png", INKSCAPE_PIXMAPDIR));
2321             _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."));
2322             break;
2323         case(NR::NR_FILTER_DISPLACEMENTMAP):
2324             _infobox_icon.set(g_strdup_printf("%s/feDisplacementMap-icon.png", INKSCAPE_PIXMAPDIR));
2325             _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."));
2326             break;
2327         case(NR::NR_FILTER_FLOOD):
2328             _infobox_icon.set(g_strdup_printf("%s/feFlood-icon.png", INKSCAPE_PIXMAPDIR));
2329             _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."));
2330             break;
2331         case(NR::NR_FILTER_GAUSSIANBLUR):
2332             _infobox_icon.set(g_strdup_printf("%s/feGaussianBlur-icon.png", INKSCAPE_PIXMAPDIR));
2333             _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."));
2334             break;
2335         case(NR::NR_FILTER_IMAGE):
2336             _infobox_icon.set(g_strdup_printf("%s/feImage-icon.png", INKSCAPE_PIXMAPDIR));
2337             _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2338             break;
2339         case(NR::NR_FILTER_MERGE):
2340             _infobox_icon.set(g_strdup_printf("%s/feMerge-icon.png", INKSCAPE_PIXMAPDIR));
2341             _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."));
2342             break;
2343         case(NR::NR_FILTER_MORPHOLOGY):
2344             _infobox_icon.set(g_strdup_printf("%s/feMorphology-icon.png", INKSCAPE_PIXMAPDIR));
2345             _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."));
2346             break;
2347         case(NR::NR_FILTER_OFFSET):
2348             _infobox_icon.set(g_strdup_printf("%s/feOffset-icon.png", INKSCAPE_PIXMAPDIR));
2349             _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."));
2350             break;
2351         case(NR::NR_FILTER_SPECULARLIGHTING):
2352             _infobox_icon.set(g_strdup_printf("%s/feSpecularLighting-icon.png", INKSCAPE_PIXMAPDIR));
2353             _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."));
2354             break;
2355         case(NR::NR_FILTER_TILE):
2356             _infobox_icon.set(g_strdup_printf("%s/feTile-icon.png", INKSCAPE_PIXMAPDIR));
2357             _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2358             break;
2359         case(NR::NR_FILTER_TURBULENCE):
2360             _infobox_icon.set(g_strdup_printf("%s/feTurbulence-icon.png", INKSCAPE_PIXMAPDIR));
2361             _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."));
2362             break;
2363         default:
2364             g_assert(false);
2365             break;
2366     }
2369 void FilterEffectsDialog::duplicate_primitive()
2371     SPFilter* filter = _filter_modifier.get_selected_filter();
2372     SPFilterPrimitive* origprim = _primitive_list.get_selected();
2374     if(filter && origprim) {
2375         Inkscape::XML::Node *repr;
2376         repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2377         SP_OBJECT_REPR(filter)->appendChild(repr);
2379         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2381         _primitive_list.update();
2382     }
2385 void FilterEffectsDialog::convolve_order_changed()
2387     _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2388     _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2389     _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2392 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2394     set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2397 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2399     if(!_locked) {
2400         _attr_lock = true;
2401         SPFilter *filter = _filter_modifier.get_selected_filter();
2402         const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2403         if (filter && name && SP_OBJECT_REPR(filter)){
2404             SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2405             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2406         }
2407         _attr_lock = false;
2408     }
2411 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2413     set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2416 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2418     if(!_locked) {
2419         _attr_lock = true;
2421         SPFilter *filter = _filter_modifier.get_selected_filter();
2422         const gchar* name = (const gchar*)sp_attribute_name(attr);
2423         if(filter && name && o) {
2424             update_settings_sensitivity();
2426             SP_OBJECT_REPR(o)->setAttribute(name, val);
2427             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2429             Glib::ustring undokey = "filtereffects:";
2430             undokey += name;
2431             sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2432                                    _("Set filter primitive attribute"));
2433         }
2435         _attr_lock = false;
2436     }
2439 void FilterEffectsDialog::update_filter_general_settings_view()
2441     if(_settings_initialized != true) return;
2443     if(!_locked) {
2444         _attr_lock = true;
2446         SPFilter* filter = _filter_modifier.get_selected_filter();
2448         if(filter) {
2449             _filter_general_settings->show_and_update(0, filter);
2450             _no_filter_selected.hide();
2451         }
2452         else {
2453             std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2454             vect[0]->hide_all();
2455             _no_filter_selected.show();
2456         }
2458         _attr_lock = false;
2459     }
2462 void FilterEffectsDialog::update_settings_view()
2464     update_settings_sensitivity();
2466     if(_attr_lock)
2467         return;
2469 //First Tab
2471     std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2472     for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2473     _empty_settings.show();
2475     if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2476         _infobox_icon.show();
2477         _infobox_desc.show();
2478     } else {
2479         _infobox_icon.hide();
2480         _infobox_desc.hide();
2481     }
2482     
2483     SPFilterPrimitive* prim = _primitive_list.get_selected();
2485     if(prim) {
2486         _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2487         _empty_settings.hide();
2488     }
2490 //Second Tab
2492     std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2493     vect2[0]->hide_all();
2494     _no_filter_selected.show();
2496     SPFilter* filter = _filter_modifier.get_selected_filter();
2498     if(filter) {
2499         _filter_general_settings->show_and_update(0, filter);
2500         _no_filter_selected.hide();
2501     }
2505 void FilterEffectsDialog::update_settings_sensitivity()
2507     SPFilterPrimitive* prim = _primitive_list.get_selected();
2508     const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2509     _k1->set_sensitive(use_k);
2510     _k2->set_sensitive(use_k);
2511     _k3->set_sensitive(use_k);
2512     _k4->set_sensitive(use_k);
2514 // Component transfer not yet implemented
2515 /*
2516     if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2517         SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2518         const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2519         const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2521         _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2522         _ct_slope->set_sensitive(linear);
2523         _ct_intercept->set_sensitive(linear);
2524         _ct_amplitude->set_sensitive(gamma);
2525         _ct_exponent->set_sensitive(gamma);
2526         _ct_offset->set_sensitive(gamma);
2527     }
2528 */
2531 void FilterEffectsDialog::update_color_matrix()
2533     _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2536 } // namespace Dialog
2537 } // namespace UI
2538 } // namespace Inkscape
2540 /*
2541   Local Variables:
2542   mode:c++
2543   c-file-style:"stroustrup"
2544   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2545   indent-tabs-mode:nil
2546   fill-column:99
2547   End:
2548 */
2549 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :