1 /**
2 * \brief Filter Effects dialog
3 *
4 * Authors:
5 * Nicholas Bishop <nicholasbishop@gmail.org>
6 * Rodrigo Kumpera <kumpera@gmail.com>
7 * Felipe C. da S. Sanches <felipe.sanches@gmail.com>
8 *
9 * Copyright (C) 2007 Authors
10 *
11 * Released under GNU GPL. Read the file 'COPYING' for more information.
12 */
14 #ifdef HAVE_CONFIG_H
15 # include <config.h>
16 #endif
18 #include <gtk/gtktreeview.h>
19 #include <gtkmm/cellrenderertext.h>
20 #include <gtkmm/colorbutton.h>
21 #include <gtkmm/messagedialog.h>
22 #include <gtkmm/paned.h>
23 #include <gtkmm/scale.h>
24 #include <gtkmm/scrolledwindow.h>
25 #include <gtkmm/spinbutton.h>
26 #include <gtkmm/stock.h>
27 #include <gtkmm/tooltips.h>
28 #include <glibmm/i18n.h>
30 #include "application/application.h"
31 #include "application/editor.h"
32 #include "desktop.h"
33 #include "desktop-handles.h"
34 #include "dialog-manager.h"
35 #include "dir-util.h"
36 #include "document.h"
37 #include "filter-chemistry.h"
38 #include "filter-effects-dialog.h"
39 #include "filter-enums.h"
40 #include "inkscape.h"
41 #include "path-prefix.h"
42 #include "prefs-utils.h"
43 #include "selection.h"
44 #include "sp-feblend.h"
45 #include "sp-fecolormatrix.h"
46 #include "sp-fecomponenttransfer.h"
47 #include "sp-fecomposite.h"
48 #include "sp-feconvolvematrix.h"
49 #include "sp-fedisplacementmap.h"
50 #include "sp-fedistantlight.h"
51 #include "sp-femerge.h"
52 #include "sp-femergenode.h"
53 #include "sp-feoffset.h"
54 #include "sp-fepointlight.h"
55 #include "sp-fespotlight.h"
56 #include "sp-filter-primitive.h"
57 #include "sp-gaussian-blur.h"
59 #include "style.h"
60 #include "svg/svg-color.h"
61 #include "ui/dialog/filedialog.h"
62 #include "verbs.h"
63 #include "xml/node.h"
64 #include "xml/node-observer.h"
65 #include "xml/repr.h"
66 #include <sstream>
68 #include "io/sys.h"
69 #include <iostream>
71 using namespace NR;
73 namespace Inkscape {
74 namespace UI {
75 namespace Dialog {
77 // Returns the number of inputs available for the filter primitive type
78 int input_count(const SPFilterPrimitive* prim)
79 {
80 if(!prim)
81 return 0;
82 else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
83 return 2;
84 else if(SP_IS_FEMERGE(prim)) {
85 // Return the number of feMergeNode connections plus an extra
86 int count = 1;
87 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count);
88 return count;
89 }
90 else
91 return 1;
92 }
94 // Very simple observer that just emits a signal if anything happens to a node
95 class FilterEffectsDialog::SignalObserver : public XML::NodeObserver
96 {
97 public:
98 SignalObserver()
99 : _oldsel(0)
100 {}
102 // Add this observer to the SPObject and remove it from any previous object
103 void set(SPObject* o)
104 {
105 if(_oldsel && _oldsel->repr)
106 _oldsel->repr->removeObserver(*this);
107 if(o && o->repr)
108 o->repr->addObserver(*this);
109 _oldsel = o;
110 }
112 void notifyChildAdded(XML::Node&, XML::Node&, XML::Node*)
113 { signal_changed()(); }
115 void notifyChildRemoved(XML::Node&, XML::Node&, XML::Node*)
116 { signal_changed()(); }
118 void notifyChildOrderChanged(XML::Node&, XML::Node&, XML::Node*, XML::Node*)
119 { signal_changed()(); }
121 void notifyContentChanged(XML::Node&, Util::ptr_shared<char>, Util::ptr_shared<char>)
122 {}
124 void notifyAttributeChanged(XML::Node&, GQuark, Util::ptr_shared<char>, Util::ptr_shared<char>)
125 { signal_changed()(); }
127 sigc::signal<void>& signal_changed()
128 {
129 return _signal_changed;
130 }
131 private:
132 sigc::signal<void> _signal_changed;
133 SPObject* _oldsel;
134 };
136 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
137 {
138 public:
139 CheckButtonAttr(bool def, const Glib::ustring& label,
140 const Glib::ustring& tv, const Glib::ustring& fv,
141 const SPAttributeEnum a, char* tip_text)
142 : Gtk::CheckButton(label),
143 AttrWidget(a, def),
144 _true_val(tv), _false_val(fv)
145 {
146 signal_toggled().connect(signal_attr_changed().make_slot());
147 if (tip_text) _tt.set_tip(*this, tip_text);
148 }
150 Glib::ustring get_as_attribute() const
151 {
152 return get_active() ? _true_val : _false_val;
153 }
155 void set_from_attribute(SPObject* o)
156 {
157 const gchar* val = attribute_value(o);
158 if(val) {
159 if(_true_val == val)
160 set_active(true);
161 else if(_false_val == val)
162 set_active(false);
163 } else {
164 set_active(get_default()->as_bool());
165 }
166 }
167 private:
168 const Glib::ustring _true_val, _false_val;
169 };
171 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
172 {
173 public:
174 SpinButtonAttr(double lower, double upper, double step_inc,
175 double climb_rate, int digits, const SPAttributeEnum a, double def, char* tip_text)
176 : Gtk::SpinButton(climb_rate, digits),
177 AttrWidget(a, def)
178 {
179 if (tip_text) _tt.set_tip(*this, tip_text);
180 set_range(lower, upper);
181 set_increments(step_inc, step_inc * 5);
183 signal_value_changed().connect(signal_attr_changed().make_slot());
184 }
186 Glib::ustring get_as_attribute() const
187 {
188 const double val = get_value();
190 if(get_digits() == 0)
191 return Glib::Ascii::dtostr((int)val);
192 else
193 return Glib::Ascii::dtostr(val);
194 }
196 void set_from_attribute(SPObject* o)
197 {
198 const gchar* val = attribute_value(o);
199 if(val){
200 set_value(Glib::Ascii::strtod(val));
201 } else {
202 set_value(get_default()->as_double());
203 }
204 }
205 };
207 template< typename T> class ComboWithTooltip : public Gtk::EventBox
208 {
209 public:
210 ComboWithTooltip<T>(T default_value, const Util::EnumDataConverter<T>& c, const SPAttributeEnum a = SP_ATTR_INVALID, char* tip_text = NULL)
211 {
212 if (tip_text) _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(char* def, double lower, double upper, double step_inc,
266 double climb_rate, int digits, const SPAttributeEnum a, char* tt1, char* tt2)
267 : AttrWidget(a, def), //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(get_default()->as_charptr());
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(unsigned int def, const SPAttributeEnum a, char* tip_text)
328 : AttrWidget(a, def)
329 {
330 signal_color_set().connect(signal_attr_changed().make_slot());
331 if (tip_text) _tt.set_tip(*this, tip_text);
333 Gdk::Color col;
334 col.set_rgb(65535, 65535, 65535);
335 set_color(col);
336 }
338 // Returns the color in 'rgb(r,g,b)' form.
339 Glib::ustring get_as_attribute() const
340 {
341 std::ostringstream os;
342 const Gdk::Color c = get_color();
343 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?
344 os << "rgb(" << r << "," << g << "," << b << ")";
345 return os.str();
346 }
349 void set_from_attribute(SPObject* o)
350 {
351 const gchar* val = attribute_value(o);
352 guint32 i = 0;
353 if(val) {
354 i = sp_svg_read_color(val, 0xFFFFFFFF);
355 } else {
356 i = (guint32) get_default()->as_uint();
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, char* tip_text = NULL)
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 if (tip_text) _tt.set_tip(_tree, tip_text);
379 }
381 std::vector<double> get_values() const
382 {
383 std::vector<double> vec;
384 for(Gtk::TreeIter iter = _model->children().begin();
385 iter != _model->children().end(); ++iter) {
386 for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
387 vec.push_back((*iter)[_columns.cols[c]]);
388 }
389 return vec;
390 }
392 void set_values(const std::vector<double>& v)
393 {
394 unsigned i = 0;
395 for(Gtk::TreeIter iter = _model->children().begin();
396 iter != _model->children().end(); ++iter) {
397 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
398 if(i >= v.size())
399 return;
400 (*iter)[_columns.cols[c]] = v[i];
401 ++i;
402 }
403 }
404 }
406 Glib::ustring get_as_attribute() const
407 {
408 std::ostringstream os;
410 for(Gtk::TreeIter iter = _model->children().begin();
411 iter != _model->children().end(); ++iter) {
412 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
413 os << (*iter)[_columns.cols[c]] << " ";
414 }
415 }
417 return os.str();
418 }
420 void set_from_attribute(SPObject* o)
421 {
422 if(o) {
423 if(SP_IS_FECONVOLVEMATRIX(o)) {
424 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
425 int cols, rows;
426 cols = (int)conv->order.getNumber();
427 if(cols > 5)
428 cols = 5;
429 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
430 update(o, rows, cols);
431 }
432 else if(SP_IS_FECOLORMATRIX(o))
433 update(o, 4, 5);
434 }
435 }
436 private:
437 class MatrixColumns : public Gtk::TreeModel::ColumnRecord
438 {
439 public:
440 MatrixColumns()
441 {
442 cols.resize(5);
443 for(unsigned i = 0; i < cols.size(); ++i)
444 add(cols[i]);
445 }
446 std::vector<Gtk::TreeModelColumn<double> > cols;
447 };
449 void update(SPObject* o, const int rows, const int cols)
450 {
451 if(_locked)
452 return;
454 _model->clear();
456 _tree.remove_all_columns();
458 std::vector<gdouble>* values = NULL;
459 if(SP_IS_FECOLORMATRIX(o))
460 values = &SP_FECOLORMATRIX(o)->values;
461 else if(SP_IS_FECONVOLVEMATRIX(o))
462 values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
463 else
464 return;
466 if(o) {
467 int ndx = 0;
469 for(int i = 0; i < cols; ++i) {
470 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
471 dynamic_cast<Gtk::CellRendererText*>(
472 _tree.get_column_cell_renderer(i))->signal_edited().connect(
473 sigc::mem_fun(*this, &MatrixAttr::rebind));
474 }
476 for(int r = 0; r < rows; ++r) {
477 Gtk::TreeRow row = *(_model->append());
478 // Default to identity matrix
479 for(int c = 0; c < cols; ++c, ++ndx)
480 row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
481 }
482 }
483 }
485 void rebind(const Glib::ustring&, const Glib::ustring&)
486 {
487 _locked = true;
488 signal_attr_changed()();
489 _locked = false;
490 }
492 bool _locked;
493 Gtk::TreeView _tree;
494 Glib::RefPtr<Gtk::ListStore> _model;
495 MatrixColumns _columns;
496 };
498 // Displays a matrix or a slider for feColorMatrix
499 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
500 {
501 public:
502 ColorMatrixValues()
503 : AttrWidget(SP_ATTR_VALUES),
504 _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.")),
505 _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
506 _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
507 _label(_("None"), Gtk::ALIGN_LEFT),
508 _use_stored(false),
509 _saturation_store(0),
510 _angle_store(0)
511 {
512 _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
513 _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
514 _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
515 signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
517 _matrix.show();
518 _saturation.show();
519 _angle.show();
520 _label.show();
521 _label.set_sensitive(false);
523 set_shadow_type(Gtk::SHADOW_NONE);
524 }
526 virtual void set_from_attribute(SPObject* o)
527 {
528 if(SP_IS_FECOLORMATRIX(o)) {
529 SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
530 remove();
531 switch(col->type) {
532 case COLORMATRIX_SATURATE:
533 add(_saturation);
534 if(_use_stored)
535 _saturation.set_value(_saturation_store);
536 else
537 _saturation.set_from_attribute(o);
538 break;
539 case COLORMATRIX_HUEROTATE:
540 add(_angle);
541 if(_use_stored)
542 _angle.set_value(_angle_store);
543 else
544 _angle.set_from_attribute(o);
545 break;
546 case COLORMATRIX_LUMINANCETOALPHA:
547 add(_label);
548 break;
549 case COLORMATRIX_MATRIX:
550 default:
551 add(_matrix);
552 if(_use_stored)
553 _matrix.set_values(_matrix_store);
554 else
555 _matrix.set_from_attribute(o);
556 break;
557 }
558 _use_stored = true;
559 }
560 }
562 virtual Glib::ustring get_as_attribute() const
563 {
564 const Widget* w = get_child();
565 if(w == &_label)
566 return "";
567 else
568 return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
569 }
571 void clear_store()
572 {
573 _use_stored = false;
574 }
575 private:
576 void update_store()
577 {
578 const Widget* w = get_child();
579 if(w == &_matrix)
580 _matrix_store = _matrix.get_values();
581 else if(w == &_saturation)
582 _saturation_store = _saturation.get_value();
583 else if(w == &_angle)
584 _angle_store = _angle.get_value();
585 }
587 MatrixAttr _matrix;
588 SpinSlider _saturation;
589 SpinSlider _angle;
590 Gtk::Label _label;
592 // Store separate values for the different color modes
593 bool _use_stored;
594 std::vector<double> _matrix_store;
595 double _saturation_store;
596 double _angle_store;
597 };
599 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
601 //Displays a chooser for feImage input
602 //It may be a filename or the id for an SVG Element
603 //described in xlink:href syntax
604 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
605 {
606 public:
607 FileOrElementChooser(const SPAttributeEnum a)
608 : AttrWidget(a)
609 {
610 pack_start(_entry, false, false);
611 pack_start(_fromFile, false, false);
612 pack_start(_fromSVGElement, false, false);
614 _fromFile.set_label(_("Image File"));
615 _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
617 _fromSVGElement.set_label(_("Selected SVG Element"));
618 _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
620 _entry.signal_changed().connect(signal_attr_changed().make_slot());
622 show_all();
624 }
626 // Returns the element in xlink:href form.
627 Glib::ustring get_as_attribute() const
628 {
629 return _entry.get_text();
630 }
633 void set_from_attribute(SPObject* o)
634 {
635 const gchar* val = attribute_value(o);
636 if(val) {
637 _entry.set_text(val);
638 } else {
639 _entry.set_text("");
640 }
641 }
643 void set_desktop(SPDesktop* d){
644 _desktop = d;
645 }
647 private:
648 void select_svg_element(){
649 Inkscape::Selection* sel = sp_desktop_selection(_desktop);
650 if (sel->isEmpty()) return;
651 Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
652 if (!node || !node->matchAttributeName("id")) return;
654 std::ostringstream xlikhref;
655 xlikhref << "#" << node->attribute("id");
656 _entry.set_text(xlikhref.str());
657 }
659 void select_file(){
661 //# Get the current directory for finding files
662 Glib::ustring open_path;
663 char *attr = (char *)prefs_get_string_attribute("dialogs.open", "path");
664 if (attr)
665 open_path = attr;
667 //# Test if the open_path directory exists
668 if (!Inkscape::IO::file_test(open_path.c_str(),
669 (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
670 open_path = "";
672 //# If no open path, default to our home directory
673 if (open_path.size() < 1)
674 {
675 open_path = g_get_home_dir();
676 open_path.append(G_DIR_SEPARATOR_S);
677 }
679 //# Create a dialog if we don't already have one
680 if (!selectFeImageFileInstance) {
681 selectFeImageFileInstance =
682 Inkscape::UI::Dialog::FileOpenDialog::create(
683 *_desktop->getToplevel(),
684 open_path,
685 Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not justy svg*/
686 (char const *)_("Select an image to be used as feImage input"));
687 }
689 //# Show the dialog
690 bool const success = selectFeImageFileInstance->show();
691 if (!success)
692 return;
694 //# User selected something. Get name and type
695 Glib::ustring fileName = selectFeImageFileInstance->getFilename();
697 if (fileName.size() > 0) {
699 Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
701 if ( newFileName.size() > 0)
702 fileName = newFileName;
703 else
704 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
706 open_path = fileName;
707 open_path.append(G_DIR_SEPARATOR_S);
708 prefs_set_string_attribute("dialogs.open", "path", open_path.c_str());
710 _entry.set_text(fileName);
711 }
712 return;
713 }
715 Gtk::Entry _entry;
716 Gtk::Button _fromFile;
717 Gtk::Button _fromSVGElement;
718 SPDesktop* _desktop;
719 };
721 class FilterEffectsDialog::Settings
722 {
723 public:
724 typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
726 Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
727 : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
728 {
729 _groups.resize(_max_types);
730 _attrwidgets.resize(_max_types);
731 _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
733 for(int i = 0; i < _max_types; ++i) {
734 _groups[i] = new Gtk::VBox;
735 b.pack_start(*_groups[i], false, false);
736 }
737 _current_type = 0;
738 }
740 ~Settings()
741 {
742 for(int i = 0; i < _max_types; ++i) {
743 delete _groups[i];
744 for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
745 delete _attrwidgets[i][j];
746 }
747 }
749 // Show the active settings group and update all the AttrWidgets with new values
750 void show_and_update(const int t, SPObject* ob)
751 {
752 if(t != _current_type) {
753 type(t);
754 for(unsigned i = 0; i < _groups.size(); ++i)
755 _groups[i]->hide();
756 }
757 if(t >= 0)
758 _groups[t]->show_all();
760 _dialog.set_attrs_locked(true);
761 for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
762 _attrwidgets[_current_type][i]->set_from_attribute(ob);
763 _dialog.set_attrs_locked(false);
764 }
766 int get_current_type() const
767 {
768 return _current_type;
769 }
771 void type(const int t)
772 {
773 _current_type = t;
774 }
776 void add_no_params()
777 {
778 Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
779 add_widget(lbl, "");
780 }
782 void add_notimplemented()
783 {
784 Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
785 add_widget(lbl, "");
786 }
788 // LightSource
789 LightSourceControl* add_lightsource();
791 // CheckBox
792 CheckButtonAttr* add_checkbutton(bool def, const SPAttributeEnum attr, const Glib::ustring& label,
793 const Glib::ustring& tv, const Glib::ustring& fv, char* tip_text = NULL)
794 {
795 CheckButtonAttr* cb = new CheckButtonAttr(def, label, tv, fv, attr, tip_text);
796 add_widget(cb, "");
797 add_attr_widget(cb);
798 return cb;
799 }
801 // ColorButton
802 ColorButton* add_color(unsigned int def, const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text = NULL)
803 {
804 ColorButton* col = new ColorButton(def, attr, tip_text);
805 add_widget(col, label);
806 add_attr_widget(col);
807 return col;
808 }
810 // Matrix
811 MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text)
812 {
813 MatrixAttr* conv = new MatrixAttr(attr, tip_text);
814 add_widget(conv, label);
815 add_attr_widget(conv);
816 return conv;
817 }
819 // ColorMatrixValues
820 ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
821 {
822 ColorMatrixValues* cmv = new ColorMatrixValues();
823 add_widget(cmv, label);
824 add_attr_widget(cmv);
825 return cmv;
826 }
828 // SpinSlider
829 SpinSlider* add_spinslider(double def, const SPAttributeEnum attr, const Glib::ustring& label,
830 const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
831 {
832 SpinSlider* spinslider = new SpinSlider(def, lo, hi, step_inc, climb, digits, attr, tip_text);
833 add_widget(spinslider, label);
834 add_attr_widget(spinslider);
835 return spinslider;
836 }
838 // DualSpinSlider
839 DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
840 const double lo, const double hi, const double step_inc,
841 const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
842 {
843 DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
844 add_widget(dss, label);
845 add_attr_widget(dss);
846 return dss;
847 }
849 // DualSpinButton
850 DualSpinButton* add_dualspinbutton(char* defalt_value, const SPAttributeEnum attr, const Glib::ustring& label,
851 const double lo, const double hi, const double step_inc,
852 const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
853 {
854 DualSpinButton* dsb = new DualSpinButton(defalt_value, lo, hi, step_inc, climb, digits, attr, tip1, tip2);
855 add_widget(dsb, label);
856 add_attr_widget(dsb);
857 return dsb;
858 }
860 // MultiSpinButton
861 MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
862 const Glib::ustring& label, const double lo, const double hi,
863 const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
864 {
865 std::vector<SPAttributeEnum> attrs;
866 attrs.push_back(attr1);
867 attrs.push_back(attr2);
869 std::vector<double> default_values;
870 default_values.push_back(def1);
871 default_values.push_back(def2);
873 std::vector<char*> tips;
874 tips.push_back(tip1);
875 tips.push_back(tip2);
877 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
878 add_widget(msb, label);
879 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
880 add_attr_widget(msb->get_spinbuttons()[i]);
881 return msb;
882 }
883 MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
884 const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
885 const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
886 {
887 std::vector<SPAttributeEnum> attrs;
888 attrs.push_back(attr1);
889 attrs.push_back(attr2);
890 attrs.push_back(attr3);
892 std::vector<double> default_values;
893 default_values.push_back(def1);
894 default_values.push_back(def2);
895 default_values.push_back(def3);
897 std::vector<char*> tips;
898 tips.push_back(tip1);
899 tips.push_back(tip2);
900 tips.push_back(tip3);
902 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
903 add_widget(msb, label);
904 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
905 add_attr_widget(msb->get_spinbuttons()[i]);
906 return msb;
907 }
909 // FileOrElementChooser
910 FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
911 {
912 FileOrElementChooser* foech = new FileOrElementChooser(attr);
913 foech->set_desktop(_dialog.getDesktop());
914 add_widget(foech, label);
915 add_attr_widget(foech);
916 return foech;
917 }
919 // ComboBoxEnum
920 template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
921 const Glib::ustring& label,
922 const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
923 {
924 ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
925 add_widget(combo, label);
926 add_attr_widget(combo->get_attrwidget());
927 return combo->get_attrwidget();
928 }
929 private:
930 Gtk::Tooltips _tt;
932 void add_attr_widget(AttrWidget* a)
933 {
934 _attrwidgets[_current_type].push_back(a);
935 a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
936 }
938 /* Adds a new settings widget using the specified label. The label will be formatted with a colon
939 and all widgets within the setting group are aligned automatically. */
940 void add_widget(Gtk::Widget* w, const Glib::ustring& label)
941 {
942 Gtk::Label *lbl = 0;
943 Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
944 hb->set_spacing(12);
946 if(label != "") {
947 lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
948 hb->pack_start(*lbl, false, false);
949 _size_group->add_widget(*lbl);
950 lbl->show();
951 }
953 hb->pack_start(*w);
954 _groups[_current_type]->pack_start(*hb);
955 hb->show();
956 w->show();
957 }
959 std::vector<Gtk::VBox*> _groups;
960 Glib::RefPtr<Gtk::SizeGroup> _size_group;
961 FilterEffectsDialog& _dialog;
962 SetAttrSlot _set_attr_slot;
963 std::vector<std::vector< AttrWidget*> > _attrwidgets;
964 int _current_type, _max_types;
965 };
967 // Settings for the three light source objects
968 class FilterEffectsDialog::LightSourceControl : public AttrWidget
969 {
970 public:
971 LightSourceControl(FilterEffectsDialog& d)
972 : AttrWidget(SP_ATTR_INVALID),
973 _dialog(d),
974 _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
975 _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
976 _light_source(LightSourceConverter),
977 _locked(false)
978 {
979 _light_box.pack_start(_light_label, false, false);
980 _light_box.pack_start(_light_source);
981 _light_box.show_all();
982 _light_box.set_spacing(12);
983 _dialog._sizegroup->add_widget(_light_label);
985 _box.add(_light_box);
986 _box.reorder_child(_light_box, 0);
987 _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
989 // FIXME: these range values are complete crap
991 _settings.type(LIGHT_DISTANT);
992 _settings.add_spinslider(0, SP_ATTR_AZIMUTH, _("Azimuth"), 0, 360, 1, 1, 0, _("Direction angle for the light source on the XY plane, in degrees"));
993 _settings.add_spinslider(0, SP_ATTR_ELEVATION, _("Elevation"), 0, 360, 1, 1, 0, _("Direction angle for the light source on the YZ plane, in degrees"));
995 _settings.type(LIGHT_POINT);
996 _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"));
998 _settings.type(LIGHT_SPOT);
999 _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.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
1001 SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
1002 _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
1003 _settings.add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
1004 //TODO: here I have used 100 degrees as default value. But spec says that if not specified, no limiting cone is applied. So, there should be a way for the user to set a "no limiting cone" option.
1005 _settings.add_spinslider(100, SP_ATTR_LIMITINGCONEANGLE, _("Cone Angle"), 1, 100, 1, 1, 0, _("This is the angle between the spot light axis (i.e. the axis between the light source and the point to which it is pointing at) and the spot light cone. No light is projected outside this cone."));
1006 }
1008 Gtk::VBox& get_box()
1009 {
1010 return _box;
1011 }
1012 protected:
1013 Glib::ustring get_as_attribute() const
1014 {
1015 return "";
1016 }
1017 void set_from_attribute(SPObject* o)
1018 {
1019 if(_locked)
1020 return;
1022 _locked = true;
1024 SPObject* child = o->children;
1026 if(SP_IS_FEDISTANTLIGHT(child))
1027 _light_source.set_active(0);
1028 else if(SP_IS_FEPOINTLIGHT(child))
1029 _light_source.set_active(1);
1030 else if(SP_IS_FESPOTLIGHT(child))
1031 _light_source.set_active(2);
1032 else
1033 _light_source.set_active(-1);
1035 update();
1037 _locked = false;
1038 }
1039 private:
1040 void on_source_changed()
1041 {
1042 if(_locked)
1043 return;
1045 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1046 if(prim) {
1047 _locked = true;
1049 SPObject* child = prim->children;
1050 const int ls = _light_source.get_active_row_number();
1051 // Check if the light source type has changed
1052 if(!(ls == -1 && !child) &&
1053 !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1054 !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1055 !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1056 if(child)
1057 sp_repr_unparent(child->repr);
1059 if(ls != -1) {
1060 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1061 Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1062 prim->repr->appendChild(repr);
1063 }
1065 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1066 update();
1067 }
1069 _locked = false;
1070 }
1071 }
1073 void update()
1074 {
1075 _box.hide_all();
1076 _box.show();
1077 _light_box.show_all();
1079 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1080 if(prim && prim->children)
1081 _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1082 }
1084 FilterEffectsDialog& _dialog;
1085 Gtk::VBox _box;
1086 Settings _settings;
1087 Gtk::HBox _light_box;
1088 Gtk::Label _light_label;
1089 ComboBoxEnum<LightSource> _light_source;
1090 bool _locked;
1091 };
1093 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1094 {
1095 LightSourceControl* ls = new LightSourceControl(_dialog);
1096 add_attr_widget(ls);
1097 add_widget(&ls->get_box(), "");
1098 return ls;
1099 }
1101 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1102 sigc::slot<void> rem)
1103 {
1104 Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1106 menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1107 Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1108 menu->append(*mi);
1109 mi->signal_activate().connect(rem);
1110 mi->show();
1111 menu->accelerate(parent);
1113 return menu;
1114 }
1116 /*** FilterModifier ***/
1117 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1118 : _dialog(d), _add(Gtk::Stock::NEW), _observer(new SignalObserver)
1119 {
1120 Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1121 pack_start(*sw);
1122 pack_start(_add, false, false);
1123 sw->add(_list);
1125 _model = Gtk::ListStore::create(_columns);
1126 _list.set_model(_model);
1127 _cell_toggle.set_active(true);
1128 const int selcol = _list.append_column("", _cell_toggle);
1129 Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1130 if(col)
1131 col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1132 _list.append_column_editable(_("_Filter"), _columns.label);
1133 ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1134 signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1136 sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1137 sw->set_shadow_type(Gtk::SHADOW_IN);
1138 show_all_children();
1139 _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1140 _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1141 _list.signal_button_release_event().connect_notify(
1142 sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1143 _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1144 sigc::mem_fun(*this, &FilterModifier::remove_filter));
1145 _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1146 _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1147 _menu->accelerate(*this);
1149 _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1150 _observer->signal_changed().connect(signal_filter_changed().make_slot());
1151 g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1152 G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1154 g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1155 G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1157 on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1158 update_filters();
1159 }
1161 FilterEffectsDialog::FilterModifier::~FilterModifier()
1162 {
1163 _resource_changed.disconnect();
1164 _doc_replaced.disconnect();
1165 }
1167 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1168 {
1169 me->_doc_replaced.disconnect();
1170 me->_doc_replaced = desktop->connectDocumentReplaced(
1171 sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1173 me->_resource_changed.disconnect();
1174 me->_resource_changed =
1175 sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1176 sigc::mem_fun(me, &FilterModifier::update_filters));
1178 me->_dialog.setDesktop(desktop);
1180 me->update_filters();
1181 }
1184 // When the selection changes, show the active filter(s) in the dialog
1185 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1186 Selection *sel,
1187 FilterModifier* fm)
1188 {
1189 if(fm && sel)
1190 fm->update_selection(sel);
1191 }
1193 // Update each filter's sel property based on the current object selection;
1194 // If the filter is not used by any selected object, sel = 0,
1195 // otherwise sel is set to the total number of filters in use by selected objects
1196 // If only one filter is in use, it is selected
1197 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1198 {
1199 std::set<SPObject*> used;
1201 for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1202 SPObject *obj = SP_OBJECT (i->data);
1203 SPStyle *style = SP_OBJECT_STYLE (obj);
1204 if(!style || !SP_IS_ITEM(obj)) continue;
1206 if(style->filter.set && style->getFilter())
1207 used.insert(style->getFilter());
1208 else
1209 used.insert(0);
1210 }
1212 const int size = used.size();
1214 for(Gtk::TreeIter iter = _model->children().begin();
1215 iter != _model->children().end(); ++iter) {
1216 if(used.find((*iter)[_columns.filter]) != used.end()) {
1217 // If only one filter is in use by the selection, select it
1218 if(size == 1)
1219 _list.get_selection()->select(iter);
1220 (*iter)[_columns.sel] = size;
1221 }
1222 else
1223 (*iter)[_columns.sel] = 0;
1224 }
1225 }
1227 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1228 {
1229 _observer->set(get_selected_filter());
1230 signal_filter_changed()();
1231 }
1233 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1234 {
1235 Gtk::TreeModel::iterator iter = _model->get_iter(path);
1237 if(iter) {
1238 SPFilter* filter = (*iter)[_columns.filter];
1239 filter->setLabel(text.c_str());
1240 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1241 if(iter)
1242 (*iter)[_columns.label] = text;
1243 }
1244 }
1246 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1247 {
1248 Gtk::TreeIter iter = _model->get_iter(path);
1250 if(iter) {
1251 SPDesktop *desktop = _dialog.getDesktop();
1252 SPDocument *doc = sp_desktop_document(desktop);
1253 SPFilter* filter = (*iter)[_columns.filter];
1254 Inkscape::Selection *sel = sp_desktop_selection(desktop);
1256 /* If this filter is the only one used in the selection, unset it */
1257 if((*iter)[_columns.sel] == 1)
1258 filter = 0;
1260 GSList const *items = sel->itemList();
1262 for (GSList const *i = items; i != NULL; i = i->next) {
1263 SPItem * item = SP_ITEM(i->data);
1264 SPStyle *style = SP_OBJECT_STYLE(item);
1265 g_assert(style != NULL);
1267 if(filter)
1268 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1269 else
1270 ::remove_filter(item, false);
1272 SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1273 }
1275 update_selection(sel);
1276 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Apply filter"));
1277 }
1278 }
1280 /* Add all filters in the document to the combobox.
1281 Keeps the same selection if possible, otherwise selects the first element */
1282 void FilterEffectsDialog::FilterModifier::update_filters()
1283 {
1284 SPDesktop* desktop = _dialog.getDesktop();
1285 SPDocument* document = sp_desktop_document(desktop);
1286 const GSList* filters = sp_document_get_resource_list(document, "filter");
1288 _model->clear();
1290 for(const GSList *l = filters; l; l = l->next) {
1291 Gtk::TreeModel::Row row = *_model->append();
1292 SPFilter* f = (SPFilter*)l->data;
1293 row[_columns.filter] = f;
1294 const gchar* lbl = f->label();
1295 const gchar* id = SP_OBJECT_ID(f);
1296 row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1297 }
1299 update_selection(desktop->selection);
1300 _dialog.update_filter_general_settings_view();
1301 }
1303 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1304 {
1305 if(_list.get_selection()) {
1306 Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1308 if(i)
1309 return (*i)[_columns.filter];
1310 }
1312 return 0;
1313 }
1315 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1316 {
1317 if(filter) {
1318 for(Gtk::TreeModel::iterator i = _model->children().begin();
1319 i != _model->children().end(); ++i) {
1320 if((*i)[_columns.filter] == filter) {
1321 _list.get_selection()->select(i);
1322 break;
1323 }
1324 }
1325 }
1326 }
1328 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1329 {
1330 if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1331 const bool sensitive = get_selected_filter() != NULL;
1332 _menu->items()[0].set_sensitive(sensitive);
1333 _menu->items()[1].set_sensitive(sensitive);
1334 _menu->popup(event->button, event->time);
1335 }
1336 }
1338 void FilterEffectsDialog::FilterModifier::add_filter()
1339 {
1340 SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1341 SPFilter* filter = new_filter(doc);
1343 const int count = _model->children().size();
1344 std::ostringstream os;
1345 os << "filter" << count;
1346 filter->setLabel(os.str().c_str());
1348 update_filters();
1350 select_filter(filter);
1352 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1353 }
1355 void FilterEffectsDialog::FilterModifier::remove_filter()
1356 {
1357 SPFilter *filter = get_selected_filter();
1359 if(filter) {
1360 SPDocument* doc = filter->document;
1361 sp_repr_unparent(filter->repr);
1363 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1365 update_filters();
1366 }
1367 }
1369 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1370 {
1371 SPFilter* filter = get_selected_filter();
1373 if(filter) {
1374 Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1375 repr = repr->duplicate(repr->document());
1376 parent->appendChild(repr);
1378 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1380 update_filters();
1381 }
1382 }
1384 void FilterEffectsDialog::FilterModifier::rename_filter()
1385 {
1386 _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1387 }
1389 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1390 : Glib::ObjectBase(typeid(CellRendererConnection)),
1391 _primitive(*this, "primitive", 0)
1392 {}
1394 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1395 {
1396 return _primitive.get_proxy();
1397 }
1399 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1400 {
1401 _text_width = w;
1402 }
1404 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1405 {
1406 return _text_width;
1407 }
1409 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1410 Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1411 int* x_offset, int* y_offset, int* width, int* height) const
1412 {
1413 PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1415 if(x_offset)
1416 (*x_offset) = 0;
1417 if(y_offset)
1418 (*y_offset) = 0;
1419 if(width)
1420 (*width) = size * primlist.primitive_count() + _text_width * 7;
1421 if(height) {
1422 // Scale the height depending on the number of inputs, unless it's
1423 // the first primitive, in which case there are no connections
1424 SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1425 (*height) = size * input_count(prim);
1426 }
1427 }
1429 /*** PrimitiveList ***/
1430 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1431 : _dialog(d),
1432 _in_drag(0),
1433 _observer(new SignalObserver)
1434 {
1435 d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1437 add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1438 signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1440 _model = Gtk::ListStore::create(_columns);
1442 set_reorderable(true);
1444 set_model(_model);
1445 append_column(_("_Effect"), _columns.type);
1447 _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1448 get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1449 signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1451 _connection_cell.set_text_width(init_text());
1453 int cols_count = append_column(_("Connections"), _connection_cell);
1454 Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1455 if(col)
1456 col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1457 }
1459 // Sets up a vertical Pango context/layout, and returns the largest
1460 // width needed to render the FilterPrimitiveInput labels.
1461 int FilterEffectsDialog::PrimitiveList::init_text()
1462 {
1463 // Set up a vertical context+layout
1464 Glib::RefPtr<Pango::Context> context = create_pango_context();
1465 const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1466 context->set_matrix(matrix);
1467 _vertical_layout = Pango::Layout::create(context);
1469 int maxfont = 0;
1470 for(int i = 0; i < FPInputConverter.end; ++i) {
1471 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1472 int fontw, fonth;
1473 _vertical_layout->get_pixel_size(fontw, fonth);
1474 if(fonth > maxfont)
1475 maxfont = fonth;
1476 }
1478 return maxfont;
1479 }
1481 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1482 {
1483 return _signal_primitive_changed;
1484 }
1486 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1487 {
1488 _observer->set(get_selected());
1489 signal_primitive_changed()();
1490 _dialog._color_matrix_values->clear_store();
1491 }
1493 /* Add all filter primitives in the current to the list.
1494 Keeps the same selection if possible, otherwise selects the first element */
1495 void FilterEffectsDialog::PrimitiveList::update()
1496 {
1497 SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1498 const SPFilterPrimitive* active_prim = get_selected();
1499 bool active_found = false;
1501 _model->clear();
1503 if(f) {
1504 _dialog._primitive_box.set_sensitive(true);
1505 _dialog.update_filter_general_settings_view();
1506 for(SPObject *prim_obj = f->children;
1507 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1508 prim_obj = prim_obj->next) {
1509 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1510 if(prim) {
1511 Gtk::TreeModel::Row row = *_model->append();
1512 row[_columns.primitive] = prim;
1513 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1514 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1515 row[_columns.id] = SP_OBJECT_ID(prim);
1517 if(prim == active_prim) {
1518 get_selection()->select(row);
1519 active_found = true;
1520 }
1521 }
1522 }
1524 if(!active_found && _model->children().begin())
1525 get_selection()->select(_model->children().begin());
1527 columns_autosize();
1528 }
1529 else {
1530 _dialog._primitive_box.set_sensitive(false);
1531 }
1532 }
1534 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1535 {
1536 _primitive_menu = menu;
1537 }
1539 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1540 {
1541 if(_dialog._filter_modifier.get_selected_filter()) {
1542 Gtk::TreeModel::iterator i = get_selection()->get_selected();
1543 if(i)
1544 return (*i)[_columns.primitive];
1545 }
1547 return 0;
1548 }
1550 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1551 {
1552 for(Gtk::TreeIter i = _model->children().begin();
1553 i != _model->children().end(); ++i) {
1554 if((*i)[_columns.primitive] == prim)
1555 get_selection()->select(i);
1556 }
1557 }
1559 void FilterEffectsDialog::PrimitiveList::remove_selected()
1560 {
1561 SPFilterPrimitive* prim = get_selected();
1563 if(prim) {
1564 _observer->set(0);
1566 sp_repr_unparent(prim->repr);
1568 sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1569 _("Remove filter primitive"));
1571 update();
1572 }
1573 }
1575 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1576 {
1577 Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1578 Glib::RefPtr<Gdk::Window> win = get_bin_window();
1579 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1581 SPFilterPrimitive* prim = get_selected();
1582 int row_count = get_model()->children().size();
1584 int fheight = CellRendererConnection::size;
1585 Gdk::Rectangle rct, vis;
1586 Gtk::TreeIter row = get_model()->children().begin();
1587 int text_start_x = 0;
1588 if(row) {
1589 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1590 get_visible_rect(vis);
1591 int vis_x, vis_y;
1592 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1594 text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter.end + 1) + 1;
1595 for(int i = 0; i < FPInputConverter.end; ++i) {
1596 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1597 const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1598 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());
1599 get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1600 get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1601 }
1602 }
1604 int row_index = 0;
1605 for(; row != get_model()->children().end(); ++row, ++row_index) {
1606 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1607 const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1609 // Check mouse state
1610 int mx, my;
1611 Gdk::ModifierType mask;
1612 get_bin_window()->get_pointer(mx, my, mask);
1614 // Outline the bottom of the connection area
1615 const int outline_x = x + fheight * (row_count - row_index);
1616 get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1618 // Side outline
1619 get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1621 std::vector<Gdk::Point> con_poly;
1622 int con_drag_y = 0;
1623 bool inside;
1624 const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1625 const int inputs = input_count(row_prim);
1627 if(SP_IS_FEMERGE(row_prim)) {
1628 for(int i = 0; i < inputs; ++i) {
1629 inside = do_connection_node(row, i, con_poly, mx, my);
1630 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1631 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1632 inside, con_poly);
1634 if(_in_drag == (i + 1))
1635 con_drag_y = con_poly[2].get_y();
1637 if(_in_drag != (i + 1) || row_prim != prim)
1638 draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1639 }
1640 }
1641 else {
1642 // Draw "in" shape
1643 inside = do_connection_node(row, 0, con_poly, mx, my);
1644 con_drag_y = con_poly[2].get_y();
1645 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1646 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1647 inside, con_poly);
1649 // Draw "in" connection
1650 if(_in_drag != 1 || row_prim != prim)
1651 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1653 if(inputs == 2) {
1654 // Draw "in2" shape
1655 inside = do_connection_node(row, 1, con_poly, mx, my);
1656 if(_in_drag == 2)
1657 con_drag_y = con_poly[2].get_y();
1658 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1659 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1660 inside, con_poly);
1661 // Draw "in2" connection
1662 if(_in_drag != 2 || row_prim != prim)
1663 draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1664 }
1665 }
1667 // Draw drag connection
1668 if(row_prim == prim && _in_drag) {
1669 get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1670 mx, con_drag_y);
1671 get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1672 }
1673 }
1675 return true;
1676 }
1678 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1679 const int text_start_x, const int x1, const int y1,
1680 const int row_count)
1681 {
1682 int src_id = 0;
1683 Gtk::TreeIter res = find_result(input, attr, src_id);
1684 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1685 Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1686 Glib::RefPtr<Gdk::GC> gc;
1688 const bool is_first = input == get_model()->children().begin();
1689 const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1690 const bool use_default = !res && !is_merge;
1692 if(res == input || (use_default && is_first)) {
1693 // Draw straight connection to a standard input
1694 // Draw a lighter line for an implicit connection to a standard input
1695 const int tw = _connection_cell.get_text_width();
1696 gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1697 gc = (use_default && is_first) ? lightgc : darkgc;
1698 get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1699 get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1700 }
1701 else {
1702 // Draw an 'L'-shaped connection to another filter primitive
1703 // If no connection is specified, draw a light connection to the previous primitive
1704 gc = use_default ? lightgc : darkgc;
1706 if(use_default) {
1707 res = input;
1708 --res;
1709 }
1711 if(res) {
1712 Gdk::Rectangle rct;
1714 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1715 const int fheight = CellRendererConnection::size;
1717 get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1718 const int row_index = find_index(res);
1719 const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1720 const int y2 = rct.get_y() + rct.get_height();
1722 // Draw a bevelled 'L'-shaped connection
1723 get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1724 get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1725 get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1726 }
1727 }
1728 }
1730 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1731 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1732 std::vector<Gdk::Point>& points,
1733 const int ix, const int iy)
1734 {
1735 Gdk::Rectangle rct;
1736 const int icnt = input_count((*row)[_columns.primitive]);
1738 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1739 const int fheight = CellRendererConnection::size;
1741 get_cell_area(_model->get_path(row), *get_column(1), rct);
1742 const float h = rct.get_height() / icnt;
1744 const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1745 const int con_w = (int)(fheight * 0.35f);
1746 const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1747 points.clear();
1748 points.push_back(Gdk::Point(x, con_y));
1749 points.push_back(Gdk::Point(x, con_y + con_w * 2));
1750 points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1752 return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1753 }
1755 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1756 const int attr, int& src_id)
1757 {
1758 SPFilterPrimitive* prim = (*start)[_columns.primitive];
1759 Gtk::TreeIter target = _model->children().end();
1760 int image = 0;
1762 if(SP_IS_FEMERGE(prim)) {
1763 int c = 0;
1764 bool found = false;
1765 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1766 if(c == attr && SP_IS_FEMERGENODE(o)) {
1767 image = SP_FEMERGENODE(o)->input;
1768 found = true;
1769 }
1770 }
1771 if(!found)
1772 return target;
1773 }
1774 else {
1775 if(attr == SP_ATTR_IN)
1776 image = prim->image_in;
1777 else if(attr == SP_ATTR_IN2) {
1778 if(SP_IS_FEBLEND(prim))
1779 image = SP_FEBLEND(prim)->in2;
1780 else if(SP_IS_FECOMPOSITE(prim))
1781 image = SP_FECOMPOSITE(prim)->in2;
1782 else if(SP_IS_FEDISPLACEMENTMAP(prim))
1783 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1784 else
1785 return target;
1786 }
1787 else
1788 return target;
1789 }
1791 if(image >= 0) {
1792 for(Gtk::TreeIter i = _model->children().begin();
1793 i != start; ++i) {
1794 if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1795 target = i;
1796 }
1797 return target;
1798 }
1799 else if(image < -1) {
1800 src_id = -(image + 2);
1801 return start;
1802 }
1804 return target;
1805 }
1807 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1808 {
1809 int i = 0;
1810 for(Gtk::TreeIter iter = _model->children().begin();
1811 iter != target; ++iter, ++i);
1812 return i;
1813 }
1815 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1816 {
1817 Gtk::TreePath path;
1818 Gtk::TreeViewColumn* col;
1819 const int x = (int)e->x, y = (int)e->y;
1820 int cx, cy;
1822 _drag_prim = 0;
1824 if(get_path_at_pos(x, y, path, col, cx, cy)) {
1825 Gtk::TreeIter iter = _model->get_iter(path);
1826 std::vector<Gdk::Point> points;
1828 _drag_prim = (*iter)[_columns.primitive];
1829 const int icnt = input_count(_drag_prim);
1831 for(int i = 0; i < icnt; ++i) {
1832 if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1833 _in_drag = i + 1;
1834 break;
1835 }
1836 }
1838 queue_draw();
1839 }
1841 if(_in_drag) {
1842 _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1843 _autoscroll = 0;
1844 get_selection()->select(path);
1845 return true;
1846 }
1847 else
1848 return Gtk::TreeView::on_button_press_event(e);
1849 }
1851 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1852 {
1853 const int speed = 10;
1854 const int limit = 15;
1856 Gdk::Rectangle vis;
1857 get_visible_rect(vis);
1858 int vis_x, vis_y;
1859 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1860 const int top = vis_y + vis.get_height();
1862 // When autoscrolling during a connection drag, set the speed based on
1863 // where the mouse is in relation to the edges.
1864 if(e->y < vis_y)
1865 _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1866 else if(e->y < vis_y + limit)
1867 _autoscroll = -speed;
1868 else if(e->y > top)
1869 _autoscroll = (int)(speed + (e->y - top) / 5);
1870 else if(e->y > top - limit)
1871 _autoscroll = speed;
1872 else
1873 _autoscroll = 0;
1875 queue_draw();
1877 return Gtk::TreeView::on_motion_notify_event(e);
1878 }
1880 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1881 {
1882 SPFilterPrimitive *prim = get_selected(), *target;
1884 _scroll_connection.disconnect();
1886 if(_in_drag && prim) {
1887 Gtk::TreePath path;
1888 Gtk::TreeViewColumn* col;
1889 int cx, cy;
1891 if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1892 const gchar *in_val = 0;
1893 Glib::ustring result;
1894 Gtk::TreeIter target_iter = _model->get_iter(path);
1895 target = (*target_iter)[_columns.primitive];
1896 col = get_column(1);
1898 Gdk::Rectangle rct;
1899 get_cell_area(path, *col, rct);
1900 const int twidth = _connection_cell.get_text_width();
1901 const int sources_x = rct.get_width() - twidth * FPInputConverter.end;
1902 if(cx > sources_x) {
1903 int src = (cx - sources_x) / twidth;
1904 if(src < 0)
1905 src = 0;
1906 else if(src >= FPInputConverter.end)
1907 src = FPInputConverter.end - 1;
1908 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1909 in_val = result.c_str();
1910 }
1911 else {
1912 // Ensure that the target comes before the selected primitive
1913 for(Gtk::TreeIter iter = _model->children().begin();
1914 iter != get_selection()->get_selected(); ++iter) {
1915 if(iter == target_iter) {
1916 Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1917 // Make sure the target has a result
1918 const gchar *gres = repr->attribute("result");
1919 if(!gres) {
1920 result = "result" + Glib::Ascii::dtostr(SP_FILTER(prim->parent)->_image_number_next);
1921 repr->setAttribute("result", result.c_str());
1922 in_val = result.c_str();
1923 }
1924 else
1925 in_val = gres;
1926 break;
1927 }
1928 }
1929 }
1931 if(SP_IS_FEMERGE(prim)) {
1932 int c = 1;
1933 bool handled = false;
1934 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1935 if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1936 // If input is null, delete it
1937 if(!in_val) {
1938 sp_repr_unparent(o->repr);
1939 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1940 _("Remove merge node"));
1941 (*get_selection()->get_selected())[_columns.primitive] = prim;
1942 }
1943 else
1944 _dialog.set_attr(o, SP_ATTR_IN, in_val);
1945 handled = true;
1946 }
1947 }
1948 // Add new input?
1949 if(!handled && c == _in_drag && in_val) {
1950 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1951 Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1952 repr->setAttribute("inkscape:collect", "always");
1953 prim->repr->appendChild(repr);
1954 SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1955 Inkscape::GC::release(repr);
1956 _dialog.set_attr(node, SP_ATTR_IN, in_val);
1957 (*get_selection()->get_selected())[_columns.primitive] = prim;
1958 }
1959 }
1960 else {
1961 if(_in_drag == 1)
1962 _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1963 else if(_in_drag == 2)
1964 _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1965 }
1966 }
1968 _in_drag = 0;
1969 queue_draw();
1971 _dialog.update_settings_view();
1972 }
1974 if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1975 const bool sensitive = get_selected() != NULL;
1976 _primitive_menu->items()[0].set_sensitive(sensitive);
1977 _primitive_menu->items()[1].set_sensitive(sensitive);
1978 _primitive_menu->popup(e->button, e->time);
1980 return true;
1981 }
1982 else
1983 return Gtk::TreeView::on_button_release_event(e);
1984 }
1986 // Checks all of prim's inputs, removes any that use result
1987 void check_single_connection(SPFilterPrimitive* prim, const int result)
1988 {
1989 if(prim && result >= 0) {
1991 if(prim->image_in == result)
1992 SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1994 if(SP_IS_FEBLEND(prim)) {
1995 if(SP_FEBLEND(prim)->in2 == result)
1996 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1997 }
1998 else if(SP_IS_FECOMPOSITE(prim)) {
1999 if(SP_FECOMPOSITE(prim)->in2 == result)
2000 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2001 }
2002 else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
2003 if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
2004 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
2005 }
2006 }
2007 }
2009 // Remove any connections going to/from prim_iter that forward-reference other primitives
2010 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
2011 {
2012 SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
2013 bool before = true;
2015 for(Gtk::TreeIter iter = _model->children().begin();
2016 iter != _model->children().end(); ++iter) {
2017 if(iter == prim_iter)
2018 before = false;
2019 else {
2020 SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
2021 if(before)
2022 check_single_connection(cur_prim, prim->image_out);
2023 else
2024 check_single_connection(prim, cur_prim->image_out);
2025 }
2026 }
2027 }
2029 // Reorder the filter primitives to match the list order
2030 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2031 {
2032 SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2033 int ndx = 0;
2035 for(Gtk::TreeModel::iterator iter = _model->children().begin();
2036 iter != _model->children().end(); ++iter, ++ndx) {
2037 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2038 if(prim && prim == _drag_prim) {
2039 SP_OBJECT_REPR(prim)->setPosition(ndx);
2040 break;
2041 }
2042 }
2044 for(Gtk::TreeModel::iterator iter = _model->children().begin();
2045 iter != _model->children().end(); ++iter, ++ndx) {
2046 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2047 if(prim && prim == _drag_prim) {
2048 sanitize_connections(iter);
2049 get_selection()->select(iter);
2050 break;
2051 }
2052 }
2054 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2056 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2057 }
2059 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2060 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2061 {
2062 if(_autoscroll) {
2063 Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2064 double v;
2066 v = a.get_value() + _autoscroll;
2067 if(v < 0)
2068 v = 0;
2069 if(v > a.get_upper() - a.get_page_size())
2070 v = a.get_upper() - a.get_page_size();
2072 a.set_value(v);
2074 queue_draw();
2075 }
2077 return true;
2078 }
2080 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2081 {
2082 return _model->children().size();
2083 }
2085 /*** FilterEffectsDialog ***/
2087 FilterEffectsDialog::FilterEffectsDialog()
2088 : UI::Widget::Panel("", "dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2089 _filter_modifier(*this),
2090 _primitive_list(*this),
2091 _add_primitive_type(FPConverter),
2092 _add_primitive(_("Add Effect:")),
2093 _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2094 _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2095 _settings_initialized(false),
2096 _locked(false),
2097 _attr_lock(false)
2098 {
2099 _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2100 NR_FILTER_ENDPRIMITIVETYPE);
2101 _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2102 1);
2103 _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2104 _sizegroup->set_ignore_hidden();
2106 _add_primitive_type.remove_row(NR_FILTER_TILE);
2107 _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2109 // Initialize widget hierarchy
2110 Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2111 Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2112 Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox(/*homogeneous:*/false, /*spacing:*/4));
2113 Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2115 _getContents()->add(*hpaned);
2116 hpaned->pack1(_filter_modifier);
2117 hpaned->pack2(_primitive_box);
2118 _primitive_box.pack_start(*sw_prims);
2119 _primitive_box.pack_start(*hb_prims, false, false);
2120 _primitive_box.pack_start(*infobox,false, false);
2121 sw_prims->add(_primitive_list);
2122 infobox->pack_start(_infobox_icon, false, false);
2123 infobox->pack_start(_infobox_desc, false, false);
2124 _infobox_desc.set_line_wrap(true);
2125 _infobox_desc.set_size_request(200, -1);
2127 hb_prims->pack_start(_add_primitive, false, false);
2128 hb_prims->pack_start(_add_primitive_type, 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(0, SP_ATTR_K1, _("K1"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2210 _k2 = _settings->add_spinslider(0, SP_ATTR_K2, _("K2"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2211 _k3 = _settings->add_spinslider(0, SP_ATTR_K3, _("K3"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2212 _k4 = _settings->add_spinslider(0, SP_ATTR_K4, _("K4"), -10, 10, 0.1, 0.01, 2, _("If the arithmetic operation is chosen, each result pixel is computed using the formula k1*i1*i2 + k2*i1 + k3*i2 + k4 where i1 and i2 are the pixel values of the first and second inputs respectively."));
2214 _settings->type(NR_FILTER_CONVOLVEMATRIX);
2215 _convolve_order = _settings->add_dualspinbutton("3", 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 //TODO: svg spec: The default value is the sum of all values in kernelMatrix, with the exception that if the sum is zero, then the divisor is set to 1.
2220 _settings->add_spinslider(1, SP_ATTR_DIVISOR, _("Divisor"), 1, 20, 1, 0.1, 2, _("After applying the kernelMatrix to the input image to yield a number, that number is divided by divisor to yield the final destination color value. A divisor that is the sum of all the matrix values tends to have an evening effect on the overall color intensity of the result."));
2221 _settings->add_spinslider(0, SP_ATTR_BIAS, _("Bias"), -10, 10, 1, 0.01, 1, _("This value is added to each component. This is useful to define a constant value as the zero response of the filter."));
2222 _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."));
2223 _settings->add_checkbutton(false, SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive."));
2225 _settings->type(NR_FILTER_DIFFUSELIGHTING);
2226 _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Diffuse Color"), _("Defines the color of the light source"));
2227 _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2228 _settings->add_spinslider(1, SP_ATTR_DIFFUSECONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2229 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2230 _settings->add_lightsource();
2232 _settings->type(NR_FILTER_DISPLACEMENTMAP);
2233 _settings->add_spinslider(0, SP_ATTR_SCALE, _("Scale"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2234 _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2235 _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2237 _settings->type(NR_FILTER_FLOOD);
2238 _settings->add_color(/*default: black*/ 0, SP_PROP_FLOOD_COLOR, _("Flood Color"), _("The whole filter region will be filled with this color."));
2239 _settings->add_spinslider(1, SP_PROP_FLOOD_OPACITY, _("Opacity"), 0, 1, 0.1, 0.01, 2);
2241 _settings->type(NR_FILTER_GAUSSIANBLUR);
2242 _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2244 _settings->type(NR_FILTER_MERGE);
2245 _settings->add_no_params();
2247 _settings->type(NR_FILTER_MORPHOLOGY);
2248 _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2249 _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius"), 0, 100, 1, 0.01, 1);
2251 _settings->type(NR_FILTER_IMAGE);
2252 _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image"));
2254 _settings->type(NR_FILTER_OFFSET);
2255 _settings->add_spinslider(0, SP_ATTR_DX, _("Delta X"), -100, 100, 1, 0.01, 1, _("This is how far the input image gets shifted to the right"));
2256 _settings->add_spinslider(0, SP_ATTR_DY, _("Delta Y"), -100, 100, 1, 0.01, 1, _("This is how far the input image gets shifted downwards"));
2258 _settings->type(NR_FILTER_SPECULARLIGHTING);
2259 _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Specular Color"), _("Defines the color of the light source"));
2260 _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2261 _settings->add_spinslider(1, SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2262 _settings->add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2263 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2264 _settings->add_lightsource();
2266 _settings->type(NR_FILTER_TILE);
2267 _settings->add_notimplemented();
2269 _settings->type(NR_FILTER_TURBULENCE);
2270 // _settings->add_checkbutton(false, SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2271 _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2272 _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency"), 0, 0.4, 0.001, 0.01, 3);
2273 _settings->add_spinslider(1, SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2274 _settings->add_spinslider(0, SP_ATTR_SEED, _("Seed"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2275 }
2277 void FilterEffectsDialog::add_primitive()
2278 {
2279 SPFilter* filter = _filter_modifier.get_selected_filter();
2281 if(filter) {
2282 SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2284 _primitive_list.select(prim);
2286 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2287 }
2288 }
2290 void FilterEffectsDialog::update_primitive_infobox()
2291 {
2292 if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2293 _infobox_icon.show();
2294 _infobox_desc.show();
2295 } else {
2296 _infobox_icon.hide();
2297 _infobox_desc.hide();
2298 }
2299 switch(_add_primitive_type.get_active_data()->id){
2300 case(NR::NR_FILTER_BLEND):
2301 _infobox_icon.set(g_strdup_printf("%s/feBlend-icon.png", INKSCAPE_PIXMAPDIR));
2302 _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2303 break;
2304 case(NR::NR_FILTER_COLORMATRIX):
2305 _infobox_icon.set(g_strdup_printf("%s/feColorMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2306 _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."));
2307 break;
2308 case(NR::NR_FILTER_COMPONENTTRANSFER):
2309 _infobox_icon.set(g_strdup_printf("%s/feComponentTransfer-icon.png", INKSCAPE_PIXMAPDIR));
2310 _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."));
2311 break;
2312 case(NR::NR_FILTER_COMPOSITE):
2313 _infobox_icon.set(g_strdup_printf("%s/feComposite-icon.png", INKSCAPE_PIXMAPDIR));
2314 _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."));
2315 break;
2316 case(NR::NR_FILTER_CONVOLVEMATRIX):
2317 _infobox_icon.set(g_strdup_printf("%s/feConvolveMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2318 _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."));
2319 break;
2320 case(NR::NR_FILTER_DIFFUSELIGHTING):
2321 _infobox_icon.set(g_strdup_printf("%s/feDiffuseLighting-icon.png", INKSCAPE_PIXMAPDIR));
2322 _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."));
2323 break;
2324 case(NR::NR_FILTER_DISPLACEMENTMAP):
2325 _infobox_icon.set(g_strdup_printf("%s/feDisplacementMap-icon.png", INKSCAPE_PIXMAPDIR));
2326 _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."));
2327 break;
2328 case(NR::NR_FILTER_FLOOD):
2329 _infobox_icon.set(g_strdup_printf("%s/feFlood-icon.png", INKSCAPE_PIXMAPDIR));
2330 _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."));
2331 break;
2332 case(NR::NR_FILTER_GAUSSIANBLUR):
2333 _infobox_icon.set(g_strdup_printf("%s/feGaussianBlur-icon.png", INKSCAPE_PIXMAPDIR));
2334 _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."));
2335 break;
2336 case(NR::NR_FILTER_IMAGE):
2337 _infobox_icon.set(g_strdup_printf("%s/feImage-icon.png", INKSCAPE_PIXMAPDIR));
2338 _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2339 break;
2340 case(NR::NR_FILTER_MERGE):
2341 _infobox_icon.set(g_strdup_printf("%s/feMerge-icon.png", INKSCAPE_PIXMAPDIR));
2342 _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."));
2343 break;
2344 case(NR::NR_FILTER_MORPHOLOGY):
2345 _infobox_icon.set(g_strdup_printf("%s/feMorphology-icon.png", INKSCAPE_PIXMAPDIR));
2346 _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."));
2347 break;
2348 case(NR::NR_FILTER_OFFSET):
2349 _infobox_icon.set(g_strdup_printf("%s/feOffset-icon.png", INKSCAPE_PIXMAPDIR));
2350 _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."));
2351 break;
2352 case(NR::NR_FILTER_SPECULARLIGHTING):
2353 _infobox_icon.set(g_strdup_printf("%s/feSpecularLighting-icon.png", INKSCAPE_PIXMAPDIR));
2354 _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."));
2355 break;
2356 case(NR::NR_FILTER_TILE):
2357 _infobox_icon.set(g_strdup_printf("%s/feTile-icon.png", INKSCAPE_PIXMAPDIR));
2358 _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2359 break;
2360 case(NR::NR_FILTER_TURBULENCE):
2361 _infobox_icon.set(g_strdup_printf("%s/feTurbulence-icon.png", INKSCAPE_PIXMAPDIR));
2362 _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."));
2363 break;
2364 default:
2365 g_assert(false);
2366 break;
2367 }
2368 }
2370 void FilterEffectsDialog::duplicate_primitive()
2371 {
2372 SPFilter* filter = _filter_modifier.get_selected_filter();
2373 SPFilterPrimitive* origprim = _primitive_list.get_selected();
2375 if(filter && origprim) {
2376 Inkscape::XML::Node *repr;
2377 repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2378 SP_OBJECT_REPR(filter)->appendChild(repr);
2380 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2382 _primitive_list.update();
2383 }
2384 }
2386 void FilterEffectsDialog::convolve_order_changed()
2387 {
2388 _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2389 _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2390 _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2391 }
2393 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2394 {
2395 set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2396 }
2398 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2399 {
2400 if(!_locked) {
2401 _attr_lock = true;
2402 SPFilter *filter = _filter_modifier.get_selected_filter();
2403 const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2404 if (filter && name && SP_OBJECT_REPR(filter)){
2405 SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2406 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2407 }
2408 _attr_lock = false;
2409 }
2410 }
2412 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2413 {
2414 set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2415 }
2417 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2418 {
2419 if(!_locked) {
2420 _attr_lock = true;
2422 SPFilter *filter = _filter_modifier.get_selected_filter();
2423 const gchar* name = (const gchar*)sp_attribute_name(attr);
2424 if(filter && name && o) {
2425 update_settings_sensitivity();
2427 SP_OBJECT_REPR(o)->setAttribute(name, val);
2428 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2430 Glib::ustring undokey = "filtereffects:";
2431 undokey += name;
2432 sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2433 _("Set filter primitive attribute"));
2434 }
2436 _attr_lock = false;
2437 }
2438 }
2440 void FilterEffectsDialog::update_filter_general_settings_view()
2441 {
2442 if(_settings_initialized != true) return;
2444 if(!_locked) {
2445 _attr_lock = true;
2447 SPFilter* filter = _filter_modifier.get_selected_filter();
2449 if(filter) {
2450 _filter_general_settings->show_and_update(0, filter);
2451 _no_filter_selected.hide();
2452 }
2453 else {
2454 std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2455 vect[0]->hide_all();
2456 _no_filter_selected.show();
2457 }
2459 _attr_lock = false;
2460 }
2461 }
2463 void FilterEffectsDialog::update_settings_view()
2464 {
2465 update_settings_sensitivity();
2467 if(_attr_lock)
2468 return;
2470 //First Tab
2472 std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2473 for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2474 _empty_settings.show();
2476 if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2477 _infobox_icon.show();
2478 _infobox_desc.show();
2479 } else {
2480 _infobox_icon.hide();
2481 _infobox_desc.hide();
2482 }
2484 SPFilterPrimitive* prim = _primitive_list.get_selected();
2486 if(prim) {
2487 _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2488 _empty_settings.hide();
2489 }
2491 //Second Tab
2493 std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2494 vect2[0]->hide_all();
2495 _no_filter_selected.show();
2497 SPFilter* filter = _filter_modifier.get_selected_filter();
2499 if(filter) {
2500 _filter_general_settings->show_and_update(0, filter);
2501 _no_filter_selected.hide();
2502 }
2504 }
2506 void FilterEffectsDialog::update_settings_sensitivity()
2507 {
2508 SPFilterPrimitive* prim = _primitive_list.get_selected();
2509 const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2510 _k1->set_sensitive(use_k);
2511 _k2->set_sensitive(use_k);
2512 _k3->set_sensitive(use_k);
2513 _k4->set_sensitive(use_k);
2515 // Component transfer not yet implemented
2516 /*
2517 if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2518 SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2519 const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2520 const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2522 _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2523 _ct_slope->set_sensitive(linear);
2524 _ct_intercept->set_sensitive(linear);
2525 _ct_amplitude->set_sensitive(gamma);
2526 _ct_exponent->set_sensitive(gamma);
2527 _ct_offset->set_sensitive(gamma);
2528 }
2529 */
2530 }
2532 void FilterEffectsDialog::update_color_matrix()
2533 {
2534 _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2535 }
2537 } // namespace Dialog
2538 } // namespace UI
2539 } // namespace Inkscape
2541 /*
2542 Local Variables:
2543 mode:c++
2544 c-file-style:"stroustrup"
2545 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2546 indent-tabs-mode:nil
2547 fill-column:99
2548 End:
2549 */
2550 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :