Code

infobox on filter effects dialog now is optional
[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 "dir-util.h"
33 #include "document.h"
34 #include "filter-chemistry.h"
35 #include "filter-effects-dialog.h"
36 #include "filter-enums.h"
37 #include "inkscape.h"
38 #include "path-prefix.h"
39 #include "prefs-utils.h"
40 #include "selection.h"
41 #include "sp-feblend.h"
42 #include "sp-fecolormatrix.h"
43 #include "sp-fecomponenttransfer.h"
44 #include "sp-fecomposite.h"
45 #include "sp-feconvolvematrix.h"
46 #include "sp-fedisplacementmap.h"
47 #include "sp-fedistantlight.h"
48 #include "sp-femerge.h"
49 #include "sp-femergenode.h"
50 #include "sp-feoffset.h"
51 #include "sp-fepointlight.h"
52 #include "sp-fespotlight.h"
53 #include "sp-filter-primitive.h"
54 #include "sp-gaussian-blur.h"
56 #include "style.h"
57 #include "svg/svg-color.h"
58 #include "ui/dialog/filedialog.h"
59 #include "verbs.h"
60 #include "xml/node.h"
61 #include "xml/node-observer.h"
62 #include "xml/repr.h"
63 #include <sstream>
65 #include "io/sys.h"
66 #include <iostream>
68 using namespace NR;
70 namespace Inkscape {
71 namespace UI {
72 namespace Dialog {
74 // Returns the number of inputs available for the filter primitive type
75 int input_count(const SPFilterPrimitive* prim)
76 {
77     if(!prim)
78         return 0;
79     else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
80         return 2;
81     else if(SP_IS_FEMERGE(prim)) {
82         // Return the number of feMergeNode connections plus an extra
83         int count = 1;
84         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count);
85         return count;
86     }
87     else
88         return 1;
89 }
91 // Very simple observer that just emits a signal if anything happens to a node
92 class FilterEffectsDialog::SignalObserver : public XML::NodeObserver
93 {
94 public:
95     SignalObserver()
96         : _oldsel(0)
97     {}
99     // Add this observer to the SPObject and remove it from any previous object
100     void set(SPObject* o)
101     {
102         if(_oldsel && _oldsel->repr)
103             _oldsel->repr->removeObserver(*this);
104         if(o && o->repr)
105             o->repr->addObserver(*this);
106         _oldsel = o;
107     }
109     void notifyChildAdded(XML::Node&, XML::Node&, XML::Node*)
110     { signal_changed()(); }
112     void notifyChildRemoved(XML::Node&, XML::Node&, XML::Node*)
113     { signal_changed()(); }
115     void notifyChildOrderChanged(XML::Node&, XML::Node&, XML::Node*, XML::Node*)
116     { signal_changed()(); }
118     void notifyContentChanged(XML::Node&, Util::ptr_shared<char>, Util::ptr_shared<char>)
119     {}
121     void notifyAttributeChanged(XML::Node&, GQuark, Util::ptr_shared<char>, Util::ptr_shared<char>)
122     { signal_changed()(); }
124     sigc::signal<void>& signal_changed()
125     {
126         return _signal_changed;
127     }
128 private:
129     sigc::signal<void> _signal_changed;
130     SPObject* _oldsel;
131 };
133 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
135 public:
136     CheckButtonAttr(const Glib::ustring& label,
137                     const Glib::ustring& tv, const Glib::ustring& fv,
138                     const SPAttributeEnum a)
139         : Gtk::CheckButton(label),
140           AttrWidget(a),
141           _true_val(tv), _false_val(fv)
142     {
143         signal_toggled().connect(signal_attr_changed().make_slot());
144     }
146     Glib::ustring get_as_attribute() const
147     {
148         return get_active() ? _true_val : _false_val;
149     }
151     void set_from_attribute(SPObject* o)
152     {
153         const gchar* val = attribute_value(o);
154         if(val) {
155             if(_true_val == val)
156                 set_active(true);
157             else if(_false_val == val)
158                 set_active(false);
159         }
160     }
161 private:
162     const Glib::ustring _true_val, _false_val;
163 };
165 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
167 public:
168     SpinButtonAttr(double lower, double upper, double step_inc,
169                    double climb_rate, int digits, const SPAttributeEnum a)
170         : Gtk::SpinButton(climb_rate, digits),
171           AttrWidget(a)
172     {
173         set_range(lower, upper);
174         set_increments(step_inc, step_inc * 5);
176         signal_value_changed().connect(signal_attr_changed().make_slot());
177     }
179     Glib::ustring get_as_attribute() const
180     {
181         const double val = get_value();
183         if(get_digits() == 0)
184             return Glib::Ascii::dtostr((int)val);
185         else
186             return Glib::Ascii::dtostr(val);
187     }
189     void set_from_attribute(SPObject* o)
190     {
191         const gchar* val = attribute_value(o);
192         if(val)
193             set_value(Glib::Ascii::strtod(val));
194     }
195 };
197 // Contains an arbitrary number of spin buttons that use seperate attributes
198 class MultiSpinButton : public Gtk::HBox
200 public:
201     MultiSpinButton(double lower, double upper, double step_inc,
202                     double climb_rate, int digits, std::vector<SPAttributeEnum> attrs)
203     {
204         for(unsigned i = 0; i < attrs.size(); ++i) {
205             _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i]));
206             pack_start(*_spins.back(), false, false);
207         }
208     }
210     ~MultiSpinButton()
211     {
212         for(unsigned i = 0; i < _spins.size(); ++i)
213             delete _spins[i];
214     }
216     std::vector<SpinButtonAttr*>& get_spinbuttons()
217     {
218         return _spins;
219     }
220 private:
221     std::vector<SpinButtonAttr*> _spins;
222 };
224 // Contains two spinbuttons that describe a NumberOptNumber
225 class DualSpinButton : public Gtk::HBox, public AttrWidget
227 public:
228     DualSpinButton(double lower, double upper, double step_inc,
229                    double climb_rate, int digits, const SPAttributeEnum a)
230         : AttrWidget(a),
231           _s1(climb_rate, digits), _s2(climb_rate, digits)
232     {
233         _s1.set_range(lower, upper);
234         _s2.set_range(lower, upper);
235         _s1.set_increments(step_inc, step_inc * 5);
236         _s2.set_increments(step_inc, step_inc * 5);
238         _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
239         _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
241         pack_start(_s1, false, false);
242         pack_start(_s2, false, false);
243     }
245     Gtk::SpinButton& get_spinbutton1()
246     {
247         return _s1;
248     }
250     Gtk::SpinButton& get_spinbutton2()
251     {
252         return _s2;
253     }
255     virtual Glib::ustring get_as_attribute() const
256     {
257         double v1 = _s1.get_value();
258         double v2 = _s2.get_value();
260         if(_s1.get_digits() == 0) {
261             v1 = (int)v1;
262             v2 = (int)v2;
263         }
265         return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
266     }
268     virtual void set_from_attribute(SPObject* o)
269     {
270         const gchar* val = attribute_value(o);
271         if(val) {
272             NumberOptNumber n;
273             n.set(val);
274             _s1.set_value(n.getNumber());
275             _s2.set_value(n.getOptNumber());
276         }
277     }
278 private:
279     Gtk::SpinButton _s1, _s2;
280 };
282 class ColorButton : public Gtk::ColorButton, public AttrWidget
284 public:
285     ColorButton(const SPAttributeEnum a)
286         : AttrWidget(a)
287     {
288         signal_color_set().connect(signal_attr_changed().make_slot());
290         Gdk::Color col;
291         col.set_rgb(65535, 65535, 65535);
292         set_color(col);
293     }
295     // Returns the color in 'rgb(r,g,b)' form.
296     Glib::ustring get_as_attribute() const
297     {
298         std::ostringstream os;
299         const Gdk::Color c = get_color();
300         const int r = c.get_red() / 257, g = c.get_green() / 257, b = c.get_blue() / 257;
301         os << "rgb(" << r << "," << g << "," << b << ")";
302         return os.str();
303     }
306     void set_from_attribute(SPObject* o)
307     {
308         const gchar* val = attribute_value(o);
309         if(val) {
310             const guint32 i = sp_svg_read_color(val, 0xFFFFFFFF);
311             const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
312             Gdk::Color col;
313             col.set_rgb(r * 257, g * 257, b * 257);
314             set_color(col);
315         }
316     }
317 };
319 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
320 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
322 public:
323     MatrixAttr(const SPAttributeEnum a)
324         : AttrWidget(a), _locked(false)
325     {
326         _model = Gtk::ListStore::create(_columns);
327         _tree.set_model(_model);
328         _tree.set_headers_visible(false);
329         _tree.show();
330         add(_tree);
331         set_shadow_type(Gtk::SHADOW_IN);
332     }
334     std::vector<double> get_values() const
335     {
336         std::vector<double> vec;
337         for(Gtk::TreeIter iter = _model->children().begin();
338             iter != _model->children().end(); ++iter) {
339             for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
340                 vec.push_back((*iter)[_columns.cols[c]]);
341         }
342         return vec;
343     }
345     void set_values(const std::vector<double>& v)
346     {
347         unsigned i = 0;
348         for(Gtk::TreeIter iter = _model->children().begin();
349             iter != _model->children().end(); ++iter) {
350             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
351                 if(i >= v.size())
352                     return;
353                 (*iter)[_columns.cols[c]] = v[i];
354                 ++i;
355             }
356         }
357     }
359     Glib::ustring get_as_attribute() const
360     {
361         std::ostringstream os;
363         for(Gtk::TreeIter iter = _model->children().begin();
364             iter != _model->children().end(); ++iter) {
365             for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
366                 os << (*iter)[_columns.cols[c]] << " ";
367             }
368         }
370         return os.str();
371     }
373     void set_from_attribute(SPObject* o)
374     {
375         if(o) {
376             if(SP_IS_FECONVOLVEMATRIX(o)) {
377                 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
378                 int cols, rows;
379                 cols = (int)conv->order.getNumber();
380                 if(cols > 5)
381                     cols = 5;
382                 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
383                 update(o, rows, cols);
384             }
385             else if(SP_IS_FECOLORMATRIX(o))
386                 update(o, 4, 5);
387         }
388     }
389 private:
390     class MatrixColumns : public Gtk::TreeModel::ColumnRecord
391     {
392     public:
393         MatrixColumns()
394         {
395             cols.resize(5);
396             for(unsigned i = 0; i < cols.size(); ++i)
397                 add(cols[i]);
398         }
399         std::vector<Gtk::TreeModelColumn<double> > cols;
400     };
402     void update(SPObject* o, const int rows, const int cols)
403     {
404         if(_locked)
405             return;
407         _model->clear();
409         _tree.remove_all_columns();
411         std::vector<gdouble>* values = NULL;
412         if(SP_IS_FECOLORMATRIX(o))
413             values = &SP_FECOLORMATRIX(o)->values;
414         else if(SP_IS_FECONVOLVEMATRIX(o))
415             values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
416         else
417             return;
419         if(o) {
420             int ndx = 0;
422             for(int i = 0; i < cols; ++i) {
423                 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
424                 dynamic_cast<Gtk::CellRendererText*>(
425                     _tree.get_column_cell_renderer(i))->signal_edited().connect(
426                         sigc::mem_fun(*this, &MatrixAttr::rebind));
427             }
429             for(int r = 0; r < rows; ++r) {
430                 Gtk::TreeRow row = *(_model->append());
431                 // Default to identity matrix
432                 for(int c = 0; c < cols; ++c, ++ndx)
433                     row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
434             }
435         }
436     }
438     void rebind(const Glib::ustring&, const Glib::ustring&)
439     {
440         _locked = true;
441         signal_attr_changed()();
442         _locked = false;
443     }
445     bool _locked;
446     Gtk::TreeView _tree;
447     Glib::RefPtr<Gtk::ListStore> _model;
448     MatrixColumns _columns;
449 };
451 // Displays a matrix or a slider for feColorMatrix
452 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
454 public:
455     ColorMatrixValues()
456         : AttrWidget(SP_ATTR_VALUES),
457           _matrix(SP_ATTR_VALUES),
458           _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
459           _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
460           _label(_("None"), Gtk::ALIGN_LEFT),
461           _use_stored(false),
462           _saturation_store(0),
463           _angle_store(0)
464     {
465         _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
466         _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
467         _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
468         signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
470         _matrix.show();
471         _saturation.show();
472         _angle.show();
473         _label.show();
474         _label.set_sensitive(false);
476         set_shadow_type(Gtk::SHADOW_NONE);
477     }
479     virtual void set_from_attribute(SPObject* o)
480     {
481         if(SP_IS_FECOLORMATRIX(o)) {
482             SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
483             remove();
484             switch(col->type) {
485                 case COLORMATRIX_SATURATE:
486                     add(_saturation);
487                     if(_use_stored)
488                         _saturation.set_value(_saturation_store);
489                     else
490                         _saturation.set_from_attribute(o);
491                     break;
492                 case COLORMATRIX_HUEROTATE:
493                     add(_angle);
494                     if(_use_stored)
495                         _angle.set_value(_angle_store);
496                     else
497                         _angle.set_from_attribute(o);
498                     break;
499                 case COLORMATRIX_LUMINANCETOALPHA:
500                     add(_label);
501                     break;
502                 case COLORMATRIX_MATRIX:
503                 default:
504                     add(_matrix);
505                     if(_use_stored)
506                         _matrix.set_values(_matrix_store);
507                     else
508                         _matrix.set_from_attribute(o);
509                     break;
510             }
511             _use_stored = true;
512         }
513     }
515     virtual Glib::ustring get_as_attribute() const
516     {
517         const Widget* w = get_child();
518         if(w == &_label)
519             return "";
520         else
521             return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
522     }
524     void clear_store()
525     {
526         _use_stored = false;
527     }
528 private:
529     void update_store()
530     {
531         const Widget* w = get_child();
532         if(w == &_matrix)
533             _matrix_store = _matrix.get_values();
534         else if(w == &_saturation)
535             _saturation_store = _saturation.get_value();
536         else if(w == &_angle)
537             _angle_store = _angle.get_value();
538     }
540     MatrixAttr _matrix;
541     SpinSlider _saturation;
542     SpinSlider _angle;
543     Gtk::Label _label;
545     // Store separate values for the different color modes
546     bool _use_stored;
547     std::vector<double> _matrix_store;
548     double _saturation_store;
549     double _angle_store;
550 };
552 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
554 //Displays a chooser for feImage input
555 //It may be a filename or the id for an SVG Element
556 //described in xlink:href syntax
557 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
559 public:
560     FileOrElementChooser(const SPAttributeEnum a)
561         : AttrWidget(a)
562     {
563         pack_start(_entry, false, false);
564         pack_start(_fromFile, false, false);
565         //pack_start(_fromSVGElement, false, false);
567         _fromFile.set_label(_("Image File"));
568         _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
570         _fromSVGElement.set_label(_("Selected SVG Element"));
571         _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
573         _entry.signal_changed().connect(signal_attr_changed().make_slot());
575         show_all();
577     }
579     // Returns the element in xlink:href form.
580     Glib::ustring get_as_attribute() const
581     {
582         return _entry.get_text();
583     }
586     void set_from_attribute(SPObject* o)
587     {
588         const gchar* val = attribute_value(o);
589         if(val) {
590             _entry.set_text(val);
591         }
592     }
594     void set_desktop(SPDesktop* d){
595         _desktop = d;
596     }
598 private:
599     void select_svg_element(){
600         Inkscape::Selection* sel = sp_desktop_selection(_desktop);
601         if (sel->isEmpty()) return;
602         Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
603         if (!node || !node->matchAttributeName("id")) return;
605         std::ostringstream xlikhref;
606         xlikhref << "#(" << node->attribute("id") << ")";
607         _entry.set_text(xlikhref.str());
608     }
610     void select_file(){
612         //# Get the current directory for finding files
613         Glib::ustring open_path;
614         char *attr = (char *)prefs_get_string_attribute("dialogs.open", "path");
615         if (attr)
616             open_path = attr;
618         //# Test if the open_path directory exists
619         if (!Inkscape::IO::file_test(open_path.c_str(),
620                   (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
621             open_path = "";
623         //# If no open path, default to our home directory
624         if (open_path.size() < 1)
625             {
626             open_path = g_get_home_dir();
627             open_path.append(G_DIR_SEPARATOR_S);
628             }
630         //# Create a dialog if we don't already have one
631         if (!selectFeImageFileInstance) {
632             selectFeImageFileInstance =
633                   Inkscape::UI::Dialog::FileOpenDialog::create(
634                      *_desktop->getToplevel(),
635                      open_path,
636                      Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not justy svg*/
637                      (char const *)_("Select an image to be used as feImage input"));
638         }
640         //# Show the dialog
641         bool const success = selectFeImageFileInstance->show();
642         if (!success)
643             return;
645         //# User selected something.  Get name and type
646         Glib::ustring fileName = selectFeImageFileInstance->getFilename();
648         if (fileName.size() > 0) {
650             Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
652             if ( newFileName.size() > 0)
653                 fileName = newFileName;
654             else
655                 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
657             open_path = fileName;
658             open_path.append(G_DIR_SEPARATOR_S);
659             prefs_set_string_attribute("dialogs.open", "path", open_path.c_str());
661             _entry.set_text(fileName);
662         }
663         return;
664     }
666     Gtk::Entry _entry;
667     Gtk::Button _fromFile;
668     Gtk::Button _fromSVGElement;
669     SPDesktop* _desktop;
670 };
672 class FilterEffectsDialog::Settings
674 public:
675     typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
677     Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
678         : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
679     {
680         _groups.resize(_max_types);
681         _attrwidgets.resize(_max_types);
683         for(int i = 0; i < _max_types; ++i) {
684             _groups[i] = new Gtk::VBox;
685             b.add(*_groups[i]);
686         }
687     }
689     ~Settings()
690     {
691         for(int i = 0; i < _max_types; ++i) {
692             delete _groups[i];
693             for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
694                 delete _attrwidgets[i][j];
695         }
696     }
698     // Show the active settings group and update all the AttrWidgets with new values
699     void show_and_update(const int t, SPObject* ob)
700     {
701         if(t != _current_type) {
702             type(t);
703             for(unsigned i = 0; i < _groups.size(); ++i)
704                 _groups[i]->hide();
705         }
706         if(t >= 0)
707             _groups[t]->show_all();
709         _dialog.set_attrs_locked(true);
710         for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
711             _attrwidgets[_current_type][i]->set_from_attribute(ob);
712         _dialog.set_attrs_locked(false);
713     }
715     int get_current_type() const
716     {
717         return _current_type;
718     }
720     void type(const int t)
721     {
722         _current_type = t;
723     }
725     void add_notimplemented()
726     {
727         Gtk::Label* lbl = Gtk::manage(new Gtk::Label("This SVG filter effect is not yet implemented in Inkscape."));
729         add_widget(lbl, "");
730     }
732     // LightSource
733     LightSourceControl* add_lightsource();
735     // CheckBox
736     CheckButtonAttr* add_checkbutton(const SPAttributeEnum attr, const Glib::ustring& label,
737                                      const Glib::ustring& tv, const Glib::ustring& fv)
738     {
739         CheckButtonAttr* cb = new CheckButtonAttr(label, tv, fv, attr);
740         add_widget(cb, "");
741         add_attr_widget(cb);
742         return cb;
743     }
745     // ColorButton
746     ColorButton* add_color(const SPAttributeEnum attr, const Glib::ustring& label)
747     {
748         ColorButton* col = new ColorButton(attr);
749         add_widget(col, label);
750         add_attr_widget(col);
751         return col;
752     }
754     // Matrix
755     MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label)
756     {
757         MatrixAttr* conv = new MatrixAttr(attr);
758         add_widget(conv, label);
759         add_attr_widget(conv);
760         return conv;
761     }
763     // ColorMatrixValues
764     ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
765     {
766         ColorMatrixValues* cmv = new ColorMatrixValues;
767         add_widget(cmv, label);
768         add_attr_widget(cmv);
769         return cmv;
770     }
772     // SpinSlider
773     SpinSlider* add_spinslider(const SPAttributeEnum attr, const Glib::ustring& label,
774                          const double lo, const double hi, const double step_inc, const double climb, const int digits)
775     {
776         SpinSlider* spinslider = new SpinSlider(lo, lo, hi, step_inc, climb, digits, attr);
777         add_widget(spinslider, label);
778         add_attr_widget(spinslider);
779         return spinslider;
780     }
782     // DualSpinSlider
783     DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
784                                        const double lo, const double hi, const double step_inc,
785                                        const double climb, const int digits)
786     {
787         DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr);
788         add_widget(dss, label);
789         add_attr_widget(dss);
790         return dss;
791     }
793     // DualSpinButton
794     DualSpinButton* add_dualspinbutton(const SPAttributeEnum attr, const Glib::ustring& label,
795                                        const double lo, const double hi, const double step_inc,
796                                        const double climb, const int digits)
797     {
798         DualSpinButton* dsb = new DualSpinButton(lo, hi, step_inc, climb, digits, attr);
799         add_widget(dsb, label);
800         add_attr_widget(dsb);
801         return dsb;
802     }
804     // MultiSpinButton
805     MultiSpinButton* add_multispinbutton(const SPAttributeEnum attr1, const SPAttributeEnum attr2,
806                                          const Glib::ustring& label, const double lo, const double hi,
807                                          const double step_inc, const double climb, const int digits)
808     {
809         std::vector<SPAttributeEnum> attrs;
810         attrs.push_back(attr1);
811         attrs.push_back(attr2);
812         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs);
813         add_widget(msb, label);
814         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
815             add_attr_widget(msb->get_spinbuttons()[i]);
816         return msb;
817     }
818     MultiSpinButton* add_multispinbutton(const SPAttributeEnum attr1, const SPAttributeEnum attr2,
819                                          const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
820                                          const double hi, const double step_inc, const double climb, const int digits)
821     {
822         std::vector<SPAttributeEnum> attrs;
823         attrs.push_back(attr1);
824         attrs.push_back(attr2);
825         attrs.push_back(attr3);
826         MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs);
827         add_widget(msb, label);
828         for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
829             add_attr_widget(msb->get_spinbuttons()[i]);
830         return msb;
831     }
833     // FileOrElementChooser
834     FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
835     {
836         FileOrElementChooser* foech = new FileOrElementChooser(attr);
837         foech->set_desktop(_dialog.getDesktop());
838         add_widget(foech, label);
839         add_attr_widget(foech);
840         return foech;
841     }
843     // ComboBoxEnum
844     template<typename T> ComboBoxEnum<T>* add_combo(const SPAttributeEnum attr,
845                                   const Glib::ustring& label,
846                                   const Util::EnumDataConverter<T>& conv)
847     {
848         ComboBoxEnum<T>* combo = new ComboBoxEnum<T>(conv, attr);
849         add_widget(combo, label);
850         add_attr_widget(combo);
851         return combo;
852     }
853 private:
854     void add_attr_widget(AttrWidget* a)
855     {
856         _attrwidgets[_current_type].push_back(a);
857         a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
858     }
860     /* Adds a new settings widget using the specified label. The label will be formatted with a colon
861        and all widgets within the setting group are aligned automatically. */
862     void add_widget(Gtk::Widget* w, const Glib::ustring& label)
863     {
864         Gtk::Label *lbl = 0;
865         Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
866         hb->set_spacing(12);
868         if(label != "") {
869             lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
870             hb->pack_start(*lbl, false, false);
871             _dialog._sizegroup->add_widget(*lbl);
872             lbl->show();
873         }
875         hb->pack_start(*w);
876         _groups[_current_type]->pack_start(*hb);
877         hb->show();
878         w->show();
879     }
881     std::vector<Gtk::VBox*> _groups;
883     FilterEffectsDialog& _dialog;
884     SetAttrSlot _set_attr_slot;
885     std::vector<std::vector<AttrWidget*> > _attrwidgets;
886     int _current_type, _max_types;
887 };
889 // Settings for the three light source objects
890 class FilterEffectsDialog::LightSourceControl : public AttrWidget
892 public:
893     LightSourceControl(FilterEffectsDialog& d)
894         : AttrWidget(SP_ATTR_INVALID),
895           _dialog(d),
896           _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
897           _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
898           _light_source(LightSourceConverter),
899           _locked(false)
900     {
901         _light_box.pack_start(_light_label, false, false);
902         _light_box.pack_start(_light_source);
903         _light_box.show_all();
904         _light_box.set_spacing(12);
905         _dialog._sizegroup->add_widget(_light_label);
907         _box.add(_light_box);
908         _box.reorder_child(_light_box, 0);
909         _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
911         // FIXME: these range values are complete crap
913         _settings.type(LIGHT_DISTANT);
914         _settings.add_spinslider(SP_ATTR_AZIMUTH, _("Azimuth"), 0, 360, 1, 1, 0);
915         _settings.add_spinslider(SP_ATTR_ELEVATION, _("Elevation"), 0, 360, 1, 1, 0);
917         _settings.type(LIGHT_POINT);
918         _settings.add_multispinbutton(SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0);
920         _settings.type(LIGHT_SPOT);
921         _settings.add_multispinbutton(SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0);
922         _settings.add_multispinbutton(SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
923                                       _("Points At"), -99999, 99999, 1, 100, 0);
924         _settings.add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0);
925         _settings.add_spinslider(SP_ATTR_LIMITINGCONEANGLE, _("Cone Angle"), 1, 100, 1, 1, 0);
926     }
928     Gtk::VBox& get_box()
929     {
930         return _box;
931     }
932 protected:
933     Glib::ustring get_as_attribute() const
934     {
935         return "";
936     }
937     void set_from_attribute(SPObject* o)
938     {
939         if(_locked)
940             return;
942         _locked = true;
944         SPObject* child = o->children;
946         if(SP_IS_FEDISTANTLIGHT(child))
947             _light_source.set_active(0);
948         else if(SP_IS_FEPOINTLIGHT(child))
949             _light_source.set_active(1);
950         else if(SP_IS_FESPOTLIGHT(child))
951             _light_source.set_active(2);
952         else
953             _light_source.set_active(-1);
955         update();
957         _locked = false;
958     }
959 private:
960     void on_source_changed()
961     {
962         if(_locked)
963             return;
965         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
966         if(prim) {
967             _locked = true;
969             SPObject* child = prim->children;
970             const int ls = _light_source.get_active_row_number();
971             // Check if the light source type has changed
972             if(!(ls == -1 && !child) &&
973                !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
974                !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
975                !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
976                 if(child)
977                     sp_repr_unparent(child->repr);
979                 if(ls != -1) {
980                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
981                     Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
982                     prim->repr->appendChild(repr);
983                 }
985                 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
986                 update();
987             }
989             _locked = false;
990         }
991     }
993     void update()
994     {
995         _box.hide_all();
996         _box.show();
997         _light_box.show_all();
999         SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1000         if(prim && prim->children)
1001             _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1002     }
1004     FilterEffectsDialog& _dialog;
1005     Gtk::VBox _box;
1006     Settings _settings;
1007     Gtk::HBox _light_box;
1008     Gtk::Label _light_label;
1009     ComboBoxEnum<LightSource> _light_source;
1010     bool _locked;
1011 };
1013 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1015     LightSourceControl* ls = new LightSourceControl(_dialog);
1016     add_attr_widget(ls);
1017     add_widget(&ls->get_box(), "");
1018     return ls;
1021 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1022                                           sigc::slot<void> rem)
1024     Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1026     menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1027     Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1028     menu->append(*mi);
1029     mi->signal_activate().connect(rem);
1030     mi->show();
1031     menu->accelerate(parent);
1033     return menu;
1036 /*** FilterModifier ***/
1037 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1038     : _dialog(d), _add(Gtk::Stock::NEW), _observer(new SignalObserver)
1040     Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1041     pack_start(*sw);
1042     pack_start(_add, false, false);
1043     sw->add(_list);
1045     _model = Gtk::ListStore::create(_columns);
1046     _list.set_model(_model);
1047     _cell_toggle.set_active(true);
1048     const int selcol = _list.append_column("", _cell_toggle);
1049     Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1050     if(col)
1051        col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1052     _list.append_column_editable(_("_Filter"), _columns.label);
1053     ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1054         signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1056     sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1057     sw->set_shadow_type(Gtk::SHADOW_IN);
1058     show_all_children();
1059     _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1060     _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1061     _list.signal_button_release_event().connect_notify(
1062         sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1063     _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1064                               sigc::mem_fun(*this, &FilterModifier::remove_filter));
1065     _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1066                                  _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1067     _menu->accelerate(*this);
1069     _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1070     _observer->signal_changed().connect(signal_filter_changed().make_slot());
1071     g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1072                      G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1074     g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1075                      G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1077     on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1078     update_filters();
1081 FilterEffectsDialog::FilterModifier::~FilterModifier()
1083    _resource_changed.disconnect();
1084    _doc_replaced.disconnect();
1087 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1089     me->_doc_replaced.disconnect();
1090     me->_doc_replaced = desktop->connectDocumentReplaced(
1091         sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1093     me->_resource_changed.disconnect();
1094     me->_resource_changed =
1095         sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1096                                               sigc::mem_fun(me, &FilterModifier::update_filters));
1098     me->_dialog.setDesktop(desktop);
1100     me->update_filters();
1104 // When the selection changes, show the active filter(s) in the dialog
1105 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1106                                                                        Selection *sel,
1107                                                                        FilterModifier* fm)
1109     if(fm && sel)
1110         fm->update_selection(sel);
1113 // Update each filter's sel property based on the current object selection;
1114 //  If the filter is not used by any selected object, sel = 0,
1115 //  otherwise sel is set to the total number of filters in use by selected objects
1116 //  If only one filter is in use, it is selected
1117 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1119     std::set<SPObject*> used;
1121     for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1122         SPObject *obj = SP_OBJECT (i->data);
1123         SPStyle *style = SP_OBJECT_STYLE (obj);
1124         if(!style || !SP_IS_ITEM(obj)) continue;
1126         if(style->filter.set && style->getFilter())
1127             used.insert(style->getFilter());
1128         else
1129             used.insert(0);
1130     }
1132     const int size = used.size();
1134     for(Gtk::TreeIter iter = _model->children().begin();
1135         iter != _model->children().end(); ++iter) {
1136         if(used.find((*iter)[_columns.filter]) != used.end()) {
1137             // If only one filter is in use by the selection, select it
1138             if(size == 1)
1139                 _list.get_selection()->select(iter);
1140             (*iter)[_columns.sel] = size;
1141         }
1142         else
1143             (*iter)[_columns.sel] = 0;
1144     }
1147 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1149     _observer->set(get_selected_filter());
1150     signal_filter_changed()();
1153 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1155     Gtk::TreeModel::iterator iter = _model->get_iter(path);
1157     if(iter) {
1158         SPFilter* filter = (*iter)[_columns.filter];
1159         filter->setLabel(text.c_str());
1160         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1161         if(iter)
1162             (*iter)[_columns.label] = text;
1163     }
1166 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1168     Gtk::TreeIter iter = _model->get_iter(path);
1170     if(iter) {
1171         SPDesktop *desktop = _dialog.getDesktop();
1172         SPDocument *doc = sp_desktop_document(desktop);
1173         SPFilter* filter = (*iter)[_columns.filter];
1174         Inkscape::Selection *sel = sp_desktop_selection(desktop);
1176         /* If this filter is the only one used in the selection, unset it */
1177         if((*iter)[_columns.sel] == 1)
1178             filter = 0;
1180         GSList const *items = sel->itemList();
1182         for (GSList const *i = items; i != NULL; i = i->next) {
1183             SPItem * item = SP_ITEM(i->data);
1184             SPStyle *style = SP_OBJECT_STYLE(item);
1185             g_assert(style != NULL);
1187             if(filter)
1188                 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1189             else
1190                 ::remove_filter(item, false);
1192             SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1193         }
1195         update_selection(sel);
1196         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS,  _("Apply filter"));
1197     }
1200 /* Add all filters in the document to the combobox.
1201    Keeps the same selection if possible, otherwise selects the first element */
1202 void FilterEffectsDialog::FilterModifier::update_filters()
1204     SPDesktop* desktop = _dialog.getDesktop();
1205     SPDocument* document = sp_desktop_document(desktop);
1206     const GSList* filters = sp_document_get_resource_list(document, "filter");
1208     _model->clear();
1210     for(const GSList *l = filters; l; l = l->next) {
1211         Gtk::TreeModel::Row row = *_model->append();
1212         SPFilter* f = (SPFilter*)l->data;
1213         row[_columns.filter] = f;
1214         const gchar* lbl = f->label();
1215         const gchar* id = SP_OBJECT_ID(f);
1216         row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1217     }
1219     update_selection(desktop->selection);
1222 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1224     if(_list.get_selection()) {
1225         Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1227         if(i)
1228             return (*i)[_columns.filter];
1229     }
1231     return 0;
1234 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1236     if(filter) {
1237         for(Gtk::TreeModel::iterator i = _model->children().begin();
1238             i != _model->children().end(); ++i) {
1239             if((*i)[_columns.filter] == filter) {
1240                 _list.get_selection()->select(i);
1241                 break;
1242             }
1243         }
1244     }
1247 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1249     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1250         const bool sensitive = get_selected_filter() != NULL;
1251         _menu->items()[0].set_sensitive(sensitive);
1252         _menu->items()[1].set_sensitive(sensitive);
1253         _menu->popup(event->button, event->time);
1254     }
1257 void FilterEffectsDialog::FilterModifier::add_filter()
1259     SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1260     SPFilter* filter = new_filter(doc);
1262     const int count = _model->children().size();
1263     std::ostringstream os;
1264     os << "filter" << count;
1265     filter->setLabel(os.str().c_str());
1267     update_filters();
1269     select_filter(filter);
1271     sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1274 void FilterEffectsDialog::FilterModifier::remove_filter()
1276     SPFilter *filter = get_selected_filter();
1278     if(filter) {
1279         SPDocument* doc = filter->document;
1280         sp_repr_unparent(filter->repr);
1282         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1284         update_filters();
1285     }
1288 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1290     SPFilter* filter = get_selected_filter();
1292     if(filter) {
1293         Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1294         repr = repr->duplicate(repr->document());
1295         parent->appendChild(repr);
1297         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1299         update_filters();
1300     }
1303 void FilterEffectsDialog::FilterModifier::rename_filter()
1305     _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1308 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1309     : Glib::ObjectBase(typeid(CellRendererConnection)),
1310       _primitive(*this, "primitive", 0)
1311 {}
1313 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1315     return _primitive.get_proxy();
1318 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1320     _text_width = w;
1323 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1325     return _text_width;
1328 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1329     Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1330     int* x_offset, int* y_offset, int* width, int* height) const
1332     PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1334     if(x_offset)
1335         (*x_offset) = 0;
1336     if(y_offset)
1337         (*y_offset) = 0;
1338     if(width)
1339         (*width) = size * primlist.primitive_count() + _text_width * 7;
1340     if(height) {
1341         // Scale the height depending on the number of inputs, unless it's
1342         // the first primitive, in which case there are no connections
1343         SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1344         (*height) = size * input_count(prim);
1345     }
1348 /*** PrimitiveList ***/
1349 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1350     : _dialog(d),
1351       _in_drag(0),
1352       _observer(new SignalObserver)
1354     d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1356     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1357     signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1359     _model = Gtk::ListStore::create(_columns);
1361     set_reorderable(true);
1363     set_model(_model);
1364     append_column(_("_Effect"), _columns.type);
1366     _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1367     get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1368     signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1370     _connection_cell.set_text_width(init_text());
1372     int cols_count = append_column(_("Connections"), _connection_cell);
1373     Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1374     if(col)
1375        col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1378 // Sets up a vertical Pango context/layout, and returns the largest
1379 // width needed to render the FilterPrimitiveInput labels.
1380 int FilterEffectsDialog::PrimitiveList::init_text()
1382     // Set up a vertical context+layout
1383     Glib::RefPtr<Pango::Context> context = create_pango_context();
1384     const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1385     context->set_matrix(matrix);
1386     _vertical_layout = Pango::Layout::create(context);
1388     int maxfont = 0;
1389     for(int i = 0; i < FPInputConverter.end; ++i) {
1390         _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1391         int fontw, fonth;
1392         _vertical_layout->get_pixel_size(fontw, fonth);
1393         if(fonth > maxfont)
1394             maxfont = fonth;
1395     }
1397     return maxfont;
1400 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1402     return _signal_primitive_changed;
1405 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1407     _observer->set(get_selected());
1408     signal_primitive_changed()();
1409     _dialog._color_matrix_values->clear_store();
1412 /* Add all filter primitives in the current to the list.
1413    Keeps the same selection if possible, otherwise selects the first element */
1414 void FilterEffectsDialog::PrimitiveList::update()
1416     SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1417     const SPFilterPrimitive* active_prim = get_selected();
1418     bool active_found = false;
1420     _model->clear();
1422     if(f) {
1423         _dialog._primitive_box.set_sensitive(true);
1425         for(SPObject *prim_obj = f->children;
1426                 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1427                 prim_obj = prim_obj->next) {
1428             SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1429             if(prim) {
1430                 Gtk::TreeModel::Row row = *_model->append();
1431                 row[_columns.primitive] = prim;
1432                 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1433                 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1434                 row[_columns.id] = SP_OBJECT_ID(prim);
1436                 if(prim == active_prim) {
1437                     get_selection()->select(row);
1438                     active_found = true;
1439                 }
1440             }
1441         }
1443         if(!active_found && _model->children().begin())
1444             get_selection()->select(_model->children().begin());
1446         columns_autosize();
1447     }
1448     else {
1449         _dialog._primitive_box.set_sensitive(false);
1450     }
1453 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1455     _primitive_menu = menu;
1458 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1460     if(_dialog._filter_modifier.get_selected_filter()) {
1461         Gtk::TreeModel::iterator i = get_selection()->get_selected();
1462         if(i)
1463             return (*i)[_columns.primitive];
1464     }
1466     return 0;
1469 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1471     for(Gtk::TreeIter i = _model->children().begin();
1472         i != _model->children().end(); ++i) {
1473         if((*i)[_columns.primitive] == prim)
1474             get_selection()->select(i);
1475     }
1478 void FilterEffectsDialog::PrimitiveList::remove_selected()
1480     SPFilterPrimitive* prim = get_selected();
1482     if(prim) {
1483         _observer->set(0);
1485         sp_repr_unparent(prim->repr);
1487         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1488                          _("Remove filter primitive"));
1490         update();
1491     }
1494 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1496     Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1497     Glib::RefPtr<Gdk::Window> win = get_bin_window();
1498     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1500     SPFilterPrimitive* prim = get_selected();
1501     int row_count = get_model()->children().size();
1503     int fheight = CellRendererConnection::size;
1504     Gdk::Rectangle rct, vis;
1505     Gtk::TreeIter row = get_model()->children().begin();
1506     int text_start_x = 0;
1507     if(row) {
1508         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1509         get_visible_rect(vis);
1510         int vis_x, vis_y;
1511         tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1513         text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter.end + 1) + 1;
1514         for(int i = 0; i < FPInputConverter.end; ++i) {
1515             _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1516             const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1517             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());
1518             get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1519             get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1520         }
1521     }
1523     int row_index = 0;
1524     for(; row != get_model()->children().end(); ++row, ++row_index) {
1525         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1526         const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1528         // Check mouse state
1529         int mx, my;
1530         Gdk::ModifierType mask;
1531         get_bin_window()->get_pointer(mx, my, mask);
1533         // Outline the bottom of the connection area
1534         const int outline_x = x + fheight * (row_count - row_index);
1535         get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1537         // Side outline
1538         get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1540         std::vector<Gdk::Point> con_poly;
1541         int con_drag_y;
1542         bool inside;
1543         const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1544         const int inputs = input_count(row_prim);
1546         if(SP_IS_FEMERGE(row_prim)) {
1547             for(int i = 0; i < inputs; ++i) {
1548                 inside = do_connection_node(row, i, con_poly, mx, my);
1549                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1550                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1551                                                inside, con_poly);
1553                 if(_in_drag == (i + 1))
1554                     con_drag_y = con_poly[2].get_y();
1556                 if(_in_drag != (i + 1) || row_prim != prim)
1557                     draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1558             }
1559         }
1560         else {
1561             // Draw "in" shape
1562             inside = do_connection_node(row, 0, con_poly, mx, my);
1563             con_drag_y = con_poly[2].get_y();
1564             get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1565                                            darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1566                                            inside, con_poly);
1568             // Draw "in" connection
1569             if(_in_drag != 1 || row_prim != prim)
1570                 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1572             if(inputs == 2) {
1573                 // Draw "in2" shape
1574                 inside = do_connection_node(row, 1, con_poly, mx, my);
1575                 if(_in_drag == 2)
1576                     con_drag_y = con_poly[2].get_y();
1577                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1578                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1579                                                inside, con_poly);
1580                 // Draw "in2" connection
1581                 if(_in_drag != 2 || row_prim != prim)
1582                     draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1583             }
1584         }
1586         // Draw drag connection
1587         if(row_prim == prim && _in_drag) {
1588             get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1589                                         mx, con_drag_y);
1590             get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1591         }
1592     }
1594     return true;
1597 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1598                                                          const int text_start_x, const int x1, const int y1,
1599                                                          const int row_count)
1601     int src_id = 0;
1602     Gtk::TreeIter res = find_result(input, attr, src_id);
1603     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1604     Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1605     Glib::RefPtr<Gdk::GC> gc;
1607     const bool is_first = input == get_model()->children().begin();
1608     const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1609     const bool use_default = !res && !is_merge;
1611     if(res == input || (use_default && is_first)) {
1612         // Draw straight connection to a standard input
1613         // Draw a lighter line for an implicit connection to a standard input
1614         const int tw = _connection_cell.get_text_width();
1615         gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1616         gc = (use_default && is_first) ? lightgc : darkgc;
1617         get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1618         get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1619     }
1620     else {
1621         // Draw an 'L'-shaped connection to another filter primitive
1622         // If no connection is specified, draw a light connection to the previous primitive
1623         gc = use_default ? lightgc : darkgc;
1625         if(use_default) {
1626             res = input;
1627             --res;
1628         }
1630         if(res) {
1631             Gdk::Rectangle rct;
1633             get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1634             const int fheight = CellRendererConnection::size;
1636             get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1637             const int row_index = find_index(res);
1638             const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1639             const int y2 = rct.get_y() + rct.get_height();
1641             // Draw a bevelled 'L'-shaped connection
1642             get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1643             get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1644             get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1645         }
1646     }
1649 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1650 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1651                                                             std::vector<Gdk::Point>& points,
1652                                                             const int ix, const int iy)
1654     Gdk::Rectangle rct;
1655     const int icnt = input_count((*row)[_columns.primitive]);
1657     get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1658     const int fheight = CellRendererConnection::size;
1660     get_cell_area(_model->get_path(row), *get_column(1), rct);
1661     const float h = rct.get_height() / icnt;
1663     const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1664     const int con_w = (int)(fheight * 0.35f);
1665     const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1666     points.clear();
1667     points.push_back(Gdk::Point(x, con_y));
1668     points.push_back(Gdk::Point(x, con_y + con_w * 2));
1669     points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1671     return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1674 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1675                                                                     const int attr, int& src_id)
1677     SPFilterPrimitive* prim = (*start)[_columns.primitive];
1678     Gtk::TreeIter target = _model->children().end();
1679     int image;
1681     if(SP_IS_FEMERGE(prim)) {
1682         int c = 0;
1683         bool found = false;
1684         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1685             if(c == attr && SP_IS_FEMERGENODE(o)) {
1686                 image = SP_FEMERGENODE(o)->input;
1687                 found = true;
1688             }
1689         }
1690         if(!found)
1691             return target;
1692     }
1693     else {
1694         if(attr == SP_ATTR_IN)
1695             image = prim->image_in;
1696         else if(attr == SP_ATTR_IN2) {
1697             if(SP_IS_FEBLEND(prim))
1698                 image = SP_FEBLEND(prim)->in2;
1699             else if(SP_IS_FECOMPOSITE(prim))
1700                 image = SP_FECOMPOSITE(prim)->in2;
1701             else if(SP_IS_FEDISPLACEMENTMAP(prim))
1702                 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1703             else
1704                 return target;
1705         }
1706         else
1707             return target;
1708     }
1710     if(image >= 0) {
1711         for(Gtk::TreeIter i = _model->children().begin();
1712             i != start; ++i) {
1713             if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1714                 target = i;
1715         }
1716         return target;
1717     }
1718     else if(image < -1) {
1719         src_id = -(image + 2);
1720         return start;
1721     }
1723     return target;
1726 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1728     int i = 0;
1729     for(Gtk::TreeIter iter = _model->children().begin();
1730         iter != target; ++iter, ++i);
1731     return i;
1734 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1736     Gtk::TreePath path;
1737     Gtk::TreeViewColumn* col;
1738     const int x = (int)e->x, y = (int)e->y;
1739     int cx, cy;
1741     _drag_prim = 0;
1743     if(get_path_at_pos(x, y, path, col, cx, cy)) {
1744         Gtk::TreeIter iter = _model->get_iter(path);
1745         std::vector<Gdk::Point> points;
1747         _drag_prim = (*iter)[_columns.primitive];
1748         const int icnt = input_count(_drag_prim);
1750         for(int i = 0; i < icnt; ++i) {
1751             if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1752                 _in_drag = i + 1;
1753                 break;
1754             }
1755         }
1757         queue_draw();
1758     }
1760     if(_in_drag) {
1761         _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1762         _autoscroll = 0;
1763         get_selection()->select(path);
1764         return true;
1765     }
1766     else
1767         return Gtk::TreeView::on_button_press_event(e);
1770 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1772     const int speed = 10;
1773     const int limit = 15;
1775     Gdk::Rectangle vis;
1776     get_visible_rect(vis);
1777     int vis_x, vis_y;
1778     tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1779     const int top = vis_y + vis.get_height();
1781     // When autoscrolling during a connection drag, set the speed based on
1782     // where the mouse is in relation to the edges.
1783     if(e->y < vis_y)
1784         _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1785     else if(e->y < vis_y + limit)
1786         _autoscroll = -speed;
1787     else if(e->y > top)
1788         _autoscroll = (int)(speed + (e->y - top) / 5);
1789     else if(e->y > top - limit)
1790         _autoscroll = speed;
1791     else
1792         _autoscroll = 0;
1794     queue_draw();
1796     return Gtk::TreeView::on_motion_notify_event(e);
1799 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1801     SPFilterPrimitive *prim = get_selected(), *target;
1803     _scroll_connection.disconnect();
1805     if(_in_drag && prim) {
1806         Gtk::TreePath path;
1807         Gtk::TreeViewColumn* col;
1808         int cx, cy;
1810         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1811             const gchar *in_val = 0;
1812             Glib::ustring result;
1813             Gtk::TreeIter target_iter = _model->get_iter(path);
1814             target = (*target_iter)[_columns.primitive];
1815             col = get_column(1);
1817             Gdk::Rectangle rct;
1818             get_cell_area(path, *col, rct);
1819             const int twidth = _connection_cell.get_text_width();
1820             const int sources_x = rct.get_width() - twidth * FPInputConverter.end;
1821             if(cx > sources_x) {
1822                 int src = (cx - sources_x) / twidth;
1823                 if(src < 0)
1824                     src = 0;
1825                 else if(src >= FPInputConverter.end)
1826                     src = FPInputConverter.end - 1;
1827                 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1828                 in_val = result.c_str();
1829             }
1830             else {
1831                 // Ensure that the target comes before the selected primitive
1832                 for(Gtk::TreeIter iter = _model->children().begin();
1833                     iter != get_selection()->get_selected(); ++iter) {
1834                     if(iter == target_iter) {
1835                         Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1836                         // Make sure the target has a result
1837                         const gchar *gres = repr->attribute("result");
1838                         if(!gres) {
1839                             result = "result" + Glib::Ascii::dtostr(SP_FILTER(prim->parent)->_image_number_next);
1840                             repr->setAttribute("result", result.c_str());
1841                             in_val = result.c_str();
1842                         }
1843                         else
1844                             in_val = gres;
1845                         break;
1846                     }
1847                 }
1848             }
1850             if(SP_IS_FEMERGE(prim)) {
1851                 int c = 1;
1852                 bool handled = false;
1853                 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1854                     if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1855                         // If input is null, delete it
1856                         if(!in_val) {
1857                             sp_repr_unparent(o->repr);
1858                             sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1859                                              _("Remove merge node"));
1860                             (*get_selection()->get_selected())[_columns.primitive] = prim;
1861                         }
1862                         else
1863                             _dialog.set_attr(o, SP_ATTR_IN, in_val);
1864                         handled = true;
1865                     }
1866                 }
1867                 // Add new input?
1868                 if(!handled && c == _in_drag && in_val) {
1869                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1870                     Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1871                     repr->setAttribute("inkscape:collect", "always");
1872                     prim->repr->appendChild(repr);
1873                     SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1874                     Inkscape::GC::release(repr);
1875                     _dialog.set_attr(node, SP_ATTR_IN, in_val);
1876                     (*get_selection()->get_selected())[_columns.primitive] = prim;
1877                 }
1878             }
1879             else {
1880                 if(_in_drag == 1)
1881                     _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1882                 else if(_in_drag == 2)
1883                     _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1884             }
1885         }
1887         _in_drag = 0;
1888         queue_draw();
1890         _dialog.update_settings_view();
1891     }
1893     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1894         const bool sensitive = get_selected() != NULL;
1895         _primitive_menu->items()[0].set_sensitive(sensitive);
1896         _primitive_menu->items()[1].set_sensitive(sensitive);
1897         _primitive_menu->popup(e->button, e->time);
1899         return true;
1900     }
1901     else
1902         return Gtk::TreeView::on_button_release_event(e);
1905 // Checks all of prim's inputs, removes any that use result
1906 void check_single_connection(SPFilterPrimitive* prim, const int result)
1908     if(prim && result >= 0) {
1910         if(prim->image_in == result)
1911             SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1913         if(SP_IS_FEBLEND(prim)) {
1914             if(SP_FEBLEND(prim)->in2 == result)
1915                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1916         }
1917         else if(SP_IS_FECOMPOSITE(prim)) {
1918             if(SP_FECOMPOSITE(prim)->in2 == result)
1919                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1920         }
1921         else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
1922             if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
1923                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1924         }
1925     }
1928 // Remove any connections going to/from prim_iter that forward-reference other primitives
1929 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
1931     SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
1932     bool before = true;
1934     for(Gtk::TreeIter iter = _model->children().begin();
1935         iter != _model->children().end(); ++iter) {
1936         if(iter == prim_iter)
1937             before = false;
1938         else {
1939             SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
1940             if(before)
1941                 check_single_connection(cur_prim, prim->image_out);
1942             else
1943                 check_single_connection(prim, cur_prim->image_out);
1944         }
1945     }
1948 // Reorder the filter primitives to match the list order
1949 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
1951     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
1952     int ndx = 0;
1954     for(Gtk::TreeModel::iterator iter = _model->children().begin();
1955         iter != _model->children().end(); ++iter, ++ndx) {
1956         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
1957         if(prim && prim == _drag_prim) {
1958             SP_OBJECT_REPR(prim)->setPosition(ndx);
1959             break;
1960         }
1961     }
1963     for(Gtk::TreeModel::iterator iter = _model->children().begin();
1964         iter != _model->children().end(); ++iter, ++ndx) {
1965         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
1966         if(prim && prim == _drag_prim) {
1967             sanitize_connections(iter);
1968             get_selection()->select(iter);
1969             break;
1970         }
1971     }
1973     filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
1975     sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
1978 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
1979 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
1981     if(_autoscroll) {
1982         Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
1983         double v;
1985         v = a.get_value() + _autoscroll;
1986         if(v < 0)
1987             v = 0;
1988         if(v > a.get_upper() - a.get_page_size())
1989             v = a.get_upper() - a.get_page_size();
1991         a.set_value(v);
1993         queue_draw();
1994     }
1996     return true;
1999 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2001     return _model->children().size();
2004 /*** FilterEffectsDialog ***/
2006 FilterEffectsDialog::FilterEffectsDialog()
2007     : UI::Widget::Panel("", "dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2008       _filter_modifier(*this),
2009       _primitive_list(*this),
2010       _add_primitive_type(FPConverter),
2011       _add_primitive(_("Add Effect:")),
2012       _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2013       _locked(false),
2014       _attr_lock(false)
2016     _settings = new Settings(*this, _settings_box, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2017                              NR_FILTER_ENDPRIMITIVETYPE);
2018     _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2019     _sizegroup->set_ignore_hidden();
2021     _add_primitive_type.remove_row(NR_FILTER_TILE);
2022     _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2024     // Initialize widget hierarchy
2025     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2026     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2027     Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox);
2028     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2029     Gtk::Frame* fr_settings = Gtk::manage(new Gtk::Frame(_("<b>Effect parameters</b>")));
2030     Gtk::Alignment* al_settings = Gtk::manage(new Gtk::Alignment);
2031     _getContents()->add(*hpaned);
2032     hpaned->pack1(_filter_modifier);
2033     hpaned->pack2(_primitive_box);
2034     _primitive_box.pack_start(*sw_prims);
2035     _primitive_box.pack_start(*infobox,false, false);
2036     _primitive_box.pack_start(*hb_prims, false, false);
2037     sw_prims->add(_primitive_list);
2038     infobox->pack_start(_infobox_icon, false, false);
2039     infobox->pack_end(_infobox_desc, false, false);
2040     _infobox_desc.set_line_wrap(true);
2042     hb_prims->pack_end(_add_primitive_type, false, false);
2043     hb_prims->pack_end(_add_primitive, false, false);
2044     _getContents()->pack_start(*fr_settings, false, false);
2045     fr_settings->add(*al_settings);
2046     al_settings->add(_settings_box);
2048     _primitive_list.signal_primitive_changed().connect(
2049         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2050     _filter_modifier.signal_filter_changed().connect(
2051         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2053     _add_primitive_type.signal_changed().connect(
2054         sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2056     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2057     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2058     al_settings->set_padding(0, 0, 12, 0);
2059     fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2060     ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2061     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2062     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2063                                                sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2065     show_all_children();
2066     init_settings_widgets();
2067     _primitive_list.update();
2068     update_primitive_infobox();
2071 FilterEffectsDialog::~FilterEffectsDialog()
2073     delete _settings;
2076 void FilterEffectsDialog::set_attrs_locked(const bool l)
2078     _locked = l;
2081 void FilterEffectsDialog::show_all_vfunc()
2083     UI::Widget::Panel::show_all_vfunc();
2085     update_settings_view();
2088 void FilterEffectsDialog::init_settings_widgets()
2090     // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2091     //       most of the current values are complete guesses!
2093     _empty_settings.set_sensitive(false);
2094     _settings_box.pack_start(_empty_settings);
2096     _settings->type(NR_FILTER_BLEND);
2097     _settings->add_combo(SP_ATTR_MODE, _("Mode"), BlendModeConverter);
2099     _settings->type(NR_FILTER_COLORMATRIX);
2100     ComboBoxEnum<FilterColorMatrixType>* colmat = _settings->add_combo(SP_ATTR_TYPE, _("Type"), ColorMatrixTypeConverter);
2101     _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s)"));
2102     colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2104     _settings->type(NR_FILTER_COMPONENTTRANSFER);
2105     _settings->add_notimplemented();
2106     /*_settings->add_combo(SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2107     _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2108     _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2109     _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2110     _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2111     _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2113     _settings->type(NR_FILTER_COMPOSITE);
2114     _settings->add_combo(SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
2115     _k1 = _settings->add_spinslider(SP_ATTR_K1, _("K1"), -10, 10, 0.1, 0.01, 2);
2116     _k2 = _settings->add_spinslider(SP_ATTR_K2, _("K2"), -10, 10, 0.1, 0.01, 2);
2117     _k3 = _settings->add_spinslider(SP_ATTR_K3, _("K3"), -10, 10, 0.1, 0.01, 2);
2118     _k4 = _settings->add_spinslider(SP_ATTR_K4, _("K4"), -10, 10, 0.1, 0.01, 2);
2120     _settings->type(NR_FILTER_CONVOLVEMATRIX);
2121     _convolve_order = _settings->add_dualspinbutton(SP_ATTR_ORDER, _("Size"), 1, 5, 1, 1, 0);
2122     _convolve_target = _settings->add_multispinbutton(SP_ATTR_TARGETX, SP_ATTR_TARGETY, _("Target"), 0, 4, 1, 1, 0);
2123     _convolve_matrix = _settings->add_matrix(SP_ATTR_KERNELMATRIX, _("Kernel"));
2124     _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2125     _settings->add_spinslider(SP_ATTR_DIVISOR, _("Divisor"), 1, 20, 1, 0.1, 2);
2126     _settings->add_spinslider(SP_ATTR_BIAS, _("Bias"), -10, 10, 1, 0.01, 1);
2127     _settings->add_combo(SP_ATTR_EDGEMODE, _("Edge Mode"), ConvolveMatrixEdgeModeConverter);
2128     _settings->add_checkbutton(SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false");
2130     _settings->type(NR_FILTER_DIFFUSELIGHTING);
2131     _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Diffuse Color"));
2132     _settings->add_spinslider(SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1);
2133     _settings->add_spinslider(SP_ATTR_DIFFUSECONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2);
2134     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2135     _settings->add_lightsource();
2137     _settings->type(NR_FILTER_DISPLACEMENTMAP);
2138     _settings->add_spinslider(SP_ATTR_SCALE, _("Scale"), 0, 100, 1, 0.01, 1);
2139     _settings->add_combo(SP_ATTR_XCHANNELSELECTOR, _("X Channel"), DisplacementMapChannelConverter);
2140     _settings->add_combo(SP_ATTR_YCHANNELSELECTOR, _("Y Channel"), DisplacementMapChannelConverter);
2142     _settings->type(NR_FILTER_FLOOD);
2143     _settings->add_color(SP_PROP_FLOOD_COLOR, _("Flood Color"));
2144     _settings->add_spinslider(SP_PROP_FLOOD_OPACITY, _("Opacity"), 0, 1, 0.1, 0.01, 2);
2146     _settings->type(NR_FILTER_GAUSSIANBLUR);
2147     _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation"), 0.01, 100, 1, 0.01, 1);
2149     _settings->type(NR_FILTER_MORPHOLOGY);
2150     _settings->add_combo(SP_ATTR_OPERATOR, _("Operator"), MorphologyOperatorConverter);
2151     _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius"), 0, 100, 1, 0.01, 1);
2153     _settings->type(NR_FILTER_IMAGE);
2154     _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image"));
2155     _settings->add_multispinbutton(SP_ATTR_X, SP_ATTR_Y, _("Coordinates"), -10000, 10000, 1, 1, 0);
2156     _settings->add_multispinbutton(SP_ATTR_WIDTH, SP_ATTR_HEIGHT, _("Dimensions"), 0, 10000, 1, 1, 0);
2158     _settings->type(NR_FILTER_OFFSET);
2159     _settings->add_spinslider(SP_ATTR_DX, _("Delta X"), -100, 100, 1, 0.01, 1);
2160     _settings->add_spinslider(SP_ATTR_DY, _("Delta Y"), -100, 100, 1, 0.01, 1);
2162     _settings->type(NR_FILTER_SPECULARLIGHTING);
2163     _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Specular Color"));
2164     _settings->add_spinslider(SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1);
2165     _settings->add_spinslider(SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2);
2166     _settings->add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1);
2167     _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2168     _settings->add_lightsource();
2170     _settings->type(NR_FILTER_TILE);
2171     _settings->add_notimplemented();
2173     _settings->type(NR_FILTER_TURBULENCE);
2174     _settings->add_checkbutton(SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2175     _settings->add_combo(SP_ATTR_TYPE, _("Type"), TurbulenceTypeConverter);
2176     _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency"), 0, 1, 0.001, 0.01, 3);
2177     _settings->add_spinslider(SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2178     _settings->add_spinslider(SP_ATTR_SEED, _("Seed"), 0, 1000, 1, 1, 0);
2181 void FilterEffectsDialog::add_primitive()
2183     SPFilter* filter = _filter_modifier.get_selected_filter();
2185     if(filter) {
2186         SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2188         _primitive_list.select(prim);
2190         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2191     }
2194 void FilterEffectsDialog::update_primitive_infobox()
2196     if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2197         _infobox_icon.show();
2198         _infobox_desc.show();
2199     } else {
2200         _infobox_icon.hide();
2201         _infobox_desc.hide();
2202     }
2203     switch(_add_primitive_type.get_active_data()->id){
2204         case(NR::NR_FILTER_BLEND):
2205             _infobox_icon.set(g_strdup_printf("%s/feBlend-icon.png", INKSCAPE_PIXMAPDIR));
2206             _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2207             break;
2208         case(NR::NR_FILTER_COLORMATRIX):
2209             _infobox_icon.set(g_strdup_printf("%s/feColorMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2210             _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."));
2211             break;
2212         case(NR::NR_FILTER_COMPONENTTRANSFER):
2213             _infobox_icon.set(g_strdup_printf("%s/feComponentTransfer-icon.png", INKSCAPE_PIXMAPDIR));
2214             _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."));
2215             break;
2216         case(NR::NR_FILTER_COMPOSITE):
2217             _infobox_icon.set(g_strdup_printf("%s/feComposite-icon.png", INKSCAPE_PIXMAPDIR));
2218             _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."));
2219             break;
2220         case(NR::NR_FILTER_CONVOLVEMATRIX):
2221             _infobox_icon.set(g_strdup_printf("%s/feConvolveMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2222             _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."));
2223             break;
2224         case(NR::NR_FILTER_DIFFUSELIGHTING):
2225             _infobox_icon.set(g_strdup_printf("%s/feDiffuseLighting-icon.png", INKSCAPE_PIXMAPDIR));
2226             _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."));
2227             break;
2228         case(NR::NR_FILTER_DISPLACEMENTMAP):
2229             _infobox_icon.set(g_strdup_printf("%s/feDisplacementMap-icon.png", INKSCAPE_PIXMAPDIR));
2230             _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."));
2231             break;
2232         case(NR::NR_FILTER_FLOOD):
2233             _infobox_icon.set(g_strdup_printf("%s/feFlood-icon.png", INKSCAPE_PIXMAPDIR));
2234             _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."));
2235             break;
2236         case(NR::NR_FILTER_GAUSSIANBLUR):
2237             _infobox_icon.set(g_strdup_printf("%s/feGaussianBlur-icon.png", INKSCAPE_PIXMAPDIR));
2238             _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."));
2239             break;
2240         case(NR::NR_FILTER_IMAGE):
2241             _infobox_icon.set(g_strdup_printf("%s/feImage-icon.png", INKSCAPE_PIXMAPDIR));
2242             _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2243             break;
2244         case(NR::NR_FILTER_MERGE):
2245             _infobox_icon.set(g_strdup_printf("%s/feMerge-icon.png", INKSCAPE_PIXMAPDIR));
2246             _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."));
2247             break;
2248         case(NR::NR_FILTER_MORPHOLOGY):
2249             _infobox_icon.set(g_strdup_printf("%s/feMorphology-icon.png", INKSCAPE_PIXMAPDIR));
2250             _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."));
2251             break;
2252         case(NR::NR_FILTER_OFFSET):
2253             _infobox_icon.set(g_strdup_printf("%s/feOffset-icon.png", INKSCAPE_PIXMAPDIR));
2254             _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."));
2255             break;
2256         case(NR::NR_FILTER_SPECULARLIGHTING):
2257             _infobox_icon.set(g_strdup_printf("%s/feSpecularLighting-icon.png", INKSCAPE_PIXMAPDIR));
2258             _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."));
2259             break;
2260         case(NR::NR_FILTER_TILE):
2261             _infobox_icon.set(g_strdup_printf("%s/feTile-icon.png", INKSCAPE_PIXMAPDIR));
2262             _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2263             break;
2264         case(NR::NR_FILTER_TURBULENCE):
2265             _infobox_icon.set(g_strdup_printf("%s/feTurbulence-icon.png", INKSCAPE_PIXMAPDIR));
2266             _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."));
2267             break;
2268     }
2271 void FilterEffectsDialog::duplicate_primitive()
2273     SPFilter* filter = _filter_modifier.get_selected_filter();
2274     SPFilterPrimitive* origprim = _primitive_list.get_selected();
2276     if(filter && origprim) {
2277         Inkscape::XML::Node *repr;
2278         repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2279         SP_OBJECT_REPR(filter)->appendChild(repr);
2281         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2283         _primitive_list.update();
2284     }
2287 void FilterEffectsDialog::convolve_order_changed()
2289     _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2290     _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2291     _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2294 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2296     set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2299 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2301     set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2304 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2306     if(!_locked) {
2307         _attr_lock = true;
2309         SPFilter *filter = _filter_modifier.get_selected_filter();
2310         const gchar* name = (const gchar*)sp_attribute_name(attr);
2311         if(filter && name && o) {
2312             update_settings_sensitivity();
2314             SP_OBJECT_REPR(o)->setAttribute(name, val);
2315             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2317             Glib::ustring undokey = "filtereffects:";
2318             undokey += name;
2319             sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2320                                    _("Set filter primitive attribute"));
2321         }
2323         _attr_lock = false;
2324     }
2327 void FilterEffectsDialog::update_settings_view()
2329     update_settings_sensitivity();
2331     if(_attr_lock)
2332         return;
2334     _settings_box.hide_all();
2335     _settings_box.show();
2336     _empty_settings.show();
2338     if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2339         _infobox_icon.show();
2340         _infobox_desc.show();
2341     } else {
2342         _infobox_icon.hide();
2343         _infobox_desc.hide();
2344     }
2345     
2346     SPFilterPrimitive* prim = _primitive_list.get_selected();
2348     if(prim) {
2349         _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2350         _empty_settings.hide();
2351     }
2354 void FilterEffectsDialog::update_settings_sensitivity()
2356     SPFilterPrimitive* prim = _primitive_list.get_selected();
2357     const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2358     _k1->set_sensitive(use_k);
2359     _k2->set_sensitive(use_k);
2360     _k3->set_sensitive(use_k);
2361     _k4->set_sensitive(use_k);
2363 // Component transfer not yet implemented
2364 /*
2365     if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2366         SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2367         const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2368         const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2370         _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2371         _ct_slope->set_sensitive(linear);
2372         _ct_intercept->set_sensitive(linear);
2373         _ct_amplitude->set_sensitive(gamma);
2374         _ct_exponent->set_sensitive(gamma);
2375         _ct_offset->set_sensitive(gamma);
2376     }
2377 */
2380 void FilterEffectsDialog::update_color_matrix()
2382     _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2385 } // namespace Dialog
2386 } // namespace UI
2387 } // namespace Inkscape
2389 /*
2390   Local Variables:
2391   mode:c++
2392   c-file-style:"stroustrup"
2393   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2394   indent-tabs-mode:nil
2395   fill-column:99
2396   End:
2397 */
2398 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :