Code

* modified strings on infobox (filter effect dialog) to fit better english style
[inkscape.git] / src / ui / dialog / filter-effects-dialog.cpp
1 /**
2  * \brief Filter Effects dialog
3  *
4  * Authors:
5  *   Nicholas Bishop <nicholasbishop@gmail.org>
6  *
7  * Copyright (C) 2007 Authors
8  *
9  * Released under GNU GPL.  Read the file 'COPYING' for more information.
10  */
12 #ifdef HAVE_CONFIG_H
13 # include <config.h>
14 #endif
16 #include <gtk/gtktreeview.h>
17 #include <gtkmm/cellrenderertext.h>
18 #include <gtkmm/colorbutton.h>
19 #include <gtkmm/messagedialog.h>
20 #include <gtkmm/paned.h>
21 #include <gtkmm/scale.h>
22 #include <gtkmm/scrolledwindow.h>
23 #include <gtkmm/spinbutton.h>
24 #include <gtkmm/stock.h>
25 #include <glibmm/i18n.h>
27 #include "application/application.h"
28 #include "application/editor.h"
29 #include "desktop.h"
30 #include "desktop-handles.h"
31 #include "dialog-manager.h"
32 #include "document.h"
33 #include "filter-chemistry.h"
34 #include "filter-effects-dialog.h"
35 #include "filter-enums.h"
36 #include "inkscape.h"
37 #include "path-prefix.h"
38 #include "selection.h"
39 #include "sp-feblend.h"
40 #include "sp-fecolormatrix.h"
41 #include "sp-fecomponenttransfer.h"
42 #include "sp-fecomposite.h"
43 #include "sp-feconvolvematrix.h"
44 #include "sp-fedisplacementmap.h"
45 #include "sp-fedistantlight.h"
46 #include "sp-femerge.h"
47 #include "sp-femergenode.h"
48 #include "sp-feoffset.h"
49 #include "sp-fepointlight.h"
50 #include "sp-fespotlight.h"
51 #include "sp-filter-primitive.h"
52 #include "sp-gaussian-blur.h"
54 #include "style.h"
55 #include "svg/svg-color.h"
56 #include "verbs.h"
57 #include "xml/node.h"
58 #include "xml/node-observer.h"
59 #include "xml/repr.h"
60 #include <sstream>
62 #include <iostream>
64 using namespace NR;
66 namespace Inkscape {
67 namespace UI {
68 namespace Dialog {
70 // Returns the number of inputs available for the filter primitive type
71 int input_count(const SPFilterPrimitive* prim)
72 {
73     if(!prim)
74         return 0;
75     else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
76         return 2;
77     else if(SP_IS_FEMERGE(prim)) {
78         // Return the number of feMergeNode connections plus an extra
79         int count = 1;
80         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count);
81         return count;
82     }
83     else
84         return 1;
85 }
87 // Very simple observer that just emits a signal if anything happens to a node
88 class FilterEffectsDialog::SignalObserver : public XML::NodeObserver
89 {
90 public:
91     SignalObserver()
92         : _oldsel(0)
93     {}
95     // Add this observer to the SPObject and remove it from any previous object
96     void set(SPObject* o)
97     {
98         if(_oldsel && _oldsel->repr)
99             _oldsel->repr->removeObserver(*this);
100         if(o && o->repr)
101             o->repr->addObserver(*this);
102         _oldsel = o;
103     }
105     void notifyChildAdded(XML::Node&, XML::Node&, XML::Node*)
106     { signal_changed()(); }
108     void notifyChildRemoved(XML::Node&, XML::Node&, XML::Node*)
109     { signal_changed()(); }
111     void notifyChildOrderChanged(XML::Node&, XML::Node&, XML::Node*, XML::Node*)
112     { signal_changed()(); }
114     void notifyContentChanged(XML::Node&, Util::ptr_shared<char>, Util::ptr_shared<char>)
115     {}
117     void notifyAttributeChanged(XML::Node&, GQuark, Util::ptr_shared<char>, Util::ptr_shared<char>)
118     { signal_changed()(); }
120     sigc::signal<void>& signal_changed()
121     {
122         return _signal_changed;
123     }
124 private:
125     sigc::signal<void> _signal_changed;
126     SPObject* _oldsel;
127 };
129 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
131 public:
132     CheckButtonAttr(const Glib::ustring& label,
133                     const Glib::ustring& tv, const Glib::ustring& fv,
134                     const SPAttributeEnum a)
135         : Gtk::CheckButton(label),
136           AttrWidget(a),
137           _true_val(tv), _false_val(fv)
138     {
139         signal_toggled().connect(signal_attr_changed().make_slot());
140     }
142     Glib::ustring get_as_attribute() const
143     {
144         return get_active() ? _true_val : _false_val;
145     }
147     void set_from_attribute(SPObject* o)
148     {
149         const gchar* val = attribute_value(o);
150         if(val) {
151             if(_true_val == val)
152                 set_active(true);
153             else if(_false_val == val)
154                 set_active(false);
155         }
156     }
157 private:
158     const Glib::ustring _true_val, _false_val;
159 };
161 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
163 public:
164     SpinButtonAttr(double lower, double upper, double step_inc,
165                    double climb_rate, int digits, const SPAttributeEnum a)
166         : Gtk::SpinButton(climb_rate, digits),
167           AttrWidget(a)
168     {
169         set_range(lower, upper);
170         set_increments(step_inc, step_inc * 5);
172         signal_value_changed().connect(signal_attr_changed().make_slot());
173     }
175     Glib::ustring get_as_attribute() const
176     {
177         const double val = get_value();
178         
179         if(get_digits() == 0)
180             return Glib::Ascii::dtostr((int)val);
181         else
182             return Glib::Ascii::dtostr(val);
183     }
184     
185     void set_from_attribute(SPObject* o)
186     {
187         const gchar* val = attribute_value(o);
188         if(val)
189             set_value(Glib::Ascii::strtod(val));
190     }
191 };
193 // Contains an arbitrary number of spin buttons that use seperate attributes
194 class MultiSpinButton : public Gtk::HBox
196 public:
197     MultiSpinButton(double lower, double upper, double step_inc,
198                     double climb_rate, int digits, std::vector<SPAttributeEnum> attrs)
199     {
200         for(unsigned i = 0; i < attrs.size(); ++i) {
201             _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i]));
202             pack_start(*_spins.back(), false, false);
203         }
204     }
206     ~MultiSpinButton()
207     {
208         for(unsigned i = 0; i < _spins.size(); ++i)
209             delete _spins[i];
210     }
212     std::vector<SpinButtonAttr*>& get_spinbuttons()
213     {
214         return _spins;
215     }
216 private:
217     std::vector<SpinButtonAttr*> _spins;
218 };
220 // Contains two spinbuttons that describe a NumberOptNumber
221 class DualSpinButton : public Gtk::HBox, public AttrWidget
223 public:
224     DualSpinButton(double lower, double upper, double step_inc,
225                    double climb_rate, int digits, const SPAttributeEnum a)
226         : AttrWidget(a),
227           _s1(climb_rate, digits), _s2(climb_rate, digits)
228     {
229         _s1.set_range(lower, upper);
230         _s2.set_range(lower, upper);
231         _s1.set_increments(step_inc, step_inc * 5);
232         _s2.set_increments(step_inc, step_inc * 5);
234         _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
235         _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
237         pack_start(_s1, false, false);
238         pack_start(_s2, false, false);
239     }
241     Gtk::SpinButton& get_spinbutton1()
242     {
243         return _s1;
244     }
246     Gtk::SpinButton& get_spinbutton2()
247     {
248         return _s2;
249     }
251     virtual Glib::ustring get_as_attribute() const
252     {
253         double v1 = _s1.get_value();
254         double v2 = _s2.get_value();
255         
256         if(_s1.get_digits() == 0) {
257             v1 = (int)v1;
258             v2 = (int)v2;
259         }
261         return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
262     }
264     virtual void set_from_attribute(SPObject* o)
265     {
266         const gchar* val = attribute_value(o);
267         if(val) {
268             NumberOptNumber n;
269             n.set(val);
270             _s1.set_value(n.getNumber());
271             _s2.set_value(n.getOptNumber());
272         }
273     }
274 private:
275     Gtk::SpinButton _s1, _s2;
276 };
278 class ColorButton : public Gtk::ColorButton, public AttrWidget
280 public:
281     ColorButton(const SPAttributeEnum a)
282         : AttrWidget(a)
283     {
284         signal_color_set().connect(signal_attr_changed().make_slot());
286         Gdk::Color col;
287         col.set_rgb(65535, 65535, 65535);
288         set_color(col);
289     }
290     
291     // Returns the color in 'rgb(r,g,b)' form.
292     Glib::ustring get_as_attribute() const
293     {
294         std::ostringstream os;
295         const Gdk::Color c = get_color();
296         const int r = c.get_red() / 257, g = c.get_green() / 257, b = c.get_blue() / 257;
297         os << "rgb(" << r << "," << g << "," << b << ")";
298         return os.str();
299     }
302     void set_from_attribute(SPObject* o)
303     {
304         const gchar* val = attribute_value(o);
305         if(val) {
306             const guint32 i = sp_svg_read_color(val, 0xFFFFFFFF);
307             const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
308             Gdk::Color col;
309             col.set_rgb(r * 257, g * 257, b * 257);
310             set_color(col);
311         }
312     }
313 };
315 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
316 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
318 public:
319     MatrixAttr(const SPAttributeEnum a)
320         : AttrWidget(a), _locked(false)
321     {
322         _model = Gtk::ListStore::create(_columns);
323         _tree.set_model(_model);
324         _tree.set_headers_visible(false);
325         _tree.show();
326         add(_tree);
327         set_shadow_type(Gtk::SHADOW_IN);
328     }
330     std::vector<double> get_values() const
331     {
332         std::vector<double> vec;
333         for(Gtk::TreeIter iter = _model->children().begin();
334             iter != _model->children().end(); ++iter) {
335             for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
336                 vec.push_back((*iter)[_columns.cols[c]]);
337         }
338         return vec;
339     }
341     void set_values(const std::vector<double>& v)
342     {
343         unsigned i = 0;
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                 if(i >= v.size())
348                     return;
349                 (*iter)[_columns.cols[c]] = v[i];
350                 ++i;
351             }
352         }
353     }
355     Glib::ustring get_as_attribute() const
356     {
357         std::ostringstream os;
358         
359         for(Gtk::TreeIter iter = _model->children().begin();
360             iter != _model->children().end(); ++iter) {
361             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
362                 os << (*iter)[_columns.cols[c]] << " ";
363             }
364         }
365         
366         return os.str();
367     }
368     
369     void set_from_attribute(SPObject* o)
370     {
371         if(o) {
372             if(SP_IS_FECONVOLVEMATRIX(o)) {
373                 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
374                 int cols, rows;
375                 cols = (int)conv->order.getNumber();
376                 if(cols > 5)
377                     cols = 5;
378                 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
379                 update(o, rows, cols);
380             }
381             else if(SP_IS_FECOLORMATRIX(o))
382                 update(o, 4, 5);
383         }
384     }
385 private:
386     class MatrixColumns : public Gtk::TreeModel::ColumnRecord
387     {
388     public:
389         MatrixColumns()
390         {
391             cols.resize(5);
392             for(unsigned i = 0; i < cols.size(); ++i)
393                 add(cols[i]);
394         }
395         std::vector<Gtk::TreeModelColumn<double> > cols;
396     };
398     void update(SPObject* o, const int rows, const int cols)
399     {
400         if(_locked)
401             return;
403         _model->clear();
405         _tree.remove_all_columns();
407         std::vector<gdouble>* values = NULL;
408         if(SP_IS_FECOLORMATRIX(o))
409             values = &SP_FECOLORMATRIX(o)->values;
410         else if(SP_IS_FECONVOLVEMATRIX(o))
411             values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
412         else
413             return;
415         if(o) {
416             int ndx = 0;
418             for(int i = 0; i < cols; ++i) {
419                 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
420                 dynamic_cast<Gtk::CellRendererText*>(
421                     _tree.get_column_cell_renderer(i))->signal_edited().connect(
422                         sigc::mem_fun(*this, &MatrixAttr::rebind));
423             }
425             for(int r = 0; r < rows; ++r) {
426                 Gtk::TreeRow row = *(_model->append());
427                 // Default to identity matrix
428                 for(int c = 0; c < cols; ++c, ++ndx)
429                     row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
430             }
431         }
432     }
434     void rebind(const Glib::ustring&, const Glib::ustring&)
435     {
436         _locked = true;
437         signal_attr_changed()();
438         _locked = false;
439     }
441     bool _locked;
442     Gtk::TreeView _tree;
443     Glib::RefPtr<Gtk::ListStore> _model;
444     MatrixColumns _columns;
445 };
447 // Displays a matrix or a slider for feColorMatrix
448 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
450 public:
451     ColorMatrixValues()
452         : AttrWidget(SP_ATTR_VALUES),
453           _matrix(SP_ATTR_VALUES),
454           _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
455           _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
456           _label(_("None"), Gtk::ALIGN_LEFT),
457           _use_stored(false),
458           _saturation_store(0),
459           _angle_store(0)
460     {
461         _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
462         _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
463         _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
464         signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
466         _matrix.show();
467         _saturation.show();
468         _angle.show();
469         _label.show();
470         _label.set_sensitive(false);
472         set_shadow_type(Gtk::SHADOW_NONE);
473     }
475     virtual void set_from_attribute(SPObject* o)
476     {
477         if(SP_IS_FECOLORMATRIX(o)) {
478             SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
479             remove();
480             switch(col->type) {
481                 case COLORMATRIX_SATURATE:
482                     add(_saturation);
483                     if(_use_stored)
484                         _saturation.set_value(_saturation_store);
485                     else
486                         _saturation.set_from_attribute(o);
487                     break;
488                 case COLORMATRIX_HUEROTATE:
489                     add(_angle);
490                     if(_use_stored)
491                         _angle.set_value(_angle_store);
492                     else
493                         _angle.set_from_attribute(o);
494                     break;
495                 case COLORMATRIX_LUMINANCETOALPHA:
496                     add(_label);
497                     break;
498                 case COLORMATRIX_MATRIX:
499                 default:
500                     add(_matrix);
501                     if(_use_stored)
502                         _matrix.set_values(_matrix_store);
503                     else
504                         _matrix.set_from_attribute(o);
505                     break;
506             }
507             _use_stored = true;
508         }
509     }
511     virtual Glib::ustring get_as_attribute() const
512     {
513         const Widget* w = get_child();
514         if(w == &_label)
515             return "";
516         else
517             return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
518     }
520     void clear_store()
521     {
522         _use_stored = false;
523     }
524 private:
525     void update_store()
526     {
527         const Widget* w = get_child();
528         if(w == &_matrix)
529             _matrix_store = _matrix.get_values();
530         else if(w == &_saturation)
531             _saturation_store = _saturation.get_value();
532         else if(w == &_angle)
533             _angle_store = _angle.get_value();
534     }
536     MatrixAttr _matrix;
537     SpinSlider _saturation;
538     SpinSlider _angle;
539     Gtk::Label _label;
541     // Store separate values for the different color modes
542     bool _use_stored;
543     std::vector<double> _matrix_store;
544     double _saturation_store;
545     double _angle_store;
546 };
548 class FilterEffectsDialog::Settings
550 public:
551     typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
553     Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
554         : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
555     {
556         _groups.resize(_max_types);
557         _attrwidgets.resize(_max_types);
559         for(int i = 0; i < _max_types; ++i) {
560             _groups[i] = new Gtk::VBox;
561             b.add(*_groups[i]);
562         }
563     }
565     ~Settings()
566     {
567         for(int i = 0; i < _max_types; ++i) {
568             delete _groups[i];
569             for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
570                 delete _attrwidgets[i][j];
571         }
572     }
574     // Show the active settings group and update all the AttrWidgets with new values
575     void show_and_update(const int t, SPObject* ob)
576     {
577         if(t != _current_type) {
578             type(t);
579             for(unsigned i = 0; i < _groups.size(); ++i)
580                 _groups[i]->hide();
581         }
582         if(t >= 0)
583             _groups[t]->show_all();
585         _dialog.set_attrs_locked(true);
586         for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
587             _attrwidgets[_current_type][i]->set_from_attribute(ob);
588         _dialog.set_attrs_locked(false);
589     }
591     int get_current_type() const
592     {
593         return _current_type;
594     }
596     void type(const int t)
597     {
598         _current_type = t;
599     }
601     void add_notimplemented()
602     {
603         Gtk::Label* lbl = Gtk::manage(new Gtk::Label("This SVG filter effect is not yet implemented in Inkscape."));
605         add_widget(lbl, "");
606     }
608     // LightSource
609     LightSourceControl* add_lightsource();
611     // CheckBox
612     CheckButtonAttr* add_checkbutton(const SPAttributeEnum attr, const Glib::ustring& label,
613                                      const Glib::ustring& tv, const Glib::ustring& fv)
614     {
615         CheckButtonAttr* cb = new CheckButtonAttr(label, tv, fv, attr);
616         add_widget(cb, "");
617         add_attr_widget(cb);
618         return cb;
619     }
621     // ColorButton
622     ColorButton* add_color(const SPAttributeEnum attr, const Glib::ustring& label)
623     {
624         ColorButton* col = new ColorButton(attr);
625         add_widget(col, label);
626         add_attr_widget(col);
627         return col;
628     }
630     // Matrix
631     MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label)
632     {
633         MatrixAttr* conv = new MatrixAttr(attr);
634         add_widget(conv, label);
635         add_attr_widget(conv);
636         return conv;
637     }
639     // ColorMatrixValues
640     ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
641     {
642         ColorMatrixValues* cmv = new ColorMatrixValues;
643         add_widget(cmv, label);
644         add_attr_widget(cmv);
645         return cmv;
646     }
648     // SpinSlider
649     SpinSlider* add_spinslider(const SPAttributeEnum attr, const Glib::ustring& label,
650                          const double lo, const double hi, const double step_inc, const double climb, const int digits)
651     {
652         SpinSlider* spinslider = new SpinSlider(lo, lo, hi, step_inc, climb, digits, attr);
653         add_widget(spinslider, label);
654         add_attr_widget(spinslider);
655         return spinslider;
656     }
658     // DualSpinSlider
659     DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
660                                        const double lo, const double hi, const double step_inc,
661                                        const double climb, const int digits)
662     {
663         DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr);
664         add_widget(dss, label);
665         add_attr_widget(dss);
666         return dss;
667     }
669     // DualSpinButton
670     DualSpinButton* add_dualspinbutton(const SPAttributeEnum attr, const Glib::ustring& label,
671                                        const double lo, const double hi, const double step_inc,
672                                        const double climb, const int digits)
673     {
674         DualSpinButton* dsb = new DualSpinButton(lo, hi, step_inc, climb, digits, attr);
675         add_widget(dsb, label);
676         add_attr_widget(dsb);
677         return dsb;
678     }
680     // MultiSpinButton
681     MultiSpinButton* add_multispinbutton(const SPAttributeEnum attr1, const SPAttributeEnum attr2,
682                                          const Glib::ustring& label, const double lo, const double hi,
683                                          const double step_inc, const double climb, const int digits)
684     {
685         std::vector<SPAttributeEnum> attrs;
686         attrs.push_back(attr1);
687         attrs.push_back(attr2);
688         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs);
689         add_widget(msb, label);
690         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
691             add_attr_widget(msb->get_spinbuttons()[i]);
692         return msb;
693     }
694     MultiSpinButton* add_multispinbutton(const SPAttributeEnum attr1, const SPAttributeEnum attr2,
695                                          const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
696                                          const double hi, const double step_inc, const double climb, const int digits)
697     {
698         std::vector<SPAttributeEnum> attrs;
699         attrs.push_back(attr1);
700         attrs.push_back(attr2);
701         attrs.push_back(attr3);
702         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs);
703         add_widget(msb, label);
704         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
705             add_attr_widget(msb->get_spinbuttons()[i]);
706         return msb;
707     }
709     // ComboBoxEnum
710     template<typename T> ComboBoxEnum<T>* add_combo(const SPAttributeEnum attr,
711                                   const Glib::ustring& label,
712                                   const Util::EnumDataConverter<T>& conv)
713     {
714         ComboBoxEnum<T>* combo = new ComboBoxEnum<T>(conv, attr);
715         add_widget(combo, label);
716         add_attr_widget(combo);
717         return combo;
718     }
719 private:
720     void add_attr_widget(AttrWidget* a)
721     {    
722         _attrwidgets[_current_type].push_back(a);
723         a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
724     }
726     /* Adds a new settings widget using the specified label. The label will be formatted with a colon
727        and all widgets within the setting group are aligned automatically. */
728     void add_widget(Gtk::Widget* w, const Glib::ustring& label)
729     {
730         Gtk::Label *lbl = 0;
731         Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
732         hb->set_spacing(12);
733         
734         if(label != "") {
735             lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
736             hb->pack_start(*lbl, false, false);
737             _dialog._sizegroup->add_widget(*lbl);
738             lbl->show();
739         }
740         
741         hb->pack_start(*w);
742         _groups[_current_type]->pack_start(*hb);
743         hb->show();
744         w->show();
745     }
747     std::vector<Gtk::VBox*> _groups;
749     FilterEffectsDialog& _dialog;
750     SetAttrSlot _set_attr_slot;
751     std::vector<std::vector<AttrWidget*> > _attrwidgets;
752     int _current_type, _max_types;
753 };
755 // Settings for the three light source objects
756 class FilterEffectsDialog::LightSourceControl : public AttrWidget
758 public:
759     LightSourceControl(FilterEffectsDialog& d)
760         : AttrWidget(SP_ATTR_INVALID),
761           _dialog(d),
762           _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
763           _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
764           _light_source(LightSourceConverter),
765           _locked(false)
766     {
767         _light_box.pack_start(_light_label, false, false);
768         _light_box.pack_start(_light_source);
769         _light_box.show_all();
770         _light_box.set_spacing(12);
771         _dialog._sizegroup->add_widget(_light_label);
773         _box.add(_light_box);
774         _box.reorder_child(_light_box, 0);
775         _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
777         // FIXME: these range values are complete crap
779         _settings.type(LIGHT_DISTANT);
780         _settings.add_spinslider(SP_ATTR_AZIMUTH, _("Azimuth"), 0, 360, 1, 1, 0);
781         _settings.add_spinslider(SP_ATTR_ELEVATION, _("Elevation"), 0, 360, 1, 1, 0);
783         _settings.type(LIGHT_POINT);
784         _settings.add_multispinbutton(SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0);
786         _settings.type(LIGHT_SPOT);
787         _settings.add_multispinbutton(SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0);
788         _settings.add_multispinbutton(SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
789                                       _("Points At"), -99999, 99999, 1, 100, 0);
790         _settings.add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0);
791         _settings.add_spinslider(SP_ATTR_LIMITINGCONEANGLE, _("Cone Angle"), 1, 100, 1, 1, 0);
792     }
794     Gtk::VBox& get_box()
795     {
796         return _box;
797     }
798 protected:
799     Glib::ustring get_as_attribute() const
800     {
801         return "";
802     }
803     void set_from_attribute(SPObject* o)
804     {
805         if(_locked)
806             return;
808         _locked = true;
810         SPObject* child = o->children;
811         
812         if(SP_IS_FEDISTANTLIGHT(child))
813             _light_source.set_active(0);
814         else if(SP_IS_FEPOINTLIGHT(child))
815             _light_source.set_active(1);
816         else if(SP_IS_FESPOTLIGHT(child))
817             _light_source.set_active(2);
818         else
819             _light_source.set_active(-1);
821         update();
823         _locked = false;
824     }
825 private:
826     void on_source_changed()
827     {
828         if(_locked)
829             return;
831         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
832         if(prim) {
833             _locked = true;
835             SPObject* child = prim->children;
836             const int ls = _light_source.get_active_row_number();
837             // Check if the light source type has changed
838             if(!(ls == -1 && !child) &&
839                !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
840                !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
841                !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
842                 if(child)
843                     sp_repr_unparent(child->repr);
845                 if(ls != -1) {
846                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
847                     Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
848                     prim->repr->appendChild(repr);
849                 }
851                 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
852                 update();
853             }
855             _locked = false;
856         }
857     }
859     void update()
860     {
861         _box.hide_all();
862         _box.show();
863         _light_box.show_all();
864         
865         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
866         if(prim && prim->children)
867             _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
868     }
870     FilterEffectsDialog& _dialog;
871     Gtk::VBox _box;
872     Settings _settings;
873     Gtk::HBox _light_box;
874     Gtk::Label _light_label;
875     ComboBoxEnum<LightSource> _light_source;
876     bool _locked;
877 };
879 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
881     LightSourceControl* ls = new LightSourceControl(_dialog);
882     add_attr_widget(ls);
883     add_widget(&ls->get_box(), "");
884     return ls;
887 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
888                                           sigc::slot<void> rem)
890     Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
892     menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
893     Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
894     menu->append(*mi);
895     mi->signal_activate().connect(rem);
896     mi->show();
897     menu->accelerate(parent);
899     return menu;
902 /*** FilterModifier ***/
903 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
904     : _dialog(d), _add(Gtk::Stock::NEW), _observer(new SignalObserver)
906     Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
907     pack_start(*sw);
908     pack_start(_add, false, false);
909     sw->add(_list);
911     _model = Gtk::ListStore::create(_columns);
912     _list.set_model(_model);
913     _cell_toggle.set_active(true);
914     const int selcol = _list.append_column("", _cell_toggle);
915     Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
916     if(col)
917        col->add_attribute(_cell_toggle.property_active(), _columns.sel);
918     _list.append_column_editable(_("_Filter"), _columns.label);
919     ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
920         signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
922     sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
923     sw->set_shadow_type(Gtk::SHADOW_IN);
924     show_all_children();
925     _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
926     _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
927     _list.signal_button_release_event().connect_notify(
928         sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
929     _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
930                               sigc::mem_fun(*this, &FilterModifier::remove_filter));
931     _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
932                                  _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));   
933     _menu->accelerate(*this);
935     _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
936     _observer->signal_changed().connect(signal_filter_changed().make_slot());
937     g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
938                      G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
940     g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
941                      G_CALLBACK(&FilterModifier::on_activate_desktop), this);
943     on_activate_desktop(INKSCAPE, d.getDesktop(), this);
944     update_filters();
947 FilterEffectsDialog::FilterModifier::~FilterModifier()
949    _resource_changed.disconnect();
950    _doc_replaced.disconnect();
953 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
955     me->_doc_replaced.disconnect();
956     me->_doc_replaced = desktop->connectDocumentReplaced(
957         sigc::mem_fun(me, &FilterModifier::on_document_replaced));
959     me->_resource_changed.disconnect();
960     me->_resource_changed =
961         sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
962                                               sigc::mem_fun(me, &FilterModifier::update_filters));
964     me->_dialog.setDesktop(desktop);
966     me->update_filters();
970 // When the selection changes, show the active filter(s) in the dialog
971 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
972                                                                        Selection *sel,
973                                                                        FilterModifier* fm)
975     if(fm && sel)
976         fm->update_selection(sel);
979 // Update each filter's sel property based on the current object selection;
980 //  If the filter is not used by any selected object, sel = 0,
981 //  otherwise sel is set to the total number of filters in use by selected objects
982 //  If only one filter is in use, it is selected
983 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
985     std::set<SPObject*> used;
987     for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
988         SPObject *obj = SP_OBJECT (i->data);
989         SPStyle *style = SP_OBJECT_STYLE (obj);
990         if(!style || !SP_IS_ITEM(obj)) continue;
992         if(style->filter.set && style->getFilter())
993             used.insert(style->getFilter());
994         else
995             used.insert(0);
996     }
998     const int size = used.size();
1000     for(Gtk::TreeIter iter = _model->children().begin();
1001         iter != _model->children().end(); ++iter) {
1002         if(used.find((*iter)[_columns.filter]) != used.end()) {
1003             // If only one filter is in use by the selection, select it
1004             if(size == 1)
1005                 _list.get_selection()->select(iter);
1006             (*iter)[_columns.sel] = size;
1007         }
1008         else
1009             (*iter)[_columns.sel] = 0;
1010     }
1013 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1015     _observer->set(get_selected_filter());
1016     signal_filter_changed()();
1019 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1021     Gtk::TreeModel::iterator iter = _model->get_iter(path);
1022     
1023     if(iter) {
1024         SPFilter* filter = (*iter)[_columns.filter];
1025         filter->setLabel(text.c_str());
1026         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1027         if(iter)
1028             (*iter)[_columns.label] = text;
1029     }
1032 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1034     Gtk::TreeIter iter = _model->get_iter(path);
1036     if(iter) {
1037         SPDesktop *desktop = _dialog.getDesktop();
1038         SPDocument *doc = sp_desktop_document(desktop);
1039         SPFilter* filter = (*iter)[_columns.filter];
1040         Inkscape::Selection *sel = sp_desktop_selection(desktop);
1042         /* If this filter is the only one used in the selection, unset it */
1043         if((*iter)[_columns.sel] == 1)
1044             filter = 0;
1046         GSList const *items = sel->itemList();
1047             
1048         for (GSList const *i = items; i != NULL; i = i->next) {
1049             SPItem * item = SP_ITEM(i->data);
1050             SPStyle *style = SP_OBJECT_STYLE(item);
1051             g_assert(style != NULL);
1052                 
1053             if(filter)
1054                 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1055             else
1056                 ::remove_filter(item, false);
1058             SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1059         }
1060     
1061         update_selection(sel);
1062         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS,  _("Apply filter"));
1063     }
1066 /* Add all filters in the document to the combobox.
1067    Keeps the same selection if possible, otherwise selects the first element */
1068 void FilterEffectsDialog::FilterModifier::update_filters()
1070     SPDesktop* desktop = _dialog.getDesktop();
1071     SPDocument* document = sp_desktop_document(desktop);
1072     const GSList* filters = sp_document_get_resource_list(document, "filter");
1074     _model->clear();
1076     for(const GSList *l = filters; l; l = l->next) {
1077         Gtk::TreeModel::Row row = *_model->append();
1078         SPFilter* f = (SPFilter*)l->data;
1079         row[_columns.filter] = f;
1080         const gchar* lbl = f->label();
1081         const gchar* id = SP_OBJECT_ID(f);
1082         row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1083     }
1085     update_selection(desktop->selection);
1088 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1090     if(_list.get_selection()) {
1091         Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1093         if(i)
1094             return (*i)[_columns.filter];
1095     }
1097     return 0;
1100 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1102     if(filter) {
1103         for(Gtk::TreeModel::iterator i = _model->children().begin();
1104             i != _model->children().end(); ++i) {
1105             if((*i)[_columns.filter] == filter) {
1106                 _list.get_selection()->select(i);
1107                 break;
1108             }
1109         }
1110     }
1113 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1115     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1116         const bool sensitive = get_selected_filter() != NULL;
1117         _menu->items()[0].set_sensitive(sensitive);
1118         _menu->items()[1].set_sensitive(sensitive);
1119         _menu->popup(event->button, event->time);
1120     }
1123 void FilterEffectsDialog::FilterModifier::add_filter()
1125     SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1126     SPFilter* filter = new_filter(doc);
1128     const int count = _model->children().size();
1129     std::ostringstream os;
1130     os << "filter" << count;
1131     filter->setLabel(os.str().c_str());
1133     update_filters();
1135     select_filter(filter);
1137     sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1140 void FilterEffectsDialog::FilterModifier::remove_filter()
1142     SPFilter *filter = get_selected_filter();
1144     if(filter) {
1145         SPDocument* doc = filter->document;
1146         sp_repr_unparent(filter->repr);
1148         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1150         update_filters();
1151     }
1154 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1156     SPFilter* filter = get_selected_filter();
1158     if(filter) {
1159         Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1160         repr = repr->duplicate(repr->document());
1161         parent->appendChild(repr);
1163         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1165         update_filters();
1166     }
1169 void FilterEffectsDialog::FilterModifier::rename_filter()
1171     _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1174 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1175     : Glib::ObjectBase(typeid(CellRendererConnection)),
1176       _primitive(*this, "primitive", 0)
1177 {}
1179 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1181     return _primitive.get_proxy();
1184 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1186     _text_width = w;
1189 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1191     return _text_width;
1194 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1195     Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1196     int* x_offset, int* y_offset, int* width, int* height) const
1198     PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1200     if(x_offset)
1201         (*x_offset) = 0;
1202     if(y_offset)
1203         (*y_offset) = 0;
1204     if(width)
1205         (*width) = size * primlist.primitive_count() + _text_width * 7;
1206     if(height) {
1207         // Scale the height depending on the number of inputs, unless it's
1208         // the first primitive, in which case there are no connections
1209         SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1210         (*height) = size * input_count(prim);
1211     }
1214 /*** PrimitiveList ***/
1215 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1216     : _dialog(d),
1217       _in_drag(0),
1218       _observer(new SignalObserver)
1220     d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1221     
1222     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1223     signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1225     _model = Gtk::ListStore::create(_columns);
1227     set_reorderable(true);
1229     set_model(_model);
1230     append_column(_("_Effect"), _columns.type);
1232     _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1233     get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1234     signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1236     _connection_cell.set_text_width(init_text());
1238     int cols_count = append_column(_("Connections"), _connection_cell);
1239     Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1240     if(col)
1241        col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1244 // Sets up a vertical Pango context/layout, and returns the largest
1245 // width needed to render the FilterPrimitiveInput labels.
1246 int FilterEffectsDialog::PrimitiveList::init_text()
1248     // Set up a vertical context+layout
1249     Glib::RefPtr<Pango::Context> context = create_pango_context();
1250     const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1251     context->set_matrix(matrix);
1252     _vertical_layout = Pango::Layout::create(context);
1254     int maxfont = 0;
1255     for(int i = 0; i < FPInputConverter.end; ++i) {
1256         _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1257         int fontw, fonth;
1258         _vertical_layout->get_pixel_size(fontw, fonth);
1259         if(fonth > maxfont)
1260             maxfont = fonth;
1261     }
1263     return maxfont;
1266 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1268     return _signal_primitive_changed;
1271 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1273     _observer->set(get_selected());
1274     signal_primitive_changed()();
1275     _dialog._color_matrix_values->clear_store();
1278 /* Add all filter primitives in the current to the list.
1279    Keeps the same selection if possible, otherwise selects the first element */
1280 void FilterEffectsDialog::PrimitiveList::update()
1282     SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1283     const SPFilterPrimitive* active_prim = get_selected();
1284     bool active_found = false;
1286     _model->clear();
1288     if(f) {
1289         _dialog._primitive_box.set_sensitive(true);
1291         for(SPObject *prim_obj = f->children;
1292                 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1293                 prim_obj = prim_obj->next) {
1294             SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1295             if(prim) {
1296                 Gtk::TreeModel::Row row = *_model->append();
1297                 row[_columns.primitive] = prim;
1298                 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1299                 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1300                 row[_columns.id] = SP_OBJECT_ID(prim);
1302                 if(prim == active_prim) {
1303                     get_selection()->select(row);
1304                     active_found = true;
1305                 }
1306             }
1307         }
1309         if(!active_found && _model->children().begin())
1310             get_selection()->select(_model->children().begin());
1312         columns_autosize();
1313     }
1314     else {
1315         _dialog._primitive_box.set_sensitive(false);
1316     }
1319 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1321     _primitive_menu = menu;
1324 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1326     if(_dialog._filter_modifier.get_selected_filter()) {
1327         Gtk::TreeModel::iterator i = get_selection()->get_selected();
1328         if(i)
1329             return (*i)[_columns.primitive];
1330     }
1332     return 0;
1335 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1337     for(Gtk::TreeIter i = _model->children().begin();
1338         i != _model->children().end(); ++i) {
1339         if((*i)[_columns.primitive] == prim)
1340             get_selection()->select(i);
1341     }
1344 void FilterEffectsDialog::PrimitiveList::remove_selected()
1346     SPFilterPrimitive* prim = get_selected();
1348     if(prim) {
1349         _observer->set(0);
1351         sp_repr_unparent(prim->repr);
1353         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1354                          _("Remove filter primitive"));
1356         update();
1357     }
1360 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1362     Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1363     Glib::RefPtr<Gdk::Window> win = get_bin_window();
1364     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1366     SPFilterPrimitive* prim = get_selected();
1367     int row_count = get_model()->children().size();
1369     int fheight = CellRendererConnection::size;
1370     Gdk::Rectangle rct, vis;
1371     Gtk::TreeIter row = get_model()->children().begin();
1372     int text_start_x = 0;
1373     if(row) {
1374         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1375         get_visible_rect(vis);
1376         int vis_x, vis_y;
1377         tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1379         text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter.end + 1) + 1;
1380         for(int i = 0; i < FPInputConverter.end; ++i) {
1381             _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1382             const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1383             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());
1384             get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1385             get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1386         }
1387     }
1389     int row_index = 0;
1390     for(; row != get_model()->children().end(); ++row, ++row_index) {
1391         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1392         const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1394         // Check mouse state
1395         int mx, my;
1396         Gdk::ModifierType mask;
1397         get_bin_window()->get_pointer(mx, my, mask);
1399         // Outline the bottom of the connection area
1400         const int outline_x = x + fheight * (row_count - row_index);
1401         get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1403         // Side outline
1404         get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1406         std::vector<Gdk::Point> con_poly;
1407         int con_drag_y;
1408         bool inside;
1409         const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1410         const int inputs = input_count(row_prim);
1412         if(SP_IS_FEMERGE(row_prim)) {
1413             for(int i = 0; i < inputs; ++i) {
1414                 inside = do_connection_node(row, i, con_poly, mx, my);
1415                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1416                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1417                                                inside, con_poly);
1419                 if(_in_drag == (i + 1))
1420                     con_drag_y = con_poly[2].get_y();
1422                 if(_in_drag != (i + 1) || row_prim != prim)
1423                     draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1424             }
1425         }
1426         else {
1427             // Draw "in" shape
1428             inside = do_connection_node(row, 0, con_poly, mx, my);
1429             con_drag_y = con_poly[2].get_y();
1430             get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1431                                            darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1432                                            inside, con_poly);
1434             // Draw "in" connection
1435             if(_in_drag != 1 || row_prim != prim)
1436                 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1438             if(inputs == 2) {
1439                 // Draw "in2" shape
1440                 inside = do_connection_node(row, 1, con_poly, mx, my);
1441                 if(_in_drag == 2)
1442                     con_drag_y = con_poly[2].get_y();
1443                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1444                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1445                                                inside, con_poly);
1446                 // Draw "in2" connection
1447                 if(_in_drag != 2 || row_prim != prim)
1448                     draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1449             }
1450         }
1452         // Draw drag connection
1453         if(row_prim == prim && _in_drag) {
1454             get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1455                                         mx, con_drag_y);
1456             get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1457         }
1458     }
1460     return true;
1463 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1464                                                          const int text_start_x, const int x1, const int y1,
1465                                                          const int row_count)
1467     int src_id = 0;
1468     Gtk::TreeIter res = find_result(input, attr, src_id);
1469     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1470     Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1471     Glib::RefPtr<Gdk::GC> gc;
1473     const bool is_first = input == get_model()->children().begin();
1474     const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1475     const bool use_default = !res && !is_merge;
1476     
1477     if(res == input || (use_default && is_first)) {
1478         // Draw straight connection to a standard input
1479         // Draw a lighter line for an implicit connection to a standard input
1480         const int tw = _connection_cell.get_text_width();
1481         gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1482         gc = (use_default && is_first) ? lightgc : darkgc;
1483         get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1484         get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1485     }
1486     else {
1487         // Draw an 'L'-shaped connection to another filter primitive
1488         // If no connection is specified, draw a light connection to the previous primitive
1489         gc = use_default ? lightgc : darkgc;
1491         if(use_default) {
1492             res = input;
1493             --res;
1494         }
1496         if(res) {
1497             Gdk::Rectangle rct;
1498             
1499             get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1500             const int fheight = CellRendererConnection::size;
1501             
1502             get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1503             const int row_index = find_index(res);
1504             const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1505             const int y2 = rct.get_y() + rct.get_height();
1507             // Draw a bevelled 'L'-shaped connection
1508             get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1509             get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1510             get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);            
1511         }
1512     }
1515 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1516 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1517                                                             std::vector<Gdk::Point>& points,
1518                                                             const int ix, const int iy)
1520     Gdk::Rectangle rct;
1521     const int icnt = input_count((*row)[_columns.primitive]);
1523     get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1524     const int fheight = CellRendererConnection::size;
1526     get_cell_area(_model->get_path(row), *get_column(1), rct);
1527     const float h = rct.get_height() / icnt;
1529     const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1530     const int con_w = (int)(fheight * 0.35f);
1531     const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1532     points.clear();
1533     points.push_back(Gdk::Point(x, con_y));
1534     points.push_back(Gdk::Point(x, con_y + con_w * 2));
1535     points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1537     return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1540 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1541                                                                     const int attr, int& src_id)
1543     SPFilterPrimitive* prim = (*start)[_columns.primitive];
1544     Gtk::TreeIter target = _model->children().end();
1545     int image;
1547     if(SP_IS_FEMERGE(prim)) {
1548         int c = 0;
1549         bool found = false;
1550         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1551             if(c == attr && SP_IS_FEMERGENODE(o)) {
1552                 image = SP_FEMERGENODE(o)->input;
1553                 found = true;
1554             }
1555         }
1556         if(!found)
1557             return target;
1558     }
1559     else {
1560         if(attr == SP_ATTR_IN)
1561             image = prim->image_in;
1562         else if(attr == SP_ATTR_IN2) {
1563             if(SP_IS_FEBLEND(prim))
1564                 image = SP_FEBLEND(prim)->in2;
1565             else if(SP_IS_FECOMPOSITE(prim))
1566                 image = SP_FECOMPOSITE(prim)->in2;
1567             else if(SP_IS_FEDISPLACEMENTMAP(prim))
1568                 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1569             else
1570                 return target;
1571         }
1572         else
1573             return target;
1574     }
1576     if(image >= 0) {
1577         for(Gtk::TreeIter i = _model->children().begin();
1578             i != start; ++i) {
1579             if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1580                 target = i;
1581         }
1582         return target;
1583     }
1584     else if(image < -1) {
1585         src_id = -(image + 2);
1586         return start;
1587     }
1589     return target;
1592 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1594     int i = 0;
1595     for(Gtk::TreeIter iter = _model->children().begin();
1596         iter != target; ++iter, ++i);
1597     return i;
1600 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1602     Gtk::TreePath path;
1603     Gtk::TreeViewColumn* col;
1604     const int x = (int)e->x, y = (int)e->y;
1605     int cx, cy;
1607     _drag_prim = 0;
1608     
1609     if(get_path_at_pos(x, y, path, col, cx, cy)) {
1610         Gtk::TreeIter iter = _model->get_iter(path);
1611         std::vector<Gdk::Point> points;
1613         _drag_prim = (*iter)[_columns.primitive];
1614         const int icnt = input_count(_drag_prim);
1616         for(int i = 0; i < icnt; ++i) {
1617             if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1618                 _in_drag = i + 1;
1619                 break;
1620             }
1621         }
1622         
1623         queue_draw();
1624     }
1626     if(_in_drag) {
1627         _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1628         _autoscroll = 0;
1629         get_selection()->select(path);
1630         return true;
1631     }
1632     else
1633         return Gtk::TreeView::on_button_press_event(e);
1636 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1638     const int speed = 10;
1639     const int limit = 15;
1641     Gdk::Rectangle vis;
1642     get_visible_rect(vis);
1643     int vis_x, vis_y;
1644     tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1645     const int top = vis_y + vis.get_height();
1647     // When autoscrolling during a connection drag, set the speed based on
1648     // where the mouse is in relation to the edges.
1649     if(e->y < vis_y)
1650         _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1651     else if(e->y < vis_y + limit)
1652         _autoscroll = -speed;
1653     else if(e->y > top)
1654         _autoscroll = (int)(speed + (e->y - top) / 5);
1655     else if(e->y > top - limit)
1656         _autoscroll = speed;
1657     else
1658         _autoscroll = 0;
1660     queue_draw();
1662     return Gtk::TreeView::on_motion_notify_event(e);
1665 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1667     SPFilterPrimitive *prim = get_selected(), *target;
1669     _scroll_connection.disconnect();
1671     if(_in_drag && prim) {
1672         Gtk::TreePath path;
1673         Gtk::TreeViewColumn* col;
1674         int cx, cy;
1675         
1676         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1677             const gchar *in_val = 0;
1678             Glib::ustring result;
1679             Gtk::TreeIter target_iter = _model->get_iter(path);
1680             target = (*target_iter)[_columns.primitive];
1682             Gdk::Rectangle rct;
1683             get_cell_area(path, *col, rct);
1684             const int twidth = _connection_cell.get_text_width();
1685             const int sources_x = rct.get_width() - twidth * FPInputConverter.end;
1686             if(cx > sources_x) {
1687                 int src = (cx - sources_x) / twidth;
1688                 if(src < 0)
1689                     src = 0;
1690                 else if(src >= FPInputConverter.end)
1691                     src = FPInputConverter.end - 1;
1692                 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1693                 in_val = result.c_str();
1694             }
1695             else {
1696                 // Ensure that the target comes before the selected primitive
1697                 for(Gtk::TreeIter iter = _model->children().begin();
1698                     iter != get_selection()->get_selected(); ++iter) {
1699                     if(iter == target_iter) {
1700                         Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1701                         // Make sure the target has a result
1702                         const gchar *gres = repr->attribute("result");
1703                         if(!gres) {
1704                             result = "result" + Glib::Ascii::dtostr(SP_FILTER(prim->parent)->_image_number_next);
1705                             repr->setAttribute("result", result.c_str());
1706                             in_val = result.c_str();
1707                         }
1708                         else
1709                             in_val = gres;
1710                         break;
1711                     }
1712                 }
1713             }
1715             if(SP_IS_FEMERGE(prim)) {
1716                 int c = 1;
1717                 bool handled = false;
1718                 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1719                     if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1720                         // If input is null, delete it
1721                         if(!in_val) {
1722                             sp_repr_unparent(o->repr);
1723                             sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1724                                              _("Remove merge node"));
1725                             (*get_selection()->get_selected())[_columns.primitive] = prim;
1726                         }
1727                         else
1728                             _dialog.set_attr(o, SP_ATTR_IN, in_val);
1729                         handled = true;
1730                     }
1731                 }
1732                 // Add new input?
1733                 if(!handled && c == _in_drag && in_val) {
1734                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1735                     Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1736                     repr->setAttribute("inkscape:collect", "always");
1737                     prim->repr->appendChild(repr);
1738                     SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1739                     Inkscape::GC::release(repr);
1740                     _dialog.set_attr(node, SP_ATTR_IN, in_val);
1741                     (*get_selection()->get_selected())[_columns.primitive] = prim;
1742                 }
1743             }
1744             else {
1745                 if(_in_drag == 1)
1746                     _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1747                 else if(_in_drag == 2)
1748                     _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1749             }
1750         }
1752         _in_drag = 0;
1753         queue_draw();
1755         _dialog.update_settings_view();
1756     }
1758     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1759         const bool sensitive = get_selected() != NULL;
1760         _primitive_menu->items()[0].set_sensitive(sensitive);
1761         _primitive_menu->items()[1].set_sensitive(sensitive);
1762         _primitive_menu->popup(e->button, e->time);
1764         return true;
1765     }
1766     else
1767         return Gtk::TreeView::on_button_release_event(e);
1770 // Checks all of prim's inputs, removes any that use result
1771 void check_single_connection(SPFilterPrimitive* prim, const int result)
1773     if(prim && result >= 0) {
1775         if(prim->image_in == result)
1776             SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1778         if(SP_IS_FEBLEND(prim)) {
1779             if(SP_FEBLEND(prim)->in2 == result)
1780                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1781         }
1782         else if(SP_IS_FECOMPOSITE(prim)) {
1783             if(SP_FECOMPOSITE(prim)->in2 == result)
1784                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1785         }
1786         else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
1787             if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
1788                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1789         }
1790     }
1793 // Remove any connections going to/from prim_iter that forward-reference other primitives
1794 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
1796     SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
1797     bool before = true;
1799     for(Gtk::TreeIter iter = _model->children().begin();
1800         iter != _model->children().end(); ++iter) {
1801         if(iter == prim_iter)
1802             before = false;
1803         else {
1804             SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
1805             if(before)
1806                 check_single_connection(cur_prim, prim->image_out);
1807             else
1808                 check_single_connection(prim, cur_prim->image_out);
1809         }
1810     }
1813 // Reorder the filter primitives to match the list order
1814 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
1816     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
1817     int ndx = 0;
1819     for(Gtk::TreeModel::iterator iter = _model->children().begin();
1820         iter != _model->children().end(); ++iter, ++ndx) {
1821         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
1822         if(prim && prim == _drag_prim) {
1823             SP_OBJECT_REPR(prim)->setPosition(ndx);
1824             break;
1825         }
1826     }
1828     for(Gtk::TreeModel::iterator iter = _model->children().begin();
1829         iter != _model->children().end(); ++iter, ++ndx) {
1830         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
1831         if(prim && prim == _drag_prim) {
1832             sanitize_connections(iter);
1833             get_selection()->select(iter);
1834             break;
1835         }
1836     }
1838     filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
1840     sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
1843 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
1844 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
1846     if(_autoscroll) {
1847         Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
1848         double v;
1850         v = a.get_value() + _autoscroll;
1851         if(v < 0)
1852             v = 0;
1853         if(v > a.get_upper() - a.get_page_size())
1854             v = a.get_upper() - a.get_page_size();
1856         a.set_value(v);
1858         queue_draw();
1859     }
1861     return true;
1864 int FilterEffectsDialog::PrimitiveList::primitive_count() const
1866     return _model->children().size();
1869 /*** FilterEffectsDialog ***/
1871 FilterEffectsDialog::FilterEffectsDialog() 
1872     : UI::Widget::Panel("", "dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
1873       _filter_modifier(*this),
1874       _primitive_list(*this),
1875       _add_primitive_type(FPConverter),
1876       _add_primitive(_("Add Effect:")),
1877       _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
1878       _locked(false),
1879       _attr_lock(false)
1881     _settings = new Settings(*this, _settings_box, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
1882                              NR_FILTER_ENDPRIMITIVETYPE);
1883     _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
1884     _sizegroup->set_ignore_hidden();
1886     _add_primitive_type.remove_row(NR_FILTER_IMAGE);
1887     _add_primitive_type.remove_row(NR_FILTER_TILE);
1888     _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
1889         
1890     // Initialize widget hierarchy
1891     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
1892     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
1893     Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox);
1894     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
1895     Gtk::Frame* fr_settings = Gtk::manage(new Gtk::Frame(_("<b>Effect parameters</b>")));
1896     Gtk::Alignment* al_settings = Gtk::manage(new Gtk::Alignment);
1897     _getContents()->add(*hpaned);
1898     hpaned->pack1(_filter_modifier);
1899     hpaned->pack2(_primitive_box);
1900     _primitive_box.pack_start(*sw_prims);
1901     _primitive_box.pack_start(*infobox,false, false);    
1902     _primitive_box.pack_start(*hb_prims, false, false);
1903     sw_prims->add(_primitive_list);
1904     infobox->pack_start(_infobox_icon, false, false);
1905     infobox->pack_end(_infobox_desc, false, false);
1906     _infobox_desc.set_line_wrap(true);
1907     
1908     hb_prims->pack_end(_add_primitive_type, false, false);
1909     hb_prims->pack_end(_add_primitive, false, false);
1910     _getContents()->pack_start(*fr_settings, false, false);
1911     fr_settings->add(*al_settings);
1912     al_settings->add(_settings_box);
1913     
1914     _primitive_list.signal_primitive_changed().connect(
1915         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
1916     _filter_modifier.signal_filter_changed().connect(
1917         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
1919     _add_primitive_type.signal_changed().connect(
1920         sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
1921           
1922     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1923     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
1924     al_settings->set_padding(0, 0, 12, 0);
1925     fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
1926     ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
1927     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
1928     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
1929                                                sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
1930     
1931     show_all_children();
1932     init_settings_widgets();
1933     _primitive_list.update();
1934     update_primitive_infobox();
1937 FilterEffectsDialog::~FilterEffectsDialog()
1939     delete _settings;
1942 void FilterEffectsDialog::set_attrs_locked(const bool l)
1944     _locked = l;
1947 void FilterEffectsDialog::show_all_vfunc()
1949     UI::Widget::Panel::show_all_vfunc();
1951     update_settings_view();
1954 void FilterEffectsDialog::init_settings_widgets()
1956     // TODO: Find better range/climb-rate/digits values for the SpinSliders,
1957     //       most of the current values are complete guesses!
1959     _empty_settings.set_sensitive(false);
1960     _settings_box.pack_start(_empty_settings);
1962     _settings->type(NR_FILTER_BLEND);
1963     _settings->add_combo(SP_ATTR_MODE, _("Mode"), BlendModeConverter);
1965     _settings->type(NR_FILTER_COLORMATRIX);
1966     ComboBoxEnum<FilterColorMatrixType>* colmat = _settings->add_combo(SP_ATTR_TYPE, _("Type"), ColorMatrixTypeConverter);
1967     _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s)"));
1968     colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
1970     _settings->type(NR_FILTER_COMPONENTTRANSFER);
1971     _settings->add_notimplemented();
1972     /*_settings->add_combo(SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
1973     _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
1974     _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
1975     _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
1976     _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
1977     _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
1979     _settings->type(NR_FILTER_COMPOSITE);
1980     _settings->add_combo(SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
1981     _k1 = _settings->add_spinslider(SP_ATTR_K1, _("K1"), -10, 10, 0.1, 0.01, 2);
1982     _k2 = _settings->add_spinslider(SP_ATTR_K2, _("K2"), -10, 10, 0.1, 0.01, 2);
1983     _k3 = _settings->add_spinslider(SP_ATTR_K3, _("K3"), -10, 10, 0.1, 0.01, 2);
1984     _k4 = _settings->add_spinslider(SP_ATTR_K4, _("K4"), -10, 10, 0.1, 0.01, 2);
1986     _settings->type(NR_FILTER_CONVOLVEMATRIX);
1987     _convolve_order = _settings->add_dualspinbutton(SP_ATTR_ORDER, _("Size"), 1, 5, 1, 1, 0);
1988     _convolve_target = _settings->add_multispinbutton(SP_ATTR_TARGETX, SP_ATTR_TARGETY, _("Target"), 0, 4, 1, 1, 0);
1989     _convolve_matrix = _settings->add_matrix(SP_ATTR_KERNELMATRIX, _("Kernel"));
1990     _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
1991     _settings->add_spinslider(SP_ATTR_DIVISOR, _("Divisor"), 1, 20, 1, 0.1, 2);
1992     _settings->add_spinslider(SP_ATTR_BIAS, _("Bias"), -10, 10, 1, 0.01, 1);
1993     _settings->add_combo(SP_ATTR_EDGEMODE, _("Edge Mode"), ConvolveMatrixEdgeModeConverter);
1994     _settings->add_checkbutton(SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false");
1996     _settings->type(NR_FILTER_DIFFUSELIGHTING);
1997     _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Diffuse Color"));
1998     _settings->add_spinslider(SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1);
1999     _settings->add_spinslider(SP_ATTR_DIFFUSECONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2);
2000     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2001     _settings->add_lightsource();
2003     _settings->type(NR_FILTER_DISPLACEMENTMAP);
2004     _settings->add_spinslider(SP_ATTR_SCALE, _("Scale"), 0, 100, 1, 0.01, 1);
2005     _settings->add_combo(SP_ATTR_XCHANNELSELECTOR, _("X Channel"), DisplacementMapChannelConverter);
2006     _settings->add_combo(SP_ATTR_YCHANNELSELECTOR, _("Y Channel"), DisplacementMapChannelConverter);
2008     _settings->type(NR_FILTER_FLOOD);
2009     _settings->add_color(SP_PROP_FLOOD_COLOR, _("Flood Color"));
2010     _settings->add_spinslider(SP_PROP_FLOOD_OPACITY, _("Opacity"), 0, 1, 0.1, 0.01, 2);
2011     
2012     _settings->type(NR_FILTER_GAUSSIANBLUR);
2013     _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation"), 0.01, 100, 1, 0.01, 1);
2015     _settings->type(NR_FILTER_MORPHOLOGY);
2016     _settings->add_combo(SP_ATTR_OPERATOR, _("Operator"), MorphologyOperatorConverter);
2017     _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius"), 0, 100, 1, 0.01, 1);
2019     _settings->type(NR_FILTER_IMAGE);
2020     _settings->add_notimplemented();
2022     _settings->type(NR_FILTER_OFFSET);
2023     _settings->add_spinslider(SP_ATTR_DX, _("Delta X"), -100, 100, 1, 0.01, 1);
2024     _settings->add_spinslider(SP_ATTR_DY, _("Delta Y"), -100, 100, 1, 0.01, 1);
2026     _settings->type(NR_FILTER_SPECULARLIGHTING);
2027     _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Specular Color"));
2028     _settings->add_spinslider(SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1);
2029     _settings->add_spinslider(SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2);
2030     _settings->add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1);
2031     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2032     _settings->add_lightsource();
2033     
2034     _settings->type(NR_FILTER_TILE);
2035     _settings->add_notimplemented();
2037     _settings->type(NR_FILTER_TURBULENCE);
2038     _settings->add_checkbutton(SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2039     _settings->add_combo(SP_ATTR_TYPE, _("Type"), TurbulenceTypeConverter);
2040     _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency"), 0, 1, 0.001, 0.01, 3);
2041     _settings->add_spinslider(SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2042     _settings->add_spinslider(SP_ATTR_SEED, _("Seed"), 0, 1000, 1, 1, 0);
2045 void FilterEffectsDialog::add_primitive()
2047     SPFilter* filter = _filter_modifier.get_selected_filter();
2049     if(filter) {
2050         SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2052         _primitive_list.select(prim);
2054         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2055     }
2058 void FilterEffectsDialog::update_primitive_infobox()
2060     switch(_add_primitive_type.get_active_data()->id){
2061         case(NR::NR_FILTER_BLEND):
2062             _infobox_icon.set(g_strdup_printf("%s/feBlend-icon.png", INKSCAPE_PIXMAPDIR));
2063             _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2064             break;
2065         case(NR::NR_FILTER_COLORMATRIX):
2066             _infobox_icon.set(g_strdup_printf("%s/feColorMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2067             _infobox_desc.set_markup(_("The <b>feColorMatrix</b> filter primitive applies a matrix transformation to colour of each rendered pixel. This allows for effects like turning object to grayscale, modifying colour saturation and changing colour hue."));
2068             break;
2069         case(NR::NR_FILTER_COMPONENTTRANSFER):
2070             _infobox_icon.set(g_strdup_printf("%s/feComponentTransfer-icon.png", INKSCAPE_PIXMAPDIR));
2071             _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."));
2072             break;
2073         case(NR::NR_FILTER_COMPOSITE):
2074             _infobox_icon.set(g_strdup_printf("%s/feComposite-icon.png", INKSCAPE_PIXMAPDIR));
2075             _infobox_desc.set_markup(_("The <b>feComposite</b> filter primitive composites two images using one of the Porter-Duff blending modes or the aritmetic mode described in SVG standard. Porter-Duff blending modes are essentially logical operations between the corresponding pixel values of the images."));
2076             break;
2077         case(NR::NR_FILTER_CONVOLVEMATRIX):
2078             _infobox_icon.set(g_strdup_printf("%s/feConvolveMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2079             _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."));
2080             break;
2081         case(NR::NR_FILTER_DIFFUSELIGHTING):
2082             _infobox_icon.set(g_strdup_printf("%s/feDiffuseLighting-icon.png", INKSCAPE_PIXMAPDIR));
2083             _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."));
2084             break;
2085         case(NR::NR_FILTER_DISPLACEMENTMAP):
2086             _infobox_icon.set(g_strdup_printf("%s/feDisplacementMap-icon.png", INKSCAPE_PIXMAPDIR));
2087             _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."));
2088             break;
2089         case(NR::NR_FILTER_FLOOD):
2090             _infobox_icon.set(g_strdup_printf("%s/feFlood-icon.png", INKSCAPE_PIXMAPDIR));
2091             _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."));
2092             break;
2093         case(NR::NR_FILTER_GAUSSIANBLUR):
2094             _infobox_icon.set(g_strdup_printf("%s/feGaussianBlur-icon.png", INKSCAPE_PIXMAPDIR));
2095             _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."));
2096             break;
2097         case(NR::NR_FILTER_IMAGE):
2098             _infobox_icon.set(g_strdup_printf("%s/feImage-icon.png", INKSCAPE_PIXMAPDIR));
2099             _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2100             break;
2101         case(NR::NR_FILTER_MERGE):
2102             _infobox_icon.set(g_strdup_printf("%s/feMerge-icon.png", INKSCAPE_PIXMAPDIR));
2103             _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."));
2104             break;
2105         case(NR::NR_FILTER_MORPHOLOGY):
2106             _infobox_icon.set(g_strdup_printf("%s/feMorphology-icon.png", INKSCAPE_PIXMAPDIR));
2107             _infobox_desc.set_markup(_("The <b>feMorphology</b> filter primitive provides erode and dilate effects. For single-colour objects erode makes the object thinner and dilate makes it thicker."));
2108             break;
2109         case(NR::NR_FILTER_OFFSET):
2110             _infobox_icon.set(g_strdup_printf("%s/feOffset-icon.png", INKSCAPE_PIXMAPDIR));
2111             _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."));
2112             break;
2113         case(NR::NR_FILTER_SPECULARLIGHTING):
2114             _infobox_icon.set(g_strdup_printf("%s/feSpecularLighting-icon.png", INKSCAPE_PIXMAPDIR));
2115             _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."));
2116             break;
2117         case(NR::NR_FILTER_TILE):
2118             _infobox_icon.set(g_strdup_printf("%s/feTile-icon.png", INKSCAPE_PIXMAPDIR));
2119             _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2120             break;
2121         case(NR::NR_FILTER_TURBULENCE):
2122             _infobox_icon.set(g_strdup_printf("%s/feTurbulence-icon.png", INKSCAPE_PIXMAPDIR));
2123             _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."));
2124             break;
2125     }
2128 void FilterEffectsDialog::duplicate_primitive()
2130     SPFilter* filter = _filter_modifier.get_selected_filter();
2131     SPFilterPrimitive* origprim = _primitive_list.get_selected();
2133     if(filter && origprim) {
2134         Inkscape::XML::Node *repr;
2135         repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2136         SP_OBJECT_REPR(filter)->appendChild(repr);
2138         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2140         _primitive_list.update();
2141     }
2144 void FilterEffectsDialog::convolve_order_changed()
2146     _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2147     _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2148     _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2151 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2153     set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2156 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2158     set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2161 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2163     if(!_locked) {
2164         _attr_lock = true;
2166         SPFilter *filter = _filter_modifier.get_selected_filter();
2167         const gchar* name = (const gchar*)sp_attribute_name(attr);
2168         if(filter && name && o) {
2169             update_settings_sensitivity();
2171             SP_OBJECT_REPR(o)->setAttribute(name, val);
2172             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2174             Glib::ustring undokey = "filtereffects:";
2175             undokey += name;
2176             sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2177                                    _("Set filter primitive attribute"));
2178         }
2180         _attr_lock = false;
2181     }
2184 void FilterEffectsDialog::update_settings_view()
2186     update_settings_sensitivity();
2188     if(_attr_lock)
2189         return;
2191     SPFilterPrimitive* prim = _primitive_list.get_selected();
2193     if(prim) {
2194         _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2195         _empty_settings.hide();
2196     }
2197     else {
2198         _settings_box.hide_all();
2199         _settings_box.show();
2200         _empty_settings.show();
2201     }
2204 void FilterEffectsDialog::update_settings_sensitivity()
2206     SPFilterPrimitive* prim = _primitive_list.get_selected();
2207     const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2208     _k1->set_sensitive(use_k);
2209     _k2->set_sensitive(use_k);
2210     _k3->set_sensitive(use_k);
2211     _k4->set_sensitive(use_k);
2213     if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2214         SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2215         const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2216         const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2218         // Component transfer not yet implemented
2219         /*_ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2220         _ct_slope->set_sensitive(linear);
2221         _ct_intercept->set_sensitive(linear);
2222         _ct_amplitude->set_sensitive(gamma);
2223         _ct_exponent->set_sensitive(gamma);
2224         _ct_offset->set_sensitive(gamma);*/
2225     }
2228 void FilterEffectsDialog::update_color_matrix()
2230     _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2233 } // namespace Dialog
2234 } // namespace UI
2235 } // namespace Inkscape
2237 /*
2238   Local Variables:
2239   mode:c++
2240   c-file-style:"stroustrup"
2241   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2242   indent-tabs-mode:nil
2243   fill-column:99
2244   End:
2245 */
2246 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :