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 "selection.h"
38 #include "sp-feblend.h"
39 #include "sp-fecolormatrix.h"
40 #include "sp-fecomponenttransfer.h"
41 #include "sp-fecomposite.h"
42 #include "sp-feconvolvematrix.h"
43 #include "sp-fedisplacementmap.h"
44 #include "sp-fedistantlight.h"
45 #include "sp-femerge.h"
46 #include "sp-femergenode.h"
47 #include "sp-feoffset.h"
48 #include "sp-fepointlight.h"
49 #include "sp-fespotlight.h"
50 #include "sp-filter-primitive.h"
51 #include "sp-gaussian-blur.h"
53 #include "style.h"
54 #include "svg/svg-color.h"
55 #include "verbs.h"
56 #include "xml/node.h"
57 #include "xml/node-observer.h"
58 #include "xml/repr.h"
59 #include <sstream>
61 #include <iostream>
63 using namespace NR;
65 namespace Inkscape {
66 namespace UI {
67 namespace Dialog {
69 // Returns the number of inputs available for the filter primitive type
70 int input_count(const SPFilterPrimitive* prim)
71 {
72 if(!prim)
73 return 0;
74 else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
75 return 2;
76 else if(SP_IS_FEMERGE(prim)) {
77 // Return the number of feMergeNode connections plus an extra
78 int count = 1;
79 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count);
80 return count;
81 }
82 else
83 return 1;
84 }
86 // Very simple observer that just emits a signal if anything happens to a node
87 class FilterEffectsDialog::SignalObserver : public XML::NodeObserver
88 {
89 public:
90 SignalObserver()
91 : _oldsel(0)
92 {}
94 // Add this observer to the SPObject and remove it from any previous object
95 void set(SPObject* o)
96 {
97 if(_oldsel && _oldsel->repr)
98 _oldsel->repr->removeObserver(*this);
99 if(o && o->repr)
100 o->repr->addObserver(*this);
101 _oldsel = o;
102 }
104 void notifyChildAdded(XML::Node&, XML::Node&, XML::Node*)
105 { signal_changed()(); }
107 void notifyChildRemoved(XML::Node&, XML::Node&, XML::Node*)
108 { signal_changed()(); }
110 void notifyChildOrderChanged(XML::Node&, XML::Node&, XML::Node*, XML::Node*)
111 { signal_changed()(); }
113 void notifyContentChanged(XML::Node&, Util::ptr_shared<char>, Util::ptr_shared<char>)
114 {}
116 void notifyAttributeChanged(XML::Node&, GQuark, Util::ptr_shared<char>, Util::ptr_shared<char>)
117 { signal_changed()(); }
119 sigc::signal<void>& signal_changed()
120 {
121 return _signal_changed;
122 }
123 private:
124 sigc::signal<void> _signal_changed;
125 SPObject* _oldsel;
126 };
128 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
129 {
130 public:
131 CheckButtonAttr(const Glib::ustring& label,
132 const Glib::ustring& tv, const Glib::ustring& fv,
133 const SPAttributeEnum a)
134 : Gtk::CheckButton(label),
135 AttrWidget(a),
136 _true_val(tv), _false_val(fv)
137 {
138 signal_toggled().connect(signal_attr_changed().make_slot());
139 }
141 Glib::ustring get_as_attribute() const
142 {
143 return get_active() ? _true_val : _false_val;
144 }
146 void set_from_attribute(SPObject* o)
147 {
148 const gchar* val = attribute_value(o);
149 if(val) {
150 if(_true_val == val)
151 set_active(true);
152 else if(_false_val == val)
153 set_active(false);
154 }
155 }
156 private:
157 const Glib::ustring _true_val, _false_val;
158 };
160 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
161 {
162 public:
163 SpinButtonAttr(double lower, double upper, double step_inc,
164 double climb_rate, int digits, const SPAttributeEnum a)
165 : Gtk::SpinButton(climb_rate, digits),
166 AttrWidget(a)
167 {
168 set_range(lower, upper);
169 set_increments(step_inc, step_inc * 5);
171 signal_value_changed().connect(signal_attr_changed().make_slot());
172 }
174 Glib::ustring get_as_attribute() const
175 {
176 const double val = get_value();
178 if(get_digits() == 0)
179 return Glib::Ascii::dtostr((int)val);
180 else
181 return Glib::Ascii::dtostr(val);
182 }
184 void set_from_attribute(SPObject* o)
185 {
186 const gchar* val = attribute_value(o);
187 if(val)
188 set_value(Glib::Ascii::strtod(val));
189 }
190 };
192 // Contains an arbitrary number of spin buttons that use seperate attributes
193 class MultiSpinButton : public Gtk::HBox
194 {
195 public:
196 MultiSpinButton(double lower, double upper, double step_inc,
197 double climb_rate, int digits, std::vector<SPAttributeEnum> attrs)
198 {
199 for(unsigned i = 0; i < attrs.size(); ++i) {
200 _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i]));
201 pack_start(*_spins.back(), false, false);
202 }
203 }
205 ~MultiSpinButton()
206 {
207 for(unsigned i = 0; i < _spins.size(); ++i)
208 delete _spins[i];
209 }
211 std::vector<SpinButtonAttr*>& get_spinbuttons()
212 {
213 return _spins;
214 }
215 private:
216 std::vector<SpinButtonAttr*> _spins;
217 };
219 // Contains two spinbuttons that describe a NumberOptNumber
220 class DualSpinButton : public Gtk::HBox, public AttrWidget
221 {
222 public:
223 DualSpinButton(double lower, double upper, double step_inc,
224 double climb_rate, int digits, const SPAttributeEnum a)
225 : AttrWidget(a),
226 _s1(climb_rate, digits), _s2(climb_rate, digits)
227 {
228 _s1.set_range(lower, upper);
229 _s2.set_range(lower, upper);
230 _s1.set_increments(step_inc, step_inc * 5);
231 _s2.set_increments(step_inc, step_inc * 5);
233 _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
234 _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
236 pack_start(_s1, false, false);
237 pack_start(_s2, false, false);
238 }
240 Gtk::SpinButton& get_spinbutton1()
241 {
242 return _s1;
243 }
245 Gtk::SpinButton& get_spinbutton2()
246 {
247 return _s2;
248 }
250 virtual Glib::ustring get_as_attribute() const
251 {
252 double v1 = _s1.get_value();
253 double v2 = _s2.get_value();
255 if(_s1.get_digits() == 0) {
256 v1 = (int)v1;
257 v2 = (int)v2;
258 }
260 return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
261 }
263 virtual void set_from_attribute(SPObject* o)
264 {
265 const gchar* val = attribute_value(o);
266 if(val) {
267 NumberOptNumber n;
268 n.set(val);
269 _s1.set_value(n.getNumber());
270 _s2.set_value(n.getOptNumber());
271 }
272 }
273 private:
274 Gtk::SpinButton _s1, _s2;
275 };
277 class ColorButton : public Gtk::ColorButton, public AttrWidget
278 {
279 public:
280 ColorButton(const SPAttributeEnum a)
281 : AttrWidget(a)
282 {
283 signal_color_set().connect(signal_attr_changed().make_slot());
285 Gdk::Color col;
286 col.set_rgb(65535, 65535, 65535);
287 set_color(col);
288 }
290 // Returns the color in 'rgb(r,g,b)' form.
291 Glib::ustring get_as_attribute() const
292 {
293 std::ostringstream os;
294 const Gdk::Color c = get_color();
295 const int r = c.get_red() / 257, g = c.get_green() / 257, b = c.get_blue() / 257;
296 os << "rgb(" << r << "," << g << "," << b << ")";
297 return os.str();
298 }
301 void set_from_attribute(SPObject* o)
302 {
303 const gchar* val = attribute_value(o);
304 if(val) {
305 const guint32 i = sp_svg_read_color(val, 0xFFFFFFFF);
306 const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
307 Gdk::Color col;
308 col.set_rgb(r * 257, g * 257, b * 257);
309 set_color(col);
310 }
311 }
312 };
314 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
315 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
316 {
317 public:
318 MatrixAttr(const SPAttributeEnum a)
319 : AttrWidget(a), _locked(false)
320 {
321 _model = Gtk::ListStore::create(_columns);
322 _tree.set_model(_model);
323 _tree.set_headers_visible(false);
324 _tree.show();
325 add(_tree);
326 set_shadow_type(Gtk::SHADOW_IN);
327 }
329 std::vector<double> get_values() const
330 {
331 std::vector<double> vec;
332 for(Gtk::TreeIter iter = _model->children().begin();
333 iter != _model->children().end(); ++iter) {
334 for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
335 vec.push_back((*iter)[_columns.cols[c]]);
336 }
337 return vec;
338 }
340 void set_values(const std::vector<double>& v)
341 {
342 unsigned i = 0;
343 for(Gtk::TreeIter iter = _model->children().begin();
344 iter != _model->children().end(); ++iter) {
345 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
346 if(i >= v.size())
347 return;
348 (*iter)[_columns.cols[c]] = v[i];
349 ++i;
350 }
351 }
352 }
354 Glib::ustring get_as_attribute() const
355 {
356 std::ostringstream os;
358 for(Gtk::TreeIter iter = _model->children().begin();
359 iter != _model->children().end(); ++iter) {
360 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
361 os << (*iter)[_columns.cols[c]] << " ";
362 }
363 }
365 return os.str();
366 }
368 void set_from_attribute(SPObject* o)
369 {
370 if(o) {
371 if(SP_IS_FECONVOLVEMATRIX(o)) {
372 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
373 int cols, rows;
374 cols = (int)conv->order.getNumber();
375 if(cols > 5)
376 cols = 5;
377 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
378 update(o, rows, cols);
379 }
380 else if(SP_IS_FECOLORMATRIX(o))
381 update(o, 4, 5);
382 }
383 }
384 private:
385 class MatrixColumns : public Gtk::TreeModel::ColumnRecord
386 {
387 public:
388 MatrixColumns()
389 {
390 cols.resize(5);
391 for(unsigned i = 0; i < cols.size(); ++i)
392 add(cols[i]);
393 }
394 std::vector<Gtk::TreeModelColumn<double> > cols;
395 };
397 void update(SPObject* o, const int rows, const int cols)
398 {
399 if(_locked)
400 return;
402 _model->clear();
404 _tree.remove_all_columns();
406 std::vector<gdouble>* values = NULL;
407 if(SP_IS_FECOLORMATRIX(o))
408 values = &SP_FECOLORMATRIX(o)->values;
409 else if(SP_IS_FECONVOLVEMATRIX(o))
410 values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
411 else
412 return;
414 if(o) {
415 int ndx = 0;
417 for(int i = 0; i < cols; ++i) {
418 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
419 dynamic_cast<Gtk::CellRendererText*>(
420 _tree.get_column_cell_renderer(i))->signal_edited().connect(
421 sigc::mem_fun(*this, &MatrixAttr::rebind));
422 }
424 for(int r = 0; r < rows; ++r) {
425 Gtk::TreeRow row = *(_model->append());
426 // Default to identity matrix
427 for(int c = 0; c < cols; ++c, ++ndx)
428 row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
429 }
430 }
431 }
433 void rebind(const Glib::ustring&, const Glib::ustring&)
434 {
435 _locked = true;
436 signal_attr_changed()();
437 _locked = false;
438 }
440 bool _locked;
441 Gtk::TreeView _tree;
442 Glib::RefPtr<Gtk::ListStore> _model;
443 MatrixColumns _columns;
444 };
446 // Displays a matrix or a slider for feColorMatrix
447 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
448 {
449 public:
450 ColorMatrixValues()
451 : AttrWidget(SP_ATTR_VALUES),
452 _matrix(SP_ATTR_VALUES),
453 _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
454 _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
455 _label(_("None"), Gtk::ALIGN_LEFT),
456 _use_stored(false),
457 _saturation_store(0),
458 _angle_store(0)
459 {
460 _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
461 _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
462 _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
463 signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
465 _matrix.show();
466 _saturation.show();
467 _angle.show();
468 _label.show();
469 _label.set_sensitive(false);
471 set_shadow_type(Gtk::SHADOW_NONE);
472 }
474 virtual void set_from_attribute(SPObject* o)
475 {
476 if(SP_IS_FECOLORMATRIX(o)) {
477 SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
478 remove();
479 switch(col->type) {
480 case COLORMATRIX_SATURATE:
481 add(_saturation);
482 if(_use_stored)
483 _saturation.set_value(_saturation_store);
484 else
485 _saturation.set_from_attribute(o);
486 break;
487 case COLORMATRIX_HUEROTATE:
488 add(_angle);
489 if(_use_stored)
490 _angle.set_value(_angle_store);
491 else
492 _angle.set_from_attribute(o);
493 break;
494 case COLORMATRIX_LUMINANCETOALPHA:
495 add(_label);
496 break;
497 case COLORMATRIX_MATRIX:
498 default:
499 add(_matrix);
500 if(_use_stored)
501 _matrix.set_values(_matrix_store);
502 else
503 _matrix.set_from_attribute(o);
504 break;
505 }
506 _use_stored = true;
507 }
508 }
510 virtual Glib::ustring get_as_attribute() const
511 {
512 const Widget* w = get_child();
513 if(w == &_label)
514 return "";
515 else
516 return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
517 }
519 void clear_store()
520 {
521 _use_stored = false;
522 }
523 private:
524 void update_store()
525 {
526 const Widget* w = get_child();
527 if(w == &_matrix)
528 _matrix_store = _matrix.get_values();
529 else if(w == &_saturation)
530 _saturation_store = _saturation.get_value();
531 else if(w == &_angle)
532 _angle_store = _angle.get_value();
533 }
535 MatrixAttr _matrix;
536 SpinSlider _saturation;
537 SpinSlider _angle;
538 Gtk::Label _label;
540 // Store separate values for the different color modes
541 bool _use_stored;
542 std::vector<double> _matrix_store;
543 double _saturation_store;
544 double _angle_store;
545 };
547 class FilterEffectsDialog::Settings
548 {
549 public:
550 typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
552 Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
553 : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
554 {
555 _groups.resize(_max_types);
556 _attrwidgets.resize(_max_types);
558 for(int i = 0; i < _max_types; ++i) {
559 _groups[i] = new Gtk::VBox;
560 b.add(*_groups[i]);
561 }
562 }
564 ~Settings()
565 {
566 for(int i = 0; i < _max_types; ++i) {
567 delete _groups[i];
568 for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
569 delete _attrwidgets[i][j];
570 }
571 }
573 // Show the active settings group and update all the AttrWidgets with new values
574 void show_and_update(const int t, SPObject* ob)
575 {
576 if(t != _current_type) {
577 type(t);
578 for(unsigned i = 0; i < _groups.size(); ++i)
579 _groups[i]->hide();
580 }
581 if(t >= 0)
582 _groups[t]->show_all();
584 _dialog.set_attrs_locked(true);
585 for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
586 _attrwidgets[_current_type][i]->set_from_attribute(ob);
587 _dialog.set_attrs_locked(false);
588 }
590 int get_current_type() const
591 {
592 return _current_type;
593 }
595 void type(const int t)
596 {
597 _current_type = t;
598 }
600 void add_notimplemented()
601 {
602 Gtk::Label* lbl = Gtk::manage(new Gtk::Label("This SVG filter effect is not yet implemented in Inkscape."));
604 add_widget(lbl, "");
605 }
607 // LightSource
608 LightSourceControl* add_lightsource();
610 // CheckBox
611 CheckButtonAttr* add_checkbutton(const SPAttributeEnum attr, const Glib::ustring& label,
612 const Glib::ustring& tv, const Glib::ustring& fv)
613 {
614 CheckButtonAttr* cb = new CheckButtonAttr(label, tv, fv, attr);
615 add_widget(cb, "");
616 add_attr_widget(cb);
617 return cb;
618 }
620 // ColorButton
621 ColorButton* add_color(const SPAttributeEnum attr, const Glib::ustring& label)
622 {
623 ColorButton* col = new ColorButton(attr);
624 add_widget(col, label);
625 add_attr_widget(col);
626 return col;
627 }
629 // Matrix
630 MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label)
631 {
632 MatrixAttr* conv = new MatrixAttr(attr);
633 add_widget(conv, label);
634 add_attr_widget(conv);
635 return conv;
636 }
638 // ColorMatrixValues
639 ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
640 {
641 ColorMatrixValues* cmv = new ColorMatrixValues;
642 add_widget(cmv, label);
643 add_attr_widget(cmv);
644 return cmv;
645 }
647 // SpinSlider
648 SpinSlider* add_spinslider(const SPAttributeEnum attr, const Glib::ustring& label,
649 const double lo, const double hi, const double step_inc, const double climb, const int digits)
650 {
651 SpinSlider* spinslider = new SpinSlider(lo, lo, hi, step_inc, climb, digits, attr);
652 add_widget(spinslider, label);
653 add_attr_widget(spinslider);
654 return spinslider;
655 }
657 // DualSpinSlider
658 DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
659 const double lo, const double hi, const double step_inc,
660 const double climb, const int digits)
661 {
662 DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr);
663 add_widget(dss, label);
664 add_attr_widget(dss);
665 return dss;
666 }
668 // DualSpinButton
669 DualSpinButton* add_dualspinbutton(const SPAttributeEnum attr, const Glib::ustring& label,
670 const double lo, const double hi, const double step_inc,
671 const double climb, const int digits)
672 {
673 DualSpinButton* dsb = new DualSpinButton(lo, hi, step_inc, climb, digits, attr);
674 add_widget(dsb, label);
675 add_attr_widget(dsb);
676 return dsb;
677 }
679 // MultiSpinButton
680 MultiSpinButton* add_multispinbutton(const SPAttributeEnum attr1, const SPAttributeEnum attr2,
681 const Glib::ustring& label, const double lo, const double hi,
682 const double step_inc, const double climb, const int digits)
683 {
684 std::vector<SPAttributeEnum> attrs;
685 attrs.push_back(attr1);
686 attrs.push_back(attr2);
687 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs);
688 add_widget(msb, label);
689 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
690 add_attr_widget(msb->get_spinbuttons()[i]);
691 return msb;
692 }
693 MultiSpinButton* add_multispinbutton(const SPAttributeEnum attr1, const SPAttributeEnum attr2,
694 const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
695 const double hi, const double step_inc, const double climb, const int digits)
696 {
697 std::vector<SPAttributeEnum> attrs;
698 attrs.push_back(attr1);
699 attrs.push_back(attr2);
700 attrs.push_back(attr3);
701 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs);
702 add_widget(msb, label);
703 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
704 add_attr_widget(msb->get_spinbuttons()[i]);
705 return msb;
706 }
708 // ComboBoxEnum
709 template<typename T> ComboBoxEnum<T>* add_combo(const SPAttributeEnum attr,
710 const Glib::ustring& label,
711 const Util::EnumDataConverter<T>& conv)
712 {
713 ComboBoxEnum<T>* combo = new ComboBoxEnum<T>(conv, attr);
714 add_widget(combo, label);
715 add_attr_widget(combo);
716 return combo;
717 }
718 private:
719 void add_attr_widget(AttrWidget* a)
720 {
721 _attrwidgets[_current_type].push_back(a);
722 a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
723 }
725 /* Adds a new settings widget using the specified label. The label will be formatted with a colon
726 and all widgets within the setting group are aligned automatically. */
727 void add_widget(Gtk::Widget* w, const Glib::ustring& label)
728 {
729 Gtk::Label *lbl = 0;
730 Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
731 hb->set_spacing(12);
733 if(label != "") {
734 lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
735 hb->pack_start(*lbl, false, false);
736 _dialog._sizegroup->add_widget(*lbl);
737 lbl->show();
738 }
740 hb->pack_start(*w);
741 _groups[_current_type]->pack_start(*hb);
742 hb->show();
743 w->show();
744 }
746 std::vector<Gtk::VBox*> _groups;
748 FilterEffectsDialog& _dialog;
749 SetAttrSlot _set_attr_slot;
750 std::vector<std::vector<AttrWidget*> > _attrwidgets;
751 int _current_type, _max_types;
752 };
754 // Settings for the three light source objects
755 class FilterEffectsDialog::LightSourceControl : public AttrWidget
756 {
757 public:
758 LightSourceControl(FilterEffectsDialog& d)
759 : AttrWidget(SP_ATTR_INVALID),
760 _dialog(d),
761 _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
762 _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
763 _light_source(LightSourceConverter),
764 _locked(false)
765 {
766 _light_box.pack_start(_light_label, false, false);
767 _light_box.pack_start(_light_source);
768 _light_box.show_all();
769 _light_box.set_spacing(12);
770 _dialog._sizegroup->add_widget(_light_label);
772 _box.add(_light_box);
773 _box.reorder_child(_light_box, 0);
774 _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
776 // FIXME: these range values are complete crap
778 _settings.type(LIGHT_DISTANT);
779 _settings.add_spinslider(SP_ATTR_AZIMUTH, _("Azimuth"), 0, 360, 1, 1, 0);
780 _settings.add_spinslider(SP_ATTR_ELEVATION, _("Elevation"), 0, 360, 1, 1, 0);
782 _settings.type(LIGHT_POINT);
783 _settings.add_multispinbutton(SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0);
785 _settings.type(LIGHT_SPOT);
786 _settings.add_multispinbutton(SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0);
787 _settings.add_multispinbutton(SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
788 _("Points At"), -99999, 99999, 1, 100, 0);
789 _settings.add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0);
790 _settings.add_spinslider(SP_ATTR_LIMITINGCONEANGLE, _("Cone Angle"), 1, 100, 1, 1, 0);
791 }
793 Gtk::VBox& get_box()
794 {
795 return _box;
796 }
797 protected:
798 Glib::ustring get_as_attribute() const
799 {
800 return "";
801 }
802 void set_from_attribute(SPObject* o)
803 {
804 if(_locked)
805 return;
807 _locked = true;
809 SPObject* child = o->children;
811 if(SP_IS_FEDISTANTLIGHT(child))
812 _light_source.set_active(0);
813 else if(SP_IS_FEPOINTLIGHT(child))
814 _light_source.set_active(1);
815 else if(SP_IS_FESPOTLIGHT(child))
816 _light_source.set_active(2);
817 else
818 _light_source.set_active(-1);
820 update();
822 _locked = false;
823 }
824 private:
825 void on_source_changed()
826 {
827 if(_locked)
828 return;
830 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
831 if(prim) {
832 _locked = true;
834 SPObject* child = prim->children;
835 const int ls = _light_source.get_active_row_number();
836 // Check if the light source type has changed
837 if(!(ls == -1 && !child) &&
838 !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
839 !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
840 !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
841 if(child)
842 sp_repr_unparent(child->repr);
844 if(ls != -1) {
845 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
846 Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
847 prim->repr->appendChild(repr);
848 }
850 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
851 update();
852 }
854 _locked = false;
855 }
856 }
858 void update()
859 {
860 _box.hide_all();
861 _box.show();
862 _light_box.show_all();
864 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
865 if(prim && prim->children)
866 _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
867 }
869 FilterEffectsDialog& _dialog;
870 Gtk::VBox _box;
871 Settings _settings;
872 Gtk::HBox _light_box;
873 Gtk::Label _light_label;
874 ComboBoxEnum<LightSource> _light_source;
875 bool _locked;
876 };
878 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
879 {
880 LightSourceControl* ls = new LightSourceControl(_dialog);
881 add_attr_widget(ls);
882 add_widget(&ls->get_box(), "");
883 return ls;
884 }
886 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
887 sigc::slot<void> rem)
888 {
889 Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
891 menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
892 Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
893 menu->append(*mi);
894 mi->signal_activate().connect(rem);
895 mi->show();
896 menu->accelerate(parent);
898 return menu;
899 }
901 /*** FilterModifier ***/
902 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
903 : _dialog(d), _add(Gtk::Stock::NEW), _observer(new SignalObserver)
904 {
905 Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
906 pack_start(*sw);
907 pack_start(_add, false, false);
908 sw->add(_list);
910 _model = Gtk::ListStore::create(_columns);
911 _list.set_model(_model);
912 _cell_toggle.set_active(true);
913 const int selcol = _list.append_column("", _cell_toggle);
914 Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
915 if(col)
916 col->add_attribute(_cell_toggle.property_active(), _columns.sel);
917 _list.append_column_editable(_("_Filter"), _columns.label);
918 ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
919 signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
921 sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
922 sw->set_shadow_type(Gtk::SHADOW_IN);
923 show_all_children();
924 _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
925 _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
926 _list.signal_button_release_event().connect_notify(
927 sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
928 _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
929 sigc::mem_fun(*this, &FilterModifier::remove_filter));
930 _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
931 _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
932 _menu->accelerate(*this);
934 _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
935 _observer->signal_changed().connect(signal_filter_changed().make_slot());
936 g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
937 G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
939 g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
940 G_CALLBACK(&FilterModifier::on_activate_desktop), this);
942 on_activate_desktop(INKSCAPE, d.getDesktop(), this);
943 update_filters();
944 }
946 FilterEffectsDialog::FilterModifier::~FilterModifier()
947 {
948 _resource_changed.disconnect();
949 _doc_replaced.disconnect();
950 }
952 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
953 {
954 me->_doc_replaced.disconnect();
955 me->_doc_replaced = desktop->connectDocumentReplaced(
956 sigc::mem_fun(me, &FilterModifier::on_document_replaced));
958 me->_resource_changed.disconnect();
959 me->_resource_changed =
960 sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
961 sigc::mem_fun(me, &FilterModifier::update_filters));
963 me->_dialog.setDesktop(desktop);
965 me->update_filters();
966 }
969 // When the selection changes, show the active filter(s) in the dialog
970 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
971 Selection *sel,
972 FilterModifier* fm)
973 {
974 if(fm && sel)
975 fm->update_selection(sel);
976 }
978 // Update each filter's sel property based on the current object selection;
979 // If the filter is not used by any selected object, sel = 0,
980 // otherwise sel is set to the total number of filters in use by selected objects
981 // If only one filter is in use, it is selected
982 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
983 {
984 std::set<SPObject*> used;
986 for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
987 SPObject *obj = SP_OBJECT (i->data);
988 SPStyle *style = SP_OBJECT_STYLE (obj);
989 if(!style || !SP_IS_ITEM(obj)) continue;
991 if(style->filter.set && style->getFilter())
992 used.insert(style->getFilter());
993 else
994 used.insert(0);
995 }
997 const int size = used.size();
999 for(Gtk::TreeIter iter = _model->children().begin();
1000 iter != _model->children().end(); ++iter) {
1001 if(used.find((*iter)[_columns.filter]) != used.end()) {
1002 // If only one filter is in use by the selection, select it
1003 if(size == 1)
1004 _list.get_selection()->select(iter);
1005 (*iter)[_columns.sel] = size;
1006 }
1007 else
1008 (*iter)[_columns.sel] = 0;
1009 }
1010 }
1012 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1013 {
1014 _observer->set(get_selected_filter());
1015 signal_filter_changed()();
1016 }
1018 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1019 {
1020 Gtk::TreeModel::iterator iter = _model->get_iter(path);
1022 if(iter) {
1023 SPFilter* filter = (*iter)[_columns.filter];
1024 filter->setLabel(text.c_str());
1025 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1026 if(iter)
1027 (*iter)[_columns.label] = text;
1028 }
1029 }
1031 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1032 {
1033 Gtk::TreeIter iter = _model->get_iter(path);
1035 if(iter) {
1036 SPDesktop *desktop = _dialog.getDesktop();
1037 SPDocument *doc = sp_desktop_document(desktop);
1038 SPFilter* filter = (*iter)[_columns.filter];
1039 Inkscape::Selection *sel = sp_desktop_selection(desktop);
1041 /* If this filter is the only one used in the selection, unset it */
1042 if((*iter)[_columns.sel] == 1)
1043 filter = 0;
1045 GSList const *items = sel->itemList();
1047 for (GSList const *i = items; i != NULL; i = i->next) {
1048 SPItem * item = SP_ITEM(i->data);
1049 SPStyle *style = SP_OBJECT_STYLE(item);
1050 g_assert(style != NULL);
1052 if(filter)
1053 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1054 else
1055 ::remove_filter(item, false);
1057 SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1058 }
1060 update_selection(sel);
1061 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Apply filter"));
1062 }
1063 }
1065 /* Add all filters in the document to the combobox.
1066 Keeps the same selection if possible, otherwise selects the first element */
1067 void FilterEffectsDialog::FilterModifier::update_filters()
1068 {
1069 SPDesktop* desktop = _dialog.getDesktop();
1070 SPDocument* document = sp_desktop_document(desktop);
1071 const GSList* filters = sp_document_get_resource_list(document, "filter");
1073 _model->clear();
1075 for(const GSList *l = filters; l; l = l->next) {
1076 Gtk::TreeModel::Row row = *_model->append();
1077 SPFilter* f = (SPFilter*)l->data;
1078 row[_columns.filter] = f;
1079 const gchar* lbl = f->label();
1080 const gchar* id = SP_OBJECT_ID(f);
1081 row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1082 }
1084 update_selection(desktop->selection);
1085 }
1087 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1088 {
1089 if(_list.get_selection()) {
1090 Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1092 if(i)
1093 return (*i)[_columns.filter];
1094 }
1096 return 0;
1097 }
1099 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1100 {
1101 if(filter) {
1102 for(Gtk::TreeModel::iterator i = _model->children().begin();
1103 i != _model->children().end(); ++i) {
1104 if((*i)[_columns.filter] == filter) {
1105 _list.get_selection()->select(i);
1106 break;
1107 }
1108 }
1109 }
1110 }
1112 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1113 {
1114 if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1115 const bool sensitive = get_selected_filter() != NULL;
1116 _menu->items()[0].set_sensitive(sensitive);
1117 _menu->items()[1].set_sensitive(sensitive);
1118 _menu->popup(event->button, event->time);
1119 }
1120 }
1122 void FilterEffectsDialog::FilterModifier::add_filter()
1123 {
1124 SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1125 SPFilter* filter = new_filter(doc);
1127 const int count = _model->children().size();
1128 std::ostringstream os;
1129 os << "filter" << count;
1130 filter->setLabel(os.str().c_str());
1132 update_filters();
1134 select_filter(filter);
1136 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1137 }
1139 void FilterEffectsDialog::FilterModifier::remove_filter()
1140 {
1141 SPFilter *filter = get_selected_filter();
1143 if(filter) {
1144 SPDocument* doc = filter->document;
1145 sp_repr_unparent(filter->repr);
1147 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1149 update_filters();
1150 }
1151 }
1153 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1154 {
1155 SPFilter* filter = get_selected_filter();
1157 if(filter) {
1158 Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1159 repr = repr->duplicate(repr->document());
1160 parent->appendChild(repr);
1162 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1164 update_filters();
1165 }
1166 }
1168 void FilterEffectsDialog::FilterModifier::rename_filter()
1169 {
1170 _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1171 }
1173 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1174 : Glib::ObjectBase(typeid(CellRendererConnection)),
1175 _primitive(*this, "primitive", 0)
1176 {}
1178 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1179 {
1180 return _primitive.get_proxy();
1181 }
1183 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1184 {
1185 _text_width = w;
1186 }
1188 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1189 {
1190 return _text_width;
1191 }
1193 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1194 Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1195 int* x_offset, int* y_offset, int* width, int* height) const
1196 {
1197 PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1199 if(x_offset)
1200 (*x_offset) = 0;
1201 if(y_offset)
1202 (*y_offset) = 0;
1203 if(width)
1204 (*width) = size * primlist.primitive_count() + _text_width * 7;
1205 if(height) {
1206 // Scale the height depending on the number of inputs, unless it's
1207 // the first primitive, in which case there are no connections
1208 SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1209 (*height) = size * input_count(prim);
1210 }
1211 }
1213 /*** PrimitiveList ***/
1214 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1215 : _dialog(d),
1216 _in_drag(0),
1217 _observer(new SignalObserver)
1218 {
1219 d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1221 add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1222 signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1224 _model = Gtk::ListStore::create(_columns);
1226 set_reorderable(true);
1228 set_model(_model);
1229 append_column(_("_Effect"), _columns.type);
1231 _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1232 get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1233 signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1235 _connection_cell.set_text_width(init_text());
1237 int cols_count = append_column(_("Connections"), _connection_cell);
1238 Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1239 if(col)
1240 col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1241 }
1243 // Sets up a vertical Pango context/layout, and returns the largest
1244 // width needed to render the FilterPrimitiveInput labels.
1245 int FilterEffectsDialog::PrimitiveList::init_text()
1246 {
1247 // Set up a vertical context+layout
1248 Glib::RefPtr<Pango::Context> context = create_pango_context();
1249 const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1250 context->set_matrix(matrix);
1251 _vertical_layout = Pango::Layout::create(context);
1253 int maxfont = 0;
1254 for(int i = 0; i < FPInputConverter.end; ++i) {
1255 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1256 int fontw, fonth;
1257 _vertical_layout->get_pixel_size(fontw, fonth);
1258 if(fonth > maxfont)
1259 maxfont = fonth;
1260 }
1262 return maxfont;
1263 }
1265 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1266 {
1267 return _signal_primitive_changed;
1268 }
1270 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1271 {
1272 _observer->set(get_selected());
1273 signal_primitive_changed()();
1274 _dialog._color_matrix_values->clear_store();
1275 }
1277 /* Add all filter primitives in the current to the list.
1278 Keeps the same selection if possible, otherwise selects the first element */
1279 void FilterEffectsDialog::PrimitiveList::update()
1280 {
1281 SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1282 const SPFilterPrimitive* active_prim = get_selected();
1283 bool active_found = false;
1285 _model->clear();
1287 if(f) {
1288 _dialog._primitive_box.set_sensitive(true);
1290 for(SPObject *prim_obj = f->children;
1291 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1292 prim_obj = prim_obj->next) {
1293 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1294 if(prim) {
1295 Gtk::TreeModel::Row row = *_model->append();
1296 row[_columns.primitive] = prim;
1297 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1298 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1299 row[_columns.id] = SP_OBJECT_ID(prim);
1301 if(prim == active_prim) {
1302 get_selection()->select(row);
1303 active_found = true;
1304 }
1305 }
1306 }
1308 if(!active_found && _model->children().begin())
1309 get_selection()->select(_model->children().begin());
1311 columns_autosize();
1312 }
1313 else {
1314 _dialog._primitive_box.set_sensitive(false);
1315 }
1316 }
1318 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1319 {
1320 _primitive_menu = menu;
1321 }
1323 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1324 {
1325 if(_dialog._filter_modifier.get_selected_filter()) {
1326 Gtk::TreeModel::iterator i = get_selection()->get_selected();
1327 if(i)
1328 return (*i)[_columns.primitive];
1329 }
1331 return 0;
1332 }
1334 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1335 {
1336 for(Gtk::TreeIter i = _model->children().begin();
1337 i != _model->children().end(); ++i) {
1338 if((*i)[_columns.primitive] == prim)
1339 get_selection()->select(i);
1340 }
1341 }
1343 void FilterEffectsDialog::PrimitiveList::remove_selected()
1344 {
1345 SPFilterPrimitive* prim = get_selected();
1347 if(prim) {
1348 _observer->set(0);
1350 sp_repr_unparent(prim->repr);
1352 sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1353 _("Remove filter primitive"));
1355 update();
1356 }
1357 }
1359 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1360 {
1361 Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1362 Glib::RefPtr<Gdk::Window> win = get_bin_window();
1363 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1365 SPFilterPrimitive* prim = get_selected();
1366 int row_count = get_model()->children().size();
1368 int fheight = CellRendererConnection::size;
1369 Gdk::Rectangle rct, vis;
1370 Gtk::TreeIter row = get_model()->children().begin();
1371 int text_start_x = 0;
1372 if(row) {
1373 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1374 get_visible_rect(vis);
1375 int vis_x, vis_y;
1376 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1378 text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter.end + 1) + 1;
1379 for(int i = 0; i < FPInputConverter.end; ++i) {
1380 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1381 const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1382 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());
1383 get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1384 get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1385 }
1386 }
1388 int row_index = 0;
1389 for(; row != get_model()->children().end(); ++row, ++row_index) {
1390 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1391 const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1393 // Check mouse state
1394 int mx, my;
1395 Gdk::ModifierType mask;
1396 get_bin_window()->get_pointer(mx, my, mask);
1398 // Outline the bottom of the connection area
1399 const int outline_x = x + fheight * (row_count - row_index);
1400 get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1402 // Side outline
1403 get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1405 std::vector<Gdk::Point> con_poly;
1406 int con_drag_y;
1407 bool inside;
1408 const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1409 const int inputs = input_count(row_prim);
1411 if(SP_IS_FEMERGE(row_prim)) {
1412 for(int i = 0; i < inputs; ++i) {
1413 inside = do_connection_node(row, i, con_poly, mx, my);
1414 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1415 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1416 inside, con_poly);
1418 if(_in_drag == (i + 1))
1419 con_drag_y = con_poly[2].get_y();
1421 if(_in_drag != (i + 1) || row_prim != prim)
1422 draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1423 }
1424 }
1425 else {
1426 // Draw "in" shape
1427 inside = do_connection_node(row, 0, con_poly, mx, my);
1428 con_drag_y = con_poly[2].get_y();
1429 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1430 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1431 inside, con_poly);
1433 // Draw "in" connection
1434 if(_in_drag != 1 || row_prim != prim)
1435 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1437 if(inputs == 2) {
1438 // Draw "in2" shape
1439 inside = do_connection_node(row, 1, con_poly, mx, my);
1440 if(_in_drag == 2)
1441 con_drag_y = con_poly[2].get_y();
1442 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1443 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1444 inside, con_poly);
1445 // Draw "in2" connection
1446 if(_in_drag != 2 || row_prim != prim)
1447 draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1448 }
1449 }
1451 // Draw drag connection
1452 if(row_prim == prim && _in_drag) {
1453 get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1454 mx, con_drag_y);
1455 get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1456 }
1457 }
1459 return true;
1460 }
1462 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1463 const int text_start_x, const int x1, const int y1,
1464 const int row_count)
1465 {
1466 int src_id = 0;
1467 Gtk::TreeIter res = find_result(input, attr, src_id);
1468 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1469 Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1470 Glib::RefPtr<Gdk::GC> gc;
1472 const bool is_first = input == get_model()->children().begin();
1473 const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1474 const bool use_default = !res && !is_merge;
1476 if(res == input || (use_default && is_first)) {
1477 // Draw straight connection to a standard input
1478 // Draw a lighter line for an implicit connection to a standard input
1479 const int tw = _connection_cell.get_text_width();
1480 gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1481 gc = (use_default && is_first) ? lightgc : darkgc;
1482 get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1483 get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1484 }
1485 else {
1486 // Draw an 'L'-shaped connection to another filter primitive
1487 // If no connection is specified, draw a light connection to the previous primitive
1488 gc = use_default ? lightgc : darkgc;
1490 if(use_default) {
1491 res = input;
1492 --res;
1493 }
1495 if(res) {
1496 Gdk::Rectangle rct;
1498 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1499 const int fheight = CellRendererConnection::size;
1501 get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1502 const int row_index = find_index(res);
1503 const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1504 const int y2 = rct.get_y() + rct.get_height();
1506 // Draw a bevelled 'L'-shaped connection
1507 get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1508 get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1509 get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1510 }
1511 }
1512 }
1514 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1515 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1516 std::vector<Gdk::Point>& points,
1517 const int ix, const int iy)
1518 {
1519 Gdk::Rectangle rct;
1520 const int icnt = input_count((*row)[_columns.primitive]);
1522 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1523 const int fheight = CellRendererConnection::size;
1525 get_cell_area(_model->get_path(row), *get_column(1), rct);
1526 const float h = rct.get_height() / icnt;
1528 const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1529 const int con_w = (int)(fheight * 0.35f);
1530 const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1531 points.clear();
1532 points.push_back(Gdk::Point(x, con_y));
1533 points.push_back(Gdk::Point(x, con_y + con_w * 2));
1534 points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1536 return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1537 }
1539 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1540 const int attr, int& src_id)
1541 {
1542 SPFilterPrimitive* prim = (*start)[_columns.primitive];
1543 Gtk::TreeIter target = _model->children().end();
1544 int image;
1546 if(SP_IS_FEMERGE(prim)) {
1547 int c = 0;
1548 bool found = false;
1549 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1550 if(c == attr && SP_IS_FEMERGENODE(o)) {
1551 image = SP_FEMERGENODE(o)->input;
1552 found = true;
1553 }
1554 }
1555 if(!found)
1556 return target;
1557 }
1558 else {
1559 if(attr == SP_ATTR_IN)
1560 image = prim->image_in;
1561 else if(attr == SP_ATTR_IN2) {
1562 if(SP_IS_FEBLEND(prim))
1563 image = SP_FEBLEND(prim)->in2;
1564 else if(SP_IS_FECOMPOSITE(prim))
1565 image = SP_FECOMPOSITE(prim)->in2;
1566 else if(SP_IS_FEDISPLACEMENTMAP(prim))
1567 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1568 else
1569 return target;
1570 }
1571 else
1572 return target;
1573 }
1575 if(image >= 0) {
1576 for(Gtk::TreeIter i = _model->children().begin();
1577 i != start; ++i) {
1578 if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1579 target = i;
1580 }
1581 return target;
1582 }
1583 else if(image < -1) {
1584 src_id = -(image + 2);
1585 return start;
1586 }
1588 return target;
1589 }
1591 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1592 {
1593 int i = 0;
1594 for(Gtk::TreeIter iter = _model->children().begin();
1595 iter != target; ++iter, ++i);
1596 return i;
1597 }
1599 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1600 {
1601 Gtk::TreePath path;
1602 Gtk::TreeViewColumn* col;
1603 const int x = (int)e->x, y = (int)e->y;
1604 int cx, cy;
1606 _drag_prim = 0;
1608 if(get_path_at_pos(x, y, path, col, cx, cy)) {
1609 Gtk::TreeIter iter = _model->get_iter(path);
1610 std::vector<Gdk::Point> points;
1612 _drag_prim = (*iter)[_columns.primitive];
1613 const int icnt = input_count(_drag_prim);
1615 for(int i = 0; i < icnt; ++i) {
1616 if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1617 _in_drag = i + 1;
1618 break;
1619 }
1620 }
1622 queue_draw();
1623 }
1625 if(_in_drag) {
1626 _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1627 _autoscroll = 0;
1628 get_selection()->select(path);
1629 return true;
1630 }
1631 else
1632 return Gtk::TreeView::on_button_press_event(e);
1633 }
1635 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1636 {
1637 const int speed = 10;
1638 const int limit = 15;
1640 Gdk::Rectangle vis;
1641 get_visible_rect(vis);
1642 int vis_x, vis_y;
1643 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1644 const int top = vis_y + vis.get_height();
1646 // When autoscrolling during a connection drag, set the speed based on
1647 // where the mouse is in relation to the edges.
1648 if(e->y < vis_y)
1649 _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1650 else if(e->y < vis_y + limit)
1651 _autoscroll = -speed;
1652 else if(e->y > top)
1653 _autoscroll = (int)(speed + (e->y - top) / 5);
1654 else if(e->y > top - limit)
1655 _autoscroll = speed;
1656 else
1657 _autoscroll = 0;
1659 queue_draw();
1661 return Gtk::TreeView::on_motion_notify_event(e);
1662 }
1664 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1665 {
1666 SPFilterPrimitive *prim = get_selected(), *target;
1668 _scroll_connection.disconnect();
1670 if(_in_drag && prim) {
1671 Gtk::TreePath path;
1672 Gtk::TreeViewColumn* col;
1673 int cx, cy;
1675 if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1676 const gchar *in_val = 0;
1677 Glib::ustring result;
1678 Gtk::TreeIter target_iter = _model->get_iter(path);
1679 target = (*target_iter)[_columns.primitive];
1681 Gdk::Rectangle rct;
1682 get_cell_area(path, *col, rct);
1683 const int twidth = _connection_cell.get_text_width();
1684 const int sources_x = rct.get_width() - twidth * FPInputConverter.end;
1685 if(cx > sources_x) {
1686 int src = (cx - sources_x) / twidth;
1687 if(src < 0)
1688 src = 0;
1689 else if(src >= FPInputConverter.end)
1690 src = FPInputConverter.end - 1;
1691 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1692 in_val = result.c_str();
1693 }
1694 else {
1695 // Ensure that the target comes before the selected primitive
1696 for(Gtk::TreeIter iter = _model->children().begin();
1697 iter != get_selection()->get_selected(); ++iter) {
1698 if(iter == target_iter) {
1699 Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1700 // Make sure the target has a result
1701 const gchar *gres = repr->attribute("result");
1702 if(!gres) {
1703 result = "result" + Glib::Ascii::dtostr(SP_FILTER(prim->parent)->_image_number_next);
1704 repr->setAttribute("result", result.c_str());
1705 in_val = result.c_str();
1706 }
1707 else
1708 in_val = gres;
1709 break;
1710 }
1711 }
1712 }
1714 if(SP_IS_FEMERGE(prim)) {
1715 int c = 1;
1716 bool handled = false;
1717 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1718 if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1719 // If input is null, delete it
1720 if(!in_val) {
1721 sp_repr_unparent(o->repr);
1722 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1723 _("Remove merge node"));
1724 (*get_selection()->get_selected())[_columns.primitive] = prim;
1725 }
1726 else
1727 _dialog.set_attr(o, SP_ATTR_IN, in_val);
1728 handled = true;
1729 }
1730 }
1731 // Add new input?
1732 if(!handled && c == _in_drag && in_val) {
1733 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1734 Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1735 repr->setAttribute("inkscape:collect", "always");
1736 prim->repr->appendChild(repr);
1737 SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1738 Inkscape::GC::release(repr);
1739 _dialog.set_attr(node, SP_ATTR_IN, in_val);
1740 (*get_selection()->get_selected())[_columns.primitive] = prim;
1741 }
1742 }
1743 else {
1744 if(_in_drag == 1)
1745 _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1746 else if(_in_drag == 2)
1747 _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1748 }
1749 }
1751 _in_drag = 0;
1752 queue_draw();
1754 _dialog.update_settings_view();
1755 }
1757 if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1758 const bool sensitive = get_selected() != NULL;
1759 _primitive_menu->items()[0].set_sensitive(sensitive);
1760 _primitive_menu->items()[1].set_sensitive(sensitive);
1761 _primitive_menu->popup(e->button, e->time);
1763 return true;
1764 }
1765 else
1766 return Gtk::TreeView::on_button_release_event(e);
1767 }
1769 // Checks all of prim's inputs, removes any that use result
1770 void check_single_connection(SPFilterPrimitive* prim, const int result)
1771 {
1772 if(prim && result >= 0) {
1774 if(prim->image_in == result)
1775 SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1777 if(SP_IS_FEBLEND(prim)) {
1778 if(SP_FEBLEND(prim)->in2 == result)
1779 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1780 }
1781 else if(SP_IS_FECOMPOSITE(prim)) {
1782 if(SP_FECOMPOSITE(prim)->in2 == result)
1783 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1784 }
1785 else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
1786 if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
1787 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1788 }
1789 }
1790 }
1792 // Remove any connections going to/from prim_iter that forward-reference other primitives
1793 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
1794 {
1795 SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
1796 bool before = true;
1798 for(Gtk::TreeIter iter = _model->children().begin();
1799 iter != _model->children().end(); ++iter) {
1800 if(iter == prim_iter)
1801 before = false;
1802 else {
1803 SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
1804 if(before)
1805 check_single_connection(cur_prim, prim->image_out);
1806 else
1807 check_single_connection(prim, cur_prim->image_out);
1808 }
1809 }
1810 }
1812 // Reorder the filter primitives to match the list order
1813 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
1814 {
1815 SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
1816 int ndx = 0;
1818 for(Gtk::TreeModel::iterator iter = _model->children().begin();
1819 iter != _model->children().end(); ++iter, ++ndx) {
1820 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
1821 if(prim && prim == _drag_prim) {
1822 SP_OBJECT_REPR(prim)->setPosition(ndx);
1823 break;
1824 }
1825 }
1827 for(Gtk::TreeModel::iterator iter = _model->children().begin();
1828 iter != _model->children().end(); ++iter, ++ndx) {
1829 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
1830 if(prim && prim == _drag_prim) {
1831 sanitize_connections(iter);
1832 get_selection()->select(iter);
1833 break;
1834 }
1835 }
1837 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
1839 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
1840 }
1842 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
1843 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
1844 {
1845 if(_autoscroll) {
1846 Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
1847 double v;
1849 v = a.get_value() + _autoscroll;
1850 if(v < 0)
1851 v = 0;
1852 if(v > a.get_upper() - a.get_page_size())
1853 v = a.get_upper() - a.get_page_size();
1855 a.set_value(v);
1857 queue_draw();
1858 }
1860 return true;
1861 }
1863 int FilterEffectsDialog::PrimitiveList::primitive_count() const
1864 {
1865 return _model->children().size();
1866 }
1868 /*** FilterEffectsDialog ***/
1870 FilterEffectsDialog::FilterEffectsDialog()
1871 : UI::Widget::Panel("", "dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
1872 _filter_modifier(*this),
1873 _primitive_list(*this),
1874 _add_primitive_type(FPConverter),
1875 _add_primitive(_("Add Effect:")),
1876 _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
1877 _locked(false),
1878 _attr_lock(false)
1879 {
1880 _settings = new Settings(*this, _settings_box, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
1881 NR_FILTER_ENDPRIMITIVETYPE);
1882 _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
1883 _sizegroup->set_ignore_hidden();
1885 _add_primitive_type.remove_row(NR_FILTER_IMAGE);
1887 // Initialize widget hierarchy
1888 Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
1889 Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
1890 Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
1891 Gtk::Frame* fr_settings = Gtk::manage(new Gtk::Frame(_("<b>Effect parameters</b>")));
1892 Gtk::Alignment* al_settings = Gtk::manage(new Gtk::Alignment);
1893 _getContents()->add(*hpaned);
1894 hpaned->pack1(_filter_modifier);
1895 hpaned->pack2(_primitive_box);
1896 _primitive_box.pack_start(*sw_prims);
1897 _primitive_box.pack_start(*hb_prims, false, false);
1898 sw_prims->add(_primitive_list);
1899 hb_prims->pack_end(_add_primitive_type, false, false);
1900 hb_prims->pack_end(_add_primitive, false, false);
1901 _getContents()->pack_start(*fr_settings, false, false);
1902 fr_settings->add(*al_settings);
1903 al_settings->add(_settings_box);
1905 _primitive_list.signal_primitive_changed().connect(
1906 sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
1907 _filter_modifier.signal_filter_changed().connect(
1908 sigc::mem_fun(_primitive_list, &PrimitiveList::update));
1910 sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1911 sw_prims->set_shadow_type(Gtk::SHADOW_IN);
1912 al_settings->set_padding(0, 0, 12, 0);
1913 fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
1914 ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
1915 _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
1916 _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
1917 sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
1919 show_all_children();
1920 init_settings_widgets();
1921 _primitive_list.update();
1922 }
1924 FilterEffectsDialog::~FilterEffectsDialog()
1925 {
1926 delete _settings;
1927 }
1929 void FilterEffectsDialog::set_attrs_locked(const bool l)
1930 {
1931 _locked = l;
1932 }
1934 void FilterEffectsDialog::show_all_vfunc()
1935 {
1936 UI::Widget::Panel::show_all_vfunc();
1938 update_settings_view();
1939 }
1941 void FilterEffectsDialog::init_settings_widgets()
1942 {
1943 // TODO: Find better range/climb-rate/digits values for the SpinSliders,
1944 // most of the current values are complete guesses!
1946 _empty_settings.set_sensitive(false);
1947 _settings_box.pack_start(_empty_settings);
1949 _settings->type(NR_FILTER_BLEND);
1950 _settings->add_combo(SP_ATTR_MODE, _("Mode"), BlendModeConverter);
1952 _settings->type(NR_FILTER_COLORMATRIX);
1953 ComboBoxEnum<FilterColorMatrixType>* colmat = _settings->add_combo(SP_ATTR_TYPE, _("Type"), ColorMatrixTypeConverter);
1954 _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s)"));
1955 colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
1957 _settings->type(NR_FILTER_COMPONENTTRANSFER);
1958 _settings->add_combo(SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
1959 _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
1960 _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
1961 _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
1962 _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
1963 _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);
1965 _settings->type(NR_FILTER_COMPOSITE);
1966 _settings->add_combo(SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
1967 _k1 = _settings->add_spinslider(SP_ATTR_K1, _("K1"), -10, 10, 0.1, 0.01, 2);
1968 _k2 = _settings->add_spinslider(SP_ATTR_K2, _("K2"), -10, 10, 0.1, 0.01, 2);
1969 _k3 = _settings->add_spinslider(SP_ATTR_K3, _("K3"), -10, 10, 0.1, 0.01, 2);
1970 _k4 = _settings->add_spinslider(SP_ATTR_K4, _("K4"), -10, 10, 0.1, 0.01, 2);
1972 _settings->type(NR_FILTER_CONVOLVEMATRIX);
1973 _convolve_order = _settings->add_dualspinbutton(SP_ATTR_ORDER, _("Size"), 1, 5, 1, 1, 0);
1974 _convolve_target = _settings->add_multispinbutton(SP_ATTR_TARGETX, SP_ATTR_TARGETY, _("Target"), 0, 4, 1, 1, 0);
1975 _convolve_matrix = _settings->add_matrix(SP_ATTR_KERNELMATRIX, _("Kernel"));
1976 _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
1977 _settings->add_spinslider(SP_ATTR_DIVISOR, _("Divisor"), 1, 20, 1, 0.1, 2);
1978 _settings->add_spinslider(SP_ATTR_BIAS, _("Bias"), -10, 10, 1, 0.01, 1);
1979 _settings->add_combo(SP_ATTR_EDGEMODE, _("Edge Mode"), ConvolveMatrixEdgeModeConverter);
1980 _settings->add_checkbutton(SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false");
1982 _settings->type(NR_FILTER_DIFFUSELIGHTING);
1983 _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Diffuse Color"));
1984 _settings->add_spinslider(SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1);
1985 _settings->add_spinslider(SP_ATTR_DIFFUSECONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2);
1986 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
1987 _settings->add_lightsource();
1989 _settings->type(NR_FILTER_DISPLACEMENTMAP);
1990 _settings->add_spinslider(SP_ATTR_SCALE, _("Scale"), 0, 100, 1, 0.01, 1);
1991 _settings->add_combo(SP_ATTR_XCHANNELSELECTOR, _("X Channel"), DisplacementMapChannelConverter);
1992 _settings->add_combo(SP_ATTR_YCHANNELSELECTOR, _("Y Channel"), DisplacementMapChannelConverter);
1994 _settings->type(NR_FILTER_FLOOD);
1995 _settings->add_color(SP_PROP_FLOOD_COLOR, _("Flood Color"));
1996 _settings->add_spinslider(SP_PROP_FLOOD_OPACITY, _("Opacity"), 0, 1, 0.1, 0.01, 2);
1998 _settings->type(NR_FILTER_GAUSSIANBLUR);
1999 _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation"), 0.01, 100, 1, 0.01, 1);
2001 _settings->type(NR_FILTER_MORPHOLOGY);
2002 _settings->add_combo(SP_ATTR_OPERATOR, _("Operator"), MorphologyOperatorConverter);
2003 _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius"), 0, 100, 1, 0.01, 1);
2005 _settings->type(NR_FILTER_IMAGE);
2006 _settings->add_notimplemented();
2008 _settings->type(NR_FILTER_OFFSET);
2009 _settings->add_spinslider(SP_ATTR_DX, _("Delta X"), -100, 100, 1, 0.01, 1);
2010 _settings->add_spinslider(SP_ATTR_DY, _("Delta Y"), -100, 100, 1, 0.01, 1);
2012 _settings->type(NR_FILTER_SPECULARLIGHTING);
2013 _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Specular Color"));
2014 _settings->add_spinslider(SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1);
2015 _settings->add_spinslider(SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2);
2016 _settings->add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1);
2017 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2018 _settings->add_lightsource();
2020 _settings->type(NR_FILTER_TURBULENCE);
2021 _settings->add_checkbutton(SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2022 _settings->add_combo(SP_ATTR_TYPE, _("Type"), TurbulenceTypeConverter);
2023 _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency"), 0, 1, 0.001, 0.01, 3);
2024 _settings->add_spinslider(SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2025 _settings->add_spinslider(SP_ATTR_SEED, _("Seed"), 0, 1000, 1, 1, 0);
2026 }
2028 void FilterEffectsDialog::add_primitive()
2029 {
2030 SPFilter* filter = _filter_modifier.get_selected_filter();
2032 if(filter) {
2033 SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2035 _primitive_list.select(prim);
2037 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2038 }
2039 }
2041 void FilterEffectsDialog::duplicate_primitive()
2042 {
2043 SPFilter* filter = _filter_modifier.get_selected_filter();
2044 SPFilterPrimitive* origprim = _primitive_list.get_selected();
2046 if(filter && origprim) {
2047 Inkscape::XML::Node *repr;
2048 repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2049 SP_OBJECT_REPR(filter)->appendChild(repr);
2051 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2053 _primitive_list.update();
2054 }
2055 }
2057 void FilterEffectsDialog::convolve_order_changed()
2058 {
2059 _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2060 _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2061 _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2062 }
2064 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2065 {
2066 set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2067 }
2069 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2070 {
2071 set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2072 }
2074 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2075 {
2076 if(!_locked) {
2077 _attr_lock = true;
2079 SPFilter *filter = _filter_modifier.get_selected_filter();
2080 const gchar* name = (const gchar*)sp_attribute_name(attr);
2081 if(filter && name && o) {
2082 update_settings_sensitivity();
2084 SP_OBJECT_REPR(o)->setAttribute(name, val);
2085 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2087 Glib::ustring undokey = "filtereffects:";
2088 undokey += name;
2089 sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2090 _("Set filter primitive attribute"));
2091 }
2093 _attr_lock = false;
2094 }
2095 }
2097 void FilterEffectsDialog::update_settings_view()
2098 {
2099 update_settings_sensitivity();
2101 if(_attr_lock)
2102 return;
2104 SPFilterPrimitive* prim = _primitive_list.get_selected();
2106 if(prim) {
2107 _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2108 _empty_settings.hide();
2109 }
2110 else {
2111 _settings_box.hide_all();
2112 _settings_box.show();
2113 _empty_settings.show();
2114 }
2115 }
2117 void FilterEffectsDialog::update_settings_sensitivity()
2118 {
2119 SPFilterPrimitive* prim = _primitive_list.get_selected();
2120 const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2121 _k1->set_sensitive(use_k);
2122 _k2->set_sensitive(use_k);
2123 _k3->set_sensitive(use_k);
2124 _k4->set_sensitive(use_k);
2126 if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2127 SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2128 const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2129 const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2130 //_ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2131 _ct_slope->set_sensitive(linear);
2132 _ct_intercept->set_sensitive(linear);
2133 _ct_amplitude->set_sensitive(gamma);
2134 _ct_exponent->set_sensitive(gamma);
2135 _ct_offset->set_sensitive(gamma);
2136 }
2137 }
2139 void FilterEffectsDialog::update_color_matrix()
2140 {
2141 _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2142 }
2144 } // namespace Dialog
2145 } // namespace UI
2146 } // namespace Inkscape
2148 /*
2149 Local Variables:
2150 mode:c++
2151 c-file-style:"stroustrup"
2152 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2153 indent-tabs-mode:nil
2154 fill-column:99
2155 End:
2156 */
2157 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :