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