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 <juca@members.fsf.org>
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/gtk.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, 0);
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, 0);
235 _s2.set_increments(step_inc, 0);
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 // TRANSLATORS: this dialog is accessible via menu Filters - Filter editor
465 _matrix(SP_ATTR_VALUES, _("This matrix determines a linear transform on color 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.")),
466 _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
467 _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
468 _label(_("None"), Gtk::ALIGN_LEFT),
469 _use_stored(false),
470 _saturation_store(0),
471 _angle_store(0)
472 {
473 _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
474 _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
475 _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
476 signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
478 _matrix.show();
479 _saturation.show();
480 _angle.show();
481 _label.show();
482 _label.set_sensitive(false);
484 set_shadow_type(Gtk::SHADOW_NONE);
485 }
487 virtual void set_from_attribute(SPObject* o)
488 {
489 if(SP_IS_FECOLORMATRIX(o)) {
490 SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
491 remove();
492 switch(col->type) {
493 case COLORMATRIX_SATURATE:
494 add(_saturation);
495 if(_use_stored)
496 _saturation.set_value(_saturation_store);
497 else
498 _saturation.set_from_attribute(o);
499 break;
500 case COLORMATRIX_HUEROTATE:
501 add(_angle);
502 if(_use_stored)
503 _angle.set_value(_angle_store);
504 else
505 _angle.set_from_attribute(o);
506 break;
507 case COLORMATRIX_LUMINANCETOALPHA:
508 add(_label);
509 break;
510 case COLORMATRIX_MATRIX:
511 default:
512 add(_matrix);
513 if(_use_stored)
514 _matrix.set_values(_matrix_store);
515 else
516 _matrix.set_from_attribute(o);
517 break;
518 }
519 _use_stored = true;
520 }
521 }
523 virtual Glib::ustring get_as_attribute() const
524 {
525 const Widget* w = get_child();
526 if(w == &_label)
527 return "";
528 else
529 return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
530 }
532 void clear_store()
533 {
534 _use_stored = false;
535 }
536 private:
537 void update_store()
538 {
539 const Widget* w = get_child();
540 if(w == &_matrix)
541 _matrix_store = _matrix.get_values();
542 else if(w == &_saturation)
543 _saturation_store = _saturation.get_value();
544 else if(w == &_angle)
545 _angle_store = _angle.get_value();
546 }
548 MatrixAttr _matrix;
549 SpinSlider _saturation;
550 SpinSlider _angle;
551 Gtk::Label _label;
553 // Store separate values for the different color modes
554 bool _use_stored;
555 std::vector<double> _matrix_store;
556 double _saturation_store;
557 double _angle_store;
558 };
560 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
562 //Displays a chooser for feImage input
563 //It may be a filename or the id for an SVG Element
564 //described in xlink:href syntax
565 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
566 {
567 public:
568 FileOrElementChooser(const SPAttributeEnum a)
569 : AttrWidget(a)
570 {
571 pack_start(_entry, false, false);
572 pack_start(_fromFile, false, false);
573 pack_start(_fromSVGElement, false, false);
575 _fromFile.set_label(_("Image File"));
576 _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
578 _fromSVGElement.set_label(_("Selected SVG Element"));
579 _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
581 _entry.signal_changed().connect(signal_attr_changed().make_slot());
583 show_all();
585 }
587 // Returns the element in xlink:href form.
588 Glib::ustring get_as_attribute() const
589 {
590 return _entry.get_text();
591 }
594 void set_from_attribute(SPObject* o)
595 {
596 const gchar* val = attribute_value(o);
597 if(val) {
598 _entry.set_text(val);
599 } else {
600 _entry.set_text("");
601 }
602 }
604 void set_desktop(SPDesktop* d){
605 _desktop = d;
606 }
608 private:
609 void select_svg_element(){
610 Inkscape::Selection* sel = sp_desktop_selection(_desktop);
611 if (sel->isEmpty()) return;
612 Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
613 if (!node || !node->matchAttributeName("id")) return;
615 std::ostringstream xlikhref;
616 xlikhref << "#" << node->attribute("id");
617 _entry.set_text(xlikhref.str());
618 }
620 void select_file(){
622 //# Get the current directory for finding files
623 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
624 Glib::ustring open_path;
625 Glib::ustring attr = prefs->getString("/dialogs/open/path");
626 if (!attr.empty())
627 open_path = attr;
629 //# Test if the open_path directory exists
630 if (!Inkscape::IO::file_test(open_path.c_str(),
631 (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
632 open_path = "";
634 //# If no open path, default to our home directory
635 if (open_path.size() < 1)
636 {
637 open_path = g_get_home_dir();
638 open_path.append(G_DIR_SEPARATOR_S);
639 }
641 //# Create a dialog if we don't already have one
642 if (!selectFeImageFileInstance) {
643 selectFeImageFileInstance =
644 Inkscape::UI::Dialog::FileOpenDialog::create(
645 *_desktop->getToplevel(),
646 open_path,
647 Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not just svg*/
648 (char const *)_("Select an image to be used as feImage input"));
649 }
651 //# Show the dialog
652 bool const success = selectFeImageFileInstance->show();
653 if (!success)
654 return;
656 //# User selected something. Get name and type
657 Glib::ustring fileName = selectFeImageFileInstance->getFilename();
659 if (fileName.size() > 0) {
661 Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
663 if ( newFileName.size() > 0)
664 fileName = newFileName;
665 else
666 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
668 open_path = fileName;
669 open_path.append(G_DIR_SEPARATOR_S);
670 prefs->setString("/dialogs/open/path", open_path);
672 _entry.set_text(fileName);
673 }
674 return;
675 }
677 Gtk::Entry _entry;
678 Gtk::Button _fromFile;
679 Gtk::Button _fromSVGElement;
680 SPDesktop* _desktop;
681 };
683 class FilterEffectsDialog::Settings
684 {
685 public:
686 typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
688 Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
689 : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
690 {
691 _groups.resize(_max_types);
692 _attrwidgets.resize(_max_types);
693 _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
695 for(int i = 0; i < _max_types; ++i) {
696 _groups[i] = new Gtk::VBox;
697 b.pack_start(*_groups[i], false, false);
698 }
699 _current_type = 0;
700 }
702 ~Settings()
703 {
704 for(int i = 0; i < _max_types; ++i) {
705 delete _groups[i];
706 for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
707 delete _attrwidgets[i][j];
708 }
709 }
711 // Show the active settings group and update all the AttrWidgets with new values
712 void show_and_update(const int t, SPObject* ob)
713 {
714 if(t != _current_type) {
715 type(t);
716 for(unsigned i = 0; i < _groups.size(); ++i)
717 _groups[i]->hide();
718 }
719 if(t >= 0)
720 _groups[t]->show_all();
722 _dialog.set_attrs_locked(true);
723 for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
724 _attrwidgets[_current_type][i]->set_from_attribute(ob);
725 _dialog.set_attrs_locked(false);
726 }
728 int get_current_type() const
729 {
730 return _current_type;
731 }
733 void type(const int t)
734 {
735 _current_type = t;
736 }
738 void add_no_params()
739 {
740 Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
741 add_widget(lbl, "");
742 }
744 void add_notimplemented()
745 {
746 Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
747 add_widget(lbl, "");
748 }
750 // LightSource
751 LightSourceControl* add_lightsource();
753 // CheckBox
754 CheckButtonAttr* add_checkbutton(bool def, const SPAttributeEnum attr, const Glib::ustring& label,
755 const Glib::ustring& tv, const Glib::ustring& fv, char* tip_text = NULL)
756 {
757 CheckButtonAttr* cb = new CheckButtonAttr(def, label, tv, fv, attr, tip_text);
758 add_widget(cb, "");
759 add_attr_widget(cb);
760 return cb;
761 }
763 // ColorButton
764 ColorButton* add_color(unsigned int def, const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text = NULL)
765 {
766 ColorButton* col = new ColorButton(def, attr, tip_text);
767 add_widget(col, label);
768 add_attr_widget(col);
769 return col;
770 }
772 // Matrix
773 MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text)
774 {
775 MatrixAttr* conv = new MatrixAttr(attr, tip_text);
776 add_widget(conv, label);
777 add_attr_widget(conv);
778 return conv;
779 }
781 // ColorMatrixValues
782 ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
783 {
784 ColorMatrixValues* cmv = new ColorMatrixValues();
785 add_widget(cmv, label);
786 add_attr_widget(cmv);
787 return cmv;
788 }
790 // SpinSlider
791 SpinSlider* add_spinslider(double def, const SPAttributeEnum attr, const Glib::ustring& label,
792 const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
793 {
794 SpinSlider* spinslider = new SpinSlider(def, lo, hi, step_inc, climb, digits, attr, tip_text);
795 add_widget(spinslider, label);
796 add_attr_widget(spinslider);
797 return spinslider;
798 }
800 // DualSpinSlider
801 DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
802 const double lo, const double hi, const double step_inc,
803 const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
804 {
805 DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
806 add_widget(dss, label);
807 add_attr_widget(dss);
808 return dss;
809 }
811 // DualSpinButton
812 DualSpinButton* add_dualspinbutton(char* defalt_value, const SPAttributeEnum attr, const Glib::ustring& label,
813 const double lo, const double hi, const double step_inc,
814 const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
815 {
816 DualSpinButton* dsb = new DualSpinButton(defalt_value, lo, hi, step_inc, climb, digits, attr, tip1, tip2);
817 add_widget(dsb, label);
818 add_attr_widget(dsb);
819 return dsb;
820 }
822 // MultiSpinButton
823 MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
824 const Glib::ustring& label, const double lo, const double hi,
825 const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
826 {
827 std::vector<SPAttributeEnum> attrs;
828 attrs.push_back(attr1);
829 attrs.push_back(attr2);
831 std::vector<double> default_values;
832 default_values.push_back(def1);
833 default_values.push_back(def2);
835 std::vector<char*> tips;
836 tips.push_back(tip1);
837 tips.push_back(tip2);
839 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
840 add_widget(msb, label);
841 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
842 add_attr_widget(msb->get_spinbuttons()[i]);
843 return msb;
844 }
845 MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
846 const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
847 const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
848 {
849 std::vector<SPAttributeEnum> attrs;
850 attrs.push_back(attr1);
851 attrs.push_back(attr2);
852 attrs.push_back(attr3);
854 std::vector<double> default_values;
855 default_values.push_back(def1);
856 default_values.push_back(def2);
857 default_values.push_back(def3);
859 std::vector<char*> tips;
860 tips.push_back(tip1);
861 tips.push_back(tip2);
862 tips.push_back(tip3);
864 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
865 add_widget(msb, label);
866 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
867 add_attr_widget(msb->get_spinbuttons()[i]);
868 return msb;
869 }
871 // FileOrElementChooser
872 FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
873 {
874 FileOrElementChooser* foech = new FileOrElementChooser(attr);
875 foech->set_desktop(_dialog.getDesktop());
876 add_widget(foech, label);
877 add_attr_widget(foech);
878 return foech;
879 }
881 // ComboBoxEnum
882 template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
883 const Glib::ustring& label,
884 const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
885 {
886 ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
887 add_widget(combo, label);
888 add_attr_widget(combo->get_attrwidget());
889 return combo->get_attrwidget();
890 }
891 private:
892 Gtk::Tooltips _tt;
894 void add_attr_widget(AttrWidget* a)
895 {
896 _attrwidgets[_current_type].push_back(a);
897 a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
898 }
900 /* Adds a new settings widget using the specified label. The label will be formatted with a colon
901 and all widgets within the setting group are aligned automatically. */
902 void add_widget(Gtk::Widget* w, const Glib::ustring& label)
903 {
904 Gtk::Label *lbl = 0;
905 Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
906 hb->set_spacing(12);
908 if(label != "") {
909 //lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT)); colon now in label (LP #358921)
910 lbl = Gtk::manage(new Gtk::Label(label, Gtk::ALIGN_LEFT));
911 hb->pack_start(*lbl, false, false);
912 _size_group->add_widget(*lbl);
913 lbl->show();
914 }
916 hb->pack_start(*w);
917 _groups[_current_type]->pack_start(*hb);
918 hb->show();
919 w->show();
920 }
922 std::vector<Gtk::VBox*> _groups;
923 Glib::RefPtr<Gtk::SizeGroup> _size_group;
924 FilterEffectsDialog& _dialog;
925 SetAttrSlot _set_attr_slot;
926 std::vector<std::vector< AttrWidget*> > _attrwidgets;
927 int _current_type, _max_types;
928 };
930 // Settings for the three light source objects
931 class FilterEffectsDialog::LightSourceControl : public AttrWidget
932 {
933 public:
934 LightSourceControl(FilterEffectsDialog& d)
935 : AttrWidget(SP_ATTR_INVALID),
936 _dialog(d),
937 _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
938 _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
939 _light_source(LightSourceConverter),
940 _locked(false)
941 {
942 _light_box.pack_start(_light_label, false, false);
943 _light_box.pack_start(_light_source);
944 _light_box.show_all();
945 _light_box.set_spacing(12);
946 _dialog._sizegroup->add_widget(_light_label);
948 _box.add(_light_box);
949 _box.reorder_child(_light_box, 0);
950 _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
952 // FIXME: these range values are complete crap
954 _settings.type(LIGHT_DISTANT);
955 _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"));
956 _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"));
958 _settings.type(LIGHT_POINT);
959 _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.type(LIGHT_SPOT);
962 _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"));
963 _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
964 SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
965 _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
966 _settings.add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
967 //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.
968 _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."));
969 }
971 Gtk::VBox& get_box()
972 {
973 return _box;
974 }
975 protected:
976 Glib::ustring get_as_attribute() const
977 {
978 return "";
979 }
980 void set_from_attribute(SPObject* o)
981 {
982 if(_locked)
983 return;
985 _locked = true;
987 SPObject* child = o->children;
989 if(SP_IS_FEDISTANTLIGHT(child))
990 _light_source.set_active(0);
991 else if(SP_IS_FEPOINTLIGHT(child))
992 _light_source.set_active(1);
993 else if(SP_IS_FESPOTLIGHT(child))
994 _light_source.set_active(2);
995 else
996 _light_source.set_active(-1);
998 update();
1000 _locked = false;
1001 }
1002 private:
1003 void on_source_changed()
1004 {
1005 if(_locked)
1006 return;
1008 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1009 if(prim) {
1010 _locked = true;
1012 SPObject* child = prim->children;
1013 const int ls = _light_source.get_active_row_number();
1014 // Check if the light source type has changed
1015 if(!(ls == -1 && !child) &&
1016 !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1017 !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1018 !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1019 if(child)
1020 sp_repr_unparent(child->repr);
1022 if(ls != -1) {
1023 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1024 Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1025 prim->repr->appendChild(repr);
1026 Inkscape::GC::release(repr);
1027 }
1029 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1030 update();
1031 }
1033 _locked = false;
1034 }
1035 }
1037 void update()
1038 {
1039 _box.hide_all();
1040 _box.show();
1041 _light_box.show_all();
1043 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1044 if(prim && prim->children)
1045 _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1046 }
1048 FilterEffectsDialog& _dialog;
1049 Gtk::VBox _box;
1050 Settings _settings;
1051 Gtk::HBox _light_box;
1052 Gtk::Label _light_label;
1053 ComboBoxEnum<LightSource> _light_source;
1054 bool _locked;
1055 };
1057 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1058 {
1059 LightSourceControl* ls = new LightSourceControl(_dialog);
1060 add_attr_widget(ls);
1061 add_widget(&ls->get_box(), "");
1062 return ls;
1063 }
1065 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1066 sigc::slot<void> rem)
1067 {
1068 Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1070 menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1071 Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1072 menu->append(*mi);
1073 mi->signal_activate().connect(rem);
1074 mi->show();
1075 menu->accelerate(parent);
1077 return menu;
1078 }
1080 /*** FilterModifier ***/
1081 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1082 : _dialog(d), _add(Gtk::Stock::NEW), _observer(new Inkscape::XML::SignalObserver)
1083 {
1084 Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1085 pack_start(*sw);
1086 pack_start(_add, false, false);
1087 sw->add(_list);
1089 _model = Gtk::ListStore::create(_columns);
1090 _list.set_model(_model);
1091 _cell_toggle.set_active(true);
1092 const int selcol = _list.append_column("", _cell_toggle);
1093 Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1094 if(col)
1095 col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1096 _list.append_column_editable(_("_Filter"), _columns.label);
1097 ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1098 signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1100 sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1101 sw->set_shadow_type(Gtk::SHADOW_IN);
1102 show_all_children();
1103 _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1104 _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1105 _list.signal_button_release_event().connect_notify(
1106 sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1107 _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1108 sigc::mem_fun(*this, &FilterModifier::remove_filter));
1109 _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1110 _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1111 _menu->accelerate(*this);
1113 _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1114 _observer->signal_changed().connect(signal_filter_changed().make_slot());
1115 g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1116 G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1118 g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1119 G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1120 g_signal_connect(G_OBJECT(INKSCAPE), "deactivate_desktop",
1121 G_CALLBACK(&FilterModifier::on_deactivate_desktop), this);
1123 on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1124 update_filters();
1125 }
1127 FilterEffectsDialog::FilterModifier::~FilterModifier()
1128 {
1129 _resource_changed.disconnect();
1130 _doc_replaced.disconnect();
1131 }
1133 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1134 {
1135 me->_doc_replaced.disconnect();
1136 me->_doc_replaced = desktop->connectDocumentReplaced(
1137 sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1139 me->_resource_changed.disconnect();
1140 me->_resource_changed =
1141 sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1142 sigc::mem_fun(me, &FilterModifier::update_filters));
1144 me->_dialog.setDesktop(desktop);
1146 me->update_filters();
1147 }
1149 void FilterEffectsDialog::FilterModifier::on_deactivate_desktop(Application*, SPDesktop* /*desktop*/, FilterModifier* me)
1150 {
1151 me->_doc_replaced.disconnect();
1152 me->_resource_changed.disconnect();
1153 me->_dialog.setDesktop(NULL);
1154 }
1157 // When the selection changes, show the active filter(s) in the dialog
1158 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1159 Selection *sel,
1160 FilterModifier* fm)
1161 {
1162 if(fm && sel)
1163 fm->update_selection(sel);
1164 }
1166 // Update each filter's sel property based on the current object selection;
1167 // If the filter is not used by any selected object, sel = 0,
1168 // otherwise sel is set to the total number of filters in use by selected objects
1169 // If only one filter is in use, it is selected
1170 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1171 {
1172 std::set<SPObject*> used;
1174 for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1175 SPObject *obj = SP_OBJECT (i->data);
1176 SPStyle *style = SP_OBJECT_STYLE (obj);
1177 if(!style || !SP_IS_ITEM(obj)) continue;
1179 if(style->filter.set && style->getFilter())
1180 used.insert(style->getFilter());
1181 else
1182 used.insert(0);
1183 }
1185 const int size = used.size();
1187 for(Gtk::TreeIter iter = _model->children().begin();
1188 iter != _model->children().end(); ++iter) {
1189 if(used.find((*iter)[_columns.filter]) != used.end()) {
1190 // If only one filter is in use by the selection, select it
1191 if(size == 1)
1192 _list.get_selection()->select(iter);
1193 (*iter)[_columns.sel] = size;
1194 }
1195 else
1196 (*iter)[_columns.sel] = 0;
1197 }
1198 }
1200 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1201 {
1202 _observer->set(get_selected_filter());
1203 signal_filter_changed()();
1204 }
1206 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1207 {
1208 Gtk::TreeModel::iterator iter = _model->get_iter(path);
1210 if(iter) {
1211 SPFilter* filter = (*iter)[_columns.filter];
1212 filter->setLabel(text.c_str());
1213 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1214 if(iter)
1215 (*iter)[_columns.label] = text;
1216 }
1217 }
1219 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1220 {
1221 Gtk::TreeIter iter = _model->get_iter(path);
1223 if(iter) {
1224 SPDesktop *desktop = _dialog.getDesktop();
1225 SPDocument *doc = sp_desktop_document(desktop);
1226 SPFilter* filter = (*iter)[_columns.filter];
1227 Inkscape::Selection *sel = sp_desktop_selection(desktop);
1229 /* If this filter is the only one used in the selection, unset it */
1230 if((*iter)[_columns.sel] == 1)
1231 filter = 0;
1233 GSList const *items = sel->itemList();
1235 for (GSList const *i = items; i != NULL; i = i->next) {
1236 SPItem * item = SP_ITEM(i->data);
1237 SPStyle *style = SP_OBJECT_STYLE(item);
1238 g_assert(style != NULL);
1240 if(filter)
1241 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1242 else
1243 ::remove_filter(item, false);
1245 SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1246 }
1248 update_selection(sel);
1249 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Apply filter"));
1250 }
1251 }
1253 /* Add all filters in the document to the combobox.
1254 Keeps the same selection if possible, otherwise selects the first element */
1255 void FilterEffectsDialog::FilterModifier::update_filters()
1256 {
1257 SPDesktop* desktop = _dialog.getDesktop();
1258 SPDocument* document = sp_desktop_document(desktop);
1259 const GSList* filters = sp_document_get_resource_list(document, "filter");
1261 _model->clear();
1263 for(const GSList *l = filters; l; l = l->next) {
1264 Gtk::TreeModel::Row row = *_model->append();
1265 SPFilter* f = (SPFilter*)l->data;
1266 row[_columns.filter] = f;
1267 const gchar* lbl = f->label();
1268 const gchar* id = f->getId();
1269 row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1270 }
1272 update_selection(desktop->selection);
1273 _dialog.update_filter_general_settings_view();
1274 }
1276 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1277 {
1278 if(_list.get_selection()) {
1279 Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1281 if(i)
1282 return (*i)[_columns.filter];
1283 }
1285 return 0;
1286 }
1288 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1289 {
1290 if(filter) {
1291 for(Gtk::TreeModel::iterator i = _model->children().begin();
1292 i != _model->children().end(); ++i) {
1293 if((*i)[_columns.filter] == filter) {
1294 _list.get_selection()->select(i);
1295 break;
1296 }
1297 }
1298 }
1299 }
1301 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1302 {
1303 if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1304 const bool sensitive = get_selected_filter() != NULL;
1305 _menu->items()[0].set_sensitive(sensitive);
1306 _menu->items()[1].set_sensitive(sensitive);
1307 _menu->popup(event->button, event->time);
1308 }
1309 }
1311 void FilterEffectsDialog::FilterModifier::add_filter()
1312 {
1313 SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1314 SPFilter* filter = new_filter(doc);
1316 const int count = _model->children().size();
1317 std::ostringstream os;
1318 os << _("filter") << count;
1319 filter->setLabel(os.str().c_str());
1321 update_filters();
1323 select_filter(filter);
1325 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1326 }
1328 void FilterEffectsDialog::FilterModifier::remove_filter()
1329 {
1330 SPFilter *filter = get_selected_filter();
1332 if(filter) {
1333 SPDocument* doc = filter->document;
1334 sp_repr_unparent(filter->repr);
1336 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1338 update_filters();
1339 }
1340 }
1342 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1343 {
1344 SPFilter* filter = get_selected_filter();
1346 if(filter) {
1347 Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1348 repr = repr->duplicate(repr->document());
1349 parent->appendChild(repr);
1351 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1353 update_filters();
1354 }
1355 }
1357 void FilterEffectsDialog::FilterModifier::rename_filter()
1358 {
1359 _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1360 }
1362 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1363 : Glib::ObjectBase(typeid(CellRendererConnection)),
1364 _primitive(*this, "primitive", 0)
1365 {}
1367 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1368 {
1369 return _primitive.get_proxy();
1370 }
1372 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1373 {
1374 _text_width = w;
1375 }
1377 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1378 {
1379 return _text_width;
1380 }
1382 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1383 Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1384 int* x_offset, int* y_offset, int* width, int* height) const
1385 {
1386 PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1388 if(x_offset)
1389 (*x_offset) = 0;
1390 if(y_offset)
1391 (*y_offset) = 0;
1392 if(width)
1393 (*width) = size * primlist.primitive_count() + _text_width * 7;
1394 if(height) {
1395 // Scale the height depending on the number of inputs, unless it's
1396 // the first primitive, in which case there are no connections
1397 SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1398 (*height) = size * input_count(prim);
1399 }
1400 }
1402 /*** PrimitiveList ***/
1403 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1404 : _dialog(d),
1405 _in_drag(0),
1406 _observer(new Inkscape::XML::SignalObserver)
1407 {
1408 d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1410 add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1411 signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1413 _model = Gtk::ListStore::create(_columns);
1415 set_reorderable(true);
1417 set_model(_model);
1418 append_column(_("_Effect"), _columns.type);
1420 _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1421 get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1422 signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1424 _connection_cell.set_text_width(init_text());
1426 int cols_count = append_column(_("Connections"), _connection_cell);
1427 Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1428 if(col)
1429 col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1430 }
1432 // Sets up a vertical Pango context/layout, and returns the largest
1433 // width needed to render the FilterPrimitiveInput labels.
1434 int FilterEffectsDialog::PrimitiveList::init_text()
1435 {
1436 // Set up a vertical context+layout
1437 Glib::RefPtr<Pango::Context> context = create_pango_context();
1438 const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1439 context->set_matrix(matrix);
1440 _vertical_layout = Pango::Layout::create(context);
1442 int maxfont = 0;
1443 for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1444 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1445 int fontw, fonth;
1446 _vertical_layout->get_pixel_size(fontw, fonth);
1447 if(fonth > maxfont)
1448 maxfont = fonth;
1449 }
1451 return maxfont;
1452 }
1454 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1455 {
1456 return _signal_primitive_changed;
1457 }
1459 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1460 {
1461 _observer->set(get_selected());
1462 signal_primitive_changed()();
1463 _dialog._color_matrix_values->clear_store();
1464 }
1466 /* Add all filter primitives in the current to the list.
1467 Keeps the same selection if possible, otherwise selects the first element */
1468 void FilterEffectsDialog::PrimitiveList::update()
1469 {
1470 SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1471 const SPFilterPrimitive* active_prim = get_selected();
1472 bool active_found = false;
1474 _model->clear();
1476 if(f) {
1477 _dialog._primitive_box.set_sensitive(true);
1478 _dialog.update_filter_general_settings_view();
1479 for(SPObject *prim_obj = f->children;
1480 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1481 prim_obj = prim_obj->next) {
1482 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1483 if(prim) {
1484 Gtk::TreeModel::Row row = *_model->append();
1485 row[_columns.primitive] = prim;
1486 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1487 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1488 row[_columns.id] = prim->getId();
1490 if(prim == active_prim) {
1491 get_selection()->select(row);
1492 active_found = true;
1493 }
1494 }
1495 }
1497 if(!active_found && _model->children().begin())
1498 get_selection()->select(_model->children().begin());
1500 columns_autosize();
1501 }
1502 else {
1503 _dialog._primitive_box.set_sensitive(false);
1504 }
1505 }
1507 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1508 {
1509 _primitive_menu = menu;
1510 }
1512 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1513 {
1514 if(_dialog._filter_modifier.get_selected_filter()) {
1515 Gtk::TreeModel::iterator i = get_selection()->get_selected();
1516 if(i)
1517 return (*i)[_columns.primitive];
1518 }
1520 return 0;
1521 }
1523 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1524 {
1525 for(Gtk::TreeIter i = _model->children().begin();
1526 i != _model->children().end(); ++i) {
1527 if((*i)[_columns.primitive] == prim)
1528 get_selection()->select(i);
1529 }
1530 }
1532 void FilterEffectsDialog::PrimitiveList::remove_selected()
1533 {
1534 SPFilterPrimitive* prim = get_selected();
1536 if(prim) {
1537 _observer->set(0);
1539 sp_repr_unparent(prim->repr);
1541 sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1542 _("Remove filter primitive"));
1544 update();
1545 }
1546 }
1548 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1549 {
1550 Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1551 Glib::RefPtr<Gdk::Window> win = get_bin_window();
1552 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1554 SPFilterPrimitive* prim = get_selected();
1555 int row_count = get_model()->children().size();
1557 int fheight = CellRendererConnection::size;
1558 Gdk::Rectangle rct, vis;
1559 Gtk::TreeIter row = get_model()->children().begin();
1560 int text_start_x = 0;
1561 if(row) {
1562 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1563 get_visible_rect(vis);
1564 int vis_x, vis_y;
1565 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1567 text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter._length + 1) + 1;
1568 for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1569 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1570 const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1571 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());
1572 get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1573 get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1574 }
1575 }
1577 int row_index = 0;
1578 for(; row != get_model()->children().end(); ++row, ++row_index) {
1579 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1580 const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1582 // Check mouse state
1583 int mx, my;
1584 Gdk::ModifierType mask;
1585 get_bin_window()->get_pointer(mx, my, mask);
1587 // Outline the bottom of the connection area
1588 const int outline_x = x + fheight * (row_count - row_index);
1589 get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1591 // Side outline
1592 get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1594 std::vector<Gdk::Point> con_poly;
1595 int con_drag_y = 0;
1596 bool inside;
1597 const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1598 const int inputs = input_count(row_prim);
1600 if(SP_IS_FEMERGE(row_prim)) {
1601 for(int i = 0; i < inputs; ++i) {
1602 inside = do_connection_node(row, i, con_poly, mx, my);
1603 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1604 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1605 inside, con_poly);
1607 if(_in_drag == (i + 1))
1608 con_drag_y = con_poly[2].get_y();
1610 if(_in_drag != (i + 1) || row_prim != prim)
1611 draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1612 }
1613 }
1614 else {
1615 // Draw "in" shape
1616 inside = do_connection_node(row, 0, con_poly, mx, my);
1617 con_drag_y = con_poly[2].get_y();
1618 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1619 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1620 inside, con_poly);
1622 // Draw "in" connection
1623 if(_in_drag != 1 || row_prim != prim)
1624 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1626 if(inputs == 2) {
1627 // Draw "in2" shape
1628 inside = do_connection_node(row, 1, con_poly, mx, my);
1629 if(_in_drag == 2)
1630 con_drag_y = con_poly[2].get_y();
1631 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1632 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1633 inside, con_poly);
1634 // Draw "in2" connection
1635 if(_in_drag != 2 || row_prim != prim)
1636 draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1637 }
1638 }
1640 // Draw drag connection
1641 if(row_prim == prim && _in_drag) {
1642 get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1643 mx, con_drag_y);
1644 get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1645 }
1646 }
1648 return true;
1649 }
1651 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1652 const int text_start_x, const int x1, const int y1,
1653 const int row_count)
1654 {
1655 int src_id = 0;
1656 Gtk::TreeIter res = find_result(input, attr, src_id);
1657 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1658 Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1659 Glib::RefPtr<Gdk::GC> gc;
1661 const bool is_first = input == get_model()->children().begin();
1662 const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1663 const bool use_default = !res && !is_merge;
1665 if(res == input || (use_default && is_first)) {
1666 // Draw straight connection to a standard input
1667 // Draw a lighter line for an implicit connection to a standard input
1668 const int tw = _connection_cell.get_text_width();
1669 gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1670 gc = (use_default && is_first) ? lightgc : darkgc;
1671 get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1672 get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1673 }
1674 else {
1675 // Draw an 'L'-shaped connection to another filter primitive
1676 // If no connection is specified, draw a light connection to the previous primitive
1677 gc = use_default ? lightgc : darkgc;
1679 if(use_default) {
1680 res = input;
1681 --res;
1682 }
1684 if(res) {
1685 Gdk::Rectangle rct;
1687 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1688 const int fheight = CellRendererConnection::size;
1690 get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1691 const int row_index = find_index(res);
1692 const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1693 const int y2 = rct.get_y() + rct.get_height();
1695 // Draw a bevelled 'L'-shaped connection
1696 get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1697 get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1698 get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1699 }
1700 }
1701 }
1703 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1704 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1705 std::vector<Gdk::Point>& points,
1706 const int ix, const int iy)
1707 {
1708 Gdk::Rectangle rct;
1709 const int icnt = input_count((*row)[_columns.primitive]);
1711 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1712 const int fheight = CellRendererConnection::size;
1714 get_cell_area(_model->get_path(row), *get_column(1), rct);
1715 const float h = rct.get_height() / icnt;
1717 const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1718 const int con_w = (int)(fheight * 0.35f);
1719 const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1720 points.clear();
1721 points.push_back(Gdk::Point(x, con_y));
1722 points.push_back(Gdk::Point(x, con_y + con_w * 2));
1723 points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1725 return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1726 }
1728 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1729 const int attr, int& src_id)
1730 {
1731 SPFilterPrimitive* prim = (*start)[_columns.primitive];
1732 Gtk::TreeIter target = _model->children().end();
1733 int image = 0;
1735 if(SP_IS_FEMERGE(prim)) {
1736 int c = 0;
1737 bool found = false;
1738 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1739 if(c == attr && SP_IS_FEMERGENODE(o)) {
1740 image = SP_FEMERGENODE(o)->input;
1741 found = true;
1742 }
1743 }
1744 if(!found)
1745 return target;
1746 }
1747 else {
1748 if(attr == SP_ATTR_IN)
1749 image = prim->image_in;
1750 else if(attr == SP_ATTR_IN2) {
1751 if(SP_IS_FEBLEND(prim))
1752 image = SP_FEBLEND(prim)->in2;
1753 else if(SP_IS_FECOMPOSITE(prim))
1754 image = SP_FECOMPOSITE(prim)->in2;
1755 else if(SP_IS_FEDISPLACEMENTMAP(prim))
1756 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1757 else
1758 return target;
1759 }
1760 else
1761 return target;
1762 }
1764 if(image >= 0) {
1765 for(Gtk::TreeIter i = _model->children().begin();
1766 i != start; ++i) {
1767 if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1768 target = i;
1769 }
1770 return target;
1771 }
1772 else if(image < -1) {
1773 src_id = -(image + 2);
1774 return start;
1775 }
1777 return target;
1778 }
1780 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1781 {
1782 int i = 0;
1783 for(Gtk::TreeIter iter = _model->children().begin();
1784 iter != target; ++iter, ++i){};
1785 return i;
1786 }
1788 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1789 {
1790 Gtk::TreePath path;
1791 Gtk::TreeViewColumn* col;
1792 const int x = (int)e->x, y = (int)e->y;
1793 int cx, cy;
1795 _drag_prim = 0;
1797 if(get_path_at_pos(x, y, path, col, cx, cy)) {
1798 Gtk::TreeIter iter = _model->get_iter(path);
1799 std::vector<Gdk::Point> points;
1801 _drag_prim = (*iter)[_columns.primitive];
1802 const int icnt = input_count(_drag_prim);
1804 for(int i = 0; i < icnt; ++i) {
1805 if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1806 _in_drag = i + 1;
1807 break;
1808 }
1809 }
1811 queue_draw();
1812 }
1814 if(_in_drag) {
1815 _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1816 _autoscroll = 0;
1817 get_selection()->select(path);
1818 return true;
1819 }
1820 else
1821 return Gtk::TreeView::on_button_press_event(e);
1822 }
1824 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1825 {
1826 const int speed = 10;
1827 const int limit = 15;
1829 Gdk::Rectangle vis;
1830 get_visible_rect(vis);
1831 int vis_x, vis_y;
1832 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1833 const int top = vis_y + vis.get_height();
1835 // When autoscrolling during a connection drag, set the speed based on
1836 // where the mouse is in relation to the edges.
1837 if(e->y < vis_y)
1838 _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1839 else if(e->y < vis_y + limit)
1840 _autoscroll = -speed;
1841 else if(e->y > top)
1842 _autoscroll = (int)(speed + (e->y - top) / 5);
1843 else if(e->y > top - limit)
1844 _autoscroll = speed;
1845 else
1846 _autoscroll = 0;
1848 queue_draw();
1850 return Gtk::TreeView::on_motion_notify_event(e);
1851 }
1853 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1854 {
1855 SPFilterPrimitive *prim = get_selected(), *target;
1857 _scroll_connection.disconnect();
1859 if(_in_drag && prim) {
1860 Gtk::TreePath path;
1861 Gtk::TreeViewColumn* col;
1862 int cx, cy;
1864 if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1865 const gchar *in_val = 0;
1866 Glib::ustring result;
1867 Gtk::TreeIter target_iter = _model->get_iter(path);
1868 target = (*target_iter)[_columns.primitive];
1869 col = get_column(1);
1871 Gdk::Rectangle rct;
1872 get_cell_area(path, *col, rct);
1873 const int twidth = _connection_cell.get_text_width();
1874 const int sources_x = rct.get_width() - twidth * FPInputConverter._length;
1875 if(cx > sources_x) {
1876 int src = (cx - sources_x) / twidth;
1877 if (src < 0) {
1878 src = 0;
1879 } else if(src >= static_cast<int>(FPInputConverter._length)) {
1880 src = FPInputConverter._length - 1;
1881 }
1882 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1883 in_val = result.c_str();
1884 }
1885 else {
1886 // Ensure that the target comes before the selected primitive
1887 for(Gtk::TreeIter iter = _model->children().begin();
1888 iter != get_selection()->get_selected(); ++iter) {
1889 if(iter == target_iter) {
1890 Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1891 // Make sure the target has a result
1892 const gchar *gres = repr->attribute("result");
1893 if(!gres) {
1894 result = sp_filter_get_new_result_name(SP_FILTER(prim->parent));
1895 repr->setAttribute("result", result.c_str());
1896 in_val = result.c_str();
1897 }
1898 else
1899 in_val = gres;
1900 break;
1901 }
1902 }
1903 }
1905 if(SP_IS_FEMERGE(prim)) {
1906 int c = 1;
1907 bool handled = false;
1908 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1909 if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1910 // If input is null, delete it
1911 if(!in_val) {
1912 sp_repr_unparent(o->repr);
1913 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1914 _("Remove merge node"));
1915 (*get_selection()->get_selected())[_columns.primitive] = prim;
1916 }
1917 else
1918 _dialog.set_attr(o, SP_ATTR_IN, in_val);
1919 handled = true;
1920 }
1921 }
1922 // Add new input?
1923 if(!handled && c == _in_drag && in_val) {
1924 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1925 Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1926 repr->setAttribute("inkscape:collect", "always");
1927 prim->repr->appendChild(repr);
1928 SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1929 Inkscape::GC::release(repr);
1930 _dialog.set_attr(node, SP_ATTR_IN, in_val);
1931 (*get_selection()->get_selected())[_columns.primitive] = prim;
1932 }
1933 }
1934 else {
1935 if(_in_drag == 1)
1936 _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1937 else if(_in_drag == 2)
1938 _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1939 }
1940 }
1942 _in_drag = 0;
1943 queue_draw();
1945 _dialog.update_settings_view();
1946 }
1948 if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1949 const bool sensitive = get_selected() != NULL;
1950 _primitive_menu->items()[0].set_sensitive(sensitive);
1951 _primitive_menu->items()[1].set_sensitive(sensitive);
1952 _primitive_menu->popup(e->button, e->time);
1954 return true;
1955 }
1956 else
1957 return Gtk::TreeView::on_button_release_event(e);
1958 }
1960 // Checks all of prim's inputs, removes any that use result
1961 void check_single_connection(SPFilterPrimitive* prim, const int result)
1962 {
1963 if(prim && result >= 0) {
1965 if(prim->image_in == result)
1966 SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1968 if(SP_IS_FEBLEND(prim)) {
1969 if(SP_FEBLEND(prim)->in2 == result)
1970 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1971 }
1972 else if(SP_IS_FECOMPOSITE(prim)) {
1973 if(SP_FECOMPOSITE(prim)->in2 == result)
1974 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1975 }
1976 else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
1977 if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
1978 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1979 }
1980 }
1981 }
1983 // Remove any connections going to/from prim_iter that forward-reference other primitives
1984 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
1985 {
1986 SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
1987 bool before = true;
1989 for(Gtk::TreeIter iter = _model->children().begin();
1990 iter != _model->children().end(); ++iter) {
1991 if(iter == prim_iter)
1992 before = false;
1993 else {
1994 SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
1995 if(before)
1996 check_single_connection(cur_prim, prim->image_out);
1997 else
1998 check_single_connection(prim, cur_prim->image_out);
1999 }
2000 }
2001 }
2003 // Reorder the filter primitives to match the list order
2004 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2005 {
2006 SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2007 int ndx = 0;
2009 for(Gtk::TreeModel::iterator iter = _model->children().begin();
2010 iter != _model->children().end(); ++iter, ++ndx) {
2011 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2012 if(prim && prim == _drag_prim) {
2013 SP_OBJECT_REPR(prim)->setPosition(ndx);
2014 break;
2015 }
2016 }
2018 for(Gtk::TreeModel::iterator iter = _model->children().begin();
2019 iter != _model->children().end(); ++iter, ++ndx) {
2020 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2021 if(prim && prim == _drag_prim) {
2022 sanitize_connections(iter);
2023 get_selection()->select(iter);
2024 break;
2025 }
2026 }
2028 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2030 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2031 }
2033 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2034 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2035 {
2036 if(_autoscroll) {
2037 Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2038 double v;
2040 v = a.get_value() + _autoscroll;
2041 if(v < 0)
2042 v = 0;
2043 if(v > a.get_upper() - a.get_page_size())
2044 v = a.get_upper() - a.get_page_size();
2046 a.set_value(v);
2048 queue_draw();
2049 }
2051 return true;
2052 }
2054 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2055 {
2056 return _model->children().size();
2057 }
2059 /*** FilterEffectsDialog ***/
2061 FilterEffectsDialog::FilterEffectsDialog()
2062 : UI::Widget::Panel("", "/dialogs/filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2063 _add_primitive_type(FPConverter),
2064 _add_primitive(_("Add Effect:")),
2065 _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2066 _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2067 _settings_initialized(false),
2068 _locked(false),
2069 _attr_lock(false),
2070 _filter_modifier(*this),
2071 _primitive_list(*this)
2072 {
2073 _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2074 NR_FILTER_ENDPRIMITIVETYPE);
2075 _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2076 1);
2077 _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2078 _sizegroup->set_ignore_hidden();
2080 _add_primitive_type.remove_row(NR_FILTER_TILE);
2081 _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2083 // Initialize widget hierarchy
2084 Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2085 Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2086 Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox(/*homogeneous:*/false, /*spacing:*/4));
2087 Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2089 _getContents()->add(*hpaned);
2090 hpaned->pack1(_filter_modifier);
2091 hpaned->pack2(_primitive_box);
2092 _primitive_box.pack_start(*sw_prims);
2093 _primitive_box.pack_start(*hb_prims, false, false);
2094 _primitive_box.pack_start(*infobox,false, false);
2095 sw_prims->add(_primitive_list);
2096 infobox->pack_start(_infobox_icon, false, false);
2097 infobox->pack_start(_infobox_desc, false, false);
2098 _infobox_desc.set_line_wrap(true);
2099 _infobox_desc.set_size_request(200, -1);
2101 hb_prims->pack_start(_add_primitive, false, false);
2102 hb_prims->pack_start(_add_primitive_type, false, false);
2103 _getContents()->pack_start(_settings_tabs, false, false);
2104 _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2105 _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2107 _primitive_list.signal_primitive_changed().connect(
2108 sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2109 _filter_modifier.signal_filter_changed().connect(
2110 sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2112 _add_primitive_type.signal_changed().connect(
2113 sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2115 sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2116 sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2117 // al_settings->set_padding(0, 0, 12, 0);
2118 // fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2119 // ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2120 _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2121 _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2122 sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2124 show_all_children();
2125 init_settings_widgets();
2126 _primitive_list.update();
2127 update_primitive_infobox();
2128 }
2130 FilterEffectsDialog::~FilterEffectsDialog()
2131 {
2132 delete _settings;
2133 delete _filter_general_settings;
2134 }
2136 void FilterEffectsDialog::set_attrs_locked(const bool l)
2137 {
2138 _locked = l;
2139 }
2141 void FilterEffectsDialog::show_all_vfunc()
2142 {
2143 UI::Widget::Panel::show_all_vfunc();
2145 update_settings_view();
2146 }
2148 void FilterEffectsDialog::init_settings_widgets()
2149 {
2150 // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2151 // most of the current values are complete guesses!
2153 _empty_settings.set_sensitive(false);
2154 _settings_tab1.pack_start(_empty_settings);
2156 _no_filter_selected.set_sensitive(false);
2157 _settings_tab2.pack_start(_no_filter_selected);
2158 _settings_initialized = true;
2160 _filter_general_settings->type(0);
2161 _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"));
2162 _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"));
2164 _settings->type(NR_FILTER_BLEND);
2165 _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode:"), BlendModeConverter);
2167 _settings->type(NR_FILTER_COLORMATRIX);
2168 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."));
2169 _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s):"));
2170 colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2172 _settings->type(NR_FILTER_COMPONENTTRANSFER);
2173 _settings->add_notimplemented();
2174 //TRANSLATORS: for info on "Slope" and "Intercept", see http://id.mind.net/~zona/mmts/functionInstitute/linearFunctions/lsif.html
2175 /*_settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2176 _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2177 _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2178 _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2179 _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2180 _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2182 _settings->type(NR_FILTER_COMPOSITE);
2183 _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator:"), CompositeOperatorConverter);
2184 _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."));
2185 _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."));
2186 _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."));
2187 _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."));
2189 _settings->type(NR_FILTER_CONVOLVEMATRIX);
2190 _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"));
2191 _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."));
2192 //TRANSLATORS: for info on "Kernel", see http://en.wikipedia.org/wiki/Kernel_(matrix)
2193 _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."));
2194 _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2195 _settings->add_spinslider(0, SP_ATTR_DIVISOR, _("Divisor:"), 0, 1000, 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."));
2196 _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."));
2197 _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."));
2198 _settings->add_checkbutton(false, SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive."));
2200 _settings->type(NR_FILTER_DIFFUSELIGHTING);
2201 _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Diffuse Color:"), _("Defines the color of the light source"));
2202 _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"));
2203 _settings->add_spinslider(1, SP_ATTR_DIFFUSECONSTANT, _("Constant:"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2204 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2205 _settings->add_lightsource();
2207 _settings->type(NR_FILTER_DISPLACEMENTMAP);
2208 _settings->add_spinslider(0, SP_ATTR_SCALE, _("Scale:"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2209 _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2210 _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2212 _settings->type(NR_FILTER_FLOOD);
2213 _settings->add_color(/*default: black*/ 0, SP_PROP_FLOOD_COLOR, _("Flood Color:"), _("The whole filter region will be filled with this color."));
2214 _settings->add_spinslider(1, SP_PROP_FLOOD_OPACITY, _("Opacity:"), 0, 1, 0.1, 0.01, 2);
2216 _settings->type(NR_FILTER_GAUSSIANBLUR);
2217 _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation:"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2219 _settings->type(NR_FILTER_MERGE);
2220 _settings->add_no_params();
2222 _settings->type(NR_FILTER_MORPHOLOGY);
2223 _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator:"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2224 _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius:"), 0, 100, 1, 0.01, 1);
2226 _settings->type(NR_FILTER_IMAGE);
2227 _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image:"));
2229 _settings->type(NR_FILTER_OFFSET);
2230 _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"));
2231 _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"));
2233 _settings->type(NR_FILTER_SPECULARLIGHTING);
2234 _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Specular Color:"), _("Defines the color of the light source"));
2235 _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"));
2236 _settings->add_spinslider(1, SP_ATTR_SPECULARCONSTANT, _("Constant:"), 0, 100, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2237 _settings->add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Exponent:"), 1, 128, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2238 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2239 _settings->add_lightsource();
2241 _settings->type(NR_FILTER_TILE);
2242 _settings->add_notimplemented();
2244 _settings->type(NR_FILTER_TURBULENCE);
2245 // _settings->add_checkbutton(false, SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2246 _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type:"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2247 _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency:"), 0, 0.4, 0.001, 0.01, 3);
2248 _settings->add_spinslider(1, SP_ATTR_NUMOCTAVES, _("Octaves:"), 1, 10, 1, 1, 0);
2249 _settings->add_spinslider(0, SP_ATTR_SEED, _("Seed:"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2250 }
2252 void FilterEffectsDialog::add_primitive()
2253 {
2254 SPFilter* filter = _filter_modifier.get_selected_filter();
2256 if(filter) {
2257 SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2259 _primitive_list.select(prim);
2261 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2262 }
2263 }
2265 void FilterEffectsDialog::update_primitive_infobox()
2266 {
2267 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2268 if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2269 _infobox_icon.show();
2270 _infobox_desc.show();
2271 } else {
2272 _infobox_icon.hide();
2273 _infobox_desc.hide();
2274 }
2275 switch(_add_primitive_type.get_active_data()->id){
2276 case(NR_FILTER_BLEND):
2277 _infobox_icon.set_from_icon_name("feBlend-icon", Gtk::ICON_SIZE_DIALOG);
2278 _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2279 break;
2280 case(NR_FILTER_COLORMATRIX):
2281 _infobox_icon.set_from_icon_name("feColorMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2282 _infobox_desc.set_markup(_("The <b>feColorMatrix</b> filter primitive applies a matrix transformation to color of each rendered pixel. This allows for effects like turning object to grayscale, modifying color saturation and changing color hue."));
2283 break;
2284 case(NR_FILTER_COMPONENTTRANSFER):
2285 _infobox_icon.set_from_icon_name("feComponentTransfer-icon", Gtk::ICON_SIZE_DIALOG);
2286 _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."));
2287 break;
2288 case(NR_FILTER_COMPOSITE):
2289 _infobox_icon.set_from_icon_name("feComposite-icon", Gtk::ICON_SIZE_DIALOG);
2290 _infobox_desc.set_markup(_("The <b>feComposite</b> filter primitive composites two images using one of the Porter-Duff blending modes or the arithmetic mode described in SVG standard. Porter-Duff blending modes are essentially logical operations between the corresponding pixel values of the images."));
2291 break;
2292 case(NR_FILTER_CONVOLVEMATRIX):
2293 _infobox_icon.set_from_icon_name("feConvolveMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2294 _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."));
2295 break;
2296 case(NR_FILTER_DIFFUSELIGHTING):
2297 _infobox_icon.set_from_icon_name("feDiffuseLighting-icon", Gtk::ICON_SIZE_DIALOG);
2298 _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."));
2299 break;
2300 case(NR_FILTER_DISPLACEMENTMAP):
2301 _infobox_icon.set_from_icon_name("feDisplacementMap-icon", Gtk::ICON_SIZE_DIALOG);
2302 _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."));
2303 break;
2304 case(NR_FILTER_FLOOD):
2305 _infobox_icon.set_from_icon_name("feFlood-icon", Gtk::ICON_SIZE_DIALOG);
2306 _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."));
2307 break;
2308 case(NR_FILTER_GAUSSIANBLUR):
2309 _infobox_icon.set_from_icon_name("feGaussianBlur-icon", Gtk::ICON_SIZE_DIALOG);
2310 _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."));
2311 break;
2312 case(NR_FILTER_IMAGE):
2313 _infobox_icon.set_from_icon_name("feImage-icon", Gtk::ICON_SIZE_DIALOG);
2314 _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2315 break;
2316 case(NR_FILTER_MERGE):
2317 _infobox_icon.set_from_icon_name("feMerge-icon", Gtk::ICON_SIZE_DIALOG);
2318 _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."));
2319 break;
2320 case(NR_FILTER_MORPHOLOGY):
2321 _infobox_icon.set_from_icon_name("feMorphology-icon", Gtk::ICON_SIZE_DIALOG);
2322 _infobox_desc.set_markup(_("The <b>feMorphology</b> filter primitive provides erode and dilate effects. For single-color objects erode makes the object thinner and dilate makes it thicker."));
2323 break;
2324 case(NR_FILTER_OFFSET):
2325 _infobox_icon.set_from_icon_name("feOffset-icon", Gtk::ICON_SIZE_DIALOG);
2326 _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."));
2327 break;
2328 case(NR_FILTER_SPECULARLIGHTING):
2329 _infobox_icon.set_from_icon_name("feSpecularLighting-icon", Gtk::ICON_SIZE_DIALOG);
2330 _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."));
2331 break;
2332 case(NR_FILTER_TILE):
2333 _infobox_icon.set_from_icon_name("feTile-icon", Gtk::ICON_SIZE_DIALOG);
2334 _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2335 break;
2336 case(NR_FILTER_TURBULENCE):
2337 _infobox_icon.set_from_icon_name("feTurbulence-icon", Gtk::ICON_SIZE_DIALOG);
2338 _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."));
2339 break;
2340 default:
2341 g_assert(false);
2342 break;
2343 }
2344 _infobox_icon.set_pixel_size(96);
2345 }
2347 void FilterEffectsDialog::duplicate_primitive()
2348 {
2349 SPFilter* filter = _filter_modifier.get_selected_filter();
2350 SPFilterPrimitive* origprim = _primitive_list.get_selected();
2352 if(filter && origprim) {
2353 Inkscape::XML::Node *repr;
2354 repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2355 SP_OBJECT_REPR(filter)->appendChild(repr);
2357 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2359 _primitive_list.update();
2360 }
2361 }
2363 void FilterEffectsDialog::convolve_order_changed()
2364 {
2365 _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2366 _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2367 _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2368 }
2370 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2371 {
2372 set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2373 }
2375 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2376 {
2377 if(!_locked) {
2378 _attr_lock = true;
2379 SPFilter *filter = _filter_modifier.get_selected_filter();
2380 const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2381 if (filter && name && SP_OBJECT_REPR(filter)){
2382 SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2383 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2384 }
2385 _attr_lock = false;
2386 }
2387 }
2389 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2390 {
2391 set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2392 }
2394 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2395 {
2396 if(!_locked) {
2397 _attr_lock = true;
2399 SPFilter *filter = _filter_modifier.get_selected_filter();
2400 const gchar* name = (const gchar*)sp_attribute_name(attr);
2401 if(filter && name && o) {
2402 update_settings_sensitivity();
2404 SP_OBJECT_REPR(o)->setAttribute(name, val);
2405 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2407 Glib::ustring undokey = "filtereffects:";
2408 undokey += name;
2409 sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2410 _("Set filter primitive attribute"));
2411 }
2413 _attr_lock = false;
2414 }
2415 }
2417 void FilterEffectsDialog::update_filter_general_settings_view()
2418 {
2419 if(_settings_initialized != true) return;
2421 if(!_locked) {
2422 _attr_lock = true;
2424 SPFilter* filter = _filter_modifier.get_selected_filter();
2426 if(filter) {
2427 _filter_general_settings->show_and_update(0, filter);
2428 _no_filter_selected.hide();
2429 }
2430 else {
2431 std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2432 vect[0]->hide_all();
2433 _no_filter_selected.show();
2434 }
2436 _attr_lock = false;
2437 }
2438 }
2440 void FilterEffectsDialog::update_settings_view()
2441 {
2442 update_settings_sensitivity();
2444 if(_attr_lock)
2445 return;
2447 //First Tab
2449 std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2450 for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2451 _empty_settings.show();
2453 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2454 if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2455 _infobox_icon.show();
2456 _infobox_desc.show();
2457 } else {
2458 _infobox_icon.hide();
2459 _infobox_desc.hide();
2460 }
2462 SPFilterPrimitive* prim = _primitive_list.get_selected();
2464 if(prim) {
2465 _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2466 _empty_settings.hide();
2467 }
2469 //Second Tab
2471 std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2472 vect2[0]->hide_all();
2473 _no_filter_selected.show();
2475 SPFilter* filter = _filter_modifier.get_selected_filter();
2477 if(filter) {
2478 _filter_general_settings->show_and_update(0, filter);
2479 _no_filter_selected.hide();
2480 }
2482 }
2484 void FilterEffectsDialog::update_settings_sensitivity()
2485 {
2486 SPFilterPrimitive* prim = _primitive_list.get_selected();
2487 const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2488 _k1->set_sensitive(use_k);
2489 _k2->set_sensitive(use_k);
2490 _k3->set_sensitive(use_k);
2491 _k4->set_sensitive(use_k);
2493 // Component transfer not yet implemented
2494 /*
2495 if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2496 SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2497 const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2498 const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2500 _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2501 _ct_slope->set_sensitive(linear);
2502 _ct_intercept->set_sensitive(linear);
2503 _ct_amplitude->set_sensitive(gamma);
2504 _ct_exponent->set_sensitive(gamma);
2505 _ct_offset->set_sensitive(gamma);
2506 }
2507 */
2508 }
2510 void FilterEffectsDialog::update_color_matrix()
2511 {
2512 _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2513 }
2515 } // namespace Dialog
2516 } // namespace UI
2517 } // namespace Inkscape
2519 /*
2520 Local Variables:
2521 mode:c++
2522 c-file-style:"stroustrup"
2523 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2524 indent-tabs-mode:nil
2525 fill-column:99
2526 End:
2527 */
2528 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :