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)
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 }
149 Glib::ustring get_as_attribute() const
150 {
151 return get_active() ? _true_val : _false_val;
152 }
154 void set_from_attribute(SPObject* o)
155 {
156 const gchar* val = attribute_value(o);
157 if(val) {
158 if(_true_val == val)
159 set_active(true);
160 else if(_false_val == val)
161 set_active(false);
162 } else {
163 set_active(get_default()->as_bool());
164 }
165 }
166 private:
167 const Glib::ustring _true_val, _false_val;
168 };
170 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
171 {
172 public:
173 SpinButtonAttr(double lower, double upper, double step_inc,
174 double climb_rate, int digits, const SPAttributeEnum a, double def, char* tip_text)
175 : Gtk::SpinButton(climb_rate, digits),
176 AttrWidget(a, def)
177 {
178 if (tip_text) _tt.set_tip(*this, tip_text);
179 set_range(lower, upper);
180 set_increments(step_inc, step_inc * 5);
182 signal_value_changed().connect(signal_attr_changed().make_slot());
183 }
185 Glib::ustring get_as_attribute() const
186 {
187 const double val = get_value();
189 if(get_digits() == 0)
190 return Glib::Ascii::dtostr((int)val);
191 else
192 return Glib::Ascii::dtostr(val);
193 }
195 void set_from_attribute(SPObject* o)
196 {
197 const gchar* val = attribute_value(o);
198 if(val){
199 set_value(Glib::Ascii::strtod(val));
200 } else {
201 set_value(get_default()->as_double());
202 }
203 }
204 };
206 template< typename T> class ComboWithTooltip : public Gtk::EventBox
207 {
208 public:
209 ComboWithTooltip<T>(T default_value, const Util::EnumDataConverter<T>& c, const SPAttributeEnum a = SP_ATTR_INVALID, char* tip_text = NULL)
210 {
211 if (tip_text) _tt.set_tip(*this, tip_text);
212 combo = new ComboBoxEnum<T>(default_value, c, a);
213 add(*combo);
214 show_all();
215 }
217 ~ComboWithTooltip()
218 {
219 delete combo;
220 }
222 ComboBoxEnum<T>* get_attrwidget()
223 {
224 return combo;
225 }
226 private:
227 Gtk::Tooltips _tt;
228 ComboBoxEnum<T>* combo;
229 };
231 // Contains an arbitrary number of spin buttons that use seperate attributes
232 class MultiSpinButton : public Gtk::HBox
233 {
234 public:
235 MultiSpinButton(double lower, double upper, double step_inc,
236 double climb_rate, int digits, std::vector<SPAttributeEnum> attrs, std::vector<double> default_values, std::vector<char*> tip_text)
237 {
238 g_assert(attrs.size()==default_values.size());
239 g_assert(attrs.size()==tip_text.size());
240 for(unsigned i = 0; i < attrs.size(); ++i) {
241 _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i], default_values[i], tip_text[i]));
242 pack_start(*_spins.back(), false, false);
243 }
244 }
246 ~MultiSpinButton()
247 {
248 for(unsigned i = 0; i < _spins.size(); ++i)
249 delete _spins[i];
250 }
252 std::vector<SpinButtonAttr*>& get_spinbuttons()
253 {
254 return _spins;
255 }
256 private:
257 std::vector<SpinButtonAttr*> _spins;
258 };
260 // Contains two spinbuttons that describe a NumberOptNumber
261 class DualSpinButton : public Gtk::HBox, public AttrWidget
262 {
263 public:
264 DualSpinButton(double lower, double upper, double step_inc,
265 double climb_rate, int digits, const SPAttributeEnum a, char* tt1, char* tt2)
266 : AttrWidget(a), //TO-DO: receive default num-opt-num as parameter in the constructor
267 _s1(climb_rate, digits), _s2(climb_rate, digits)
268 {
269 if (tt1) _tt.set_tip(_s1, tt1);
270 if (tt2) _tt.set_tip(_s2, tt2);
271 _s1.set_range(lower, upper);
272 _s2.set_range(lower, upper);
273 _s1.set_increments(step_inc, step_inc * 5);
274 _s2.set_increments(step_inc, step_inc * 5);
276 _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
277 _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
279 pack_start(_s1, false, false);
280 pack_start(_s2, false, false);
281 }
283 Gtk::SpinButton& get_spinbutton1()
284 {
285 return _s1;
286 }
288 Gtk::SpinButton& get_spinbutton2()
289 {
290 return _s2;
291 }
293 virtual Glib::ustring get_as_attribute() const
294 {
295 double v1 = _s1.get_value();
296 double v2 = _s2.get_value();
298 if(_s1.get_digits() == 0) {
299 v1 = (int)v1;
300 v2 = (int)v2;
301 }
303 return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
304 }
306 virtual void set_from_attribute(SPObject* o)
307 {
308 const gchar* val = attribute_value(o);
309 NumberOptNumber n;
310 if(val) {
311 n.set(val);
312 } else {
313 n.set("0 0"); //TO-DO: replace this line by the next one that is currently commented out
314 // n.set(default_value(o));
315 }
316 _s1.set_value(n.getNumber());
317 _s2.set_value(n.getOptNumber());
319 }
320 private:
321 Gtk::SpinButton _s1, _s2;
322 };
324 class ColorButton : public Gtk::ColorButton, public AttrWidget
325 {
326 public:
327 ColorButton(const SPAttributeEnum a)
328 : AttrWidget(a)
329 {
330 signal_color_set().connect(signal_attr_changed().make_slot());
332 Gdk::Color col;
333 col.set_rgb(65535, 65535, 65535);
334 set_color(col);
335 }
337 // Returns the color in 'rgb(r,g,b)' form.
338 Glib::ustring get_as_attribute() const
339 {
340 std::ostringstream os;
341 const Gdk::Color c = get_color();
342 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?
343 os << "rgb(" << r << "," << g << "," << b << ")";
344 return os.str();
345 }
348 void set_from_attribute(SPObject* o)
349 {
350 const gchar* val = attribute_value(o);
351 guint32 i = 0;
352 if(val) {
353 i = sp_svg_read_color(val, 0xFFFFFFFF);
354 } else {
355 //TO-DO: read from constructor attribute
356 //i = default_value(o);
357 }
358 const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
359 Gdk::Color col;
360 col.set_rgb(r * 256, g * 256, b * 256);
361 set_color(col);
362 }
363 };
365 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
366 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
367 {
368 public:
369 MatrixAttr(const SPAttributeEnum a)
370 : AttrWidget(a), _locked(false)
371 {
372 _model = Gtk::ListStore::create(_columns);
373 _tree.set_model(_model);
374 _tree.set_headers_visible(false);
375 _tree.show();
376 add(_tree);
377 set_shadow_type(Gtk::SHADOW_IN);
378 }
380 std::vector<double> get_values() const
381 {
382 std::vector<double> vec;
383 for(Gtk::TreeIter iter = _model->children().begin();
384 iter != _model->children().end(); ++iter) {
385 for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
386 vec.push_back((*iter)[_columns.cols[c]]);
387 }
388 return vec;
389 }
391 void set_values(const std::vector<double>& v)
392 {
393 unsigned i = 0;
394 for(Gtk::TreeIter iter = _model->children().begin();
395 iter != _model->children().end(); ++iter) {
396 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
397 if(i >= v.size())
398 return;
399 (*iter)[_columns.cols[c]] = v[i];
400 ++i;
401 }
402 }
403 }
405 Glib::ustring get_as_attribute() const
406 {
407 std::ostringstream os;
409 for(Gtk::TreeIter iter = _model->children().begin();
410 iter != _model->children().end(); ++iter) {
411 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
412 os << (*iter)[_columns.cols[c]] << " ";
413 }
414 }
416 return os.str();
417 }
419 void set_from_attribute(SPObject* o)
420 {
421 if(o) {
422 if(SP_IS_FECONVOLVEMATRIX(o)) {
423 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
424 int cols, rows;
425 cols = (int)conv->order.getNumber();
426 if(cols > 5)
427 cols = 5;
428 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
429 update(o, rows, cols);
430 }
431 else if(SP_IS_FECOLORMATRIX(o))
432 update(o, 4, 5);
433 }
434 }
435 private:
436 class MatrixColumns : public Gtk::TreeModel::ColumnRecord
437 {
438 public:
439 MatrixColumns()
440 {
441 cols.resize(5);
442 for(unsigned i = 0; i < cols.size(); ++i)
443 add(cols[i]);
444 }
445 std::vector<Gtk::TreeModelColumn<double> > cols;
446 };
448 void update(SPObject* o, const int rows, const int cols)
449 {
450 if(_locked)
451 return;
453 _model->clear();
455 _tree.remove_all_columns();
457 std::vector<gdouble>* values = NULL;
458 if(SP_IS_FECOLORMATRIX(o))
459 values = &SP_FECOLORMATRIX(o)->values;
460 else if(SP_IS_FECONVOLVEMATRIX(o))
461 values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
462 else
463 return;
465 if(o) {
466 int ndx = 0;
468 for(int i = 0; i < cols; ++i) {
469 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
470 dynamic_cast<Gtk::CellRendererText*>(
471 _tree.get_column_cell_renderer(i))->signal_edited().connect(
472 sigc::mem_fun(*this, &MatrixAttr::rebind));
473 }
475 for(int r = 0; r < rows; ++r) {
476 Gtk::TreeRow row = *(_model->append());
477 // Default to identity matrix
478 for(int c = 0; c < cols; ++c, ++ndx)
479 row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
480 }
481 }
482 }
484 void rebind(const Glib::ustring&, const Glib::ustring&)
485 {
486 _locked = true;
487 signal_attr_changed()();
488 _locked = false;
489 }
491 bool _locked;
492 Gtk::TreeView _tree;
493 Glib::RefPtr<Gtk::ListStore> _model;
494 MatrixColumns _columns;
495 };
497 // Displays a matrix or a slider for feColorMatrix
498 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
499 {
500 public:
501 ColorMatrixValues()
502 : AttrWidget(SP_ATTR_VALUES),
503 _matrix(SP_ATTR_VALUES),
504 _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
505 _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
506 _label(_("None"), Gtk::ALIGN_LEFT),
507 _use_stored(false),
508 _saturation_store(0),
509 _angle_store(0)
510 {
511 _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
512 _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
513 _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
514 signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
516 _matrix.show();
517 _saturation.show();
518 _angle.show();
519 _label.show();
520 _label.set_sensitive(false);
522 set_shadow_type(Gtk::SHADOW_NONE);
523 }
525 virtual void set_from_attribute(SPObject* o)
526 {
527 if(SP_IS_FECOLORMATRIX(o)) {
528 SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
529 remove();
530 switch(col->type) {
531 case COLORMATRIX_SATURATE:
532 add(_saturation);
533 if(_use_stored)
534 _saturation.set_value(_saturation_store);
535 else
536 _saturation.set_from_attribute(o);
537 break;
538 case COLORMATRIX_HUEROTATE:
539 add(_angle);
540 if(_use_stored)
541 _angle.set_value(_angle_store);
542 else
543 _angle.set_from_attribute(o);
544 break;
545 case COLORMATRIX_LUMINANCETOALPHA:
546 add(_label);
547 break;
548 case COLORMATRIX_MATRIX:
549 default:
550 add(_matrix);
551 if(_use_stored)
552 _matrix.set_values(_matrix_store);
553 else
554 _matrix.set_from_attribute(o);
555 break;
556 }
557 _use_stored = true;
558 }
559 }
561 virtual Glib::ustring get_as_attribute() const
562 {
563 const Widget* w = get_child();
564 if(w == &_label)
565 return "";
566 else
567 return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
568 }
570 void clear_store()
571 {
572 _use_stored = false;
573 }
574 private:
575 void update_store()
576 {
577 const Widget* w = get_child();
578 if(w == &_matrix)
579 _matrix_store = _matrix.get_values();
580 else if(w == &_saturation)
581 _saturation_store = _saturation.get_value();
582 else if(w == &_angle)
583 _angle_store = _angle.get_value();
584 }
586 MatrixAttr _matrix;
587 SpinSlider _saturation;
588 SpinSlider _angle;
589 Gtk::Label _label;
591 // Store separate values for the different color modes
592 bool _use_stored;
593 std::vector<double> _matrix_store;
594 double _saturation_store;
595 double _angle_store;
596 };
598 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
600 //Displays a chooser for feImage input
601 //It may be a filename or the id for an SVG Element
602 //described in xlink:href syntax
603 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
604 {
605 public:
606 FileOrElementChooser(const SPAttributeEnum a)
607 : AttrWidget(a)
608 {
609 pack_start(_entry, false, false);
610 pack_start(_fromFile, false, false);
611 //pack_start(_fromSVGElement, false, false);
613 _fromFile.set_label(_("Image File"));
614 _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
616 _fromSVGElement.set_label(_("Selected SVG Element"));
617 _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
619 _entry.signal_changed().connect(signal_attr_changed().make_slot());
621 show_all();
623 }
625 // Returns the element in xlink:href form.
626 Glib::ustring get_as_attribute() const
627 {
628 return _entry.get_text();
629 }
632 void set_from_attribute(SPObject* o)
633 {
634 const gchar* val = attribute_value(o);
635 if(val) {
636 _entry.set_text(val);
637 } else {
638 _entry.set_text("");
639 }
640 }
642 void set_desktop(SPDesktop* d){
643 _desktop = d;
644 }
646 private:
647 void select_svg_element(){
648 Inkscape::Selection* sel = sp_desktop_selection(_desktop);
649 if (sel->isEmpty()) return;
650 Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
651 if (!node || !node->matchAttributeName("id")) return;
653 std::ostringstream xlikhref;
654 xlikhref << "#" << node->attribute("id");
655 _entry.set_text(xlikhref.str());
656 }
658 void select_file(){
660 //# Get the current directory for finding files
661 Glib::ustring open_path;
662 char *attr = (char *)prefs_get_string_attribute("dialogs.open", "path");
663 if (attr)
664 open_path = attr;
666 //# Test if the open_path directory exists
667 if (!Inkscape::IO::file_test(open_path.c_str(),
668 (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
669 open_path = "";
671 //# If no open path, default to our home directory
672 if (open_path.size() < 1)
673 {
674 open_path = g_get_home_dir();
675 open_path.append(G_DIR_SEPARATOR_S);
676 }
678 //# Create a dialog if we don't already have one
679 if (!selectFeImageFileInstance) {
680 selectFeImageFileInstance =
681 Inkscape::UI::Dialog::FileOpenDialog::create(
682 *_desktop->getToplevel(),
683 open_path,
684 Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not justy svg*/
685 (char const *)_("Select an image to be used as feImage input"));
686 }
688 //# Show the dialog
689 bool const success = selectFeImageFileInstance->show();
690 if (!success)
691 return;
693 //# User selected something. Get name and type
694 Glib::ustring fileName = selectFeImageFileInstance->getFilename();
696 if (fileName.size() > 0) {
698 Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
700 if ( newFileName.size() > 0)
701 fileName = newFileName;
702 else
703 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
705 open_path = fileName;
706 open_path.append(G_DIR_SEPARATOR_S);
707 prefs_set_string_attribute("dialogs.open", "path", open_path.c_str());
709 _entry.set_text(fileName);
710 }
711 return;
712 }
714 Gtk::Entry _entry;
715 Gtk::Button _fromFile;
716 Gtk::Button _fromSVGElement;
717 SPDesktop* _desktop;
718 };
720 class FilterEffectsDialog::Settings
721 {
722 public:
723 typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
725 Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
726 : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
727 {
728 _groups.resize(_max_types);
729 _attrwidgets.resize(_max_types);
730 _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
732 for(int i = 0; i < _max_types; ++i) {
733 _groups[i] = new Gtk::VBox;
734 b.pack_start(*_groups[i], false, false);
735 }
736 _current_type = 0;
737 }
739 ~Settings()
740 {
741 for(int i = 0; i < _max_types; ++i) {
742 delete _groups[i];
743 for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
744 delete _attrwidgets[i][j];
745 }
746 }
748 // Show the active settings group and update all the AttrWidgets with new values
749 void show_and_update(const int t, SPObject* ob)
750 {
751 if(t != _current_type) {
752 type(t);
753 for(unsigned i = 0; i < _groups.size(); ++i)
754 _groups[i]->hide();
755 }
756 if(t >= 0)
757 _groups[t]->show_all();
759 _dialog.set_attrs_locked(true);
760 for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
761 _attrwidgets[_current_type][i]->set_from_attribute(ob);
762 _dialog.set_attrs_locked(false);
763 }
765 int get_current_type() const
766 {
767 return _current_type;
768 }
770 void type(const int t)
771 {
772 _current_type = t;
773 }
775 void add_no_params()
776 {
777 Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
778 add_widget(lbl, "");
779 }
781 void add_notimplemented()
782 {
783 Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
784 add_widget(lbl, "");
785 }
787 // LightSource
788 LightSourceControl* add_lightsource();
790 // CheckBox
791 CheckButtonAttr* add_checkbutton(const SPAttributeEnum attr, const Glib::ustring& label,
792 const Glib::ustring& tv, const Glib::ustring& fv)
793 {
794 CheckButtonAttr* cb = new CheckButtonAttr(label, tv, fv, attr);
795 add_widget(cb, "");
796 add_attr_widget(cb);
797 return cb;
798 }
800 // ColorButton
801 ColorButton* add_color(const SPAttributeEnum attr, const Glib::ustring& label)
802 {
803 ColorButton* col = new ColorButton(attr);
804 add_widget(col, label);
805 add_attr_widget(col);
806 return col;
807 }
809 // Matrix
810 MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label)
811 {
812 MatrixAttr* conv = new MatrixAttr(attr);
813 add_widget(conv, label);
814 add_attr_widget(conv);
815 return conv;
816 }
818 // ColorMatrixValues
819 ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
820 {
821 ColorMatrixValues* cmv = new ColorMatrixValues();
822 add_widget(cmv, label);
823 add_attr_widget(cmv);
824 return cmv;
825 }
827 // SpinSlider
828 SpinSlider* add_spinslider(const SPAttributeEnum attr, const Glib::ustring& label,
829 const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
830 {
831 SpinSlider* spinslider = new SpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text);
832 add_widget(spinslider, label);
833 add_attr_widget(spinslider);
834 return spinslider;
835 }
837 // DualSpinSlider
838 DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
839 const double lo, const double hi, const double step_inc,
840 const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
841 {
842 DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
843 add_widget(dss, label);
844 add_attr_widget(dss);
845 return dss;
846 }
848 // DualSpinButton
849 DualSpinButton* add_dualspinbutton(const SPAttributeEnum attr, const Glib::ustring& label,
850 const double lo, const double hi, const double step_inc,
851 const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
852 {
853 DualSpinButton* dsb = new DualSpinButton(lo, hi, step_inc, climb, digits, attr, tip1, tip2);
854 add_widget(dsb, label);
855 add_attr_widget(dsb);
856 return dsb;
857 }
859 // MultiSpinButton
860 MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
861 const Glib::ustring& label, const double lo, const double hi,
862 const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
863 {
864 std::vector<SPAttributeEnum> attrs;
865 attrs.push_back(attr1);
866 attrs.push_back(attr2);
868 std::vector<double> default_values;
869 default_values.push_back(def1);
870 default_values.push_back(def2);
872 std::vector<char*> tips;
873 tips.push_back(tip1);
874 tips.push_back(tip2);
876 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
877 add_widget(msb, label);
878 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
879 add_attr_widget(msb->get_spinbuttons()[i]);
880 return msb;
881 }
882 MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
883 const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
884 const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
885 {
886 std::vector<SPAttributeEnum> attrs;
887 attrs.push_back(attr1);
888 attrs.push_back(attr2);
889 attrs.push_back(attr3);
891 std::vector<double> default_values;
892 default_values.push_back(def1);
893 default_values.push_back(def2);
894 default_values.push_back(def3);
896 std::vector<char*> tips;
897 tips.push_back(tip1);
898 tips.push_back(tip2);
899 tips.push_back(tip3);
901 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
902 add_widget(msb, label);
903 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
904 add_attr_widget(msb->get_spinbuttons()[i]);
905 return msb;
906 }
908 // FileOrElementChooser
909 FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
910 {
911 FileOrElementChooser* foech = new FileOrElementChooser(attr);
912 foech->set_desktop(_dialog.getDesktop());
913 add_widget(foech, label);
914 add_attr_widget(foech);
915 return foech;
916 }
918 // ComboBoxEnum
919 template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
920 const Glib::ustring& label,
921 const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
922 {
923 ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
924 add_widget(combo, label);
925 add_attr_widget(combo->get_attrwidget());
926 return combo->get_attrwidget();
927 }
928 private:
929 Gtk::Tooltips _tt;
931 void add_attr_widget(AttrWidget* a)
932 {
933 _attrwidgets[_current_type].push_back(a);
934 a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
935 }
937 /* Adds a new settings widget using the specified label. The label will be formatted with a colon
938 and all widgets within the setting group are aligned automatically. */
939 void add_widget(Gtk::Widget* w, const Glib::ustring& label)
940 {
941 Gtk::Label *lbl = 0;
942 Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
943 hb->set_spacing(12);
945 if(label != "") {
946 lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
947 hb->pack_start(*lbl, false, false);
948 _size_group->add_widget(*lbl);
949 lbl->show();
950 }
952 hb->pack_start(*w);
953 _groups[_current_type]->pack_start(*hb);
954 hb->show();
955 w->show();
956 }
958 std::vector<Gtk::VBox*> _groups;
959 Glib::RefPtr<Gtk::SizeGroup> _size_group;
960 FilterEffectsDialog& _dialog;
961 SetAttrSlot _set_attr_slot;
962 std::vector<std::vector< AttrWidget*> > _attrwidgets;
963 int _current_type, _max_types;
964 };
966 // Settings for the three light source objects
967 class FilterEffectsDialog::LightSourceControl : public AttrWidget
968 {
969 public:
970 LightSourceControl(FilterEffectsDialog& d)
971 : AttrWidget(SP_ATTR_INVALID),
972 _dialog(d),
973 _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
974 _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
975 _light_source(LightSourceConverter),
976 _locked(false)
977 {
978 _light_box.pack_start(_light_label, false, false);
979 _light_box.pack_start(_light_source);
980 _light_box.show_all();
981 _light_box.set_spacing(12);
982 _dialog._sizegroup->add_widget(_light_label);
984 _box.add(_light_box);
985 _box.reorder_child(_light_box, 0);
986 _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
988 // FIXME: these range values are complete crap
990 _settings.type(LIGHT_DISTANT);
991 _settings.add_spinslider(SP_ATTR_AZIMUTH, _("Azimuth"), 0, 360, 1, 1, 0, _("Direction angle for the light source on the XY plane, in degrees"));
992 _settings.add_spinslider(SP_ATTR_ELEVATION, _("Elevation"), 0, 360, 1, 1, 0, _("Direction angle for the light source on the YZ plane, in degrees"));
994 _settings.type(LIGHT_POINT);
995 _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"));
997 _settings.type(LIGHT_SPOT);
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"));
999 _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
1000 SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
1001 _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
1002 _settings.add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
1003 _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."));
1004 }
1006 Gtk::VBox& get_box()
1007 {
1008 return _box;
1009 }
1010 protected:
1011 Glib::ustring get_as_attribute() const
1012 {
1013 return "";
1014 }
1015 void set_from_attribute(SPObject* o)
1016 {
1017 if(_locked)
1018 return;
1020 _locked = true;
1022 SPObject* child = o->children;
1024 if(SP_IS_FEDISTANTLIGHT(child))
1025 _light_source.set_active(0);
1026 else if(SP_IS_FEPOINTLIGHT(child))
1027 _light_source.set_active(1);
1028 else if(SP_IS_FESPOTLIGHT(child))
1029 _light_source.set_active(2);
1030 else
1031 _light_source.set_active(-1);
1033 update();
1035 _locked = false;
1036 }
1037 private:
1038 void on_source_changed()
1039 {
1040 if(_locked)
1041 return;
1043 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1044 if(prim) {
1045 _locked = true;
1047 SPObject* child = prim->children;
1048 const int ls = _light_source.get_active_row_number();
1049 // Check if the light source type has changed
1050 if(!(ls == -1 && !child) &&
1051 !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1052 !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1053 !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1054 if(child)
1055 sp_repr_unparent(child->repr);
1057 if(ls != -1) {
1058 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1059 Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1060 prim->repr->appendChild(repr);
1061 }
1063 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1064 update();
1065 }
1067 _locked = false;
1068 }
1069 }
1071 void update()
1072 {
1073 _box.hide_all();
1074 _box.show();
1075 _light_box.show_all();
1077 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1078 if(prim && prim->children)
1079 _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1080 }
1082 FilterEffectsDialog& _dialog;
1083 Gtk::VBox _box;
1084 Settings _settings;
1085 Gtk::HBox _light_box;
1086 Gtk::Label _light_label;
1087 ComboBoxEnum<LightSource> _light_source;
1088 bool _locked;
1089 };
1091 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1092 {
1093 LightSourceControl* ls = new LightSourceControl(_dialog);
1094 add_attr_widget(ls);
1095 add_widget(&ls->get_box(), "");
1096 return ls;
1097 }
1099 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1100 sigc::slot<void> rem)
1101 {
1102 Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1104 menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1105 Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1106 menu->append(*mi);
1107 mi->signal_activate().connect(rem);
1108 mi->show();
1109 menu->accelerate(parent);
1111 return menu;
1112 }
1114 /*** FilterModifier ***/
1115 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1116 : _dialog(d), _add(Gtk::Stock::NEW), _observer(new SignalObserver)
1117 {
1118 Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1119 pack_start(*sw);
1120 pack_start(_add, false, false);
1121 sw->add(_list);
1123 _model = Gtk::ListStore::create(_columns);
1124 _list.set_model(_model);
1125 _cell_toggle.set_active(true);
1126 const int selcol = _list.append_column("", _cell_toggle);
1127 Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1128 if(col)
1129 col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1130 _list.append_column_editable(_("_Filter"), _columns.label);
1131 ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1132 signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1134 sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1135 sw->set_shadow_type(Gtk::SHADOW_IN);
1136 show_all_children();
1137 _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1138 _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1139 _list.signal_button_release_event().connect_notify(
1140 sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1141 _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1142 sigc::mem_fun(*this, &FilterModifier::remove_filter));
1143 _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1144 _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1145 _menu->accelerate(*this);
1147 _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1148 _observer->signal_changed().connect(signal_filter_changed().make_slot());
1149 g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1150 G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1152 g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1153 G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1155 on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1156 update_filters();
1157 }
1159 FilterEffectsDialog::FilterModifier::~FilterModifier()
1160 {
1161 _resource_changed.disconnect();
1162 _doc_replaced.disconnect();
1163 }
1165 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1166 {
1167 me->_doc_replaced.disconnect();
1168 me->_doc_replaced = desktop->connectDocumentReplaced(
1169 sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1171 me->_resource_changed.disconnect();
1172 me->_resource_changed =
1173 sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1174 sigc::mem_fun(me, &FilterModifier::update_filters));
1176 me->_dialog.setDesktop(desktop);
1178 me->update_filters();
1179 }
1182 // When the selection changes, show the active filter(s) in the dialog
1183 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1184 Selection *sel,
1185 FilterModifier* fm)
1186 {
1187 if(fm && sel)
1188 fm->update_selection(sel);
1189 }
1191 // Update each filter's sel property based on the current object selection;
1192 // If the filter is not used by any selected object, sel = 0,
1193 // otherwise sel is set to the total number of filters in use by selected objects
1194 // If only one filter is in use, it is selected
1195 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1196 {
1197 std::set<SPObject*> used;
1199 for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1200 SPObject *obj = SP_OBJECT (i->data);
1201 SPStyle *style = SP_OBJECT_STYLE (obj);
1202 if(!style || !SP_IS_ITEM(obj)) continue;
1204 if(style->filter.set && style->getFilter())
1205 used.insert(style->getFilter());
1206 else
1207 used.insert(0);
1208 }
1210 const int size = used.size();
1212 for(Gtk::TreeIter iter = _model->children().begin();
1213 iter != _model->children().end(); ++iter) {
1214 if(used.find((*iter)[_columns.filter]) != used.end()) {
1215 // If only one filter is in use by the selection, select it
1216 if(size == 1)
1217 _list.get_selection()->select(iter);
1218 (*iter)[_columns.sel] = size;
1219 }
1220 else
1221 (*iter)[_columns.sel] = 0;
1222 }
1223 }
1225 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1226 {
1227 _observer->set(get_selected_filter());
1228 signal_filter_changed()();
1229 }
1231 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1232 {
1233 Gtk::TreeModel::iterator iter = _model->get_iter(path);
1235 if(iter) {
1236 SPFilter* filter = (*iter)[_columns.filter];
1237 filter->setLabel(text.c_str());
1238 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1239 if(iter)
1240 (*iter)[_columns.label] = text;
1241 }
1242 }
1244 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1245 {
1246 Gtk::TreeIter iter = _model->get_iter(path);
1248 if(iter) {
1249 SPDesktop *desktop = _dialog.getDesktop();
1250 SPDocument *doc = sp_desktop_document(desktop);
1251 SPFilter* filter = (*iter)[_columns.filter];
1252 Inkscape::Selection *sel = sp_desktop_selection(desktop);
1254 /* If this filter is the only one used in the selection, unset it */
1255 if((*iter)[_columns.sel] == 1)
1256 filter = 0;
1258 GSList const *items = sel->itemList();
1260 for (GSList const *i = items; i != NULL; i = i->next) {
1261 SPItem * item = SP_ITEM(i->data);
1262 SPStyle *style = SP_OBJECT_STYLE(item);
1263 g_assert(style != NULL);
1265 if(filter)
1266 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1267 else
1268 ::remove_filter(item, false);
1270 SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1271 }
1273 update_selection(sel);
1274 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Apply filter"));
1275 }
1276 }
1278 /* Add all filters in the document to the combobox.
1279 Keeps the same selection if possible, otherwise selects the first element */
1280 void FilterEffectsDialog::FilterModifier::update_filters()
1281 {
1282 SPDesktop* desktop = _dialog.getDesktop();
1283 SPDocument* document = sp_desktop_document(desktop);
1284 const GSList* filters = sp_document_get_resource_list(document, "filter");
1286 _model->clear();
1288 for(const GSList *l = filters; l; l = l->next) {
1289 Gtk::TreeModel::Row row = *_model->append();
1290 SPFilter* f = (SPFilter*)l->data;
1291 row[_columns.filter] = f;
1292 const gchar* lbl = f->label();
1293 const gchar* id = SP_OBJECT_ID(f);
1294 row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1295 }
1297 update_selection(desktop->selection);
1298 _dialog.update_filter_general_settings_view();
1299 }
1301 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1302 {
1303 if(_list.get_selection()) {
1304 Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1306 if(i)
1307 return (*i)[_columns.filter];
1308 }
1310 return 0;
1311 }
1313 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1314 {
1315 if(filter) {
1316 for(Gtk::TreeModel::iterator i = _model->children().begin();
1317 i != _model->children().end(); ++i) {
1318 if((*i)[_columns.filter] == filter) {
1319 _list.get_selection()->select(i);
1320 break;
1321 }
1322 }
1323 }
1324 }
1326 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1327 {
1328 if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1329 const bool sensitive = get_selected_filter() != NULL;
1330 _menu->items()[0].set_sensitive(sensitive);
1331 _menu->items()[1].set_sensitive(sensitive);
1332 _menu->popup(event->button, event->time);
1333 }
1334 }
1336 void FilterEffectsDialog::FilterModifier::add_filter()
1337 {
1338 SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1339 SPFilter* filter = new_filter(doc);
1341 const int count = _model->children().size();
1342 std::ostringstream os;
1343 os << "filter" << count;
1344 filter->setLabel(os.str().c_str());
1346 update_filters();
1348 select_filter(filter);
1350 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1351 }
1353 void FilterEffectsDialog::FilterModifier::remove_filter()
1354 {
1355 SPFilter *filter = get_selected_filter();
1357 if(filter) {
1358 SPDocument* doc = filter->document;
1359 sp_repr_unparent(filter->repr);
1361 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1363 update_filters();
1364 }
1365 }
1367 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1368 {
1369 SPFilter* filter = get_selected_filter();
1371 if(filter) {
1372 Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1373 repr = repr->duplicate(repr->document());
1374 parent->appendChild(repr);
1376 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1378 update_filters();
1379 }
1380 }
1382 void FilterEffectsDialog::FilterModifier::rename_filter()
1383 {
1384 _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1385 }
1387 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1388 : Glib::ObjectBase(typeid(CellRendererConnection)),
1389 _primitive(*this, "primitive", 0)
1390 {}
1392 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1393 {
1394 return _primitive.get_proxy();
1395 }
1397 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1398 {
1399 _text_width = w;
1400 }
1402 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1403 {
1404 return _text_width;
1405 }
1407 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1408 Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1409 int* x_offset, int* y_offset, int* width, int* height) const
1410 {
1411 PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1413 if(x_offset)
1414 (*x_offset) = 0;
1415 if(y_offset)
1416 (*y_offset) = 0;
1417 if(width)
1418 (*width) = size * primlist.primitive_count() + _text_width * 7;
1419 if(height) {
1420 // Scale the height depending on the number of inputs, unless it's
1421 // the first primitive, in which case there are no connections
1422 SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1423 (*height) = size * input_count(prim);
1424 }
1425 }
1427 /*** PrimitiveList ***/
1428 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1429 : _dialog(d),
1430 _in_drag(0),
1431 _observer(new SignalObserver)
1432 {
1433 d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1435 add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1436 signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1438 _model = Gtk::ListStore::create(_columns);
1440 set_reorderable(true);
1442 set_model(_model);
1443 append_column(_("_Effect"), _columns.type);
1445 _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1446 get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1447 signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1449 _connection_cell.set_text_width(init_text());
1451 int cols_count = append_column(_("Connections"), _connection_cell);
1452 Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1453 if(col)
1454 col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1455 }
1457 // Sets up a vertical Pango context/layout, and returns the largest
1458 // width needed to render the FilterPrimitiveInput labels.
1459 int FilterEffectsDialog::PrimitiveList::init_text()
1460 {
1461 // Set up a vertical context+layout
1462 Glib::RefPtr<Pango::Context> context = create_pango_context();
1463 const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1464 context->set_matrix(matrix);
1465 _vertical_layout = Pango::Layout::create(context);
1467 int maxfont = 0;
1468 for(int i = 0; i < FPInputConverter.end; ++i) {
1469 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1470 int fontw, fonth;
1471 _vertical_layout->get_pixel_size(fontw, fonth);
1472 if(fonth > maxfont)
1473 maxfont = fonth;
1474 }
1476 return maxfont;
1477 }
1479 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1480 {
1481 return _signal_primitive_changed;
1482 }
1484 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1485 {
1486 _observer->set(get_selected());
1487 signal_primitive_changed()();
1488 _dialog._color_matrix_values->clear_store();
1489 }
1491 /* Add all filter primitives in the current to the list.
1492 Keeps the same selection if possible, otherwise selects the first element */
1493 void FilterEffectsDialog::PrimitiveList::update()
1494 {
1495 SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1496 const SPFilterPrimitive* active_prim = get_selected();
1497 bool active_found = false;
1499 _model->clear();
1501 if(f) {
1502 _dialog._primitive_box.set_sensitive(true);
1503 _dialog.update_filter_general_settings_view();
1504 for(SPObject *prim_obj = f->children;
1505 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1506 prim_obj = prim_obj->next) {
1507 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1508 if(prim) {
1509 Gtk::TreeModel::Row row = *_model->append();
1510 row[_columns.primitive] = prim;
1511 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1512 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1513 row[_columns.id] = SP_OBJECT_ID(prim);
1515 if(prim == active_prim) {
1516 get_selection()->select(row);
1517 active_found = true;
1518 }
1519 }
1520 }
1522 if(!active_found && _model->children().begin())
1523 get_selection()->select(_model->children().begin());
1525 columns_autosize();
1526 }
1527 else {
1528 _dialog._primitive_box.set_sensitive(false);
1529 }
1530 }
1532 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1533 {
1534 _primitive_menu = menu;
1535 }
1537 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1538 {
1539 if(_dialog._filter_modifier.get_selected_filter()) {
1540 Gtk::TreeModel::iterator i = get_selection()->get_selected();
1541 if(i)
1542 return (*i)[_columns.primitive];
1543 }
1545 return 0;
1546 }
1548 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1549 {
1550 for(Gtk::TreeIter i = _model->children().begin();
1551 i != _model->children().end(); ++i) {
1552 if((*i)[_columns.primitive] == prim)
1553 get_selection()->select(i);
1554 }
1555 }
1557 void FilterEffectsDialog::PrimitiveList::remove_selected()
1558 {
1559 SPFilterPrimitive* prim = get_selected();
1561 if(prim) {
1562 _observer->set(0);
1564 sp_repr_unparent(prim->repr);
1566 sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1567 _("Remove filter primitive"));
1569 update();
1570 }
1571 }
1573 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1574 {
1575 Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1576 Glib::RefPtr<Gdk::Window> win = get_bin_window();
1577 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1579 SPFilterPrimitive* prim = get_selected();
1580 int row_count = get_model()->children().size();
1582 int fheight = CellRendererConnection::size;
1583 Gdk::Rectangle rct, vis;
1584 Gtk::TreeIter row = get_model()->children().begin();
1585 int text_start_x = 0;
1586 if(row) {
1587 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1588 get_visible_rect(vis);
1589 int vis_x, vis_y;
1590 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1592 text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter.end + 1) + 1;
1593 for(int i = 0; i < FPInputConverter.end; ++i) {
1594 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1595 const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1596 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());
1597 get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1598 get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1599 }
1600 }
1602 int row_index = 0;
1603 for(; row != get_model()->children().end(); ++row, ++row_index) {
1604 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1605 const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1607 // Check mouse state
1608 int mx, my;
1609 Gdk::ModifierType mask;
1610 get_bin_window()->get_pointer(mx, my, mask);
1612 // Outline the bottom of the connection area
1613 const int outline_x = x + fheight * (row_count - row_index);
1614 get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1616 // Side outline
1617 get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1619 std::vector<Gdk::Point> con_poly;
1620 int con_drag_y = 0;
1621 bool inside;
1622 const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1623 const int inputs = input_count(row_prim);
1625 if(SP_IS_FEMERGE(row_prim)) {
1626 for(int i = 0; i < inputs; ++i) {
1627 inside = do_connection_node(row, i, con_poly, mx, my);
1628 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1629 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1630 inside, con_poly);
1632 if(_in_drag == (i + 1))
1633 con_drag_y = con_poly[2].get_y();
1635 if(_in_drag != (i + 1) || row_prim != prim)
1636 draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1637 }
1638 }
1639 else {
1640 // Draw "in" shape
1641 inside = do_connection_node(row, 0, con_poly, mx, my);
1642 con_drag_y = con_poly[2].get_y();
1643 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1644 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1645 inside, con_poly);
1647 // Draw "in" connection
1648 if(_in_drag != 1 || row_prim != prim)
1649 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1651 if(inputs == 2) {
1652 // Draw "in2" shape
1653 inside = do_connection_node(row, 1, con_poly, mx, my);
1654 if(_in_drag == 2)
1655 con_drag_y = con_poly[2].get_y();
1656 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1657 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1658 inside, con_poly);
1659 // Draw "in2" connection
1660 if(_in_drag != 2 || row_prim != prim)
1661 draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1662 }
1663 }
1665 // Draw drag connection
1666 if(row_prim == prim && _in_drag) {
1667 get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1668 mx, con_drag_y);
1669 get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1670 }
1671 }
1673 return true;
1674 }
1676 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1677 const int text_start_x, const int x1, const int y1,
1678 const int row_count)
1679 {
1680 int src_id = 0;
1681 Gtk::TreeIter res = find_result(input, attr, src_id);
1682 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1683 Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1684 Glib::RefPtr<Gdk::GC> gc;
1686 const bool is_first = input == get_model()->children().begin();
1687 const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1688 const bool use_default = !res && !is_merge;
1690 if(res == input || (use_default && is_first)) {
1691 // Draw straight connection to a standard input
1692 // Draw a lighter line for an implicit connection to a standard input
1693 const int tw = _connection_cell.get_text_width();
1694 gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1695 gc = (use_default && is_first) ? lightgc : darkgc;
1696 get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1697 get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1698 }
1699 else {
1700 // Draw an 'L'-shaped connection to another filter primitive
1701 // If no connection is specified, draw a light connection to the previous primitive
1702 gc = use_default ? lightgc : darkgc;
1704 if(use_default) {
1705 res = input;
1706 --res;
1707 }
1709 if(res) {
1710 Gdk::Rectangle rct;
1712 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1713 const int fheight = CellRendererConnection::size;
1715 get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1716 const int row_index = find_index(res);
1717 const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1718 const int y2 = rct.get_y() + rct.get_height();
1720 // Draw a bevelled 'L'-shaped connection
1721 get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1722 get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1723 get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1724 }
1725 }
1726 }
1728 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1729 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1730 std::vector<Gdk::Point>& points,
1731 const int ix, const int iy)
1732 {
1733 Gdk::Rectangle rct;
1734 const int icnt = input_count((*row)[_columns.primitive]);
1736 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1737 const int fheight = CellRendererConnection::size;
1739 get_cell_area(_model->get_path(row), *get_column(1), rct);
1740 const float h = rct.get_height() / icnt;
1742 const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1743 const int con_w = (int)(fheight * 0.35f);
1744 const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1745 points.clear();
1746 points.push_back(Gdk::Point(x, con_y));
1747 points.push_back(Gdk::Point(x, con_y + con_w * 2));
1748 points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1750 return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1751 }
1753 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1754 const int attr, int& src_id)
1755 {
1756 SPFilterPrimitive* prim = (*start)[_columns.primitive];
1757 Gtk::TreeIter target = _model->children().end();
1758 int image = 0;
1760 if(SP_IS_FEMERGE(prim)) {
1761 int c = 0;
1762 bool found = false;
1763 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1764 if(c == attr && SP_IS_FEMERGENODE(o)) {
1765 image = SP_FEMERGENODE(o)->input;
1766 found = true;
1767 }
1768 }
1769 if(!found)
1770 return target;
1771 }
1772 else {
1773 if(attr == SP_ATTR_IN)
1774 image = prim->image_in;
1775 else if(attr == SP_ATTR_IN2) {
1776 if(SP_IS_FEBLEND(prim))
1777 image = SP_FEBLEND(prim)->in2;
1778 else if(SP_IS_FECOMPOSITE(prim))
1779 image = SP_FECOMPOSITE(prim)->in2;
1780 else if(SP_IS_FEDISPLACEMENTMAP(prim))
1781 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1782 else
1783 return target;
1784 }
1785 else
1786 return target;
1787 }
1789 if(image >= 0) {
1790 for(Gtk::TreeIter i = _model->children().begin();
1791 i != start; ++i) {
1792 if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1793 target = i;
1794 }
1795 return target;
1796 }
1797 else if(image < -1) {
1798 src_id = -(image + 2);
1799 return start;
1800 }
1802 return target;
1803 }
1805 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1806 {
1807 int i = 0;
1808 for(Gtk::TreeIter iter = _model->children().begin();
1809 iter != target; ++iter, ++i);
1810 return i;
1811 }
1813 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1814 {
1815 Gtk::TreePath path;
1816 Gtk::TreeViewColumn* col;
1817 const int x = (int)e->x, y = (int)e->y;
1818 int cx, cy;
1820 _drag_prim = 0;
1822 if(get_path_at_pos(x, y, path, col, cx, cy)) {
1823 Gtk::TreeIter iter = _model->get_iter(path);
1824 std::vector<Gdk::Point> points;
1826 _drag_prim = (*iter)[_columns.primitive];
1827 const int icnt = input_count(_drag_prim);
1829 for(int i = 0; i < icnt; ++i) {
1830 if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1831 _in_drag = i + 1;
1832 break;
1833 }
1834 }
1836 queue_draw();
1837 }
1839 if(_in_drag) {
1840 _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1841 _autoscroll = 0;
1842 get_selection()->select(path);
1843 return true;
1844 }
1845 else
1846 return Gtk::TreeView::on_button_press_event(e);
1847 }
1849 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1850 {
1851 const int speed = 10;
1852 const int limit = 15;
1854 Gdk::Rectangle vis;
1855 get_visible_rect(vis);
1856 int vis_x, vis_y;
1857 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1858 const int top = vis_y + vis.get_height();
1860 // When autoscrolling during a connection drag, set the speed based on
1861 // where the mouse is in relation to the edges.
1862 if(e->y < vis_y)
1863 _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1864 else if(e->y < vis_y + limit)
1865 _autoscroll = -speed;
1866 else if(e->y > top)
1867 _autoscroll = (int)(speed + (e->y - top) / 5);
1868 else if(e->y > top - limit)
1869 _autoscroll = speed;
1870 else
1871 _autoscroll = 0;
1873 queue_draw();
1875 return Gtk::TreeView::on_motion_notify_event(e);
1876 }
1878 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1879 {
1880 SPFilterPrimitive *prim = get_selected(), *target;
1882 _scroll_connection.disconnect();
1884 if(_in_drag && prim) {
1885 Gtk::TreePath path;
1886 Gtk::TreeViewColumn* col;
1887 int cx, cy;
1889 if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1890 const gchar *in_val = 0;
1891 Glib::ustring result;
1892 Gtk::TreeIter target_iter = _model->get_iter(path);
1893 target = (*target_iter)[_columns.primitive];
1894 col = get_column(1);
1896 Gdk::Rectangle rct;
1897 get_cell_area(path, *col, rct);
1898 const int twidth = _connection_cell.get_text_width();
1899 const int sources_x = rct.get_width() - twidth * FPInputConverter.end;
1900 if(cx > sources_x) {
1901 int src = (cx - sources_x) / twidth;
1902 if(src < 0)
1903 src = 0;
1904 else if(src >= FPInputConverter.end)
1905 src = FPInputConverter.end - 1;
1906 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1907 in_val = result.c_str();
1908 }
1909 else {
1910 // Ensure that the target comes before the selected primitive
1911 for(Gtk::TreeIter iter = _model->children().begin();
1912 iter != get_selection()->get_selected(); ++iter) {
1913 if(iter == target_iter) {
1914 Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1915 // Make sure the target has a result
1916 const gchar *gres = repr->attribute("result");
1917 if(!gres) {
1918 result = "result" + Glib::Ascii::dtostr(SP_FILTER(prim->parent)->_image_number_next);
1919 repr->setAttribute("result", result.c_str());
1920 in_val = result.c_str();
1921 }
1922 else
1923 in_val = gres;
1924 break;
1925 }
1926 }
1927 }
1929 if(SP_IS_FEMERGE(prim)) {
1930 int c = 1;
1931 bool handled = false;
1932 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1933 if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1934 // If input is null, delete it
1935 if(!in_val) {
1936 sp_repr_unparent(o->repr);
1937 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1938 _("Remove merge node"));
1939 (*get_selection()->get_selected())[_columns.primitive] = prim;
1940 }
1941 else
1942 _dialog.set_attr(o, SP_ATTR_IN, in_val);
1943 handled = true;
1944 }
1945 }
1946 // Add new input?
1947 if(!handled && c == _in_drag && in_val) {
1948 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1949 Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1950 repr->setAttribute("inkscape:collect", "always");
1951 prim->repr->appendChild(repr);
1952 SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1953 Inkscape::GC::release(repr);
1954 _dialog.set_attr(node, SP_ATTR_IN, in_val);
1955 (*get_selection()->get_selected())[_columns.primitive] = prim;
1956 }
1957 }
1958 else {
1959 if(_in_drag == 1)
1960 _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1961 else if(_in_drag == 2)
1962 _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1963 }
1964 }
1966 _in_drag = 0;
1967 queue_draw();
1969 _dialog.update_settings_view();
1970 }
1972 if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1973 const bool sensitive = get_selected() != NULL;
1974 _primitive_menu->items()[0].set_sensitive(sensitive);
1975 _primitive_menu->items()[1].set_sensitive(sensitive);
1976 _primitive_menu->popup(e->button, e->time);
1978 return true;
1979 }
1980 else
1981 return Gtk::TreeView::on_button_release_event(e);
1982 }
1984 // Checks all of prim's inputs, removes any that use result
1985 void check_single_connection(SPFilterPrimitive* prim, const int result)
1986 {
1987 if(prim && result >= 0) {
1989 if(prim->image_in == result)
1990 SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1992 if(SP_IS_FEBLEND(prim)) {
1993 if(SP_FEBLEND(prim)->in2 == result)
1994 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1995 }
1996 else if(SP_IS_FECOMPOSITE(prim)) {
1997 if(SP_FECOMPOSITE(prim)->in2 == result)
1998 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1999 }
2000 else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
2001 if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
2002 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2003 }
2004 }
2005 }
2007 // Remove any connections going to/from prim_iter that forward-reference other primitives
2008 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
2009 {
2010 SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
2011 bool before = true;
2013 for(Gtk::TreeIter iter = _model->children().begin();
2014 iter != _model->children().end(); ++iter) {
2015 if(iter == prim_iter)
2016 before = false;
2017 else {
2018 SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
2019 if(before)
2020 check_single_connection(cur_prim, prim->image_out);
2021 else
2022 check_single_connection(prim, cur_prim->image_out);
2023 }
2024 }
2025 }
2027 // Reorder the filter primitives to match the list order
2028 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2029 {
2030 SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2031 int ndx = 0;
2033 for(Gtk::TreeModel::iterator iter = _model->children().begin();
2034 iter != _model->children().end(); ++iter, ++ndx) {
2035 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2036 if(prim && prim == _drag_prim) {
2037 SP_OBJECT_REPR(prim)->setPosition(ndx);
2038 break;
2039 }
2040 }
2042 for(Gtk::TreeModel::iterator iter = _model->children().begin();
2043 iter != _model->children().end(); ++iter, ++ndx) {
2044 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2045 if(prim && prim == _drag_prim) {
2046 sanitize_connections(iter);
2047 get_selection()->select(iter);
2048 break;
2049 }
2050 }
2052 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2054 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2055 }
2057 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2058 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2059 {
2060 if(_autoscroll) {
2061 Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2062 double v;
2064 v = a.get_value() + _autoscroll;
2065 if(v < 0)
2066 v = 0;
2067 if(v > a.get_upper() - a.get_page_size())
2068 v = a.get_upper() - a.get_page_size();
2070 a.set_value(v);
2072 queue_draw();
2073 }
2075 return true;
2076 }
2078 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2079 {
2080 return _model->children().size();
2081 }
2083 /*** FilterEffectsDialog ***/
2085 FilterEffectsDialog::FilterEffectsDialog()
2086 : UI::Widget::Panel("", "dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2087 _filter_modifier(*this),
2088 _primitive_list(*this),
2089 _add_primitive_type(FPConverter),
2090 _add_primitive(_("Add Effect:")),
2091 _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2092 _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2093 _settings_initialized(false),
2094 _locked(false),
2095 _attr_lock(false)
2096 {
2097 _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2098 NR_FILTER_ENDPRIMITIVETYPE);
2099 _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2100 1);
2101 _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2102 _sizegroup->set_ignore_hidden();
2104 _add_primitive_type.remove_row(NR_FILTER_TILE);
2105 _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2107 // Initialize widget hierarchy
2108 Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2109 Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2110 Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox);
2111 Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2113 _getContents()->add(*hpaned);
2114 hpaned->pack1(_filter_modifier);
2115 hpaned->pack2(_primitive_box);
2116 _primitive_box.pack_start(*sw_prims);
2117 _primitive_box.pack_start(*infobox,false, false);
2118 _primitive_box.pack_start(*hb_prims, false, false);
2119 sw_prims->add(_primitive_list);
2120 infobox->pack_start(_infobox_icon, false, false);
2121 infobox->pack_start(_infobox_desc, false, false);
2122 _infobox_desc.set_line_wrap(true);
2124 hb_prims->pack_end(_add_primitive_type, false, false);
2125 hb_prims->pack_end(_add_primitive, false, false);
2126 _getContents()->pack_start(_settings_tabs, false, false);
2127 _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2128 _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2130 _primitive_list.signal_primitive_changed().connect(
2131 sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2132 _filter_modifier.signal_filter_changed().connect(
2133 sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2135 _add_primitive_type.signal_changed().connect(
2136 sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2138 sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2139 sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2140 // al_settings->set_padding(0, 0, 12, 0);
2141 // fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2142 // ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2143 _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2144 _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2145 sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2147 show_all_children();
2148 init_settings_widgets();
2149 _primitive_list.update();
2150 update_primitive_infobox();
2151 }
2153 FilterEffectsDialog::~FilterEffectsDialog()
2154 {
2155 delete _settings;
2156 delete _filter_general_settings;
2157 }
2159 void FilterEffectsDialog::set_attrs_locked(const bool l)
2160 {
2161 _locked = l;
2162 }
2164 void FilterEffectsDialog::show_all_vfunc()
2165 {
2166 UI::Widget::Panel::show_all_vfunc();
2168 update_settings_view();
2169 }
2171 void FilterEffectsDialog::init_settings_widgets()
2172 {
2173 // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2174 // most of the current values are complete guesses!
2176 _empty_settings.set_sensitive(false);
2177 _settings_tab1.pack_start(_empty_settings);
2179 _no_filter_selected.set_sensitive(false);
2180 _settings_tab2.pack_start(_no_filter_selected);
2181 _settings_initialized = true;
2183 _filter_general_settings->type(0);
2184 _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"));
2185 _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"));
2187 _settings->type(NR_FILTER_BLEND);
2188 _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode"), BlendModeConverter);
2190 _settings->type(NR_FILTER_COLORMATRIX);
2191 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."));
2192 _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s)"));
2193 colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2195 _settings->type(NR_FILTER_COMPONENTTRANSFER);
2196 _settings->add_notimplemented();
2197 /*_settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2198 _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2199 _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2200 _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2201 _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2202 _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2204 _settings->type(NR_FILTER_COMPOSITE);
2205 _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
2206 _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."));
2207 _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."));
2208 _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."));
2209 _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."));
2211 _settings->type(NR_FILTER_CONVOLVEMATRIX);
2212 _convolve_order = _settings->add_dualspinbutton(SP_ATTR_ORDER, _("Size"), 1, 5, 1, 1, 0, _("width of the convolve matrix"), _("height of the convolve matrix"));
2213 _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."));
2214 _convolve_matrix = _settings->add_matrix(SP_ATTR_KERNELMATRIX, _("Kernel"));
2215 _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2216 _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."));
2217 _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."));
2218 _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."));
2219 _settings->add_checkbutton(SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false");
2221 _settings->type(NR_FILTER_DIFFUSELIGHTING);
2222 _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Diffuse Color"));
2223 _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"));
2224 _settings->add_spinslider(SP_ATTR_DIFFUSECONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2225 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2226 _settings->add_lightsource();
2228 _settings->type(NR_FILTER_DISPLACEMENTMAP);
2229 _settings->add_spinslider(SP_ATTR_SCALE, _("Scale"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2230 _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2231 _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2233 _settings->type(NR_FILTER_FLOOD);
2234 _settings->add_color(SP_PROP_FLOOD_COLOR, _("Flood Color"));
2235 _settings->add_spinslider(SP_PROP_FLOOD_OPACITY, _("Opacity"), 0, 1, 0.1, 0.01, 2);
2237 _settings->type(NR_FILTER_GAUSSIANBLUR);
2238 _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2240 _settings->type(NR_FILTER_MERGE);
2241 _settings->add_no_params();
2243 _settings->type(NR_FILTER_MORPHOLOGY);
2244 _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2245 _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius"), 0, 100, 1, 0.01, 1);
2247 _settings->type(NR_FILTER_IMAGE);
2248 _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image"));
2250 _settings->type(NR_FILTER_OFFSET);
2251 _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"));
2252 _settings->add_spinslider(SP_ATTR_DY, _("Delta Y"), -100, 100, 1, 0.01, 1, _("This is how far the input image gets shifted downwards"));
2254 _settings->type(NR_FILTER_SPECULARLIGHTING);
2255 _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Specular Color"));
2256 _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"));
2257 _settings->add_spinslider(SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2258 _settings->add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2259 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2260 _settings->add_lightsource();
2262 _settings->type(NR_FILTER_TILE);
2263 _settings->add_notimplemented();
2265 _settings->type(NR_FILTER_TURBULENCE);
2266 _settings->add_checkbutton(SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2267 _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2268 _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency"), 0, 1, 0.001, 0.01, 3);
2269 _settings->add_spinslider(SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2270 _settings->add_spinslider(SP_ATTR_SEED, _("Seed"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2271 }
2273 void FilterEffectsDialog::add_primitive()
2274 {
2275 SPFilter* filter = _filter_modifier.get_selected_filter();
2277 if(filter) {
2278 SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2280 _primitive_list.select(prim);
2282 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2283 }
2284 }
2286 void FilterEffectsDialog::update_primitive_infobox()
2287 {
2288 if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2289 _infobox_icon.show();
2290 _infobox_desc.show();
2291 } else {
2292 _infobox_icon.hide();
2293 _infobox_desc.hide();
2294 }
2295 switch(_add_primitive_type.get_active_data()->id){
2296 case(NR::NR_FILTER_BLEND):
2297 _infobox_icon.set(g_strdup_printf("%s/feBlend-icon.png", INKSCAPE_PIXMAPDIR));
2298 _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2299 break;
2300 case(NR::NR_FILTER_COLORMATRIX):
2301 _infobox_icon.set(g_strdup_printf("%s/feColorMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2302 _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."));
2303 break;
2304 case(NR::NR_FILTER_COMPONENTTRANSFER):
2305 _infobox_icon.set(g_strdup_printf("%s/feComponentTransfer-icon.png", INKSCAPE_PIXMAPDIR));
2306 _infobox_desc.set_markup(_("The <b>feComponentTransfer</b> filter primitive manipulates the input's color components (red, green, blue, and alpha) according to particular transfer functions, allowing operations like brightness and contrast adjustment, color balance, and thresholding."));
2307 break;
2308 case(NR::NR_FILTER_COMPOSITE):
2309 _infobox_icon.set(g_strdup_printf("%s/feComposite-icon.png", INKSCAPE_PIXMAPDIR));
2310 _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."));
2311 break;
2312 case(NR::NR_FILTER_CONVOLVEMATRIX):
2313 _infobox_icon.set(g_strdup_printf("%s/feConvolveMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2314 _infobox_desc.set_markup(_("The <b>feConvolveMatrix</b> lets you specify a Convolution to be applied on the image. Common effects created using convolution matrices are blur, sharpening, embossing and edge detection. Note that while gaussian blur can be created using this filter primitive, the special gaussian blur primitive is faster and resolution-independent."));
2315 break;
2316 case(NR::NR_FILTER_DIFFUSELIGHTING):
2317 _infobox_icon.set(g_strdup_printf("%s/feDiffuseLighting-icon.png", INKSCAPE_PIXMAPDIR));
2318 _infobox_desc.set_markup(_("The <b>feDiffuseLighting</b> and feSpecularLighting filter primitives create \"embossed\" shadings. The input's alpha channel is used to provide depth information: higher opacity areas are raised toward the viewer and lower opacity areas recede away from the viewer."));
2319 break;
2320 case(NR::NR_FILTER_DISPLACEMENTMAP):
2321 _infobox_icon.set(g_strdup_printf("%s/feDisplacementMap-icon.png", INKSCAPE_PIXMAPDIR));
2322 _infobox_desc.set_markup(_("The <b>feDisplacementMap</b> filter primitive displaces the pixels in the first input using the second input as a displacement map, that shows from how far the pixel should come from. Classical examples are whirl and pinch effects."));
2323 break;
2324 case(NR::NR_FILTER_FLOOD):
2325 _infobox_icon.set(g_strdup_printf("%s/feFlood-icon.png", INKSCAPE_PIXMAPDIR));
2326 _infobox_desc.set_markup(_("The <b>feFlood</b> filter primitive fills the region with a given color and opacity. It is usually used as an input to other filters to apply color to a graphic."));
2327 break;
2328 case(NR::NR_FILTER_GAUSSIANBLUR):
2329 _infobox_icon.set(g_strdup_printf("%s/feGaussianBlur-icon.png", INKSCAPE_PIXMAPDIR));
2330 _infobox_desc.set_markup(_("The <b>feGaussianBlur</b> filter primitive uniformly blurs its input. It is commonly used together with feOffset to create a drop shadow effect."));
2331 break;
2332 case(NR::NR_FILTER_IMAGE):
2333 _infobox_icon.set(g_strdup_printf("%s/feImage-icon.png", INKSCAPE_PIXMAPDIR));
2334 _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2335 break;
2336 case(NR::NR_FILTER_MERGE):
2337 _infobox_icon.set(g_strdup_printf("%s/feMerge-icon.png", INKSCAPE_PIXMAPDIR));
2338 _infobox_desc.set_markup(_("The <b>feMerge</b> filter primitive composites several temporary images inside the filter primitive to a single image. It uses normal alpha compositing for this. This is equivalent to using several feBlend primitives in 'normal' mode or several feComposite primitives in 'over' mode."));
2339 break;
2340 case(NR::NR_FILTER_MORPHOLOGY):
2341 _infobox_icon.set(g_strdup_printf("%s/feMorphology-icon.png", INKSCAPE_PIXMAPDIR));
2342 _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."));
2343 break;
2344 case(NR::NR_FILTER_OFFSET):
2345 _infobox_icon.set(g_strdup_printf("%s/feOffset-icon.png", INKSCAPE_PIXMAPDIR));
2346 _infobox_desc.set_markup(_("The <b>feOffset</b> filter primitive offsets the image by an user-defined amount. For example, this is useful for drop shadows, where the shadow is in a slightly different position than the actual object."));
2347 break;
2348 case(NR::NR_FILTER_SPECULARLIGHTING):
2349 _infobox_icon.set(g_strdup_printf("%s/feSpecularLighting-icon.png", INKSCAPE_PIXMAPDIR));
2350 _infobox_desc.set_markup(_("The feDiffuseLighting and <b>feSpecularLighting</b> filter primitives create \"embossed\" shadings. The input's alpha channel is used to provide depth information: higher opacity areas are raised toward the viewer and lower opacity areas recede away from the viewer."));
2351 break;
2352 case(NR::NR_FILTER_TILE):
2353 _infobox_icon.set(g_strdup_printf("%s/feTile-icon.png", INKSCAPE_PIXMAPDIR));
2354 _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2355 break;
2356 case(NR::NR_FILTER_TURBULENCE):
2357 _infobox_icon.set(g_strdup_printf("%s/feTurbulence-icon.png", INKSCAPE_PIXMAPDIR));
2358 _infobox_desc.set_markup(_("The <b>feTurbulence</b> filter primitive renders Perlin noise. This kind of noise is useful in simulating several nature phenomena like clouds, fire and smoke and in generating complex textures like marble or granite."));
2359 break;
2360 default:
2361 g_assert(false);
2362 break;
2363 }
2364 }
2366 void FilterEffectsDialog::duplicate_primitive()
2367 {
2368 SPFilter* filter = _filter_modifier.get_selected_filter();
2369 SPFilterPrimitive* origprim = _primitive_list.get_selected();
2371 if(filter && origprim) {
2372 Inkscape::XML::Node *repr;
2373 repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2374 SP_OBJECT_REPR(filter)->appendChild(repr);
2376 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2378 _primitive_list.update();
2379 }
2380 }
2382 void FilterEffectsDialog::convolve_order_changed()
2383 {
2384 _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2385 _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2386 _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2387 }
2389 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2390 {
2391 set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2392 }
2394 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2395 {
2396 if(!_locked) {
2397 _attr_lock = true;
2398 SPFilter *filter = _filter_modifier.get_selected_filter();
2399 const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2400 if (filter && name && SP_OBJECT_REPR(filter)){
2401 SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2402 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2403 }
2404 _attr_lock = false;
2405 }
2406 }
2408 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2409 {
2410 set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2411 }
2413 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2414 {
2415 if(!_locked) {
2416 _attr_lock = true;
2418 SPFilter *filter = _filter_modifier.get_selected_filter();
2419 const gchar* name = (const gchar*)sp_attribute_name(attr);
2420 if(filter && name && o) {
2421 update_settings_sensitivity();
2423 SP_OBJECT_REPR(o)->setAttribute(name, val);
2424 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2426 Glib::ustring undokey = "filtereffects:";
2427 undokey += name;
2428 sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2429 _("Set filter primitive attribute"));
2430 }
2432 _attr_lock = false;
2433 }
2434 }
2436 void FilterEffectsDialog::update_filter_general_settings_view()
2437 {
2438 if(_settings_initialized != true) return;
2440 if(!_locked) {
2441 _attr_lock = true;
2443 SPFilter* filter = _filter_modifier.get_selected_filter();
2445 if(filter) {
2446 _filter_general_settings->show_and_update(0, filter);
2447 _no_filter_selected.hide();
2448 }
2449 else {
2450 std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2451 vect[0]->hide_all();
2452 _no_filter_selected.show();
2453 }
2455 _attr_lock = false;
2456 }
2457 }
2459 void FilterEffectsDialog::update_settings_view()
2460 {
2461 update_settings_sensitivity();
2463 if(_attr_lock)
2464 return;
2466 //First Tab
2468 std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2469 for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2470 _empty_settings.show();
2472 if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2473 _infobox_icon.show();
2474 _infobox_desc.show();
2475 } else {
2476 _infobox_icon.hide();
2477 _infobox_desc.hide();
2478 }
2480 SPFilterPrimitive* prim = _primitive_list.get_selected();
2482 if(prim) {
2483 _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2484 _empty_settings.hide();
2485 }
2487 //Second Tab
2489 std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2490 vect2[0]->hide_all();
2491 _no_filter_selected.show();
2493 SPFilter* filter = _filter_modifier.get_selected_filter();
2495 if(filter) {
2496 _filter_general_settings->show_and_update(0, filter);
2497 _no_filter_selected.hide();
2498 }
2500 }
2502 void FilterEffectsDialog::update_settings_sensitivity()
2503 {
2504 SPFilterPrimitive* prim = _primitive_list.get_selected();
2505 const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2506 _k1->set_sensitive(use_k);
2507 _k2->set_sensitive(use_k);
2508 _k3->set_sensitive(use_k);
2509 _k4->set_sensitive(use_k);
2511 // Component transfer not yet implemented
2512 /*
2513 if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2514 SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2515 const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2516 const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2518 _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2519 _ct_slope->set_sensitive(linear);
2520 _ct_intercept->set_sensitive(linear);
2521 _ct_amplitude->set_sensitive(gamma);
2522 _ct_exponent->set_sensitive(gamma);
2523 _ct_offset->set_sensitive(gamma);
2524 }
2525 */
2526 }
2528 void FilterEffectsDialog::update_color_matrix()
2529 {
2530 _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2531 }
2533 } // namespace Dialog
2534 } // namespace UI
2535 } // namespace Inkscape
2537 /*
2538 Local Variables:
2539 mode:c++
2540 c-file-style:"stroustrup"
2541 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2542 indent-tabs-mode:nil
2543 fill-column:99
2544 End:
2545 */
2546 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :