Code

add x,y, width and height parameter settings for <filter> node in a separate tab...
[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 <glibmm/i18n.h>
29 #include "application/application.h"
30 #include "application/editor.h"
31 #include "desktop.h"
32 #include "desktop-handles.h"
33 #include "dialog-manager.h"
34 #include "dir-util.h"
35 #include "document.h"
36 #include "filter-chemistry.h"
37 #include "filter-effects-dialog.h"
38 #include "filter-enums.h"
39 #include "inkscape.h"
40 #include "path-prefix.h"
41 #include "prefs-utils.h"
42 #include "selection.h"
43 #include "sp-feblend.h"
44 #include "sp-fecolormatrix.h"
45 #include "sp-fecomponenttransfer.h"
46 #include "sp-fecomposite.h"
47 #include "sp-feconvolvematrix.h"
48 #include "sp-fedisplacementmap.h"
49 #include "sp-fedistantlight.h"
50 #include "sp-femerge.h"
51 #include "sp-femergenode.h"
52 #include "sp-feoffset.h"
53 #include "sp-fepointlight.h"
54 #include "sp-fespotlight.h"
55 #include "sp-filter-primitive.h"
56 #include "sp-gaussian-blur.h"
58 #include "style.h"
59 #include "svg/svg-color.h"
60 #include "ui/dialog/filedialog.h"
61 #include "verbs.h"
62 #include "xml/node.h"
63 #include "xml/node-observer.h"
64 #include "xml/repr.h"
65 #include <sstream>
67 #include "io/sys.h"
68 #include <iostream>
70 using namespace NR;
72 namespace Inkscape {
73 namespace UI {
74 namespace Dialog {
76 // Returns the number of inputs available for the filter primitive type
77 int input_count(const SPFilterPrimitive* prim)
78 {
79     if(!prim)
80         return 0;
81     else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
82         return 2;
83     else if(SP_IS_FEMERGE(prim)) {
84         // Return the number of feMergeNode connections plus an extra
85         int count = 1;
86         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count);
87         return count;
88     }
89     else
90         return 1;
91 }
93 // Very simple observer that just emits a signal if anything happens to a node
94 class FilterEffectsDialog::SignalObserver : public XML::NodeObserver
95 {
96 public:
97     SignalObserver()
98         : _oldsel(0)
99     {}
101     // Add this observer to the SPObject and remove it from any previous object
102     void set(SPObject* o)
103     {
104         if(_oldsel && _oldsel->repr)
105             _oldsel->repr->removeObserver(*this);
106         if(o && o->repr)
107             o->repr->addObserver(*this);
108         _oldsel = o;
109     }
111     void notifyChildAdded(XML::Node&, XML::Node&, XML::Node*)
112     { signal_changed()(); }
114     void notifyChildRemoved(XML::Node&, XML::Node&, XML::Node*)
115     { signal_changed()(); }
117     void notifyChildOrderChanged(XML::Node&, XML::Node&, XML::Node*, XML::Node*)
118     { signal_changed()(); }
120     void notifyContentChanged(XML::Node&, Util::ptr_shared<char>, Util::ptr_shared<char>)
121     {}
123     void notifyAttributeChanged(XML::Node&, GQuark, Util::ptr_shared<char>, Util::ptr_shared<char>)
124     { signal_changed()(); }
126     sigc::signal<void>& signal_changed()
127     {
128         return _signal_changed;
129     }
130 private:
131     sigc::signal<void> _signal_changed;
132     SPObject* _oldsel;
133 };
135 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
137 public:
138     CheckButtonAttr(const Glib::ustring& label,
139                     const Glib::ustring& tv, const Glib::ustring& fv,
140                     const SPAttributeEnum a)
141         : Gtk::CheckButton(label),
142           AttrWidget(a, true),//TO-DO: receive a defaultvalue parameter in the constructor
143           _true_val(tv), _false_val(fv)
144     {
145         signal_toggled().connect(signal_attr_changed().make_slot());
146     }
148     Glib::ustring get_as_attribute() const
149     {
150         return get_active() ? _true_val : _false_val;
151     }
153     void set_from_attribute(SPObject* o)
154     {
155         const gchar* val = attribute_value(o);
156         if(val) {
157             if(_true_val == val)
158                 set_active(true);
159             else if(_false_val == val)
160                 set_active(false);
161         } else {
162             set_active(get_default()->as_bool());
163         }
164     }
165 private:
166     const Glib::ustring _true_val, _false_val;
167 };
169 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
171 public:
172     SpinButtonAttr(double lower, double upper, double step_inc,
173                    double climb_rate, int digits, const SPAttributeEnum a, double def)
174         : Gtk::SpinButton(climb_rate, digits),
175           AttrWidget(a, def)
176     {
177         set_range(lower, upper);
178         set_increments(step_inc, step_inc * 5);
180         signal_value_changed().connect(signal_attr_changed().make_slot());
181     }
183     Glib::ustring get_as_attribute() const
184     {
185         const double val = get_value();
187         if(get_digits() == 0)
188             return Glib::Ascii::dtostr((int)val);
189         else
190             return Glib::Ascii::dtostr(val);
191     }
193     void set_from_attribute(SPObject* o)
194     {
195         const gchar* val = attribute_value(o);
196         if(val){
197             set_value(Glib::Ascii::strtod(val));
198         } else {
199             set_value(get_default()->as_double());
200         }
201     }
202 };
204 // Contains an arbitrary number of spin buttons that use seperate attributes
205 class MultiSpinButton : public Gtk::HBox
207 public:
208     MultiSpinButton(double lower, double upper, double step_inc,
209                     double climb_rate, int digits, std::vector<SPAttributeEnum> attrs, std::vector<double> default_values)
210     {
211         g_assert(attrs.size()==default_values.size());
212         for(unsigned i = 0; i < attrs.size(); ++i) {
213             _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i], default_values[i]));
214             pack_start(*_spins.back(), false, false);
215         }
216     }
218     ~MultiSpinButton()
219     {
220         for(unsigned i = 0; i < _spins.size(); ++i)
221             delete _spins[i];
222     }
224     std::vector<SpinButtonAttr*>& get_spinbuttons()
225     {
226         return _spins;
227     }
228 private:
229     std::vector<SpinButtonAttr*> _spins;
230 };
232 // Contains two spinbuttons that describe a NumberOptNumber
233 class DualSpinButton : public Gtk::HBox, public AttrWidget
235 public:
236     DualSpinButton(double lower, double upper, double step_inc,
237                    double climb_rate, int digits, const SPAttributeEnum a)
238         : AttrWidget(a), //TO-DO: receive default num-opt-num as parameter in the constructor
239           _s1(climb_rate, digits), _s2(climb_rate, digits)
240     {
241         _s1.set_range(lower, upper);
242         _s2.set_range(lower, upper);
243         _s1.set_increments(step_inc, step_inc * 5);
244         _s2.set_increments(step_inc, step_inc * 5);
246         _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
247         _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
249         pack_start(_s1, false, false);
250         pack_start(_s2, false, false);
251     }
253     Gtk::SpinButton& get_spinbutton1()
254     {
255         return _s1;
256     }
258     Gtk::SpinButton& get_spinbutton2()
259     {
260         return _s2;
261     }
263     virtual Glib::ustring get_as_attribute() const
264     {
265         double v1 = _s1.get_value();
266         double v2 = _s2.get_value();
268         if(_s1.get_digits() == 0) {
269             v1 = (int)v1;
270             v2 = (int)v2;
271         }
273         return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
274     }
276     virtual void set_from_attribute(SPObject* o)
277     {
278         const gchar* val = attribute_value(o);
279         NumberOptNumber n;
280         if(val) {
281             n.set(val);
282         } else {
283             n.set("0 0"); //TO-DO: replace this line by the next one that is currently commented out
284 //            n.set(default_value(o));
285         }
286         _s1.set_value(n.getNumber());
287         _s2.set_value(n.getOptNumber());
288         
289     }
290 private:
291     Gtk::SpinButton _s1, _s2;
292 };
294 class ColorButton : public Gtk::ColorButton, public AttrWidget
296 public:
297     ColorButton(const SPAttributeEnum a)
298         : AttrWidget(a)
299     {
300         signal_color_set().connect(signal_attr_changed().make_slot());
302         Gdk::Color col;
303         col.set_rgb(65535, 65535, 65535);
304         set_color(col);
305     }
307     // Returns the color in 'rgb(r,g,b)' form.
308     Glib::ustring get_as_attribute() const
309     {
310         std::ostringstream os;
311         const Gdk::Color c = get_color();
312         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?
313         os << "rgb(" << r << "," << g << "," << b << ")";
314         return os.str();
315     }
318     void set_from_attribute(SPObject* o)
319     {
320         const gchar* val = attribute_value(o);
321         guint32 i = 0;
322         if(val) {
323             i = sp_svg_read_color(val, 0xFFFFFFFF);
324         } else {
325             //TO-DO: read from constructor attribute
326             //i = default_value(o);
327         }
328         const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
329         Gdk::Color col;
330         col.set_rgb(r * 256, g * 256, b * 256);
331         set_color(col);
332     }
333 };
335 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
336 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
338 public:
339     MatrixAttr(const SPAttributeEnum a)
340         : AttrWidget(a), _locked(false)
341     {
342         _model = Gtk::ListStore::create(_columns);
343         _tree.set_model(_model);
344         _tree.set_headers_visible(false);
345         _tree.show();
346         add(_tree);
347         set_shadow_type(Gtk::SHADOW_IN);
348     }
350     std::vector<double> get_values() const
351     {
352         std::vector<double> vec;
353         for(Gtk::TreeIter iter = _model->children().begin();
354             iter != _model->children().end(); ++iter) {
355             for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
356                 vec.push_back((*iter)[_columns.cols[c]]);
357         }
358         return vec;
359     }
361     void set_values(const std::vector<double>& v)
362     {
363         unsigned i = 0;
364         for(Gtk::TreeIter iter = _model->children().begin();
365             iter != _model->children().end(); ++iter) {
366             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
367                 if(i >= v.size())
368                     return;
369                 (*iter)[_columns.cols[c]] = v[i];
370                 ++i;
371             }
372         }
373     }
375     Glib::ustring get_as_attribute() const
376     {
377         std::ostringstream os;
379         for(Gtk::TreeIter iter = _model->children().begin();
380             iter != _model->children().end(); ++iter) {
381             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
382                 os << (*iter)[_columns.cols[c]] << " ";
383             }
384         }
386         return os.str();
387     }
389     void set_from_attribute(SPObject* o)
390     {
391         if(o) {
392             if(SP_IS_FECONVOLVEMATRIX(o)) {
393                 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
394                 int cols, rows;
395                 cols = (int)conv->order.getNumber();
396                 if(cols > 5)
397                     cols = 5;
398                 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
399                 update(o, rows, cols);
400             }
401             else if(SP_IS_FECOLORMATRIX(o))
402                 update(o, 4, 5);
403         }
404     }
405 private:
406     class MatrixColumns : public Gtk::TreeModel::ColumnRecord
407     {
408     public:
409         MatrixColumns()
410         {
411             cols.resize(5);
412             for(unsigned i = 0; i < cols.size(); ++i)
413                 add(cols[i]);
414         }
415         std::vector<Gtk::TreeModelColumn<double> > cols;
416     };
418     void update(SPObject* o, const int rows, const int cols)
419     {
420         if(_locked)
421             return;
423         _model->clear();
425         _tree.remove_all_columns();
427         std::vector<gdouble>* values = NULL;
428         if(SP_IS_FECOLORMATRIX(o))
429             values = &SP_FECOLORMATRIX(o)->values;
430         else if(SP_IS_FECONVOLVEMATRIX(o))
431             values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
432         else
433             return;
435         if(o) {
436             int ndx = 0;
438             for(int i = 0; i < cols; ++i) {
439                 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
440                 dynamic_cast<Gtk::CellRendererText*>(
441                     _tree.get_column_cell_renderer(i))->signal_edited().connect(
442                         sigc::mem_fun(*this, &MatrixAttr::rebind));
443             }
445             for(int r = 0; r < rows; ++r) {
446                 Gtk::TreeRow row = *(_model->append());
447                 // Default to identity matrix
448                 for(int c = 0; c < cols; ++c, ++ndx)
449                     row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
450             }
451         }
452     }
454     void rebind(const Glib::ustring&, const Glib::ustring&)
455     {
456         _locked = true;
457         signal_attr_changed()();
458         _locked = false;
459     }
461     bool _locked;
462     Gtk::TreeView _tree;
463     Glib::RefPtr<Gtk::ListStore> _model;
464     MatrixColumns _columns;
465 };
467 // Displays a matrix or a slider for feColorMatrix
468 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
470 public:
471     ColorMatrixValues()
472         : AttrWidget(SP_ATTR_VALUES),
473           _matrix(SP_ATTR_VALUES),
474           _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
475           _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
476           _label(_("None"), Gtk::ALIGN_LEFT),
477           _use_stored(false),
478           _saturation_store(0),
479           _angle_store(0)
480     {
481         _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
482         _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
483         _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
484         signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
486         _matrix.show();
487         _saturation.show();
488         _angle.show();
489         _label.show();
490         _label.set_sensitive(false);
492         set_shadow_type(Gtk::SHADOW_NONE);
493     }
495     virtual void set_from_attribute(SPObject* o)
496     {
497         if(SP_IS_FECOLORMATRIX(o)) {
498             SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
499             remove();
500             switch(col->type) {
501                 case COLORMATRIX_SATURATE:
502                     add(_saturation);
503                     if(_use_stored)
504                         _saturation.set_value(_saturation_store);
505                     else
506                         _saturation.set_from_attribute(o);
507                     break;
508                 case COLORMATRIX_HUEROTATE:
509                     add(_angle);
510                     if(_use_stored)
511                         _angle.set_value(_angle_store);
512                     else
513                         _angle.set_from_attribute(o);
514                     break;
515                 case COLORMATRIX_LUMINANCETOALPHA:
516                     add(_label);
517                     break;
518                 case COLORMATRIX_MATRIX:
519                 default:
520                     add(_matrix);
521                     if(_use_stored)
522                         _matrix.set_values(_matrix_store);
523                     else
524                         _matrix.set_from_attribute(o);
525                     break;
526             }
527             _use_stored = true;
528         }
529     }
531     virtual Glib::ustring get_as_attribute() const
532     {
533         const Widget* w = get_child();
534         if(w == &_label)
535             return "";
536         else
537             return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
538     }
540     void clear_store()
541     {
542         _use_stored = false;
543     }
544 private:
545     void update_store()
546     {
547         const Widget* w = get_child();
548         if(w == &_matrix)
549             _matrix_store = _matrix.get_values();
550         else if(w == &_saturation)
551             _saturation_store = _saturation.get_value();
552         else if(w == &_angle)
553             _angle_store = _angle.get_value();
554     }
556     MatrixAttr _matrix;
557     SpinSlider _saturation;
558     SpinSlider _angle;
559     Gtk::Label _label;
561     // Store separate values for the different color modes
562     bool _use_stored;
563     std::vector<double> _matrix_store;
564     double _saturation_store;
565     double _angle_store;
566 };
568 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
570 //Displays a chooser for feImage input
571 //It may be a filename or the id for an SVG Element
572 //described in xlink:href syntax
573 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
575 public:
576     FileOrElementChooser(const SPAttributeEnum a)
577         : AttrWidget(a)
578     {
579         pack_start(_entry, false, false);
580         pack_start(_fromFile, false, false);
581         //pack_start(_fromSVGElement, false, false);
583         _fromFile.set_label(_("Image File"));
584         _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
586         _fromSVGElement.set_label(_("Selected SVG Element"));
587         _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
589         _entry.signal_changed().connect(signal_attr_changed().make_slot());
591         show_all();
593     }
595     // Returns the element in xlink:href form.
596     Glib::ustring get_as_attribute() const
597     {
598         return _entry.get_text();
599     }
602     void set_from_attribute(SPObject* o)
603     {
604         const gchar* val = attribute_value(o);
605         if(val) {
606             _entry.set_text(val);
607         } else {
608             _entry.set_text("");
609         }
610     }
612     void set_desktop(SPDesktop* d){
613         _desktop = d;
614     }
616 private:
617     void select_svg_element(){
618         Inkscape::Selection* sel = sp_desktop_selection(_desktop);
619         if (sel->isEmpty()) return;
620         Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
621         if (!node || !node->matchAttributeName("id")) return;
623         std::ostringstream xlikhref;
624         xlikhref << "#" << node->attribute("id");
625         _entry.set_text(xlikhref.str());
626     }
628     void select_file(){
630         //# Get the current directory for finding files
631         Glib::ustring open_path;
632         char *attr = (char *)prefs_get_string_attribute("dialogs.open", "path");
633         if (attr)
634             open_path = attr;
636         //# Test if the open_path directory exists
637         if (!Inkscape::IO::file_test(open_path.c_str(),
638                   (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
639             open_path = "";
641         //# If no open path, default to our home directory
642         if (open_path.size() < 1)
643             {
644             open_path = g_get_home_dir();
645             open_path.append(G_DIR_SEPARATOR_S);
646             }
648         //# Create a dialog if we don't already have one
649         if (!selectFeImageFileInstance) {
650             selectFeImageFileInstance =
651                   Inkscape::UI::Dialog::FileOpenDialog::create(
652                      *_desktop->getToplevel(),
653                      open_path,
654                      Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not justy svg*/
655                      (char const *)_("Select an image to be used as feImage input"));
656         }
658         //# Show the dialog
659         bool const success = selectFeImageFileInstance->show();
660         if (!success)
661             return;
663         //# User selected something.  Get name and type
664         Glib::ustring fileName = selectFeImageFileInstance->getFilename();
666         if (fileName.size() > 0) {
668             Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
670             if ( newFileName.size() > 0)
671                 fileName = newFileName;
672             else
673                 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
675             open_path = fileName;
676             open_path.append(G_DIR_SEPARATOR_S);
677             prefs_set_string_attribute("dialogs.open", "path", open_path.c_str());
679             _entry.set_text(fileName);
680         }
681         return;
682     }
684     Gtk::Entry _entry;
685     Gtk::Button _fromFile;
686     Gtk::Button _fromSVGElement;
687     SPDesktop* _desktop;
688 };
690 class FilterEffectsDialog::Settings
692 public:
693     typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
695     Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
696         : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
697     {
698         _groups.resize(_max_types);
699         _attrwidgets.resize(_max_types);
700         _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
702         for(int i = 0; i < _max_types; ++i) {
703             _groups[i] = new Gtk::VBox;
704             b.pack_start(*_groups[i], false, false);
705         }
706         _current_type = 0;
707     }
709     ~Settings()
710     {
711         for(int i = 0; i < _max_types; ++i) {
712             delete _groups[i];
713             for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
714                 delete _attrwidgets[i][j];
715         }
716     }
718     // Show the active settings group and update all the AttrWidgets with new values
719     void show_and_update(const int t, SPObject* ob)
720     {
721         if(t != _current_type) {
722             type(t);
723             for(unsigned i = 0; i < _groups.size(); ++i)
724                 _groups[i]->hide();
725         }
726         if(t >= 0)
727             _groups[t]->show_all();
729         _dialog.set_attrs_locked(true);
730         for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
731             _attrwidgets[_current_type][i]->set_from_attribute(ob);
732         _dialog.set_attrs_locked(false);
733     }
735     int get_current_type() const
736     {
737         return _current_type;
738     }
740     void type(const int t)
741     {
742         _current_type = t;
743     }
745     void add_notimplemented()
746     {
747         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
749         add_widget(lbl, "");
750     }
752     // LightSource
753     LightSourceControl* add_lightsource();
755     // CheckBox
756     CheckButtonAttr* add_checkbutton(const SPAttributeEnum attr, const Glib::ustring& label,
757                                      const Glib::ustring& tv, const Glib::ustring& fv)
758     {
759         CheckButtonAttr* cb = new CheckButtonAttr(label, tv, fv, attr);
760         add_widget(cb, "");
761         add_attr_widget(cb);
762         return cb;
763     }
765     // ColorButton
766     ColorButton* add_color(const SPAttributeEnum attr, const Glib::ustring& label)
767     {
768         ColorButton* col = new ColorButton(attr);
769         add_widget(col, label);
770         add_attr_widget(col);
771         return col;
772     }
774     // Matrix
775     MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label)
776     {
777         MatrixAttr* conv = new MatrixAttr(attr);
778         add_widget(conv, label);
779         add_attr_widget(conv);
780         return conv;
781     }
783     // ColorMatrixValues
784     ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
785     {
786         ColorMatrixValues* cmv = new ColorMatrixValues;
787         add_widget(cmv, label);
788         add_attr_widget(cmv);
789         return cmv;
790     }
792     // SpinSlider
793     SpinSlider* add_spinslider(const SPAttributeEnum attr, const Glib::ustring& label,
794                          const double lo, const double hi, const double step_inc, const double climb, const int digits)
795     {
796         SpinSlider* spinslider = new SpinSlider(lo, lo, hi, step_inc, climb, digits, attr);
797         add_widget(spinslider, label);
798         add_attr_widget(spinslider);
799         return spinslider;
800     }
802     // DualSpinSlider
803     DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
804                                        const double lo, const double hi, const double step_inc,
805                                        const double climb, const int digits)
806     {
807         DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr);
808         add_widget(dss, label);
809         add_attr_widget(dss);
810         return dss;
811     }
813     // DualSpinButton
814     DualSpinButton* add_dualspinbutton(const SPAttributeEnum attr, const Glib::ustring& label,
815                                        const double lo, const double hi, const double step_inc,
816                                        const double climb, const int digits)
817     {
818         DualSpinButton* dsb = new DualSpinButton(lo, hi, step_inc, climb, digits, attr);
819         add_widget(dsb, label);
820         add_attr_widget(dsb);
821         return dsb;
822     }
824     // MultiSpinButton
825     MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
826                                          const Glib::ustring& label, const double lo, const double hi,
827                                          const double step_inc, const double climb, const int digits)
828     {
829         std::vector<SPAttributeEnum> attrs;
830         attrs.push_back(attr1);
831         attrs.push_back(attr2);
833         std::vector<double> default_values;
834         default_values.push_back(def1);
835         default_values.push_back(def2);
836         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values);
837         add_widget(msb, label);
838         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
839             add_attr_widget(msb->get_spinbuttons()[i]);
840         return msb;
841     }
842     MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
843                                          const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
844                                          const double hi, const double step_inc, const double climb, const int digits)
845     {
846         std::vector<SPAttributeEnum> attrs;
847         attrs.push_back(attr1);
848         attrs.push_back(attr2);
849         attrs.push_back(attr3);
851         std::vector<double> default_values;
852         default_values.push_back(def1);
853         default_values.push_back(def2);
854         default_values.push_back(def3);
856         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values);
857         add_widget(msb, label);
858         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
859             add_attr_widget(msb->get_spinbuttons()[i]);
860         return msb;
861     }
863     // FileOrElementChooser
864     FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
865     {
866         FileOrElementChooser* foech = new FileOrElementChooser(attr);
867         foech->set_desktop(_dialog.getDesktop());
868         add_widget(foech, label);
869         add_attr_widget(foech);
870         return foech;
871     }
873     // ComboBoxEnum
874     template<typename T> ComboBoxEnum<T>* add_combo(const SPAttributeEnum attr,
875                                   const Glib::ustring& label,
876                                   const Util::EnumDataConverter<T>& conv)
877     {
878         ComboBoxEnum<T>* combo = new ComboBoxEnum<T>(conv, attr);
879         add_widget(combo, label);
880         add_attr_widget(combo);
881         return combo;
882     }
883 private:
884     void add_attr_widget(AttrWidget* a)
885     {
886         _attrwidgets[_current_type].push_back(a);
887         a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
888     }
890     /* Adds a new settings widget using the specified label. The label will be formatted with a colon
891        and all widgets within the setting group are aligned automatically. */
892     void add_widget(Gtk::Widget* w, const Glib::ustring& label)
893     {
894         Gtk::Label *lbl = 0;
895         Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
896         hb->set_spacing(12);
898         if(label != "") {
899             lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
900             hb->pack_start(*lbl, false, false);
901             _size_group->add_widget(*lbl);
902             lbl->show();
903         }
905         hb->pack_start(*w);
906         _groups[_current_type]->pack_start(*hb);
907         hb->show();
908         w->show();
909     }
911     std::vector<Gtk::VBox*> _groups;
912     Glib::RefPtr<Gtk::SizeGroup> _size_group;
913     FilterEffectsDialog& _dialog;
914     SetAttrSlot _set_attr_slot;
915     std::vector<std::vector< AttrWidget*> > _attrwidgets;
916     int _current_type, _max_types;
917 };
919 // Settings for the three light source objects
920 class FilterEffectsDialog::LightSourceControl : public AttrWidget
922 public:
923     LightSourceControl(FilterEffectsDialog& d)
924         : AttrWidget(SP_ATTR_INVALID),
925           _dialog(d),
926           _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
927           _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
928           _light_source(LightSourceConverter),
929           _locked(false)
930     {
931         _light_box.pack_start(_light_label, false, false);
932         _light_box.pack_start(_light_source);
933         _light_box.show_all();
934         _light_box.set_spacing(12);
935         _dialog._sizegroup->add_widget(_light_label);
937         _box.add(_light_box);
938         _box.reorder_child(_light_box, 0);
939         _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
941         // FIXME: these range values are complete crap
943         _settings.type(LIGHT_DISTANT);
944         _settings.add_spinslider(SP_ATTR_AZIMUTH, _("Azimuth"), 0, 360, 1, 1, 0);
945         _settings.add_spinslider(SP_ATTR_ELEVATION, _("Elevation"), 0, 360, 1, 1, 0);
947         _settings.type(LIGHT_POINT);
948         _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);
950         _settings.type(LIGHT_SPOT);
951         _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);
952         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
953                                       SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
954                                       _("Points At"), -99999, 99999, 1, 100, 0);
955         _settings.add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0);
956         _settings.add_spinslider(SP_ATTR_LIMITINGCONEANGLE, _("Cone Angle"), 1, 100, 1, 1, 0);
957     }
959     Gtk::VBox& get_box()
960     {
961         return _box;
962     }
963 protected:
964     Glib::ustring get_as_attribute() const
965     {
966         return "";
967     }
968     void set_from_attribute(SPObject* o)
969     {
970         if(_locked)
971             return;
973         _locked = true;
975         SPObject* child = o->children;
977         if(SP_IS_FEDISTANTLIGHT(child))
978             _light_source.set_active(0);
979         else if(SP_IS_FEPOINTLIGHT(child))
980             _light_source.set_active(1);
981         else if(SP_IS_FESPOTLIGHT(child))
982             _light_source.set_active(2);
983         else
984             _light_source.set_active(-1);
986         update();
988         _locked = false;
989     }
990 private:
991     void on_source_changed()
992     {
993         if(_locked)
994             return;
996         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
997         if(prim) {
998             _locked = true;
1000             SPObject* child = prim->children;
1001             const int ls = _light_source.get_active_row_number();
1002             // Check if the light source type has changed
1003             if(!(ls == -1 && !child) &&
1004                !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1005                !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1006                !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1007                 if(child)
1008                     sp_repr_unparent(child->repr);
1010                 if(ls != -1) {
1011                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1012                     Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1013                     prim->repr->appendChild(repr);
1014                 }
1016                 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1017                 update();
1018             }
1020             _locked = false;
1021         }
1022     }
1024     void update()
1025     {
1026         _box.hide_all();
1027         _box.show();
1028         _light_box.show_all();
1030         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1031         if(prim && prim->children)
1032             _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1033     }
1035     FilterEffectsDialog& _dialog;
1036     Gtk::VBox _box;
1037     Settings _settings;
1038     Gtk::HBox _light_box;
1039     Gtk::Label _light_label;
1040     ComboBoxEnum<LightSource> _light_source;
1041     bool _locked;
1042 };
1044 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1046     LightSourceControl* ls = new LightSourceControl(_dialog);
1047     add_attr_widget(ls);
1048     add_widget(&ls->get_box(), "");
1049     return ls;
1052 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1053                                           sigc::slot<void> rem)
1055     Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1057     menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1058     Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1059     menu->append(*mi);
1060     mi->signal_activate().connect(rem);
1061     mi->show();
1062     menu->accelerate(parent);
1064     return menu;
1067 /*** FilterModifier ***/
1068 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1069     : _dialog(d), _add(Gtk::Stock::NEW), _observer(new SignalObserver)
1071     Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1072     pack_start(*sw);
1073     pack_start(_add, false, false);
1074     sw->add(_list);
1076     _model = Gtk::ListStore::create(_columns);
1077     _list.set_model(_model);
1078     _cell_toggle.set_active(true);
1079     const int selcol = _list.append_column("", _cell_toggle);
1080     Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1081     if(col)
1082        col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1083     _list.append_column_editable(_("_Filter"), _columns.label);
1084     ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1085         signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1087     sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1088     sw->set_shadow_type(Gtk::SHADOW_IN);
1089     show_all_children();
1090     _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1091     _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1092     _list.signal_button_release_event().connect_notify(
1093         sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1094     _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1095                               sigc::mem_fun(*this, &FilterModifier::remove_filter));
1096     _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1097                                  _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1098     _menu->accelerate(*this);
1100     _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1101     _observer->signal_changed().connect(signal_filter_changed().make_slot());
1102     g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1103                      G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1105     g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1106                      G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1108     on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1109     update_filters();
1112 FilterEffectsDialog::FilterModifier::~FilterModifier()
1114    _resource_changed.disconnect();
1115    _doc_replaced.disconnect();
1118 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1120     me->_doc_replaced.disconnect();
1121     me->_doc_replaced = desktop->connectDocumentReplaced(
1122         sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1124     me->_resource_changed.disconnect();
1125     me->_resource_changed =
1126         sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1127                                               sigc::mem_fun(me, &FilterModifier::update_filters));
1129     me->_dialog.setDesktop(desktop);
1131     me->update_filters();
1135 // When the selection changes, show the active filter(s) in the dialog
1136 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1137                                                                        Selection *sel,
1138                                                                        FilterModifier* fm)
1140     if(fm && sel)
1141         fm->update_selection(sel);
1144 // Update each filter's sel property based on the current object selection;
1145 //  If the filter is not used by any selected object, sel = 0,
1146 //  otherwise sel is set to the total number of filters in use by selected objects
1147 //  If only one filter is in use, it is selected
1148 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1150     std::set<SPObject*> used;
1152     for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1153         SPObject *obj = SP_OBJECT (i->data);
1154         SPStyle *style = SP_OBJECT_STYLE (obj);
1155         if(!style || !SP_IS_ITEM(obj)) continue;
1157         if(style->filter.set && style->getFilter())
1158             used.insert(style->getFilter());
1159         else
1160             used.insert(0);
1161     }
1163     const int size = used.size();
1165     for(Gtk::TreeIter iter = _model->children().begin();
1166         iter != _model->children().end(); ++iter) {
1167         if(used.find((*iter)[_columns.filter]) != used.end()) {
1168             // If only one filter is in use by the selection, select it
1169             if(size == 1)
1170                 _list.get_selection()->select(iter);
1171             (*iter)[_columns.sel] = size;
1172         }
1173         else
1174             (*iter)[_columns.sel] = 0;
1175     }
1178 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1180     _observer->set(get_selected_filter());
1181     signal_filter_changed()();
1184 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1186     Gtk::TreeModel::iterator iter = _model->get_iter(path);
1188     if(iter) {
1189         SPFilter* filter = (*iter)[_columns.filter];
1190         filter->setLabel(text.c_str());
1191         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1192         if(iter)
1193             (*iter)[_columns.label] = text;
1194     }
1197 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1199     Gtk::TreeIter iter = _model->get_iter(path);
1201     if(iter) {
1202         SPDesktop *desktop = _dialog.getDesktop();
1203         SPDocument *doc = sp_desktop_document(desktop);
1204         SPFilter* filter = (*iter)[_columns.filter];
1205         Inkscape::Selection *sel = sp_desktop_selection(desktop);
1207         /* If this filter is the only one used in the selection, unset it */
1208         if((*iter)[_columns.sel] == 1)
1209             filter = 0;
1211         GSList const *items = sel->itemList();
1213         for (GSList const *i = items; i != NULL; i = i->next) {
1214             SPItem * item = SP_ITEM(i->data);
1215             SPStyle *style = SP_OBJECT_STYLE(item);
1216             g_assert(style != NULL);
1218             if(filter)
1219                 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1220             else
1221                 ::remove_filter(item, false);
1223             SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1224         }
1226         update_selection(sel);
1227         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS,  _("Apply filter"));
1228     }
1231 /* Add all filters in the document to the combobox.
1232    Keeps the same selection if possible, otherwise selects the first element */
1233 void FilterEffectsDialog::FilterModifier::update_filters()
1235     SPDesktop* desktop = _dialog.getDesktop();
1236     SPDocument* document = sp_desktop_document(desktop);
1237     const GSList* filters = sp_document_get_resource_list(document, "filter");
1239     _model->clear();
1241     for(const GSList *l = filters; l; l = l->next) {
1242         Gtk::TreeModel::Row row = *_model->append();
1243         SPFilter* f = (SPFilter*)l->data;
1244         row[_columns.filter] = f;
1245         const gchar* lbl = f->label();
1246         const gchar* id = SP_OBJECT_ID(f);
1247         row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1248     }
1250     update_selection(desktop->selection);
1251     _dialog.update_filter_general_settings_view();
1254 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1256     if(_list.get_selection()) {
1257         Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1259         if(i)
1260             return (*i)[_columns.filter];
1261     }
1263     return 0;
1266 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1268     if(filter) {
1269         for(Gtk::TreeModel::iterator i = _model->children().begin();
1270             i != _model->children().end(); ++i) {
1271             if((*i)[_columns.filter] == filter) {
1272                 _list.get_selection()->select(i);
1273                 break;
1274             }
1275         }
1276     }
1279 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1281     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1282         const bool sensitive = get_selected_filter() != NULL;
1283         _menu->items()[0].set_sensitive(sensitive);
1284         _menu->items()[1].set_sensitive(sensitive);
1285         _menu->popup(event->button, event->time);
1286     }
1289 void FilterEffectsDialog::FilterModifier::add_filter()
1291     SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1292     SPFilter* filter = new_filter(doc);
1294     const int count = _model->children().size();
1295     std::ostringstream os;
1296     os << "filter" << count;
1297     filter->setLabel(os.str().c_str());
1299     update_filters();
1301     select_filter(filter);
1303     sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1306 void FilterEffectsDialog::FilterModifier::remove_filter()
1308     SPFilter *filter = get_selected_filter();
1310     if(filter) {
1311         SPDocument* doc = filter->document;
1312         sp_repr_unparent(filter->repr);
1314         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1316         update_filters();
1317     }
1320 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1322     SPFilter* filter = get_selected_filter();
1324     if(filter) {
1325         Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1326         repr = repr->duplicate(repr->document());
1327         parent->appendChild(repr);
1329         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1331         update_filters();
1332     }
1335 void FilterEffectsDialog::FilterModifier::rename_filter()
1337     _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1340 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1341     : Glib::ObjectBase(typeid(CellRendererConnection)),
1342       _primitive(*this, "primitive", 0)
1343 {}
1345 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1347     return _primitive.get_proxy();
1350 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1352     _text_width = w;
1355 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1357     return _text_width;
1360 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1361     Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1362     int* x_offset, int* y_offset, int* width, int* height) const
1364     PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1366     if(x_offset)
1367         (*x_offset) = 0;
1368     if(y_offset)
1369         (*y_offset) = 0;
1370     if(width)
1371         (*width) = size * primlist.primitive_count() + _text_width * 7;
1372     if(height) {
1373         // Scale the height depending on the number of inputs, unless it's
1374         // the first primitive, in which case there are no connections
1375         SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1376         (*height) = size * input_count(prim);
1377     }
1380 /*** PrimitiveList ***/
1381 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1382     : _dialog(d),
1383       _in_drag(0),
1384       _observer(new SignalObserver)
1386     d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1388     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1389     signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1391     _model = Gtk::ListStore::create(_columns);
1393     set_reorderable(true);
1395     set_model(_model);
1396     append_column(_("_Effect"), _columns.type);
1398     _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1399     get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1400     signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1402     _connection_cell.set_text_width(init_text());
1404     int cols_count = append_column(_("Connections"), _connection_cell);
1405     Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1406     if(col)
1407        col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1410 // Sets up a vertical Pango context/layout, and returns the largest
1411 // width needed to render the FilterPrimitiveInput labels.
1412 int FilterEffectsDialog::PrimitiveList::init_text()
1414     // Set up a vertical context+layout
1415     Glib::RefPtr<Pango::Context> context = create_pango_context();
1416     const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1417     context->set_matrix(matrix);
1418     _vertical_layout = Pango::Layout::create(context);
1420     int maxfont = 0;
1421     for(int i = 0; i < FPInputConverter.end; ++i) {
1422         _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1423         int fontw, fonth;
1424         _vertical_layout->get_pixel_size(fontw, fonth);
1425         if(fonth > maxfont)
1426             maxfont = fonth;
1427     }
1429     return maxfont;
1432 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1434     return _signal_primitive_changed;
1437 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1439     _observer->set(get_selected());
1440     signal_primitive_changed()();
1441     _dialog._color_matrix_values->clear_store();
1444 /* Add all filter primitives in the current to the list.
1445    Keeps the same selection if possible, otherwise selects the first element */
1446 void FilterEffectsDialog::PrimitiveList::update()
1448     SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1449     const SPFilterPrimitive* active_prim = get_selected();
1450     bool active_found = false;
1452     _model->clear();
1454     if(f) {
1455         _dialog._primitive_box.set_sensitive(true);
1456         _dialog.update_filter_general_settings_view();
1457         for(SPObject *prim_obj = f->children;
1458                 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1459                 prim_obj = prim_obj->next) {
1460             SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1461             if(prim) {
1462                 Gtk::TreeModel::Row row = *_model->append();
1463                 row[_columns.primitive] = prim;
1464                 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1465                 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1466                 row[_columns.id] = SP_OBJECT_ID(prim);
1468                 if(prim == active_prim) {
1469                     get_selection()->select(row);
1470                     active_found = true;
1471                 }
1472             }
1473         }
1475         if(!active_found && _model->children().begin())
1476             get_selection()->select(_model->children().begin());
1478         columns_autosize();
1479     }
1480     else {
1481         _dialog._primitive_box.set_sensitive(false);
1482     }
1485 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1487     _primitive_menu = menu;
1490 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1492     if(_dialog._filter_modifier.get_selected_filter()) {
1493         Gtk::TreeModel::iterator i = get_selection()->get_selected();
1494         if(i)
1495             return (*i)[_columns.primitive];
1496     }
1498     return 0;
1501 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1503     for(Gtk::TreeIter i = _model->children().begin();
1504         i != _model->children().end(); ++i) {
1505         if((*i)[_columns.primitive] == prim)
1506             get_selection()->select(i);
1507     }
1510 void FilterEffectsDialog::PrimitiveList::remove_selected()
1512     SPFilterPrimitive* prim = get_selected();
1514     if(prim) {
1515         _observer->set(0);
1517         sp_repr_unparent(prim->repr);
1519         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1520                          _("Remove filter primitive"));
1522         update();
1523     }
1526 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1528     Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1529     Glib::RefPtr<Gdk::Window> win = get_bin_window();
1530     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1532     SPFilterPrimitive* prim = get_selected();
1533     int row_count = get_model()->children().size();
1535     int fheight = CellRendererConnection::size;
1536     Gdk::Rectangle rct, vis;
1537     Gtk::TreeIter row = get_model()->children().begin();
1538     int text_start_x = 0;
1539     if(row) {
1540         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1541         get_visible_rect(vis);
1542         int vis_x, vis_y;
1543         tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1545         text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter.end + 1) + 1;
1546         for(int i = 0; i < FPInputConverter.end; ++i) {
1547             _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1548             const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1549             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());
1550             get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1551             get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1552         }
1553     }
1555     int row_index = 0;
1556     for(; row != get_model()->children().end(); ++row, ++row_index) {
1557         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1558         const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1560         // Check mouse state
1561         int mx, my;
1562         Gdk::ModifierType mask;
1563         get_bin_window()->get_pointer(mx, my, mask);
1565         // Outline the bottom of the connection area
1566         const int outline_x = x + fheight * (row_count - row_index);
1567         get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1569         // Side outline
1570         get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1572         std::vector<Gdk::Point> con_poly;
1573         int con_drag_y;
1574         bool inside;
1575         const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1576         const int inputs = input_count(row_prim);
1578         if(SP_IS_FEMERGE(row_prim)) {
1579             for(int i = 0; i < inputs; ++i) {
1580                 inside = do_connection_node(row, i, con_poly, mx, my);
1581                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1582                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1583                                                inside, con_poly);
1585                 if(_in_drag == (i + 1))
1586                     con_drag_y = con_poly[2].get_y();
1588                 if(_in_drag != (i + 1) || row_prim != prim)
1589                     draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1590             }
1591         }
1592         else {
1593             // Draw "in" shape
1594             inside = do_connection_node(row, 0, con_poly, mx, my);
1595             con_drag_y = con_poly[2].get_y();
1596             get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1597                                            darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1598                                            inside, con_poly);
1600             // Draw "in" connection
1601             if(_in_drag != 1 || row_prim != prim)
1602                 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1604             if(inputs == 2) {
1605                 // Draw "in2" shape
1606                 inside = do_connection_node(row, 1, con_poly, mx, my);
1607                 if(_in_drag == 2)
1608                     con_drag_y = con_poly[2].get_y();
1609                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1610                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1611                                                inside, con_poly);
1612                 // Draw "in2" connection
1613                 if(_in_drag != 2 || row_prim != prim)
1614                     draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1615             }
1616         }
1618         // Draw drag connection
1619         if(row_prim == prim && _in_drag) {
1620             get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1621                                         mx, con_drag_y);
1622             get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1623         }
1624     }
1626     return true;
1629 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1630                                                          const int text_start_x, const int x1, const int y1,
1631                                                          const int row_count)
1633     int src_id = 0;
1634     Gtk::TreeIter res = find_result(input, attr, src_id);
1635     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1636     Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1637     Glib::RefPtr<Gdk::GC> gc;
1639     const bool is_first = input == get_model()->children().begin();
1640     const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1641     const bool use_default = !res && !is_merge;
1643     if(res == input || (use_default && is_first)) {
1644         // Draw straight connection to a standard input
1645         // Draw a lighter line for an implicit connection to a standard input
1646         const int tw = _connection_cell.get_text_width();
1647         gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1648         gc = (use_default && is_first) ? lightgc : darkgc;
1649         get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1650         get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1651     }
1652     else {
1653         // Draw an 'L'-shaped connection to another filter primitive
1654         // If no connection is specified, draw a light connection to the previous primitive
1655         gc = use_default ? lightgc : darkgc;
1657         if(use_default) {
1658             res = input;
1659             --res;
1660         }
1662         if(res) {
1663             Gdk::Rectangle rct;
1665             get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1666             const int fheight = CellRendererConnection::size;
1668             get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1669             const int row_index = find_index(res);
1670             const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1671             const int y2 = rct.get_y() + rct.get_height();
1673             // Draw a bevelled 'L'-shaped connection
1674             get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1675             get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1676             get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1677         }
1678     }
1681 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1682 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1683                                                             std::vector<Gdk::Point>& points,
1684                                                             const int ix, const int iy)
1686     Gdk::Rectangle rct;
1687     const int icnt = input_count((*row)[_columns.primitive]);
1689     get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1690     const int fheight = CellRendererConnection::size;
1692     get_cell_area(_model->get_path(row), *get_column(1), rct);
1693     const float h = rct.get_height() / icnt;
1695     const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1696     const int con_w = (int)(fheight * 0.35f);
1697     const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1698     points.clear();
1699     points.push_back(Gdk::Point(x, con_y));
1700     points.push_back(Gdk::Point(x, con_y + con_w * 2));
1701     points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1703     return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1706 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1707                                                                     const int attr, int& src_id)
1709     SPFilterPrimitive* prim = (*start)[_columns.primitive];
1710     Gtk::TreeIter target = _model->children().end();
1711     int image;
1713     if(SP_IS_FEMERGE(prim)) {
1714         int c = 0;
1715         bool found = false;
1716         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1717             if(c == attr && SP_IS_FEMERGENODE(o)) {
1718                 image = SP_FEMERGENODE(o)->input;
1719                 found = true;
1720             }
1721         }
1722         if(!found)
1723             return target;
1724     }
1725     else {
1726         if(attr == SP_ATTR_IN)
1727             image = prim->image_in;
1728         else if(attr == SP_ATTR_IN2) {
1729             if(SP_IS_FEBLEND(prim))
1730                 image = SP_FEBLEND(prim)->in2;
1731             else if(SP_IS_FECOMPOSITE(prim))
1732                 image = SP_FECOMPOSITE(prim)->in2;
1733             else if(SP_IS_FEDISPLACEMENTMAP(prim))
1734                 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1735             else
1736                 return target;
1737         }
1738         else
1739             return target;
1740     }
1742     if(image >= 0) {
1743         for(Gtk::TreeIter i = _model->children().begin();
1744             i != start; ++i) {
1745             if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1746                 target = i;
1747         }
1748         return target;
1749     }
1750     else if(image < -1) {
1751         src_id = -(image + 2);
1752         return start;
1753     }
1755     return target;
1758 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1760     int i = 0;
1761     for(Gtk::TreeIter iter = _model->children().begin();
1762         iter != target; ++iter, ++i);
1763     return i;
1766 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1768     Gtk::TreePath path;
1769     Gtk::TreeViewColumn* col;
1770     const int x = (int)e->x, y = (int)e->y;
1771     int cx, cy;
1773     _drag_prim = 0;
1775     if(get_path_at_pos(x, y, path, col, cx, cy)) {
1776         Gtk::TreeIter iter = _model->get_iter(path);
1777         std::vector<Gdk::Point> points;
1779         _drag_prim = (*iter)[_columns.primitive];
1780         const int icnt = input_count(_drag_prim);
1782         for(int i = 0; i < icnt; ++i) {
1783             if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1784                 _in_drag = i + 1;
1785                 break;
1786             }
1787         }
1789         queue_draw();
1790     }
1792     if(_in_drag) {
1793         _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1794         _autoscroll = 0;
1795         get_selection()->select(path);
1796         return true;
1797     }
1798     else
1799         return Gtk::TreeView::on_button_press_event(e);
1802 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1804     const int speed = 10;
1805     const int limit = 15;
1807     Gdk::Rectangle vis;
1808     get_visible_rect(vis);
1809     int vis_x, vis_y;
1810     tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1811     const int top = vis_y + vis.get_height();
1813     // When autoscrolling during a connection drag, set the speed based on
1814     // where the mouse is in relation to the edges.
1815     if(e->y < vis_y)
1816         _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1817     else if(e->y < vis_y + limit)
1818         _autoscroll = -speed;
1819     else if(e->y > top)
1820         _autoscroll = (int)(speed + (e->y - top) / 5);
1821     else if(e->y > top - limit)
1822         _autoscroll = speed;
1823     else
1824         _autoscroll = 0;
1826     queue_draw();
1828     return Gtk::TreeView::on_motion_notify_event(e);
1831 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1833     SPFilterPrimitive *prim = get_selected(), *target;
1835     _scroll_connection.disconnect();
1837     if(_in_drag && prim) {
1838         Gtk::TreePath path;
1839         Gtk::TreeViewColumn* col;
1840         int cx, cy;
1842         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1843             const gchar *in_val = 0;
1844             Glib::ustring result;
1845             Gtk::TreeIter target_iter = _model->get_iter(path);
1846             target = (*target_iter)[_columns.primitive];
1847             col = get_column(1);
1849             Gdk::Rectangle rct;
1850             get_cell_area(path, *col, rct);
1851             const int twidth = _connection_cell.get_text_width();
1852             const int sources_x = rct.get_width() - twidth * FPInputConverter.end;
1853             if(cx > sources_x) {
1854                 int src = (cx - sources_x) / twidth;
1855                 if(src < 0)
1856                     src = 0;
1857                 else if(src >= FPInputConverter.end)
1858                     src = FPInputConverter.end - 1;
1859                 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1860                 in_val = result.c_str();
1861             }
1862             else {
1863                 // Ensure that the target comes before the selected primitive
1864                 for(Gtk::TreeIter iter = _model->children().begin();
1865                     iter != get_selection()->get_selected(); ++iter) {
1866                     if(iter == target_iter) {
1867                         Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1868                         // Make sure the target has a result
1869                         const gchar *gres = repr->attribute("result");
1870                         if(!gres) {
1871                             result = "result" + Glib::Ascii::dtostr(SP_FILTER(prim->parent)->_image_number_next);
1872                             repr->setAttribute("result", result.c_str());
1873                             in_val = result.c_str();
1874                         }
1875                         else
1876                             in_val = gres;
1877                         break;
1878                     }
1879                 }
1880             }
1882             if(SP_IS_FEMERGE(prim)) {
1883                 int c = 1;
1884                 bool handled = false;
1885                 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1886                     if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1887                         // If input is null, delete it
1888                         if(!in_val) {
1889                             sp_repr_unparent(o->repr);
1890                             sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1891                                              _("Remove merge node"));
1892                             (*get_selection()->get_selected())[_columns.primitive] = prim;
1893                         }
1894                         else
1895                             _dialog.set_attr(o, SP_ATTR_IN, in_val);
1896                         handled = true;
1897                     }
1898                 }
1899                 // Add new input?
1900                 if(!handled && c == _in_drag && in_val) {
1901                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1902                     Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1903                     repr->setAttribute("inkscape:collect", "always");
1904                     prim->repr->appendChild(repr);
1905                     SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1906                     Inkscape::GC::release(repr);
1907                     _dialog.set_attr(node, SP_ATTR_IN, in_val);
1908                     (*get_selection()->get_selected())[_columns.primitive] = prim;
1909                 }
1910             }
1911             else {
1912                 if(_in_drag == 1)
1913                     _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1914                 else if(_in_drag == 2)
1915                     _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1916             }
1917         }
1919         _in_drag = 0;
1920         queue_draw();
1922         _dialog.update_settings_view();
1923     }
1925     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1926         const bool sensitive = get_selected() != NULL;
1927         _primitive_menu->items()[0].set_sensitive(sensitive);
1928         _primitive_menu->items()[1].set_sensitive(sensitive);
1929         _primitive_menu->popup(e->button, e->time);
1931         return true;
1932     }
1933     else
1934         return Gtk::TreeView::on_button_release_event(e);
1937 // Checks all of prim's inputs, removes any that use result
1938 void check_single_connection(SPFilterPrimitive* prim, const int result)
1940     if(prim && result >= 0) {
1942         if(prim->image_in == result)
1943             SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1945         if(SP_IS_FEBLEND(prim)) {
1946             if(SP_FEBLEND(prim)->in2 == result)
1947                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1948         }
1949         else if(SP_IS_FECOMPOSITE(prim)) {
1950             if(SP_FECOMPOSITE(prim)->in2 == result)
1951                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1952         }
1953         else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
1954             if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
1955                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1956         }
1957     }
1960 // Remove any connections going to/from prim_iter that forward-reference other primitives
1961 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
1963     SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
1964     bool before = true;
1966     for(Gtk::TreeIter iter = _model->children().begin();
1967         iter != _model->children().end(); ++iter) {
1968         if(iter == prim_iter)
1969             before = false;
1970         else {
1971             SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
1972             if(before)
1973                 check_single_connection(cur_prim, prim->image_out);
1974             else
1975                 check_single_connection(prim, cur_prim->image_out);
1976         }
1977     }
1980 // Reorder the filter primitives to match the list order
1981 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
1983     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
1984     int ndx = 0;
1986     for(Gtk::TreeModel::iterator iter = _model->children().begin();
1987         iter != _model->children().end(); ++iter, ++ndx) {
1988         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
1989         if(prim && prim == _drag_prim) {
1990             SP_OBJECT_REPR(prim)->setPosition(ndx);
1991             break;
1992         }
1993     }
1995     for(Gtk::TreeModel::iterator iter = _model->children().begin();
1996         iter != _model->children().end(); ++iter, ++ndx) {
1997         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
1998         if(prim && prim == _drag_prim) {
1999             sanitize_connections(iter);
2000             get_selection()->select(iter);
2001             break;
2002         }
2003     }
2005     filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2007     sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2010 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2011 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2013     if(_autoscroll) {
2014         Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2015         double v;
2017         v = a.get_value() + _autoscroll;
2018         if(v < 0)
2019             v = 0;
2020         if(v > a.get_upper() - a.get_page_size())
2021             v = a.get_upper() - a.get_page_size();
2023         a.set_value(v);
2025         queue_draw();
2026     }
2028     return true;
2031 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2033     return _model->children().size();
2036 /*** FilterEffectsDialog ***/
2038 FilterEffectsDialog::FilterEffectsDialog()
2039     : UI::Widget::Panel("", "dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2040       _filter_modifier(*this),
2041       _primitive_list(*this),
2042       _add_primitive_type(FPConverter),
2043       _add_primitive(_("Add Effect:")),
2044       _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2045       _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2046       _locked(false),
2047       _attr_lock(false)
2049     _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2050                              NR_FILTER_ENDPRIMITIVETYPE);
2051     _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2052                              1);
2053     _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2054     _sizegroup->set_ignore_hidden();
2056     _add_primitive_type.remove_row(NR_FILTER_TILE);
2057     _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2059     // Initialize widget hierarchy
2060     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2061     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2062     Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox);
2063     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2065     _getContents()->add(*hpaned);
2066     hpaned->pack1(_filter_modifier);
2067     hpaned->pack2(_primitive_box);
2068     _primitive_box.pack_start(*sw_prims);
2069     _primitive_box.pack_start(*infobox,false, false);
2070     _primitive_box.pack_start(*hb_prims, false, false);
2071     sw_prims->add(_primitive_list);
2072     infobox->pack_start(_infobox_icon, false, false);
2073     infobox->pack_start(_infobox_desc, false, false);
2074     _infobox_desc.set_line_wrap(true);
2076     hb_prims->pack_end(_add_primitive_type, false, false);
2077     hb_prims->pack_end(_add_primitive, false, false);
2078     _getContents()->pack_start(_settings_tabs, false, false);
2079     _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2080     _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2082     _primitive_list.signal_primitive_changed().connect(
2083         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2084     _filter_modifier.signal_filter_changed().connect(
2085         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2087     _add_primitive_type.signal_changed().connect(
2088         sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2090     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2091     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2092 //    al_settings->set_padding(0, 0, 12, 0);
2093 //    fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2094 //    ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2095     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2096     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2097                                                sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2099     show_all_children();
2100     init_settings_widgets();
2101     _primitive_list.update();
2102     update_primitive_infobox();
2105 FilterEffectsDialog::~FilterEffectsDialog()
2107     delete _settings;
2108     delete _filter_general_settings;
2111 void FilterEffectsDialog::set_attrs_locked(const bool l)
2113     _locked = l;
2116 void FilterEffectsDialog::show_all_vfunc()
2118     UI::Widget::Panel::show_all_vfunc();
2120     update_settings_view();
2123 void FilterEffectsDialog::init_settings_widgets()
2125     // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2126     //       most of the current values are complete guesses!
2128     _empty_settings.set_sensitive(false);
2129     _settings_tab1.pack_start(_empty_settings);
2130     
2131     _no_filter_selected.set_sensitive(false);
2132     _settings_tab2.pack_start(_no_filter_selected);
2134     _filter_general_settings->type(0);
2135     _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);
2136     _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);
2138     _settings->type(NR_FILTER_BLEND);
2139     _settings->add_combo(SP_ATTR_MODE, _("Mode"), BlendModeConverter);
2141     _settings->type(NR_FILTER_COLORMATRIX);
2142     ComboBoxEnum<FilterColorMatrixType>* colmat = _settings->add_combo(SP_ATTR_TYPE, _("Type"), ColorMatrixTypeConverter);
2143     _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s)"));
2144     colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2146     _settings->type(NR_FILTER_COMPONENTTRANSFER);
2147     _settings->add_notimplemented();
2148     /*_settings->add_combo(SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2149     _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2150     _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2151     _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2152     _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2153     _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2155     _settings->type(NR_FILTER_COMPOSITE);
2156     _settings->add_combo(SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
2157     _k1 = _settings->add_spinslider(SP_ATTR_K1, _("K1"), -10, 10, 0.1, 0.01, 2);
2158     _k2 = _settings->add_spinslider(SP_ATTR_K2, _("K2"), -10, 10, 0.1, 0.01, 2);
2159     _k3 = _settings->add_spinslider(SP_ATTR_K3, _("K3"), -10, 10, 0.1, 0.01, 2);
2160     _k4 = _settings->add_spinslider(SP_ATTR_K4, _("K4"), -10, 10, 0.1, 0.01, 2);
2162     _settings->type(NR_FILTER_CONVOLVEMATRIX);
2163     _convolve_order = _settings->add_dualspinbutton(SP_ATTR_ORDER, _("Size"), 1, 5, 1, 1, 0);
2164     _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);
2165     _convolve_matrix = _settings->add_matrix(SP_ATTR_KERNELMATRIX, _("Kernel"));
2166     _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2167     _settings->add_spinslider(SP_ATTR_DIVISOR, _("Divisor"), 1, 20, 1, 0.1, 2);
2168     _settings->add_spinslider(SP_ATTR_BIAS, _("Bias"), -10, 10, 1, 0.01, 1);
2169     _settings->add_combo(SP_ATTR_EDGEMODE, _("Edge Mode"), ConvolveMatrixEdgeModeConverter);
2170     _settings->add_checkbutton(SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false");
2172     _settings->type(NR_FILTER_DIFFUSELIGHTING);
2173     _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Diffuse Color"));
2174     _settings->add_spinslider(SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1);
2175     _settings->add_spinslider(SP_ATTR_DIFFUSECONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2);
2176     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2177     _settings->add_lightsource();
2179     _settings->type(NR_FILTER_DISPLACEMENTMAP);
2180     _settings->add_spinslider(SP_ATTR_SCALE, _("Scale"), 0, 100, 1, 0.01, 1);
2181     _settings->add_combo(SP_ATTR_XCHANNELSELECTOR, _("X Channel"), DisplacementMapChannelConverter);
2182     _settings->add_combo(SP_ATTR_YCHANNELSELECTOR, _("Y Channel"), DisplacementMapChannelConverter);
2184     _settings->type(NR_FILTER_FLOOD);
2185     _settings->add_color(SP_PROP_FLOOD_COLOR, _("Flood Color"));
2186     _settings->add_spinslider(SP_PROP_FLOOD_OPACITY, _("Opacity"), 0, 1, 0.1, 0.01, 2);
2188     _settings->type(NR_FILTER_GAUSSIANBLUR);
2189     _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation"), 0.01, 100, 1, 0.01, 1);
2191     _settings->type(NR_FILTER_MORPHOLOGY);
2192     _settings->add_combo(SP_ATTR_OPERATOR, _("Operator"), MorphologyOperatorConverter);
2193     _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius"), 0, 100, 1, 0.01, 1);
2195     _settings->type(NR_FILTER_IMAGE);
2196     _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image"));
2198     _settings->type(NR_FILTER_OFFSET);
2199     _settings->add_spinslider(SP_ATTR_DX, _("Delta X"), -100, 100, 1, 0.01, 1);
2200     _settings->add_spinslider(SP_ATTR_DY, _("Delta Y"), -100, 100, 1, 0.01, 1);
2202     _settings->type(NR_FILTER_SPECULARLIGHTING);
2203     _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Specular Color"));
2204     _settings->add_spinslider(SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1);
2205     _settings->add_spinslider(SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2);
2206     _settings->add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1);
2207     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2208     _settings->add_lightsource();
2210     _settings->type(NR_FILTER_TILE);
2211     _settings->add_notimplemented();
2213     _settings->type(NR_FILTER_TURBULENCE);
2214     _settings->add_checkbutton(SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2215     _settings->add_combo(SP_ATTR_TYPE, _("Type"), TurbulenceTypeConverter);
2216     _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency"), 0, 1, 0.001, 0.01, 3);
2217     _settings->add_spinslider(SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2218     _settings->add_spinslider(SP_ATTR_SEED, _("Seed"), 0, 1000, 1, 1, 0);
2221 void FilterEffectsDialog::add_primitive()
2223     SPFilter* filter = _filter_modifier.get_selected_filter();
2225     if(filter) {
2226         SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2228         _primitive_list.select(prim);
2230         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2231     }
2234 void FilterEffectsDialog::update_primitive_infobox()
2236     if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2237         _infobox_icon.show();
2238         _infobox_desc.show();
2239     } else {
2240         _infobox_icon.hide();
2241         _infobox_desc.hide();
2242     }
2243     switch(_add_primitive_type.get_active_data()->id){
2244         case(NR::NR_FILTER_BLEND):
2245             _infobox_icon.set(g_strdup_printf("%s/feBlend-icon.png", INKSCAPE_PIXMAPDIR));
2246             _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2247             break;
2248         case(NR::NR_FILTER_COLORMATRIX):
2249             _infobox_icon.set(g_strdup_printf("%s/feColorMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2250             _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."));
2251             break;
2252         case(NR::NR_FILTER_COMPONENTTRANSFER):
2253             _infobox_icon.set(g_strdup_printf("%s/feComponentTransfer-icon.png", INKSCAPE_PIXMAPDIR));
2254             _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."));
2255             break;
2256         case(NR::NR_FILTER_COMPOSITE):
2257             _infobox_icon.set(g_strdup_printf("%s/feComposite-icon.png", INKSCAPE_PIXMAPDIR));
2258             _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."));
2259             break;
2260         case(NR::NR_FILTER_CONVOLVEMATRIX):
2261             _infobox_icon.set(g_strdup_printf("%s/feConvolveMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2262             _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."));
2263             break;
2264         case(NR::NR_FILTER_DIFFUSELIGHTING):
2265             _infobox_icon.set(g_strdup_printf("%s/feDiffuseLighting-icon.png", INKSCAPE_PIXMAPDIR));
2266             _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."));
2267             break;
2268         case(NR::NR_FILTER_DISPLACEMENTMAP):
2269             _infobox_icon.set(g_strdup_printf("%s/feDisplacementMap-icon.png", INKSCAPE_PIXMAPDIR));
2270             _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."));
2271             break;
2272         case(NR::NR_FILTER_FLOOD):
2273             _infobox_icon.set(g_strdup_printf("%s/feFlood-icon.png", INKSCAPE_PIXMAPDIR));
2274             _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."));
2275             break;
2276         case(NR::NR_FILTER_GAUSSIANBLUR):
2277             _infobox_icon.set(g_strdup_printf("%s/feGaussianBlur-icon.png", INKSCAPE_PIXMAPDIR));
2278             _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."));
2279             break;
2280         case(NR::NR_FILTER_IMAGE):
2281             _infobox_icon.set(g_strdup_printf("%s/feImage-icon.png", INKSCAPE_PIXMAPDIR));
2282             _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2283             break;
2284         case(NR::NR_FILTER_MERGE):
2285             _infobox_icon.set(g_strdup_printf("%s/feMerge-icon.png", INKSCAPE_PIXMAPDIR));
2286             _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."));
2287             break;
2288         case(NR::NR_FILTER_MORPHOLOGY):
2289             _infobox_icon.set(g_strdup_printf("%s/feMorphology-icon.png", INKSCAPE_PIXMAPDIR));
2290             _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."));
2291             break;
2292         case(NR::NR_FILTER_OFFSET):
2293             _infobox_icon.set(g_strdup_printf("%s/feOffset-icon.png", INKSCAPE_PIXMAPDIR));
2294             _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."));
2295             break;
2296         case(NR::NR_FILTER_SPECULARLIGHTING):
2297             _infobox_icon.set(g_strdup_printf("%s/feSpecularLighting-icon.png", INKSCAPE_PIXMAPDIR));
2298             _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."));
2299             break;
2300         case(NR::NR_FILTER_TILE):
2301             _infobox_icon.set(g_strdup_printf("%s/feTile-icon.png", INKSCAPE_PIXMAPDIR));
2302             _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2303             break;
2304         case(NR::NR_FILTER_TURBULENCE):
2305             _infobox_icon.set(g_strdup_printf("%s/feTurbulence-icon.png", INKSCAPE_PIXMAPDIR));
2306             _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."));
2307             break;
2308     }
2311 void FilterEffectsDialog::duplicate_primitive()
2313     SPFilter* filter = _filter_modifier.get_selected_filter();
2314     SPFilterPrimitive* origprim = _primitive_list.get_selected();
2316     if(filter && origprim) {
2317         Inkscape::XML::Node *repr;
2318         repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2319         SP_OBJECT_REPR(filter)->appendChild(repr);
2321         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2323         _primitive_list.update();
2324     }
2327 void FilterEffectsDialog::convolve_order_changed()
2329     _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2330     _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2331     _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2334 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2336     set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2339 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2341     if(!_locked) {
2342         _attr_lock = true;
2343         SPFilter *filter = _filter_modifier.get_selected_filter();
2344         const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2345         if (filter && name && SP_OBJECT_REPR(filter)){
2346             SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2347             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2348         }
2349         _attr_lock = false;
2350     }
2353 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2355     set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2358 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2360     if(!_locked) {
2361         _attr_lock = true;
2363         SPFilter *filter = _filter_modifier.get_selected_filter();
2364         const gchar* name = (const gchar*)sp_attribute_name(attr);
2365         if(filter && name && o) {
2366             update_settings_sensitivity();
2368             SP_OBJECT_REPR(o)->setAttribute(name, val);
2369             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2371             Glib::ustring undokey = "filtereffects:";
2372             undokey += name;
2373             sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2374                                    _("Set filter primitive attribute"));
2375         }
2377         _attr_lock = false;
2378     }
2381 void FilterEffectsDialog::update_filter_general_settings_view()
2383     if(!_locked) {
2384         _attr_lock = true;
2386         SPFilter* filter = _filter_modifier.get_selected_filter();
2388         if(filter) {
2389             _filter_general_settings->show_and_update(0, filter);
2390             _no_filter_selected.hide();
2391         }
2392         else {
2393             std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2394             vect[0]->hide_all();
2395             _no_filter_selected.show();
2396         }
2398         _attr_lock = false;
2399     }
2402 void FilterEffectsDialog::update_settings_view()
2404     update_settings_sensitivity();
2406     if(_attr_lock)
2407         return;
2409 //First Tab
2411     std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2412     for(int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2413     _empty_settings.show();
2415     if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2416         _infobox_icon.show();
2417         _infobox_desc.show();
2418     } else {
2419         _infobox_icon.hide();
2420         _infobox_desc.hide();
2421     }
2422     
2423     SPFilterPrimitive* prim = _primitive_list.get_selected();
2425     if(prim) {
2426         _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2427         _empty_settings.hide();
2428     }
2430 //Second Tab
2432     std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2433     vect2[0]->hide_all();
2434     _no_filter_selected.show();
2436     SPFilter* filter = _filter_modifier.get_selected_filter();
2438     if(filter) {
2439         _filter_general_settings->show_and_update(0, filter);
2440         _no_filter_selected.hide();
2441     }
2445 void FilterEffectsDialog::update_settings_sensitivity()
2447     SPFilterPrimitive* prim = _primitive_list.get_selected();
2448     const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2449     _k1->set_sensitive(use_k);
2450     _k2->set_sensitive(use_k);
2451     _k3->set_sensitive(use_k);
2452     _k4->set_sensitive(use_k);
2454 // Component transfer not yet implemented
2455 /*
2456     if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2457         SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2458         const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2459         const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2461         _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2462         _ct_slope->set_sensitive(linear);
2463         _ct_intercept->set_sensitive(linear);
2464         _ct_amplitude->set_sensitive(gamma);
2465         _ct_exponent->set_sensitive(gamma);
2466         _ct_offset->set_sensitive(gamma);
2467     }
2468 */
2471 void FilterEffectsDialog::update_color_matrix()
2473     _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2476 } // namespace Dialog
2477 } // namespace UI
2478 } // namespace Inkscape
2480 /*
2481   Local Variables:
2482   mode:c++
2483   c-file-style:"stroustrup"
2484   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2485   indent-tabs-mode:nil
2486   fill-column:99
2487   End:
2488 */
2489 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :