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 <juca@members.fsf.org>
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/gtk.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 "desktop.h"
31 #include "desktop-handles.h"
32 #include "dialog-manager.h"
33 #include "dir-util.h"
34 #include "document.h"
35 #include "filter-chemistry.h"
36 #include "filter-effects-dialog.h"
37 #include "filter-enums.h"
38 #include "inkscape.h"
39 #include "path-prefix.h"
40 #include "preferences.h"
41 #include "selection.h"
42 #include "filters/blend.h"
43 #include "filters/colormatrix.h"
44 #include "filters/componenttransfer.h"
45 #include "filters/composite.h"
46 #include "filters/convolvematrix.h"
47 #include "filters/displacementmap.h"
48 #include "filters/distantlight.h"
49 #include "filters/merge.h"
50 #include "filters/mergenode.h"
51 #include "filters/offset.h"
52 #include "filters/pointlight.h"
53 #include "filters/spotlight.h"
54 #include "sp-filter-primitive.h"
55 #include "sp-gaussian-blur.h"
57 #include "style.h"
58 #include "svg/svg-color.h"
59 #include "ui/dialog/filedialog.h"
60 #include "verbs.h"
61 #include "xml/node.h"
62 #include "xml/node-observer.h"
63 #include "xml/repr.h"
64 #include <sstream>
66 #include "io/sys.h"
67 #include <iostream>
69 using namespace Inkscape::Filters;
71 namespace Inkscape {
72 namespace UI {
73 namespace Dialog {
75 using Inkscape::UI::Widget::AttrWidget;
76 using Inkscape::UI::Widget::ComboBoxEnum;
77 using Inkscape::UI::Widget::DualSpinSlider;
78 using Inkscape::UI::Widget::SpinSlider;
81 // Returns the number of inputs available for the filter primitive type
82 int input_count(const SPFilterPrimitive* prim)
83 {
84 if(!prim)
85 return 0;
86 else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
87 return 2;
88 else if(SP_IS_FEMERGE(prim)) {
89 // Return the number of feMergeNode connections plus an extra
90 int count = 1;
91 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count){};
92 return count;
93 }
94 else
95 return 1;
96 }
98 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
99 {
100 public:
101 CheckButtonAttr(bool def, const Glib::ustring& label,
102 const Glib::ustring& tv, const Glib::ustring& fv,
103 const SPAttributeEnum a, char* tip_text)
104 : Gtk::CheckButton(label),
105 AttrWidget(a, def),
106 _true_val(tv), _false_val(fv)
107 {
108 signal_toggled().connect(signal_attr_changed().make_slot());
109 if (tip_text) _tt.set_tip(*this, tip_text);
110 }
112 Glib::ustring get_as_attribute() const
113 {
114 return get_active() ? _true_val : _false_val;
115 }
117 void set_from_attribute(SPObject* o)
118 {
119 const gchar* val = attribute_value(o);
120 if(val) {
121 if(_true_val == val)
122 set_active(true);
123 else if(_false_val == val)
124 set_active(false);
125 } else {
126 set_active(get_default()->as_bool());
127 }
128 }
129 private:
130 const Glib::ustring _true_val, _false_val;
131 };
133 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
134 {
135 public:
136 SpinButtonAttr(double lower, double upper, double step_inc,
137 double climb_rate, int digits, const SPAttributeEnum a, double def, char* tip_text)
138 : Gtk::SpinButton(climb_rate, digits),
139 AttrWidget(a, def)
140 {
141 if (tip_text) _tt.set_tip(*this, tip_text);
142 set_range(lower, upper);
143 set_increments(step_inc, 0);
145 signal_value_changed().connect(signal_attr_changed().make_slot());
146 }
148 Glib::ustring get_as_attribute() const
149 {
150 const double val = get_value();
152 if(get_digits() == 0)
153 return Glib::Ascii::dtostr((int)val);
154 else
155 return Glib::Ascii::dtostr(val);
156 }
158 void set_from_attribute(SPObject* o)
159 {
160 const gchar* val = attribute_value(o);
161 if(val){
162 set_value(Glib::Ascii::strtod(val));
163 } else {
164 set_value(get_default()->as_double());
165 }
166 }
167 };
169 template< typename T> class ComboWithTooltip : public Gtk::EventBox
170 {
171 public:
172 ComboWithTooltip<T>(T default_value, const Util::EnumDataConverter<T>& c, const SPAttributeEnum a = SP_ATTR_INVALID, char* tip_text = NULL)
173 {
174 if (tip_text) {
175 _tt.set_tip(*this, tip_text);
176 }
177 combo = new ComboBoxEnum<T>(default_value, c, a, false);
178 add(*combo);
179 show_all();
180 }
182 ~ComboWithTooltip()
183 {
184 delete combo;
185 }
187 ComboBoxEnum<T>* get_attrwidget()
188 {
189 return combo;
190 }
191 private:
192 Gtk::Tooltips _tt;
193 ComboBoxEnum<T>* combo;
194 };
196 // Contains an arbitrary number of spin buttons that use seperate attributes
197 class MultiSpinButton : public Gtk::HBox
198 {
199 public:
200 MultiSpinButton(double lower, double upper, double step_inc,
201 double climb_rate, int digits, std::vector<SPAttributeEnum> attrs, std::vector<double> default_values, std::vector<char*> tip_text)
202 {
203 g_assert(attrs.size()==default_values.size());
204 g_assert(attrs.size()==tip_text.size());
205 for(unsigned i = 0; i < attrs.size(); ++i) {
206 _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i], default_values[i], tip_text[i]));
207 pack_start(*_spins.back(), false, false);
208 }
209 }
211 ~MultiSpinButton()
212 {
213 for(unsigned i = 0; i < _spins.size(); ++i)
214 delete _spins[i];
215 }
217 std::vector<SpinButtonAttr*>& get_spinbuttons()
218 {
219 return _spins;
220 }
221 private:
222 std::vector<SpinButtonAttr*> _spins;
223 };
225 // Contains two spinbuttons that describe a NumberOptNumber
226 class DualSpinButton : public Gtk::HBox, public AttrWidget
227 {
228 public:
229 DualSpinButton(char* def, double lower, double upper, double step_inc,
230 double climb_rate, int digits, const SPAttributeEnum a, char* tt1, char* tt2)
231 : AttrWidget(a, def), //TO-DO: receive default num-opt-num as parameter in the constructor
232 _s1(climb_rate, digits), _s2(climb_rate, digits)
233 {
234 if (tt1) _tt.set_tip(_s1, tt1);
235 if (tt2) _tt.set_tip(_s2, tt2);
236 _s1.set_range(lower, upper);
237 _s2.set_range(lower, upper);
238 _s1.set_increments(step_inc, 0);
239 _s2.set_increments(step_inc, 0);
241 _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
242 _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
244 pack_start(_s1, false, false);
245 pack_start(_s2, false, false);
246 }
248 Gtk::SpinButton& get_spinbutton1()
249 {
250 return _s1;
251 }
253 Gtk::SpinButton& get_spinbutton2()
254 {
255 return _s2;
256 }
258 virtual Glib::ustring get_as_attribute() const
259 {
260 double v1 = _s1.get_value();
261 double v2 = _s2.get_value();
263 if(_s1.get_digits() == 0) {
264 v1 = (int)v1;
265 v2 = (int)v2;
266 }
268 return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
269 }
271 virtual void set_from_attribute(SPObject* o)
272 {
273 const gchar* val = attribute_value(o);
274 NumberOptNumber n;
275 if(val) {
276 n.set(val);
277 } else {
278 n.set(get_default()->as_charptr());
279 }
280 _s1.set_value(n.getNumber());
281 _s2.set_value(n.getOptNumber());
283 }
284 private:
285 Gtk::SpinButton _s1, _s2;
286 };
288 class ColorButton : public Gtk::ColorButton, public AttrWidget
289 {
290 public:
291 ColorButton(unsigned int def, const SPAttributeEnum a, char* tip_text)
292 : AttrWidget(a, def)
293 {
294 signal_color_set().connect(signal_attr_changed().make_slot());
295 if (tip_text) _tt.set_tip(*this, tip_text);
297 Gdk::Color col;
298 col.set_rgb(65535, 65535, 65535);
299 set_color(col);
300 }
302 // Returns the color in 'rgb(r,g,b)' form.
303 Glib::ustring get_as_attribute() const
304 {
305 std::ostringstream os;
306 const Gdk::Color c = get_color();
307 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?
308 os << "rgb(" << r << "," << g << "," << b << ")";
309 return os.str();
310 }
313 void set_from_attribute(SPObject* o)
314 {
315 const gchar* val = attribute_value(o);
316 guint32 i = 0;
317 if(val) {
318 i = sp_svg_read_color(val, 0xFFFFFFFF);
319 } else {
320 i = (guint32) get_default()->as_uint();
321 }
322 const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
323 Gdk::Color col;
324 col.set_rgb(r * 256, g * 256, b * 256);
325 set_color(col);
326 }
327 };
329 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
330 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
331 {
332 public:
333 MatrixAttr(const SPAttributeEnum a, char* tip_text = NULL)
334 : AttrWidget(a), _locked(false)
335 {
336 _model = Gtk::ListStore::create(_columns);
337 _tree.set_model(_model);
338 _tree.set_headers_visible(false);
339 _tree.show();
340 add(_tree);
341 set_shadow_type(Gtk::SHADOW_IN);
342 if (tip_text) _tt.set_tip(_tree, tip_text);
343 }
345 std::vector<double> get_values() const
346 {
347 std::vector<double> vec;
348 for(Gtk::TreeIter iter = _model->children().begin();
349 iter != _model->children().end(); ++iter) {
350 for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
351 vec.push_back((*iter)[_columns.cols[c]]);
352 }
353 return vec;
354 }
356 void set_values(const std::vector<double>& v)
357 {
358 unsigned i = 0;
359 for(Gtk::TreeIter iter = _model->children().begin();
360 iter != _model->children().end(); ++iter) {
361 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
362 if(i >= v.size())
363 return;
364 (*iter)[_columns.cols[c]] = v[i];
365 ++i;
366 }
367 }
368 }
370 Glib::ustring get_as_attribute() const
371 {
372 std::ostringstream os;
374 for(Gtk::TreeIter iter = _model->children().begin();
375 iter != _model->children().end(); ++iter) {
376 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
377 os << (*iter)[_columns.cols[c]] << " ";
378 }
379 }
381 return os.str();
382 }
384 void set_from_attribute(SPObject* o)
385 {
386 if(o) {
387 if(SP_IS_FECONVOLVEMATRIX(o)) {
388 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
389 int cols, rows;
390 cols = (int)conv->order.getNumber();
391 if(cols > 5)
392 cols = 5;
393 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
394 update(o, rows, cols);
395 }
396 else if(SP_IS_FECOLORMATRIX(o))
397 update(o, 4, 5);
398 }
399 }
400 private:
401 class MatrixColumns : public Gtk::TreeModel::ColumnRecord
402 {
403 public:
404 MatrixColumns()
405 {
406 cols.resize(5);
407 for(unsigned i = 0; i < cols.size(); ++i)
408 add(cols[i]);
409 }
410 std::vector<Gtk::TreeModelColumn<double> > cols;
411 };
413 void update(SPObject* o, const int rows, const int cols)
414 {
415 if(_locked)
416 return;
418 _model->clear();
420 _tree.remove_all_columns();
422 std::vector<gdouble>* values = NULL;
423 if(SP_IS_FECOLORMATRIX(o))
424 values = &SP_FECOLORMATRIX(o)->values;
425 else if(SP_IS_FECONVOLVEMATRIX(o))
426 values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
427 else
428 return;
430 if(o) {
431 int ndx = 0;
433 for(int i = 0; i < cols; ++i) {
434 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
435 dynamic_cast<Gtk::CellRendererText*>(
436 _tree.get_column_cell_renderer(i))->signal_edited().connect(
437 sigc::mem_fun(*this, &MatrixAttr::rebind));
438 }
440 for(int r = 0; r < rows; ++r) {
441 Gtk::TreeRow row = *(_model->append());
442 // Default to identity matrix
443 for(int c = 0; c < cols; ++c, ++ndx)
444 row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
445 }
446 }
447 }
449 void rebind(const Glib::ustring&, const Glib::ustring&)
450 {
451 _locked = true;
452 signal_attr_changed()();
453 _locked = false;
454 }
456 bool _locked;
457 Gtk::TreeView _tree;
458 Glib::RefPtr<Gtk::ListStore> _model;
459 MatrixColumns _columns;
460 };
462 // Displays a matrix or a slider for feColorMatrix
463 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
464 {
465 public:
466 ColorMatrixValues()
467 : AttrWidget(SP_ATTR_VALUES),
468 // TRANSLATORS: this dialog is accessible via menu Filters - Filter editor
469 _matrix(SP_ATTR_VALUES, _("This matrix determines a linear transform on color 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.")),
470 _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
471 _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
472 _label(_("None"), Gtk::ALIGN_LEFT),
473 _use_stored(false),
474 _saturation_store(0),
475 _angle_store(0)
476 {
477 _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
478 _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
479 _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
480 signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
482 _matrix.show();
483 _saturation.show();
484 _angle.show();
485 _label.show();
486 _label.set_sensitive(false);
488 set_shadow_type(Gtk::SHADOW_NONE);
489 }
491 virtual void set_from_attribute(SPObject* o)
492 {
493 if(SP_IS_FECOLORMATRIX(o)) {
494 SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
495 remove();
496 switch(col->type) {
497 case COLORMATRIX_SATURATE:
498 add(_saturation);
499 if(_use_stored)
500 _saturation.set_value(_saturation_store);
501 else
502 _saturation.set_from_attribute(o);
503 break;
504 case COLORMATRIX_HUEROTATE:
505 add(_angle);
506 if(_use_stored)
507 _angle.set_value(_angle_store);
508 else
509 _angle.set_from_attribute(o);
510 break;
511 case COLORMATRIX_LUMINANCETOALPHA:
512 add(_label);
513 break;
514 case COLORMATRIX_MATRIX:
515 default:
516 add(_matrix);
517 if(_use_stored)
518 _matrix.set_values(_matrix_store);
519 else
520 _matrix.set_from_attribute(o);
521 break;
522 }
523 _use_stored = true;
524 }
525 }
527 virtual Glib::ustring get_as_attribute() const
528 {
529 const Widget* w = get_child();
530 if(w == &_label)
531 return "";
532 else
533 return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
534 }
536 void clear_store()
537 {
538 _use_stored = false;
539 }
540 private:
541 void update_store()
542 {
543 const Widget* w = get_child();
544 if(w == &_matrix)
545 _matrix_store = _matrix.get_values();
546 else if(w == &_saturation)
547 _saturation_store = _saturation.get_value();
548 else if(w == &_angle)
549 _angle_store = _angle.get_value();
550 }
552 MatrixAttr _matrix;
553 SpinSlider _saturation;
554 SpinSlider _angle;
555 Gtk::Label _label;
557 // Store separate values for the different color modes
558 bool _use_stored;
559 std::vector<double> _matrix_store;
560 double _saturation_store;
561 double _angle_store;
562 };
564 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
566 //Displays a chooser for feImage input
567 //It may be a filename or the id for an SVG Element
568 //described in xlink:href syntax
569 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
570 {
571 public:
572 FileOrElementChooser(const SPAttributeEnum a)
573 : AttrWidget(a)
574 {
575 pack_start(_entry, false, false);
576 pack_start(_fromFile, false, false);
577 pack_start(_fromSVGElement, false, false);
579 _fromFile.set_label(_("Image File"));
580 _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
582 _fromSVGElement.set_label(_("Selected SVG Element"));
583 _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
585 _entry.signal_changed().connect(signal_attr_changed().make_slot());
587 show_all();
589 }
591 // Returns the element in xlink:href form.
592 Glib::ustring get_as_attribute() const
593 {
594 return _entry.get_text();
595 }
598 void set_from_attribute(SPObject* o)
599 {
600 const gchar* val = attribute_value(o);
601 if(val) {
602 _entry.set_text(val);
603 } else {
604 _entry.set_text("");
605 }
606 }
608 void set_desktop(SPDesktop* d){
609 _desktop = d;
610 }
612 private:
613 void select_svg_element(){
614 Inkscape::Selection* sel = sp_desktop_selection(_desktop);
615 if (sel->isEmpty()) return;
616 Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
617 if (!node || !node->matchAttributeName("id")) return;
619 std::ostringstream xlikhref;
620 xlikhref << "#" << node->attribute("id");
621 _entry.set_text(xlikhref.str());
622 }
624 void select_file(){
626 //# Get the current directory for finding files
627 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
628 Glib::ustring open_path;
629 Glib::ustring attr = prefs->getString("/dialogs/open/path");
630 if (!attr.empty())
631 open_path = attr;
633 //# Test if the open_path directory exists
634 if (!Inkscape::IO::file_test(open_path.c_str(),
635 (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
636 open_path = "";
638 //# If no open path, default to our home directory
639 if (open_path.size() < 1)
640 {
641 open_path = g_get_home_dir();
642 open_path.append(G_DIR_SEPARATOR_S);
643 }
645 //# Create a dialog if we don't already have one
646 if (!selectFeImageFileInstance) {
647 selectFeImageFileInstance =
648 Inkscape::UI::Dialog::FileOpenDialog::create(
649 *_desktop->getToplevel(),
650 open_path,
651 Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not just svg*/
652 (char const *)_("Select an image to be used as feImage input"));
653 }
655 //# Show the dialog
656 bool const success = selectFeImageFileInstance->show();
657 if (!success)
658 return;
660 //# User selected something. Get name and type
661 Glib::ustring fileName = selectFeImageFileInstance->getFilename();
663 if (fileName.size() > 0) {
665 Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
667 if ( newFileName.size() > 0)
668 fileName = newFileName;
669 else
670 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
672 open_path = fileName;
673 open_path.append(G_DIR_SEPARATOR_S);
674 prefs->setString("/dialogs/open/path", open_path);
676 _entry.set_text(fileName);
677 }
678 return;
679 }
681 Gtk::Entry _entry;
682 Gtk::Button _fromFile;
683 Gtk::Button _fromSVGElement;
684 SPDesktop* _desktop;
685 };
687 class FilterEffectsDialog::Settings
688 {
689 public:
690 typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
692 Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
693 : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
694 {
695 _groups.resize(_max_types);
696 _attrwidgets.resize(_max_types);
697 _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
699 for(int i = 0; i < _max_types; ++i) {
700 _groups[i] = new Gtk::VBox;
701 b.pack_start(*_groups[i], false, false);
702 }
703 _current_type = 0;
704 }
706 ~Settings()
707 {
708 for(int i = 0; i < _max_types; ++i) {
709 delete _groups[i];
710 for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
711 delete _attrwidgets[i][j];
712 }
713 }
715 // Show the active settings group and update all the AttrWidgets with new values
716 void show_and_update(const int t, SPObject* ob)
717 {
718 if(t != _current_type) {
719 type(t);
720 for(unsigned i = 0; i < _groups.size(); ++i)
721 _groups[i]->hide();
722 }
723 if(t >= 0)
724 _groups[t]->show_all();
726 _dialog.set_attrs_locked(true);
727 for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
728 _attrwidgets[_current_type][i]->set_from_attribute(ob);
729 _dialog.set_attrs_locked(false);
730 }
732 int get_current_type() const
733 {
734 return _current_type;
735 }
737 void type(const int t)
738 {
739 _current_type = t;
740 }
742 void add_no_params()
743 {
744 Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
745 add_widget(lbl, "");
746 }
748 void add_notimplemented()
749 {
750 Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
751 add_widget(lbl, "");
752 }
754 // LightSource
755 LightSourceControl* add_lightsource();
757 // CheckBox
758 CheckButtonAttr* add_checkbutton(bool def, const SPAttributeEnum attr, const Glib::ustring& label,
759 const Glib::ustring& tv, const Glib::ustring& fv, char* tip_text = NULL)
760 {
761 CheckButtonAttr* cb = new CheckButtonAttr(def, label, tv, fv, attr, tip_text);
762 add_widget(cb, "");
763 add_attr_widget(cb);
764 return cb;
765 }
767 // ColorButton
768 ColorButton* add_color(unsigned int def, const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text = NULL)
769 {
770 ColorButton* col = new ColorButton(def, attr, tip_text);
771 add_widget(col, label);
772 add_attr_widget(col);
773 return col;
774 }
776 // Matrix
777 MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text)
778 {
779 MatrixAttr* conv = new MatrixAttr(attr, tip_text);
780 add_widget(conv, label);
781 add_attr_widget(conv);
782 return conv;
783 }
785 // ColorMatrixValues
786 ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
787 {
788 ColorMatrixValues* cmv = new ColorMatrixValues();
789 add_widget(cmv, label);
790 add_attr_widget(cmv);
791 return cmv;
792 }
794 // SpinSlider
795 SpinSlider* add_spinslider(double def, const SPAttributeEnum attr, const Glib::ustring& label,
796 const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
797 {
798 SpinSlider* spinslider = new SpinSlider(def, lo, hi, step_inc, climb, digits, attr, tip_text);
799 add_widget(spinslider, label);
800 add_attr_widget(spinslider);
801 return spinslider;
802 }
804 // DualSpinSlider
805 DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
806 const double lo, const double hi, const double step_inc,
807 const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
808 {
809 DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
810 add_widget(dss, label);
811 add_attr_widget(dss);
812 return dss;
813 }
815 // DualSpinButton
816 DualSpinButton* add_dualspinbutton(char* defalt_value, const SPAttributeEnum attr, const Glib::ustring& label,
817 const double lo, const double hi, const double step_inc,
818 const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
819 {
820 DualSpinButton* dsb = new DualSpinButton(defalt_value, lo, hi, step_inc, climb, digits, attr, tip1, tip2);
821 add_widget(dsb, label);
822 add_attr_widget(dsb);
823 return dsb;
824 }
826 // MultiSpinButton
827 MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
828 const Glib::ustring& label, const double lo, const double hi,
829 const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
830 {
831 std::vector<SPAttributeEnum> attrs;
832 attrs.push_back(attr1);
833 attrs.push_back(attr2);
835 std::vector<double> default_values;
836 default_values.push_back(def1);
837 default_values.push_back(def2);
839 std::vector<char*> tips;
840 tips.push_back(tip1);
841 tips.push_back(tip2);
843 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
844 add_widget(msb, label);
845 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
846 add_attr_widget(msb->get_spinbuttons()[i]);
847 return msb;
848 }
849 MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
850 const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
851 const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
852 {
853 std::vector<SPAttributeEnum> attrs;
854 attrs.push_back(attr1);
855 attrs.push_back(attr2);
856 attrs.push_back(attr3);
858 std::vector<double> default_values;
859 default_values.push_back(def1);
860 default_values.push_back(def2);
861 default_values.push_back(def3);
863 std::vector<char*> tips;
864 tips.push_back(tip1);
865 tips.push_back(tip2);
866 tips.push_back(tip3);
868 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
869 add_widget(msb, label);
870 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
871 add_attr_widget(msb->get_spinbuttons()[i]);
872 return msb;
873 }
875 // FileOrElementChooser
876 FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
877 {
878 FileOrElementChooser* foech = new FileOrElementChooser(attr);
879 foech->set_desktop(_dialog.getDesktop());
880 add_widget(foech, label);
881 add_attr_widget(foech);
882 return foech;
883 }
885 // ComboBoxEnum
886 template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
887 const Glib::ustring& label,
888 const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
889 {
890 ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
891 add_widget(combo, label);
892 add_attr_widget(combo->get_attrwidget());
893 return combo->get_attrwidget();
894 }
895 private:
896 Gtk::Tooltips _tt;
898 void add_attr_widget(AttrWidget* a)
899 {
900 _attrwidgets[_current_type].push_back(a);
901 a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
902 }
904 /* Adds a new settings widget using the specified label. The label will be formatted with a colon
905 and all widgets within the setting group are aligned automatically. */
906 void add_widget(Gtk::Widget* w, const Glib::ustring& label)
907 {
908 Gtk::Label *lbl = 0;
909 Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
910 hb->set_spacing(12);
912 if(label != "") {
913 //lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT)); colon now in label (LP #358921)
914 lbl = Gtk::manage(new Gtk::Label(label, Gtk::ALIGN_LEFT));
915 hb->pack_start(*lbl, false, false);
916 _size_group->add_widget(*lbl);
917 lbl->show();
918 }
920 hb->pack_start(*w);
921 _groups[_current_type]->pack_start(*hb);
922 hb->show();
923 w->show();
924 }
926 std::vector<Gtk::VBox*> _groups;
927 Glib::RefPtr<Gtk::SizeGroup> _size_group;
928 FilterEffectsDialog& _dialog;
929 SetAttrSlot _set_attr_slot;
930 std::vector<std::vector< AttrWidget*> > _attrwidgets;
931 int _current_type, _max_types;
932 };
934 // Settings for the three light source objects
935 class FilterEffectsDialog::LightSourceControl : public AttrWidget
936 {
937 public:
938 LightSourceControl(FilterEffectsDialog& d)
939 : AttrWidget(SP_ATTR_INVALID),
940 _dialog(d),
941 _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
942 _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
943 _light_source(LightSourceConverter),
944 _locked(false)
945 {
946 _light_box.pack_start(_light_label, false, false);
947 _light_box.pack_start(_light_source);
948 _light_box.show_all();
949 _light_box.set_spacing(12);
950 _dialog._sizegroup->add_widget(_light_label);
952 _box.add(_light_box);
953 _box.reorder_child(_light_box, 0);
954 _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
956 // FIXME: these range values are complete crap
958 _settings.type(LIGHT_DISTANT);
959 _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"));
960 _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"));
962 _settings.type(LIGHT_POINT);
963 _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"));
965 _settings.type(LIGHT_SPOT);
966 _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"));
967 _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
968 SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
969 _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
970 _settings.add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
971 //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.
972 _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."));
973 }
975 Gtk::VBox& get_box()
976 {
977 return _box;
978 }
979 protected:
980 Glib::ustring get_as_attribute() const
981 {
982 return "";
983 }
984 void set_from_attribute(SPObject* o)
985 {
986 if(_locked)
987 return;
989 _locked = true;
991 SPObject* child = o->children;
993 if(SP_IS_FEDISTANTLIGHT(child))
994 _light_source.set_active(0);
995 else if(SP_IS_FEPOINTLIGHT(child))
996 _light_source.set_active(1);
997 else if(SP_IS_FESPOTLIGHT(child))
998 _light_source.set_active(2);
999 else
1000 _light_source.set_active(-1);
1002 update();
1004 _locked = false;
1005 }
1006 private:
1007 void on_source_changed()
1008 {
1009 if(_locked)
1010 return;
1012 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1013 if(prim) {
1014 _locked = true;
1016 SPObject* child = prim->children;
1017 const int ls = _light_source.get_active_row_number();
1018 // Check if the light source type has changed
1019 if(!(ls == -1 && !child) &&
1020 !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1021 !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1022 !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1023 if(child)
1024 sp_repr_unparent(child->repr);
1026 if(ls != -1) {
1027 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1028 Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1029 prim->repr->appendChild(repr);
1030 Inkscape::GC::release(repr);
1031 }
1033 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1034 update();
1035 }
1037 _locked = false;
1038 }
1039 }
1041 void update()
1042 {
1043 _box.hide_all();
1044 _box.show();
1045 _light_box.show_all();
1047 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1048 if(prim && prim->children)
1049 _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1050 }
1052 FilterEffectsDialog& _dialog;
1053 Gtk::VBox _box;
1054 Settings _settings;
1055 Gtk::HBox _light_box;
1056 Gtk::Label _light_label;
1057 ComboBoxEnum<LightSource> _light_source;
1058 bool _locked;
1059 };
1061 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1062 {
1063 LightSourceControl* ls = new LightSourceControl(_dialog);
1064 add_attr_widget(ls);
1065 add_widget(&ls->get_box(), "");
1066 return ls;
1067 }
1069 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1070 sigc::slot<void> rem)
1071 {
1072 Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1074 menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1075 Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1076 menu->append(*mi);
1077 mi->signal_activate().connect(rem);
1078 mi->show();
1079 menu->accelerate(parent);
1081 return menu;
1082 }
1084 /*** FilterModifier ***/
1085 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1086 : _dialog(d), _add(Gtk::Stock::NEW), _observer(new Inkscape::XML::SignalObserver)
1087 {
1088 Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1089 pack_start(*sw);
1090 pack_start(_add, false, false);
1091 sw->add(_list);
1093 _model = Gtk::ListStore::create(_columns);
1094 _list.set_model(_model);
1095 _cell_toggle.set_active(true);
1096 const int selcol = _list.append_column("", _cell_toggle);
1097 Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1098 if(col)
1099 col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1100 _list.append_column_editable(_("_Filter"), _columns.label);
1101 ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1102 signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1104 sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1105 sw->set_shadow_type(Gtk::SHADOW_IN);
1106 show_all_children();
1107 _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1108 _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1109 _list.signal_button_release_event().connect_notify(
1110 sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1111 _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1112 sigc::mem_fun(*this, &FilterModifier::remove_filter));
1113 _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1114 _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1115 _menu->accelerate(*this);
1117 _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1118 _observer->signal_changed().connect(signal_filter_changed().make_slot());
1119 g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1120 G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1122 g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1123 G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1124 g_signal_connect(G_OBJECT(INKSCAPE), "deactivate_desktop",
1125 G_CALLBACK(&FilterModifier::on_deactivate_desktop), this);
1127 on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1128 update_filters();
1129 }
1131 FilterEffectsDialog::FilterModifier::~FilterModifier()
1132 {
1133 _resource_changed.disconnect();
1134 _doc_replaced.disconnect();
1135 }
1137 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1138 {
1139 me->_doc_replaced.disconnect();
1140 me->_doc_replaced = desktop->connectDocumentReplaced(
1141 sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1143 me->_resource_changed.disconnect();
1144 me->_resource_changed =
1145 sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1146 sigc::mem_fun(me, &FilterModifier::update_filters));
1148 me->_dialog.setDesktop(desktop);
1150 me->update_filters();
1151 }
1153 void FilterEffectsDialog::FilterModifier::on_deactivate_desktop(Application*, SPDesktop* /*desktop*/, FilterModifier* me)
1154 {
1155 me->_doc_replaced.disconnect();
1156 me->_resource_changed.disconnect();
1157 me->_dialog.setDesktop(NULL);
1158 }
1161 // When the selection changes, show the active filter(s) in the dialog
1162 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1163 Selection *sel,
1164 FilterModifier* fm)
1165 {
1166 if(fm && sel)
1167 fm->update_selection(sel);
1168 }
1170 // Update each filter's sel property based on the current object selection;
1171 // If the filter is not used by any selected object, sel = 0,
1172 // otherwise sel is set to the total number of filters in use by selected objects
1173 // If only one filter is in use, it is selected
1174 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1175 {
1176 std::set<SPObject*> used;
1178 for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1179 SPObject *obj = SP_OBJECT (i->data);
1180 SPStyle *style = SP_OBJECT_STYLE (obj);
1181 if(!style || !SP_IS_ITEM(obj)) continue;
1183 if(style->filter.set && style->getFilter())
1184 used.insert(style->getFilter());
1185 else
1186 used.insert(0);
1187 }
1189 const int size = used.size();
1191 for(Gtk::TreeIter iter = _model->children().begin();
1192 iter != _model->children().end(); ++iter) {
1193 if(used.find((*iter)[_columns.filter]) != used.end()) {
1194 // If only one filter is in use by the selection, select it
1195 if(size == 1)
1196 _list.get_selection()->select(iter);
1197 (*iter)[_columns.sel] = size;
1198 }
1199 else
1200 (*iter)[_columns.sel] = 0;
1201 }
1202 }
1204 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1205 {
1206 _observer->set(get_selected_filter());
1207 signal_filter_changed()();
1208 }
1210 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1211 {
1212 Gtk::TreeModel::iterator iter = _model->get_iter(path);
1214 if(iter) {
1215 SPFilter* filter = (*iter)[_columns.filter];
1216 filter->setLabel(text.c_str());
1217 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1218 if(iter)
1219 (*iter)[_columns.label] = text;
1220 }
1221 }
1223 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1224 {
1225 Gtk::TreeIter iter = _model->get_iter(path);
1227 if(iter) {
1228 SPDesktop *desktop = _dialog.getDesktop();
1229 SPDocument *doc = sp_desktop_document(desktop);
1230 SPFilter* filter = (*iter)[_columns.filter];
1231 Inkscape::Selection *sel = sp_desktop_selection(desktop);
1233 /* If this filter is the only one used in the selection, unset it */
1234 if((*iter)[_columns.sel] == 1)
1235 filter = 0;
1237 GSList const *items = sel->itemList();
1239 for (GSList const *i = items; i != NULL; i = i->next) {
1240 SPItem * item = SP_ITEM(i->data);
1241 SPStyle *style = SP_OBJECT_STYLE(item);
1242 g_assert(style != NULL);
1244 if(filter)
1245 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1246 else
1247 ::remove_filter(item, false);
1249 SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1250 }
1252 update_selection(sel);
1253 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Apply filter"));
1254 }
1255 }
1257 /* Add all filters in the document to the combobox.
1258 Keeps the same selection if possible, otherwise selects the first element */
1259 void FilterEffectsDialog::FilterModifier::update_filters()
1260 {
1261 SPDesktop* desktop = _dialog.getDesktop();
1262 SPDocument* document = sp_desktop_document(desktop);
1263 const GSList* filters = sp_document_get_resource_list(document, "filter");
1265 _model->clear();
1267 for(const GSList *l = filters; l; l = l->next) {
1268 Gtk::TreeModel::Row row = *_model->append();
1269 SPFilter* f = (SPFilter*)l->data;
1270 row[_columns.filter] = f;
1271 const gchar* lbl = f->label();
1272 const gchar* id = f->getId();
1273 row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1274 }
1276 update_selection(desktop->selection);
1277 _dialog.update_filter_general_settings_view();
1278 }
1280 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1281 {
1282 if(_list.get_selection()) {
1283 Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1285 if(i)
1286 return (*i)[_columns.filter];
1287 }
1289 return 0;
1290 }
1292 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1293 {
1294 if(filter) {
1295 for(Gtk::TreeModel::iterator i = _model->children().begin();
1296 i != _model->children().end(); ++i) {
1297 if((*i)[_columns.filter] == filter) {
1298 _list.get_selection()->select(i);
1299 break;
1300 }
1301 }
1302 }
1303 }
1305 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1306 {
1307 if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1308 const bool sensitive = get_selected_filter() != NULL;
1309 _menu->items()[0].set_sensitive(sensitive);
1310 _menu->items()[1].set_sensitive(sensitive);
1311 _menu->popup(event->button, event->time);
1312 }
1313 }
1315 void FilterEffectsDialog::FilterModifier::add_filter()
1316 {
1317 SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1318 SPFilter* filter = new_filter(doc);
1320 const int count = _model->children().size();
1321 std::ostringstream os;
1322 os << _("filter") << count;
1323 filter->setLabel(os.str().c_str());
1325 update_filters();
1327 select_filter(filter);
1329 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1330 }
1332 void FilterEffectsDialog::FilterModifier::remove_filter()
1333 {
1334 SPFilter *filter = get_selected_filter();
1336 if(filter) {
1337 SPDocument* doc = filter->document;
1338 sp_repr_unparent(filter->repr);
1340 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1342 update_filters();
1343 }
1344 }
1346 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1347 {
1348 SPFilter* filter = get_selected_filter();
1350 if(filter) {
1351 Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1352 repr = repr->duplicate(repr->document());
1353 parent->appendChild(repr);
1355 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1357 update_filters();
1358 }
1359 }
1361 void FilterEffectsDialog::FilterModifier::rename_filter()
1362 {
1363 _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1364 }
1366 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1367 : Glib::ObjectBase(typeid(CellRendererConnection)),
1368 _primitive(*this, "primitive", 0)
1369 {}
1371 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1372 {
1373 return _primitive.get_proxy();
1374 }
1376 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1377 {
1378 _text_width = w;
1379 }
1381 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1382 {
1383 return _text_width;
1384 }
1386 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1387 Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1388 int* x_offset, int* y_offset, int* width, int* height) const
1389 {
1390 PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1392 if(x_offset)
1393 (*x_offset) = 0;
1394 if(y_offset)
1395 (*y_offset) = 0;
1396 if(width)
1397 (*width) = size * primlist.primitive_count() + _text_width * 7;
1398 if(height) {
1399 // Scale the height depending on the number of inputs, unless it's
1400 // the first primitive, in which case there are no connections
1401 SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1402 (*height) = size * input_count(prim);
1403 }
1404 }
1406 /*** PrimitiveList ***/
1407 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1408 : _dialog(d),
1409 _in_drag(0),
1410 _observer(new Inkscape::XML::SignalObserver)
1411 {
1412 d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1414 add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1415 signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1417 _model = Gtk::ListStore::create(_columns);
1419 set_reorderable(true);
1421 set_model(_model);
1422 append_column(_("_Effect"), _columns.type);
1424 _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1425 get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1426 signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1428 _connection_cell.set_text_width(init_text());
1430 int cols_count = append_column(_("Connections"), _connection_cell);
1431 Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1432 if(col)
1433 col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1434 }
1436 // Sets up a vertical Pango context/layout, and returns the largest
1437 // width needed to render the FilterPrimitiveInput labels.
1438 int FilterEffectsDialog::PrimitiveList::init_text()
1439 {
1440 // Set up a vertical context+layout
1441 Glib::RefPtr<Pango::Context> context = create_pango_context();
1442 const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1443 context->set_matrix(matrix);
1444 _vertical_layout = Pango::Layout::create(context);
1446 int maxfont = 0;
1447 for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1448 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1449 int fontw, fonth;
1450 _vertical_layout->get_pixel_size(fontw, fonth);
1451 if(fonth > maxfont)
1452 maxfont = fonth;
1453 }
1455 return maxfont;
1456 }
1458 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1459 {
1460 return _signal_primitive_changed;
1461 }
1463 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1464 {
1465 _observer->set(get_selected());
1466 signal_primitive_changed()();
1467 _dialog._color_matrix_values->clear_store();
1468 }
1470 /* Add all filter primitives in the current to the list.
1471 Keeps the same selection if possible, otherwise selects the first element */
1472 void FilterEffectsDialog::PrimitiveList::update()
1473 {
1474 SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1475 const SPFilterPrimitive* active_prim = get_selected();
1476 bool active_found = false;
1478 _model->clear();
1480 if(f) {
1481 _dialog._primitive_box.set_sensitive(true);
1482 _dialog.update_filter_general_settings_view();
1483 for(SPObject *prim_obj = f->children;
1484 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1485 prim_obj = prim_obj->next) {
1486 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1487 if(prim) {
1488 Gtk::TreeModel::Row row = *_model->append();
1489 row[_columns.primitive] = prim;
1490 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1491 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1492 row[_columns.id] = prim->getId();
1494 if(prim == active_prim) {
1495 get_selection()->select(row);
1496 active_found = true;
1497 }
1498 }
1499 }
1501 if(!active_found && _model->children().begin())
1502 get_selection()->select(_model->children().begin());
1504 columns_autosize();
1505 }
1506 else {
1507 _dialog._primitive_box.set_sensitive(false);
1508 }
1509 }
1511 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1512 {
1513 _primitive_menu = menu;
1514 }
1516 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1517 {
1518 if(_dialog._filter_modifier.get_selected_filter()) {
1519 Gtk::TreeModel::iterator i = get_selection()->get_selected();
1520 if(i)
1521 return (*i)[_columns.primitive];
1522 }
1524 return 0;
1525 }
1527 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1528 {
1529 for(Gtk::TreeIter i = _model->children().begin();
1530 i != _model->children().end(); ++i) {
1531 if((*i)[_columns.primitive] == prim)
1532 get_selection()->select(i);
1533 }
1534 }
1536 void FilterEffectsDialog::PrimitiveList::remove_selected()
1537 {
1538 SPFilterPrimitive* prim = get_selected();
1540 if(prim) {
1541 _observer->set(0);
1543 sp_repr_unparent(prim->repr);
1545 sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1546 _("Remove filter primitive"));
1548 update();
1549 }
1550 }
1552 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1553 {
1554 Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1555 Glib::RefPtr<Gdk::Window> win = get_bin_window();
1556 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1558 SPFilterPrimitive* prim = get_selected();
1559 int row_count = get_model()->children().size();
1561 int fheight = CellRendererConnection::size;
1562 Gdk::Rectangle rct, vis;
1563 Gtk::TreeIter row = get_model()->children().begin();
1564 int text_start_x = 0;
1565 if(row) {
1566 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1567 get_visible_rect(vis);
1568 int vis_x, vis_y;
1569 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1571 text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter._length + 1) + 1;
1572 for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1573 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1574 const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1575 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());
1576 get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1577 get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1578 }
1579 }
1581 int row_index = 0;
1582 for(; row != get_model()->children().end(); ++row, ++row_index) {
1583 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1584 const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1586 // Check mouse state
1587 int mx, my;
1588 Gdk::ModifierType mask;
1589 get_bin_window()->get_pointer(mx, my, mask);
1591 // Outline the bottom of the connection area
1592 const int outline_x = x + fheight * (row_count - row_index);
1593 get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1595 // Side outline
1596 get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1598 std::vector<Gdk::Point> con_poly;
1599 int con_drag_y = 0;
1600 bool inside;
1601 const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1602 const int inputs = input_count(row_prim);
1604 if(SP_IS_FEMERGE(row_prim)) {
1605 for(int i = 0; i < inputs; ++i) {
1606 inside = do_connection_node(row, i, con_poly, mx, my);
1607 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1608 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1609 inside, con_poly);
1611 if(_in_drag == (i + 1))
1612 con_drag_y = con_poly[2].get_y();
1614 if(_in_drag != (i + 1) || row_prim != prim)
1615 draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1616 }
1617 }
1618 else {
1619 // Draw "in" shape
1620 inside = do_connection_node(row, 0, con_poly, mx, my);
1621 con_drag_y = con_poly[2].get_y();
1622 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1623 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1624 inside, con_poly);
1626 // Draw "in" connection
1627 if(_in_drag != 1 || row_prim != prim)
1628 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1630 if(inputs == 2) {
1631 // Draw "in2" shape
1632 inside = do_connection_node(row, 1, con_poly, mx, my);
1633 if(_in_drag == 2)
1634 con_drag_y = con_poly[2].get_y();
1635 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1636 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1637 inside, con_poly);
1638 // Draw "in2" connection
1639 if(_in_drag != 2 || row_prim != prim)
1640 draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1641 }
1642 }
1644 // Draw drag connection
1645 if(row_prim == prim && _in_drag) {
1646 get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1647 mx, con_drag_y);
1648 get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1649 }
1650 }
1652 return true;
1653 }
1655 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1656 const int text_start_x, const int x1, const int y1,
1657 const int row_count)
1658 {
1659 int src_id = 0;
1660 Gtk::TreeIter res = find_result(input, attr, src_id);
1661 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1662 Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1663 Glib::RefPtr<Gdk::GC> gc;
1665 const bool is_first = input == get_model()->children().begin();
1666 const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1667 const bool use_default = !res && !is_merge;
1669 if(res == input || (use_default && is_first)) {
1670 // Draw straight connection to a standard input
1671 // Draw a lighter line for an implicit connection to a standard input
1672 const int tw = _connection_cell.get_text_width();
1673 gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1674 gc = (use_default && is_first) ? lightgc : darkgc;
1675 get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1676 get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1677 }
1678 else {
1679 // Draw an 'L'-shaped connection to another filter primitive
1680 // If no connection is specified, draw a light connection to the previous primitive
1681 gc = use_default ? lightgc : darkgc;
1683 if(use_default) {
1684 res = input;
1685 --res;
1686 }
1688 if(res) {
1689 Gdk::Rectangle rct;
1691 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1692 const int fheight = CellRendererConnection::size;
1694 get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1695 const int row_index = find_index(res);
1696 const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1697 const int y2 = rct.get_y() + rct.get_height();
1699 // Draw a bevelled 'L'-shaped connection
1700 get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1701 get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1702 get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1703 }
1704 }
1705 }
1707 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1708 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1709 std::vector<Gdk::Point>& points,
1710 const int ix, const int iy)
1711 {
1712 Gdk::Rectangle rct;
1713 const int icnt = input_count((*row)[_columns.primitive]);
1715 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1716 const int fheight = CellRendererConnection::size;
1718 get_cell_area(_model->get_path(row), *get_column(1), rct);
1719 const float h = rct.get_height() / icnt;
1721 const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1722 const int con_w = (int)(fheight * 0.35f);
1723 const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1724 points.clear();
1725 points.push_back(Gdk::Point(x, con_y));
1726 points.push_back(Gdk::Point(x, con_y + con_w * 2));
1727 points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1729 return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1730 }
1732 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1733 const int attr, int& src_id)
1734 {
1735 SPFilterPrimitive* prim = (*start)[_columns.primitive];
1736 Gtk::TreeIter target = _model->children().end();
1737 int image = 0;
1739 if(SP_IS_FEMERGE(prim)) {
1740 int c = 0;
1741 bool found = false;
1742 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1743 if(c == attr && SP_IS_FEMERGENODE(o)) {
1744 image = SP_FEMERGENODE(o)->input;
1745 found = true;
1746 }
1747 }
1748 if(!found)
1749 return target;
1750 }
1751 else {
1752 if(attr == SP_ATTR_IN)
1753 image = prim->image_in;
1754 else if(attr == SP_ATTR_IN2) {
1755 if(SP_IS_FEBLEND(prim))
1756 image = SP_FEBLEND(prim)->in2;
1757 else if(SP_IS_FECOMPOSITE(prim))
1758 image = SP_FECOMPOSITE(prim)->in2;
1759 else if(SP_IS_FEDISPLACEMENTMAP(prim))
1760 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1761 else
1762 return target;
1763 }
1764 else
1765 return target;
1766 }
1768 if(image >= 0) {
1769 for(Gtk::TreeIter i = _model->children().begin();
1770 i != start; ++i) {
1771 if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1772 target = i;
1773 }
1774 return target;
1775 }
1776 else if(image < -1) {
1777 src_id = -(image + 2);
1778 return start;
1779 }
1781 return target;
1782 }
1784 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1785 {
1786 int i = 0;
1787 for(Gtk::TreeIter iter = _model->children().begin();
1788 iter != target; ++iter, ++i){};
1789 return i;
1790 }
1792 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1793 {
1794 Gtk::TreePath path;
1795 Gtk::TreeViewColumn* col;
1796 const int x = (int)e->x, y = (int)e->y;
1797 int cx, cy;
1799 _drag_prim = 0;
1801 if(get_path_at_pos(x, y, path, col, cx, cy)) {
1802 Gtk::TreeIter iter = _model->get_iter(path);
1803 std::vector<Gdk::Point> points;
1805 _drag_prim = (*iter)[_columns.primitive];
1806 const int icnt = input_count(_drag_prim);
1808 for(int i = 0; i < icnt; ++i) {
1809 if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1810 _in_drag = i + 1;
1811 break;
1812 }
1813 }
1815 queue_draw();
1816 }
1818 if(_in_drag) {
1819 _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1820 _autoscroll = 0;
1821 get_selection()->select(path);
1822 return true;
1823 }
1824 else
1825 return Gtk::TreeView::on_button_press_event(e);
1826 }
1828 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1829 {
1830 const int speed = 10;
1831 const int limit = 15;
1833 Gdk::Rectangle vis;
1834 get_visible_rect(vis);
1835 int vis_x, vis_y;
1836 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1837 const int top = vis_y + vis.get_height();
1839 // When autoscrolling during a connection drag, set the speed based on
1840 // where the mouse is in relation to the edges.
1841 if(e->y < vis_y)
1842 _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1843 else if(e->y < vis_y + limit)
1844 _autoscroll = -speed;
1845 else if(e->y > top)
1846 _autoscroll = (int)(speed + (e->y - top) / 5);
1847 else if(e->y > top - limit)
1848 _autoscroll = speed;
1849 else
1850 _autoscroll = 0;
1852 queue_draw();
1854 return Gtk::TreeView::on_motion_notify_event(e);
1855 }
1857 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1858 {
1859 SPFilterPrimitive *prim = get_selected(), *target;
1861 _scroll_connection.disconnect();
1863 if(_in_drag && prim) {
1864 Gtk::TreePath path;
1865 Gtk::TreeViewColumn* col;
1866 int cx, cy;
1868 if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1869 const gchar *in_val = 0;
1870 Glib::ustring result;
1871 Gtk::TreeIter target_iter = _model->get_iter(path);
1872 target = (*target_iter)[_columns.primitive];
1873 col = get_column(1);
1875 Gdk::Rectangle rct;
1876 get_cell_area(path, *col, rct);
1877 const int twidth = _connection_cell.get_text_width();
1878 const int sources_x = rct.get_width() - twidth * FPInputConverter._length;
1879 if(cx > sources_x) {
1880 int src = (cx - sources_x) / twidth;
1881 if (src < 0) {
1882 src = 0;
1883 } else if(src >= static_cast<int>(FPInputConverter._length)) {
1884 src = FPInputConverter._length - 1;
1885 }
1886 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1887 in_val = result.c_str();
1888 }
1889 else {
1890 // Ensure that the target comes before the selected primitive
1891 for(Gtk::TreeIter iter = _model->children().begin();
1892 iter != get_selection()->get_selected(); ++iter) {
1893 if(iter == target_iter) {
1894 Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1895 // Make sure the target has a result
1896 const gchar *gres = repr->attribute("result");
1897 if(!gres) {
1898 result = sp_filter_get_new_result_name(SP_FILTER(prim->parent));
1899 repr->setAttribute("result", result.c_str());
1900 in_val = result.c_str();
1901 }
1902 else
1903 in_val = gres;
1904 break;
1905 }
1906 }
1907 }
1909 if(SP_IS_FEMERGE(prim)) {
1910 int c = 1;
1911 bool handled = false;
1912 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1913 if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1914 // If input is null, delete it
1915 if(!in_val) {
1916 sp_repr_unparent(o->repr);
1917 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1918 _("Remove merge node"));
1919 (*get_selection()->get_selected())[_columns.primitive] = prim;
1920 }
1921 else
1922 _dialog.set_attr(o, SP_ATTR_IN, in_val);
1923 handled = true;
1924 }
1925 }
1926 // Add new input?
1927 if(!handled && c == _in_drag && in_val) {
1928 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1929 Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1930 repr->setAttribute("inkscape:collect", "always");
1931 prim->repr->appendChild(repr);
1932 SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1933 Inkscape::GC::release(repr);
1934 _dialog.set_attr(node, SP_ATTR_IN, in_val);
1935 (*get_selection()->get_selected())[_columns.primitive] = prim;
1936 }
1937 }
1938 else {
1939 if(_in_drag == 1)
1940 _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1941 else if(_in_drag == 2)
1942 _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1943 }
1944 }
1946 _in_drag = 0;
1947 queue_draw();
1949 _dialog.update_settings_view();
1950 }
1952 if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1953 const bool sensitive = get_selected() != NULL;
1954 _primitive_menu->items()[0].set_sensitive(sensitive);
1955 _primitive_menu->items()[1].set_sensitive(sensitive);
1956 _primitive_menu->popup(e->button, e->time);
1958 return true;
1959 }
1960 else
1961 return Gtk::TreeView::on_button_release_event(e);
1962 }
1964 // Checks all of prim's inputs, removes any that use result
1965 void check_single_connection(SPFilterPrimitive* prim, const int result)
1966 {
1967 if(prim && result >= 0) {
1969 if(prim->image_in == result)
1970 SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1972 if(SP_IS_FEBLEND(prim)) {
1973 if(SP_FEBLEND(prim)->in2 == result)
1974 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1975 }
1976 else if(SP_IS_FECOMPOSITE(prim)) {
1977 if(SP_FECOMPOSITE(prim)->in2 == result)
1978 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1979 }
1980 else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
1981 if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
1982 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1983 }
1984 }
1985 }
1987 // Remove any connections going to/from prim_iter that forward-reference other primitives
1988 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
1989 {
1990 SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
1991 bool before = true;
1993 for(Gtk::TreeIter iter = _model->children().begin();
1994 iter != _model->children().end(); ++iter) {
1995 if(iter == prim_iter)
1996 before = false;
1997 else {
1998 SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
1999 if(before)
2000 check_single_connection(cur_prim, prim->image_out);
2001 else
2002 check_single_connection(prim, cur_prim->image_out);
2003 }
2004 }
2005 }
2007 // Reorder the filter primitives to match the list order
2008 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2009 {
2010 SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2011 int ndx = 0;
2013 for(Gtk::TreeModel::iterator iter = _model->children().begin();
2014 iter != _model->children().end(); ++iter, ++ndx) {
2015 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2016 if(prim && prim == _drag_prim) {
2017 SP_OBJECT_REPR(prim)->setPosition(ndx);
2018 break;
2019 }
2020 }
2022 for(Gtk::TreeModel::iterator iter = _model->children().begin();
2023 iter != _model->children().end(); ++iter, ++ndx) {
2024 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2025 if(prim && prim == _drag_prim) {
2026 sanitize_connections(iter);
2027 get_selection()->select(iter);
2028 break;
2029 }
2030 }
2032 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2034 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2035 }
2037 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2038 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2039 {
2040 if(_autoscroll) {
2041 Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2042 double v;
2044 v = a.get_value() + _autoscroll;
2045 if(v < 0)
2046 v = 0;
2047 if(v > a.get_upper() - a.get_page_size())
2048 v = a.get_upper() - a.get_page_size();
2050 a.set_value(v);
2052 queue_draw();
2053 }
2055 return true;
2056 }
2058 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2059 {
2060 return _model->children().size();
2061 }
2063 /*** FilterEffectsDialog ***/
2065 FilterEffectsDialog::FilterEffectsDialog()
2066 : UI::Widget::Panel("", "/dialogs/filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2067 _add_primitive_type(FPConverter),
2068 _add_primitive(_("Add Effect:")),
2069 _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2070 _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2071 _settings_initialized(false),
2072 _locked(false),
2073 _attr_lock(false),
2074 _filter_modifier(*this),
2075 _primitive_list(*this)
2076 {
2077 _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2078 NR_FILTER_ENDPRIMITIVETYPE);
2079 _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2080 1);
2081 _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2082 _sizegroup->set_ignore_hidden();
2084 _add_primitive_type.remove_row(NR_FILTER_TILE);
2085 _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2087 // Initialize widget hierarchy
2088 Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2089 Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2090 Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox(/*homogeneous:*/false, /*spacing:*/4));
2091 Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2093 _getContents()->add(*hpaned);
2094 hpaned->pack1(_filter_modifier);
2095 hpaned->pack2(_primitive_box);
2096 _primitive_box.pack_start(*sw_prims);
2097 _primitive_box.pack_start(*hb_prims, false, false);
2098 _primitive_box.pack_start(*infobox,false, false);
2099 sw_prims->add(_primitive_list);
2100 infobox->pack_start(_infobox_icon, false, false);
2101 infobox->pack_start(_infobox_desc, false, false);
2102 _infobox_desc.set_line_wrap(true);
2103 _infobox_desc.set_size_request(200, -1);
2105 hb_prims->pack_start(_add_primitive, false, false);
2106 hb_prims->pack_start(_add_primitive_type, false, false);
2107 _getContents()->pack_start(_settings_tabs, false, false);
2108 _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2109 _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2111 _primitive_list.signal_primitive_changed().connect(
2112 sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2113 _filter_modifier.signal_filter_changed().connect(
2114 sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2116 _add_primitive_type.signal_changed().connect(
2117 sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2119 sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2120 sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2121 // al_settings->set_padding(0, 0, 12, 0);
2122 // fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2123 // ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2124 _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2125 _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2126 sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2128 show_all_children();
2129 init_settings_widgets();
2130 _primitive_list.update();
2131 update_primitive_infobox();
2132 }
2134 FilterEffectsDialog::~FilterEffectsDialog()
2135 {
2136 delete _settings;
2137 delete _filter_general_settings;
2138 }
2140 void FilterEffectsDialog::set_attrs_locked(const bool l)
2141 {
2142 _locked = l;
2143 }
2145 void FilterEffectsDialog::show_all_vfunc()
2146 {
2147 UI::Widget::Panel::show_all_vfunc();
2149 update_settings_view();
2150 }
2152 void FilterEffectsDialog::init_settings_widgets()
2153 {
2154 // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2155 // most of the current values are complete guesses!
2157 _empty_settings.set_sensitive(false);
2158 _settings_tab1.pack_start(_empty_settings);
2160 _no_filter_selected.set_sensitive(false);
2161 _settings_tab2.pack_start(_no_filter_selected);
2162 _settings_initialized = true;
2164 _filter_general_settings->type(0);
2165 _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"));
2166 _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"));
2168 _settings->type(NR_FILTER_BLEND);
2169 _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode:"), BlendModeConverter);
2171 _settings->type(NR_FILTER_COLORMATRIX);
2172 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."));
2173 _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s):"));
2174 colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2176 _settings->type(NR_FILTER_COMPONENTTRANSFER);
2177 _settings->add_notimplemented();
2178 /*
2179 //TRANSLATORS: for info on "Slope" and "Intercept", see http://id.mind.net/~zona/mmts/functionInstitute/linearFunctions/lsif.html
2180 _settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2181 _ct_slope = _settings->add_spinslider(1, SP_ATTR_SLOPE, _("Slope"), -10, 10, 0.1, 0.01, 2);
2182 _ct_intercept = _settings->add_spinslider(0, SP_ATTR_INTERCEPT, _("Intercept"), -10, 10, 0.1, 0.01, 2);
2183 _ct_amplitude = _settings->add_spinslider(1, SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 10, 0.1, 0.01, 2);
2184 _ct_exponent = _settings->add_spinslider(1, SP_ATTR_EXPONENT, _("Exponent"), 0, 10, 0.1, 0.01, 2);
2185 _ct_offset = _settings->add_spinslider(0, SP_ATTR_OFFSET, _("Offset"), -10, 10, 0.1, 0.01, 2);*/
2187 _settings->type(NR_FILTER_COMPOSITE);
2188 _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator:"), CompositeOperatorConverter);
2189 _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."));
2190 _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."));
2191 _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."));
2192 _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."));
2194 _settings->type(NR_FILTER_CONVOLVEMATRIX);
2195 _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"));
2196 _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."));
2197 //TRANSLATORS: for info on "Kernel", see http://en.wikipedia.org/wiki/Kernel_(matrix)
2198 _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."));
2199 _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2200 _settings->add_spinslider(0, SP_ATTR_DIVISOR, _("Divisor:"), 0, 1000, 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."));
2201 _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."));
2202 _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."));
2203 _settings->add_checkbutton(false, SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive."));
2205 _settings->type(NR_FILTER_DIFFUSELIGHTING);
2206 _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Diffuse Color:"), _("Defines the color of the light source"));
2207 _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale:"), -5, 5, 0.01, 0.001, 3, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2208 _settings->add_spinslider(1, SP_ATTR_DIFFUSECONSTANT, _("Constant:"), 0, 5, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2209 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2210 _settings->add_lightsource();
2212 _settings->type(NR_FILTER_DISPLACEMENTMAP);
2213 _settings->add_spinslider(0, SP_ATTR_SCALE, _("Scale:"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2214 _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2215 _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2217 _settings->type(NR_FILTER_FLOOD);
2218 _settings->add_color(/*default: black*/ 0, SP_PROP_FLOOD_COLOR, _("Flood Color:"), _("The whole filter region will be filled with this color."));
2219 _settings->add_spinslider(1, SP_PROP_FLOOD_OPACITY, _("Opacity:"), 0, 1, 0.1, 0.01, 2);
2221 _settings->type(NR_FILTER_GAUSSIANBLUR);
2222 _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation:"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2224 _settings->type(NR_FILTER_MERGE);
2225 _settings->add_no_params();
2227 _settings->type(NR_FILTER_MORPHOLOGY);
2228 _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator:"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2229 _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius:"), 0, 100, 1, 0.01, 1);
2231 _settings->type(NR_FILTER_IMAGE);
2232 _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image:"));
2234 _settings->type(NR_FILTER_OFFSET);
2235 _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"));
2236 _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"));
2238 _settings->type(NR_FILTER_SPECULARLIGHTING);
2239 _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Specular Color:"), _("Defines the color of the light source"));
2240 _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale:"), -5, 5, 0.1, 0.01, 2, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2241 _settings->add_spinslider(1, SP_ATTR_SPECULARCONSTANT, _("Constant:"), 0, 5, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2242 _settings->add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Exponent:"), 1, 50, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2243 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2244 _settings->add_lightsource();
2246 _settings->type(NR_FILTER_TILE);
2247 _settings->add_notimplemented();
2249 _settings->type(NR_FILTER_TURBULENCE);
2250 // _settings->add_checkbutton(false, SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2251 _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type:"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2252 _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency:"), 0, 0.4, 0.001, 0.01, 3);
2253 _settings->add_spinslider(1, SP_ATTR_NUMOCTAVES, _("Octaves:"), 1, 10, 1, 1, 0);
2254 _settings->add_spinslider(0, SP_ATTR_SEED, _("Seed:"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2255 }
2257 void FilterEffectsDialog::add_primitive()
2258 {
2259 SPFilter* filter = _filter_modifier.get_selected_filter();
2261 if(filter) {
2262 SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2264 _primitive_list.select(prim);
2266 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2267 }
2268 }
2270 void FilterEffectsDialog::update_primitive_infobox()
2271 {
2272 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2273 if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2274 _infobox_icon.show();
2275 _infobox_desc.show();
2276 } else {
2277 _infobox_icon.hide();
2278 _infobox_desc.hide();
2279 }
2280 switch(_add_primitive_type.get_active_data()->id){
2281 case(NR_FILTER_BLEND):
2282 _infobox_icon.set_from_icon_name("feBlend-icon", Gtk::ICON_SIZE_DIALOG);
2283 _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2284 break;
2285 case(NR_FILTER_COLORMATRIX):
2286 _infobox_icon.set_from_icon_name("feColorMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2287 _infobox_desc.set_markup(_("The <b>feColorMatrix</b> filter primitive applies a matrix transformation to color of each rendered pixel. This allows for effects like turning object to grayscale, modifying color saturation and changing color hue."));
2288 break;
2289 case(NR_FILTER_COMPONENTTRANSFER):
2290 _infobox_icon.set_from_icon_name("feComponentTransfer-icon", Gtk::ICON_SIZE_DIALOG);
2291 _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."));
2292 break;
2293 case(NR_FILTER_COMPOSITE):
2294 _infobox_icon.set_from_icon_name("feComposite-icon", Gtk::ICON_SIZE_DIALOG);
2295 _infobox_desc.set_markup(_("The <b>feComposite</b> filter primitive composites two images using one of the Porter-Duff blending modes or the arithmetic mode described in SVG standard. Porter-Duff blending modes are essentially logical operations between the corresponding pixel values of the images."));
2296 break;
2297 case(NR_FILTER_CONVOLVEMATRIX):
2298 _infobox_icon.set_from_icon_name("feConvolveMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2299 _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."));
2300 break;
2301 case(NR_FILTER_DIFFUSELIGHTING):
2302 _infobox_icon.set_from_icon_name("feDiffuseLighting-icon", Gtk::ICON_SIZE_DIALOG);
2303 _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."));
2304 break;
2305 case(NR_FILTER_DISPLACEMENTMAP):
2306 _infobox_icon.set_from_icon_name("feDisplacementMap-icon", Gtk::ICON_SIZE_DIALOG);
2307 _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."));
2308 break;
2309 case(NR_FILTER_FLOOD):
2310 _infobox_icon.set_from_icon_name("feFlood-icon", Gtk::ICON_SIZE_DIALOG);
2311 _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."));
2312 break;
2313 case(NR_FILTER_GAUSSIANBLUR):
2314 _infobox_icon.set_from_icon_name("feGaussianBlur-icon", Gtk::ICON_SIZE_DIALOG);
2315 _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."));
2316 break;
2317 case(NR_FILTER_IMAGE):
2318 _infobox_icon.set_from_icon_name("feImage-icon", Gtk::ICON_SIZE_DIALOG);
2319 _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2320 break;
2321 case(NR_FILTER_MERGE):
2322 _infobox_icon.set_from_icon_name("feMerge-icon", Gtk::ICON_SIZE_DIALOG);
2323 _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."));
2324 break;
2325 case(NR_FILTER_MORPHOLOGY):
2326 _infobox_icon.set_from_icon_name("feMorphology-icon", Gtk::ICON_SIZE_DIALOG);
2327 _infobox_desc.set_markup(_("The <b>feMorphology</b> filter primitive provides erode and dilate effects. For single-color objects erode makes the object thinner and dilate makes it thicker."));
2328 break;
2329 case(NR_FILTER_OFFSET):
2330 _infobox_icon.set_from_icon_name("feOffset-icon", Gtk::ICON_SIZE_DIALOG);
2331 _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."));
2332 break;
2333 case(NR_FILTER_SPECULARLIGHTING):
2334 _infobox_icon.set_from_icon_name("feSpecularLighting-icon", Gtk::ICON_SIZE_DIALOG);
2335 _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."));
2336 break;
2337 case(NR_FILTER_TILE):
2338 _infobox_icon.set_from_icon_name("feTile-icon", Gtk::ICON_SIZE_DIALOG);
2339 _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2340 break;
2341 case(NR_FILTER_TURBULENCE):
2342 _infobox_icon.set_from_icon_name("feTurbulence-icon", Gtk::ICON_SIZE_DIALOG);
2343 _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."));
2344 break;
2345 default:
2346 g_assert(false);
2347 break;
2348 }
2349 _infobox_icon.set_pixel_size(96);
2350 }
2352 void FilterEffectsDialog::duplicate_primitive()
2353 {
2354 SPFilter* filter = _filter_modifier.get_selected_filter();
2355 SPFilterPrimitive* origprim = _primitive_list.get_selected();
2357 if(filter && origprim) {
2358 Inkscape::XML::Node *repr;
2359 repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2360 SP_OBJECT_REPR(filter)->appendChild(repr);
2362 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2364 _primitive_list.update();
2365 }
2366 }
2368 void FilterEffectsDialog::convolve_order_changed()
2369 {
2370 _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2371 _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2372 _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2373 }
2375 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2376 {
2377 set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2378 }
2380 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2381 {
2382 if(!_locked) {
2383 _attr_lock = true;
2384 SPFilter *filter = _filter_modifier.get_selected_filter();
2385 const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2386 if (filter && name && SP_OBJECT_REPR(filter)){
2387 SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2388 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2389 }
2390 _attr_lock = false;
2391 }
2392 }
2394 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2395 {
2396 set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2397 }
2399 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2400 {
2401 if(!_locked) {
2402 _attr_lock = true;
2404 SPFilter *filter = _filter_modifier.get_selected_filter();
2405 const gchar* name = (const gchar*)sp_attribute_name(attr);
2406 if(filter && name && o) {
2407 update_settings_sensitivity();
2409 SP_OBJECT_REPR(o)->setAttribute(name, val);
2410 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2412 Glib::ustring undokey = "filtereffects:";
2413 undokey += name;
2414 sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2415 _("Set filter primitive attribute"));
2416 }
2418 _attr_lock = false;
2419 }
2420 }
2422 void FilterEffectsDialog::update_filter_general_settings_view()
2423 {
2424 if(_settings_initialized != true) return;
2426 if(!_locked) {
2427 _attr_lock = true;
2429 SPFilter* filter = _filter_modifier.get_selected_filter();
2431 if(filter) {
2432 _filter_general_settings->show_and_update(0, filter);
2433 _no_filter_selected.hide();
2434 }
2435 else {
2436 std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2437 vect[0]->hide_all();
2438 _no_filter_selected.show();
2439 }
2441 _attr_lock = false;
2442 }
2443 }
2445 void FilterEffectsDialog::update_settings_view()
2446 {
2447 update_settings_sensitivity();
2449 if(_attr_lock)
2450 return;
2452 //First Tab
2454 std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2455 for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2456 _empty_settings.show();
2458 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2459 if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2460 _infobox_icon.show();
2461 _infobox_desc.show();
2462 } else {
2463 _infobox_icon.hide();
2464 _infobox_desc.hide();
2465 }
2467 SPFilterPrimitive* prim = _primitive_list.get_selected();
2469 if(prim) {
2470 _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2471 _empty_settings.hide();
2472 }
2474 //Second Tab
2476 std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2477 vect2[0]->hide_all();
2478 _no_filter_selected.show();
2480 SPFilter* filter = _filter_modifier.get_selected_filter();
2482 if(filter) {
2483 _filter_general_settings->show_and_update(0, filter);
2484 _no_filter_selected.hide();
2485 }
2487 }
2489 void FilterEffectsDialog::update_settings_sensitivity()
2490 {
2491 SPFilterPrimitive* prim = _primitive_list.get_selected();
2492 const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2493 _k1->set_sensitive(use_k);
2494 _k2->set_sensitive(use_k);
2495 _k3->set_sensitive(use_k);
2496 _k4->set_sensitive(use_k);
2498 // Component transfer not yet implemented
2499 /*
2500 if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2501 SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2502 const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2503 const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2505 _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2506 _ct_slope->set_sensitive(linear);
2507 _ct_intercept->set_sensitive(linear);
2508 _ct_amplitude->set_sensitive(gamma);
2509 _ct_exponent->set_sensitive(gamma);
2510 _ct_offset->set_sensitive(gamma);
2511 }
2512 */
2513 }
2515 void FilterEffectsDialog::update_color_matrix()
2516 {
2517 _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2518 }
2520 } // namespace Dialog
2521 } // namespace UI
2522 } // namespace Inkscape
2524 /*
2525 Local Variables:
2526 mode:c++
2527 c-file-style:"stroustrup"
2528 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2529 indent-tabs-mode:nil
2530 fill-column:99
2531 End:
2532 */
2533 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :