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