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