Code

* add tooltips to spinsliders, spinbuttons and comboboxes on the filter settings UI
[inkscape.git] / src / ui / dialog / filter-effects-dialog.cpp
1 /**
2  * \brief Filter Effects dialog
3  *
4  * Authors:
5  *   Nicholas Bishop <nicholasbishop@gmail.org>
6  *   Rodrigo Kumpera <kumpera@gmail.com>
7  *   Felipe C. da S. Sanches <felipe.sanches@gmail.com>
8  *
9  * Copyright (C) 2007 Authors
10  *
11  * Released under GNU GPL.  Read the file 'COPYING' for more information.
12  */
14 #ifdef HAVE_CONFIG_H
15 # include <config.h>
16 #endif
18 #include <gtk/gtktreeview.h>
19 #include <gtkmm/cellrenderertext.h>
20 #include <gtkmm/colorbutton.h>
21 #include <gtkmm/messagedialog.h>
22 #include <gtkmm/paned.h>
23 #include <gtkmm/scale.h>
24 #include <gtkmm/scrolledwindow.h>
25 #include <gtkmm/spinbutton.h>
26 #include <gtkmm/stock.h>
27 #include <gtkmm/tooltips.h>
28 #include <glibmm/i18n.h>
30 #include "application/application.h"
31 #include "application/editor.h"
32 #include "desktop.h"
33 #include "desktop-handles.h"
34 #include "dialog-manager.h"
35 #include "dir-util.h"
36 #include "document.h"
37 #include "filter-chemistry.h"
38 #include "filter-effects-dialog.h"
39 #include "filter-enums.h"
40 #include "inkscape.h"
41 #include "path-prefix.h"
42 #include "prefs-utils.h"
43 #include "selection.h"
44 #include "sp-feblend.h"
45 #include "sp-fecolormatrix.h"
46 #include "sp-fecomponenttransfer.h"
47 #include "sp-fecomposite.h"
48 #include "sp-feconvolvematrix.h"
49 #include "sp-fedisplacementmap.h"
50 #include "sp-fedistantlight.h"
51 #include "sp-femerge.h"
52 #include "sp-femergenode.h"
53 #include "sp-feoffset.h"
54 #include "sp-fepointlight.h"
55 #include "sp-fespotlight.h"
56 #include "sp-filter-primitive.h"
57 #include "sp-gaussian-blur.h"
59 #include "style.h"
60 #include "svg/svg-color.h"
61 #include "ui/dialog/filedialog.h"
62 #include "verbs.h"
63 #include "xml/node.h"
64 #include "xml/node-observer.h"
65 #include "xml/repr.h"
66 #include <sstream>
68 #include "io/sys.h"
69 #include <iostream>
71 using namespace NR;
73 namespace Inkscape {
74 namespace UI {
75 namespace Dialog {
77 // Returns the number of inputs available for the filter primitive type
78 int input_count(const SPFilterPrimitive* prim)
79 {
80     if(!prim)
81         return 0;
82     else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
83         return 2;
84     else if(SP_IS_FEMERGE(prim)) {
85         // Return the number of feMergeNode connections plus an extra
86         int count = 1;
87         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count);
88         return count;
89     }
90     else
91         return 1;
92 }
94 // Very simple observer that just emits a signal if anything happens to a node
95 class FilterEffectsDialog::SignalObserver : public XML::NodeObserver
96 {
97 public:
98     SignalObserver()
99         : _oldsel(0)
100     {}
102     // Add this observer to the SPObject and remove it from any previous object
103     void set(SPObject* o)
104     {
105         if(_oldsel && _oldsel->repr)
106             _oldsel->repr->removeObserver(*this);
107         if(o && o->repr)
108             o->repr->addObserver(*this);
109         _oldsel = o;
110     }
112     void notifyChildAdded(XML::Node&, XML::Node&, XML::Node*)
113     { signal_changed()(); }
115     void notifyChildRemoved(XML::Node&, XML::Node&, XML::Node*)
116     { signal_changed()(); }
118     void notifyChildOrderChanged(XML::Node&, XML::Node&, XML::Node*, XML::Node*)
119     { signal_changed()(); }
121     void notifyContentChanged(XML::Node&, Util::ptr_shared<char>, Util::ptr_shared<char>)
122     {}
124     void notifyAttributeChanged(XML::Node&, GQuark, Util::ptr_shared<char>, Util::ptr_shared<char>)
125     { signal_changed()(); }
127     sigc::signal<void>& signal_changed()
128     {
129         return _signal_changed;
130     }
131 private:
132     sigc::signal<void> _signal_changed;
133     SPObject* _oldsel;
134 };
136 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
138 public:
139     CheckButtonAttr(const Glib::ustring& label,
140                     const Glib::ustring& tv, const Glib::ustring& fv,
141                     const SPAttributeEnum a)
142         : Gtk::CheckButton(label),
143           AttrWidget(a, true),//TO-DO: receive a defaultvalue parameter in the constructor
144           _true_val(tv), _false_val(fv)
145     {
146         signal_toggled().connect(signal_attr_changed().make_slot());
147     }
149     Glib::ustring get_as_attribute() const
150     {
151         return get_active() ? _true_val : _false_val;
152     }
154     void set_from_attribute(SPObject* o)
155     {
156         const gchar* val = attribute_value(o);
157         if(val) {
158             if(_true_val == val)
159                 set_active(true);
160             else if(_false_val == val)
161                 set_active(false);
162         } else {
163             set_active(get_default()->as_bool());
164         }
165     }
166 private:
167     const Glib::ustring _true_val, _false_val;
168 };
170 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
172 public:
173     SpinButtonAttr(double lower, double upper, double step_inc,
174                    double climb_rate, int digits, const SPAttributeEnum a, double def, char* tip_text)
175         : Gtk::SpinButton(climb_rate, digits),
176           AttrWidget(a, def)
177     {
178         if (tip_text) _tt.set_tip(*this, tip_text);
179         set_range(lower, upper);
180         set_increments(step_inc, step_inc * 5);
182         signal_value_changed().connect(signal_attr_changed().make_slot());
183     }
185     Glib::ustring get_as_attribute() const
186     {
187         const double val = get_value();
189         if(get_digits() == 0)
190             return Glib::Ascii::dtostr((int)val);
191         else
192             return Glib::Ascii::dtostr(val);
193     }
195     void set_from_attribute(SPObject* o)
196     {
197         const gchar* val = attribute_value(o);
198         if(val){
199             set_value(Glib::Ascii::strtod(val));
200         } else {
201             set_value(get_default()->as_double());
202         }
203     }
204 };
206 template< typename T> class ComboWithTooltip : public Gtk::EventBox
208 public:
209     ComboWithTooltip<T>(T default_value, const Util::EnumDataConverter<T>& c, const SPAttributeEnum a = SP_ATTR_INVALID, char* tip_text = NULL)
210     {
211         if (tip_text) _tt.set_tip(*this, tip_text);
212         combo = new ComboBoxEnum<T>(default_value, c, a);
213         add(*combo);
214         show_all();
215     }
216     
217     ~ComboWithTooltip()
218     {
219         delete combo;
220     }
221     
222     ComboBoxEnum<T>* get_attrwidget()
223     {
224         return combo;
225     }
226 private:
227     Gtk::Tooltips _tt;
228     ComboBoxEnum<T>* combo;
229 };
231 // Contains an arbitrary number of spin buttons that use seperate attributes
232 class MultiSpinButton : public Gtk::HBox
234 public:
235     MultiSpinButton(double lower, double upper, double step_inc,
236                     double climb_rate, int digits, std::vector<SPAttributeEnum> attrs, std::vector<double> default_values, std::vector<char*> tip_text)
237     {
238         g_assert(attrs.size()==default_values.size());
239         g_assert(attrs.size()==tip_text.size());
240         for(unsigned i = 0; i < attrs.size(); ++i) {
241             _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i], default_values[i], tip_text[i]));
242             pack_start(*_spins.back(), false, false);
243         }
244     }
246     ~MultiSpinButton()
247     {
248         for(unsigned i = 0; i < _spins.size(); ++i)
249             delete _spins[i];
250     }
252     std::vector<SpinButtonAttr*>& get_spinbuttons()
253     {
254         return _spins;
255     }
256 private:
257     std::vector<SpinButtonAttr*> _spins;
258 };
260 // Contains two spinbuttons that describe a NumberOptNumber
261 class DualSpinButton : public Gtk::HBox, public AttrWidget
263 public:
264     DualSpinButton(double lower, double upper, double step_inc,
265                    double climb_rate, int digits, const SPAttributeEnum a, char* tt1, char* tt2)
266         : AttrWidget(a), //TO-DO: receive default num-opt-num as parameter in the constructor
267           _s1(climb_rate, digits), _s2(climb_rate, digits)
268     {
269         if (tt1) _tt.set_tip(_s1, tt1);
270         if (tt2) _tt.set_tip(_s2, tt2);
271         _s1.set_range(lower, upper);
272         _s2.set_range(lower, upper);
273         _s1.set_increments(step_inc, step_inc * 5);
274         _s2.set_increments(step_inc, step_inc * 5);
276         _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
277         _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
279         pack_start(_s1, false, false);
280         pack_start(_s2, false, false);
281     }
283     Gtk::SpinButton& get_spinbutton1()
284     {
285         return _s1;
286     }
288     Gtk::SpinButton& get_spinbutton2()
289     {
290         return _s2;
291     }
293     virtual Glib::ustring get_as_attribute() const
294     {
295         double v1 = _s1.get_value();
296         double v2 = _s2.get_value();
298         if(_s1.get_digits() == 0) {
299             v1 = (int)v1;
300             v2 = (int)v2;
301         }
303         return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
304     }
306     virtual void set_from_attribute(SPObject* o)
307     {
308         const gchar* val = attribute_value(o);
309         NumberOptNumber n;
310         if(val) {
311             n.set(val);
312         } else {
313             n.set("0 0"); //TO-DO: replace this line by the next one that is currently commented out
314 //            n.set(default_value(o));
315         }
316         _s1.set_value(n.getNumber());
317         _s2.set_value(n.getOptNumber());
318         
319     }
320 private:
321     Gtk::SpinButton _s1, _s2;
322 };
324 class ColorButton : public Gtk::ColorButton, public AttrWidget
326 public:
327     ColorButton(const SPAttributeEnum a)
328         : AttrWidget(a)
329     {
330         signal_color_set().connect(signal_attr_changed().make_slot());
332         Gdk::Color col;
333         col.set_rgb(65535, 65535, 65535);
334         set_color(col);
335     }
337     // Returns the color in 'rgb(r,g,b)' form.
338     Glib::ustring get_as_attribute() const
339     {
340         std::ostringstream os;
341         const Gdk::Color c = get_color();
342         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?
343         os << "rgb(" << r << "," << g << "," << b << ")";
344         return os.str();
345     }
348     void set_from_attribute(SPObject* o)
349     {
350         const gchar* val = attribute_value(o);
351         guint32 i = 0;
352         if(val) {
353             i = sp_svg_read_color(val, 0xFFFFFFFF);
354         } else {
355             //TO-DO: read from constructor attribute
356             //i = default_value(o);
357         }
358         const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
359         Gdk::Color col;
360         col.set_rgb(r * 256, g * 256, b * 256);
361         set_color(col);
362     }
363 };
365 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
366 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
368 public:
369     MatrixAttr(const SPAttributeEnum a)
370         : AttrWidget(a), _locked(false)
371     {
372         _model = Gtk::ListStore::create(_columns);
373         _tree.set_model(_model);
374         _tree.set_headers_visible(false);
375         _tree.show();
376         add(_tree);
377         set_shadow_type(Gtk::SHADOW_IN);
378     }
380     std::vector<double> get_values() const
381     {
382         std::vector<double> vec;
383         for(Gtk::TreeIter iter = _model->children().begin();
384             iter != _model->children().end(); ++iter) {
385             for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
386                 vec.push_back((*iter)[_columns.cols[c]]);
387         }
388         return vec;
389     }
391     void set_values(const std::vector<double>& v)
392     {
393         unsigned i = 0;
394         for(Gtk::TreeIter iter = _model->children().begin();
395             iter != _model->children().end(); ++iter) {
396             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
397                 if(i >= v.size())
398                     return;
399                 (*iter)[_columns.cols[c]] = v[i];
400                 ++i;
401             }
402         }
403     }
405     Glib::ustring get_as_attribute() const
406     {
407         std::ostringstream os;
409         for(Gtk::TreeIter iter = _model->children().begin();
410             iter != _model->children().end(); ++iter) {
411             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
412                 os << (*iter)[_columns.cols[c]] << " ";
413             }
414         }
416         return os.str();
417     }
419     void set_from_attribute(SPObject* o)
420     {
421         if(o) {
422             if(SP_IS_FECONVOLVEMATRIX(o)) {
423                 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
424                 int cols, rows;
425                 cols = (int)conv->order.getNumber();
426                 if(cols > 5)
427                     cols = 5;
428                 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
429                 update(o, rows, cols);
430             }
431             else if(SP_IS_FECOLORMATRIX(o))
432                 update(o, 4, 5);
433         }
434     }
435 private:
436     class MatrixColumns : public Gtk::TreeModel::ColumnRecord
437     {
438     public:
439         MatrixColumns()
440         {
441             cols.resize(5);
442             for(unsigned i = 0; i < cols.size(); ++i)
443                 add(cols[i]);
444         }
445         std::vector<Gtk::TreeModelColumn<double> > cols;
446     };
448     void update(SPObject* o, const int rows, const int cols)
449     {
450         if(_locked)
451             return;
453         _model->clear();
455         _tree.remove_all_columns();
457         std::vector<gdouble>* values = NULL;
458         if(SP_IS_FECOLORMATRIX(o))
459             values = &SP_FECOLORMATRIX(o)->values;
460         else if(SP_IS_FECONVOLVEMATRIX(o))
461             values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
462         else
463             return;
465         if(o) {
466             int ndx = 0;
468             for(int i = 0; i < cols; ++i) {
469                 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
470                 dynamic_cast<Gtk::CellRendererText*>(
471                     _tree.get_column_cell_renderer(i))->signal_edited().connect(
472                         sigc::mem_fun(*this, &MatrixAttr::rebind));
473             }
475             for(int r = 0; r < rows; ++r) {
476                 Gtk::TreeRow row = *(_model->append());
477                 // Default to identity matrix
478                 for(int c = 0; c < cols; ++c, ++ndx)
479                     row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
480             }
481         }
482     }
484     void rebind(const Glib::ustring&, const Glib::ustring&)
485     {
486         _locked = true;
487         signal_attr_changed()();
488         _locked = false;
489     }
491     bool _locked;
492     Gtk::TreeView _tree;
493     Glib::RefPtr<Gtk::ListStore> _model;
494     MatrixColumns _columns;
495 };
497 // Displays a matrix or a slider for feColorMatrix
498 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
500 public:
501     ColorMatrixValues()
502         : AttrWidget(SP_ATTR_VALUES),
503           _matrix(SP_ATTR_VALUES),
504           _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
505           _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
506           _label(_("None"), Gtk::ALIGN_LEFT),
507           _use_stored(false),
508           _saturation_store(0),
509           _angle_store(0)
510     {
511         _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
512         _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
513         _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
514         signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
516         _matrix.show();
517         _saturation.show();
518         _angle.show();
519         _label.show();
520         _label.set_sensitive(false);
522         set_shadow_type(Gtk::SHADOW_NONE);
523     }
525     virtual void set_from_attribute(SPObject* o)
526     {
527         if(SP_IS_FECOLORMATRIX(o)) {
528             SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
529             remove();
530             switch(col->type) {
531                 case COLORMATRIX_SATURATE:
532                     add(_saturation);
533                     if(_use_stored)
534                         _saturation.set_value(_saturation_store);
535                     else
536                         _saturation.set_from_attribute(o);
537                     break;
538                 case COLORMATRIX_HUEROTATE:
539                     add(_angle);
540                     if(_use_stored)
541                         _angle.set_value(_angle_store);
542                     else
543                         _angle.set_from_attribute(o);
544                     break;
545                 case COLORMATRIX_LUMINANCETOALPHA:
546                     add(_label);
547                     break;
548                 case COLORMATRIX_MATRIX:
549                 default:
550                     add(_matrix);
551                     if(_use_stored)
552                         _matrix.set_values(_matrix_store);
553                     else
554                         _matrix.set_from_attribute(o);
555                     break;
556             }
557             _use_stored = true;
558         }
559     }
561     virtual Glib::ustring get_as_attribute() const
562     {
563         const Widget* w = get_child();
564         if(w == &_label)
565             return "";
566         else
567             return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
568     }
570     void clear_store()
571     {
572         _use_stored = false;
573     }
574 private:
575     void update_store()
576     {
577         const Widget* w = get_child();
578         if(w == &_matrix)
579             _matrix_store = _matrix.get_values();
580         else if(w == &_saturation)
581             _saturation_store = _saturation.get_value();
582         else if(w == &_angle)
583             _angle_store = _angle.get_value();
584     }
586     MatrixAttr _matrix;
587     SpinSlider _saturation;
588     SpinSlider _angle;
589     Gtk::Label _label;
591     // Store separate values for the different color modes
592     bool _use_stored;
593     std::vector<double> _matrix_store;
594     double _saturation_store;
595     double _angle_store;
596 };
598 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
600 //Displays a chooser for feImage input
601 //It may be a filename or the id for an SVG Element
602 //described in xlink:href syntax
603 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
605 public:
606     FileOrElementChooser(const SPAttributeEnum a)
607         : AttrWidget(a)
608     {
609         pack_start(_entry, false, false);
610         pack_start(_fromFile, false, false);
611         //pack_start(_fromSVGElement, false, false);
613         _fromFile.set_label(_("Image File"));
614         _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
616         _fromSVGElement.set_label(_("Selected SVG Element"));
617         _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
619         _entry.signal_changed().connect(signal_attr_changed().make_slot());
621         show_all();
623     }
625     // Returns the element in xlink:href form.
626     Glib::ustring get_as_attribute() const
627     {
628         return _entry.get_text();
629     }
632     void set_from_attribute(SPObject* o)
633     {
634         const gchar* val = attribute_value(o);
635         if(val) {
636             _entry.set_text(val);
637         } else {
638             _entry.set_text("");
639         }
640     }
642     void set_desktop(SPDesktop* d){
643         _desktop = d;
644     }
646 private:
647     void select_svg_element(){
648         Inkscape::Selection* sel = sp_desktop_selection(_desktop);
649         if (sel->isEmpty()) return;
650         Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
651         if (!node || !node->matchAttributeName("id")) return;
653         std::ostringstream xlikhref;
654         xlikhref << "#" << node->attribute("id");
655         _entry.set_text(xlikhref.str());
656     }
658     void select_file(){
660         //# Get the current directory for finding files
661         Glib::ustring open_path;
662         char *attr = (char *)prefs_get_string_attribute("dialogs.open", "path");
663         if (attr)
664             open_path = attr;
666         //# Test if the open_path directory exists
667         if (!Inkscape::IO::file_test(open_path.c_str(),
668                   (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
669             open_path = "";
671         //# If no open path, default to our home directory
672         if (open_path.size() < 1)
673             {
674             open_path = g_get_home_dir();
675             open_path.append(G_DIR_SEPARATOR_S);
676             }
678         //# Create a dialog if we don't already have one
679         if (!selectFeImageFileInstance) {
680             selectFeImageFileInstance =
681                   Inkscape::UI::Dialog::FileOpenDialog::create(
682                      *_desktop->getToplevel(),
683                      open_path,
684                      Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not justy svg*/
685                      (char const *)_("Select an image to be used as feImage input"));
686         }
688         //# Show the dialog
689         bool const success = selectFeImageFileInstance->show();
690         if (!success)
691             return;
693         //# User selected something.  Get name and type
694         Glib::ustring fileName = selectFeImageFileInstance->getFilename();
696         if (fileName.size() > 0) {
698             Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
700             if ( newFileName.size() > 0)
701                 fileName = newFileName;
702             else
703                 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
705             open_path = fileName;
706             open_path.append(G_DIR_SEPARATOR_S);
707             prefs_set_string_attribute("dialogs.open", "path", open_path.c_str());
709             _entry.set_text(fileName);
710         }
711         return;
712     }
714     Gtk::Entry _entry;
715     Gtk::Button _fromFile;
716     Gtk::Button _fromSVGElement;
717     SPDesktop* _desktop;
718 };
720 class FilterEffectsDialog::Settings
722 public:
723     typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
725     Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
726         : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
727     {
728         _groups.resize(_max_types);
729         _attrwidgets.resize(_max_types);
730         _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
732         for(int i = 0; i < _max_types; ++i) {
733             _groups[i] = new Gtk::VBox;
734             b.pack_start(*_groups[i], false, false);
735         }
736         _current_type = 0;
737     }
739     ~Settings()
740     {
741         for(int i = 0; i < _max_types; ++i) {
742             delete _groups[i];
743             for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
744                 delete _attrwidgets[i][j];
745         }
746     }
748     // Show the active settings group and update all the AttrWidgets with new values
749     void show_and_update(const int t, SPObject* ob)
750     {
751         if(t != _current_type) {
752             type(t);
753             for(unsigned i = 0; i < _groups.size(); ++i)
754                 _groups[i]->hide();
755         }
756         if(t >= 0)
757             _groups[t]->show_all();
759         _dialog.set_attrs_locked(true);
760         for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
761             _attrwidgets[_current_type][i]->set_from_attribute(ob);
762         _dialog.set_attrs_locked(false);
763     }
765     int get_current_type() const
766     {
767         return _current_type;
768     }
770     void type(const int t)
771     {
772         _current_type = t;
773     }
775     void add_no_params()
776     {
777         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
778         add_widget(lbl, "");
779     }
781     void add_notimplemented()
782     {
783         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
784         add_widget(lbl, "");
785     }
787     // LightSource
788     LightSourceControl* add_lightsource();
790     // CheckBox
791     CheckButtonAttr* add_checkbutton(const SPAttributeEnum attr, const Glib::ustring& label,
792                                      const Glib::ustring& tv, const Glib::ustring& fv)
793     {
794         CheckButtonAttr* cb = new CheckButtonAttr(label, tv, fv, attr);
795         add_widget(cb, "");
796         add_attr_widget(cb);
797         return cb;
798     }
800     // ColorButton
801     ColorButton* add_color(const SPAttributeEnum attr, const Glib::ustring& label)
802     {
803         ColorButton* col = new ColorButton(attr);
804         add_widget(col, label);
805         add_attr_widget(col);
806         return col;
807     }
809     // Matrix
810     MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label)
811     {
812         MatrixAttr* conv = new MatrixAttr(attr);
813         add_widget(conv, label);
814         add_attr_widget(conv);
815         return conv;
816     }
818     // ColorMatrixValues
819     ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
820     {
821         ColorMatrixValues* cmv = new ColorMatrixValues();
822         add_widget(cmv, label);
823         add_attr_widget(cmv);
824         return cmv;
825     }
827     // SpinSlider
828     SpinSlider* add_spinslider(const SPAttributeEnum attr, const Glib::ustring& label,
829                          const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
830     {
831         SpinSlider* spinslider = new SpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text);
832         add_widget(spinslider, label);
833         add_attr_widget(spinslider);
834         return spinslider;
835     }
837     // DualSpinSlider
838     DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
839                                        const double lo, const double hi, const double step_inc,
840                                        const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
841     {
842         DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
843         add_widget(dss, label);
844         add_attr_widget(dss);
845         return dss;
846     }
848     // DualSpinButton
849     DualSpinButton* add_dualspinbutton(const SPAttributeEnum attr, const Glib::ustring& label,
850                                        const double lo, const double hi, const double step_inc,
851                                        const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
852     {
853         DualSpinButton* dsb = new DualSpinButton(lo, hi, step_inc, climb, digits, attr, tip1, tip2);
854         add_widget(dsb, label);
855         add_attr_widget(dsb);
856         return dsb;
857     }
859     // MultiSpinButton
860     MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
861                                          const Glib::ustring& label, const double lo, const double hi,
862                                          const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
863     {
864         std::vector<SPAttributeEnum> attrs;
865         attrs.push_back(attr1);
866         attrs.push_back(attr2);
868         std::vector<double> default_values;
869         default_values.push_back(def1);
870         default_values.push_back(def2);
871         
872         std::vector<char*> tips;
873         tips.push_back(tip1);
874         tips.push_back(tip2);
876         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
877         add_widget(msb, label);
878         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
879             add_attr_widget(msb->get_spinbuttons()[i]);
880         return msb;
881     }
882     MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
883                                          const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
884                                          const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
885     {
886         std::vector<SPAttributeEnum> attrs;
887         attrs.push_back(attr1);
888         attrs.push_back(attr2);
889         attrs.push_back(attr3);
891         std::vector<double> default_values;
892         default_values.push_back(def1);
893         default_values.push_back(def2);
894         default_values.push_back(def3);
896         std::vector<char*> tips;
897         tips.push_back(tip1);
898         tips.push_back(tip2);
899         tips.push_back(tip3);
901         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
902         add_widget(msb, label);
903         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
904             add_attr_widget(msb->get_spinbuttons()[i]);
905         return msb;
906     }
908     // FileOrElementChooser
909     FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
910     {
911         FileOrElementChooser* foech = new FileOrElementChooser(attr);
912         foech->set_desktop(_dialog.getDesktop());
913         add_widget(foech, label);
914         add_attr_widget(foech);
915         return foech;
916     }
918     // ComboBoxEnum
919     template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
920                                   const Glib::ustring& label,
921                                   const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
922     {
923         ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
924         add_widget(combo, label);
925         add_attr_widget(combo->get_attrwidget());
926         return combo->get_attrwidget();
927     }
928 private:
929     Gtk::Tooltips _tt;
931     void add_attr_widget(AttrWidget* a)
932     {
933         _attrwidgets[_current_type].push_back(a);
934         a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
935     }
937     /* Adds a new settings widget using the specified label. The label will be formatted with a colon
938        and all widgets within the setting group are aligned automatically. */
939     void add_widget(Gtk::Widget* w, const Glib::ustring& label)
940     {
941         Gtk::Label *lbl = 0;
942         Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
943         hb->set_spacing(12);
945         if(label != "") {
946             lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
947             hb->pack_start(*lbl, false, false);
948             _size_group->add_widget(*lbl);
949             lbl->show();
950         }
952         hb->pack_start(*w);
953         _groups[_current_type]->pack_start(*hb);
954         hb->show();
955         w->show();
956     }
958     std::vector<Gtk::VBox*> _groups;
959     Glib::RefPtr<Gtk::SizeGroup> _size_group;
960     FilterEffectsDialog& _dialog;
961     SetAttrSlot _set_attr_slot;
962     std::vector<std::vector< AttrWidget*> > _attrwidgets;
963     int _current_type, _max_types;
964 };
966 // Settings for the three light source objects
967 class FilterEffectsDialog::LightSourceControl : public AttrWidget
969 public:
970     LightSourceControl(FilterEffectsDialog& d)
971         : AttrWidget(SP_ATTR_INVALID),
972           _dialog(d),
973           _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
974           _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
975           _light_source(LightSourceConverter),
976           _locked(false)
977     {
978         _light_box.pack_start(_light_label, false, false);
979         _light_box.pack_start(_light_source);
980         _light_box.show_all();
981         _light_box.set_spacing(12);
982         _dialog._sizegroup->add_widget(_light_label);
984         _box.add(_light_box);
985         _box.reorder_child(_light_box, 0);
986         _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
988         // FIXME: these range values are complete crap
990         _settings.type(LIGHT_DISTANT);
991         _settings.add_spinslider(SP_ATTR_AZIMUTH, _("Azimuth"), 0, 360, 1, 1, 0, _("Direction angle for the light source on the XY plane, in degrees"));
992         _settings.add_spinslider(SP_ATTR_ELEVATION, _("Elevation"), 0, 360, 1, 1, 0, _("Direction angle for the light source on the YZ plane, in degrees"));
994         _settings.type(LIGHT_POINT);
995         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0, SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
997         _settings.type(LIGHT_SPOT);
998         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0, SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
999         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
1000                                       SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
1001                                       _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
1002         _settings.add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
1003         _settings.add_spinslider(SP_ATTR_LIMITINGCONEANGLE, _("Cone Angle"), 1, 100, 1, 1, 0, _("This is the angle between the spot light axis (i.e. the axis between the light source and the point to which it is pointing at) and the spot light cone. No light is projected outside this cone."));
1004     }
1006     Gtk::VBox& get_box()
1007     {
1008         return _box;
1009     }
1010 protected:
1011     Glib::ustring get_as_attribute() const
1012     {
1013         return "";
1014     }
1015     void set_from_attribute(SPObject* o)
1016     {
1017         if(_locked)
1018             return;
1020         _locked = true;
1022         SPObject* child = o->children;
1024         if(SP_IS_FEDISTANTLIGHT(child))
1025             _light_source.set_active(0);
1026         else if(SP_IS_FEPOINTLIGHT(child))
1027             _light_source.set_active(1);
1028         else if(SP_IS_FESPOTLIGHT(child))
1029             _light_source.set_active(2);
1030         else
1031             _light_source.set_active(-1);
1033         update();
1035         _locked = false;
1036     }
1037 private:
1038     void on_source_changed()
1039     {
1040         if(_locked)
1041             return;
1043         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1044         if(prim) {
1045             _locked = true;
1047             SPObject* child = prim->children;
1048             const int ls = _light_source.get_active_row_number();
1049             // Check if the light source type has changed
1050             if(!(ls == -1 && !child) &&
1051                !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1052                !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1053                !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1054                 if(child)
1055                     sp_repr_unparent(child->repr);
1057                 if(ls != -1) {
1058                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1059                     Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1060                     prim->repr->appendChild(repr);
1061                 }
1063                 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1064                 update();
1065             }
1067             _locked = false;
1068         }
1069     }
1071     void update()
1072     {
1073         _box.hide_all();
1074         _box.show();
1075         _light_box.show_all();
1077         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1078         if(prim && prim->children)
1079             _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1080     }
1082     FilterEffectsDialog& _dialog;
1083     Gtk::VBox _box;
1084     Settings _settings;
1085     Gtk::HBox _light_box;
1086     Gtk::Label _light_label;
1087     ComboBoxEnum<LightSource> _light_source;
1088     bool _locked;
1089 };
1091 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1093     LightSourceControl* ls = new LightSourceControl(_dialog);
1094     add_attr_widget(ls);
1095     add_widget(&ls->get_box(), "");
1096     return ls;
1099 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1100                                           sigc::slot<void> rem)
1102     Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1104     menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1105     Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1106     menu->append(*mi);
1107     mi->signal_activate().connect(rem);
1108     mi->show();
1109     menu->accelerate(parent);
1111     return menu;
1114 /*** FilterModifier ***/
1115 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1116     : _dialog(d), _add(Gtk::Stock::NEW), _observer(new SignalObserver)
1118     Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1119     pack_start(*sw);
1120     pack_start(_add, false, false);
1121     sw->add(_list);
1123     _model = Gtk::ListStore::create(_columns);
1124     _list.set_model(_model);
1125     _cell_toggle.set_active(true);
1126     const int selcol = _list.append_column("", _cell_toggle);
1127     Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1128     if(col)
1129        col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1130     _list.append_column_editable(_("_Filter"), _columns.label);
1131     ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1132         signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1134     sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1135     sw->set_shadow_type(Gtk::SHADOW_IN);
1136     show_all_children();
1137     _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1138     _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1139     _list.signal_button_release_event().connect_notify(
1140         sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1141     _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1142                               sigc::mem_fun(*this, &FilterModifier::remove_filter));
1143     _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1144                                  _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1145     _menu->accelerate(*this);
1147     _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1148     _observer->signal_changed().connect(signal_filter_changed().make_slot());
1149     g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1150                      G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1152     g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1153                      G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1155     on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1156     update_filters();
1159 FilterEffectsDialog::FilterModifier::~FilterModifier()
1161    _resource_changed.disconnect();
1162    _doc_replaced.disconnect();
1165 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1167     me->_doc_replaced.disconnect();
1168     me->_doc_replaced = desktop->connectDocumentReplaced(
1169         sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1171     me->_resource_changed.disconnect();
1172     me->_resource_changed =
1173         sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1174                                               sigc::mem_fun(me, &FilterModifier::update_filters));
1176     me->_dialog.setDesktop(desktop);
1178     me->update_filters();
1182 // When the selection changes, show the active filter(s) in the dialog
1183 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1184                                                                        Selection *sel,
1185                                                                        FilterModifier* fm)
1187     if(fm && sel)
1188         fm->update_selection(sel);
1191 // Update each filter's sel property based on the current object selection;
1192 //  If the filter is not used by any selected object, sel = 0,
1193 //  otherwise sel is set to the total number of filters in use by selected objects
1194 //  If only one filter is in use, it is selected
1195 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1197     std::set<SPObject*> used;
1199     for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1200         SPObject *obj = SP_OBJECT (i->data);
1201         SPStyle *style = SP_OBJECT_STYLE (obj);
1202         if(!style || !SP_IS_ITEM(obj)) continue;
1204         if(style->filter.set && style->getFilter())
1205             used.insert(style->getFilter());
1206         else
1207             used.insert(0);
1208     }
1210     const int size = used.size();
1212     for(Gtk::TreeIter iter = _model->children().begin();
1213         iter != _model->children().end(); ++iter) {
1214         if(used.find((*iter)[_columns.filter]) != used.end()) {
1215             // If only one filter is in use by the selection, select it
1216             if(size == 1)
1217                 _list.get_selection()->select(iter);
1218             (*iter)[_columns.sel] = size;
1219         }
1220         else
1221             (*iter)[_columns.sel] = 0;
1222     }
1225 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1227     _observer->set(get_selected_filter());
1228     signal_filter_changed()();
1231 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1233     Gtk::TreeModel::iterator iter = _model->get_iter(path);
1235     if(iter) {
1236         SPFilter* filter = (*iter)[_columns.filter];
1237         filter->setLabel(text.c_str());
1238         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1239         if(iter)
1240             (*iter)[_columns.label] = text;
1241     }
1244 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1246     Gtk::TreeIter iter = _model->get_iter(path);
1248     if(iter) {
1249         SPDesktop *desktop = _dialog.getDesktop();
1250         SPDocument *doc = sp_desktop_document(desktop);
1251         SPFilter* filter = (*iter)[_columns.filter];
1252         Inkscape::Selection *sel = sp_desktop_selection(desktop);
1254         /* If this filter is the only one used in the selection, unset it */
1255         if((*iter)[_columns.sel] == 1)
1256             filter = 0;
1258         GSList const *items = sel->itemList();
1260         for (GSList const *i = items; i != NULL; i = i->next) {
1261             SPItem * item = SP_ITEM(i->data);
1262             SPStyle *style = SP_OBJECT_STYLE(item);
1263             g_assert(style != NULL);
1265             if(filter)
1266                 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1267             else
1268                 ::remove_filter(item, false);
1270             SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1271         }
1273         update_selection(sel);
1274         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS,  _("Apply filter"));
1275     }
1278 /* Add all filters in the document to the combobox.
1279    Keeps the same selection if possible, otherwise selects the first element */
1280 void FilterEffectsDialog::FilterModifier::update_filters()
1282     SPDesktop* desktop = _dialog.getDesktop();
1283     SPDocument* document = sp_desktop_document(desktop);
1284     const GSList* filters = sp_document_get_resource_list(document, "filter");
1286     _model->clear();
1288     for(const GSList *l = filters; l; l = l->next) {
1289         Gtk::TreeModel::Row row = *_model->append();
1290         SPFilter* f = (SPFilter*)l->data;
1291         row[_columns.filter] = f;
1292         const gchar* lbl = f->label();
1293         const gchar* id = SP_OBJECT_ID(f);
1294         row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1295     }
1297     update_selection(desktop->selection);
1298     _dialog.update_filter_general_settings_view();
1301 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1303     if(_list.get_selection()) {
1304         Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1306         if(i)
1307             return (*i)[_columns.filter];
1308     }
1310     return 0;
1313 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1315     if(filter) {
1316         for(Gtk::TreeModel::iterator i = _model->children().begin();
1317             i != _model->children().end(); ++i) {
1318             if((*i)[_columns.filter] == filter) {
1319                 _list.get_selection()->select(i);
1320                 break;
1321             }
1322         }
1323     }
1326 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1328     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1329         const bool sensitive = get_selected_filter() != NULL;
1330         _menu->items()[0].set_sensitive(sensitive);
1331         _menu->items()[1].set_sensitive(sensitive);
1332         _menu->popup(event->button, event->time);
1333     }
1336 void FilterEffectsDialog::FilterModifier::add_filter()
1338     SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1339     SPFilter* filter = new_filter(doc);
1341     const int count = _model->children().size();
1342     std::ostringstream os;
1343     os << "filter" << count;
1344     filter->setLabel(os.str().c_str());
1346     update_filters();
1348     select_filter(filter);
1350     sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1353 void FilterEffectsDialog::FilterModifier::remove_filter()
1355     SPFilter *filter = get_selected_filter();
1357     if(filter) {
1358         SPDocument* doc = filter->document;
1359         sp_repr_unparent(filter->repr);
1361         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1363         update_filters();
1364     }
1367 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1369     SPFilter* filter = get_selected_filter();
1371     if(filter) {
1372         Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1373         repr = repr->duplicate(repr->document());
1374         parent->appendChild(repr);
1376         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1378         update_filters();
1379     }
1382 void FilterEffectsDialog::FilterModifier::rename_filter()
1384     _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1387 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1388     : Glib::ObjectBase(typeid(CellRendererConnection)),
1389       _primitive(*this, "primitive", 0)
1390 {}
1392 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1394     return _primitive.get_proxy();
1397 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1399     _text_width = w;
1402 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1404     return _text_width;
1407 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1408     Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1409     int* x_offset, int* y_offset, int* width, int* height) const
1411     PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1413     if(x_offset)
1414         (*x_offset) = 0;
1415     if(y_offset)
1416         (*y_offset) = 0;
1417     if(width)
1418         (*width) = size * primlist.primitive_count() + _text_width * 7;
1419     if(height) {
1420         // Scale the height depending on the number of inputs, unless it's
1421         // the first primitive, in which case there are no connections
1422         SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1423         (*height) = size * input_count(prim);
1424     }
1427 /*** PrimitiveList ***/
1428 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1429     : _dialog(d),
1430       _in_drag(0),
1431       _observer(new SignalObserver)
1433     d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1435     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1436     signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1438     _model = Gtk::ListStore::create(_columns);
1440     set_reorderable(true);
1442     set_model(_model);
1443     append_column(_("_Effect"), _columns.type);
1445     _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1446     get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1447     signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1449     _connection_cell.set_text_width(init_text());
1451     int cols_count = append_column(_("Connections"), _connection_cell);
1452     Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1453     if(col)
1454        col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1457 // Sets up a vertical Pango context/layout, and returns the largest
1458 // width needed to render the FilterPrimitiveInput labels.
1459 int FilterEffectsDialog::PrimitiveList::init_text()
1461     // Set up a vertical context+layout
1462     Glib::RefPtr<Pango::Context> context = create_pango_context();
1463     const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1464     context->set_matrix(matrix);
1465     _vertical_layout = Pango::Layout::create(context);
1467     int maxfont = 0;
1468     for(int i = 0; i < FPInputConverter.end; ++i) {
1469         _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1470         int fontw, fonth;
1471         _vertical_layout->get_pixel_size(fontw, fonth);
1472         if(fonth > maxfont)
1473             maxfont = fonth;
1474     }
1476     return maxfont;
1479 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1481     return _signal_primitive_changed;
1484 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1486     _observer->set(get_selected());
1487     signal_primitive_changed()();
1488     _dialog._color_matrix_values->clear_store();
1491 /* Add all filter primitives in the current to the list.
1492    Keeps the same selection if possible, otherwise selects the first element */
1493 void FilterEffectsDialog::PrimitiveList::update()
1495     SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1496     const SPFilterPrimitive* active_prim = get_selected();
1497     bool active_found = false;
1499     _model->clear();
1501     if(f) {
1502         _dialog._primitive_box.set_sensitive(true);
1503         _dialog.update_filter_general_settings_view();
1504         for(SPObject *prim_obj = f->children;
1505                 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1506                 prim_obj = prim_obj->next) {
1507             SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1508             if(prim) {
1509                 Gtk::TreeModel::Row row = *_model->append();
1510                 row[_columns.primitive] = prim;
1511                 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1512                 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1513                 row[_columns.id] = SP_OBJECT_ID(prim);
1515                 if(prim == active_prim) {
1516                     get_selection()->select(row);
1517                     active_found = true;
1518                 }
1519             }
1520         }
1522         if(!active_found && _model->children().begin())
1523             get_selection()->select(_model->children().begin());
1525         columns_autosize();
1526     }
1527     else {
1528         _dialog._primitive_box.set_sensitive(false);
1529     }
1532 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1534     _primitive_menu = menu;
1537 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1539     if(_dialog._filter_modifier.get_selected_filter()) {
1540         Gtk::TreeModel::iterator i = get_selection()->get_selected();
1541         if(i)
1542             return (*i)[_columns.primitive];
1543     }
1545     return 0;
1548 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1550     for(Gtk::TreeIter i = _model->children().begin();
1551         i != _model->children().end(); ++i) {
1552         if((*i)[_columns.primitive] == prim)
1553             get_selection()->select(i);
1554     }
1557 void FilterEffectsDialog::PrimitiveList::remove_selected()
1559     SPFilterPrimitive* prim = get_selected();
1561     if(prim) {
1562         _observer->set(0);
1564         sp_repr_unparent(prim->repr);
1566         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1567                          _("Remove filter primitive"));
1569         update();
1570     }
1573 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1575     Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1576     Glib::RefPtr<Gdk::Window> win = get_bin_window();
1577     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1579     SPFilterPrimitive* prim = get_selected();
1580     int row_count = get_model()->children().size();
1582     int fheight = CellRendererConnection::size;
1583     Gdk::Rectangle rct, vis;
1584     Gtk::TreeIter row = get_model()->children().begin();
1585     int text_start_x = 0;
1586     if(row) {
1587         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1588         get_visible_rect(vis);
1589         int vis_x, vis_y;
1590         tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1592         text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter.end + 1) + 1;
1593         for(int i = 0; i < FPInputConverter.end; ++i) {
1594             _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1595             const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1596             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());
1597             get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1598             get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1599         }
1600     }
1602     int row_index = 0;
1603     for(; row != get_model()->children().end(); ++row, ++row_index) {
1604         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1605         const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1607         // Check mouse state
1608         int mx, my;
1609         Gdk::ModifierType mask;
1610         get_bin_window()->get_pointer(mx, my, mask);
1612         // Outline the bottom of the connection area
1613         const int outline_x = x + fheight * (row_count - row_index);
1614         get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1616         // Side outline
1617         get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1619         std::vector<Gdk::Point> con_poly;
1620         int con_drag_y = 0;
1621         bool inside;
1622         const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1623         const int inputs = input_count(row_prim);
1625         if(SP_IS_FEMERGE(row_prim)) {
1626             for(int i = 0; i < inputs; ++i) {
1627                 inside = do_connection_node(row, i, con_poly, mx, my);
1628                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1629                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1630                                                inside, con_poly);
1632                 if(_in_drag == (i + 1))
1633                     con_drag_y = con_poly[2].get_y();
1635                 if(_in_drag != (i + 1) || row_prim != prim)
1636                     draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1637             }
1638         }
1639         else {
1640             // Draw "in" shape
1641             inside = do_connection_node(row, 0, con_poly, mx, my);
1642             con_drag_y = con_poly[2].get_y();
1643             get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1644                                            darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1645                                            inside, con_poly);
1647             // Draw "in" connection
1648             if(_in_drag != 1 || row_prim != prim)
1649                 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1651             if(inputs == 2) {
1652                 // Draw "in2" shape
1653                 inside = do_connection_node(row, 1, con_poly, mx, my);
1654                 if(_in_drag == 2)
1655                     con_drag_y = con_poly[2].get_y();
1656                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1657                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1658                                                inside, con_poly);
1659                 // Draw "in2" connection
1660                 if(_in_drag != 2 || row_prim != prim)
1661                     draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1662             }
1663         }
1665         // Draw drag connection
1666         if(row_prim == prim && _in_drag) {
1667             get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1668                                         mx, con_drag_y);
1669             get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1670         }
1671     }
1673     return true;
1676 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1677                                                          const int text_start_x, const int x1, const int y1,
1678                                                          const int row_count)
1680     int src_id = 0;
1681     Gtk::TreeIter res = find_result(input, attr, src_id);
1682     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1683     Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1684     Glib::RefPtr<Gdk::GC> gc;
1686     const bool is_first = input == get_model()->children().begin();
1687     const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1688     const bool use_default = !res && !is_merge;
1690     if(res == input || (use_default && is_first)) {
1691         // Draw straight connection to a standard input
1692         // Draw a lighter line for an implicit connection to a standard input
1693         const int tw = _connection_cell.get_text_width();
1694         gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1695         gc = (use_default && is_first) ? lightgc : darkgc;
1696         get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1697         get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1698     }
1699     else {
1700         // Draw an 'L'-shaped connection to another filter primitive
1701         // If no connection is specified, draw a light connection to the previous primitive
1702         gc = use_default ? lightgc : darkgc;
1704         if(use_default) {
1705             res = input;
1706             --res;
1707         }
1709         if(res) {
1710             Gdk::Rectangle rct;
1712             get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1713             const int fheight = CellRendererConnection::size;
1715             get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1716             const int row_index = find_index(res);
1717             const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1718             const int y2 = rct.get_y() + rct.get_height();
1720             // Draw a bevelled 'L'-shaped connection
1721             get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1722             get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1723             get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1724         }
1725     }
1728 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1729 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1730                                                             std::vector<Gdk::Point>& points,
1731                                                             const int ix, const int iy)
1733     Gdk::Rectangle rct;
1734     const int icnt = input_count((*row)[_columns.primitive]);
1736     get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1737     const int fheight = CellRendererConnection::size;
1739     get_cell_area(_model->get_path(row), *get_column(1), rct);
1740     const float h = rct.get_height() / icnt;
1742     const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1743     const int con_w = (int)(fheight * 0.35f);
1744     const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1745     points.clear();
1746     points.push_back(Gdk::Point(x, con_y));
1747     points.push_back(Gdk::Point(x, con_y + con_w * 2));
1748     points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1750     return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1753 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1754                                                                     const int attr, int& src_id)
1756     SPFilterPrimitive* prim = (*start)[_columns.primitive];
1757     Gtk::TreeIter target = _model->children().end();
1758     int image = 0;
1760     if(SP_IS_FEMERGE(prim)) {
1761         int c = 0;
1762         bool found = false;
1763         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1764             if(c == attr && SP_IS_FEMERGENODE(o)) {
1765                 image = SP_FEMERGENODE(o)->input;
1766                 found = true;
1767             }
1768         }
1769         if(!found)
1770             return target;
1771     }
1772     else {
1773         if(attr == SP_ATTR_IN)
1774             image = prim->image_in;
1775         else if(attr == SP_ATTR_IN2) {
1776             if(SP_IS_FEBLEND(prim))
1777                 image = SP_FEBLEND(prim)->in2;
1778             else if(SP_IS_FECOMPOSITE(prim))
1779                 image = SP_FECOMPOSITE(prim)->in2;
1780             else if(SP_IS_FEDISPLACEMENTMAP(prim))
1781                 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1782             else
1783                 return target;
1784         }
1785         else
1786             return target;
1787     }
1789     if(image >= 0) {
1790         for(Gtk::TreeIter i = _model->children().begin();
1791             i != start; ++i) {
1792             if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1793                 target = i;
1794         }
1795         return target;
1796     }
1797     else if(image < -1) {
1798         src_id = -(image + 2);
1799         return start;
1800     }
1802     return target;
1805 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1807     int i = 0;
1808     for(Gtk::TreeIter iter = _model->children().begin();
1809         iter != target; ++iter, ++i);
1810     return i;
1813 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1815     Gtk::TreePath path;
1816     Gtk::TreeViewColumn* col;
1817     const int x = (int)e->x, y = (int)e->y;
1818     int cx, cy;
1820     _drag_prim = 0;
1822     if(get_path_at_pos(x, y, path, col, cx, cy)) {
1823         Gtk::TreeIter iter = _model->get_iter(path);
1824         std::vector<Gdk::Point> points;
1826         _drag_prim = (*iter)[_columns.primitive];
1827         const int icnt = input_count(_drag_prim);
1829         for(int i = 0; i < icnt; ++i) {
1830             if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1831                 _in_drag = i + 1;
1832                 break;
1833             }
1834         }
1836         queue_draw();
1837     }
1839     if(_in_drag) {
1840         _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1841         _autoscroll = 0;
1842         get_selection()->select(path);
1843         return true;
1844     }
1845     else
1846         return Gtk::TreeView::on_button_press_event(e);
1849 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1851     const int speed = 10;
1852     const int limit = 15;
1854     Gdk::Rectangle vis;
1855     get_visible_rect(vis);
1856     int vis_x, vis_y;
1857     tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1858     const int top = vis_y + vis.get_height();
1860     // When autoscrolling during a connection drag, set the speed based on
1861     // where the mouse is in relation to the edges.
1862     if(e->y < vis_y)
1863         _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1864     else if(e->y < vis_y + limit)
1865         _autoscroll = -speed;
1866     else if(e->y > top)
1867         _autoscroll = (int)(speed + (e->y - top) / 5);
1868     else if(e->y > top - limit)
1869         _autoscroll = speed;
1870     else
1871         _autoscroll = 0;
1873     queue_draw();
1875     return Gtk::TreeView::on_motion_notify_event(e);
1878 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1880     SPFilterPrimitive *prim = get_selected(), *target;
1882     _scroll_connection.disconnect();
1884     if(_in_drag && prim) {
1885         Gtk::TreePath path;
1886         Gtk::TreeViewColumn* col;
1887         int cx, cy;
1889         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1890             const gchar *in_val = 0;
1891             Glib::ustring result;
1892             Gtk::TreeIter target_iter = _model->get_iter(path);
1893             target = (*target_iter)[_columns.primitive];
1894             col = get_column(1);
1896             Gdk::Rectangle rct;
1897             get_cell_area(path, *col, rct);
1898             const int twidth = _connection_cell.get_text_width();
1899             const int sources_x = rct.get_width() - twidth * FPInputConverter.end;
1900             if(cx > sources_x) {
1901                 int src = (cx - sources_x) / twidth;
1902                 if(src < 0)
1903                     src = 0;
1904                 else if(src >= FPInputConverter.end)
1905                     src = FPInputConverter.end - 1;
1906                 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1907                 in_val = result.c_str();
1908             }
1909             else {
1910                 // Ensure that the target comes before the selected primitive
1911                 for(Gtk::TreeIter iter = _model->children().begin();
1912                     iter != get_selection()->get_selected(); ++iter) {
1913                     if(iter == target_iter) {
1914                         Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1915                         // Make sure the target has a result
1916                         const gchar *gres = repr->attribute("result");
1917                         if(!gres) {
1918                             result = "result" + Glib::Ascii::dtostr(SP_FILTER(prim->parent)->_image_number_next);
1919                             repr->setAttribute("result", result.c_str());
1920                             in_val = result.c_str();
1921                         }
1922                         else
1923                             in_val = gres;
1924                         break;
1925                     }
1926                 }
1927             }
1929             if(SP_IS_FEMERGE(prim)) {
1930                 int c = 1;
1931                 bool handled = false;
1932                 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1933                     if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1934                         // If input is null, delete it
1935                         if(!in_val) {
1936                             sp_repr_unparent(o->repr);
1937                             sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1938                                              _("Remove merge node"));
1939                             (*get_selection()->get_selected())[_columns.primitive] = prim;
1940                         }
1941                         else
1942                             _dialog.set_attr(o, SP_ATTR_IN, in_val);
1943                         handled = true;
1944                     }
1945                 }
1946                 // Add new input?
1947                 if(!handled && c == _in_drag && in_val) {
1948                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1949                     Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1950                     repr->setAttribute("inkscape:collect", "always");
1951                     prim->repr->appendChild(repr);
1952                     SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1953                     Inkscape::GC::release(repr);
1954                     _dialog.set_attr(node, SP_ATTR_IN, in_val);
1955                     (*get_selection()->get_selected())[_columns.primitive] = prim;
1956                 }
1957             }
1958             else {
1959                 if(_in_drag == 1)
1960                     _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1961                 else if(_in_drag == 2)
1962                     _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1963             }
1964         }
1966         _in_drag = 0;
1967         queue_draw();
1969         _dialog.update_settings_view();
1970     }
1972     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1973         const bool sensitive = get_selected() != NULL;
1974         _primitive_menu->items()[0].set_sensitive(sensitive);
1975         _primitive_menu->items()[1].set_sensitive(sensitive);
1976         _primitive_menu->popup(e->button, e->time);
1978         return true;
1979     }
1980     else
1981         return Gtk::TreeView::on_button_release_event(e);
1984 // Checks all of prim's inputs, removes any that use result
1985 void check_single_connection(SPFilterPrimitive* prim, const int result)
1987     if(prim && result >= 0) {
1989         if(prim->image_in == result)
1990             SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1992         if(SP_IS_FEBLEND(prim)) {
1993             if(SP_FEBLEND(prim)->in2 == result)
1994                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1995         }
1996         else if(SP_IS_FECOMPOSITE(prim)) {
1997             if(SP_FECOMPOSITE(prim)->in2 == result)
1998                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1999         }
2000         else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
2001             if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
2002                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2003         }
2004     }
2007 // Remove any connections going to/from prim_iter that forward-reference other primitives
2008 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
2010     SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
2011     bool before = true;
2013     for(Gtk::TreeIter iter = _model->children().begin();
2014         iter != _model->children().end(); ++iter) {
2015         if(iter == prim_iter)
2016             before = false;
2017         else {
2018             SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
2019             if(before)
2020                 check_single_connection(cur_prim, prim->image_out);
2021             else
2022                 check_single_connection(prim, cur_prim->image_out);
2023         }
2024     }
2027 // Reorder the filter primitives to match the list order
2028 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2030     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2031     int ndx = 0;
2033     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2034         iter != _model->children().end(); ++iter, ++ndx) {
2035         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2036         if(prim && prim == _drag_prim) {
2037             SP_OBJECT_REPR(prim)->setPosition(ndx);
2038             break;
2039         }
2040     }
2042     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2043         iter != _model->children().end(); ++iter, ++ndx) {
2044         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2045         if(prim && prim == _drag_prim) {
2046             sanitize_connections(iter);
2047             get_selection()->select(iter);
2048             break;
2049         }
2050     }
2052     filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2054     sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2057 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2058 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2060     if(_autoscroll) {
2061         Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2062         double v;
2064         v = a.get_value() + _autoscroll;
2065         if(v < 0)
2066             v = 0;
2067         if(v > a.get_upper() - a.get_page_size())
2068             v = a.get_upper() - a.get_page_size();
2070         a.set_value(v);
2072         queue_draw();
2073     }
2075     return true;
2078 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2080     return _model->children().size();
2083 /*** FilterEffectsDialog ***/
2085 FilterEffectsDialog::FilterEffectsDialog()
2086     : UI::Widget::Panel("", "dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2087       _filter_modifier(*this),
2088       _primitive_list(*this),
2089       _add_primitive_type(FPConverter),
2090       _add_primitive(_("Add Effect:")),
2091       _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2092       _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2093       _settings_initialized(false),
2094       _locked(false),
2095       _attr_lock(false)
2097     _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2098                              NR_FILTER_ENDPRIMITIVETYPE);
2099     _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2100                              1);
2101     _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2102     _sizegroup->set_ignore_hidden();
2104     _add_primitive_type.remove_row(NR_FILTER_TILE);
2105     _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2107     // Initialize widget hierarchy
2108     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2109     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2110     Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox);
2111     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2113     _getContents()->add(*hpaned);
2114     hpaned->pack1(_filter_modifier);
2115     hpaned->pack2(_primitive_box);
2116     _primitive_box.pack_start(*sw_prims);
2117     _primitive_box.pack_start(*infobox,false, false);
2118     _primitive_box.pack_start(*hb_prims, false, false);
2119     sw_prims->add(_primitive_list);
2120     infobox->pack_start(_infobox_icon, false, false);
2121     infobox->pack_start(_infobox_desc, false, false);
2122     _infobox_desc.set_line_wrap(true);
2124     hb_prims->pack_end(_add_primitive_type, false, false);
2125     hb_prims->pack_end(_add_primitive, false, false);
2126     _getContents()->pack_start(_settings_tabs, false, false);
2127     _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2128     _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2130     _primitive_list.signal_primitive_changed().connect(
2131         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2132     _filter_modifier.signal_filter_changed().connect(
2133         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2135     _add_primitive_type.signal_changed().connect(
2136         sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2138     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2139     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2140 //    al_settings->set_padding(0, 0, 12, 0);
2141 //    fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2142 //    ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2143     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2144     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2145                                                sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2147     show_all_children();
2148     init_settings_widgets();
2149     _primitive_list.update();
2150     update_primitive_infobox();
2153 FilterEffectsDialog::~FilterEffectsDialog()
2155     delete _settings;
2156     delete _filter_general_settings;
2159 void FilterEffectsDialog::set_attrs_locked(const bool l)
2161     _locked = l;
2164 void FilterEffectsDialog::show_all_vfunc()
2166     UI::Widget::Panel::show_all_vfunc();
2168     update_settings_view();
2171 void FilterEffectsDialog::init_settings_widgets()
2173     // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2174     //       most of the current values are complete guesses!
2176     _empty_settings.set_sensitive(false);
2177     _settings_tab1.pack_start(_empty_settings);
2178     
2179     _no_filter_selected.set_sensitive(false);
2180     _settings_tab2.pack_start(_no_filter_selected);
2181     _settings_initialized = true;
2183     _filter_general_settings->type(0);
2184     _filter_general_settings->add_multispinbutton(/*default x:*/ (double) -0.1, /*default y:*/ (double) -0.1, SP_ATTR_X, SP_ATTR_Y, _("Coordinates"), -100, 100, 0.01, 0.1, 2, _("X coordinate of the left corners of filter effects region"), _("Y coordinate of the upper corners of filter effects region"));
2185     _filter_general_settings->add_multispinbutton(/*default width:*/ (double) 1.2, /*default height:*/ (double) 1.2, SP_ATTR_WIDTH, SP_ATTR_HEIGHT, _("Dimensions"), 0, 1000, 0.01, 0.1, 2, _("Width of filter effects region"), _("Height of filter effects region"));
2187     _settings->type(NR_FILTER_BLEND);
2188     _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode"), BlendModeConverter);
2190     _settings->type(NR_FILTER_COLORMATRIX);
2191     ComboBoxEnum<FilterColorMatrixType>* colmat = _settings->add_combo(COLORMATRIX_MATRIX, SP_ATTR_TYPE, _("Type"), ColorMatrixTypeConverter, _("Indicates the type of matrix operation. The keyword 'matrix' indicates that a full 5x4 matrix of values will be provided. The other keywords represent convenience shortcuts to allow commonly used color operations to be performed without specifying a complete matrix."));
2192     _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s)"));
2193     colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2195     _settings->type(NR_FILTER_COMPONENTTRANSFER);
2196     _settings->add_notimplemented();
2197     /*_settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2198     _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2199     _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2200     _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2201     _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2202     _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2204     _settings->type(NR_FILTER_COMPOSITE);
2205     _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
2206     _k1 = _settings->add_spinslider(SP_ATTR_K1, _("K1"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2207     _k2 = _settings->add_spinslider(SP_ATTR_K2, _("K2"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2208     _k3 = _settings->add_spinslider(SP_ATTR_K3, _("K3"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2209     _k4 = _settings->add_spinslider(SP_ATTR_K4, _("K4"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2211     _settings->type(NR_FILTER_CONVOLVEMATRIX);
2212     _convolve_order = _settings->add_dualspinbutton(SP_ATTR_ORDER, _("Size"), 1, 5, 1, 1, 0, _("width of the convolve matrix"), _("height of the convolve matrix"));
2213     _convolve_target = _settings->add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, SP_ATTR_TARGETX, SP_ATTR_TARGETY, _("Target"), 0, 4, 1, 1, 0, _("X coordinate of the target point in the convolve matrix. The convolution is applied to pixels around this point."), _("Y coordinate of the target point in the convolve matrix. The convolution is applied to pixels around this point."));
2214     _convolve_matrix = _settings->add_matrix(SP_ATTR_KERNELMATRIX, _("Kernel"));
2215     _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2216     _settings->add_spinslider(SP_ATTR_DIVISOR, _("Divisor"), 1, 20, 1, 0.1, 2, _("After applying the kernelMatrix to the input image to yield a number, that number is divided by divisor to yield the final destination color value. A divisor that is the sum of all the matrix values tends to have an evening effect on the overall color intensity of the result."));
2217     _settings->add_spinslider(SP_ATTR_BIAS, _("Bias"), -10, 10, 1, 0.01, 1, _("This value is added to each component. This is useful to define a constant value as the zero response of the filter."));
2218     _settings->add_combo(CONVOLVEMATRIX_EDGEMODE_DUPLICATE, SP_ATTR_EDGEMODE, _("Edge Mode"), ConvolveMatrixEdgeModeConverter, _("Determines how to extend the input image as necessary with color values so that the matrix operations can be applied when the kernel is positioned at or near the edge of the input image."));
2219     _settings->add_checkbutton(SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false");
2221     _settings->type(NR_FILTER_DIFFUSELIGHTING);
2222     _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Diffuse Color"));
2223     _settings->add_spinslider(SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2224     _settings->add_spinslider(SP_ATTR_DIFFUSECONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2225     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2226     _settings->add_lightsource();
2228     _settings->type(NR_FILTER_DISPLACEMENTMAP);
2229     _settings->add_spinslider(SP_ATTR_SCALE, _("Scale"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2230     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2231     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2233     _settings->type(NR_FILTER_FLOOD);
2234     _settings->add_color(SP_PROP_FLOOD_COLOR, _("Flood Color"));
2235     _settings->add_spinslider(SP_PROP_FLOOD_OPACITY, _("Opacity"), 0, 1, 0.1, 0.01, 2);
2237     _settings->type(NR_FILTER_GAUSSIANBLUR);
2238     _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2240     _settings->type(NR_FILTER_MERGE);
2241     _settings->add_no_params();
2243     _settings->type(NR_FILTER_MORPHOLOGY);
2244     _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2245     _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius"), 0, 100, 1, 0.01, 1);
2247     _settings->type(NR_FILTER_IMAGE);
2248     _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image"));
2250     _settings->type(NR_FILTER_OFFSET);
2251     _settings->add_spinslider(SP_ATTR_DX, _("Delta X"), -100, 100, 1, 0.01, 1, _("This is how far the input image gets shifted to the right"));
2252     _settings->add_spinslider(SP_ATTR_DY, _("Delta Y"), -100, 100, 1, 0.01, 1, _("This is how far the input image gets shifted downwards"));
2254     _settings->type(NR_FILTER_SPECULARLIGHTING);
2255     _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Specular Color"));
2256     _settings->add_spinslider(SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2257     _settings->add_spinslider(SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2258     _settings->add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2259     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2260     _settings->add_lightsource();
2262     _settings->type(NR_FILTER_TILE);
2263     _settings->add_notimplemented();
2265     _settings->type(NR_FILTER_TURBULENCE);
2266     _settings->add_checkbutton(SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2267     _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2268     _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency"), 0, 1, 0.001, 0.01, 3);
2269     _settings->add_spinslider(SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2270     _settings->add_spinslider(SP_ATTR_SEED, _("Seed"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2273 void FilterEffectsDialog::add_primitive()
2275     SPFilter* filter = _filter_modifier.get_selected_filter();
2277     if(filter) {
2278         SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2280         _primitive_list.select(prim);
2282         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2283     }
2286 void FilterEffectsDialog::update_primitive_infobox()
2288     if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2289         _infobox_icon.show();
2290         _infobox_desc.show();
2291     } else {
2292         _infobox_icon.hide();
2293         _infobox_desc.hide();
2294     }
2295     switch(_add_primitive_type.get_active_data()->id){
2296         case(NR::NR_FILTER_BLEND):
2297             _infobox_icon.set(g_strdup_printf("%s/feBlend-icon.png", INKSCAPE_PIXMAPDIR));
2298             _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2299             break;
2300         case(NR::NR_FILTER_COLORMATRIX):
2301             _infobox_icon.set(g_strdup_printf("%s/feColorMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2302             _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."));
2303             break;
2304         case(NR::NR_FILTER_COMPONENTTRANSFER):
2305             _infobox_icon.set(g_strdup_printf("%s/feComponentTransfer-icon.png", INKSCAPE_PIXMAPDIR));
2306             _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."));
2307             break;
2308         case(NR::NR_FILTER_COMPOSITE):
2309             _infobox_icon.set(g_strdup_printf("%s/feComposite-icon.png", INKSCAPE_PIXMAPDIR));
2310             _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."));
2311             break;
2312         case(NR::NR_FILTER_CONVOLVEMATRIX):
2313             _infobox_icon.set(g_strdup_printf("%s/feConvolveMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2314             _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."));
2315             break;
2316         case(NR::NR_FILTER_DIFFUSELIGHTING):
2317             _infobox_icon.set(g_strdup_printf("%s/feDiffuseLighting-icon.png", INKSCAPE_PIXMAPDIR));
2318             _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."));
2319             break;
2320         case(NR::NR_FILTER_DISPLACEMENTMAP):
2321             _infobox_icon.set(g_strdup_printf("%s/feDisplacementMap-icon.png", INKSCAPE_PIXMAPDIR));
2322             _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."));
2323             break;
2324         case(NR::NR_FILTER_FLOOD):
2325             _infobox_icon.set(g_strdup_printf("%s/feFlood-icon.png", INKSCAPE_PIXMAPDIR));
2326             _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."));
2327             break;
2328         case(NR::NR_FILTER_GAUSSIANBLUR):
2329             _infobox_icon.set(g_strdup_printf("%s/feGaussianBlur-icon.png", INKSCAPE_PIXMAPDIR));
2330             _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."));
2331             break;
2332         case(NR::NR_FILTER_IMAGE):
2333             _infobox_icon.set(g_strdup_printf("%s/feImage-icon.png", INKSCAPE_PIXMAPDIR));
2334             _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2335             break;
2336         case(NR::NR_FILTER_MERGE):
2337             _infobox_icon.set(g_strdup_printf("%s/feMerge-icon.png", INKSCAPE_PIXMAPDIR));
2338             _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."));
2339             break;
2340         case(NR::NR_FILTER_MORPHOLOGY):
2341             _infobox_icon.set(g_strdup_printf("%s/feMorphology-icon.png", INKSCAPE_PIXMAPDIR));
2342             _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."));
2343             break;
2344         case(NR::NR_FILTER_OFFSET):
2345             _infobox_icon.set(g_strdup_printf("%s/feOffset-icon.png", INKSCAPE_PIXMAPDIR));
2346             _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."));
2347             break;
2348         case(NR::NR_FILTER_SPECULARLIGHTING):
2349             _infobox_icon.set(g_strdup_printf("%s/feSpecularLighting-icon.png", INKSCAPE_PIXMAPDIR));
2350             _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."));
2351             break;
2352         case(NR::NR_FILTER_TILE):
2353             _infobox_icon.set(g_strdup_printf("%s/feTile-icon.png", INKSCAPE_PIXMAPDIR));
2354             _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2355             break;
2356         case(NR::NR_FILTER_TURBULENCE):
2357             _infobox_icon.set(g_strdup_printf("%s/feTurbulence-icon.png", INKSCAPE_PIXMAPDIR));
2358             _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."));
2359             break;
2360         default:
2361             g_assert(false);
2362             break;
2363     }
2366 void FilterEffectsDialog::duplicate_primitive()
2368     SPFilter* filter = _filter_modifier.get_selected_filter();
2369     SPFilterPrimitive* origprim = _primitive_list.get_selected();
2371     if(filter && origprim) {
2372         Inkscape::XML::Node *repr;
2373         repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2374         SP_OBJECT_REPR(filter)->appendChild(repr);
2376         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2378         _primitive_list.update();
2379     }
2382 void FilterEffectsDialog::convolve_order_changed()
2384     _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2385     _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2386     _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2389 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2391     set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2394 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2396     if(!_locked) {
2397         _attr_lock = true;
2398         SPFilter *filter = _filter_modifier.get_selected_filter();
2399         const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2400         if (filter && name && SP_OBJECT_REPR(filter)){
2401             SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2402             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2403         }
2404         _attr_lock = false;
2405     }
2408 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2410     set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2413 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2415     if(!_locked) {
2416         _attr_lock = true;
2418         SPFilter *filter = _filter_modifier.get_selected_filter();
2419         const gchar* name = (const gchar*)sp_attribute_name(attr);
2420         if(filter && name && o) {
2421             update_settings_sensitivity();
2423             SP_OBJECT_REPR(o)->setAttribute(name, val);
2424             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2426             Glib::ustring undokey = "filtereffects:";
2427             undokey += name;
2428             sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2429                                    _("Set filter primitive attribute"));
2430         }
2432         _attr_lock = false;
2433     }
2436 void FilterEffectsDialog::update_filter_general_settings_view()
2438     if(_settings_initialized != true) return;
2440     if(!_locked) {
2441         _attr_lock = true;
2443         SPFilter* filter = _filter_modifier.get_selected_filter();
2445         if(filter) {
2446             _filter_general_settings->show_and_update(0, filter);
2447             _no_filter_selected.hide();
2448         }
2449         else {
2450             std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2451             vect[0]->hide_all();
2452             _no_filter_selected.show();
2453         }
2455         _attr_lock = false;
2456     }
2459 void FilterEffectsDialog::update_settings_view()
2461     update_settings_sensitivity();
2463     if(_attr_lock)
2464         return;
2466 //First Tab
2468     std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2469     for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2470     _empty_settings.show();
2472     if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2473         _infobox_icon.show();
2474         _infobox_desc.show();
2475     } else {
2476         _infobox_icon.hide();
2477         _infobox_desc.hide();
2478     }
2479     
2480     SPFilterPrimitive* prim = _primitive_list.get_selected();
2482     if(prim) {
2483         _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2484         _empty_settings.hide();
2485     }
2487 //Second Tab
2489     std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2490     vect2[0]->hide_all();
2491     _no_filter_selected.show();
2493     SPFilter* filter = _filter_modifier.get_selected_filter();
2495     if(filter) {
2496         _filter_general_settings->show_and_update(0, filter);
2497         _no_filter_selected.hide();
2498     }
2502 void FilterEffectsDialog::update_settings_sensitivity()
2504     SPFilterPrimitive* prim = _primitive_list.get_selected();
2505     const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2506     _k1->set_sensitive(use_k);
2507     _k2->set_sensitive(use_k);
2508     _k3->set_sensitive(use_k);
2509     _k4->set_sensitive(use_k);
2511 // Component transfer not yet implemented
2512 /*
2513     if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2514         SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2515         const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2516         const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2518         _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2519         _ct_slope->set_sensitive(linear);
2520         _ct_intercept->set_sensitive(linear);
2521         _ct_amplitude->set_sensitive(gamma);
2522         _ct_exponent->set_sensitive(gamma);
2523         _ct_offset->set_sensitive(gamma);
2524     }
2525 */
2528 void FilterEffectsDialog::update_color_matrix()
2530     _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2533 } // namespace Dialog
2534 } // namespace UI
2535 } // namespace Inkscape
2537 /*
2538   Local Variables:
2539   mode:c++
2540   c-file-style:"stroustrup"
2541   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2542   indent-tabs-mode:nil
2543   fill-column:99
2544   End:
2545 */
2546 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :