Code

change colour to color in display strings
[inkscape.git] / src / ui / dialog / filter-effects-dialog.cpp
1 /** @file
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/gtk.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 "preferences.h"
43 #include "selection.h"
44 #include "filters/blend.h"
45 #include "filters/colormatrix.h"
46 #include "filters/componenttransfer.h"
47 #include "filters/composite.h"
48 #include "filters/convolvematrix.h"
49 #include "filters/displacementmap.h"
50 #include "filters/distantlight.h"
51 #include "filters/merge.h"
52 #include "filters/mergenode.h"
53 #include "filters/offset.h"
54 #include "filters/pointlight.h"
55 #include "filters/spotlight.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 Inkscape::Filters;
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 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
95 {
96 public:
97     CheckButtonAttr(bool def, const Glib::ustring& label,
98                     const Glib::ustring& tv, const Glib::ustring& fv,
99                     const SPAttributeEnum a, char* tip_text)
100         : Gtk::CheckButton(label),
101           AttrWidget(a, def),
102           _true_val(tv), _false_val(fv)
103     {
104         signal_toggled().connect(signal_attr_changed().make_slot());
105         if (tip_text) _tt.set_tip(*this, tip_text);
106     }
108     Glib::ustring get_as_attribute() const
109     {
110         return get_active() ? _true_val : _false_val;
111     }
113     void set_from_attribute(SPObject* o)
114     {
115         const gchar* val = attribute_value(o);
116         if(val) {
117             if(_true_val == val)
118                 set_active(true);
119             else if(_false_val == val)
120                 set_active(false);
121         } else {
122             set_active(get_default()->as_bool());
123         }
124     }
125 private:
126     const Glib::ustring _true_val, _false_val;
127 };
129 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
131 public:
132     SpinButtonAttr(double lower, double upper, double step_inc,
133                    double climb_rate, int digits, const SPAttributeEnum a, double def, char* tip_text)
134         : Gtk::SpinButton(climb_rate, digits),
135           AttrWidget(a, def)
136     {
137         if (tip_text) _tt.set_tip(*this, tip_text);
138         set_range(lower, upper);
139         set_increments(step_inc, 0);
141         signal_value_changed().connect(signal_attr_changed().make_slot());
142     }
144     Glib::ustring get_as_attribute() const
145     {
146         const double val = get_value();
148         if(get_digits() == 0)
149             return Glib::Ascii::dtostr((int)val);
150         else
151             return Glib::Ascii::dtostr(val);
152     }
154     void set_from_attribute(SPObject* o)
155     {
156         const gchar* val = attribute_value(o);
157         if(val){
158             set_value(Glib::Ascii::strtod(val));
159         } else {
160             set_value(get_default()->as_double());
161         }
162     }
163 };
165 template< typename T> class ComboWithTooltip : public Gtk::EventBox
167 public:
168     ComboWithTooltip<T>(T default_value, const Util::EnumDataConverter<T>& c, const SPAttributeEnum a = SP_ATTR_INVALID, char* tip_text = NULL)
169     {
170         if (tip_text) {
171             _tt.set_tip(*this, tip_text);
172         }
173         combo = new ComboBoxEnum<T>(default_value, c, a);
174         add(*combo);
175         show_all();
176     }
178     ~ComboWithTooltip()
179     {
180         delete combo;
181     }
183     ComboBoxEnum<T>* get_attrwidget()
184     {
185         return combo;
186     }
187 private:
188     Gtk::Tooltips _tt;
189     ComboBoxEnum<T>* combo;
190 };
192 // Contains an arbitrary number of spin buttons that use seperate attributes
193 class MultiSpinButton : public Gtk::HBox
195 public:
196     MultiSpinButton(double lower, double upper, double step_inc,
197                     double climb_rate, int digits, std::vector<SPAttributeEnum> attrs, std::vector<double> default_values, std::vector<char*> tip_text)
198     {
199         g_assert(attrs.size()==default_values.size());
200         g_assert(attrs.size()==tip_text.size());
201         for(unsigned i = 0; i < attrs.size(); ++i) {
202             _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i], default_values[i], tip_text[i]));
203             pack_start(*_spins.back(), false, false);
204         }
205     }
207     ~MultiSpinButton()
208     {
209         for(unsigned i = 0; i < _spins.size(); ++i)
210             delete _spins[i];
211     }
213     std::vector<SpinButtonAttr*>& get_spinbuttons()
214     {
215         return _spins;
216     }
217 private:
218     std::vector<SpinButtonAttr*> _spins;
219 };
221 // Contains two spinbuttons that describe a NumberOptNumber
222 class DualSpinButton : public Gtk::HBox, public AttrWidget
224 public:
225     DualSpinButton(char* def, double lower, double upper, double step_inc,
226                    double climb_rate, int digits, const SPAttributeEnum a, char* tt1, char* tt2)
227         : AttrWidget(a, def), //TO-DO: receive default num-opt-num as parameter in the constructor
228           _s1(climb_rate, digits), _s2(climb_rate, digits)
229     {
230         if (tt1) _tt.set_tip(_s1, tt1);
231         if (tt2) _tt.set_tip(_s2, tt2);
232         _s1.set_range(lower, upper);
233         _s2.set_range(lower, upper);
234         _s1.set_increments(step_inc, 0);
235         _s2.set_increments(step_inc, 0);
237         _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
238         _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
240         pack_start(_s1, false, false);
241         pack_start(_s2, false, false);
242     }
244     Gtk::SpinButton& get_spinbutton1()
245     {
246         return _s1;
247     }
249     Gtk::SpinButton& get_spinbutton2()
250     {
251         return _s2;
252     }
254     virtual Glib::ustring get_as_attribute() const
255     {
256         double v1 = _s1.get_value();
257         double v2 = _s2.get_value();
259         if(_s1.get_digits() == 0) {
260             v1 = (int)v1;
261             v2 = (int)v2;
262         }
264         return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
265     }
267     virtual void set_from_attribute(SPObject* o)
268     {
269         const gchar* val = attribute_value(o);
270         NumberOptNumber n;
271         if(val) {
272             n.set(val);
273         } else {
274             n.set(get_default()->as_charptr());
275         }
276         _s1.set_value(n.getNumber());
277         _s2.set_value(n.getOptNumber());
279     }
280 private:
281     Gtk::SpinButton _s1, _s2;
282 };
284 class ColorButton : public Gtk::ColorButton, public AttrWidget
286 public:
287     ColorButton(unsigned int def, const SPAttributeEnum a, char* tip_text)
288         : AttrWidget(a, def)
289     {
290         signal_color_set().connect(signal_attr_changed().make_slot());
291         if (tip_text) _tt.set_tip(*this, tip_text);
293         Gdk::Color col;
294         col.set_rgb(65535, 65535, 65535);
295         set_color(col);
296     }
298     // Returns the color in 'rgb(r,g,b)' form.
299     Glib::ustring get_as_attribute() const
300     {
301         std::ostringstream os;
302         const Gdk::Color c = get_color();
303         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?
304         os << "rgb(" << r << "," << g << "," << b << ")";
305         return os.str();
306     }
309     void set_from_attribute(SPObject* o)
310     {
311         const gchar* val = attribute_value(o);
312         guint32 i = 0;
313         if(val) {
314             i = sp_svg_read_color(val, 0xFFFFFFFF);
315         } else {
316             i = (guint32) get_default()->as_uint();
317         }
318         const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
319         Gdk::Color col;
320         col.set_rgb(r * 256, g * 256, b * 256);
321         set_color(col);
322     }
323 };
325 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
326 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
328 public:
329     MatrixAttr(const SPAttributeEnum a, char* tip_text = NULL)
330         : AttrWidget(a), _locked(false)
331     {
332         _model = Gtk::ListStore::create(_columns);
333         _tree.set_model(_model);
334         _tree.set_headers_visible(false);
335         _tree.show();
336         add(_tree);
337         set_shadow_type(Gtk::SHADOW_IN);
338         if (tip_text) _tt.set_tip(_tree, tip_text);
339     }
341     std::vector<double> get_values() const
342     {
343         std::vector<double> vec;
344         for(Gtk::TreeIter iter = _model->children().begin();
345             iter != _model->children().end(); ++iter) {
346             for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
347                 vec.push_back((*iter)[_columns.cols[c]]);
348         }
349         return vec;
350     }
352     void set_values(const std::vector<double>& v)
353     {
354         unsigned i = 0;
355         for(Gtk::TreeIter iter = _model->children().begin();
356             iter != _model->children().end(); ++iter) {
357             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
358                 if(i >= v.size())
359                     return;
360                 (*iter)[_columns.cols[c]] = v[i];
361                 ++i;
362             }
363         }
364     }
366     Glib::ustring get_as_attribute() const
367     {
368         std::ostringstream os;
370         for(Gtk::TreeIter iter = _model->children().begin();
371             iter != _model->children().end(); ++iter) {
372             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
373                 os << (*iter)[_columns.cols[c]] << " ";
374             }
375         }
377         return os.str();
378     }
380     void set_from_attribute(SPObject* o)
381     {
382         if(o) {
383             if(SP_IS_FECONVOLVEMATRIX(o)) {
384                 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
385                 int cols, rows;
386                 cols = (int)conv->order.getNumber();
387                 if(cols > 5)
388                     cols = 5;
389                 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
390                 update(o, rows, cols);
391             }
392             else if(SP_IS_FECOLORMATRIX(o))
393                 update(o, 4, 5);
394         }
395     }
396 private:
397     class MatrixColumns : public Gtk::TreeModel::ColumnRecord
398     {
399     public:
400         MatrixColumns()
401         {
402             cols.resize(5);
403             for(unsigned i = 0; i < cols.size(); ++i)
404                 add(cols[i]);
405         }
406         std::vector<Gtk::TreeModelColumn<double> > cols;
407     };
409     void update(SPObject* o, const int rows, const int cols)
410     {
411         if(_locked)
412             return;
414         _model->clear();
416         _tree.remove_all_columns();
418         std::vector<gdouble>* values = NULL;
419         if(SP_IS_FECOLORMATRIX(o))
420             values = &SP_FECOLORMATRIX(o)->values;
421         else if(SP_IS_FECONVOLVEMATRIX(o))
422             values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
423         else
424             return;
426         if(o) {
427             int ndx = 0;
429             for(int i = 0; i < cols; ++i) {
430                 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
431                 dynamic_cast<Gtk::CellRendererText*>(
432                     _tree.get_column_cell_renderer(i))->signal_edited().connect(
433                         sigc::mem_fun(*this, &MatrixAttr::rebind));
434             }
436             for(int r = 0; r < rows; ++r) {
437                 Gtk::TreeRow row = *(_model->append());
438                 // Default to identity matrix
439                 for(int c = 0; c < cols; ++c, ++ndx)
440                     row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
441             }
442         }
443     }
445     void rebind(const Glib::ustring&, const Glib::ustring&)
446     {
447         _locked = true;
448         signal_attr_changed()();
449         _locked = false;
450     }
452     bool _locked;
453     Gtk::TreeView _tree;
454     Glib::RefPtr<Gtk::ListStore> _model;
455     MatrixColumns _columns;
456 };
458 // Displays a matrix or a slider for feColorMatrix
459 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
461 public:
462     ColorMatrixValues()
463         : AttrWidget(SP_ATTR_VALUES),
464           // TRANSLATORS: this dialog is accessible via menu Filters - Filter editor
465           _matrix(SP_ATTR_VALUES, _("This matrix determines a linear transform on color space. Each line affects one of the color components. Each column determines how much of each color component from the input is passed to the output. The last column does not depend on input colors, so can be used to adjust a constant component value.")),
466           _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
467           _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
468           _label(_("None"), Gtk::ALIGN_LEFT),
469           _use_stored(false),
470           _saturation_store(0),
471           _angle_store(0)
472     {
473         _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
474         _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
475         _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
476         signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
478         _matrix.show();
479         _saturation.show();
480         _angle.show();
481         _label.show();
482         _label.set_sensitive(false);
484         set_shadow_type(Gtk::SHADOW_NONE);
485     }
487     virtual void set_from_attribute(SPObject* o)
488     {
489         if(SP_IS_FECOLORMATRIX(o)) {
490             SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
491             remove();
492             switch(col->type) {
493                 case COLORMATRIX_SATURATE:
494                     add(_saturation);
495                     if(_use_stored)
496                         _saturation.set_value(_saturation_store);
497                     else
498                         _saturation.set_from_attribute(o);
499                     break;
500                 case COLORMATRIX_HUEROTATE:
501                     add(_angle);
502                     if(_use_stored)
503                         _angle.set_value(_angle_store);
504                     else
505                         _angle.set_from_attribute(o);
506                     break;
507                 case COLORMATRIX_LUMINANCETOALPHA:
508                     add(_label);
509                     break;
510                 case COLORMATRIX_MATRIX:
511                 default:
512                     add(_matrix);
513                     if(_use_stored)
514                         _matrix.set_values(_matrix_store);
515                     else
516                         _matrix.set_from_attribute(o);
517                     break;
518             }
519             _use_stored = true;
520         }
521     }
523     virtual Glib::ustring get_as_attribute() const
524     {
525         const Widget* w = get_child();
526         if(w == &_label)
527             return "";
528         else
529             return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
530     }
532     void clear_store()
533     {
534         _use_stored = false;
535     }
536 private:
537     void update_store()
538     {
539         const Widget* w = get_child();
540         if(w == &_matrix)
541             _matrix_store = _matrix.get_values();
542         else if(w == &_saturation)
543             _saturation_store = _saturation.get_value();
544         else if(w == &_angle)
545             _angle_store = _angle.get_value();
546     }
548     MatrixAttr _matrix;
549     SpinSlider _saturation;
550     SpinSlider _angle;
551     Gtk::Label _label;
553     // Store separate values for the different color modes
554     bool _use_stored;
555     std::vector<double> _matrix_store;
556     double _saturation_store;
557     double _angle_store;
558 };
560 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
562 //Displays a chooser for feImage input
563 //It may be a filename or the id for an SVG Element
564 //described in xlink:href syntax
565 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
567 public:
568     FileOrElementChooser(const SPAttributeEnum a)
569         : AttrWidget(a)
570     {
571         pack_start(_entry, false, false);
572         pack_start(_fromFile, false, false);
573         pack_start(_fromSVGElement, false, false);
575         _fromFile.set_label(_("Image File"));
576         _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
578         _fromSVGElement.set_label(_("Selected SVG Element"));
579         _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
581         _entry.signal_changed().connect(signal_attr_changed().make_slot());
583         show_all();
585     }
587     // Returns the element in xlink:href form.
588     Glib::ustring get_as_attribute() const
589     {
590         return _entry.get_text();
591     }
594     void set_from_attribute(SPObject* o)
595     {
596         const gchar* val = attribute_value(o);
597         if(val) {
598             _entry.set_text(val);
599         } else {
600             _entry.set_text("");
601         }
602     }
604     void set_desktop(SPDesktop* d){
605         _desktop = d;
606     }
608 private:
609     void select_svg_element(){
610         Inkscape::Selection* sel = sp_desktop_selection(_desktop);
611         if (sel->isEmpty()) return;
612         Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
613         if (!node || !node->matchAttributeName("id")) return;
615         std::ostringstream xlikhref;
616         xlikhref << "#" << node->attribute("id");
617         _entry.set_text(xlikhref.str());
618     }
620     void select_file(){
622         //# Get the current directory for finding files
623         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
624         Glib::ustring open_path;
625         Glib::ustring attr = prefs->getString("/dialogs/open/path");
626         if (!attr.empty())
627             open_path = attr;
629         //# Test if the open_path directory exists
630         if (!Inkscape::IO::file_test(open_path.c_str(),
631                   (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
632             open_path = "";
634         //# If no open path, default to our home directory
635         if (open_path.size() < 1)
636             {
637             open_path = g_get_home_dir();
638             open_path.append(G_DIR_SEPARATOR_S);
639             }
641         //# Create a dialog if we don't already have one
642         if (!selectFeImageFileInstance) {
643             selectFeImageFileInstance =
644                   Inkscape::UI::Dialog::FileOpenDialog::create(
645                      *_desktop->getToplevel(),
646                      open_path,
647                      Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not just svg*/
648                      (char const *)_("Select an image to be used as feImage input"));
649         }
651         //# Show the dialog
652         bool const success = selectFeImageFileInstance->show();
653         if (!success)
654             return;
656         //# User selected something.  Get name and type
657         Glib::ustring fileName = selectFeImageFileInstance->getFilename();
659         if (fileName.size() > 0) {
661             Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
663             if ( newFileName.size() > 0)
664                 fileName = newFileName;
665             else
666                 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
668             open_path = fileName;
669             open_path.append(G_DIR_SEPARATOR_S);
670             prefs->setString("/dialogs/open/path", open_path);
672             _entry.set_text(fileName);
673         }
674         return;
675     }
677     Gtk::Entry _entry;
678     Gtk::Button _fromFile;
679     Gtk::Button _fromSVGElement;
680     SPDesktop* _desktop;
681 };
683 class FilterEffectsDialog::Settings
685 public:
686     typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
688     Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
689         : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
690     {
691         _groups.resize(_max_types);
692         _attrwidgets.resize(_max_types);
693         _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
695         for(int i = 0; i < _max_types; ++i) {
696             _groups[i] = new Gtk::VBox;
697             b.pack_start(*_groups[i], false, false);
698         }
699         _current_type = 0;
700     }
702     ~Settings()
703     {
704         for(int i = 0; i < _max_types; ++i) {
705             delete _groups[i];
706             for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
707                 delete _attrwidgets[i][j];
708         }
709     }
711     // Show the active settings group and update all the AttrWidgets with new values
712     void show_and_update(const int t, SPObject* ob)
713     {
714         if(t != _current_type) {
715             type(t);
716             for(unsigned i = 0; i < _groups.size(); ++i)
717                 _groups[i]->hide();
718         }
719         if(t >= 0)
720             _groups[t]->show_all();
722         _dialog.set_attrs_locked(true);
723         for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
724             _attrwidgets[_current_type][i]->set_from_attribute(ob);
725         _dialog.set_attrs_locked(false);
726     }
728     int get_current_type() const
729     {
730         return _current_type;
731     }
733     void type(const int t)
734     {
735         _current_type = t;
736     }
738     void add_no_params()
739     {
740         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
741         add_widget(lbl, "");
742     }
744     void add_notimplemented()
745     {
746         Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
747         add_widget(lbl, "");
748     }
750     // LightSource
751     LightSourceControl* add_lightsource();
753     // CheckBox
754     CheckButtonAttr* add_checkbutton(bool def, const SPAttributeEnum attr, const Glib::ustring& label,
755                                      const Glib::ustring& tv, const Glib::ustring& fv, char* tip_text = NULL)
756     {
757         CheckButtonAttr* cb = new CheckButtonAttr(def, label, tv, fv, attr, tip_text);
758         add_widget(cb, "");
759         add_attr_widget(cb);
760         return cb;
761     }
763     // ColorButton
764     ColorButton* add_color(unsigned int def, const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text = NULL)
765     {
766         ColorButton* col = new ColorButton(def, attr, tip_text);
767         add_widget(col, label);
768         add_attr_widget(col);
769         return col;
770     }
772     // Matrix
773     MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text)
774     {
775         MatrixAttr* conv = new MatrixAttr(attr, tip_text);
776         add_widget(conv, label);
777         add_attr_widget(conv);
778         return conv;
779     }
781     // ColorMatrixValues
782     ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
783     {
784         ColorMatrixValues* cmv = new ColorMatrixValues();
785         add_widget(cmv, label);
786         add_attr_widget(cmv);
787         return cmv;
788     }
790     // SpinSlider
791     SpinSlider* add_spinslider(double def, const SPAttributeEnum attr, const Glib::ustring& label,
792                          const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
793     {
794         SpinSlider* spinslider = new SpinSlider(def, lo, hi, step_inc, climb, digits, attr, tip_text);
795         add_widget(spinslider, label);
796         add_attr_widget(spinslider);
797         return spinslider;
798     }
800     // DualSpinSlider
801     DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
802                                        const double lo, const double hi, const double step_inc,
803                                        const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
804     {
805         DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
806         add_widget(dss, label);
807         add_attr_widget(dss);
808         return dss;
809     }
811     // DualSpinButton
812     DualSpinButton* add_dualspinbutton(char* defalt_value, const SPAttributeEnum attr, const Glib::ustring& label,
813                                        const double lo, const double hi, const double step_inc,
814                                        const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
815     {
816         DualSpinButton* dsb = new DualSpinButton(defalt_value, lo, hi, step_inc, climb, digits, attr, tip1, tip2);
817         add_widget(dsb, label);
818         add_attr_widget(dsb);
819         return dsb;
820     }
822     // MultiSpinButton
823     MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
824                                          const Glib::ustring& label, const double lo, const double hi,
825                                          const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
826     {
827         std::vector<SPAttributeEnum> attrs;
828         attrs.push_back(attr1);
829         attrs.push_back(attr2);
831         std::vector<double> default_values;
832         default_values.push_back(def1);
833         default_values.push_back(def2);
835         std::vector<char*> tips;
836         tips.push_back(tip1);
837         tips.push_back(tip2);
839         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
840         add_widget(msb, label);
841         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
842             add_attr_widget(msb->get_spinbuttons()[i]);
843         return msb;
844     }
845     MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
846                                          const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
847                                          const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
848     {
849         std::vector<SPAttributeEnum> attrs;
850         attrs.push_back(attr1);
851         attrs.push_back(attr2);
852         attrs.push_back(attr3);
854         std::vector<double> default_values;
855         default_values.push_back(def1);
856         default_values.push_back(def2);
857         default_values.push_back(def3);
859         std::vector<char*> tips;
860         tips.push_back(tip1);
861         tips.push_back(tip2);
862         tips.push_back(tip3);
864         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
865         add_widget(msb, label);
866         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
867             add_attr_widget(msb->get_spinbuttons()[i]);
868         return msb;
869     }
871     // FileOrElementChooser
872     FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
873     {
874         FileOrElementChooser* foech = new FileOrElementChooser(attr);
875         foech->set_desktop(_dialog.getDesktop());
876         add_widget(foech, label);
877         add_attr_widget(foech);
878         return foech;
879     }
881     // ComboBoxEnum
882     template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
883                                   const Glib::ustring& label,
884                                   const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
885     {
886         ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
887         add_widget(combo, label);
888         add_attr_widget(combo->get_attrwidget());
889         return combo->get_attrwidget();
890     }
891 private:
892     Gtk::Tooltips _tt;
894     void add_attr_widget(AttrWidget* a)
895     {
896         _attrwidgets[_current_type].push_back(a);
897         a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
898     }
900     /* Adds a new settings widget using the specified label. The label will be formatted with a colon
901        and all widgets within the setting group are aligned automatically. */
902     void add_widget(Gtk::Widget* w, const Glib::ustring& label)
903     {
904         Gtk::Label *lbl = 0;
905         Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
906         hb->set_spacing(12);
908         if(label != "") {
909             //lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT)); colon now in label (LP #358921)
910             lbl = Gtk::manage(new Gtk::Label(label, Gtk::ALIGN_LEFT));
911             hb->pack_start(*lbl, false, false);
912             _size_group->add_widget(*lbl);
913             lbl->show();
914         }
916         hb->pack_start(*w);
917         _groups[_current_type]->pack_start(*hb);
918         hb->show();
919         w->show();
920     }
922     std::vector<Gtk::VBox*> _groups;
923     Glib::RefPtr<Gtk::SizeGroup> _size_group;
924     FilterEffectsDialog& _dialog;
925     SetAttrSlot _set_attr_slot;
926     std::vector<std::vector< AttrWidget*> > _attrwidgets;
927     int _current_type, _max_types;
928 };
930 // Settings for the three light source objects
931 class FilterEffectsDialog::LightSourceControl : public AttrWidget
933 public:
934     LightSourceControl(FilterEffectsDialog& d)
935         : AttrWidget(SP_ATTR_INVALID),
936           _dialog(d),
937           _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
938           _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
939           _light_source(LightSourceConverter),
940           _locked(false)
941     {
942         _light_box.pack_start(_light_label, false, false);
943         _light_box.pack_start(_light_source);
944         _light_box.show_all();
945         _light_box.set_spacing(12);
946         _dialog._sizegroup->add_widget(_light_label);
948         _box.add(_light_box);
949         _box.reorder_child(_light_box, 0);
950         _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
952         // FIXME: these range values are complete crap
954         _settings.type(LIGHT_DISTANT);
955         _settings.add_spinslider(0, SP_ATTR_AZIMUTH, _("Azimuth"), 0, 360, 1, 1, 0, _("Direction angle for the light source on the XY plane, in degrees"));
956         _settings.add_spinslider(0, SP_ATTR_ELEVATION, _("Elevation"), 0, 360, 1, 1, 0, _("Direction angle for the light source on the YZ plane, in degrees"));
958         _settings.type(LIGHT_POINT);
959         _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"));
961         _settings.type(LIGHT_SPOT);
962         _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"));
963         _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
964                                       SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
965                                       _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
966         _settings.add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
967         //TODO: here I have used 100 degrees as default value. But spec says that if not specified, no limiting cone is applied. So, there should be a way for the user to set a "no limiting cone" option.
968         _settings.add_spinslider(100, SP_ATTR_LIMITINGCONEANGLE, _("Cone Angle"), 1, 100, 1, 1, 0, _("This is the angle between the spot light axis (i.e. the axis between the light source and the point to which it is pointing at) and the spot light cone. No light is projected outside this cone."));
969     }
971     Gtk::VBox& get_box()
972     {
973         return _box;
974     }
975 protected:
976     Glib::ustring get_as_attribute() const
977     {
978         return "";
979     }
980     void set_from_attribute(SPObject* o)
981     {
982         if(_locked)
983             return;
985         _locked = true;
987         SPObject* child = o->children;
989         if(SP_IS_FEDISTANTLIGHT(child))
990             _light_source.set_active(0);
991         else if(SP_IS_FEPOINTLIGHT(child))
992             _light_source.set_active(1);
993         else if(SP_IS_FESPOTLIGHT(child))
994             _light_source.set_active(2);
995         else
996             _light_source.set_active(-1);
998         update();
1000         _locked = false;
1001     }
1002 private:
1003     void on_source_changed()
1004     {
1005         if(_locked)
1006             return;
1008         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1009         if(prim) {
1010             _locked = true;
1012             SPObject* child = prim->children;
1013             const int ls = _light_source.get_active_row_number();
1014             // Check if the light source type has changed
1015             if(!(ls == -1 && !child) &&
1016                !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1017                !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1018                !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1019                 if(child)
1020                     sp_repr_unparent(child->repr);
1022                 if(ls != -1) {
1023                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1024                     Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1025                     prim->repr->appendChild(repr);
1026                     Inkscape::GC::release(repr);
1027                 }
1029                 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1030                 update();
1031             }
1033             _locked = false;
1034         }
1035     }
1037     void update()
1038     {
1039         _box.hide_all();
1040         _box.show();
1041         _light_box.show_all();
1043         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1044         if(prim && prim->children)
1045             _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1046     }
1048     FilterEffectsDialog& _dialog;
1049     Gtk::VBox _box;
1050     Settings _settings;
1051     Gtk::HBox _light_box;
1052     Gtk::Label _light_label;
1053     ComboBoxEnum<LightSource> _light_source;
1054     bool _locked;
1055 };
1057 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1059     LightSourceControl* ls = new LightSourceControl(_dialog);
1060     add_attr_widget(ls);
1061     add_widget(&ls->get_box(), "");
1062     return ls;
1065 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1066                                           sigc::slot<void> rem)
1068     Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1070     menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1071     Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1072     menu->append(*mi);
1073     mi->signal_activate().connect(rem);
1074     mi->show();
1075     menu->accelerate(parent);
1077     return menu;
1080 /*** FilterModifier ***/
1081 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1082     : _dialog(d), _add(Gtk::Stock::NEW), _observer(new Inkscape::XML::SignalObserver)
1084     Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1085     pack_start(*sw);
1086     pack_start(_add, false, false);
1087     sw->add(_list);
1089     _model = Gtk::ListStore::create(_columns);
1090     _list.set_model(_model);
1091     _cell_toggle.set_active(true);
1092     const int selcol = _list.append_column("", _cell_toggle);
1093     Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1094     if(col)
1095        col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1096     _list.append_column_editable(_("_Filter"), _columns.label);
1097     ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1098         signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1100     sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1101     sw->set_shadow_type(Gtk::SHADOW_IN);
1102     show_all_children();
1103     _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1104     _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1105     _list.signal_button_release_event().connect_notify(
1106         sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1107     _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1108                               sigc::mem_fun(*this, &FilterModifier::remove_filter));
1109     _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1110                                  _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1111     _menu->accelerate(*this);
1113     _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1114     _observer->signal_changed().connect(signal_filter_changed().make_slot());
1115     g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1116                      G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1118     g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1119                      G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1120     g_signal_connect(G_OBJECT(INKSCAPE), "deactivate_desktop",
1121                      G_CALLBACK(&FilterModifier::on_deactivate_desktop), this);
1123     on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1124     update_filters();
1127 FilterEffectsDialog::FilterModifier::~FilterModifier()
1129    _resource_changed.disconnect();
1130    _doc_replaced.disconnect();
1133 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1135     me->_doc_replaced.disconnect();
1136     me->_doc_replaced = desktop->connectDocumentReplaced(
1137         sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1139     me->_resource_changed.disconnect();
1140     me->_resource_changed =
1141         sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1142                                               sigc::mem_fun(me, &FilterModifier::update_filters));
1144     me->_dialog.setDesktop(desktop);
1146     me->update_filters();
1149 void FilterEffectsDialog::FilterModifier::on_deactivate_desktop(Application*, SPDesktop* /*desktop*/, FilterModifier* me)
1151     me->_doc_replaced.disconnect();
1152     me->_resource_changed.disconnect();
1153     me->_dialog.setDesktop(NULL);
1157 // When the selection changes, show the active filter(s) in the dialog
1158 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1159                                                                        Selection *sel,
1160                                                                        FilterModifier* fm)
1162     if(fm && sel)
1163         fm->update_selection(sel);
1166 // Update each filter's sel property based on the current object selection;
1167 //  If the filter is not used by any selected object, sel = 0,
1168 //  otherwise sel is set to the total number of filters in use by selected objects
1169 //  If only one filter is in use, it is selected
1170 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1172     std::set<SPObject*> used;
1174     for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1175         SPObject *obj = SP_OBJECT (i->data);
1176         SPStyle *style = SP_OBJECT_STYLE (obj);
1177         if(!style || !SP_IS_ITEM(obj)) continue;
1179         if(style->filter.set && style->getFilter())
1180             used.insert(style->getFilter());
1181         else
1182             used.insert(0);
1183     }
1185     const int size = used.size();
1187     for(Gtk::TreeIter iter = _model->children().begin();
1188         iter != _model->children().end(); ++iter) {
1189         if(used.find((*iter)[_columns.filter]) != used.end()) {
1190             // If only one filter is in use by the selection, select it
1191             if(size == 1)
1192                 _list.get_selection()->select(iter);
1193             (*iter)[_columns.sel] = size;
1194         }
1195         else
1196             (*iter)[_columns.sel] = 0;
1197     }
1200 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1202     _observer->set(get_selected_filter());
1203     signal_filter_changed()();
1206 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1208     Gtk::TreeModel::iterator iter = _model->get_iter(path);
1210     if(iter) {
1211         SPFilter* filter = (*iter)[_columns.filter];
1212         filter->setLabel(text.c_str());
1213         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1214         if(iter)
1215             (*iter)[_columns.label] = text;
1216     }
1219 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1221     Gtk::TreeIter iter = _model->get_iter(path);
1223     if(iter) {
1224         SPDesktop *desktop = _dialog.getDesktop();
1225         SPDocument *doc = sp_desktop_document(desktop);
1226         SPFilter* filter = (*iter)[_columns.filter];
1227         Inkscape::Selection *sel = sp_desktop_selection(desktop);
1229         /* If this filter is the only one used in the selection, unset it */
1230         if((*iter)[_columns.sel] == 1)
1231             filter = 0;
1233         GSList const *items = sel->itemList();
1235         for (GSList const *i = items; i != NULL; i = i->next) {
1236             SPItem * item = SP_ITEM(i->data);
1237             SPStyle *style = SP_OBJECT_STYLE(item);
1238             g_assert(style != NULL);
1240             if(filter)
1241                 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1242             else
1243                 ::remove_filter(item, false);
1245             SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1246         }
1248         update_selection(sel);
1249         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS,  _("Apply filter"));
1250     }
1253 /* Add all filters in the document to the combobox.
1254    Keeps the same selection if possible, otherwise selects the first element */
1255 void FilterEffectsDialog::FilterModifier::update_filters()
1257     SPDesktop* desktop = _dialog.getDesktop();
1258     SPDocument* document = sp_desktop_document(desktop);
1259     const GSList* filters = sp_document_get_resource_list(document, "filter");
1261     _model->clear();
1263     for(const GSList *l = filters; l; l = l->next) {
1264         Gtk::TreeModel::Row row = *_model->append();
1265         SPFilter* f = (SPFilter*)l->data;
1266         row[_columns.filter] = f;
1267         const gchar* lbl = f->label();
1268         const gchar* id = SP_OBJECT_ID(f);
1269         row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1270     }
1272     update_selection(desktop->selection);
1273     _dialog.update_filter_general_settings_view();
1276 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1278     if(_list.get_selection()) {
1279         Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1281         if(i)
1282             return (*i)[_columns.filter];
1283     }
1285     return 0;
1288 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1290     if(filter) {
1291         for(Gtk::TreeModel::iterator i = _model->children().begin();
1292             i != _model->children().end(); ++i) {
1293             if((*i)[_columns.filter] == filter) {
1294                 _list.get_selection()->select(i);
1295                 break;
1296             }
1297         }
1298     }
1301 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1303     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1304         const bool sensitive = get_selected_filter() != NULL;
1305         _menu->items()[0].set_sensitive(sensitive);
1306         _menu->items()[1].set_sensitive(sensitive);
1307         _menu->popup(event->button, event->time);
1308     }
1311 void FilterEffectsDialog::FilterModifier::add_filter()
1313     SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1314     SPFilter* filter = new_filter(doc);
1316     const int count = _model->children().size();
1317     std::ostringstream os;
1318     os << "filter" << count;
1319     filter->setLabel(os.str().c_str());
1321     update_filters();
1323     select_filter(filter);
1325     sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1328 void FilterEffectsDialog::FilterModifier::remove_filter()
1330     SPFilter *filter = get_selected_filter();
1332     if(filter) {
1333         SPDocument* doc = filter->document;
1334         sp_repr_unparent(filter->repr);
1336         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1338         update_filters();
1339     }
1342 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1344     SPFilter* filter = get_selected_filter();
1346     if(filter) {
1347         Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1348         repr = repr->duplicate(repr->document());
1349         parent->appendChild(repr);
1351         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1353         update_filters();
1354     }
1357 void FilterEffectsDialog::FilterModifier::rename_filter()
1359     _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1362 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1363     : Glib::ObjectBase(typeid(CellRendererConnection)),
1364       _primitive(*this, "primitive", 0)
1365 {}
1367 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1369     return _primitive.get_proxy();
1372 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1374     _text_width = w;
1377 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1379     return _text_width;
1382 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1383     Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1384     int* x_offset, int* y_offset, int* width, int* height) const
1386     PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1388     if(x_offset)
1389         (*x_offset) = 0;
1390     if(y_offset)
1391         (*y_offset) = 0;
1392     if(width)
1393         (*width) = size * primlist.primitive_count() + _text_width * 7;
1394     if(height) {
1395         // Scale the height depending on the number of inputs, unless it's
1396         // the first primitive, in which case there are no connections
1397         SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1398         (*height) = size * input_count(prim);
1399     }
1402 /*** PrimitiveList ***/
1403 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1404     : _dialog(d),
1405       _in_drag(0),
1406       _observer(new Inkscape::XML::SignalObserver)
1408     d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1410     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1411     signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1413     _model = Gtk::ListStore::create(_columns);
1415     set_reorderable(true);
1417     set_model(_model);
1418     append_column(_("_Effect"), _columns.type);
1420     _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1421     get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1422     signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1424     _connection_cell.set_text_width(init_text());
1426     int cols_count = append_column(_("Connections"), _connection_cell);
1427     Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1428     if(col)
1429        col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1432 // Sets up a vertical Pango context/layout, and returns the largest
1433 // width needed to render the FilterPrimitiveInput labels.
1434 int FilterEffectsDialog::PrimitiveList::init_text()
1436     // Set up a vertical context+layout
1437     Glib::RefPtr<Pango::Context> context = create_pango_context();
1438     const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1439     context->set_matrix(matrix);
1440     _vertical_layout = Pango::Layout::create(context);
1442     int maxfont = 0;
1443     for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1444         _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1445         int fontw, fonth;
1446         _vertical_layout->get_pixel_size(fontw, fonth);
1447         if(fonth > maxfont)
1448             maxfont = fonth;
1449     }
1451     return maxfont;
1454 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1456     return _signal_primitive_changed;
1459 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1461     _observer->set(get_selected());
1462     signal_primitive_changed()();
1463     _dialog._color_matrix_values->clear_store();
1466 /* Add all filter primitives in the current to the list.
1467    Keeps the same selection if possible, otherwise selects the first element */
1468 void FilterEffectsDialog::PrimitiveList::update()
1470     SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1471     const SPFilterPrimitive* active_prim = get_selected();
1472     bool active_found = false;
1474     _model->clear();
1476     if(f) {
1477         _dialog._primitive_box.set_sensitive(true);
1478         _dialog.update_filter_general_settings_view();
1479         for(SPObject *prim_obj = f->children;
1480                 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1481                 prim_obj = prim_obj->next) {
1482             SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1483             if(prim) {
1484                 Gtk::TreeModel::Row row = *_model->append();
1485                 row[_columns.primitive] = prim;
1486                 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1487                 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1488                 row[_columns.id] = SP_OBJECT_ID(prim);
1490                 if(prim == active_prim) {
1491                     get_selection()->select(row);
1492                     active_found = true;
1493                 }
1494             }
1495         }
1497         if(!active_found && _model->children().begin())
1498             get_selection()->select(_model->children().begin());
1500         columns_autosize();
1501     }
1502     else {
1503         _dialog._primitive_box.set_sensitive(false);
1504     }
1507 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1509     _primitive_menu = menu;
1512 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1514     if(_dialog._filter_modifier.get_selected_filter()) {
1515         Gtk::TreeModel::iterator i = get_selection()->get_selected();
1516         if(i)
1517             return (*i)[_columns.primitive];
1518     }
1520     return 0;
1523 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1525     for(Gtk::TreeIter i = _model->children().begin();
1526         i != _model->children().end(); ++i) {
1527         if((*i)[_columns.primitive] == prim)
1528             get_selection()->select(i);
1529     }
1532 void FilterEffectsDialog::PrimitiveList::remove_selected()
1534     SPFilterPrimitive* prim = get_selected();
1536     if(prim) {
1537         _observer->set(0);
1539         sp_repr_unparent(prim->repr);
1541         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1542                          _("Remove filter primitive"));
1544         update();
1545     }
1548 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1550     Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1551     Glib::RefPtr<Gdk::Window> win = get_bin_window();
1552     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1554     SPFilterPrimitive* prim = get_selected();
1555     int row_count = get_model()->children().size();
1557     int fheight = CellRendererConnection::size;
1558     Gdk::Rectangle rct, vis;
1559     Gtk::TreeIter row = get_model()->children().begin();
1560     int text_start_x = 0;
1561     if(row) {
1562         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1563         get_visible_rect(vis);
1564         int vis_x, vis_y;
1565         tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1567         text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter._length + 1) + 1;
1568         for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1569             _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1570             const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1571             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());
1572             get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1573             get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1574         }
1575     }
1577     int row_index = 0;
1578     for(; row != get_model()->children().end(); ++row, ++row_index) {
1579         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1580         const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1582         // Check mouse state
1583         int mx, my;
1584         Gdk::ModifierType mask;
1585         get_bin_window()->get_pointer(mx, my, mask);
1587         // Outline the bottom of the connection area
1588         const int outline_x = x + fheight * (row_count - row_index);
1589         get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1591         // Side outline
1592         get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1594         std::vector<Gdk::Point> con_poly;
1595         int con_drag_y = 0;
1596         bool inside;
1597         const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1598         const int inputs = input_count(row_prim);
1600         if(SP_IS_FEMERGE(row_prim)) {
1601             for(int i = 0; i < inputs; ++i) {
1602                 inside = do_connection_node(row, i, con_poly, mx, my);
1603                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1604                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1605                                                inside, con_poly);
1607                 if(_in_drag == (i + 1))
1608                     con_drag_y = con_poly[2].get_y();
1610                 if(_in_drag != (i + 1) || row_prim != prim)
1611                     draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1612             }
1613         }
1614         else {
1615             // Draw "in" shape
1616             inside = do_connection_node(row, 0, con_poly, mx, my);
1617             con_drag_y = con_poly[2].get_y();
1618             get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1619                                            darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1620                                            inside, con_poly);
1622             // Draw "in" connection
1623             if(_in_drag != 1 || row_prim != prim)
1624                 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1626             if(inputs == 2) {
1627                 // Draw "in2" shape
1628                 inside = do_connection_node(row, 1, con_poly, mx, my);
1629                 if(_in_drag == 2)
1630                     con_drag_y = con_poly[2].get_y();
1631                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1632                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1633                                                inside, con_poly);
1634                 // Draw "in2" connection
1635                 if(_in_drag != 2 || row_prim != prim)
1636                     draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1637             }
1638         }
1640         // Draw drag connection
1641         if(row_prim == prim && _in_drag) {
1642             get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1643                                         mx, con_drag_y);
1644             get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1645         }
1646     }
1648     return true;
1651 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1652                                                          const int text_start_x, const int x1, const int y1,
1653                                                          const int row_count)
1655     int src_id = 0;
1656     Gtk::TreeIter res = find_result(input, attr, src_id);
1657     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1658     Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1659     Glib::RefPtr<Gdk::GC> gc;
1661     const bool is_first = input == get_model()->children().begin();
1662     const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1663     const bool use_default = !res && !is_merge;
1665     if(res == input || (use_default && is_first)) {
1666         // Draw straight connection to a standard input
1667         // Draw a lighter line for an implicit connection to a standard input
1668         const int tw = _connection_cell.get_text_width();
1669         gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1670         gc = (use_default && is_first) ? lightgc : darkgc;
1671         get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1672         get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1673     }
1674     else {
1675         // Draw an 'L'-shaped connection to another filter primitive
1676         // If no connection is specified, draw a light connection to the previous primitive
1677         gc = use_default ? lightgc : darkgc;
1679         if(use_default) {
1680             res = input;
1681             --res;
1682         }
1684         if(res) {
1685             Gdk::Rectangle rct;
1687             get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1688             const int fheight = CellRendererConnection::size;
1690             get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1691             const int row_index = find_index(res);
1692             const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1693             const int y2 = rct.get_y() + rct.get_height();
1695             // Draw a bevelled 'L'-shaped connection
1696             get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1697             get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1698             get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1699         }
1700     }
1703 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1704 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1705                                                             std::vector<Gdk::Point>& points,
1706                                                             const int ix, const int iy)
1708     Gdk::Rectangle rct;
1709     const int icnt = input_count((*row)[_columns.primitive]);
1711     get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1712     const int fheight = CellRendererConnection::size;
1714     get_cell_area(_model->get_path(row), *get_column(1), rct);
1715     const float h = rct.get_height() / icnt;
1717     const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1718     const int con_w = (int)(fheight * 0.35f);
1719     const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1720     points.clear();
1721     points.push_back(Gdk::Point(x, con_y));
1722     points.push_back(Gdk::Point(x, con_y + con_w * 2));
1723     points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1725     return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1728 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1729                                                                     const int attr, int& src_id)
1731     SPFilterPrimitive* prim = (*start)[_columns.primitive];
1732     Gtk::TreeIter target = _model->children().end();
1733     int image = 0;
1735     if(SP_IS_FEMERGE(prim)) {
1736         int c = 0;
1737         bool found = false;
1738         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1739             if(c == attr && SP_IS_FEMERGENODE(o)) {
1740                 image = SP_FEMERGENODE(o)->input;
1741                 found = true;
1742             }
1743         }
1744         if(!found)
1745             return target;
1746     }
1747     else {
1748         if(attr == SP_ATTR_IN)
1749             image = prim->image_in;
1750         else if(attr == SP_ATTR_IN2) {
1751             if(SP_IS_FEBLEND(prim))
1752                 image = SP_FEBLEND(prim)->in2;
1753             else if(SP_IS_FECOMPOSITE(prim))
1754                 image = SP_FECOMPOSITE(prim)->in2;
1755             else if(SP_IS_FEDISPLACEMENTMAP(prim))
1756                 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1757             else
1758                 return target;
1759         }
1760         else
1761             return target;
1762     }
1764     if(image >= 0) {
1765         for(Gtk::TreeIter i = _model->children().begin();
1766             i != start; ++i) {
1767             if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1768                 target = i;
1769         }
1770         return target;
1771     }
1772     else if(image < -1) {
1773         src_id = -(image + 2);
1774         return start;
1775     }
1777     return target;
1780 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1782     int i = 0;
1783     for(Gtk::TreeIter iter = _model->children().begin();
1784         iter != target; ++iter, ++i){};
1785     return i;
1788 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1790     Gtk::TreePath path;
1791     Gtk::TreeViewColumn* col;
1792     const int x = (int)e->x, y = (int)e->y;
1793     int cx, cy;
1795     _drag_prim = 0;
1797     if(get_path_at_pos(x, y, path, col, cx, cy)) {
1798         Gtk::TreeIter iter = _model->get_iter(path);
1799         std::vector<Gdk::Point> points;
1801         _drag_prim = (*iter)[_columns.primitive];
1802         const int icnt = input_count(_drag_prim);
1804         for(int i = 0; i < icnt; ++i) {
1805             if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1806                 _in_drag = i + 1;
1807                 break;
1808             }
1809         }
1811         queue_draw();
1812     }
1814     if(_in_drag) {
1815         _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1816         _autoscroll = 0;
1817         get_selection()->select(path);
1818         return true;
1819     }
1820     else
1821         return Gtk::TreeView::on_button_press_event(e);
1824 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1826     const int speed = 10;
1827     const int limit = 15;
1829     Gdk::Rectangle vis;
1830     get_visible_rect(vis);
1831     int vis_x, vis_y;
1832     tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1833     const int top = vis_y + vis.get_height();
1835     // When autoscrolling during a connection drag, set the speed based on
1836     // where the mouse is in relation to the edges.
1837     if(e->y < vis_y)
1838         _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1839     else if(e->y < vis_y + limit)
1840         _autoscroll = -speed;
1841     else if(e->y > top)
1842         _autoscroll = (int)(speed + (e->y - top) / 5);
1843     else if(e->y > top - limit)
1844         _autoscroll = speed;
1845     else
1846         _autoscroll = 0;
1848     queue_draw();
1850     return Gtk::TreeView::on_motion_notify_event(e);
1853 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1855     SPFilterPrimitive *prim = get_selected(), *target;
1857     _scroll_connection.disconnect();
1859     if(_in_drag && prim) {
1860         Gtk::TreePath path;
1861         Gtk::TreeViewColumn* col;
1862         int cx, cy;
1864         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1865             const gchar *in_val = 0;
1866             Glib::ustring result;
1867             Gtk::TreeIter target_iter = _model->get_iter(path);
1868             target = (*target_iter)[_columns.primitive];
1869             col = get_column(1);
1871             Gdk::Rectangle rct;
1872             get_cell_area(path, *col, rct);
1873             const int twidth = _connection_cell.get_text_width();
1874             const int sources_x = rct.get_width() - twidth * FPInputConverter._length;
1875             if(cx > sources_x) {
1876                 int src = (cx - sources_x) / twidth;
1877                 if (src < 0) {
1878                     src = 0;
1879                 } else if(src >= static_cast<int>(FPInputConverter._length)) {
1880                     src = FPInputConverter._length - 1;
1881                 }
1882                 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1883                 in_val = result.c_str();
1884             }
1885             else {
1886                 // Ensure that the target comes before the selected primitive
1887                 for(Gtk::TreeIter iter = _model->children().begin();
1888                     iter != get_selection()->get_selected(); ++iter) {
1889                     if(iter == target_iter) {
1890                         Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1891                         // Make sure the target has a result
1892                         const gchar *gres = repr->attribute("result");
1893                         if(!gres) {
1894                             result = sp_filter_get_new_result_name(SP_FILTER(prim->parent));
1895                             repr->setAttribute("result", result.c_str());
1896                             in_val = result.c_str();
1897                         }
1898                         else
1899                             in_val = gres;
1900                         break;
1901                     }
1902                 }
1903             }
1905             if(SP_IS_FEMERGE(prim)) {
1906                 int c = 1;
1907                 bool handled = false;
1908                 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1909                     if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1910                         // If input is null, delete it
1911                         if(!in_val) {
1912                             sp_repr_unparent(o->repr);
1913                             sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1914                                              _("Remove merge node"));
1915                             (*get_selection()->get_selected())[_columns.primitive] = prim;
1916                         }
1917                         else
1918                             _dialog.set_attr(o, SP_ATTR_IN, in_val);
1919                         handled = true;
1920                     }
1921                 }
1922                 // Add new input?
1923                 if(!handled && c == _in_drag && in_val) {
1924                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1925                     Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1926                     repr->setAttribute("inkscape:collect", "always");
1927                     prim->repr->appendChild(repr);
1928                     SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1929                     Inkscape::GC::release(repr);
1930                     _dialog.set_attr(node, SP_ATTR_IN, in_val);
1931                     (*get_selection()->get_selected())[_columns.primitive] = prim;
1932                 }
1933             }
1934             else {
1935                 if(_in_drag == 1)
1936                     _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1937                 else if(_in_drag == 2)
1938                     _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1939             }
1940         }
1942         _in_drag = 0;
1943         queue_draw();
1945         _dialog.update_settings_view();
1946     }
1948     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1949         const bool sensitive = get_selected() != NULL;
1950         _primitive_menu->items()[0].set_sensitive(sensitive);
1951         _primitive_menu->items()[1].set_sensitive(sensitive);
1952         _primitive_menu->popup(e->button, e->time);
1954         return true;
1955     }
1956     else
1957         return Gtk::TreeView::on_button_release_event(e);
1960 // Checks all of prim's inputs, removes any that use result
1961 void check_single_connection(SPFilterPrimitive* prim, const int result)
1963     if(prim && result >= 0) {
1965         if(prim->image_in == result)
1966             SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1968         if(SP_IS_FEBLEND(prim)) {
1969             if(SP_FEBLEND(prim)->in2 == result)
1970                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1971         }
1972         else if(SP_IS_FECOMPOSITE(prim)) {
1973             if(SP_FECOMPOSITE(prim)->in2 == result)
1974                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1975         }
1976         else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
1977             if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
1978                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1979         }
1980     }
1983 // Remove any connections going to/from prim_iter that forward-reference other primitives
1984 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
1986     SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
1987     bool before = true;
1989     for(Gtk::TreeIter iter = _model->children().begin();
1990         iter != _model->children().end(); ++iter) {
1991         if(iter == prim_iter)
1992             before = false;
1993         else {
1994             SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
1995             if(before)
1996                 check_single_connection(cur_prim, prim->image_out);
1997             else
1998                 check_single_connection(prim, cur_prim->image_out);
1999         }
2000     }
2003 // Reorder the filter primitives to match the list order
2004 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2006     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2007     int ndx = 0;
2009     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2010         iter != _model->children().end(); ++iter, ++ndx) {
2011         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2012         if(prim && prim == _drag_prim) {
2013             SP_OBJECT_REPR(prim)->setPosition(ndx);
2014             break;
2015         }
2016     }
2018     for(Gtk::TreeModel::iterator iter = _model->children().begin();
2019         iter != _model->children().end(); ++iter, ++ndx) {
2020         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2021         if(prim && prim == _drag_prim) {
2022             sanitize_connections(iter);
2023             get_selection()->select(iter);
2024             break;
2025         }
2026     }
2028     filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2030     sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2033 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2034 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2036     if(_autoscroll) {
2037         Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2038         double v;
2040         v = a.get_value() + _autoscroll;
2041         if(v < 0)
2042             v = 0;
2043         if(v > a.get_upper() - a.get_page_size())
2044             v = a.get_upper() - a.get_page_size();
2046         a.set_value(v);
2048         queue_draw();
2049     }
2051     return true;
2054 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2056     return _model->children().size();
2059 /*** FilterEffectsDialog ***/
2061 FilterEffectsDialog::FilterEffectsDialog()
2062     : UI::Widget::Panel("", "/dialogs/filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2063       _add_primitive_type(FPConverter),
2064       _add_primitive(_("Add Effect:")),
2065       _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2066       _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2067       _settings_initialized(false),
2068       _locked(false),
2069       _attr_lock(false),
2070       _filter_modifier(*this),
2071       _primitive_list(*this)
2073     _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2074                              NR_FILTER_ENDPRIMITIVETYPE);
2075     _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2076                              1);
2077     _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2078     _sizegroup->set_ignore_hidden();
2080     _add_primitive_type.remove_row(NR_FILTER_TILE);
2081     _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2083     // Initialize widget hierarchy
2084     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2085     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2086     Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox(/*homogeneous:*/false, /*spacing:*/4));
2087     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2089     _getContents()->add(*hpaned);
2090     hpaned->pack1(_filter_modifier);
2091     hpaned->pack2(_primitive_box);
2092     _primitive_box.pack_start(*sw_prims);
2093     _primitive_box.pack_start(*hb_prims, false, false);
2094     _primitive_box.pack_start(*infobox,false, false);
2095     sw_prims->add(_primitive_list);
2096     infobox->pack_start(_infobox_icon, false, false);
2097     infobox->pack_start(_infobox_desc, false, false);
2098     _infobox_desc.set_line_wrap(true);
2099     _infobox_desc.set_size_request(200, -1);
2101     hb_prims->pack_start(_add_primitive, false, false);
2102     hb_prims->pack_start(_add_primitive_type, false, false);
2103     _getContents()->pack_start(_settings_tabs, false, false);
2104     _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2105     _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2107     _primitive_list.signal_primitive_changed().connect(
2108         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2109     _filter_modifier.signal_filter_changed().connect(
2110         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2112     _add_primitive_type.signal_changed().connect(
2113         sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2115     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2116     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2117 //    al_settings->set_padding(0, 0, 12, 0);
2118 //    fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2119 //    ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2120     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2121     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2122                                                sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2124     show_all_children();
2125     init_settings_widgets();
2126     _primitive_list.update();
2127     update_primitive_infobox();
2130 FilterEffectsDialog::~FilterEffectsDialog()
2132     delete _settings;
2133     delete _filter_general_settings;
2136 void FilterEffectsDialog::set_attrs_locked(const bool l)
2138     _locked = l;
2141 void FilterEffectsDialog::show_all_vfunc()
2143     UI::Widget::Panel::show_all_vfunc();
2145     update_settings_view();
2148 void FilterEffectsDialog::init_settings_widgets()
2150     // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2151     //       most of the current values are complete guesses!
2153     _empty_settings.set_sensitive(false);
2154     _settings_tab1.pack_start(_empty_settings);
2156     _no_filter_selected.set_sensitive(false);
2157     _settings_tab2.pack_start(_no_filter_selected);
2158     _settings_initialized = true;
2160     _filter_general_settings->type(0);
2161     _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"));
2162     _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"));
2164     _settings->type(NR_FILTER_BLEND);
2165     _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode:"), BlendModeConverter);
2167     _settings->type(NR_FILTER_COLORMATRIX);
2168     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."));
2169     _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s):"));
2170     colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2172     _settings->type(NR_FILTER_COMPONENTTRANSFER);
2173     _settings->add_notimplemented();
2174     //TRANSLATORS: for info on "Slope" and "Intercept", see http://id.mind.net/~zona/mmts/functionInstitute/linearFunctions/lsif.html
2175     /*_settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2176     _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2177     _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2178     _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2179     _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2180     _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2182     _settings->type(NR_FILTER_COMPOSITE);
2183     _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator:"), CompositeOperatorConverter);
2184     _k1 = _settings->add_spinslider(0, SP_ATTR_K1, _("K1:"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2185     _k2 = _settings->add_spinslider(0, SP_ATTR_K2, _("K2:"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2186     _k3 = _settings->add_spinslider(0, SP_ATTR_K3, _("K3:"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2187     _k4 = _settings->add_spinslider(0, SP_ATTR_K4, _("K4:"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2189     _settings->type(NR_FILTER_CONVOLVEMATRIX);
2190     _convolve_order = _settings->add_dualspinbutton((char*)"3", SP_ATTR_ORDER, _("Size:"), 1, 5, 1, 1, 0, _("width of the convolve matrix"), _("height of the convolve matrix"));
2191     _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."));
2192     //TRANSLATORS: for info on "Kernel", see http://en.wikipedia.org/wiki/Kernel_(matrix)
2193     _convolve_matrix = _settings->add_matrix(SP_ATTR_KERNELMATRIX, _("Kernel:"), _("This matrix describes the convolve operation that is applied to the input image in order to calculate the pixel colors at the output. Different arrangements of values in this matrix result in various possible visual effects. An identity matrix would lead to a motion blur effect (parallel to the matrix diagonal) while a matrix filled with a constant non-zero value would lead to a common blur effect."));
2194     _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2195     _settings->add_spinslider(0, SP_ATTR_DIVISOR, _("Divisor:"), 0, 1000, 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."));
2196     _settings->add_spinslider(0, SP_ATTR_BIAS, _("Bias:"), -10, 10, 1, 0.01, 1, _("This value is added to each component. This is useful to define a constant value as the zero response of the filter."));
2197     _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."));
2198     _settings->add_checkbutton(false, SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive."));
2200     _settings->type(NR_FILTER_DIFFUSELIGHTING);
2201     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Diffuse Color:"), _("Defines the color of the light source"));
2202     _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale:"), -1000, 1000, 1, 0.01, 1, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2203     _settings->add_spinslider(1, SP_ATTR_DIFFUSECONSTANT, _("Constant:"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2204     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2205     _settings->add_lightsource();
2207     _settings->type(NR_FILTER_DISPLACEMENTMAP);
2208     _settings->add_spinslider(0, SP_ATTR_SCALE, _("Scale:"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2209     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2210     _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2212     _settings->type(NR_FILTER_FLOOD);
2213     _settings->add_color(/*default: black*/ 0, SP_PROP_FLOOD_COLOR, _("Flood Color:"), _("The whole filter region will be filled with this color."));
2214     _settings->add_spinslider(1, SP_PROP_FLOOD_OPACITY, _("Opacity:"), 0, 1, 0.1, 0.01, 2);
2216     _settings->type(NR_FILTER_GAUSSIANBLUR);
2217     _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation:"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2219     _settings->type(NR_FILTER_MERGE);
2220     _settings->add_no_params();
2222     _settings->type(NR_FILTER_MORPHOLOGY);
2223     _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator:"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2224     _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius:"), 0, 100, 1, 0.01, 1);
2226     _settings->type(NR_FILTER_IMAGE);
2227     _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image:"));
2229     _settings->type(NR_FILTER_OFFSET);
2230     _settings->add_spinslider(0, SP_ATTR_DX, _("Delta X:"), -100, 100, 1, 0.01, 1, _("This is how far the input image gets shifted to the right"));
2231     _settings->add_spinslider(0, SP_ATTR_DY, _("Delta Y:"), -100, 100, 1, 0.01, 1, _("This is how far the input image gets shifted downwards"));
2233     _settings->type(NR_FILTER_SPECULARLIGHTING);
2234     _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Specular Color:"), _("Defines the color of the light source"));
2235     _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale:"), -1000, 1000, 1, 0.01, 1, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2236     _settings->add_spinslider(1, SP_ATTR_SPECULARCONSTANT, _("Constant:"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2237     _settings->add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Exponent:"), 1, 128, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2238     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2239     _settings->add_lightsource();
2241     _settings->type(NR_FILTER_TILE);
2242     _settings->add_notimplemented();
2244     _settings->type(NR_FILTER_TURBULENCE);
2245 //    _settings->add_checkbutton(false, SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2246     _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type:"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2247     _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency:"), 0, 0.4, 0.001, 0.01, 3);
2248     _settings->add_spinslider(1, SP_ATTR_NUMOCTAVES, _("Octaves:"), 1, 10, 1, 1, 0);
2249     _settings->add_spinslider(0, SP_ATTR_SEED, _("Seed:"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2252 void FilterEffectsDialog::add_primitive()
2254     SPFilter* filter = _filter_modifier.get_selected_filter();
2256     if(filter) {
2257         SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2259         _primitive_list.select(prim);
2261         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2262     }
2265 void FilterEffectsDialog::update_primitive_infobox()
2267     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2268     if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2269         _infobox_icon.show();
2270         _infobox_desc.show();
2271     } else {
2272         _infobox_icon.hide();
2273         _infobox_desc.hide();
2274     }
2275     switch(_add_primitive_type.get_active_data()->id){
2276         case(NR_FILTER_BLEND):
2277             _infobox_icon.set_from_icon_name("feBlend-icon", Gtk::ICON_SIZE_DIALOG);
2278             _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2279             break;
2280         case(NR_FILTER_COLORMATRIX):
2281             _infobox_icon.set_from_icon_name("feColorMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2282             _infobox_desc.set_markup(_("The <b>feColorMatrix</b> filter primitive applies a matrix transformation to color of each rendered pixel. This allows for effects like turning object to grayscale, modifying color saturation and changing color hue."));
2283             break;
2284         case(NR_FILTER_COMPONENTTRANSFER):
2285             _infobox_icon.set_from_icon_name("feComponentTransfer-icon", Gtk::ICON_SIZE_DIALOG);
2286             _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."));
2287             break;
2288         case(NR_FILTER_COMPOSITE):
2289             _infobox_icon.set_from_icon_name("feComposite-icon", Gtk::ICON_SIZE_DIALOG);
2290             _infobox_desc.set_markup(_("The <b>feComposite</b> filter primitive composites two images using one of the Porter-Duff blending modes or the arithmetic mode described in SVG standard. Porter-Duff blending modes are essentially logical operations between the corresponding pixel values of the images."));
2291             break;
2292         case(NR_FILTER_CONVOLVEMATRIX):
2293             _infobox_icon.set_from_icon_name("feConvolveMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2294             _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."));
2295             break;
2296         case(NR_FILTER_DIFFUSELIGHTING):
2297             _infobox_icon.set_from_icon_name("feDiffuseLighting-icon", Gtk::ICON_SIZE_DIALOG);
2298             _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."));
2299             break;
2300         case(NR_FILTER_DISPLACEMENTMAP):
2301             _infobox_icon.set_from_icon_name("feDisplacementMap-icon", Gtk::ICON_SIZE_DIALOG);
2302             _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."));
2303             break;
2304         case(NR_FILTER_FLOOD):
2305             _infobox_icon.set_from_icon_name("feFlood-icon", Gtk::ICON_SIZE_DIALOG);
2306             _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."));
2307             break;
2308         case(NR_FILTER_GAUSSIANBLUR):
2309             _infobox_icon.set_from_icon_name("feGaussianBlur-icon", Gtk::ICON_SIZE_DIALOG);
2310             _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."));
2311             break;
2312         case(NR_FILTER_IMAGE):
2313             _infobox_icon.set_from_icon_name("feImage-icon", Gtk::ICON_SIZE_DIALOG);
2314             _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2315             break;
2316         case(NR_FILTER_MERGE):
2317             _infobox_icon.set_from_icon_name("feMerge-icon", Gtk::ICON_SIZE_DIALOG);
2318             _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."));
2319             break;
2320         case(NR_FILTER_MORPHOLOGY):
2321             _infobox_icon.set_from_icon_name("feMorphology-icon", Gtk::ICON_SIZE_DIALOG);
2322             _infobox_desc.set_markup(_("The <b>feMorphology</b> filter primitive provides erode and dilate effects. For single-color objects erode makes the object thinner and dilate makes it thicker."));
2323             break;
2324         case(NR_FILTER_OFFSET):
2325             _infobox_icon.set_from_icon_name("feOffset-icon", Gtk::ICON_SIZE_DIALOG);
2326             _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."));
2327             break;
2328         case(NR_FILTER_SPECULARLIGHTING):
2329             _infobox_icon.set_from_icon_name("feSpecularLighting-icon", Gtk::ICON_SIZE_DIALOG);
2330             _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."));
2331             break;
2332         case(NR_FILTER_TILE):
2333             _infobox_icon.set_from_icon_name("feTile-icon", Gtk::ICON_SIZE_DIALOG);
2334             _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2335             break;
2336         case(NR_FILTER_TURBULENCE):
2337             _infobox_icon.set_from_icon_name("feTurbulence-icon", Gtk::ICON_SIZE_DIALOG);
2338             _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."));
2339             break;
2340         default:
2341             g_assert(false);
2342             break;
2343     }
2344     _infobox_icon.set_pixel_size(96);
2347 void FilterEffectsDialog::duplicate_primitive()
2349     SPFilter* filter = _filter_modifier.get_selected_filter();
2350     SPFilterPrimitive* origprim = _primitive_list.get_selected();
2352     if(filter && origprim) {
2353         Inkscape::XML::Node *repr;
2354         repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2355         SP_OBJECT_REPR(filter)->appendChild(repr);
2357         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2359         _primitive_list.update();
2360     }
2363 void FilterEffectsDialog::convolve_order_changed()
2365     _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2366     _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2367     _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2370 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2372     set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2375 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2377     if(!_locked) {
2378         _attr_lock = true;
2379         SPFilter *filter = _filter_modifier.get_selected_filter();
2380         const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2381         if (filter && name && SP_OBJECT_REPR(filter)){
2382             SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2383             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2384         }
2385         _attr_lock = false;
2386     }
2389 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2391     set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2394 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2396     if(!_locked) {
2397         _attr_lock = true;
2399         SPFilter *filter = _filter_modifier.get_selected_filter();
2400         const gchar* name = (const gchar*)sp_attribute_name(attr);
2401         if(filter && name && o) {
2402             update_settings_sensitivity();
2404             SP_OBJECT_REPR(o)->setAttribute(name, val);
2405             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2407             Glib::ustring undokey = "filtereffects:";
2408             undokey += name;
2409             sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2410                                    _("Set filter primitive attribute"));
2411         }
2413         _attr_lock = false;
2414     }
2417 void FilterEffectsDialog::update_filter_general_settings_view()
2419     if(_settings_initialized != true) return;
2421     if(!_locked) {
2422         _attr_lock = true;
2424         SPFilter* filter = _filter_modifier.get_selected_filter();
2426         if(filter) {
2427             _filter_general_settings->show_and_update(0, filter);
2428             _no_filter_selected.hide();
2429         }
2430         else {
2431             std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2432             vect[0]->hide_all();
2433             _no_filter_selected.show();
2434         }
2436         _attr_lock = false;
2437     }
2440 void FilterEffectsDialog::update_settings_view()
2442     update_settings_sensitivity();
2444     if(_attr_lock)
2445         return;
2447 //First Tab
2449     std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2450     for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2451     _empty_settings.show();
2453     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2454     if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2455         _infobox_icon.show();
2456         _infobox_desc.show();
2457     } else {
2458         _infobox_icon.hide();
2459         _infobox_desc.hide();
2460     }
2462     SPFilterPrimitive* prim = _primitive_list.get_selected();
2464     if(prim) {
2465         _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2466         _empty_settings.hide();
2467     }
2469 //Second Tab
2471     std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2472     vect2[0]->hide_all();
2473     _no_filter_selected.show();
2475     SPFilter* filter = _filter_modifier.get_selected_filter();
2477     if(filter) {
2478         _filter_general_settings->show_and_update(0, filter);
2479         _no_filter_selected.hide();
2480     }
2484 void FilterEffectsDialog::update_settings_sensitivity()
2486     SPFilterPrimitive* prim = _primitive_list.get_selected();
2487     const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2488     _k1->set_sensitive(use_k);
2489     _k2->set_sensitive(use_k);
2490     _k3->set_sensitive(use_k);
2491     _k4->set_sensitive(use_k);
2493 // Component transfer not yet implemented
2494 /*
2495     if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2496         SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2497         const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2498         const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2500         _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2501         _ct_slope->set_sensitive(linear);
2502         _ct_intercept->set_sensitive(linear);
2503         _ct_amplitude->set_sensitive(gamma);
2504         _ct_exponent->set_sensitive(gamma);
2505         _ct_offset->set_sensitive(gamma);
2506     }
2507 */
2510 void FilterEffectsDialog::update_color_matrix()
2512     _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2515 } // namespace Dialog
2516 } // namespace UI
2517 } // namespace Inkscape
2519 /*
2520   Local Variables:
2521   mode:c++
2522   c-file-style:"stroustrup"
2523   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2524   indent-tabs-mode:nil
2525   fill-column:99
2526   End:
2527 */
2528 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :