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(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, true),//TO-DO: receive a defaultvalue parameter in the constructor
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) _tt.set_tip(*this, tip_text);
213 combo = new ComboBoxEnum<T>(default_value, c, a);
214 add(*combo);
215 show_all();
216 }
218 ~ComboWithTooltip()
219 {
220 delete combo;
221 }
223 ComboBoxEnum<T>* get_attrwidget()
224 {
225 return combo;
226 }
227 private:
228 Gtk::Tooltips _tt;
229 ComboBoxEnum<T>* combo;
230 };
232 // Contains an arbitrary number of spin buttons that use seperate attributes
233 class MultiSpinButton : public Gtk::HBox
234 {
235 public:
236 MultiSpinButton(double lower, double upper, double step_inc,
237 double climb_rate, int digits, std::vector<SPAttributeEnum> attrs, std::vector<double> default_values, std::vector<char*> tip_text)
238 {
239 g_assert(attrs.size()==default_values.size());
240 g_assert(attrs.size()==tip_text.size());
241 for(unsigned i = 0; i < attrs.size(); ++i) {
242 _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i], default_values[i], tip_text[i]));
243 pack_start(*_spins.back(), false, false);
244 }
245 }
247 ~MultiSpinButton()
248 {
249 for(unsigned i = 0; i < _spins.size(); ++i)
250 delete _spins[i];
251 }
253 std::vector<SpinButtonAttr*>& get_spinbuttons()
254 {
255 return _spins;
256 }
257 private:
258 std::vector<SpinButtonAttr*> _spins;
259 };
261 // Contains two spinbuttons that describe a NumberOptNumber
262 class DualSpinButton : public Gtk::HBox, public AttrWidget
263 {
264 public:
265 DualSpinButton(double lower, double upper, double step_inc,
266 double climb_rate, int digits, const SPAttributeEnum a, char* tt1, char* tt2)
267 : AttrWidget(a), //TO-DO: receive default num-opt-num as parameter in the constructor
268 _s1(climb_rate, digits), _s2(climb_rate, digits)
269 {
270 if (tt1) _tt.set_tip(_s1, tt1);
271 if (tt2) _tt.set_tip(_s2, tt2);
272 _s1.set_range(lower, upper);
273 _s2.set_range(lower, upper);
274 _s1.set_increments(step_inc, step_inc * 5);
275 _s2.set_increments(step_inc, step_inc * 5);
277 _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
278 _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
280 pack_start(_s1, false, false);
281 pack_start(_s2, false, false);
282 }
284 Gtk::SpinButton& get_spinbutton1()
285 {
286 return _s1;
287 }
289 Gtk::SpinButton& get_spinbutton2()
290 {
291 return _s2;
292 }
294 virtual Glib::ustring get_as_attribute() const
295 {
296 double v1 = _s1.get_value();
297 double v2 = _s2.get_value();
299 if(_s1.get_digits() == 0) {
300 v1 = (int)v1;
301 v2 = (int)v2;
302 }
304 return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
305 }
307 virtual void set_from_attribute(SPObject* o)
308 {
309 const gchar* val = attribute_value(o);
310 NumberOptNumber n;
311 if(val) {
312 n.set(val);
313 } else {
314 n.set("0 0"); //TO-DO: replace this line by the next one that is currently commented out
315 // n.set(default_value(o));
316 }
317 _s1.set_value(n.getNumber());
318 _s2.set_value(n.getOptNumber());
320 }
321 private:
322 Gtk::SpinButton _s1, _s2;
323 };
325 class ColorButton : public Gtk::ColorButton, public AttrWidget
326 {
327 public:
328 ColorButton(const SPAttributeEnum a, char* tip_text)
329 : AttrWidget(a)
330 {
331 signal_color_set().connect(signal_attr_changed().make_slot());
332 if (tip_text) _tt.set_tip(*this, tip_text);
334 Gdk::Color col;
335 col.set_rgb(65535, 65535, 65535);
336 set_color(col);
337 }
339 // Returns the color in 'rgb(r,g,b)' form.
340 Glib::ustring get_as_attribute() const
341 {
342 std::ostringstream os;
343 const Gdk::Color c = get_color();
344 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?
345 os << "rgb(" << r << "," << g << "," << b << ")";
346 return os.str();
347 }
350 void set_from_attribute(SPObject* o)
351 {
352 const gchar* val = attribute_value(o);
353 guint32 i = 0;
354 if(val) {
355 i = sp_svg_read_color(val, 0xFFFFFFFF);
356 } else {
357 //TO-DO: read from constructor attribute
358 //i = default_value(o);
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(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(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(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text = NULL)
805 {
806 ColorButton* col = new ColorButton(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(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(lo, 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(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(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(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(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(SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
1006 _settings.add_spinslider(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."));
1007 }
1009 Gtk::VBox& get_box()
1010 {
1011 return _box;
1012 }
1013 protected:
1014 Glib::ustring get_as_attribute() const
1015 {
1016 return "";
1017 }
1018 void set_from_attribute(SPObject* o)
1019 {
1020 if(_locked)
1021 return;
1023 _locked = true;
1025 SPObject* child = o->children;
1027 if(SP_IS_FEDISTANTLIGHT(child))
1028 _light_source.set_active(0);
1029 else if(SP_IS_FEPOINTLIGHT(child))
1030 _light_source.set_active(1);
1031 else if(SP_IS_FESPOTLIGHT(child))
1032 _light_source.set_active(2);
1033 else
1034 _light_source.set_active(-1);
1036 update();
1038 _locked = false;
1039 }
1040 private:
1041 void on_source_changed()
1042 {
1043 if(_locked)
1044 return;
1046 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1047 if(prim) {
1048 _locked = true;
1050 SPObject* child = prim->children;
1051 const int ls = _light_source.get_active_row_number();
1052 // Check if the light source type has changed
1053 if(!(ls == -1 && !child) &&
1054 !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1055 !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1056 !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1057 if(child)
1058 sp_repr_unparent(child->repr);
1060 if(ls != -1) {
1061 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1062 Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1063 prim->repr->appendChild(repr);
1064 }
1066 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1067 update();
1068 }
1070 _locked = false;
1071 }
1072 }
1074 void update()
1075 {
1076 _box.hide_all();
1077 _box.show();
1078 _light_box.show_all();
1080 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1081 if(prim && prim->children)
1082 _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1083 }
1085 FilterEffectsDialog& _dialog;
1086 Gtk::VBox _box;
1087 Settings _settings;
1088 Gtk::HBox _light_box;
1089 Gtk::Label _light_label;
1090 ComboBoxEnum<LightSource> _light_source;
1091 bool _locked;
1092 };
1094 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1095 {
1096 LightSourceControl* ls = new LightSourceControl(_dialog);
1097 add_attr_widget(ls);
1098 add_widget(&ls->get_box(), "");
1099 return ls;
1100 }
1102 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1103 sigc::slot<void> rem)
1104 {
1105 Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1107 menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1108 Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1109 menu->append(*mi);
1110 mi->signal_activate().connect(rem);
1111 mi->show();
1112 menu->accelerate(parent);
1114 return menu;
1115 }
1117 /*** FilterModifier ***/
1118 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1119 : _dialog(d), _add(Gtk::Stock::NEW), _observer(new SignalObserver)
1120 {
1121 Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1122 pack_start(*sw);
1123 pack_start(_add, false, false);
1124 sw->add(_list);
1126 _model = Gtk::ListStore::create(_columns);
1127 _list.set_model(_model);
1128 _cell_toggle.set_active(true);
1129 const int selcol = _list.append_column("", _cell_toggle);
1130 Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1131 if(col)
1132 col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1133 _list.append_column_editable(_("_Filter"), _columns.label);
1134 ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1135 signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1137 sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1138 sw->set_shadow_type(Gtk::SHADOW_IN);
1139 show_all_children();
1140 _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1141 _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1142 _list.signal_button_release_event().connect_notify(
1143 sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1144 _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1145 sigc::mem_fun(*this, &FilterModifier::remove_filter));
1146 _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1147 _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1148 _menu->accelerate(*this);
1150 _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1151 _observer->signal_changed().connect(signal_filter_changed().make_slot());
1152 g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1153 G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1155 g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1156 G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1158 on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1159 update_filters();
1160 }
1162 FilterEffectsDialog::FilterModifier::~FilterModifier()
1163 {
1164 _resource_changed.disconnect();
1165 _doc_replaced.disconnect();
1166 }
1168 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1169 {
1170 me->_doc_replaced.disconnect();
1171 me->_doc_replaced = desktop->connectDocumentReplaced(
1172 sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1174 me->_resource_changed.disconnect();
1175 me->_resource_changed =
1176 sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1177 sigc::mem_fun(me, &FilterModifier::update_filters));
1179 me->_dialog.setDesktop(desktop);
1181 me->update_filters();
1182 }
1185 // When the selection changes, show the active filter(s) in the dialog
1186 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1187 Selection *sel,
1188 FilterModifier* fm)
1189 {
1190 if(fm && sel)
1191 fm->update_selection(sel);
1192 }
1194 // Update each filter's sel property based on the current object selection;
1195 // If the filter is not used by any selected object, sel = 0,
1196 // otherwise sel is set to the total number of filters in use by selected objects
1197 // If only one filter is in use, it is selected
1198 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1199 {
1200 std::set<SPObject*> used;
1202 for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1203 SPObject *obj = SP_OBJECT (i->data);
1204 SPStyle *style = SP_OBJECT_STYLE (obj);
1205 if(!style || !SP_IS_ITEM(obj)) continue;
1207 if(style->filter.set && style->getFilter())
1208 used.insert(style->getFilter());
1209 else
1210 used.insert(0);
1211 }
1213 const int size = used.size();
1215 for(Gtk::TreeIter iter = _model->children().begin();
1216 iter != _model->children().end(); ++iter) {
1217 if(used.find((*iter)[_columns.filter]) != used.end()) {
1218 // If only one filter is in use by the selection, select it
1219 if(size == 1)
1220 _list.get_selection()->select(iter);
1221 (*iter)[_columns.sel] = size;
1222 }
1223 else
1224 (*iter)[_columns.sel] = 0;
1225 }
1226 }
1228 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1229 {
1230 _observer->set(get_selected_filter());
1231 signal_filter_changed()();
1232 }
1234 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1235 {
1236 Gtk::TreeModel::iterator iter = _model->get_iter(path);
1238 if(iter) {
1239 SPFilter* filter = (*iter)[_columns.filter];
1240 filter->setLabel(text.c_str());
1241 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1242 if(iter)
1243 (*iter)[_columns.label] = text;
1244 }
1245 }
1247 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1248 {
1249 Gtk::TreeIter iter = _model->get_iter(path);
1251 if(iter) {
1252 SPDesktop *desktop = _dialog.getDesktop();
1253 SPDocument *doc = sp_desktop_document(desktop);
1254 SPFilter* filter = (*iter)[_columns.filter];
1255 Inkscape::Selection *sel = sp_desktop_selection(desktop);
1257 /* If this filter is the only one used in the selection, unset it */
1258 if((*iter)[_columns.sel] == 1)
1259 filter = 0;
1261 GSList const *items = sel->itemList();
1263 for (GSList const *i = items; i != NULL; i = i->next) {
1264 SPItem * item = SP_ITEM(i->data);
1265 SPStyle *style = SP_OBJECT_STYLE(item);
1266 g_assert(style != NULL);
1268 if(filter)
1269 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1270 else
1271 ::remove_filter(item, false);
1273 SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1274 }
1276 update_selection(sel);
1277 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Apply filter"));
1278 }
1279 }
1281 /* Add all filters in the document to the combobox.
1282 Keeps the same selection if possible, otherwise selects the first element */
1283 void FilterEffectsDialog::FilterModifier::update_filters()
1284 {
1285 SPDesktop* desktop = _dialog.getDesktop();
1286 SPDocument* document = sp_desktop_document(desktop);
1287 const GSList* filters = sp_document_get_resource_list(document, "filter");
1289 _model->clear();
1291 for(const GSList *l = filters; l; l = l->next) {
1292 Gtk::TreeModel::Row row = *_model->append();
1293 SPFilter* f = (SPFilter*)l->data;
1294 row[_columns.filter] = f;
1295 const gchar* lbl = f->label();
1296 const gchar* id = SP_OBJECT_ID(f);
1297 row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1298 }
1300 update_selection(desktop->selection);
1301 _dialog.update_filter_general_settings_view();
1302 }
1304 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1305 {
1306 if(_list.get_selection()) {
1307 Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1309 if(i)
1310 return (*i)[_columns.filter];
1311 }
1313 return 0;
1314 }
1316 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1317 {
1318 if(filter) {
1319 for(Gtk::TreeModel::iterator i = _model->children().begin();
1320 i != _model->children().end(); ++i) {
1321 if((*i)[_columns.filter] == filter) {
1322 _list.get_selection()->select(i);
1323 break;
1324 }
1325 }
1326 }
1327 }
1329 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1330 {
1331 if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1332 const bool sensitive = get_selected_filter() != NULL;
1333 _menu->items()[0].set_sensitive(sensitive);
1334 _menu->items()[1].set_sensitive(sensitive);
1335 _menu->popup(event->button, event->time);
1336 }
1337 }
1339 void FilterEffectsDialog::FilterModifier::add_filter()
1340 {
1341 SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1342 SPFilter* filter = new_filter(doc);
1344 const int count = _model->children().size();
1345 std::ostringstream os;
1346 os << "filter" << count;
1347 filter->setLabel(os.str().c_str());
1349 update_filters();
1351 select_filter(filter);
1353 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1354 }
1356 void FilterEffectsDialog::FilterModifier::remove_filter()
1357 {
1358 SPFilter *filter = get_selected_filter();
1360 if(filter) {
1361 SPDocument* doc = filter->document;
1362 sp_repr_unparent(filter->repr);
1364 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1366 update_filters();
1367 }
1368 }
1370 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1371 {
1372 SPFilter* filter = get_selected_filter();
1374 if(filter) {
1375 Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1376 repr = repr->duplicate(repr->document());
1377 parent->appendChild(repr);
1379 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1381 update_filters();
1382 }
1383 }
1385 void FilterEffectsDialog::FilterModifier::rename_filter()
1386 {
1387 _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1388 }
1390 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1391 : Glib::ObjectBase(typeid(CellRendererConnection)),
1392 _primitive(*this, "primitive", 0)
1393 {}
1395 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1396 {
1397 return _primitive.get_proxy();
1398 }
1400 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1401 {
1402 _text_width = w;
1403 }
1405 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1406 {
1407 return _text_width;
1408 }
1410 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1411 Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1412 int* x_offset, int* y_offset, int* width, int* height) const
1413 {
1414 PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1416 if(x_offset)
1417 (*x_offset) = 0;
1418 if(y_offset)
1419 (*y_offset) = 0;
1420 if(width)
1421 (*width) = size * primlist.primitive_count() + _text_width * 7;
1422 if(height) {
1423 // Scale the height depending on the number of inputs, unless it's
1424 // the first primitive, in which case there are no connections
1425 SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1426 (*height) = size * input_count(prim);
1427 }
1428 }
1430 /*** PrimitiveList ***/
1431 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1432 : _dialog(d),
1433 _in_drag(0),
1434 _observer(new SignalObserver)
1435 {
1436 d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1438 add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1439 signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1441 _model = Gtk::ListStore::create(_columns);
1443 set_reorderable(true);
1445 set_model(_model);
1446 append_column(_("_Effect"), _columns.type);
1448 _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1449 get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1450 signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1452 _connection_cell.set_text_width(init_text());
1454 int cols_count = append_column(_("Connections"), _connection_cell);
1455 Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1456 if(col)
1457 col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1458 }
1460 // Sets up a vertical Pango context/layout, and returns the largest
1461 // width needed to render the FilterPrimitiveInput labels.
1462 int FilterEffectsDialog::PrimitiveList::init_text()
1463 {
1464 // Set up a vertical context+layout
1465 Glib::RefPtr<Pango::Context> context = create_pango_context();
1466 const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1467 context->set_matrix(matrix);
1468 _vertical_layout = Pango::Layout::create(context);
1470 int maxfont = 0;
1471 for(int i = 0; i < FPInputConverter.end; ++i) {
1472 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1473 int fontw, fonth;
1474 _vertical_layout->get_pixel_size(fontw, fonth);
1475 if(fonth > maxfont)
1476 maxfont = fonth;
1477 }
1479 return maxfont;
1480 }
1482 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1483 {
1484 return _signal_primitive_changed;
1485 }
1487 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1488 {
1489 _observer->set(get_selected());
1490 signal_primitive_changed()();
1491 _dialog._color_matrix_values->clear_store();
1492 }
1494 /* Add all filter primitives in the current to the list.
1495 Keeps the same selection if possible, otherwise selects the first element */
1496 void FilterEffectsDialog::PrimitiveList::update()
1497 {
1498 SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1499 const SPFilterPrimitive* active_prim = get_selected();
1500 bool active_found = false;
1502 _model->clear();
1504 if(f) {
1505 _dialog._primitive_box.set_sensitive(true);
1506 _dialog.update_filter_general_settings_view();
1507 for(SPObject *prim_obj = f->children;
1508 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1509 prim_obj = prim_obj->next) {
1510 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1511 if(prim) {
1512 Gtk::TreeModel::Row row = *_model->append();
1513 row[_columns.primitive] = prim;
1514 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1515 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1516 row[_columns.id] = SP_OBJECT_ID(prim);
1518 if(prim == active_prim) {
1519 get_selection()->select(row);
1520 active_found = true;
1521 }
1522 }
1523 }
1525 if(!active_found && _model->children().begin())
1526 get_selection()->select(_model->children().begin());
1528 columns_autosize();
1529 }
1530 else {
1531 _dialog._primitive_box.set_sensitive(false);
1532 }
1533 }
1535 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1536 {
1537 _primitive_menu = menu;
1538 }
1540 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1541 {
1542 if(_dialog._filter_modifier.get_selected_filter()) {
1543 Gtk::TreeModel::iterator i = get_selection()->get_selected();
1544 if(i)
1545 return (*i)[_columns.primitive];
1546 }
1548 return 0;
1549 }
1551 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1552 {
1553 for(Gtk::TreeIter i = _model->children().begin();
1554 i != _model->children().end(); ++i) {
1555 if((*i)[_columns.primitive] == prim)
1556 get_selection()->select(i);
1557 }
1558 }
1560 void FilterEffectsDialog::PrimitiveList::remove_selected()
1561 {
1562 SPFilterPrimitive* prim = get_selected();
1564 if(prim) {
1565 _observer->set(0);
1567 sp_repr_unparent(prim->repr);
1569 sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1570 _("Remove filter primitive"));
1572 update();
1573 }
1574 }
1576 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1577 {
1578 Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1579 Glib::RefPtr<Gdk::Window> win = get_bin_window();
1580 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1582 SPFilterPrimitive* prim = get_selected();
1583 int row_count = get_model()->children().size();
1585 int fheight = CellRendererConnection::size;
1586 Gdk::Rectangle rct, vis;
1587 Gtk::TreeIter row = get_model()->children().begin();
1588 int text_start_x = 0;
1589 if(row) {
1590 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1591 get_visible_rect(vis);
1592 int vis_x, vis_y;
1593 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1595 text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter.end + 1) + 1;
1596 for(int i = 0; i < FPInputConverter.end; ++i) {
1597 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1598 const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1599 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());
1600 get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1601 get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1602 }
1603 }
1605 int row_index = 0;
1606 for(; row != get_model()->children().end(); ++row, ++row_index) {
1607 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1608 const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1610 // Check mouse state
1611 int mx, my;
1612 Gdk::ModifierType mask;
1613 get_bin_window()->get_pointer(mx, my, mask);
1615 // Outline the bottom of the connection area
1616 const int outline_x = x + fheight * (row_count - row_index);
1617 get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1619 // Side outline
1620 get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1622 std::vector<Gdk::Point> con_poly;
1623 int con_drag_y = 0;
1624 bool inside;
1625 const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1626 const int inputs = input_count(row_prim);
1628 if(SP_IS_FEMERGE(row_prim)) {
1629 for(int i = 0; i < inputs; ++i) {
1630 inside = do_connection_node(row, i, con_poly, mx, my);
1631 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1632 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1633 inside, con_poly);
1635 if(_in_drag == (i + 1))
1636 con_drag_y = con_poly[2].get_y();
1638 if(_in_drag != (i + 1) || row_prim != prim)
1639 draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1640 }
1641 }
1642 else {
1643 // Draw "in" shape
1644 inside = do_connection_node(row, 0, con_poly, mx, my);
1645 con_drag_y = con_poly[2].get_y();
1646 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1647 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1648 inside, con_poly);
1650 // Draw "in" connection
1651 if(_in_drag != 1 || row_prim != prim)
1652 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1654 if(inputs == 2) {
1655 // Draw "in2" shape
1656 inside = do_connection_node(row, 1, con_poly, mx, my);
1657 if(_in_drag == 2)
1658 con_drag_y = con_poly[2].get_y();
1659 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1660 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1661 inside, con_poly);
1662 // Draw "in2" connection
1663 if(_in_drag != 2 || row_prim != prim)
1664 draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1665 }
1666 }
1668 // Draw drag connection
1669 if(row_prim == prim && _in_drag) {
1670 get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1671 mx, con_drag_y);
1672 get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1673 }
1674 }
1676 return true;
1677 }
1679 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1680 const int text_start_x, const int x1, const int y1,
1681 const int row_count)
1682 {
1683 int src_id = 0;
1684 Gtk::TreeIter res = find_result(input, attr, src_id);
1685 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1686 Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1687 Glib::RefPtr<Gdk::GC> gc;
1689 const bool is_first = input == get_model()->children().begin();
1690 const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1691 const bool use_default = !res && !is_merge;
1693 if(res == input || (use_default && is_first)) {
1694 // Draw straight connection to a standard input
1695 // Draw a lighter line for an implicit connection to a standard input
1696 const int tw = _connection_cell.get_text_width();
1697 gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1698 gc = (use_default && is_first) ? lightgc : darkgc;
1699 get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1700 get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1701 }
1702 else {
1703 // Draw an 'L'-shaped connection to another filter primitive
1704 // If no connection is specified, draw a light connection to the previous primitive
1705 gc = use_default ? lightgc : darkgc;
1707 if(use_default) {
1708 res = input;
1709 --res;
1710 }
1712 if(res) {
1713 Gdk::Rectangle rct;
1715 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1716 const int fheight = CellRendererConnection::size;
1718 get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1719 const int row_index = find_index(res);
1720 const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1721 const int y2 = rct.get_y() + rct.get_height();
1723 // Draw a bevelled 'L'-shaped connection
1724 get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1725 get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1726 get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1727 }
1728 }
1729 }
1731 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1732 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1733 std::vector<Gdk::Point>& points,
1734 const int ix, const int iy)
1735 {
1736 Gdk::Rectangle rct;
1737 const int icnt = input_count((*row)[_columns.primitive]);
1739 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1740 const int fheight = CellRendererConnection::size;
1742 get_cell_area(_model->get_path(row), *get_column(1), rct);
1743 const float h = rct.get_height() / icnt;
1745 const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1746 const int con_w = (int)(fheight * 0.35f);
1747 const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1748 points.clear();
1749 points.push_back(Gdk::Point(x, con_y));
1750 points.push_back(Gdk::Point(x, con_y + con_w * 2));
1751 points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1753 return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1754 }
1756 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1757 const int attr, int& src_id)
1758 {
1759 SPFilterPrimitive* prim = (*start)[_columns.primitive];
1760 Gtk::TreeIter target = _model->children().end();
1761 int image = 0;
1763 if(SP_IS_FEMERGE(prim)) {
1764 int c = 0;
1765 bool found = false;
1766 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1767 if(c == attr && SP_IS_FEMERGENODE(o)) {
1768 image = SP_FEMERGENODE(o)->input;
1769 found = true;
1770 }
1771 }
1772 if(!found)
1773 return target;
1774 }
1775 else {
1776 if(attr == SP_ATTR_IN)
1777 image = prim->image_in;
1778 else if(attr == SP_ATTR_IN2) {
1779 if(SP_IS_FEBLEND(prim))
1780 image = SP_FEBLEND(prim)->in2;
1781 else if(SP_IS_FECOMPOSITE(prim))
1782 image = SP_FECOMPOSITE(prim)->in2;
1783 else if(SP_IS_FEDISPLACEMENTMAP(prim))
1784 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1785 else
1786 return target;
1787 }
1788 else
1789 return target;
1790 }
1792 if(image >= 0) {
1793 for(Gtk::TreeIter i = _model->children().begin();
1794 i != start; ++i) {
1795 if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1796 target = i;
1797 }
1798 return target;
1799 }
1800 else if(image < -1) {
1801 src_id = -(image + 2);
1802 return start;
1803 }
1805 return target;
1806 }
1808 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1809 {
1810 int i = 0;
1811 for(Gtk::TreeIter iter = _model->children().begin();
1812 iter != target; ++iter, ++i);
1813 return i;
1814 }
1816 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1817 {
1818 Gtk::TreePath path;
1819 Gtk::TreeViewColumn* col;
1820 const int x = (int)e->x, y = (int)e->y;
1821 int cx, cy;
1823 _drag_prim = 0;
1825 if(get_path_at_pos(x, y, path, col, cx, cy)) {
1826 Gtk::TreeIter iter = _model->get_iter(path);
1827 std::vector<Gdk::Point> points;
1829 _drag_prim = (*iter)[_columns.primitive];
1830 const int icnt = input_count(_drag_prim);
1832 for(int i = 0; i < icnt; ++i) {
1833 if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1834 _in_drag = i + 1;
1835 break;
1836 }
1837 }
1839 queue_draw();
1840 }
1842 if(_in_drag) {
1843 _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1844 _autoscroll = 0;
1845 get_selection()->select(path);
1846 return true;
1847 }
1848 else
1849 return Gtk::TreeView::on_button_press_event(e);
1850 }
1852 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1853 {
1854 const int speed = 10;
1855 const int limit = 15;
1857 Gdk::Rectangle vis;
1858 get_visible_rect(vis);
1859 int vis_x, vis_y;
1860 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1861 const int top = vis_y + vis.get_height();
1863 // When autoscrolling during a connection drag, set the speed based on
1864 // where the mouse is in relation to the edges.
1865 if(e->y < vis_y)
1866 _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1867 else if(e->y < vis_y + limit)
1868 _autoscroll = -speed;
1869 else if(e->y > top)
1870 _autoscroll = (int)(speed + (e->y - top) / 5);
1871 else if(e->y > top - limit)
1872 _autoscroll = speed;
1873 else
1874 _autoscroll = 0;
1876 queue_draw();
1878 return Gtk::TreeView::on_motion_notify_event(e);
1879 }
1881 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1882 {
1883 SPFilterPrimitive *prim = get_selected(), *target;
1885 _scroll_connection.disconnect();
1887 if(_in_drag && prim) {
1888 Gtk::TreePath path;
1889 Gtk::TreeViewColumn* col;
1890 int cx, cy;
1892 if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1893 const gchar *in_val = 0;
1894 Glib::ustring result;
1895 Gtk::TreeIter target_iter = _model->get_iter(path);
1896 target = (*target_iter)[_columns.primitive];
1897 col = get_column(1);
1899 Gdk::Rectangle rct;
1900 get_cell_area(path, *col, rct);
1901 const int twidth = _connection_cell.get_text_width();
1902 const int sources_x = rct.get_width() - twidth * FPInputConverter.end;
1903 if(cx > sources_x) {
1904 int src = (cx - sources_x) / twidth;
1905 if(src < 0)
1906 src = 0;
1907 else if(src >= FPInputConverter.end)
1908 src = FPInputConverter.end - 1;
1909 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1910 in_val = result.c_str();
1911 }
1912 else {
1913 // Ensure that the target comes before the selected primitive
1914 for(Gtk::TreeIter iter = _model->children().begin();
1915 iter != get_selection()->get_selected(); ++iter) {
1916 if(iter == target_iter) {
1917 Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1918 // Make sure the target has a result
1919 const gchar *gres = repr->attribute("result");
1920 if(!gres) {
1921 result = "result" + Glib::Ascii::dtostr(SP_FILTER(prim->parent)->_image_number_next);
1922 repr->setAttribute("result", result.c_str());
1923 in_val = result.c_str();
1924 }
1925 else
1926 in_val = gres;
1927 break;
1928 }
1929 }
1930 }
1932 if(SP_IS_FEMERGE(prim)) {
1933 int c = 1;
1934 bool handled = false;
1935 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1936 if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1937 // If input is null, delete it
1938 if(!in_val) {
1939 sp_repr_unparent(o->repr);
1940 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1941 _("Remove merge node"));
1942 (*get_selection()->get_selected())[_columns.primitive] = prim;
1943 }
1944 else
1945 _dialog.set_attr(o, SP_ATTR_IN, in_val);
1946 handled = true;
1947 }
1948 }
1949 // Add new input?
1950 if(!handled && c == _in_drag && in_val) {
1951 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1952 Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1953 repr->setAttribute("inkscape:collect", "always");
1954 prim->repr->appendChild(repr);
1955 SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1956 Inkscape::GC::release(repr);
1957 _dialog.set_attr(node, SP_ATTR_IN, in_val);
1958 (*get_selection()->get_selected())[_columns.primitive] = prim;
1959 }
1960 }
1961 else {
1962 if(_in_drag == 1)
1963 _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1964 else if(_in_drag == 2)
1965 _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1966 }
1967 }
1969 _in_drag = 0;
1970 queue_draw();
1972 _dialog.update_settings_view();
1973 }
1975 if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1976 const bool sensitive = get_selected() != NULL;
1977 _primitive_menu->items()[0].set_sensitive(sensitive);
1978 _primitive_menu->items()[1].set_sensitive(sensitive);
1979 _primitive_menu->popup(e->button, e->time);
1981 return true;
1982 }
1983 else
1984 return Gtk::TreeView::on_button_release_event(e);
1985 }
1987 // Checks all of prim's inputs, removes any that use result
1988 void check_single_connection(SPFilterPrimitive* prim, const int result)
1989 {
1990 if(prim && result >= 0) {
1992 if(prim->image_in == result)
1993 SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1995 if(SP_IS_FEBLEND(prim)) {
1996 if(SP_FEBLEND(prim)->in2 == result)
1997 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1998 }
1999 else if(SP_IS_FECOMPOSITE(prim)) {
2000 if(SP_FECOMPOSITE(prim)->in2 == result)
2001 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2002 }
2003 else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
2004 if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
2005 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2006 }
2007 }
2008 }
2010 // Remove any connections going to/from prim_iter that forward-reference other primitives
2011 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
2012 {
2013 SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
2014 bool before = true;
2016 for(Gtk::TreeIter iter = _model->children().begin();
2017 iter != _model->children().end(); ++iter) {
2018 if(iter == prim_iter)
2019 before = false;
2020 else {
2021 SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
2022 if(before)
2023 check_single_connection(cur_prim, prim->image_out);
2024 else
2025 check_single_connection(prim, cur_prim->image_out);
2026 }
2027 }
2028 }
2030 // Reorder the filter primitives to match the list order
2031 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2032 {
2033 SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2034 int ndx = 0;
2036 for(Gtk::TreeModel::iterator iter = _model->children().begin();
2037 iter != _model->children().end(); ++iter, ++ndx) {
2038 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2039 if(prim && prim == _drag_prim) {
2040 SP_OBJECT_REPR(prim)->setPosition(ndx);
2041 break;
2042 }
2043 }
2045 for(Gtk::TreeModel::iterator iter = _model->children().begin();
2046 iter != _model->children().end(); ++iter, ++ndx) {
2047 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2048 if(prim && prim == _drag_prim) {
2049 sanitize_connections(iter);
2050 get_selection()->select(iter);
2051 break;
2052 }
2053 }
2055 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2057 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2058 }
2060 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2061 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2062 {
2063 if(_autoscroll) {
2064 Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2065 double v;
2067 v = a.get_value() + _autoscroll;
2068 if(v < 0)
2069 v = 0;
2070 if(v > a.get_upper() - a.get_page_size())
2071 v = a.get_upper() - a.get_page_size();
2073 a.set_value(v);
2075 queue_draw();
2076 }
2078 return true;
2079 }
2081 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2082 {
2083 return _model->children().size();
2084 }
2086 /*** FilterEffectsDialog ***/
2088 FilterEffectsDialog::FilterEffectsDialog()
2089 : UI::Widget::Panel("", "dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2090 _filter_modifier(*this),
2091 _primitive_list(*this),
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 {
2100 _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2101 NR_FILTER_ENDPRIMITIVETYPE);
2102 _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2103 1);
2104 _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2105 _sizegroup->set_ignore_hidden();
2107 _add_primitive_type.remove_row(NR_FILTER_TILE);
2108 _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2110 // Initialize widget hierarchy
2111 Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2112 Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2113 Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox);
2114 Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2116 _getContents()->add(*hpaned);
2117 hpaned->pack1(_filter_modifier);
2118 hpaned->pack2(_primitive_box);
2119 _primitive_box.pack_start(*sw_prims);
2120 _primitive_box.pack_start(*infobox,false, false);
2121 _primitive_box.pack_start(*hb_prims, false, false);
2122 sw_prims->add(_primitive_list);
2123 infobox->pack_start(_infobox_icon, false, false);
2124 infobox->pack_start(_infobox_desc, false, false);
2125 _infobox_desc.set_line_wrap(true);
2127 hb_prims->pack_end(_add_primitive_type, false, false);
2128 hb_prims->pack_end(_add_primitive, false, false);
2129 _getContents()->pack_start(_settings_tabs, false, false);
2130 _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2131 _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2133 _primitive_list.signal_primitive_changed().connect(
2134 sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2135 _filter_modifier.signal_filter_changed().connect(
2136 sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2138 _add_primitive_type.signal_changed().connect(
2139 sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2141 sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2142 sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2143 // al_settings->set_padding(0, 0, 12, 0);
2144 // fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2145 // ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2146 _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2147 _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2148 sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2150 show_all_children();
2151 init_settings_widgets();
2152 _primitive_list.update();
2153 update_primitive_infobox();
2154 }
2156 FilterEffectsDialog::~FilterEffectsDialog()
2157 {
2158 delete _settings;
2159 delete _filter_general_settings;
2160 }
2162 void FilterEffectsDialog::set_attrs_locked(const bool l)
2163 {
2164 _locked = l;
2165 }
2167 void FilterEffectsDialog::show_all_vfunc()
2168 {
2169 UI::Widget::Panel::show_all_vfunc();
2171 update_settings_view();
2172 }
2174 void FilterEffectsDialog::init_settings_widgets()
2175 {
2176 // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2177 // most of the current values are complete guesses!
2179 _empty_settings.set_sensitive(false);
2180 _settings_tab1.pack_start(_empty_settings);
2182 _no_filter_selected.set_sensitive(false);
2183 _settings_tab2.pack_start(_no_filter_selected);
2184 _settings_initialized = true;
2186 _filter_general_settings->type(0);
2187 _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"));
2188 _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"));
2190 _settings->type(NR_FILTER_BLEND);
2191 _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode"), BlendModeConverter);
2193 _settings->type(NR_FILTER_COLORMATRIX);
2194 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."));
2195 _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s)"));
2196 colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2198 _settings->type(NR_FILTER_COMPONENTTRANSFER);
2199 _settings->add_notimplemented();
2200 /*_settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2201 _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2202 _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2203 _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2204 _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2205 _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2207 _settings->type(NR_FILTER_COMPOSITE);
2208 _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
2209 _k1 = _settings->add_spinslider(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."));
2210 _k2 = _settings->add_spinslider(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."));
2211 _k3 = _settings->add_spinslider(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."));
2212 _k4 = _settings->add_spinslider(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."));
2214 _settings->type(NR_FILTER_CONVOLVEMATRIX);
2215 _convolve_order = _settings->add_dualspinbutton(SP_ATTR_ORDER, _("Size"), 1, 5, 1, 1, 0, _("width of the convolve matrix"), _("height of the convolve matrix"));
2216 _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."));
2217 _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."));
2218 _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2219 _settings->add_spinslider(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."));
2220 _settings->add_spinslider(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."));
2221 _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."));
2222 _settings->add_checkbutton(SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive."));
2224 _settings->type(NR_FILTER_DIFFUSELIGHTING);
2225 _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Diffuse Color"), _("Defines the color of the light source"));
2226 _settings->add_spinslider(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"));
2227 _settings->add_spinslider(SP_ATTR_DIFFUSECONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2228 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2229 _settings->add_lightsource();
2231 _settings->type(NR_FILTER_DISPLACEMENTMAP);
2232 _settings->add_spinslider(SP_ATTR_SCALE, _("Scale"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2233 _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2234 _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2236 _settings->type(NR_FILTER_FLOOD);
2237 _settings->add_color(SP_PROP_FLOOD_COLOR, _("Flood Color"), _("The whole filter region will be filled with this color."));
2238 _settings->add_spinslider(SP_PROP_FLOOD_OPACITY, _("Opacity"), 0, 1, 0.1, 0.01, 2);
2240 _settings->type(NR_FILTER_GAUSSIANBLUR);
2241 _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2243 _settings->type(NR_FILTER_MERGE);
2244 _settings->add_no_params();
2246 _settings->type(NR_FILTER_MORPHOLOGY);
2247 _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2248 _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius"), 0, 100, 1, 0.01, 1);
2250 _settings->type(NR_FILTER_IMAGE);
2251 _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image"));
2253 _settings->type(NR_FILTER_OFFSET);
2254 _settings->add_spinslider(SP_ATTR_DX, _("Delta X"), -100, 100, 1, 0.01, 1, _("This is how far the input image gets shifted to the right"));
2255 _settings->add_spinslider(SP_ATTR_DY, _("Delta Y"), -100, 100, 1, 0.01, 1, _("This is how far the input image gets shifted downwards"));
2257 _settings->type(NR_FILTER_SPECULARLIGHTING);
2258 _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Specular Color"), _("Defines the color of the light source"));
2259 _settings->add_spinslider(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"));
2260 _settings->add_spinslider(SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2261 _settings->add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2262 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2263 _settings->add_lightsource();
2265 _settings->type(NR_FILTER_TILE);
2266 _settings->add_notimplemented();
2268 _settings->type(NR_FILTER_TURBULENCE);
2269 // _settings->add_checkbutton(SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2270 _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2271 _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency"), 0, 1, 0.001, 0.01, 3);
2272 _settings->add_spinslider(SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2273 _settings->add_spinslider(SP_ATTR_SEED, _("Seed"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2274 }
2276 void FilterEffectsDialog::add_primitive()
2277 {
2278 SPFilter* filter = _filter_modifier.get_selected_filter();
2280 if(filter) {
2281 SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2283 _primitive_list.select(prim);
2285 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2286 }
2287 }
2289 void FilterEffectsDialog::update_primitive_infobox()
2290 {
2291 if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2292 _infobox_icon.show();
2293 _infobox_desc.show();
2294 } else {
2295 _infobox_icon.hide();
2296 _infobox_desc.hide();
2297 }
2298 switch(_add_primitive_type.get_active_data()->id){
2299 case(NR::NR_FILTER_BLEND):
2300 _infobox_icon.set(g_strdup_printf("%s/feBlend-icon.png", INKSCAPE_PIXMAPDIR));
2301 _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2302 break;
2303 case(NR::NR_FILTER_COLORMATRIX):
2304 _infobox_icon.set(g_strdup_printf("%s/feColorMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2305 _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."));
2306 break;
2307 case(NR::NR_FILTER_COMPONENTTRANSFER):
2308 _infobox_icon.set(g_strdup_printf("%s/feComponentTransfer-icon.png", INKSCAPE_PIXMAPDIR));
2309 _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."));
2310 break;
2311 case(NR::NR_FILTER_COMPOSITE):
2312 _infobox_icon.set(g_strdup_printf("%s/feComposite-icon.png", INKSCAPE_PIXMAPDIR));
2313 _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."));
2314 break;
2315 case(NR::NR_FILTER_CONVOLVEMATRIX):
2316 _infobox_icon.set(g_strdup_printf("%s/feConvolveMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2317 _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."));
2318 break;
2319 case(NR::NR_FILTER_DIFFUSELIGHTING):
2320 _infobox_icon.set(g_strdup_printf("%s/feDiffuseLighting-icon.png", INKSCAPE_PIXMAPDIR));
2321 _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."));
2322 break;
2323 case(NR::NR_FILTER_DISPLACEMENTMAP):
2324 _infobox_icon.set(g_strdup_printf("%s/feDisplacementMap-icon.png", INKSCAPE_PIXMAPDIR));
2325 _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."));
2326 break;
2327 case(NR::NR_FILTER_FLOOD):
2328 _infobox_icon.set(g_strdup_printf("%s/feFlood-icon.png", INKSCAPE_PIXMAPDIR));
2329 _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."));
2330 break;
2331 case(NR::NR_FILTER_GAUSSIANBLUR):
2332 _infobox_icon.set(g_strdup_printf("%s/feGaussianBlur-icon.png", INKSCAPE_PIXMAPDIR));
2333 _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."));
2334 break;
2335 case(NR::NR_FILTER_IMAGE):
2336 _infobox_icon.set(g_strdup_printf("%s/feImage-icon.png", INKSCAPE_PIXMAPDIR));
2337 _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2338 break;
2339 case(NR::NR_FILTER_MERGE):
2340 _infobox_icon.set(g_strdup_printf("%s/feMerge-icon.png", INKSCAPE_PIXMAPDIR));
2341 _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."));
2342 break;
2343 case(NR::NR_FILTER_MORPHOLOGY):
2344 _infobox_icon.set(g_strdup_printf("%s/feMorphology-icon.png", INKSCAPE_PIXMAPDIR));
2345 _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."));
2346 break;
2347 case(NR::NR_FILTER_OFFSET):
2348 _infobox_icon.set(g_strdup_printf("%s/feOffset-icon.png", INKSCAPE_PIXMAPDIR));
2349 _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."));
2350 break;
2351 case(NR::NR_FILTER_SPECULARLIGHTING):
2352 _infobox_icon.set(g_strdup_printf("%s/feSpecularLighting-icon.png", INKSCAPE_PIXMAPDIR));
2353 _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."));
2354 break;
2355 case(NR::NR_FILTER_TILE):
2356 _infobox_icon.set(g_strdup_printf("%s/feTile-icon.png", INKSCAPE_PIXMAPDIR));
2357 _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2358 break;
2359 case(NR::NR_FILTER_TURBULENCE):
2360 _infobox_icon.set(g_strdup_printf("%s/feTurbulence-icon.png", INKSCAPE_PIXMAPDIR));
2361 _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."));
2362 break;
2363 default:
2364 g_assert(false);
2365 break;
2366 }
2367 }
2369 void FilterEffectsDialog::duplicate_primitive()
2370 {
2371 SPFilter* filter = _filter_modifier.get_selected_filter();
2372 SPFilterPrimitive* origprim = _primitive_list.get_selected();
2374 if(filter && origprim) {
2375 Inkscape::XML::Node *repr;
2376 repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2377 SP_OBJECT_REPR(filter)->appendChild(repr);
2379 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2381 _primitive_list.update();
2382 }
2383 }
2385 void FilterEffectsDialog::convolve_order_changed()
2386 {
2387 _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2388 _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2389 _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2390 }
2392 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2393 {
2394 set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2395 }
2397 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2398 {
2399 if(!_locked) {
2400 _attr_lock = true;
2401 SPFilter *filter = _filter_modifier.get_selected_filter();
2402 const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2403 if (filter && name && SP_OBJECT_REPR(filter)){
2404 SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2405 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2406 }
2407 _attr_lock = false;
2408 }
2409 }
2411 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2412 {
2413 set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2414 }
2416 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2417 {
2418 if(!_locked) {
2419 _attr_lock = true;
2421 SPFilter *filter = _filter_modifier.get_selected_filter();
2422 const gchar* name = (const gchar*)sp_attribute_name(attr);
2423 if(filter && name && o) {
2424 update_settings_sensitivity();
2426 SP_OBJECT_REPR(o)->setAttribute(name, val);
2427 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2429 Glib::ustring undokey = "filtereffects:";
2430 undokey += name;
2431 sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2432 _("Set filter primitive attribute"));
2433 }
2435 _attr_lock = false;
2436 }
2437 }
2439 void FilterEffectsDialog::update_filter_general_settings_view()
2440 {
2441 if(_settings_initialized != true) return;
2443 if(!_locked) {
2444 _attr_lock = true;
2446 SPFilter* filter = _filter_modifier.get_selected_filter();
2448 if(filter) {
2449 _filter_general_settings->show_and_update(0, filter);
2450 _no_filter_selected.hide();
2451 }
2452 else {
2453 std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2454 vect[0]->hide_all();
2455 _no_filter_selected.show();
2456 }
2458 _attr_lock = false;
2459 }
2460 }
2462 void FilterEffectsDialog::update_settings_view()
2463 {
2464 update_settings_sensitivity();
2466 if(_attr_lock)
2467 return;
2469 //First Tab
2471 std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2472 for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2473 _empty_settings.show();
2475 if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2476 _infobox_icon.show();
2477 _infobox_desc.show();
2478 } else {
2479 _infobox_icon.hide();
2480 _infobox_desc.hide();
2481 }
2483 SPFilterPrimitive* prim = _primitive_list.get_selected();
2485 if(prim) {
2486 _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2487 _empty_settings.hide();
2488 }
2490 //Second Tab
2492 std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2493 vect2[0]->hide_all();
2494 _no_filter_selected.show();
2496 SPFilter* filter = _filter_modifier.get_selected_filter();
2498 if(filter) {
2499 _filter_general_settings->show_and_update(0, filter);
2500 _no_filter_selected.hide();
2501 }
2503 }
2505 void FilterEffectsDialog::update_settings_sensitivity()
2506 {
2507 SPFilterPrimitive* prim = _primitive_list.get_selected();
2508 const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2509 _k1->set_sensitive(use_k);
2510 _k2->set_sensitive(use_k);
2511 _k3->set_sensitive(use_k);
2512 _k4->set_sensitive(use_k);
2514 // Component transfer not yet implemented
2515 /*
2516 if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2517 SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2518 const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2519 const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2521 _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2522 _ct_slope->set_sensitive(linear);
2523 _ct_intercept->set_sensitive(linear);
2524 _ct_amplitude->set_sensitive(gamma);
2525 _ct_exponent->set_sensitive(gamma);
2526 _ct_offset->set_sensitive(gamma);
2527 }
2528 */
2529 }
2531 void FilterEffectsDialog::update_color_matrix()
2532 {
2533 _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2534 }
2536 } // namespace Dialog
2537 } // namespace UI
2538 } // namespace Inkscape
2540 /*
2541 Local Variables:
2542 mode:c++
2543 c-file-style:"stroustrup"
2544 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2545 indent-tabs-mode:nil
2546 fill-column:99
2547 End:
2548 */
2549 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :