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