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