Code

* added tooltips for checkboxes and matrices
[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(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, true),//TO-DO: receive a defaultvalue parameter in the constructor
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(double lower, double upper, double step_inc,
266                    double climb_rate, int digits, const SPAttributeEnum a, char* tt1, char* tt2)
267         : AttrWidget(a), //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("0 0"); //TO-DO: replace this line by the next one that is currently commented out
315 //            n.set(default_value(o));
316         }
317         _s1.set_value(n.getNumber());
318         _s2.set_value(n.getOptNumber());
319         
320     }
321 private:
322     Gtk::SpinButton _s1, _s2;
323 };
325 class ColorButton : public Gtk::ColorButton, public AttrWidget
327 public:
328     ColorButton(const SPAttributeEnum a, char* tip_text)
329         : AttrWidget(a)
330     {
331         signal_color_set().connect(signal_attr_changed().make_slot());
332         if (tip_text) _tt.set_tip(*this, tip_text);
334         Gdk::Color col;
335         col.set_rgb(65535, 65535, 65535);
336         set_color(col);
337     }
339     // Returns the color in 'rgb(r,g,b)' form.
340     Glib::ustring get_as_attribute() const
341     {
342         std::ostringstream os;
343         const Gdk::Color c = get_color();
344         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?
345         os << "rgb(" << r << "," << g << "," << b << ")";
346         return os.str();
347     }
350     void set_from_attribute(SPObject* o)
351     {
352         const gchar* val = attribute_value(o);
353         guint32 i = 0;
354         if(val) {
355             i = sp_svg_read_color(val, 0xFFFFFFFF);
356         } else {
357             //TO-DO: read from constructor attribute
358             //i = default_value(o);
359         }
360         const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
361         Gdk::Color col;
362         col.set_rgb(r * 256, g * 256, b * 256);
363         set_color(col);
364     }
365 };
367 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
368 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
370 public:
371     MatrixAttr(const SPAttributeEnum a, char* tip_text = NULL)
372         : AttrWidget(a), _locked(false)
373     {
374         _model = Gtk::ListStore::create(_columns);
375         _tree.set_model(_model);
376         _tree.set_headers_visible(false);
377         _tree.show();
378         add(_tree);
379         set_shadow_type(Gtk::SHADOW_IN);
380         if (tip_text) _tt.set_tip(_tree, tip_text);
381     }
383     std::vector<double> get_values() const
384     {
385         std::vector<double> vec;
386         for(Gtk::TreeIter iter = _model->children().begin();
387             iter != _model->children().end(); ++iter) {
388             for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
389                 vec.push_back((*iter)[_columns.cols[c]]);
390         }
391         return vec;
392     }
394     void set_values(const std::vector<double>& v)
395     {
396         unsigned i = 0;
397         for(Gtk::TreeIter iter = _model->children().begin();
398             iter != _model->children().end(); ++iter) {
399             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
400                 if(i >= v.size())
401                     return;
402                 (*iter)[_columns.cols[c]] = v[i];
403                 ++i;
404             }
405         }
406     }
408     Glib::ustring get_as_attribute() const
409     {
410         std::ostringstream os;
412         for(Gtk::TreeIter iter = _model->children().begin();
413             iter != _model->children().end(); ++iter) {
414             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
415                 os << (*iter)[_columns.cols[c]] << " ";
416             }
417         }
419         return os.str();
420     }
422     void set_from_attribute(SPObject* o)
423     {
424         if(o) {
425             if(SP_IS_FECONVOLVEMATRIX(o)) {
426                 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
427                 int cols, rows;
428                 cols = (int)conv->order.getNumber();
429                 if(cols > 5)
430                     cols = 5;
431                 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
432                 update(o, rows, cols);
433             }
434             else if(SP_IS_FECOLORMATRIX(o))
435                 update(o, 4, 5);
436         }
437     }
438 private:
439     class MatrixColumns : public Gtk::TreeModel::ColumnRecord
440     {
441     public:
442         MatrixColumns()
443         {
444             cols.resize(5);
445             for(unsigned i = 0; i < cols.size(); ++i)
446                 add(cols[i]);
447         }
448         std::vector<Gtk::TreeModelColumn<double> > cols;
449     };
451     void update(SPObject* o, const int rows, const int cols)
452     {
453         if(_locked)
454             return;
456         _model->clear();
458         _tree.remove_all_columns();
460         std::vector<gdouble>* values = NULL;
461         if(SP_IS_FECOLORMATRIX(o))
462             values = &SP_FECOLORMATRIX(o)->values;
463         else if(SP_IS_FECONVOLVEMATRIX(o))
464             values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
465         else
466             return;
468         if(o) {
469             int ndx = 0;
471             for(int i = 0; i < cols; ++i) {
472                 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
473                 dynamic_cast<Gtk::CellRendererText*>(
474                     _tree.get_column_cell_renderer(i))->signal_edited().connect(
475                         sigc::mem_fun(*this, &MatrixAttr::rebind));
476             }
478             for(int r = 0; r < rows; ++r) {
479                 Gtk::TreeRow row = *(_model->append());
480                 // Default to identity matrix
481                 for(int c = 0; c < cols; ++c, ++ndx)
482                     row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
483             }
484         }
485     }
487     void rebind(const Glib::ustring&, const Glib::ustring&)
488     {
489         _locked = true;
490         signal_attr_changed()();
491         _locked = false;
492     }
494     bool _locked;
495     Gtk::TreeView _tree;
496     Glib::RefPtr<Gtk::ListStore> _model;
497     MatrixColumns _columns;
498 };
500 // Displays a matrix or a slider for feColorMatrix
501 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
503 public:
504     ColorMatrixValues()
505         : AttrWidget(SP_ATTR_VALUES),
506           _matrix(SP_ATTR_VALUES, _("This matrix determines a linear transform on colour space. Each line affects one of the color components. Each column determines how much of each color component from the input is passed to the output. The last column does not depend on input colors, so can be used to adjust a constant component value.")),
507           _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
508           _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
509           _label(_("None"), Gtk::ALIGN_LEFT),
510           _use_stored(false),
511           _saturation_store(0),
512           _angle_store(0)
513     {
514         _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
515         _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
516         _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
517         signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
519         _matrix.show();
520         _saturation.show();
521         _angle.show();
522         _label.show();
523         _label.set_sensitive(false);
525         set_shadow_type(Gtk::SHADOW_NONE);
526     }
528     virtual void set_from_attribute(SPObject* o)
529     {
530         if(SP_IS_FECOLORMATRIX(o)) {
531             SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
532             remove();
533             switch(col->type) {
534                 case COLORMATRIX_SATURATE:
535                     add(_saturation);
536                     if(_use_stored)
537                         _saturation.set_value(_saturation_store);
538                     else
539                         _saturation.set_from_attribute(o);
540                     break;
541                 case COLORMATRIX_HUEROTATE:
542                     add(_angle);
543                     if(_use_stored)
544                         _angle.set_value(_angle_store);
545                     else
546                         _angle.set_from_attribute(o);
547                     break;
548                 case COLORMATRIX_LUMINANCETOALPHA:
549                     add(_label);
550                     break;
551                 case COLORMATRIX_MATRIX:
552                 default:
553                     add(_matrix);
554                     if(_use_stored)
555                         _matrix.set_values(_matrix_store);
556                     else
557                         _matrix.set_from_attribute(o);
558                     break;
559             }
560             _use_stored = true;
561         }
562     }
564     virtual Glib::ustring get_as_attribute() const
565     {
566         const Widget* w = get_child();
567         if(w == &_label)
568             return "";
569         else
570             return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
571     }
573     void clear_store()
574     {
575         _use_stored = false;
576     }
577 private:
578     void update_store()
579     {
580         const Widget* w = get_child();
581         if(w == &_matrix)
582             _matrix_store = _matrix.get_values();
583         else if(w == &_saturation)
584             _saturation_store = _saturation.get_value();
585         else if(w == &_angle)
586             _angle_store = _angle.get_value();
587     }
589     MatrixAttr _matrix;
590     SpinSlider _saturation;
591     SpinSlider _angle;
592     Gtk::Label _label;
594     // Store separate values for the different color modes
595     bool _use_stored;
596     std::vector<double> _matrix_store;
597     double _saturation_store;
598     double _angle_store;
599 };
601 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
603 //Displays a chooser for feImage input
604 //It may be a filename or the id for an SVG Element
605 //described in xlink:href syntax
606 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
608 public:
609     FileOrElementChooser(const SPAttributeEnum a)
610         : AttrWidget(a)
611     {
612         pack_start(_entry, false, false);
613         pack_start(_fromFile, false, false);
614         //pack_start(_fromSVGElement, false, false);
616         _fromFile.set_label(_("Image File"));
617         _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
619         _fromSVGElement.set_label(_("Selected SVG Element"));
620         _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
622         _entry.signal_changed().connect(signal_attr_changed().make_slot());
624         show_all();
626     }
628     // Returns the element in xlink:href form.
629     Glib::ustring get_as_attribute() const
630     {
631         return _entry.get_text();
632     }
635     void set_from_attribute(SPObject* o)
636     {
637         const gchar* val = attribute_value(o);
638         if(val) {
639             _entry.set_text(val);
640         } else {
641             _entry.set_text("");
642         }
643     }
645     void set_desktop(SPDesktop* d){
646         _desktop = d;
647     }
649 private:
650     void select_svg_element(){
651         Inkscape::Selection* sel = sp_desktop_selection(_desktop);
652         if (sel->isEmpty()) return;
653         Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
654         if (!node || !node->matchAttributeName("id")) return;
656         std::ostringstream xlikhref;
657         xlikhref << "#" << node->attribute("id");
658         _entry.set_text(xlikhref.str());
659     }
661     void select_file(){
663         //# Get the current directory for finding files
664         Glib::ustring open_path;
665         char *attr = (char *)prefs_get_string_attribute("dialogs.open", "path");
666         if (attr)
667             open_path = attr;
669         //# Test if the open_path directory exists
670         if (!Inkscape::IO::file_test(open_path.c_str(),
671                   (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
672             open_path = "";
674         //# If no open path, default to our home directory
675         if (open_path.size() < 1)
676             {
677             open_path = g_get_home_dir();
678             open_path.append(G_DIR_SEPARATOR_S);
679             }
681         //# Create a dialog if we don't already have one
682         if (!selectFeImageFileInstance) {
683             selectFeImageFileInstance =
684                   Inkscape::UI::Dialog::FileOpenDialog::create(
685                      *_desktop->getToplevel(),
686                      open_path,
687                      Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not justy svg*/
688                      (char const *)_("Select an image to be used as feImage input"));
689         }
691         //# Show the dialog
692         bool const success = selectFeImageFileInstance->show();
693         if (!success)
694             return;
696         //# User selected something.  Get name and type
697         Glib::ustring fileName = selectFeImageFileInstance->getFilename();
699         if (fileName.size() > 0) {
701             Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
703             if ( newFileName.size() > 0)
704                 fileName = newFileName;
705             else
706                 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
708             open_path = fileName;
709             open_path.append(G_DIR_SEPARATOR_S);
710             prefs_set_string_attribute("dialogs.open", "path", open_path.c_str());
712             _entry.set_text(fileName);
713         }
714         return;
715     }
717     Gtk::Entry _entry;
718     Gtk::Button _fromFile;
719     Gtk::Button _fromSVGElement;
720     SPDesktop* _desktop;
721 };
723 class FilterEffectsDialog::Settings
725 public:
726     typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
728     Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
729         : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
730     {
731         _groups.resize(_max_types);
732         _attrwidgets.resize(_max_types);
733         _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
735         for(int i = 0; i < _max_types; ++i) {
736             _groups[i] = new Gtk::VBox;
737             b.pack_start(*_groups[i], false, false);
738         }
739         _current_type = 0;
740     }
742     ~Settings()
743     {
744         for(int i = 0; i < _max_types; ++i) {
745             delete _groups[i];
746             for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
747                 delete _attrwidgets[i][j];
748         }
749     }
751     // Show the active settings group and update all the AttrWidgets with new values
752     void show_and_update(const int t, SPObject* ob)
753     {
754         if(t != _current_type) {
755             type(t);
756             for(unsigned i = 0; i < _groups.size(); ++i)
757                 _groups[i]->hide();
758         }
759         if(t >= 0)
760             _groups[t]->show_all();
762         _dialog.set_attrs_locked(true);
763         for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
764             _attrwidgets[_current_type][i]->set_from_attribute(ob);
765         _dialog.set_attrs_locked(false);
766     }
768     int get_current_type() const
769     {
770         return _current_type;
771     }
773     void type(const int t)
774     {
775         _current_type = t;
776     }
778     void add_no_params()
779     {
780         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
781         add_widget(lbl, "");
782     }
784     void add_notimplemented()
785     {
786         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
787         add_widget(lbl, "");
788     }
790     // LightSource
791     LightSourceControl* add_lightsource();
793     // CheckBox
794     CheckButtonAttr* add_checkbutton(const SPAttributeEnum attr, const Glib::ustring& label,
795                                      const Glib::ustring& tv, const Glib::ustring& fv, char* tip_text = NULL)
796     {
797         CheckButtonAttr* cb = new CheckButtonAttr(label, tv, fv, attr, tip_text);
798         add_widget(cb, "");
799         add_attr_widget(cb);
800         return cb;
801     }
803     // ColorButton
804     ColorButton* add_color(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text = NULL)
805     {
806         ColorButton* col = new ColorButton(attr, tip_text);
807         add_widget(col, label);
808         add_attr_widget(col);
809         return col;
810     }
812     // Matrix
813     MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text)
814     {
815         MatrixAttr* conv = new MatrixAttr(attr, tip_text);
816         add_widget(conv, label);
817         add_attr_widget(conv);
818         return conv;
819     }
821     // ColorMatrixValues
822     ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
823     {
824         ColorMatrixValues* cmv = new ColorMatrixValues();
825         add_widget(cmv, label);
826         add_attr_widget(cmv);
827         return cmv;
828     }
830     // SpinSlider
831     SpinSlider* add_spinslider(const SPAttributeEnum attr, const Glib::ustring& label,
832                          const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
833     {
834         SpinSlider* spinslider = new SpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text);
835         add_widget(spinslider, label);
836         add_attr_widget(spinslider);
837         return spinslider;
838     }
840     // DualSpinSlider
841     DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
842                                        const double lo, const double hi, const double step_inc,
843                                        const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
844     {
845         DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
846         add_widget(dss, label);
847         add_attr_widget(dss);
848         return dss;
849     }
851     // DualSpinButton
852     DualSpinButton* add_dualspinbutton(const SPAttributeEnum attr, const Glib::ustring& label,
853                                        const double lo, const double hi, const double step_inc,
854                                        const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
855     {
856         DualSpinButton* dsb = new DualSpinButton(lo, hi, step_inc, climb, digits, attr, tip1, tip2);
857         add_widget(dsb, label);
858         add_attr_widget(dsb);
859         return dsb;
860     }
862     // MultiSpinButton
863     MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
864                                          const Glib::ustring& label, const double lo, const double hi,
865                                          const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
866     {
867         std::vector<SPAttributeEnum> attrs;
868         attrs.push_back(attr1);
869         attrs.push_back(attr2);
871         std::vector<double> default_values;
872         default_values.push_back(def1);
873         default_values.push_back(def2);
874         
875         std::vector<char*> tips;
876         tips.push_back(tip1);
877         tips.push_back(tip2);
879         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
880         add_widget(msb, label);
881         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
882             add_attr_widget(msb->get_spinbuttons()[i]);
883         return msb;
884     }
885     MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
886                                          const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
887                                          const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
888     {
889         std::vector<SPAttributeEnum> attrs;
890         attrs.push_back(attr1);
891         attrs.push_back(attr2);
892         attrs.push_back(attr3);
894         std::vector<double> default_values;
895         default_values.push_back(def1);
896         default_values.push_back(def2);
897         default_values.push_back(def3);
899         std::vector<char*> tips;
900         tips.push_back(tip1);
901         tips.push_back(tip2);
902         tips.push_back(tip3);
904         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
905         add_widget(msb, label);
906         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
907             add_attr_widget(msb->get_spinbuttons()[i]);
908         return msb;
909     }
911     // FileOrElementChooser
912     FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
913     {
914         FileOrElementChooser* foech = new FileOrElementChooser(attr);
915         foech->set_desktop(_dialog.getDesktop());
916         add_widget(foech, label);
917         add_attr_widget(foech);
918         return foech;
919     }
921     // ComboBoxEnum
922     template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
923                                   const Glib::ustring& label,
924                                   const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
925     {
926         ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
927         add_widget(combo, label);
928         add_attr_widget(combo->get_attrwidget());
929         return combo->get_attrwidget();
930     }
931 private:
932     Gtk::Tooltips _tt;
934     void add_attr_widget(AttrWidget* a)
935     {
936         _attrwidgets[_current_type].push_back(a);
937         a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
938     }
940     /* Adds a new settings widget using the specified label. The label will be formatted with a colon
941        and all widgets within the setting group are aligned automatically. */
942     void add_widget(Gtk::Widget* w, const Glib::ustring& label)
943     {
944         Gtk::Label *lbl = 0;
945         Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
946         hb->set_spacing(12);
948         if(label != "") {
949             lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
950             hb->pack_start(*lbl, false, false);
951             _size_group->add_widget(*lbl);
952             lbl->show();
953         }
955         hb->pack_start(*w);
956         _groups[_current_type]->pack_start(*hb);
957         hb->show();
958         w->show();
959     }
961     std::vector<Gtk::VBox*> _groups;
962     Glib::RefPtr<Gtk::SizeGroup> _size_group;
963     FilterEffectsDialog& _dialog;
964     SetAttrSlot _set_attr_slot;
965     std::vector<std::vector< AttrWidget*> > _attrwidgets;
966     int _current_type, _max_types;
967 };
969 // Settings for the three light source objects
970 class FilterEffectsDialog::LightSourceControl : public AttrWidget
972 public:
973     LightSourceControl(FilterEffectsDialog& d)
974         : AttrWidget(SP_ATTR_INVALID),
975           _dialog(d),
976           _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
977           _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
978           _light_source(LightSourceConverter),
979           _locked(false)
980     {
981         _light_box.pack_start(_light_label, false, false);
982         _light_box.pack_start(_light_source);
983         _light_box.show_all();
984         _light_box.set_spacing(12);
985         _dialog._sizegroup->add_widget(_light_label);
987         _box.add(_light_box);
988         _box.reorder_child(_light_box, 0);
989         _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
991         // FIXME: these range values are complete crap
993         _settings.type(LIGHT_DISTANT);
994         _settings.add_spinslider(SP_ATTR_AZIMUTH, _("Azimuth"), 0, 360, 1, 1, 0, _("Direction angle for the light source on the XY plane, in degrees"));
995         _settings.add_spinslider(SP_ATTR_ELEVATION, _("Elevation"), 0, 360, 1, 1, 0, _("Direction angle for the light source on the YZ plane, in degrees"));
997         _settings.type(LIGHT_POINT);
998         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0, SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
1000         _settings.type(LIGHT_SPOT);
1001         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0, SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
1002         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
1003                                       SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
1004                                       _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
1005         _settings.add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
1006         _settings.add_spinslider(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."));
1007     }
1009     Gtk::VBox& get_box()
1010     {
1011         return _box;
1012     }
1013 protected:
1014     Glib::ustring get_as_attribute() const
1015     {
1016         return "";
1017     }
1018     void set_from_attribute(SPObject* o)
1019     {
1020         if(_locked)
1021             return;
1023         _locked = true;
1025         SPObject* child = o->children;
1027         if(SP_IS_FEDISTANTLIGHT(child))
1028             _light_source.set_active(0);
1029         else if(SP_IS_FEPOINTLIGHT(child))
1030             _light_source.set_active(1);
1031         else if(SP_IS_FESPOTLIGHT(child))
1032             _light_source.set_active(2);
1033         else
1034             _light_source.set_active(-1);
1036         update();
1038         _locked = false;
1039     }
1040 private:
1041     void on_source_changed()
1042     {
1043         if(_locked)
1044             return;
1046         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1047         if(prim) {
1048             _locked = true;
1050             SPObject* child = prim->children;
1051             const int ls = _light_source.get_active_row_number();
1052             // Check if the light source type has changed
1053             if(!(ls == -1 && !child) &&
1054                !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1055                !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1056                !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1057                 if(child)
1058                     sp_repr_unparent(child->repr);
1060                 if(ls != -1) {
1061                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1062                     Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1063                     prim->repr->appendChild(repr);
1064                 }
1066                 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1067                 update();
1068             }
1070             _locked = false;
1071         }
1072     }
1074     void update()
1075     {
1076         _box.hide_all();
1077         _box.show();
1078         _light_box.show_all();
1080         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1081         if(prim && prim->children)
1082             _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1083     }
1085     FilterEffectsDialog& _dialog;
1086     Gtk::VBox _box;
1087     Settings _settings;
1088     Gtk::HBox _light_box;
1089     Gtk::Label _light_label;
1090     ComboBoxEnum<LightSource> _light_source;
1091     bool _locked;
1092 };
1094 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1096     LightSourceControl* ls = new LightSourceControl(_dialog);
1097     add_attr_widget(ls);
1098     add_widget(&ls->get_box(), "");
1099     return ls;
1102 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1103                                           sigc::slot<void> rem)
1105     Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1107     menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1108     Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1109     menu->append(*mi);
1110     mi->signal_activate().connect(rem);
1111     mi->show();
1112     menu->accelerate(parent);
1114     return menu;
1117 /*** FilterModifier ***/
1118 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1119     : _dialog(d), _add(Gtk::Stock::NEW), _observer(new SignalObserver)
1121     Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1122     pack_start(*sw);
1123     pack_start(_add, false, false);
1124     sw->add(_list);
1126     _model = Gtk::ListStore::create(_columns);
1127     _list.set_model(_model);
1128     _cell_toggle.set_active(true);
1129     const int selcol = _list.append_column("", _cell_toggle);
1130     Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1131     if(col)
1132        col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1133     _list.append_column_editable(_("_Filter"), _columns.label);
1134     ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1135         signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1137     sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1138     sw->set_shadow_type(Gtk::SHADOW_IN);
1139     show_all_children();
1140     _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1141     _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1142     _list.signal_button_release_event().connect_notify(
1143         sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1144     _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1145                               sigc::mem_fun(*this, &FilterModifier::remove_filter));
1146     _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1147                                  _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1148     _menu->accelerate(*this);
1150     _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1151     _observer->signal_changed().connect(signal_filter_changed().make_slot());
1152     g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1153                      G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1155     g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1156                      G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1158     on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1159     update_filters();
1162 FilterEffectsDialog::FilterModifier::~FilterModifier()
1164    _resource_changed.disconnect();
1165    _doc_replaced.disconnect();
1168 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1170     me->_doc_replaced.disconnect();
1171     me->_doc_replaced = desktop->connectDocumentReplaced(
1172         sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1174     me->_resource_changed.disconnect();
1175     me->_resource_changed =
1176         sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1177                                               sigc::mem_fun(me, &FilterModifier::update_filters));
1179     me->_dialog.setDesktop(desktop);
1181     me->update_filters();
1185 // When the selection changes, show the active filter(s) in the dialog
1186 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1187                                                                        Selection *sel,
1188                                                                        FilterModifier* fm)
1190     if(fm && sel)
1191         fm->update_selection(sel);
1194 // Update each filter's sel property based on the current object selection;
1195 //  If the filter is not used by any selected object, sel = 0,
1196 //  otherwise sel is set to the total number of filters in use by selected objects
1197 //  If only one filter is in use, it is selected
1198 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1200     std::set<SPObject*> used;
1202     for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1203         SPObject *obj = SP_OBJECT (i->data);
1204         SPStyle *style = SP_OBJECT_STYLE (obj);
1205         if(!style || !SP_IS_ITEM(obj)) continue;
1207         if(style->filter.set && style->getFilter())
1208             used.insert(style->getFilter());
1209         else
1210             used.insert(0);
1211     }
1213     const int size = used.size();
1215     for(Gtk::TreeIter iter = _model->children().begin();
1216         iter != _model->children().end(); ++iter) {
1217         if(used.find((*iter)[_columns.filter]) != used.end()) {
1218             // If only one filter is in use by the selection, select it
1219             if(size == 1)
1220                 _list.get_selection()->select(iter);
1221             (*iter)[_columns.sel] = size;
1222         }
1223         else
1224             (*iter)[_columns.sel] = 0;
1225     }
1228 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1230     _observer->set(get_selected_filter());
1231     signal_filter_changed()();
1234 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1236     Gtk::TreeModel::iterator iter = _model->get_iter(path);
1238     if(iter) {
1239         SPFilter* filter = (*iter)[_columns.filter];
1240         filter->setLabel(text.c_str());
1241         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1242         if(iter)
1243             (*iter)[_columns.label] = text;
1244     }
1247 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1249     Gtk::TreeIter iter = _model->get_iter(path);
1251     if(iter) {
1252         SPDesktop *desktop = _dialog.getDesktop();
1253         SPDocument *doc = sp_desktop_document(desktop);
1254         SPFilter* filter = (*iter)[_columns.filter];
1255         Inkscape::Selection *sel = sp_desktop_selection(desktop);
1257         /* If this filter is the only one used in the selection, unset it */
1258         if((*iter)[_columns.sel] == 1)
1259             filter = 0;
1261         GSList const *items = sel->itemList();
1263         for (GSList const *i = items; i != NULL; i = i->next) {
1264             SPItem * item = SP_ITEM(i->data);
1265             SPStyle *style = SP_OBJECT_STYLE(item);
1266             g_assert(style != NULL);
1268             if(filter)
1269                 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1270             else
1271                 ::remove_filter(item, false);
1273             SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1274         }
1276         update_selection(sel);
1277         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS,  _("Apply filter"));
1278     }
1281 /* Add all filters in the document to the combobox.
1282    Keeps the same selection if possible, otherwise selects the first element */
1283 void FilterEffectsDialog::FilterModifier::update_filters()
1285     SPDesktop* desktop = _dialog.getDesktop();
1286     SPDocument* document = sp_desktop_document(desktop);
1287     const GSList* filters = sp_document_get_resource_list(document, "filter");
1289     _model->clear();
1291     for(const GSList *l = filters; l; l = l->next) {
1292         Gtk::TreeModel::Row row = *_model->append();
1293         SPFilter* f = (SPFilter*)l->data;
1294         row[_columns.filter] = f;
1295         const gchar* lbl = f->label();
1296         const gchar* id = SP_OBJECT_ID(f);
1297         row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1298     }
1300     update_selection(desktop->selection);
1301     _dialog.update_filter_general_settings_view();
1304 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1306     if(_list.get_selection()) {
1307         Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1309         if(i)
1310             return (*i)[_columns.filter];
1311     }
1313     return 0;
1316 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1318     if(filter) {
1319         for(Gtk::TreeModel::iterator i = _model->children().begin();
1320             i != _model->children().end(); ++i) {
1321             if((*i)[_columns.filter] == filter) {
1322                 _list.get_selection()->select(i);
1323                 break;
1324             }
1325         }
1326     }
1329 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1331     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1332         const bool sensitive = get_selected_filter() != NULL;
1333         _menu->items()[0].set_sensitive(sensitive);
1334         _menu->items()[1].set_sensitive(sensitive);
1335         _menu->popup(event->button, event->time);
1336     }
1339 void FilterEffectsDialog::FilterModifier::add_filter()
1341     SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1342     SPFilter* filter = new_filter(doc);
1344     const int count = _model->children().size();
1345     std::ostringstream os;
1346     os << "filter" << count;
1347     filter->setLabel(os.str().c_str());
1349     update_filters();
1351     select_filter(filter);
1353     sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1356 void FilterEffectsDialog::FilterModifier::remove_filter()
1358     SPFilter *filter = get_selected_filter();
1360     if(filter) {
1361         SPDocument* doc = filter->document;
1362         sp_repr_unparent(filter->repr);
1364         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1366         update_filters();
1367     }
1370 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1372     SPFilter* filter = get_selected_filter();
1374     if(filter) {
1375         Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1376         repr = repr->duplicate(repr->document());
1377         parent->appendChild(repr);
1379         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1381         update_filters();
1382     }
1385 void FilterEffectsDialog::FilterModifier::rename_filter()
1387     _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1390 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1391     : Glib::ObjectBase(typeid(CellRendererConnection)),
1392       _primitive(*this, "primitive", 0)
1393 {}
1395 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1397     return _primitive.get_proxy();
1400 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1402     _text_width = w;
1405 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1407     return _text_width;
1410 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1411     Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1412     int* x_offset, int* y_offset, int* width, int* height) const
1414     PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1416     if(x_offset)
1417         (*x_offset) = 0;
1418     if(y_offset)
1419         (*y_offset) = 0;
1420     if(width)
1421         (*width) = size * primlist.primitive_count() + _text_width * 7;
1422     if(height) {
1423         // Scale the height depending on the number of inputs, unless it's
1424         // the first primitive, in which case there are no connections
1425         SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1426         (*height) = size * input_count(prim);
1427     }
1430 /*** PrimitiveList ***/
1431 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1432     : _dialog(d),
1433       _in_drag(0),
1434       _observer(new SignalObserver)
1436     d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1438     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1439     signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1441     _model = Gtk::ListStore::create(_columns);
1443     set_reorderable(true);
1445     set_model(_model);
1446     append_column(_("_Effect"), _columns.type);
1448     _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1449     get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1450     signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1452     _connection_cell.set_text_width(init_text());
1454     int cols_count = append_column(_("Connections"), _connection_cell);
1455     Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1456     if(col)
1457        col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1460 // Sets up a vertical Pango context/layout, and returns the largest
1461 // width needed to render the FilterPrimitiveInput labels.
1462 int FilterEffectsDialog::PrimitiveList::init_text()
1464     // Set up a vertical context+layout
1465     Glib::RefPtr<Pango::Context> context = create_pango_context();
1466     const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1467     context->set_matrix(matrix);
1468     _vertical_layout = Pango::Layout::create(context);
1470     int maxfont = 0;
1471     for(int i = 0; i < FPInputConverter.end; ++i) {
1472         _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1473         int fontw, fonth;
1474         _vertical_layout->get_pixel_size(fontw, fonth);
1475         if(fonth > maxfont)
1476             maxfont = fonth;
1477     }
1479     return maxfont;
1482 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1484     return _signal_primitive_changed;
1487 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1489     _observer->set(get_selected());
1490     signal_primitive_changed()();
1491     _dialog._color_matrix_values->clear_store();
1494 /* Add all filter primitives in the current to the list.
1495    Keeps the same selection if possible, otherwise selects the first element */
1496 void FilterEffectsDialog::PrimitiveList::update()
1498     SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1499     const SPFilterPrimitive* active_prim = get_selected();
1500     bool active_found = false;
1502     _model->clear();
1504     if(f) {
1505         _dialog._primitive_box.set_sensitive(true);
1506         _dialog.update_filter_general_settings_view();
1507         for(SPObject *prim_obj = f->children;
1508                 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1509                 prim_obj = prim_obj->next) {
1510             SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1511             if(prim) {
1512                 Gtk::TreeModel::Row row = *_model->append();
1513                 row[_columns.primitive] = prim;
1514                 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1515                 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1516                 row[_columns.id] = SP_OBJECT_ID(prim);
1518                 if(prim == active_prim) {
1519                     get_selection()->select(row);
1520                     active_found = true;
1521                 }
1522             }
1523         }
1525         if(!active_found && _model->children().begin())
1526             get_selection()->select(_model->children().begin());
1528         columns_autosize();
1529     }
1530     else {
1531         _dialog._primitive_box.set_sensitive(false);
1532     }
1535 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1537     _primitive_menu = menu;
1540 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1542     if(_dialog._filter_modifier.get_selected_filter()) {
1543         Gtk::TreeModel::iterator i = get_selection()->get_selected();
1544         if(i)
1545             return (*i)[_columns.primitive];
1546     }
1548     return 0;
1551 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1553     for(Gtk::TreeIter i = _model->children().begin();
1554         i != _model->children().end(); ++i) {
1555         if((*i)[_columns.primitive] == prim)
1556             get_selection()->select(i);
1557     }
1560 void FilterEffectsDialog::PrimitiveList::remove_selected()
1562     SPFilterPrimitive* prim = get_selected();
1564     if(prim) {
1565         _observer->set(0);
1567         sp_repr_unparent(prim->repr);
1569         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1570                          _("Remove filter primitive"));
1572         update();
1573     }
1576 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1578     Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1579     Glib::RefPtr<Gdk::Window> win = get_bin_window();
1580     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1582     SPFilterPrimitive* prim = get_selected();
1583     int row_count = get_model()->children().size();
1585     int fheight = CellRendererConnection::size;
1586     Gdk::Rectangle rct, vis;
1587     Gtk::TreeIter row = get_model()->children().begin();
1588     int text_start_x = 0;
1589     if(row) {
1590         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1591         get_visible_rect(vis);
1592         int vis_x, vis_y;
1593         tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1595         text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter.end + 1) + 1;
1596         for(int i = 0; i < FPInputConverter.end; ++i) {
1597             _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1598             const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1599             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());
1600             get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1601             get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1602         }
1603     }
1605     int row_index = 0;
1606     for(; row != get_model()->children().end(); ++row, ++row_index) {
1607         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1608         const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1610         // Check mouse state
1611         int mx, my;
1612         Gdk::ModifierType mask;
1613         get_bin_window()->get_pointer(mx, my, mask);
1615         // Outline the bottom of the connection area
1616         const int outline_x = x + fheight * (row_count - row_index);
1617         get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1619         // Side outline
1620         get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1622         std::vector<Gdk::Point> con_poly;
1623         int con_drag_y = 0;
1624         bool inside;
1625         const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1626         const int inputs = input_count(row_prim);
1628         if(SP_IS_FEMERGE(row_prim)) {
1629             for(int i = 0; i < inputs; ++i) {
1630                 inside = do_connection_node(row, i, con_poly, mx, my);
1631                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1632                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1633                                                inside, con_poly);
1635                 if(_in_drag == (i + 1))
1636                     con_drag_y = con_poly[2].get_y();
1638                 if(_in_drag != (i + 1) || row_prim != prim)
1639                     draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1640             }
1641         }
1642         else {
1643             // Draw "in" shape
1644             inside = do_connection_node(row, 0, con_poly, mx, my);
1645             con_drag_y = con_poly[2].get_y();
1646             get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1647                                            darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1648                                            inside, con_poly);
1650             // Draw "in" connection
1651             if(_in_drag != 1 || row_prim != prim)
1652                 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1654             if(inputs == 2) {
1655                 // Draw "in2" shape
1656                 inside = do_connection_node(row, 1, con_poly, mx, my);
1657                 if(_in_drag == 2)
1658                     con_drag_y = con_poly[2].get_y();
1659                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1660                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1661                                                inside, con_poly);
1662                 // Draw "in2" connection
1663                 if(_in_drag != 2 || row_prim != prim)
1664                     draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1665             }
1666         }
1668         // Draw drag connection
1669         if(row_prim == prim && _in_drag) {
1670             get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1671                                         mx, con_drag_y);
1672             get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1673         }
1674     }
1676     return true;
1679 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1680                                                          const int text_start_x, const int x1, const int y1,
1681                                                          const int row_count)
1683     int src_id = 0;
1684     Gtk::TreeIter res = find_result(input, attr, src_id);
1685     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1686     Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1687     Glib::RefPtr<Gdk::GC> gc;
1689     const bool is_first = input == get_model()->children().begin();
1690     const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1691     const bool use_default = !res && !is_merge;
1693     if(res == input || (use_default && is_first)) {
1694         // Draw straight connection to a standard input
1695         // Draw a lighter line for an implicit connection to a standard input
1696         const int tw = _connection_cell.get_text_width();
1697         gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1698         gc = (use_default && is_first) ? lightgc : darkgc;
1699         get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1700         get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1701     }
1702     else {
1703         // Draw an 'L'-shaped connection to another filter primitive
1704         // If no connection is specified, draw a light connection to the previous primitive
1705         gc = use_default ? lightgc : darkgc;
1707         if(use_default) {
1708             res = input;
1709             --res;
1710         }
1712         if(res) {
1713             Gdk::Rectangle rct;
1715             get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1716             const int fheight = CellRendererConnection::size;
1718             get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1719             const int row_index = find_index(res);
1720             const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1721             const int y2 = rct.get_y() + rct.get_height();
1723             // Draw a bevelled 'L'-shaped connection
1724             get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1725             get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1726             get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1727         }
1728     }
1731 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1732 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1733                                                             std::vector<Gdk::Point>& points,
1734                                                             const int ix, const int iy)
1736     Gdk::Rectangle rct;
1737     const int icnt = input_count((*row)[_columns.primitive]);
1739     get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1740     const int fheight = CellRendererConnection::size;
1742     get_cell_area(_model->get_path(row), *get_column(1), rct);
1743     const float h = rct.get_height() / icnt;
1745     const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1746     const int con_w = (int)(fheight * 0.35f);
1747     const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1748     points.clear();
1749     points.push_back(Gdk::Point(x, con_y));
1750     points.push_back(Gdk::Point(x, con_y + con_w * 2));
1751     points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1753     return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1756 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1757                                                                     const int attr, int& src_id)
1759     SPFilterPrimitive* prim = (*start)[_columns.primitive];
1760     Gtk::TreeIter target = _model->children().end();
1761     int image = 0;
1763     if(SP_IS_FEMERGE(prim)) {
1764         int c = 0;
1765         bool found = false;
1766         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1767             if(c == attr && SP_IS_FEMERGENODE(o)) {
1768                 image = SP_FEMERGENODE(o)->input;
1769                 found = true;
1770             }
1771         }
1772         if(!found)
1773             return target;
1774     }
1775     else {
1776         if(attr == SP_ATTR_IN)
1777             image = prim->image_in;
1778         else if(attr == SP_ATTR_IN2) {
1779             if(SP_IS_FEBLEND(prim))
1780                 image = SP_FEBLEND(prim)->in2;
1781             else if(SP_IS_FECOMPOSITE(prim))
1782                 image = SP_FECOMPOSITE(prim)->in2;
1783             else if(SP_IS_FEDISPLACEMENTMAP(prim))
1784                 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1785             else
1786                 return target;
1787         }
1788         else
1789             return target;
1790     }
1792     if(image >= 0) {
1793         for(Gtk::TreeIter i = _model->children().begin();
1794             i != start; ++i) {
1795             if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1796                 target = i;
1797         }
1798         return target;
1799     }
1800     else if(image < -1) {
1801         src_id = -(image + 2);
1802         return start;
1803     }
1805     return target;
1808 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1810     int i = 0;
1811     for(Gtk::TreeIter iter = _model->children().begin();
1812         iter != target; ++iter, ++i);
1813     return i;
1816 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1818     Gtk::TreePath path;
1819     Gtk::TreeViewColumn* col;
1820     const int x = (int)e->x, y = (int)e->y;
1821     int cx, cy;
1823     _drag_prim = 0;
1825     if(get_path_at_pos(x, y, path, col, cx, cy)) {
1826         Gtk::TreeIter iter = _model->get_iter(path);
1827         std::vector<Gdk::Point> points;
1829         _drag_prim = (*iter)[_columns.primitive];
1830         const int icnt = input_count(_drag_prim);
1832         for(int i = 0; i < icnt; ++i) {
1833             if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1834                 _in_drag = i + 1;
1835                 break;
1836             }
1837         }
1839         queue_draw();
1840     }
1842     if(_in_drag) {
1843         _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1844         _autoscroll = 0;
1845         get_selection()->select(path);
1846         return true;
1847     }
1848     else
1849         return Gtk::TreeView::on_button_press_event(e);
1852 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1854     const int speed = 10;
1855     const int limit = 15;
1857     Gdk::Rectangle vis;
1858     get_visible_rect(vis);
1859     int vis_x, vis_y;
1860     tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1861     const int top = vis_y + vis.get_height();
1863     // When autoscrolling during a connection drag, set the speed based on
1864     // where the mouse is in relation to the edges.
1865     if(e->y < vis_y)
1866         _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1867     else if(e->y < vis_y + limit)
1868         _autoscroll = -speed;
1869     else if(e->y > top)
1870         _autoscroll = (int)(speed + (e->y - top) / 5);
1871     else if(e->y > top - limit)
1872         _autoscroll = speed;
1873     else
1874         _autoscroll = 0;
1876     queue_draw();
1878     return Gtk::TreeView::on_motion_notify_event(e);
1881 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1883     SPFilterPrimitive *prim = get_selected(), *target;
1885     _scroll_connection.disconnect();
1887     if(_in_drag && prim) {
1888         Gtk::TreePath path;
1889         Gtk::TreeViewColumn* col;
1890         int cx, cy;
1892         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1893             const gchar *in_val = 0;
1894             Glib::ustring result;
1895             Gtk::TreeIter target_iter = _model->get_iter(path);
1896             target = (*target_iter)[_columns.primitive];
1897             col = get_column(1);
1899             Gdk::Rectangle rct;
1900             get_cell_area(path, *col, rct);
1901             const int twidth = _connection_cell.get_text_width();
1902             const int sources_x = rct.get_width() - twidth * FPInputConverter.end;
1903             if(cx > sources_x) {
1904                 int src = (cx - sources_x) / twidth;
1905                 if(src < 0)
1906                     src = 0;
1907                 else if(src >= FPInputConverter.end)
1908                     src = FPInputConverter.end - 1;
1909                 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1910                 in_val = result.c_str();
1911             }
1912             else {
1913                 // Ensure that the target comes before the selected primitive
1914                 for(Gtk::TreeIter iter = _model->children().begin();
1915                     iter != get_selection()->get_selected(); ++iter) {
1916                     if(iter == target_iter) {
1917                         Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1918                         // Make sure the target has a result
1919                         const gchar *gres = repr->attribute("result");
1920                         if(!gres) {
1921                             result = "result" + Glib::Ascii::dtostr(SP_FILTER(prim->parent)->_image_number_next);
1922                             repr->setAttribute("result", result.c_str());
1923                             in_val = result.c_str();
1924                         }
1925                         else
1926                             in_val = gres;
1927                         break;
1928                     }
1929                 }
1930             }
1932             if(SP_IS_FEMERGE(prim)) {
1933                 int c = 1;
1934                 bool handled = false;
1935                 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1936                     if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1937                         // If input is null, delete it
1938                         if(!in_val) {
1939                             sp_repr_unparent(o->repr);
1940                             sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1941                                              _("Remove merge node"));
1942                             (*get_selection()->get_selected())[_columns.primitive] = prim;
1943                         }
1944                         else
1945                             _dialog.set_attr(o, SP_ATTR_IN, in_val);
1946                         handled = true;
1947                     }
1948                 }
1949                 // Add new input?
1950                 if(!handled && c == _in_drag && in_val) {
1951                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1952                     Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1953                     repr->setAttribute("inkscape:collect", "always");
1954                     prim->repr->appendChild(repr);
1955                     SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1956                     Inkscape::GC::release(repr);
1957                     _dialog.set_attr(node, SP_ATTR_IN, in_val);
1958                     (*get_selection()->get_selected())[_columns.primitive] = prim;
1959                 }
1960             }
1961             else {
1962                 if(_in_drag == 1)
1963                     _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1964                 else if(_in_drag == 2)
1965                     _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1966             }
1967         }
1969         _in_drag = 0;
1970         queue_draw();
1972         _dialog.update_settings_view();
1973     }
1975     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1976         const bool sensitive = get_selected() != NULL;
1977         _primitive_menu->items()[0].set_sensitive(sensitive);
1978         _primitive_menu->items()[1].set_sensitive(sensitive);
1979         _primitive_menu->popup(e->button, e->time);
1981         return true;
1982     }
1983     else
1984         return Gtk::TreeView::on_button_release_event(e);
1987 // Checks all of prim's inputs, removes any that use result
1988 void check_single_connection(SPFilterPrimitive* prim, const int result)
1990     if(prim && result >= 0) {
1992         if(prim->image_in == result)
1993             SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1995         if(SP_IS_FEBLEND(prim)) {
1996             if(SP_FEBLEND(prim)->in2 == result)
1997                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1998         }
1999         else if(SP_IS_FECOMPOSITE(prim)) {
2000             if(SP_FECOMPOSITE(prim)->in2 == result)
2001                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2002         }
2003         else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
2004             if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
2005                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2006         }
2007     }
2010 // Remove any connections going to/from prim_iter that forward-reference other primitives
2011 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
2013     SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
2014     bool before = true;
2016     for(Gtk::TreeIter iter = _model->children().begin();
2017         iter != _model->children().end(); ++iter) {
2018         if(iter == prim_iter)
2019             before = false;
2020         else {
2021             SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
2022             if(before)
2023                 check_single_connection(cur_prim, prim->image_out);
2024             else
2025                 check_single_connection(prim, cur_prim->image_out);
2026         }
2027     }
2030 // Reorder the filter primitives to match the list order
2031 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2033     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2034     int ndx = 0;
2036     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2037         iter != _model->children().end(); ++iter, ++ndx) {
2038         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2039         if(prim && prim == _drag_prim) {
2040             SP_OBJECT_REPR(prim)->setPosition(ndx);
2041             break;
2042         }
2043     }
2045     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2046         iter != _model->children().end(); ++iter, ++ndx) {
2047         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2048         if(prim && prim == _drag_prim) {
2049             sanitize_connections(iter);
2050             get_selection()->select(iter);
2051             break;
2052         }
2053     }
2055     filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2057     sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2060 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2061 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2063     if(_autoscroll) {
2064         Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2065         double v;
2067         v = a.get_value() + _autoscroll;
2068         if(v < 0)
2069             v = 0;
2070         if(v > a.get_upper() - a.get_page_size())
2071             v = a.get_upper() - a.get_page_size();
2073         a.set_value(v);
2075         queue_draw();
2076     }
2078     return true;
2081 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2083     return _model->children().size();
2086 /*** FilterEffectsDialog ***/
2088 FilterEffectsDialog::FilterEffectsDialog()
2089     : UI::Widget::Panel("", "dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2090       _filter_modifier(*this),
2091       _primitive_list(*this),
2092       _add_primitive_type(FPConverter),
2093       _add_primitive(_("Add Effect:")),
2094       _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2095       _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2096       _settings_initialized(false),
2097       _locked(false),
2098       _attr_lock(false)
2100     _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2101                              NR_FILTER_ENDPRIMITIVETYPE);
2102     _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2103                              1);
2104     _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2105     _sizegroup->set_ignore_hidden();
2107     _add_primitive_type.remove_row(NR_FILTER_TILE);
2108     _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2110     // Initialize widget hierarchy
2111     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2112     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2113     Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox);
2114     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2116     _getContents()->add(*hpaned);
2117     hpaned->pack1(_filter_modifier);
2118     hpaned->pack2(_primitive_box);
2119     _primitive_box.pack_start(*sw_prims);
2120     _primitive_box.pack_start(*infobox,false, false);
2121     _primitive_box.pack_start(*hb_prims, false, false);
2122     sw_prims->add(_primitive_list);
2123     infobox->pack_start(_infobox_icon, false, false);
2124     infobox->pack_start(_infobox_desc, false, false);
2125     _infobox_desc.set_line_wrap(true);
2127     hb_prims->pack_end(_add_primitive_type, false, false);
2128     hb_prims->pack_end(_add_primitive, false, false);
2129     _getContents()->pack_start(_settings_tabs, false, false);
2130     _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2131     _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2133     _primitive_list.signal_primitive_changed().connect(
2134         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2135     _filter_modifier.signal_filter_changed().connect(
2136         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2138     _add_primitive_type.signal_changed().connect(
2139         sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2141     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2142     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2143 //    al_settings->set_padding(0, 0, 12, 0);
2144 //    fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2145 //    ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2146     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2147     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2148                                                sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2150     show_all_children();
2151     init_settings_widgets();
2152     _primitive_list.update();
2153     update_primitive_infobox();
2156 FilterEffectsDialog::~FilterEffectsDialog()
2158     delete _settings;
2159     delete _filter_general_settings;
2162 void FilterEffectsDialog::set_attrs_locked(const bool l)
2164     _locked = l;
2167 void FilterEffectsDialog::show_all_vfunc()
2169     UI::Widget::Panel::show_all_vfunc();
2171     update_settings_view();
2174 void FilterEffectsDialog::init_settings_widgets()
2176     // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2177     //       most of the current values are complete guesses!
2179     _empty_settings.set_sensitive(false);
2180     _settings_tab1.pack_start(_empty_settings);
2181     
2182     _no_filter_selected.set_sensitive(false);
2183     _settings_tab2.pack_start(_no_filter_selected);
2184     _settings_initialized = true;
2186     _filter_general_settings->type(0);
2187     _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"));
2188     _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"));
2190     _settings->type(NR_FILTER_BLEND);
2191     _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode"), BlendModeConverter);
2193     _settings->type(NR_FILTER_COLORMATRIX);
2194     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."));
2195     _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s)"));
2196     colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2198     _settings->type(NR_FILTER_COMPONENTTRANSFER);
2199     _settings->add_notimplemented();
2200     /*_settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2201     _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2202     _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2203     _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2204     _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2205     _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2207     _settings->type(NR_FILTER_COMPOSITE);
2208     _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
2209     _k1 = _settings->add_spinslider(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."));
2210     _k2 = _settings->add_spinslider(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."));
2211     _k3 = _settings->add_spinslider(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."));
2212     _k4 = _settings->add_spinslider(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."));
2214     _settings->type(NR_FILTER_CONVOLVEMATRIX);
2215     _convolve_order = _settings->add_dualspinbutton(SP_ATTR_ORDER, _("Size"), 1, 5, 1, 1, 0, _("width of the convolve matrix"), _("height of the convolve matrix"));
2216     _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."));
2217     _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."));
2218     _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2219     _settings->add_spinslider(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(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(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(SP_PROP_LIGHTING_COLOR, _("Diffuse Color"), _("Defines the color of the light source"));
2226     _settings->add_spinslider(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(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(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(SP_PROP_FLOOD_COLOR, _("Flood Color"), _("The whole filter region will be filled with this color."));
2238     _settings->add_spinslider(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(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(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(SP_PROP_LIGHTING_COLOR, _("Specular Color"), _("Defines the color of the light source"));
2259     _settings->add_spinslider(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(SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2261     _settings->add_spinslider(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(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, 1, 0.001, 0.01, 3);
2272     _settings->add_spinslider(SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2273     _settings->add_spinslider(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 :