1 /** @file
2 * @brief Filter Effects dialog
3 */
4 /* Authors:
5 * Nicholas Bishop <nicholasbishop@gmail.org>
6 * Rodrigo Kumpera <kumpera@gmail.com>
7 * Felipe C. da S. Sanches <felipe.sanches@gmail.com>
8 *
9 * Copyright (C) 2007 Authors
10 *
11 * Released under GNU GPL. Read the file 'COPYING' for more information.
12 */
14 #ifdef HAVE_CONFIG_H
15 # include <config.h>
16 #endif
18 #include <gtk/gtktreeview.h>
19 #include <gtkmm/cellrenderertext.h>
20 #include <gtkmm/colorbutton.h>
21 #include <gtkmm/messagedialog.h>
22 #include <gtkmm/paned.h>
23 #include <gtkmm/scale.h>
24 #include <gtkmm/scrolledwindow.h>
25 #include <gtkmm/spinbutton.h>
26 #include <gtkmm/stock.h>
27 #include <gtkmm/tooltips.h>
28 #include <glibmm/i18n.h>
30 #include "application/application.h"
31 #include "application/editor.h"
32 #include "desktop.h"
33 #include "desktop-handles.h"
34 #include "dialog-manager.h"
35 #include "dir-util.h"
36 #include "document.h"
37 #include "filter-chemistry.h"
38 #include "filter-effects-dialog.h"
39 #include "filter-enums.h"
40 #include "inkscape.h"
41 #include "path-prefix.h"
42 #include "preferences.h"
43 #include "selection.h"
44 #include "filters/blend.h"
45 #include "filters/colormatrix.h"
46 #include "filters/componenttransfer.h"
47 #include "filters/composite.h"
48 #include "filters/convolvematrix.h"
49 #include "filters/displacementmap.h"
50 #include "filters/distantlight.h"
51 #include "filters/merge.h"
52 #include "filters/mergenode.h"
53 #include "filters/offset.h"
54 #include "filters/pointlight.h"
55 #include "filters/spotlight.h"
56 #include "sp-filter-primitive.h"
57 #include "sp-gaussian-blur.h"
59 #include "style.h"
60 #include "svg/svg-color.h"
61 #include "ui/dialog/filedialog.h"
62 #include "verbs.h"
63 #include "xml/node.h"
64 #include "xml/node-observer.h"
65 #include "xml/repr.h"
66 #include <sstream>
68 #include "io/sys.h"
69 #include <iostream>
71 using namespace NR;
73 namespace Inkscape {
74 namespace UI {
75 namespace Dialog {
77 // Returns the number of inputs available for the filter primitive type
78 int input_count(const SPFilterPrimitive* prim)
79 {
80 if(!prim)
81 return 0;
82 else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
83 return 2;
84 else if(SP_IS_FEMERGE(prim)) {
85 // Return the number of feMergeNode connections plus an extra
86 int count = 1;
87 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count){};
88 return count;
89 }
90 else
91 return 1;
92 }
94 // Very simple observer that just emits a signal if anything happens to a node
95 class FilterEffectsDialog::SignalObserver : public XML::NodeObserver
96 {
97 public:
98 SignalObserver()
99 : _oldsel(0)
100 {}
102 // Add this observer to the SPObject and remove it from any previous object
103 void set(SPObject* o)
104 {
105 if(_oldsel && _oldsel->repr)
106 _oldsel->repr->removeObserver(*this);
107 if(o && o->repr)
108 o->repr->addObserver(*this);
109 _oldsel = o;
110 }
112 void notifyChildAdded(XML::Node&, XML::Node&, XML::Node*)
113 { signal_changed()(); }
115 void notifyChildRemoved(XML::Node&, XML::Node&, XML::Node*)
116 { signal_changed()(); }
118 void notifyChildOrderChanged(XML::Node&, XML::Node&, XML::Node*, XML::Node*)
119 { signal_changed()(); }
121 void notifyContentChanged(XML::Node&, Util::ptr_shared<char>, Util::ptr_shared<char>)
122 {}
124 void notifyAttributeChanged(XML::Node&, GQuark, Util::ptr_shared<char>, Util::ptr_shared<char>)
125 { signal_changed()(); }
127 sigc::signal<void>& signal_changed()
128 {
129 return _signal_changed;
130 }
131 private:
132 sigc::signal<void> _signal_changed;
133 SPObject* _oldsel;
134 };
136 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
137 {
138 public:
139 CheckButtonAttr(bool def, const Glib::ustring& label,
140 const Glib::ustring& tv, const Glib::ustring& fv,
141 const SPAttributeEnum a, char* tip_text)
142 : Gtk::CheckButton(label),
143 AttrWidget(a, def),
144 _true_val(tv), _false_val(fv)
145 {
146 signal_toggled().connect(signal_attr_changed().make_slot());
147 if (tip_text) _tt.set_tip(*this, tip_text);
148 }
150 Glib::ustring get_as_attribute() const
151 {
152 return get_active() ? _true_val : _false_val;
153 }
155 void set_from_attribute(SPObject* o)
156 {
157 const gchar* val = attribute_value(o);
158 if(val) {
159 if(_true_val == val)
160 set_active(true);
161 else if(_false_val == val)
162 set_active(false);
163 } else {
164 set_active(get_default()->as_bool());
165 }
166 }
167 private:
168 const Glib::ustring _true_val, _false_val;
169 };
171 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
172 {
173 public:
174 SpinButtonAttr(double lower, double upper, double step_inc,
175 double climb_rate, int digits, const SPAttributeEnum a, double def, char* tip_text)
176 : Gtk::SpinButton(climb_rate, digits),
177 AttrWidget(a, def)
178 {
179 if (tip_text) _tt.set_tip(*this, tip_text);
180 set_range(lower, upper);
181 set_increments(step_inc, step_inc * 5);
183 signal_value_changed().connect(signal_attr_changed().make_slot());
184 }
186 Glib::ustring get_as_attribute() const
187 {
188 const double val = get_value();
190 if(get_digits() == 0)
191 return Glib::Ascii::dtostr((int)val);
192 else
193 return Glib::Ascii::dtostr(val);
194 }
196 void set_from_attribute(SPObject* o)
197 {
198 const gchar* val = attribute_value(o);
199 if(val){
200 set_value(Glib::Ascii::strtod(val));
201 } else {
202 set_value(get_default()->as_double());
203 }
204 }
205 };
207 template< typename T> class ComboWithTooltip : public Gtk::EventBox
208 {
209 public:
210 ComboWithTooltip<T>(T default_value, const Util::EnumDataConverter<T>& c, const SPAttributeEnum a = SP_ATTR_INVALID, char* tip_text = NULL)
211 {
212 if (tip_text) {
213 _tt.set_tip(*this, tip_text);
214 }
215 combo = new ComboBoxEnum<T>(default_value, c, a);
216 add(*combo);
217 show_all();
218 }
220 ~ComboWithTooltip()
221 {
222 delete combo;
223 }
225 ComboBoxEnum<T>* get_attrwidget()
226 {
227 return combo;
228 }
229 private:
230 Gtk::Tooltips _tt;
231 ComboBoxEnum<T>* combo;
232 };
234 // Contains an arbitrary number of spin buttons that use seperate attributes
235 class MultiSpinButton : public Gtk::HBox
236 {
237 public:
238 MultiSpinButton(double lower, double upper, double step_inc,
239 double climb_rate, int digits, std::vector<SPAttributeEnum> attrs, std::vector<double> default_values, std::vector<char*> tip_text)
240 {
241 g_assert(attrs.size()==default_values.size());
242 g_assert(attrs.size()==tip_text.size());
243 for(unsigned i = 0; i < attrs.size(); ++i) {
244 _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i], default_values[i], tip_text[i]));
245 pack_start(*_spins.back(), false, false);
246 }
247 }
249 ~MultiSpinButton()
250 {
251 for(unsigned i = 0; i < _spins.size(); ++i)
252 delete _spins[i];
253 }
255 std::vector<SpinButtonAttr*>& get_spinbuttons()
256 {
257 return _spins;
258 }
259 private:
260 std::vector<SpinButtonAttr*> _spins;
261 };
263 // Contains two spinbuttons that describe a NumberOptNumber
264 class DualSpinButton : public Gtk::HBox, public AttrWidget
265 {
266 public:
267 DualSpinButton(char* def, double lower, double upper, double step_inc,
268 double climb_rate, int digits, const SPAttributeEnum a, char* tt1, char* tt2)
269 : AttrWidget(a, def), //TO-DO: receive default num-opt-num as parameter in the constructor
270 _s1(climb_rate, digits), _s2(climb_rate, digits)
271 {
272 if (tt1) _tt.set_tip(_s1, tt1);
273 if (tt2) _tt.set_tip(_s2, tt2);
274 _s1.set_range(lower, upper);
275 _s2.set_range(lower, upper);
276 _s1.set_increments(step_inc, step_inc * 5);
277 _s2.set_increments(step_inc, step_inc * 5);
279 _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
280 _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
282 pack_start(_s1, false, false);
283 pack_start(_s2, false, false);
284 }
286 Gtk::SpinButton& get_spinbutton1()
287 {
288 return _s1;
289 }
291 Gtk::SpinButton& get_spinbutton2()
292 {
293 return _s2;
294 }
296 virtual Glib::ustring get_as_attribute() const
297 {
298 double v1 = _s1.get_value();
299 double v2 = _s2.get_value();
301 if(_s1.get_digits() == 0) {
302 v1 = (int)v1;
303 v2 = (int)v2;
304 }
306 return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
307 }
309 virtual void set_from_attribute(SPObject* o)
310 {
311 const gchar* val = attribute_value(o);
312 NumberOptNumber n;
313 if(val) {
314 n.set(val);
315 } else {
316 n.set(get_default()->as_charptr());
317 }
318 _s1.set_value(n.getNumber());
319 _s2.set_value(n.getOptNumber());
321 }
322 private:
323 Gtk::SpinButton _s1, _s2;
324 };
326 class ColorButton : public Gtk::ColorButton, public AttrWidget
327 {
328 public:
329 ColorButton(unsigned int def, const SPAttributeEnum a, char* tip_text)
330 : AttrWidget(a, def)
331 {
332 signal_color_set().connect(signal_attr_changed().make_slot());
333 if (tip_text) _tt.set_tip(*this, tip_text);
335 Gdk::Color col;
336 col.set_rgb(65535, 65535, 65535);
337 set_color(col);
338 }
340 // Returns the color in 'rgb(r,g,b)' form.
341 Glib::ustring get_as_attribute() const
342 {
343 std::ostringstream os;
344 const Gdk::Color c = get_color();
345 const int r = c.get_red() / 257, g = c.get_green() / 257, b = c.get_blue() / 257;//TO-DO: verify this. This sounds a lot strange! shouldn't it be 256?
346 os << "rgb(" << r << "," << g << "," << b << ")";
347 return os.str();
348 }
351 void set_from_attribute(SPObject* o)
352 {
353 const gchar* val = attribute_value(o);
354 guint32 i = 0;
355 if(val) {
356 i = sp_svg_read_color(val, 0xFFFFFFFF);
357 } else {
358 i = (guint32) get_default()->as_uint();
359 }
360 const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
361 Gdk::Color col;
362 col.set_rgb(r * 256, g * 256, b * 256);
363 set_color(col);
364 }
365 };
367 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
368 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
369 {
370 public:
371 MatrixAttr(const SPAttributeEnum a, char* tip_text = NULL)
372 : AttrWidget(a), _locked(false)
373 {
374 _model = Gtk::ListStore::create(_columns);
375 _tree.set_model(_model);
376 _tree.set_headers_visible(false);
377 _tree.show();
378 add(_tree);
379 set_shadow_type(Gtk::SHADOW_IN);
380 if (tip_text) _tt.set_tip(_tree, tip_text);
381 }
383 std::vector<double> get_values() const
384 {
385 std::vector<double> vec;
386 for(Gtk::TreeIter iter = _model->children().begin();
387 iter != _model->children().end(); ++iter) {
388 for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
389 vec.push_back((*iter)[_columns.cols[c]]);
390 }
391 return vec;
392 }
394 void set_values(const std::vector<double>& v)
395 {
396 unsigned i = 0;
397 for(Gtk::TreeIter iter = _model->children().begin();
398 iter != _model->children().end(); ++iter) {
399 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
400 if(i >= v.size())
401 return;
402 (*iter)[_columns.cols[c]] = v[i];
403 ++i;
404 }
405 }
406 }
408 Glib::ustring get_as_attribute() const
409 {
410 std::ostringstream os;
412 for(Gtk::TreeIter iter = _model->children().begin();
413 iter != _model->children().end(); ++iter) {
414 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
415 os << (*iter)[_columns.cols[c]] << " ";
416 }
417 }
419 return os.str();
420 }
422 void set_from_attribute(SPObject* o)
423 {
424 if(o) {
425 if(SP_IS_FECONVOLVEMATRIX(o)) {
426 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
427 int cols, rows;
428 cols = (int)conv->order.getNumber();
429 if(cols > 5)
430 cols = 5;
431 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
432 update(o, rows, cols);
433 }
434 else if(SP_IS_FECOLORMATRIX(o))
435 update(o, 4, 5);
436 }
437 }
438 private:
439 class MatrixColumns : public Gtk::TreeModel::ColumnRecord
440 {
441 public:
442 MatrixColumns()
443 {
444 cols.resize(5);
445 for(unsigned i = 0; i < cols.size(); ++i)
446 add(cols[i]);
447 }
448 std::vector<Gtk::TreeModelColumn<double> > cols;
449 };
451 void update(SPObject* o, const int rows, const int cols)
452 {
453 if(_locked)
454 return;
456 _model->clear();
458 _tree.remove_all_columns();
460 std::vector<gdouble>* values = NULL;
461 if(SP_IS_FECOLORMATRIX(o))
462 values = &SP_FECOLORMATRIX(o)->values;
463 else if(SP_IS_FECONVOLVEMATRIX(o))
464 values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
465 else
466 return;
468 if(o) {
469 int ndx = 0;
471 for(int i = 0; i < cols; ++i) {
472 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
473 dynamic_cast<Gtk::CellRendererText*>(
474 _tree.get_column_cell_renderer(i))->signal_edited().connect(
475 sigc::mem_fun(*this, &MatrixAttr::rebind));
476 }
478 for(int r = 0; r < rows; ++r) {
479 Gtk::TreeRow row = *(_model->append());
480 // Default to identity matrix
481 for(int c = 0; c < cols; ++c, ++ndx)
482 row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
483 }
484 }
485 }
487 void rebind(const Glib::ustring&, const Glib::ustring&)
488 {
489 _locked = true;
490 signal_attr_changed()();
491 _locked = false;
492 }
494 bool _locked;
495 Gtk::TreeView _tree;
496 Glib::RefPtr<Gtk::ListStore> _model;
497 MatrixColumns _columns;
498 };
500 // Displays a matrix or a slider for feColorMatrix
501 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
502 {
503 public:
504 ColorMatrixValues()
505 : AttrWidget(SP_ATTR_VALUES),
506 _matrix(SP_ATTR_VALUES, _("This matrix determines a linear transform on colour space. Each line affects one of the color components. Each column determines how much of each color component from the input is passed to the output. The last column does not depend on input colors, so can be used to adjust a constant component value.")),
507 _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
508 _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
509 _label(_("None"), Gtk::ALIGN_LEFT),
510 _use_stored(false),
511 _saturation_store(0),
512 _angle_store(0)
513 {
514 _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
515 _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
516 _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
517 signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
519 _matrix.show();
520 _saturation.show();
521 _angle.show();
522 _label.show();
523 _label.set_sensitive(false);
525 set_shadow_type(Gtk::SHADOW_NONE);
526 }
528 virtual void set_from_attribute(SPObject* o)
529 {
530 if(SP_IS_FECOLORMATRIX(o)) {
531 SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
532 remove();
533 switch(col->type) {
534 case COLORMATRIX_SATURATE:
535 add(_saturation);
536 if(_use_stored)
537 _saturation.set_value(_saturation_store);
538 else
539 _saturation.set_from_attribute(o);
540 break;
541 case COLORMATRIX_HUEROTATE:
542 add(_angle);
543 if(_use_stored)
544 _angle.set_value(_angle_store);
545 else
546 _angle.set_from_attribute(o);
547 break;
548 case COLORMATRIX_LUMINANCETOALPHA:
549 add(_label);
550 break;
551 case COLORMATRIX_MATRIX:
552 default:
553 add(_matrix);
554 if(_use_stored)
555 _matrix.set_values(_matrix_store);
556 else
557 _matrix.set_from_attribute(o);
558 break;
559 }
560 _use_stored = true;
561 }
562 }
564 virtual Glib::ustring get_as_attribute() const
565 {
566 const Widget* w = get_child();
567 if(w == &_label)
568 return "";
569 else
570 return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
571 }
573 void clear_store()
574 {
575 _use_stored = false;
576 }
577 private:
578 void update_store()
579 {
580 const Widget* w = get_child();
581 if(w == &_matrix)
582 _matrix_store = _matrix.get_values();
583 else if(w == &_saturation)
584 _saturation_store = _saturation.get_value();
585 else if(w == &_angle)
586 _angle_store = _angle.get_value();
587 }
589 MatrixAttr _matrix;
590 SpinSlider _saturation;
591 SpinSlider _angle;
592 Gtk::Label _label;
594 // Store separate values for the different color modes
595 bool _use_stored;
596 std::vector<double> _matrix_store;
597 double _saturation_store;
598 double _angle_store;
599 };
601 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
603 //Displays a chooser for feImage input
604 //It may be a filename or the id for an SVG Element
605 //described in xlink:href syntax
606 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
607 {
608 public:
609 FileOrElementChooser(const SPAttributeEnum a)
610 : AttrWidget(a)
611 {
612 pack_start(_entry, false, false);
613 pack_start(_fromFile, false, false);
614 pack_start(_fromSVGElement, false, false);
616 _fromFile.set_label(_("Image File"));
617 _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
619 _fromSVGElement.set_label(_("Selected SVG Element"));
620 _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
622 _entry.signal_changed().connect(signal_attr_changed().make_slot());
624 show_all();
626 }
628 // Returns the element in xlink:href form.
629 Glib::ustring get_as_attribute() const
630 {
631 return _entry.get_text();
632 }
635 void set_from_attribute(SPObject* o)
636 {
637 const gchar* val = attribute_value(o);
638 if(val) {
639 _entry.set_text(val);
640 } else {
641 _entry.set_text("");
642 }
643 }
645 void set_desktop(SPDesktop* d){
646 _desktop = d;
647 }
649 private:
650 void select_svg_element(){
651 Inkscape::Selection* sel = sp_desktop_selection(_desktop);
652 if (sel->isEmpty()) return;
653 Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
654 if (!node || !node->matchAttributeName("id")) return;
656 std::ostringstream xlikhref;
657 xlikhref << "#" << node->attribute("id");
658 _entry.set_text(xlikhref.str());
659 }
661 void select_file(){
663 //# Get the current directory for finding files
664 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
665 Glib::ustring open_path;
666 Glib::ustring attr = prefs->getString("/dialogs/open/path");
667 if (!attr.empty())
668 open_path = attr;
670 //# Test if the open_path directory exists
671 if (!Inkscape::IO::file_test(open_path.c_str(),
672 (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
673 open_path = "";
675 //# If no open path, default to our home directory
676 if (open_path.size() < 1)
677 {
678 open_path = g_get_home_dir();
679 open_path.append(G_DIR_SEPARATOR_S);
680 }
682 //# Create a dialog if we don't already have one
683 if (!selectFeImageFileInstance) {
684 selectFeImageFileInstance =
685 Inkscape::UI::Dialog::FileOpenDialog::create(
686 *_desktop->getToplevel(),
687 open_path,
688 Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not justy svg*/
689 (char const *)_("Select an image to be used as feImage input"));
690 }
692 //# Show the dialog
693 bool const success = selectFeImageFileInstance->show();
694 if (!success)
695 return;
697 //# User selected something. Get name and type
698 Glib::ustring fileName = selectFeImageFileInstance->getFilename();
700 if (fileName.size() > 0) {
702 Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
704 if ( newFileName.size() > 0)
705 fileName = newFileName;
706 else
707 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
709 open_path = fileName;
710 open_path.append(G_DIR_SEPARATOR_S);
711 prefs->setString("/dialogs/open/path", open_path);
713 _entry.set_text(fileName);
714 }
715 return;
716 }
718 Gtk::Entry _entry;
719 Gtk::Button _fromFile;
720 Gtk::Button _fromSVGElement;
721 SPDesktop* _desktop;
722 };
724 class FilterEffectsDialog::Settings
725 {
726 public:
727 typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
729 Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
730 : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
731 {
732 _groups.resize(_max_types);
733 _attrwidgets.resize(_max_types);
734 _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
736 for(int i = 0; i < _max_types; ++i) {
737 _groups[i] = new Gtk::VBox;
738 b.pack_start(*_groups[i], false, false);
739 }
740 _current_type = 0;
741 }
743 ~Settings()
744 {
745 for(int i = 0; i < _max_types; ++i) {
746 delete _groups[i];
747 for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
748 delete _attrwidgets[i][j];
749 }
750 }
752 // Show the active settings group and update all the AttrWidgets with new values
753 void show_and_update(const int t, SPObject* ob)
754 {
755 if(t != _current_type) {
756 type(t);
757 for(unsigned i = 0; i < _groups.size(); ++i)
758 _groups[i]->hide();
759 }
760 if(t >= 0)
761 _groups[t]->show_all();
763 _dialog.set_attrs_locked(true);
764 for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
765 _attrwidgets[_current_type][i]->set_from_attribute(ob);
766 _dialog.set_attrs_locked(false);
767 }
769 int get_current_type() const
770 {
771 return _current_type;
772 }
774 void type(const int t)
775 {
776 _current_type = t;
777 }
779 void add_no_params()
780 {
781 Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
782 add_widget(lbl, "");
783 }
785 void add_notimplemented()
786 {
787 Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
788 add_widget(lbl, "");
789 }
791 // LightSource
792 LightSourceControl* add_lightsource();
794 // CheckBox
795 CheckButtonAttr* add_checkbutton(bool def, const SPAttributeEnum attr, const Glib::ustring& label,
796 const Glib::ustring& tv, const Glib::ustring& fv, char* tip_text = NULL)
797 {
798 CheckButtonAttr* cb = new CheckButtonAttr(def, label, tv, fv, attr, tip_text);
799 add_widget(cb, "");
800 add_attr_widget(cb);
801 return cb;
802 }
804 // ColorButton
805 ColorButton* add_color(unsigned int def, const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text = NULL)
806 {
807 ColorButton* col = new ColorButton(def, attr, tip_text);
808 add_widget(col, label);
809 add_attr_widget(col);
810 return col;
811 }
813 // Matrix
814 MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text)
815 {
816 MatrixAttr* conv = new MatrixAttr(attr, tip_text);
817 add_widget(conv, label);
818 add_attr_widget(conv);
819 return conv;
820 }
822 // ColorMatrixValues
823 ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
824 {
825 ColorMatrixValues* cmv = new ColorMatrixValues();
826 add_widget(cmv, label);
827 add_attr_widget(cmv);
828 return cmv;
829 }
831 // SpinSlider
832 SpinSlider* add_spinslider(double def, const SPAttributeEnum attr, const Glib::ustring& label,
833 const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
834 {
835 SpinSlider* spinslider = new SpinSlider(def, lo, hi, step_inc, climb, digits, attr, tip_text);
836 add_widget(spinslider, label);
837 add_attr_widget(spinslider);
838 return spinslider;
839 }
841 // DualSpinSlider
842 DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
843 const double lo, const double hi, const double step_inc,
844 const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
845 {
846 DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
847 add_widget(dss, label);
848 add_attr_widget(dss);
849 return dss;
850 }
852 // DualSpinButton
853 DualSpinButton* add_dualspinbutton(char* defalt_value, const SPAttributeEnum attr, const Glib::ustring& label,
854 const double lo, const double hi, const double step_inc,
855 const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
856 {
857 DualSpinButton* dsb = new DualSpinButton(defalt_value, lo, hi, step_inc, climb, digits, attr, tip1, tip2);
858 add_widget(dsb, label);
859 add_attr_widget(dsb);
860 return dsb;
861 }
863 // MultiSpinButton
864 MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
865 const Glib::ustring& label, const double lo, const double hi,
866 const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
867 {
868 std::vector<SPAttributeEnum> attrs;
869 attrs.push_back(attr1);
870 attrs.push_back(attr2);
872 std::vector<double> default_values;
873 default_values.push_back(def1);
874 default_values.push_back(def2);
876 std::vector<char*> tips;
877 tips.push_back(tip1);
878 tips.push_back(tip2);
880 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
881 add_widget(msb, label);
882 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
883 add_attr_widget(msb->get_spinbuttons()[i]);
884 return msb;
885 }
886 MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
887 const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
888 const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
889 {
890 std::vector<SPAttributeEnum> attrs;
891 attrs.push_back(attr1);
892 attrs.push_back(attr2);
893 attrs.push_back(attr3);
895 std::vector<double> default_values;
896 default_values.push_back(def1);
897 default_values.push_back(def2);
898 default_values.push_back(def3);
900 std::vector<char*> tips;
901 tips.push_back(tip1);
902 tips.push_back(tip2);
903 tips.push_back(tip3);
905 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
906 add_widget(msb, label);
907 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
908 add_attr_widget(msb->get_spinbuttons()[i]);
909 return msb;
910 }
912 // FileOrElementChooser
913 FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
914 {
915 FileOrElementChooser* foech = new FileOrElementChooser(attr);
916 foech->set_desktop(_dialog.getDesktop());
917 add_widget(foech, label);
918 add_attr_widget(foech);
919 return foech;
920 }
922 // ComboBoxEnum
923 template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
924 const Glib::ustring& label,
925 const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
926 {
927 ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
928 add_widget(combo, label);
929 add_attr_widget(combo->get_attrwidget());
930 return combo->get_attrwidget();
931 }
932 private:
933 Gtk::Tooltips _tt;
935 void add_attr_widget(AttrWidget* a)
936 {
937 _attrwidgets[_current_type].push_back(a);
938 a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
939 }
941 /* Adds a new settings widget using the specified label. The label will be formatted with a colon
942 and all widgets within the setting group are aligned automatically. */
943 void add_widget(Gtk::Widget* w, const Glib::ustring& label)
944 {
945 Gtk::Label *lbl = 0;
946 Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
947 hb->set_spacing(12);
949 if(label != "") {
950 lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
951 hb->pack_start(*lbl, false, false);
952 _size_group->add_widget(*lbl);
953 lbl->show();
954 }
956 hb->pack_start(*w);
957 _groups[_current_type]->pack_start(*hb);
958 hb->show();
959 w->show();
960 }
962 std::vector<Gtk::VBox*> _groups;
963 Glib::RefPtr<Gtk::SizeGroup> _size_group;
964 FilterEffectsDialog& _dialog;
965 SetAttrSlot _set_attr_slot;
966 std::vector<std::vector< AttrWidget*> > _attrwidgets;
967 int _current_type, _max_types;
968 };
970 // Settings for the three light source objects
971 class FilterEffectsDialog::LightSourceControl : public AttrWidget
972 {
973 public:
974 LightSourceControl(FilterEffectsDialog& d)
975 : AttrWidget(SP_ATTR_INVALID),
976 _dialog(d),
977 _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
978 _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
979 _light_source(LightSourceConverter),
980 _locked(false)
981 {
982 _light_box.pack_start(_light_label, false, false);
983 _light_box.pack_start(_light_source);
984 _light_box.show_all();
985 _light_box.set_spacing(12);
986 _dialog._sizegroup->add_widget(_light_label);
988 _box.add(_light_box);
989 _box.reorder_child(_light_box, 0);
990 _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
992 // FIXME: these range values are complete crap
994 _settings.type(LIGHT_DISTANT);
995 _settings.add_spinslider(0, SP_ATTR_AZIMUTH, _("Azimuth"), 0, 360, 1, 1, 0, _("Direction angle for the light source on the XY plane, in degrees"));
996 _settings.add_spinslider(0, SP_ATTR_ELEVATION, _("Elevation"), 0, 360, 1, 1, 0, _("Direction angle for the light source on the YZ plane, in degrees"));
998 _settings.type(LIGHT_POINT);
999 _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0, SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
1001 _settings.type(LIGHT_SPOT);
1002 _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0, SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
1003 _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
1004 SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
1005 _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
1006 _settings.add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
1007 //TODO: here I have used 100 degrees as default value. But spec says that if not specified, no limiting cone is applied. So, there should be a way for the user to set a "no limiting cone" option.
1008 _settings.add_spinslider(100, SP_ATTR_LIMITINGCONEANGLE, _("Cone Angle"), 1, 100, 1, 1, 0, _("This is the angle between the spot light axis (i.e. the axis between the light source and the point to which it is pointing at) and the spot light cone. No light is projected outside this cone."));
1009 }
1011 Gtk::VBox& get_box()
1012 {
1013 return _box;
1014 }
1015 protected:
1016 Glib::ustring get_as_attribute() const
1017 {
1018 return "";
1019 }
1020 void set_from_attribute(SPObject* o)
1021 {
1022 if(_locked)
1023 return;
1025 _locked = true;
1027 SPObject* child = o->children;
1029 if(SP_IS_FEDISTANTLIGHT(child))
1030 _light_source.set_active(0);
1031 else if(SP_IS_FEPOINTLIGHT(child))
1032 _light_source.set_active(1);
1033 else if(SP_IS_FESPOTLIGHT(child))
1034 _light_source.set_active(2);
1035 else
1036 _light_source.set_active(-1);
1038 update();
1040 _locked = false;
1041 }
1042 private:
1043 void on_source_changed()
1044 {
1045 if(_locked)
1046 return;
1048 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1049 if(prim) {
1050 _locked = true;
1052 SPObject* child = prim->children;
1053 const int ls = _light_source.get_active_row_number();
1054 // Check if the light source type has changed
1055 if(!(ls == -1 && !child) &&
1056 !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1057 !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1058 !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1059 if(child)
1060 sp_repr_unparent(child->repr);
1062 if(ls != -1) {
1063 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1064 Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1065 prim->repr->appendChild(repr);
1066 Inkscape::GC::release(repr);
1067 }
1069 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1070 update();
1071 }
1073 _locked = false;
1074 }
1075 }
1077 void update()
1078 {
1079 _box.hide_all();
1080 _box.show();
1081 _light_box.show_all();
1083 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1084 if(prim && prim->children)
1085 _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1086 }
1088 FilterEffectsDialog& _dialog;
1089 Gtk::VBox _box;
1090 Settings _settings;
1091 Gtk::HBox _light_box;
1092 Gtk::Label _light_label;
1093 ComboBoxEnum<LightSource> _light_source;
1094 bool _locked;
1095 };
1097 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1098 {
1099 LightSourceControl* ls = new LightSourceControl(_dialog);
1100 add_attr_widget(ls);
1101 add_widget(&ls->get_box(), "");
1102 return ls;
1103 }
1105 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1106 sigc::slot<void> rem)
1107 {
1108 Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1110 menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1111 Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1112 menu->append(*mi);
1113 mi->signal_activate().connect(rem);
1114 mi->show();
1115 menu->accelerate(parent);
1117 return menu;
1118 }
1120 /*** FilterModifier ***/
1121 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1122 : _dialog(d), _add(Gtk::Stock::NEW), _observer(new SignalObserver)
1123 {
1124 Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1125 pack_start(*sw);
1126 pack_start(_add, false, false);
1127 sw->add(_list);
1129 _model = Gtk::ListStore::create(_columns);
1130 _list.set_model(_model);
1131 _cell_toggle.set_active(true);
1132 const int selcol = _list.append_column("", _cell_toggle);
1133 Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1134 if(col)
1135 col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1136 _list.append_column_editable(_("_Filter"), _columns.label);
1137 ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1138 signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1140 sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1141 sw->set_shadow_type(Gtk::SHADOW_IN);
1142 show_all_children();
1143 _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1144 _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1145 _list.signal_button_release_event().connect_notify(
1146 sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1147 _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1148 sigc::mem_fun(*this, &FilterModifier::remove_filter));
1149 _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1150 _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1151 _menu->accelerate(*this);
1153 _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1154 _observer->signal_changed().connect(signal_filter_changed().make_slot());
1155 g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1156 G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1158 g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1159 G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1160 g_signal_connect(G_OBJECT(INKSCAPE), "deactivate_desktop",
1161 G_CALLBACK(&FilterModifier::on_deactivate_desktop), this);
1163 on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1164 update_filters();
1165 }
1167 FilterEffectsDialog::FilterModifier::~FilterModifier()
1168 {
1169 _resource_changed.disconnect();
1170 _doc_replaced.disconnect();
1171 }
1173 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1174 {
1175 me->_doc_replaced.disconnect();
1176 me->_doc_replaced = desktop->connectDocumentReplaced(
1177 sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1179 me->_resource_changed.disconnect();
1180 me->_resource_changed =
1181 sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1182 sigc::mem_fun(me, &FilterModifier::update_filters));
1184 me->_dialog.setDesktop(desktop);
1186 me->update_filters();
1187 }
1189 void FilterEffectsDialog::FilterModifier::on_deactivate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1190 {
1191 me->_doc_replaced.disconnect();
1192 me->_resource_changed.disconnect();
1193 me->_dialog.setDesktop(NULL);
1194 }
1197 // When the selection changes, show the active filter(s) in the dialog
1198 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1199 Selection *sel,
1200 FilterModifier* fm)
1201 {
1202 if(fm && sel)
1203 fm->update_selection(sel);
1204 }
1206 // Update each filter's sel property based on the current object selection;
1207 // If the filter is not used by any selected object, sel = 0,
1208 // otherwise sel is set to the total number of filters in use by selected objects
1209 // If only one filter is in use, it is selected
1210 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1211 {
1212 std::set<SPObject*> used;
1214 for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1215 SPObject *obj = SP_OBJECT (i->data);
1216 SPStyle *style = SP_OBJECT_STYLE (obj);
1217 if(!style || !SP_IS_ITEM(obj)) continue;
1219 if(style->filter.set && style->getFilter())
1220 used.insert(style->getFilter());
1221 else
1222 used.insert(0);
1223 }
1225 const int size = used.size();
1227 for(Gtk::TreeIter iter = _model->children().begin();
1228 iter != _model->children().end(); ++iter) {
1229 if(used.find((*iter)[_columns.filter]) != used.end()) {
1230 // If only one filter is in use by the selection, select it
1231 if(size == 1)
1232 _list.get_selection()->select(iter);
1233 (*iter)[_columns.sel] = size;
1234 }
1235 else
1236 (*iter)[_columns.sel] = 0;
1237 }
1238 }
1240 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1241 {
1242 _observer->set(get_selected_filter());
1243 signal_filter_changed()();
1244 }
1246 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1247 {
1248 Gtk::TreeModel::iterator iter = _model->get_iter(path);
1250 if(iter) {
1251 SPFilter* filter = (*iter)[_columns.filter];
1252 filter->setLabel(text.c_str());
1253 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1254 if(iter)
1255 (*iter)[_columns.label] = text;
1256 }
1257 }
1259 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1260 {
1261 Gtk::TreeIter iter = _model->get_iter(path);
1263 if(iter) {
1264 SPDesktop *desktop = _dialog.getDesktop();
1265 SPDocument *doc = sp_desktop_document(desktop);
1266 SPFilter* filter = (*iter)[_columns.filter];
1267 Inkscape::Selection *sel = sp_desktop_selection(desktop);
1269 /* If this filter is the only one used in the selection, unset it */
1270 if((*iter)[_columns.sel] == 1)
1271 filter = 0;
1273 GSList const *items = sel->itemList();
1275 for (GSList const *i = items; i != NULL; i = i->next) {
1276 SPItem * item = SP_ITEM(i->data);
1277 SPStyle *style = SP_OBJECT_STYLE(item);
1278 g_assert(style != NULL);
1280 if(filter)
1281 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1282 else
1283 ::remove_filter(item, false);
1285 SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1286 }
1288 update_selection(sel);
1289 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Apply filter"));
1290 }
1291 }
1293 /* Add all filters in the document to the combobox.
1294 Keeps the same selection if possible, otherwise selects the first element */
1295 void FilterEffectsDialog::FilterModifier::update_filters()
1296 {
1297 SPDesktop* desktop = _dialog.getDesktop();
1298 SPDocument* document = sp_desktop_document(desktop);
1299 const GSList* filters = sp_document_get_resource_list(document, "filter");
1301 _model->clear();
1303 for(const GSList *l = filters; l; l = l->next) {
1304 Gtk::TreeModel::Row row = *_model->append();
1305 SPFilter* f = (SPFilter*)l->data;
1306 row[_columns.filter] = f;
1307 const gchar* lbl = f->label();
1308 const gchar* id = SP_OBJECT_ID(f);
1309 row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1310 }
1312 update_selection(desktop->selection);
1313 _dialog.update_filter_general_settings_view();
1314 }
1316 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1317 {
1318 if(_list.get_selection()) {
1319 Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1321 if(i)
1322 return (*i)[_columns.filter];
1323 }
1325 return 0;
1326 }
1328 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1329 {
1330 if(filter) {
1331 for(Gtk::TreeModel::iterator i = _model->children().begin();
1332 i != _model->children().end(); ++i) {
1333 if((*i)[_columns.filter] == filter) {
1334 _list.get_selection()->select(i);
1335 break;
1336 }
1337 }
1338 }
1339 }
1341 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1342 {
1343 if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1344 const bool sensitive = get_selected_filter() != NULL;
1345 _menu->items()[0].set_sensitive(sensitive);
1346 _menu->items()[1].set_sensitive(sensitive);
1347 _menu->popup(event->button, event->time);
1348 }
1349 }
1351 void FilterEffectsDialog::FilterModifier::add_filter()
1352 {
1353 SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1354 SPFilter* filter = new_filter(doc);
1356 const int count = _model->children().size();
1357 std::ostringstream os;
1358 os << "filter" << count;
1359 filter->setLabel(os.str().c_str());
1361 update_filters();
1363 select_filter(filter);
1365 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1366 }
1368 void FilterEffectsDialog::FilterModifier::remove_filter()
1369 {
1370 SPFilter *filter = get_selected_filter();
1372 if(filter) {
1373 SPDocument* doc = filter->document;
1374 sp_repr_unparent(filter->repr);
1376 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1378 update_filters();
1379 }
1380 }
1382 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1383 {
1384 SPFilter* filter = get_selected_filter();
1386 if(filter) {
1387 Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1388 repr = repr->duplicate(repr->document());
1389 parent->appendChild(repr);
1391 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1393 update_filters();
1394 }
1395 }
1397 void FilterEffectsDialog::FilterModifier::rename_filter()
1398 {
1399 _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1400 }
1402 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1403 : Glib::ObjectBase(typeid(CellRendererConnection)),
1404 _primitive(*this, "primitive", 0)
1405 {}
1407 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1408 {
1409 return _primitive.get_proxy();
1410 }
1412 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1413 {
1414 _text_width = w;
1415 }
1417 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1418 {
1419 return _text_width;
1420 }
1422 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1423 Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1424 int* x_offset, int* y_offset, int* width, int* height) const
1425 {
1426 PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1428 if(x_offset)
1429 (*x_offset) = 0;
1430 if(y_offset)
1431 (*y_offset) = 0;
1432 if(width)
1433 (*width) = size * primlist.primitive_count() + _text_width * 7;
1434 if(height) {
1435 // Scale the height depending on the number of inputs, unless it's
1436 // the first primitive, in which case there are no connections
1437 SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1438 (*height) = size * input_count(prim);
1439 }
1440 }
1442 /*** PrimitiveList ***/
1443 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1444 : _dialog(d),
1445 _in_drag(0),
1446 _observer(new SignalObserver)
1447 {
1448 d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1450 add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1451 signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1453 _model = Gtk::ListStore::create(_columns);
1455 set_reorderable(true);
1457 set_model(_model);
1458 append_column(_("_Effect"), _columns.type);
1460 _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1461 get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1462 signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1464 _connection_cell.set_text_width(init_text());
1466 int cols_count = append_column(_("Connections"), _connection_cell);
1467 Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1468 if(col)
1469 col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1470 }
1472 // Sets up a vertical Pango context/layout, and returns the largest
1473 // width needed to render the FilterPrimitiveInput labels.
1474 int FilterEffectsDialog::PrimitiveList::init_text()
1475 {
1476 // Set up a vertical context+layout
1477 Glib::RefPtr<Pango::Context> context = create_pango_context();
1478 const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1479 context->set_matrix(matrix);
1480 _vertical_layout = Pango::Layout::create(context);
1482 int maxfont = 0;
1483 for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1484 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1485 int fontw, fonth;
1486 _vertical_layout->get_pixel_size(fontw, fonth);
1487 if(fonth > maxfont)
1488 maxfont = fonth;
1489 }
1491 return maxfont;
1492 }
1494 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1495 {
1496 return _signal_primitive_changed;
1497 }
1499 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1500 {
1501 _observer->set(get_selected());
1502 signal_primitive_changed()();
1503 _dialog._color_matrix_values->clear_store();
1504 }
1506 /* Add all filter primitives in the current to the list.
1507 Keeps the same selection if possible, otherwise selects the first element */
1508 void FilterEffectsDialog::PrimitiveList::update()
1509 {
1510 SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1511 const SPFilterPrimitive* active_prim = get_selected();
1512 bool active_found = false;
1514 _model->clear();
1516 if(f) {
1517 _dialog._primitive_box.set_sensitive(true);
1518 _dialog.update_filter_general_settings_view();
1519 for(SPObject *prim_obj = f->children;
1520 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1521 prim_obj = prim_obj->next) {
1522 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1523 if(prim) {
1524 Gtk::TreeModel::Row row = *_model->append();
1525 row[_columns.primitive] = prim;
1526 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1527 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1528 row[_columns.id] = SP_OBJECT_ID(prim);
1530 if(prim == active_prim) {
1531 get_selection()->select(row);
1532 active_found = true;
1533 }
1534 }
1535 }
1537 if(!active_found && _model->children().begin())
1538 get_selection()->select(_model->children().begin());
1540 columns_autosize();
1541 }
1542 else {
1543 _dialog._primitive_box.set_sensitive(false);
1544 }
1545 }
1547 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1548 {
1549 _primitive_menu = menu;
1550 }
1552 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1553 {
1554 if(_dialog._filter_modifier.get_selected_filter()) {
1555 Gtk::TreeModel::iterator i = get_selection()->get_selected();
1556 if(i)
1557 return (*i)[_columns.primitive];
1558 }
1560 return 0;
1561 }
1563 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1564 {
1565 for(Gtk::TreeIter i = _model->children().begin();
1566 i != _model->children().end(); ++i) {
1567 if((*i)[_columns.primitive] == prim)
1568 get_selection()->select(i);
1569 }
1570 }
1572 void FilterEffectsDialog::PrimitiveList::remove_selected()
1573 {
1574 SPFilterPrimitive* prim = get_selected();
1576 if(prim) {
1577 _observer->set(0);
1579 sp_repr_unparent(prim->repr);
1581 sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1582 _("Remove filter primitive"));
1584 update();
1585 }
1586 }
1588 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1589 {
1590 Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1591 Glib::RefPtr<Gdk::Window> win = get_bin_window();
1592 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1594 SPFilterPrimitive* prim = get_selected();
1595 int row_count = get_model()->children().size();
1597 int fheight = CellRendererConnection::size;
1598 Gdk::Rectangle rct, vis;
1599 Gtk::TreeIter row = get_model()->children().begin();
1600 int text_start_x = 0;
1601 if(row) {
1602 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1603 get_visible_rect(vis);
1604 int vis_x, vis_y;
1605 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1607 text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter._length + 1) + 1;
1608 for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1609 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1610 const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1611 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());
1612 get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1613 get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1614 }
1615 }
1617 int row_index = 0;
1618 for(; row != get_model()->children().end(); ++row, ++row_index) {
1619 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1620 const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1622 // Check mouse state
1623 int mx, my;
1624 Gdk::ModifierType mask;
1625 get_bin_window()->get_pointer(mx, my, mask);
1627 // Outline the bottom of the connection area
1628 const int outline_x = x + fheight * (row_count - row_index);
1629 get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1631 // Side outline
1632 get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1634 std::vector<Gdk::Point> con_poly;
1635 int con_drag_y = 0;
1636 bool inside;
1637 const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1638 const int inputs = input_count(row_prim);
1640 if(SP_IS_FEMERGE(row_prim)) {
1641 for(int i = 0; i < inputs; ++i) {
1642 inside = do_connection_node(row, i, con_poly, mx, my);
1643 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1644 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1645 inside, con_poly);
1647 if(_in_drag == (i + 1))
1648 con_drag_y = con_poly[2].get_y();
1650 if(_in_drag != (i + 1) || row_prim != prim)
1651 draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1652 }
1653 }
1654 else {
1655 // Draw "in" shape
1656 inside = do_connection_node(row, 0, con_poly, mx, my);
1657 con_drag_y = con_poly[2].get_y();
1658 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1659 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1660 inside, con_poly);
1662 // Draw "in" connection
1663 if(_in_drag != 1 || row_prim != prim)
1664 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1666 if(inputs == 2) {
1667 // Draw "in2" shape
1668 inside = do_connection_node(row, 1, con_poly, mx, my);
1669 if(_in_drag == 2)
1670 con_drag_y = con_poly[2].get_y();
1671 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1672 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1673 inside, con_poly);
1674 // Draw "in2" connection
1675 if(_in_drag != 2 || row_prim != prim)
1676 draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1677 }
1678 }
1680 // Draw drag connection
1681 if(row_prim == prim && _in_drag) {
1682 get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1683 mx, con_drag_y);
1684 get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1685 }
1686 }
1688 return true;
1689 }
1691 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1692 const int text_start_x, const int x1, const int y1,
1693 const int row_count)
1694 {
1695 int src_id = 0;
1696 Gtk::TreeIter res = find_result(input, attr, src_id);
1697 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1698 Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1699 Glib::RefPtr<Gdk::GC> gc;
1701 const bool is_first = input == get_model()->children().begin();
1702 const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1703 const bool use_default = !res && !is_merge;
1705 if(res == input || (use_default && is_first)) {
1706 // Draw straight connection to a standard input
1707 // Draw a lighter line for an implicit connection to a standard input
1708 const int tw = _connection_cell.get_text_width();
1709 gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1710 gc = (use_default && is_first) ? lightgc : darkgc;
1711 get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1712 get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1713 }
1714 else {
1715 // Draw an 'L'-shaped connection to another filter primitive
1716 // If no connection is specified, draw a light connection to the previous primitive
1717 gc = use_default ? lightgc : darkgc;
1719 if(use_default) {
1720 res = input;
1721 --res;
1722 }
1724 if(res) {
1725 Gdk::Rectangle rct;
1727 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1728 const int fheight = CellRendererConnection::size;
1730 get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1731 const int row_index = find_index(res);
1732 const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1733 const int y2 = rct.get_y() + rct.get_height();
1735 // Draw a bevelled 'L'-shaped connection
1736 get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1737 get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1738 get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1739 }
1740 }
1741 }
1743 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1744 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1745 std::vector<Gdk::Point>& points,
1746 const int ix, const int iy)
1747 {
1748 Gdk::Rectangle rct;
1749 const int icnt = input_count((*row)[_columns.primitive]);
1751 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1752 const int fheight = CellRendererConnection::size;
1754 get_cell_area(_model->get_path(row), *get_column(1), rct);
1755 const float h = rct.get_height() / icnt;
1757 const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1758 const int con_w = (int)(fheight * 0.35f);
1759 const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1760 points.clear();
1761 points.push_back(Gdk::Point(x, con_y));
1762 points.push_back(Gdk::Point(x, con_y + con_w * 2));
1763 points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1765 return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1766 }
1768 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1769 const int attr, int& src_id)
1770 {
1771 SPFilterPrimitive* prim = (*start)[_columns.primitive];
1772 Gtk::TreeIter target = _model->children().end();
1773 int image = 0;
1775 if(SP_IS_FEMERGE(prim)) {
1776 int c = 0;
1777 bool found = false;
1778 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1779 if(c == attr && SP_IS_FEMERGENODE(o)) {
1780 image = SP_FEMERGENODE(o)->input;
1781 found = true;
1782 }
1783 }
1784 if(!found)
1785 return target;
1786 }
1787 else {
1788 if(attr == SP_ATTR_IN)
1789 image = prim->image_in;
1790 else if(attr == SP_ATTR_IN2) {
1791 if(SP_IS_FEBLEND(prim))
1792 image = SP_FEBLEND(prim)->in2;
1793 else if(SP_IS_FECOMPOSITE(prim))
1794 image = SP_FECOMPOSITE(prim)->in2;
1795 else if(SP_IS_FEDISPLACEMENTMAP(prim))
1796 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1797 else
1798 return target;
1799 }
1800 else
1801 return target;
1802 }
1804 if(image >= 0) {
1805 for(Gtk::TreeIter i = _model->children().begin();
1806 i != start; ++i) {
1807 if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1808 target = i;
1809 }
1810 return target;
1811 }
1812 else if(image < -1) {
1813 src_id = -(image + 2);
1814 return start;
1815 }
1817 return target;
1818 }
1820 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1821 {
1822 int i = 0;
1823 for(Gtk::TreeIter iter = _model->children().begin();
1824 iter != target; ++iter, ++i){};
1825 return i;
1826 }
1828 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1829 {
1830 Gtk::TreePath path;
1831 Gtk::TreeViewColumn* col;
1832 const int x = (int)e->x, y = (int)e->y;
1833 int cx, cy;
1835 _drag_prim = 0;
1837 if(get_path_at_pos(x, y, path, col, cx, cy)) {
1838 Gtk::TreeIter iter = _model->get_iter(path);
1839 std::vector<Gdk::Point> points;
1841 _drag_prim = (*iter)[_columns.primitive];
1842 const int icnt = input_count(_drag_prim);
1844 for(int i = 0; i < icnt; ++i) {
1845 if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1846 _in_drag = i + 1;
1847 break;
1848 }
1849 }
1851 queue_draw();
1852 }
1854 if(_in_drag) {
1855 _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1856 _autoscroll = 0;
1857 get_selection()->select(path);
1858 return true;
1859 }
1860 else
1861 return Gtk::TreeView::on_button_press_event(e);
1862 }
1864 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1865 {
1866 const int speed = 10;
1867 const int limit = 15;
1869 Gdk::Rectangle vis;
1870 get_visible_rect(vis);
1871 int vis_x, vis_y;
1872 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1873 const int top = vis_y + vis.get_height();
1875 // When autoscrolling during a connection drag, set the speed based on
1876 // where the mouse is in relation to the edges.
1877 if(e->y < vis_y)
1878 _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1879 else if(e->y < vis_y + limit)
1880 _autoscroll = -speed;
1881 else if(e->y > top)
1882 _autoscroll = (int)(speed + (e->y - top) / 5);
1883 else if(e->y > top - limit)
1884 _autoscroll = speed;
1885 else
1886 _autoscroll = 0;
1888 queue_draw();
1890 return Gtk::TreeView::on_motion_notify_event(e);
1891 }
1893 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1894 {
1895 SPFilterPrimitive *prim = get_selected(), *target;
1897 _scroll_connection.disconnect();
1899 if(_in_drag && prim) {
1900 Gtk::TreePath path;
1901 Gtk::TreeViewColumn* col;
1902 int cx, cy;
1904 if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1905 const gchar *in_val = 0;
1906 Glib::ustring result;
1907 Gtk::TreeIter target_iter = _model->get_iter(path);
1908 target = (*target_iter)[_columns.primitive];
1909 col = get_column(1);
1911 Gdk::Rectangle rct;
1912 get_cell_area(path, *col, rct);
1913 const int twidth = _connection_cell.get_text_width();
1914 const int sources_x = rct.get_width() - twidth * FPInputConverter._length;
1915 if(cx > sources_x) {
1916 int src = (cx - sources_x) / twidth;
1917 if (src < 0) {
1918 src = 0;
1919 } else if(src >= static_cast<int>(FPInputConverter._length)) {
1920 src = FPInputConverter._length - 1;
1921 }
1922 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1923 in_val = result.c_str();
1924 }
1925 else {
1926 // Ensure that the target comes before the selected primitive
1927 for(Gtk::TreeIter iter = _model->children().begin();
1928 iter != get_selection()->get_selected(); ++iter) {
1929 if(iter == target_iter) {
1930 Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1931 // Make sure the target has a result
1932 const gchar *gres = repr->attribute("result");
1933 if(!gres) {
1934 result = sp_filter_get_new_result_name(SP_FILTER(prim->parent));
1935 repr->setAttribute("result", result.c_str());
1936 in_val = result.c_str();
1937 }
1938 else
1939 in_val = gres;
1940 break;
1941 }
1942 }
1943 }
1945 if(SP_IS_FEMERGE(prim)) {
1946 int c = 1;
1947 bool handled = false;
1948 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1949 if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1950 // If input is null, delete it
1951 if(!in_val) {
1952 sp_repr_unparent(o->repr);
1953 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1954 _("Remove merge node"));
1955 (*get_selection()->get_selected())[_columns.primitive] = prim;
1956 }
1957 else
1958 _dialog.set_attr(o, SP_ATTR_IN, in_val);
1959 handled = true;
1960 }
1961 }
1962 // Add new input?
1963 if(!handled && c == _in_drag && in_val) {
1964 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1965 Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1966 repr->setAttribute("inkscape:collect", "always");
1967 prim->repr->appendChild(repr);
1968 SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1969 Inkscape::GC::release(repr);
1970 _dialog.set_attr(node, SP_ATTR_IN, in_val);
1971 (*get_selection()->get_selected())[_columns.primitive] = prim;
1972 }
1973 }
1974 else {
1975 if(_in_drag == 1)
1976 _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1977 else if(_in_drag == 2)
1978 _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1979 }
1980 }
1982 _in_drag = 0;
1983 queue_draw();
1985 _dialog.update_settings_view();
1986 }
1988 if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1989 const bool sensitive = get_selected() != NULL;
1990 _primitive_menu->items()[0].set_sensitive(sensitive);
1991 _primitive_menu->items()[1].set_sensitive(sensitive);
1992 _primitive_menu->popup(e->button, e->time);
1994 return true;
1995 }
1996 else
1997 return Gtk::TreeView::on_button_release_event(e);
1998 }
2000 // Checks all of prim's inputs, removes any that use result
2001 void check_single_connection(SPFilterPrimitive* prim, const int result)
2002 {
2003 if(prim && result >= 0) {
2005 if(prim->image_in == result)
2006 SP_OBJECT_REPR(prim)->setAttribute("in", 0);
2008 if(SP_IS_FEBLEND(prim)) {
2009 if(SP_FEBLEND(prim)->in2 == result)
2010 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2011 }
2012 else if(SP_IS_FECOMPOSITE(prim)) {
2013 if(SP_FECOMPOSITE(prim)->in2 == result)
2014 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2015 }
2016 else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
2017 if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
2018 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2019 }
2020 }
2021 }
2023 // Remove any connections going to/from prim_iter that forward-reference other primitives
2024 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
2025 {
2026 SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
2027 bool before = true;
2029 for(Gtk::TreeIter iter = _model->children().begin();
2030 iter != _model->children().end(); ++iter) {
2031 if(iter == prim_iter)
2032 before = false;
2033 else {
2034 SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
2035 if(before)
2036 check_single_connection(cur_prim, prim->image_out);
2037 else
2038 check_single_connection(prim, cur_prim->image_out);
2039 }
2040 }
2041 }
2043 // Reorder the filter primitives to match the list order
2044 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2045 {
2046 SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2047 int ndx = 0;
2049 for(Gtk::TreeModel::iterator iter = _model->children().begin();
2050 iter != _model->children().end(); ++iter, ++ndx) {
2051 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2052 if(prim && prim == _drag_prim) {
2053 SP_OBJECT_REPR(prim)->setPosition(ndx);
2054 break;
2055 }
2056 }
2058 for(Gtk::TreeModel::iterator iter = _model->children().begin();
2059 iter != _model->children().end(); ++iter, ++ndx) {
2060 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2061 if(prim && prim == _drag_prim) {
2062 sanitize_connections(iter);
2063 get_selection()->select(iter);
2064 break;
2065 }
2066 }
2068 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2070 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2071 }
2073 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2074 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2075 {
2076 if(_autoscroll) {
2077 Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2078 double v;
2080 v = a.get_value() + _autoscroll;
2081 if(v < 0)
2082 v = 0;
2083 if(v > a.get_upper() - a.get_page_size())
2084 v = a.get_upper() - a.get_page_size();
2086 a.set_value(v);
2088 queue_draw();
2089 }
2091 return true;
2092 }
2094 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2095 {
2096 return _model->children().size();
2097 }
2099 /*** FilterEffectsDialog ***/
2101 FilterEffectsDialog::FilterEffectsDialog()
2102 : UI::Widget::Panel("", "/dialogs/filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2103 _add_primitive_type(FPConverter),
2104 _add_primitive(_("Add Effect:")),
2105 _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2106 _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2107 _settings_initialized(false),
2108 _locked(false),
2109 _attr_lock(false),
2110 _filter_modifier(*this),
2111 _primitive_list(*this)
2112 {
2113 _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2114 NR_FILTER_ENDPRIMITIVETYPE);
2115 _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2116 1);
2117 _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2118 _sizegroup->set_ignore_hidden();
2120 _add_primitive_type.remove_row(NR_FILTER_TILE);
2121 _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2123 // Initialize widget hierarchy
2124 Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2125 Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2126 Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox(/*homogeneous:*/false, /*spacing:*/4));
2127 Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2129 _getContents()->add(*hpaned);
2130 hpaned->pack1(_filter_modifier);
2131 hpaned->pack2(_primitive_box);
2132 _primitive_box.pack_start(*sw_prims);
2133 _primitive_box.pack_start(*hb_prims, false, false);
2134 _primitive_box.pack_start(*infobox,false, false);
2135 sw_prims->add(_primitive_list);
2136 infobox->pack_start(_infobox_icon, false, false);
2137 infobox->pack_start(_infobox_desc, false, false);
2138 _infobox_desc.set_line_wrap(true);
2139 _infobox_desc.set_size_request(200, -1);
2141 hb_prims->pack_start(_add_primitive, false, false);
2142 hb_prims->pack_start(_add_primitive_type, false, false);
2143 _getContents()->pack_start(_settings_tabs, false, false);
2144 _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2145 _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2147 _primitive_list.signal_primitive_changed().connect(
2148 sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2149 _filter_modifier.signal_filter_changed().connect(
2150 sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2152 _add_primitive_type.signal_changed().connect(
2153 sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2155 sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2156 sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2157 // al_settings->set_padding(0, 0, 12, 0);
2158 // fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2159 // ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2160 _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2161 _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2162 sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2164 show_all_children();
2165 init_settings_widgets();
2166 _primitive_list.update();
2167 update_primitive_infobox();
2168 }
2170 FilterEffectsDialog::~FilterEffectsDialog()
2171 {
2172 delete _settings;
2173 delete _filter_general_settings;
2174 }
2176 void FilterEffectsDialog::set_attrs_locked(const bool l)
2177 {
2178 _locked = l;
2179 }
2181 void FilterEffectsDialog::show_all_vfunc()
2182 {
2183 UI::Widget::Panel::show_all_vfunc();
2185 update_settings_view();
2186 }
2188 void FilterEffectsDialog::init_settings_widgets()
2189 {
2190 // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2191 // most of the current values are complete guesses!
2193 _empty_settings.set_sensitive(false);
2194 _settings_tab1.pack_start(_empty_settings);
2196 _no_filter_selected.set_sensitive(false);
2197 _settings_tab2.pack_start(_no_filter_selected);
2198 _settings_initialized = true;
2200 _filter_general_settings->type(0);
2201 _filter_general_settings->add_multispinbutton(/*default x:*/ (double) -0.1, /*default y:*/ (double) -0.1, SP_ATTR_X, SP_ATTR_Y, _("Coordinates"), -100, 100, 0.01, 0.1, 2, _("X coordinate of the left corners of filter effects region"), _("Y coordinate of the upper corners of filter effects region"));
2202 _filter_general_settings->add_multispinbutton(/*default width:*/ (double) 1.2, /*default height:*/ (double) 1.2, SP_ATTR_WIDTH, SP_ATTR_HEIGHT, _("Dimensions"), 0, 1000, 0.01, 0.1, 2, _("Width of filter effects region"), _("Height of filter effects region"));
2204 _settings->type(NR_FILTER_BLEND);
2205 _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode"), BlendModeConverter);
2207 _settings->type(NR_FILTER_COLORMATRIX);
2208 ComboBoxEnum<FilterColorMatrixType>* colmat = _settings->add_combo(COLORMATRIX_MATRIX, SP_ATTR_TYPE, _("Type"), ColorMatrixTypeConverter, _("Indicates the type of matrix operation. The keyword 'matrix' indicates that a full 5x4 matrix of values will be provided. The other keywords represent convenience shortcuts to allow commonly used color operations to be performed without specifying a complete matrix."));
2209 _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s)"));
2210 colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2212 _settings->type(NR_FILTER_COMPONENTTRANSFER);
2213 _settings->add_notimplemented();
2214 //TRANSLATORS: for info on "Slope" and "Intercept", see http://id.mind.net/~zona/mmts/functionInstitute/linearFunctions/lsif.html
2215 /*_settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2216 _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2217 _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2218 _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2219 _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2220 _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2222 _settings->type(NR_FILTER_COMPOSITE);
2223 _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
2224 _k1 = _settings->add_spinslider(0, SP_ATTR_K1, _("K1"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2225 _k2 = _settings->add_spinslider(0, SP_ATTR_K2, _("K2"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2226 _k3 = _settings->add_spinslider(0, SP_ATTR_K3, _("K3"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2227 _k4 = _settings->add_spinslider(0, SP_ATTR_K4, _("K4"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2229 _settings->type(NR_FILTER_CONVOLVEMATRIX);
2230 _convolve_order = _settings->add_dualspinbutton((char*)"3", SP_ATTR_ORDER, _("Size"), 1, 5, 1, 1, 0, _("width of the convolve matrix"), _("height of the convolve matrix"));
2231 _convolve_target = _settings->add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, SP_ATTR_TARGETX, SP_ATTR_TARGETY, _("Target"), 0, 4, 1, 1, 0, _("X coordinate of the target point in the convolve matrix. The convolution is applied to pixels around this point."), _("Y coordinate of the target point in the convolve matrix. The convolution is applied to pixels around this point."));
2232 //TRANSLATORS: for info on "Kernel", see http://en.wikipedia.org/wiki/Kernel_(matrix)
2233 _convolve_matrix = _settings->add_matrix(SP_ATTR_KERNELMATRIX, _("Kernel"), _("This matrix describes the convolve operation that is applied to the input image in order to calculate the pixel colors at the output. Different arrangements of values in this matrix result in various possible visual effects. An identity matrix would lead to a motion blur effect (parallel to the matrix diagonal) while a matrix filled with a constant non-zero value would lead to a common blur effect."));
2234 _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2235 //TODO: svg spec: The default value is the sum of all values in kernelMatrix, with the exception that if the sum is zero, then the divisor is set to 1.
2236 _settings->add_spinslider(1, SP_ATTR_DIVISOR, _("Divisor"), 1, 20, 1, 0.1, 2, _("After applying the kernelMatrix to the input image to yield a number, that number is divided by divisor to yield the final destination color value. A divisor that is the sum of all the matrix values tends to have an evening effect on the overall color intensity of the result."));
2237 _settings->add_spinslider(0, SP_ATTR_BIAS, _("Bias"), -10, 10, 1, 0.01, 1, _("This value is added to each component. This is useful to define a constant value as the zero response of the filter."));
2238 _settings->add_combo(CONVOLVEMATRIX_EDGEMODE_DUPLICATE, SP_ATTR_EDGEMODE, _("Edge Mode"), ConvolveMatrixEdgeModeConverter, _("Determines how to extend the input image as necessary with color values so that the matrix operations can be applied when the kernel is positioned at or near the edge of the input image."));
2239 _settings->add_checkbutton(false, SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive."));
2241 _settings->type(NR_FILTER_DIFFUSELIGHTING);
2242 _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Diffuse Color"), _("Defines the color of the light source"));
2243 _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2244 _settings->add_spinslider(1, SP_ATTR_DIFFUSECONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2245 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2246 _settings->add_lightsource();
2248 _settings->type(NR_FILTER_DISPLACEMENTMAP);
2249 _settings->add_spinslider(0, SP_ATTR_SCALE, _("Scale"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2250 _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2251 _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2253 _settings->type(NR_FILTER_FLOOD);
2254 _settings->add_color(/*default: black*/ 0, SP_PROP_FLOOD_COLOR, _("Flood Color"), _("The whole filter region will be filled with this color."));
2255 _settings->add_spinslider(1, SP_PROP_FLOOD_OPACITY, _("Opacity"), 0, 1, 0.1, 0.01, 2);
2257 _settings->type(NR_FILTER_GAUSSIANBLUR);
2258 _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2260 _settings->type(NR_FILTER_MERGE);
2261 _settings->add_no_params();
2263 _settings->type(NR_FILTER_MORPHOLOGY);
2264 _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2265 _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius"), 0, 100, 1, 0.01, 1);
2267 _settings->type(NR_FILTER_IMAGE);
2268 _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image"));
2270 _settings->type(NR_FILTER_OFFSET);
2271 _settings->add_spinslider(0, SP_ATTR_DX, _("Delta X"), -100, 100, 1, 0.01, 1, _("This is how far the input image gets shifted to the right"));
2272 _settings->add_spinslider(0, SP_ATTR_DY, _("Delta Y"), -100, 100, 1, 0.01, 1, _("This is how far the input image gets shifted downwards"));
2274 _settings->type(NR_FILTER_SPECULARLIGHTING);
2275 _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Specular Color"), _("Defines the color of the light source"));
2276 _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2277 _settings->add_spinslider(1, SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2278 _settings->add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2279 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2280 _settings->add_lightsource();
2282 _settings->type(NR_FILTER_TILE);
2283 _settings->add_notimplemented();
2285 _settings->type(NR_FILTER_TURBULENCE);
2286 // _settings->add_checkbutton(false, SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2287 _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2288 _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency"), 0, 0.4, 0.001, 0.01, 3);
2289 _settings->add_spinslider(1, SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2290 _settings->add_spinslider(0, SP_ATTR_SEED, _("Seed"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2291 }
2293 void FilterEffectsDialog::add_primitive()
2294 {
2295 SPFilter* filter = _filter_modifier.get_selected_filter();
2297 if(filter) {
2298 SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2300 _primitive_list.select(prim);
2302 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2303 }
2304 }
2306 void FilterEffectsDialog::update_primitive_infobox()
2307 {
2308 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2309 if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2310 _infobox_icon.show();
2311 _infobox_desc.show();
2312 } else {
2313 _infobox_icon.hide();
2314 _infobox_desc.hide();
2315 }
2316 switch(_add_primitive_type.get_active_data()->id){
2317 case(NR::NR_FILTER_BLEND):
2318 _infobox_icon.set(g_strdup_printf("%s/feBlend-icon.png", INKSCAPE_PIXMAPDIR));
2319 _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2320 break;
2321 case(NR::NR_FILTER_COLORMATRIX):
2322 _infobox_icon.set(g_strdup_printf("%s/feColorMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2323 _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."));
2324 break;
2325 case(NR::NR_FILTER_COMPONENTTRANSFER):
2326 _infobox_icon.set(g_strdup_printf("%s/feComponentTransfer-icon.png", INKSCAPE_PIXMAPDIR));
2327 _infobox_desc.set_markup(_("The <b>feComponentTransfer</b> filter primitive manipulates the input's color components (red, green, blue, and alpha) according to particular transfer functions, allowing operations like brightness and contrast adjustment, color balance, and thresholding."));
2328 break;
2329 case(NR::NR_FILTER_COMPOSITE):
2330 _infobox_icon.set(g_strdup_printf("%s/feComposite-icon.png", INKSCAPE_PIXMAPDIR));
2331 _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."));
2332 break;
2333 case(NR::NR_FILTER_CONVOLVEMATRIX):
2334 _infobox_icon.set(g_strdup_printf("%s/feConvolveMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2335 _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."));
2336 break;
2337 case(NR::NR_FILTER_DIFFUSELIGHTING):
2338 _infobox_icon.set(g_strdup_printf("%s/feDiffuseLighting-icon.png", INKSCAPE_PIXMAPDIR));
2339 _infobox_desc.set_markup(_("The <b>feDiffuseLighting</b> and feSpecularLighting filter primitives create \"embossed\" shadings. The input's alpha channel is used to provide depth information: higher opacity areas are raised toward the viewer and lower opacity areas recede away from the viewer."));
2340 break;
2341 case(NR::NR_FILTER_DISPLACEMENTMAP):
2342 _infobox_icon.set(g_strdup_printf("%s/feDisplacementMap-icon.png", INKSCAPE_PIXMAPDIR));
2343 _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."));
2344 break;
2345 case(NR::NR_FILTER_FLOOD):
2346 _infobox_icon.set(g_strdup_printf("%s/feFlood-icon.png", INKSCAPE_PIXMAPDIR));
2347 _infobox_desc.set_markup(_("The <b>feFlood</b> filter primitive fills the region with a given color and opacity. It is usually used as an input to other filters to apply color to a graphic."));
2348 break;
2349 case(NR::NR_FILTER_GAUSSIANBLUR):
2350 _infobox_icon.set(g_strdup_printf("%s/feGaussianBlur-icon.png", INKSCAPE_PIXMAPDIR));
2351 _infobox_desc.set_markup(_("The <b>feGaussianBlur</b> filter primitive uniformly blurs its input. It is commonly used together with feOffset to create a drop shadow effect."));
2352 break;
2353 case(NR::NR_FILTER_IMAGE):
2354 _infobox_icon.set(g_strdup_printf("%s/feImage-icon.png", INKSCAPE_PIXMAPDIR));
2355 _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2356 break;
2357 case(NR::NR_FILTER_MERGE):
2358 _infobox_icon.set(g_strdup_printf("%s/feMerge-icon.png", INKSCAPE_PIXMAPDIR));
2359 _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."));
2360 break;
2361 case(NR::NR_FILTER_MORPHOLOGY):
2362 _infobox_icon.set(g_strdup_printf("%s/feMorphology-icon.png", INKSCAPE_PIXMAPDIR));
2363 _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."));
2364 break;
2365 case(NR::NR_FILTER_OFFSET):
2366 _infobox_icon.set(g_strdup_printf("%s/feOffset-icon.png", INKSCAPE_PIXMAPDIR));
2367 _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."));
2368 break;
2369 case(NR::NR_FILTER_SPECULARLIGHTING):
2370 _infobox_icon.set(g_strdup_printf("%s/feSpecularLighting-icon.png", INKSCAPE_PIXMAPDIR));
2371 _infobox_desc.set_markup(_("The feDiffuseLighting and <b>feSpecularLighting</b> filter primitives create \"embossed\" shadings. The input's alpha channel is used to provide depth information: higher opacity areas are raised toward the viewer and lower opacity areas recede away from the viewer."));
2372 break;
2373 case(NR::NR_FILTER_TILE):
2374 _infobox_icon.set(g_strdup_printf("%s/feTile-icon.png", INKSCAPE_PIXMAPDIR));
2375 _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2376 break;
2377 case(NR::NR_FILTER_TURBULENCE):
2378 _infobox_icon.set(g_strdup_printf("%s/feTurbulence-icon.png", INKSCAPE_PIXMAPDIR));
2379 _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."));
2380 break;
2381 default:
2382 g_assert(false);
2383 break;
2384 }
2385 }
2387 void FilterEffectsDialog::duplicate_primitive()
2388 {
2389 SPFilter* filter = _filter_modifier.get_selected_filter();
2390 SPFilterPrimitive* origprim = _primitive_list.get_selected();
2392 if(filter && origprim) {
2393 Inkscape::XML::Node *repr;
2394 repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2395 SP_OBJECT_REPR(filter)->appendChild(repr);
2397 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2399 _primitive_list.update();
2400 }
2401 }
2403 void FilterEffectsDialog::convolve_order_changed()
2404 {
2405 _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2406 _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2407 _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2408 }
2410 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2411 {
2412 set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2413 }
2415 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2416 {
2417 if(!_locked) {
2418 _attr_lock = true;
2419 SPFilter *filter = _filter_modifier.get_selected_filter();
2420 const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2421 if (filter && name && SP_OBJECT_REPR(filter)){
2422 SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2423 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2424 }
2425 _attr_lock = false;
2426 }
2427 }
2429 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2430 {
2431 set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2432 }
2434 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2435 {
2436 if(!_locked) {
2437 _attr_lock = true;
2439 SPFilter *filter = _filter_modifier.get_selected_filter();
2440 const gchar* name = (const gchar*)sp_attribute_name(attr);
2441 if(filter && name && o) {
2442 update_settings_sensitivity();
2444 SP_OBJECT_REPR(o)->setAttribute(name, val);
2445 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2447 Glib::ustring undokey = "filtereffects:";
2448 undokey += name;
2449 sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2450 _("Set filter primitive attribute"));
2451 }
2453 _attr_lock = false;
2454 }
2455 }
2457 void FilterEffectsDialog::update_filter_general_settings_view()
2458 {
2459 if(_settings_initialized != true) return;
2461 if(!_locked) {
2462 _attr_lock = true;
2464 SPFilter* filter = _filter_modifier.get_selected_filter();
2466 if(filter) {
2467 _filter_general_settings->show_and_update(0, filter);
2468 _no_filter_selected.hide();
2469 }
2470 else {
2471 std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2472 vect[0]->hide_all();
2473 _no_filter_selected.show();
2474 }
2476 _attr_lock = false;
2477 }
2478 }
2480 void FilterEffectsDialog::update_settings_view()
2481 {
2482 update_settings_sensitivity();
2484 if(_attr_lock)
2485 return;
2487 //First Tab
2489 std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2490 for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2491 _empty_settings.show();
2493 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2494 if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2495 _infobox_icon.show();
2496 _infobox_desc.show();
2497 } else {
2498 _infobox_icon.hide();
2499 _infobox_desc.hide();
2500 }
2502 SPFilterPrimitive* prim = _primitive_list.get_selected();
2504 if(prim) {
2505 _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2506 _empty_settings.hide();
2507 }
2509 //Second Tab
2511 std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2512 vect2[0]->hide_all();
2513 _no_filter_selected.show();
2515 SPFilter* filter = _filter_modifier.get_selected_filter();
2517 if(filter) {
2518 _filter_general_settings->show_and_update(0, filter);
2519 _no_filter_selected.hide();
2520 }
2522 }
2524 void FilterEffectsDialog::update_settings_sensitivity()
2525 {
2526 SPFilterPrimitive* prim = _primitive_list.get_selected();
2527 const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2528 _k1->set_sensitive(use_k);
2529 _k2->set_sensitive(use_k);
2530 _k3->set_sensitive(use_k);
2531 _k4->set_sensitive(use_k);
2533 // Component transfer not yet implemented
2534 /*
2535 if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2536 SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2537 const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2538 const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2540 _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2541 _ct_slope->set_sensitive(linear);
2542 _ct_intercept->set_sensitive(linear);
2543 _ct_amplitude->set_sensitive(gamma);
2544 _ct_exponent->set_sensitive(gamma);
2545 _ct_offset->set_sensitive(gamma);
2546 }
2547 */
2548 }
2550 void FilterEffectsDialog::update_color_matrix()
2551 {
2552 _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2553 }
2555 } // namespace Dialog
2556 } // namespace UI
2557 } // namespace Inkscape
2559 /*
2560 Local Variables:
2561 mode:c++
2562 c-file-style:"stroustrup"
2563 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2564 indent-tabs-mode:nil
2565 fill-column:99
2566 End:
2567 */
2568 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :