ed7103be3c801a91fc4628d271e403d0e13c38b1
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 * Jon A. Cruz <jon@joncruz.org>
9 * Abhishek Sharma
10 *
11 * Copyright (C) 2007 Authors
12 *
13 * Released under GNU GPL. Read the file 'COPYING' for more information.
14 */
16 #ifdef HAVE_CONFIG_H
17 # include <config.h>
18 #endif
20 #include <gtk/gtk.h>
21 #include <gtkmm/cellrenderertext.h>
22 #include <gtkmm/colorbutton.h>
23 #include <gtkmm/messagedialog.h>
24 #include <gtkmm/paned.h>
25 #include <gtkmm/scale.h>
26 #include <gtkmm/scrolledwindow.h>
27 #include <gtkmm/spinbutton.h>
28 #include <gtkmm/stock.h>
29 #include <gtkmm/tooltips.h>
30 #include <glibmm/i18n.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 using Inkscape::UI::Widget::AttrWidget;
78 using Inkscape::UI::Widget::ComboBoxEnum;
79 using Inkscape::UI::Widget::DualSpinSlider;
80 using Inkscape::UI::Widget::SpinSlider;
83 // Returns the number of inputs available for the filter primitive type
84 int input_count(const SPFilterPrimitive* prim)
85 {
86 if(!prim)
87 return 0;
88 else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
89 return 2;
90 else if(SP_IS_FEMERGE(prim)) {
91 // Return the number of feMergeNode connections plus an extra
92 int count = 1;
93 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count){};
94 return count;
95 }
96 else
97 return 1;
98 }
100 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
101 {
102 public:
103 CheckButtonAttr(bool def, const Glib::ustring& label,
104 const Glib::ustring& tv, const Glib::ustring& fv,
105 const SPAttributeEnum a, char* tip_text)
106 : Gtk::CheckButton(label),
107 AttrWidget(a, def),
108 _true_val(tv), _false_val(fv)
109 {
110 signal_toggled().connect(signal_attr_changed().make_slot());
111 if (tip_text) _tt.set_tip(*this, tip_text);
112 }
114 Glib::ustring get_as_attribute() const
115 {
116 return get_active() ? _true_val : _false_val;
117 }
119 void set_from_attribute(SPObject* o)
120 {
121 const gchar* val = attribute_value(o);
122 if(val) {
123 if(_true_val == val)
124 set_active(true);
125 else if(_false_val == val)
126 set_active(false);
127 } else {
128 set_active(get_default()->as_bool());
129 }
130 }
131 private:
132 const Glib::ustring _true_val, _false_val;
133 };
135 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
136 {
137 public:
138 SpinButtonAttr(double lower, double upper, double step_inc,
139 double climb_rate, int digits, const SPAttributeEnum a, double def, char* tip_text)
140 : Gtk::SpinButton(climb_rate, digits),
141 AttrWidget(a, def)
142 {
143 if (tip_text) _tt.set_tip(*this, tip_text);
144 set_range(lower, upper);
145 set_increments(step_inc, 0);
147 signal_value_changed().connect(signal_attr_changed().make_slot());
148 }
150 Glib::ustring get_as_attribute() const
151 {
152 const double val = get_value();
154 if(get_digits() == 0)
155 return Glib::Ascii::dtostr((int)val);
156 else
157 return Glib::Ascii::dtostr(val);
158 }
160 void set_from_attribute(SPObject* o)
161 {
162 const gchar* val = attribute_value(o);
163 if(val){
164 set_value(Glib::Ascii::strtod(val));
165 } else {
166 set_value(get_default()->as_double());
167 }
168 }
169 };
171 template< typename T> class ComboWithTooltip : public Gtk::EventBox
172 {
173 public:
174 ComboWithTooltip<T>(T default_value, const Util::EnumDataConverter<T>& c, const SPAttributeEnum a = SP_ATTR_INVALID, char* tip_text = NULL)
175 {
176 if (tip_text) {
177 _tt.set_tip(*this, tip_text);
178 }
179 combo = new ComboBoxEnum<T>(default_value, c, a, false);
180 add(*combo);
181 show_all();
182 }
184 ~ComboWithTooltip()
185 {
186 delete combo;
187 }
189 ComboBoxEnum<T>* get_attrwidget()
190 {
191 return combo;
192 }
193 private:
194 Gtk::Tooltips _tt;
195 ComboBoxEnum<T>* combo;
196 };
198 // Contains an arbitrary number of spin buttons that use seperate attributes
199 class MultiSpinButton : public Gtk::HBox
200 {
201 public:
202 MultiSpinButton(double lower, double upper, double step_inc,
203 double climb_rate, int digits, std::vector<SPAttributeEnum> attrs, std::vector<double> default_values, std::vector<char*> tip_text)
204 {
205 g_assert(attrs.size()==default_values.size());
206 g_assert(attrs.size()==tip_text.size());
207 for(unsigned i = 0; i < attrs.size(); ++i) {
208 _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i], default_values[i], tip_text[i]));
209 pack_start(*_spins.back(), false, false);
210 }
211 }
213 ~MultiSpinButton()
214 {
215 for(unsigned i = 0; i < _spins.size(); ++i)
216 delete _spins[i];
217 }
219 std::vector<SpinButtonAttr*>& get_spinbuttons()
220 {
221 return _spins;
222 }
223 private:
224 std::vector<SpinButtonAttr*> _spins;
225 };
227 // Contains two spinbuttons that describe a NumberOptNumber
228 class DualSpinButton : public Gtk::HBox, public AttrWidget
229 {
230 public:
231 DualSpinButton(char* def, double lower, double upper, double step_inc,
232 double climb_rate, int digits, const SPAttributeEnum a, char* tt1, char* tt2)
233 : AttrWidget(a, def), //TO-DO: receive default num-opt-num as parameter in the constructor
234 _s1(climb_rate, digits), _s2(climb_rate, digits)
235 {
236 if (tt1) _tt.set_tip(_s1, tt1);
237 if (tt2) _tt.set_tip(_s2, tt2);
238 _s1.set_range(lower, upper);
239 _s2.set_range(lower, upper);
240 _s1.set_increments(step_inc, 0);
241 _s2.set_increments(step_inc, 0);
243 _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
244 _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
246 pack_start(_s1, false, false);
247 pack_start(_s2, false, false);
248 }
250 Gtk::SpinButton& get_spinbutton1()
251 {
252 return _s1;
253 }
255 Gtk::SpinButton& get_spinbutton2()
256 {
257 return _s2;
258 }
260 virtual Glib::ustring get_as_attribute() const
261 {
262 double v1 = _s1.get_value();
263 double v2 = _s2.get_value();
265 if(_s1.get_digits() == 0) {
266 v1 = (int)v1;
267 v2 = (int)v2;
268 }
270 return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
271 }
273 virtual void set_from_attribute(SPObject* o)
274 {
275 const gchar* val = attribute_value(o);
276 NumberOptNumber n;
277 if(val) {
278 n.set(val);
279 } else {
280 n.set(get_default()->as_charptr());
281 }
282 _s1.set_value(n.getNumber());
283 _s2.set_value(n.getOptNumber());
285 }
286 private:
287 Gtk::SpinButton _s1, _s2;
288 };
290 class ColorButton : public Gtk::ColorButton, public AttrWidget
291 {
292 public:
293 ColorButton(unsigned int def, const SPAttributeEnum a, char* tip_text)
294 : AttrWidget(a, def)
295 {
296 signal_color_set().connect(signal_attr_changed().make_slot());
297 if (tip_text) _tt.set_tip(*this, tip_text);
299 Gdk::Color col;
300 col.set_rgb(65535, 65535, 65535);
301 set_color(col);
302 }
304 // Returns the color in 'rgb(r,g,b)' form.
305 Glib::ustring get_as_attribute() const
306 {
307 std::ostringstream os;
308 const Gdk::Color c = get_color();
309 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?
310 os << "rgb(" << r << "," << g << "," << b << ")";
311 return os.str();
312 }
315 void set_from_attribute(SPObject* o)
316 {
317 const gchar* val = attribute_value(o);
318 guint32 i = 0;
319 if(val) {
320 i = sp_svg_read_color(val, 0xFFFFFFFF);
321 } else {
322 i = (guint32) get_default()->as_uint();
323 }
324 const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
325 Gdk::Color col;
326 col.set_rgb(r * 256, g * 256, b * 256);
327 set_color(col);
328 }
329 };
331 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
332 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
333 {
334 public:
335 MatrixAttr(const SPAttributeEnum a, char* tip_text = NULL)
336 : AttrWidget(a), _locked(false)
337 {
338 _model = Gtk::ListStore::create(_columns);
339 _tree.set_model(_model);
340 _tree.set_headers_visible(false);
341 _tree.show();
342 add(_tree);
343 set_shadow_type(Gtk::SHADOW_IN);
344 if (tip_text) _tt.set_tip(_tree, tip_text);
345 }
347 std::vector<double> get_values() const
348 {
349 std::vector<double> vec;
350 for(Gtk::TreeIter iter = _model->children().begin();
351 iter != _model->children().end(); ++iter) {
352 for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
353 vec.push_back((*iter)[_columns.cols[c]]);
354 }
355 return vec;
356 }
358 void set_values(const std::vector<double>& v)
359 {
360 unsigned i = 0;
361 for(Gtk::TreeIter iter = _model->children().begin();
362 iter != _model->children().end(); ++iter) {
363 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
364 if(i >= v.size())
365 return;
366 (*iter)[_columns.cols[c]] = v[i];
367 ++i;
368 }
369 }
370 }
372 Glib::ustring get_as_attribute() const
373 {
374 std::ostringstream os;
376 for(Gtk::TreeIter iter = _model->children().begin();
377 iter != _model->children().end(); ++iter) {
378 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
379 os << (*iter)[_columns.cols[c]] << " ";
380 }
381 }
383 return os.str();
384 }
386 void set_from_attribute(SPObject* o)
387 {
388 if(o) {
389 if(SP_IS_FECONVOLVEMATRIX(o)) {
390 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
391 int cols, rows;
392 cols = (int)conv->order.getNumber();
393 if(cols > 5)
394 cols = 5;
395 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
396 update(o, rows, cols);
397 }
398 else if(SP_IS_FECOLORMATRIX(o))
399 update(o, 4, 5);
400 }
401 }
402 private:
403 class MatrixColumns : public Gtk::TreeModel::ColumnRecord
404 {
405 public:
406 MatrixColumns()
407 {
408 cols.resize(5);
409 for(unsigned i = 0; i < cols.size(); ++i)
410 add(cols[i]);
411 }
412 std::vector<Gtk::TreeModelColumn<double> > cols;
413 };
415 void update(SPObject* o, const int rows, const int cols)
416 {
417 if(_locked)
418 return;
420 _model->clear();
422 _tree.remove_all_columns();
424 std::vector<gdouble>* values = NULL;
425 if(SP_IS_FECOLORMATRIX(o))
426 values = &SP_FECOLORMATRIX(o)->values;
427 else if(SP_IS_FECONVOLVEMATRIX(o))
428 values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
429 else
430 return;
432 if(o) {
433 int ndx = 0;
435 for(int i = 0; i < cols; ++i) {
436 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
437 dynamic_cast<Gtk::CellRendererText*>(
438 _tree.get_column_cell_renderer(i))->signal_edited().connect(
439 sigc::mem_fun(*this, &MatrixAttr::rebind));
440 }
442 for(int r = 0; r < rows; ++r) {
443 Gtk::TreeRow row = *(_model->append());
444 // Default to identity matrix
445 for(int c = 0; c < cols; ++c, ++ndx)
446 row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
447 }
448 }
449 }
451 void rebind(const Glib::ustring&, const Glib::ustring&)
452 {
453 _locked = true;
454 signal_attr_changed()();
455 _locked = false;
456 }
458 bool _locked;
459 Gtk::TreeView _tree;
460 Glib::RefPtr<Gtk::ListStore> _model;
461 MatrixColumns _columns;
462 };
464 // Displays a matrix or a slider for feColorMatrix
465 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
466 {
467 public:
468 ColorMatrixValues()
469 : AttrWidget(SP_ATTR_VALUES),
470 // TRANSLATORS: this dialog is accessible via menu Filters - Filter editor
471 _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.")),
472 _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
473 _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
474 _label(_("None"), Gtk::ALIGN_LEFT),
475 _use_stored(false),
476 _saturation_store(0),
477 _angle_store(0)
478 {
479 _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
480 _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
481 _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
482 signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
484 _matrix.show();
485 _saturation.show();
486 _angle.show();
487 _label.show();
488 _label.set_sensitive(false);
490 set_shadow_type(Gtk::SHADOW_NONE);
491 }
493 virtual void set_from_attribute(SPObject* o)
494 {
495 if(SP_IS_FECOLORMATRIX(o)) {
496 SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
497 remove();
498 switch(col->type) {
499 case COLORMATRIX_SATURATE:
500 add(_saturation);
501 if(_use_stored)
502 _saturation.set_value(_saturation_store);
503 else
504 _saturation.set_from_attribute(o);
505 break;
506 case COLORMATRIX_HUEROTATE:
507 add(_angle);
508 if(_use_stored)
509 _angle.set_value(_angle_store);
510 else
511 _angle.set_from_attribute(o);
512 break;
513 case COLORMATRIX_LUMINANCETOALPHA:
514 add(_label);
515 break;
516 case COLORMATRIX_MATRIX:
517 default:
518 add(_matrix);
519 if(_use_stored)
520 _matrix.set_values(_matrix_store);
521 else
522 _matrix.set_from_attribute(o);
523 break;
524 }
525 _use_stored = true;
526 }
527 }
529 virtual Glib::ustring get_as_attribute() const
530 {
531 const Widget* w = get_child();
532 if(w == &_label)
533 return "";
534 else
535 return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
536 }
538 void clear_store()
539 {
540 _use_stored = false;
541 }
542 private:
543 void update_store()
544 {
545 const Widget* w = get_child();
546 if(w == &_matrix)
547 _matrix_store = _matrix.get_values();
548 else if(w == &_saturation)
549 _saturation_store = _saturation.get_value();
550 else if(w == &_angle)
551 _angle_store = _angle.get_value();
552 }
554 MatrixAttr _matrix;
555 SpinSlider _saturation;
556 SpinSlider _angle;
557 Gtk::Label _label;
559 // Store separate values for the different color modes
560 bool _use_stored;
561 std::vector<double> _matrix_store;
562 double _saturation_store;
563 double _angle_store;
564 };
566 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
568 //Displays a chooser for feImage input
569 //It may be a filename or the id for an SVG Element
570 //described in xlink:href syntax
571 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
572 {
573 public:
574 FileOrElementChooser(const SPAttributeEnum a)
575 : AttrWidget(a)
576 {
577 pack_start(_entry, false, false);
578 pack_start(_fromFile, false, false);
579 pack_start(_fromSVGElement, false, false);
581 _fromFile.set_label(_("Image File"));
582 _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
584 _fromSVGElement.set_label(_("Selected SVG Element"));
585 _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
587 _entry.signal_changed().connect(signal_attr_changed().make_slot());
589 show_all();
591 }
593 // Returns the element in xlink:href form.
594 Glib::ustring get_as_attribute() const
595 {
596 return _entry.get_text();
597 }
600 void set_from_attribute(SPObject* o)
601 {
602 const gchar* val = attribute_value(o);
603 if(val) {
604 _entry.set_text(val);
605 } else {
606 _entry.set_text("");
607 }
608 }
610 void set_desktop(SPDesktop* d){
611 _desktop = d;
612 }
614 private:
615 void select_svg_element(){
616 Inkscape::Selection* sel = sp_desktop_selection(_desktop);
617 if (sel->isEmpty()) return;
618 Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
619 if (!node || !node->matchAttributeName("id")) return;
621 std::ostringstream xlikhref;
622 xlikhref << "#" << node->attribute("id");
623 _entry.set_text(xlikhref.str());
624 }
626 void select_file(){
628 //# Get the current directory for finding files
629 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
630 Glib::ustring open_path;
631 Glib::ustring attr = prefs->getString("/dialogs/open/path");
632 if (!attr.empty())
633 open_path = attr;
635 //# Test if the open_path directory exists
636 if (!Inkscape::IO::file_test(open_path.c_str(),
637 (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
638 open_path = "";
640 //# If no open path, default to our home directory
641 if (open_path.size() < 1)
642 {
643 open_path = g_get_home_dir();
644 open_path.append(G_DIR_SEPARATOR_S);
645 }
647 //# Create a dialog if we don't already have one
648 if (!selectFeImageFileInstance) {
649 selectFeImageFileInstance =
650 Inkscape::UI::Dialog::FileOpenDialog::create(
651 *_desktop->getToplevel(),
652 open_path,
653 Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not just svg*/
654 (char const *)_("Select an image to be used as feImage input"));
655 }
657 //# Show the dialog
658 bool const success = selectFeImageFileInstance->show();
659 if (!success)
660 return;
662 //# User selected something. Get name and type
663 Glib::ustring fileName = selectFeImageFileInstance->getFilename();
665 if (fileName.size() > 0) {
667 Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
669 if ( newFileName.size() > 0)
670 fileName = newFileName;
671 else
672 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
674 open_path = fileName;
675 open_path.append(G_DIR_SEPARATOR_S);
676 prefs->setString("/dialogs/open/path", open_path);
678 _entry.set_text(fileName);
679 }
680 return;
681 }
683 Gtk::Entry _entry;
684 Gtk::Button _fromFile;
685 Gtk::Button _fromSVGElement;
686 SPDesktop* _desktop;
687 };
689 class FilterEffectsDialog::Settings
690 {
691 public:
692 typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
694 Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
695 : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
696 {
697 _groups.resize(_max_types);
698 _attrwidgets.resize(_max_types);
699 _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
701 for(int i = 0; i < _max_types; ++i) {
702 _groups[i] = new Gtk::VBox;
703 b.pack_start(*_groups[i], false, false);
704 }
705 _current_type = 0;
706 }
708 ~Settings()
709 {
710 for(int i = 0; i < _max_types; ++i) {
711 delete _groups[i];
712 for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
713 delete _attrwidgets[i][j];
714 }
715 }
717 // Show the active settings group and update all the AttrWidgets with new values
718 void show_and_update(const int t, SPObject* ob)
719 {
720 if(t != _current_type) {
721 type(t);
722 for(unsigned i = 0; i < _groups.size(); ++i)
723 _groups[i]->hide();
724 }
725 if(t >= 0)
726 _groups[t]->show_all();
728 _dialog.set_attrs_locked(true);
729 for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
730 _attrwidgets[_current_type][i]->set_from_attribute(ob);
731 _dialog.set_attrs_locked(false);
732 }
734 int get_current_type() const
735 {
736 return _current_type;
737 }
739 void type(const int t)
740 {
741 _current_type = t;
742 }
744 void add_no_params()
745 {
746 Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect does not require any parameters.")));
747 add_widget(lbl, "");
748 }
750 void add_notimplemented()
751 {
752 Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
753 add_widget(lbl, "");
754 }
756 // LightSource
757 LightSourceControl* add_lightsource();
759 // CheckBox
760 CheckButtonAttr* add_checkbutton(bool def, const SPAttributeEnum attr, const Glib::ustring& label,
761 const Glib::ustring& tv, const Glib::ustring& fv, char* tip_text = NULL)
762 {
763 CheckButtonAttr* cb = new CheckButtonAttr(def, label, tv, fv, attr, tip_text);
764 add_widget(cb, "");
765 add_attr_widget(cb);
766 return cb;
767 }
769 // ColorButton
770 ColorButton* add_color(unsigned int def, const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text = NULL)
771 {
772 ColorButton* col = new ColorButton(def, attr, tip_text);
773 add_widget(col, label);
774 add_attr_widget(col);
775 return col;
776 }
778 // Matrix
779 MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label, char* tip_text)
780 {
781 MatrixAttr* conv = new MatrixAttr(attr, tip_text);
782 add_widget(conv, label);
783 add_attr_widget(conv);
784 return conv;
785 }
787 // ColorMatrixValues
788 ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
789 {
790 ColorMatrixValues* cmv = new ColorMatrixValues();
791 add_widget(cmv, label);
792 add_attr_widget(cmv);
793 return cmv;
794 }
796 // SpinSlider
797 SpinSlider* add_spinslider(double def, const SPAttributeEnum attr, const Glib::ustring& label,
798 const double lo, const double hi, const double step_inc, const double climb, const int digits, char* tip_text = NULL)
799 {
800 SpinSlider* spinslider = new SpinSlider(def, lo, hi, step_inc, climb, digits, attr, tip_text);
801 add_widget(spinslider, label);
802 add_attr_widget(spinslider);
803 return spinslider;
804 }
806 // DualSpinSlider
807 DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
808 const double lo, const double hi, const double step_inc,
809 const double climb, const int digits, char* tip_text1 = NULL, char* tip_text2 = NULL)
810 {
811 DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr, tip_text1, tip_text2);
812 add_widget(dss, label);
813 add_attr_widget(dss);
814 return dss;
815 }
817 // DualSpinButton
818 DualSpinButton* add_dualspinbutton(char* defalt_value, const SPAttributeEnum attr, const Glib::ustring& label,
819 const double lo, const double hi, const double step_inc,
820 const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
821 {
822 DualSpinButton* dsb = new DualSpinButton(defalt_value, lo, hi, step_inc, climb, digits, attr, tip1, tip2);
823 add_widget(dsb, label);
824 add_attr_widget(dsb);
825 return dsb;
826 }
828 // MultiSpinButton
829 MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
830 const Glib::ustring& label, const double lo, const double hi,
831 const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL)
832 {
833 std::vector<SPAttributeEnum> attrs;
834 attrs.push_back(attr1);
835 attrs.push_back(attr2);
837 std::vector<double> default_values;
838 default_values.push_back(def1);
839 default_values.push_back(def2);
841 std::vector<char*> tips;
842 tips.push_back(tip1);
843 tips.push_back(tip2);
845 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
846 add_widget(msb, label);
847 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
848 add_attr_widget(msb->get_spinbuttons()[i]);
849 return msb;
850 }
851 MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
852 const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
853 const double hi, const double step_inc, const double climb, const int digits, char* tip1 = NULL, char* tip2 = NULL, char* tip3 = NULL)
854 {
855 std::vector<SPAttributeEnum> attrs;
856 attrs.push_back(attr1);
857 attrs.push_back(attr2);
858 attrs.push_back(attr3);
860 std::vector<double> default_values;
861 default_values.push_back(def1);
862 default_values.push_back(def2);
863 default_values.push_back(def3);
865 std::vector<char*> tips;
866 tips.push_back(tip1);
867 tips.push_back(tip2);
868 tips.push_back(tip3);
870 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values, tips);
871 add_widget(msb, label);
872 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
873 add_attr_widget(msb->get_spinbuttons()[i]);
874 return msb;
875 }
877 // FileOrElementChooser
878 FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
879 {
880 FileOrElementChooser* foech = new FileOrElementChooser(attr);
881 foech->set_desktop(_dialog.getDesktop());
882 add_widget(foech, label);
883 add_attr_widget(foech);
884 return foech;
885 }
887 // ComboBoxEnum
888 template<typename T> ComboBoxEnum<T>* add_combo(T default_value, const SPAttributeEnum attr,
889 const Glib::ustring& label,
890 const Util::EnumDataConverter<T>& conv, char* tip_text = NULL)
891 {
892 ComboWithTooltip<T>* combo = new ComboWithTooltip<T>(default_value, conv, attr, tip_text);
893 add_widget(combo, label);
894 add_attr_widget(combo->get_attrwidget());
895 return combo->get_attrwidget();
896 }
897 private:
898 Gtk::Tooltips _tt;
900 void add_attr_widget(AttrWidget* a)
901 {
902 _attrwidgets[_current_type].push_back(a);
903 a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
904 }
906 /* Adds a new settings widget using the specified label. The label will be formatted with a colon
907 and all widgets within the setting group are aligned automatically. */
908 void add_widget(Gtk::Widget* w, const Glib::ustring& label)
909 {
910 Gtk::Label *lbl = 0;
911 Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
912 hb->set_spacing(12);
914 if(label != "") {
915 //lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT)); colon now in label (LP #358921)
916 lbl = Gtk::manage(new Gtk::Label(label, Gtk::ALIGN_LEFT));
917 hb->pack_start(*lbl, false, false);
918 _size_group->add_widget(*lbl);
919 lbl->show();
920 }
922 hb->pack_start(*w);
923 _groups[_current_type]->pack_start(*hb);
924 hb->show();
925 w->show();
926 }
928 std::vector<Gtk::VBox*> _groups;
929 Glib::RefPtr<Gtk::SizeGroup> _size_group;
930 FilterEffectsDialog& _dialog;
931 SetAttrSlot _set_attr_slot;
932 std::vector<std::vector< AttrWidget*> > _attrwidgets;
933 int _current_type, _max_types;
934 };
936 // Settings for the three light source objects
937 class FilterEffectsDialog::LightSourceControl : public AttrWidget
938 {
939 public:
940 LightSourceControl(FilterEffectsDialog& d)
941 : AttrWidget(SP_ATTR_INVALID),
942 _dialog(d),
943 _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
944 _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
945 _light_source(LightSourceConverter),
946 _locked(false)
947 {
948 _light_box.pack_start(_light_label, false, false);
949 _light_box.pack_start(_light_source);
950 _light_box.show_all();
951 _light_box.set_spacing(12);
952 _dialog._sizegroup->add_widget(_light_label);
954 _box.add(_light_box);
955 _box.reorder_child(_light_box, 0);
956 _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
958 // FIXME: these range values are complete crap
960 _settings.type(LIGHT_DISTANT);
961 _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"));
962 _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"));
964 _settings.type(LIGHT_POINT);
965 _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"));
967 _settings.type(LIGHT_SPOT);
968 _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"));
969 _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
970 SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
971 _("Points At"), -99999, 99999, 1, 100, 0, _("X coordinate"), _("Y coordinate"), _("Z coordinate"));
972 _settings.add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0, _("Exponent value controlling the focus for the light source"));
973 //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.
974 _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."));
975 }
977 Gtk::VBox& get_box()
978 {
979 return _box;
980 }
981 protected:
982 Glib::ustring get_as_attribute() const
983 {
984 return "";
985 }
986 void set_from_attribute(SPObject* o)
987 {
988 if(_locked)
989 return;
991 _locked = true;
993 SPObject* child = o->children;
995 if(SP_IS_FEDISTANTLIGHT(child))
996 _light_source.set_active(0);
997 else if(SP_IS_FEPOINTLIGHT(child))
998 _light_source.set_active(1);
999 else if(SP_IS_FESPOTLIGHT(child))
1000 _light_source.set_active(2);
1001 else
1002 _light_source.set_active(-1);
1004 update();
1006 _locked = false;
1007 }
1008 private:
1009 void on_source_changed()
1010 {
1011 if(_locked)
1012 return;
1014 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1015 if(prim) {
1016 _locked = true;
1018 SPObject* child = prim->children;
1019 const int ls = _light_source.get_active_row_number();
1020 // Check if the light source type has changed
1021 if(!(ls == -1 && !child) &&
1022 !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1023 !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1024 !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1025 if(child)
1026 //XML Tree being used directly here while it shouldn't be.
1027 sp_repr_unparent(child->getRepr());
1029 if(ls != -1) {
1030 Inkscape::XML::Document *xml_doc = prim->document->getReprDoc();
1031 Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1032 //XML Tree being used directly here while it shouldn't be.
1033 prim->getRepr()->appendChild(repr);
1034 Inkscape::GC::release(repr);
1035 }
1037 DocumentUndo::done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1038 update();
1039 }
1041 _locked = false;
1042 }
1043 }
1045 void update()
1046 {
1047 _box.hide_all();
1048 _box.show();
1049 _light_box.show_all();
1051 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1052 if(prim && prim->children)
1053 _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1054 }
1056 FilterEffectsDialog& _dialog;
1057 Gtk::VBox _box;
1058 Settings _settings;
1059 Gtk::HBox _light_box;
1060 Gtk::Label _light_label;
1061 ComboBoxEnum<LightSource> _light_source;
1062 bool _locked;
1063 };
1065 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1066 {
1067 LightSourceControl* ls = new LightSourceControl(_dialog);
1068 add_attr_widget(ls);
1069 add_widget(&ls->get_box(), "");
1070 return ls;
1071 }
1073 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1074 sigc::slot<void> rem)
1075 {
1076 Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1078 menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1079 Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1080 menu->append(*mi);
1081 mi->signal_activate().connect(rem);
1082 mi->show();
1083 menu->accelerate(parent);
1085 return menu;
1086 }
1088 /*** FilterModifier ***/
1089 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1090 : _dialog(d), _add(Gtk::Stock::NEW), _observer(new Inkscape::XML::SignalObserver)
1091 {
1092 Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1093 pack_start(*sw);
1094 pack_start(_add, false, false);
1095 sw->add(_list);
1097 _model = Gtk::ListStore::create(_columns);
1098 _list.set_model(_model);
1099 _cell_toggle.set_active(true);
1100 const int selcol = _list.append_column("", _cell_toggle);
1101 Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1102 if(col)
1103 col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1104 _list.append_column_editable(_("_Filter"), _columns.label);
1105 ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1106 signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1108 sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1109 sw->set_shadow_type(Gtk::SHADOW_IN);
1110 show_all_children();
1111 _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1112 _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1113 _list.signal_button_release_event().connect_notify(
1114 sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1115 _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1116 sigc::mem_fun(*this, &FilterModifier::remove_filter));
1117 _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1118 _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1119 _menu->accelerate(*this);
1121 _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1122 _observer->signal_changed().connect(signal_filter_changed().make_slot());
1123 g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1124 G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1126 g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1127 G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1128 g_signal_connect(G_OBJECT(INKSCAPE), "deactivate_desktop",
1129 G_CALLBACK(&FilterModifier::on_deactivate_desktop), this);
1131 on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1132 update_filters();
1133 }
1135 FilterEffectsDialog::FilterModifier::~FilterModifier()
1136 {
1137 _resource_changed.disconnect();
1138 _doc_replaced.disconnect();
1139 }
1141 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1142 {
1143 me->_doc_replaced.disconnect();
1144 me->_doc_replaced = desktop->connectDocumentReplaced(
1145 sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1147 me->_resource_changed.disconnect();
1148 me->_resource_changed =
1149 sp_desktop_document(desktop)->connectResourcesChanged("filter",sigc::mem_fun(me, &FilterModifier::update_filters));
1151 me->_dialog.setDesktop(desktop);
1153 me->update_filters();
1154 }
1156 void FilterEffectsDialog::FilterModifier::on_deactivate_desktop(Application*, SPDesktop* /*desktop*/, FilterModifier* me)
1157 {
1158 me->_doc_replaced.disconnect();
1159 me->_resource_changed.disconnect();
1160 me->_dialog.setDesktop(NULL);
1161 }
1164 // When the selection changes, show the active filter(s) in the dialog
1165 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1166 Selection *sel,
1167 FilterModifier* fm)
1168 {
1169 if(fm && sel)
1170 fm->update_selection(sel);
1171 }
1173 // Update each filter's sel property based on the current object selection;
1174 // If the filter is not used by any selected object, sel = 0,
1175 // otherwise sel is set to the total number of filters in use by selected objects
1176 // If only one filter is in use, it is selected
1177 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1178 {
1179 std::set<SPObject*> used;
1181 for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1182 SPObject *obj = SP_OBJECT (i->data);
1183 SPStyle *style = SP_OBJECT_STYLE (obj);
1184 if(!style || !SP_IS_ITEM(obj)) continue;
1186 if(style->filter.set && style->getFilter())
1187 used.insert(style->getFilter());
1188 else
1189 used.insert(0);
1190 }
1192 const int size = used.size();
1194 for(Gtk::TreeIter iter = _model->children().begin();
1195 iter != _model->children().end(); ++iter) {
1196 if(used.find((*iter)[_columns.filter]) != used.end()) {
1197 // If only one filter is in use by the selection, select it
1198 if(size == 1)
1199 _list.get_selection()->select(iter);
1200 (*iter)[_columns.sel] = size;
1201 }
1202 else
1203 (*iter)[_columns.sel] = 0;
1204 }
1205 }
1207 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1208 {
1209 _observer->set(get_selected_filter());
1210 signal_filter_changed()();
1211 }
1213 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1214 {
1215 Gtk::TreeModel::iterator iter = _model->get_iter(path);
1217 if(iter) {
1218 SPFilter* filter = (*iter)[_columns.filter];
1219 filter->setLabel(text.c_str());
1220 DocumentUndo::done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1221 if(iter)
1222 (*iter)[_columns.label] = text;
1223 }
1224 }
1226 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1227 {
1228 Gtk::TreeIter iter = _model->get_iter(path);
1230 if(iter) {
1231 SPDesktop *desktop = _dialog.getDesktop();
1232 SPDocument *doc = sp_desktop_document(desktop);
1233 SPFilter* filter = (*iter)[_columns.filter];
1234 Inkscape::Selection *sel = sp_desktop_selection(desktop);
1236 /* If this filter is the only one used in the selection, unset it */
1237 if((*iter)[_columns.sel] == 1)
1238 filter = 0;
1240 GSList const *items = sel->itemList();
1242 for (GSList const *i = items; i != NULL; i = i->next) {
1243 SPItem * item = SP_ITEM(i->data);
1244 SPStyle *style = SP_OBJECT_STYLE(item);
1245 g_assert(style != NULL);
1247 if(filter)
1248 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1249 else
1250 ::remove_filter(item, false);
1252 SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1253 }
1255 update_selection(sel);
1256 DocumentUndo::done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Apply filter"));
1257 }
1258 }
1260 /* Add all filters in the document to the combobox.
1261 Keeps the same selection if possible, otherwise selects the first element */
1262 void FilterEffectsDialog::FilterModifier::update_filters()
1263 {
1264 SPDesktop* desktop = _dialog.getDesktop();
1265 SPDocument* document = sp_desktop_document(desktop);
1266 const GSList* filters = document->getResourceList("filter");
1268 _model->clear();
1270 for(const GSList *l = filters; l; l = l->next) {
1271 Gtk::TreeModel::Row row = *_model->append();
1272 SPFilter* f = (SPFilter*)l->data;
1273 row[_columns.filter] = f;
1274 const gchar* lbl = f->label();
1275 const gchar* id = f->getId();
1276 row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1277 }
1279 update_selection(desktop->selection);
1280 _dialog.update_filter_general_settings_view();
1281 }
1283 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1284 {
1285 if(_list.get_selection()) {
1286 Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1288 if(i)
1289 return (*i)[_columns.filter];
1290 }
1292 return 0;
1293 }
1295 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1296 {
1297 if(filter) {
1298 for(Gtk::TreeModel::iterator i = _model->children().begin();
1299 i != _model->children().end(); ++i) {
1300 if((*i)[_columns.filter] == filter) {
1301 _list.get_selection()->select(i);
1302 break;
1303 }
1304 }
1305 }
1306 }
1308 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1309 {
1310 if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1311 const bool sensitive = get_selected_filter() != NULL;
1312 _menu->items()[0].set_sensitive(sensitive);
1313 _menu->items()[1].set_sensitive(sensitive);
1314 _menu->popup(event->button, event->time);
1315 }
1316 }
1318 void FilterEffectsDialog::FilterModifier::add_filter()
1319 {
1320 SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1321 SPFilter* filter = new_filter(doc);
1323 const int count = _model->children().size();
1324 std::ostringstream os;
1325 os << _("filter") << count;
1326 filter->setLabel(os.str().c_str());
1328 update_filters();
1330 select_filter(filter);
1332 DocumentUndo::done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1333 }
1335 void FilterEffectsDialog::FilterModifier::remove_filter()
1336 {
1337 SPFilter *filter = get_selected_filter();
1339 if(filter) {
1340 SPDocument* doc = filter->document;
1342 //XML Tree being used directly here while it shouldn't be.
1343 sp_repr_unparent(filter->getRepr());
1345 DocumentUndo::done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1347 update_filters();
1348 }
1349 }
1351 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1352 {
1353 SPFilter* filter = get_selected_filter();
1355 if(filter) {
1356 Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1357 repr = repr->duplicate(repr->document());
1358 parent->appendChild(repr);
1360 DocumentUndo::done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1362 update_filters();
1363 }
1364 }
1366 void FilterEffectsDialog::FilterModifier::rename_filter()
1367 {
1368 _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1369 }
1371 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1372 : Glib::ObjectBase(typeid(CellRendererConnection)),
1373 _primitive(*this, "primitive", 0)
1374 {}
1376 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1377 {
1378 return _primitive.get_proxy();
1379 }
1381 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1382 {
1383 _text_width = w;
1384 }
1386 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1387 {
1388 return _text_width;
1389 }
1391 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1392 Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1393 int* x_offset, int* y_offset, int* width, int* height) const
1394 {
1395 PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1397 if(x_offset)
1398 (*x_offset) = 0;
1399 if(y_offset)
1400 (*y_offset) = 0;
1401 if(width)
1402 (*width) = size * primlist.primitive_count() + _text_width * 7;
1403 if(height) {
1404 // Scale the height depending on the number of inputs, unless it's
1405 // the first primitive, in which case there are no connections
1406 SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1407 (*height) = size * input_count(prim);
1408 }
1409 }
1411 /*** PrimitiveList ***/
1412 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1413 : _dialog(d),
1414 _in_drag(0),
1415 _observer(new Inkscape::XML::SignalObserver)
1416 {
1417 d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1419 add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1420 signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1422 _model = Gtk::ListStore::create(_columns);
1424 set_reorderable(true);
1426 set_model(_model);
1427 append_column(_("_Effect"), _columns.type);
1429 _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1430 get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1431 signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1433 _connection_cell.set_text_width(init_text());
1435 int cols_count = append_column(_("Connections"), _connection_cell);
1436 Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1437 if(col)
1438 col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1439 }
1441 // Sets up a vertical Pango context/layout, and returns the largest
1442 // width needed to render the FilterPrimitiveInput labels.
1443 int FilterEffectsDialog::PrimitiveList::init_text()
1444 {
1445 // Set up a vertical context+layout
1446 Glib::RefPtr<Pango::Context> context = create_pango_context();
1447 const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1448 context->set_matrix(matrix);
1449 _vertical_layout = Pango::Layout::create(context);
1451 int maxfont = 0;
1452 for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1453 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1454 int fontw, fonth;
1455 _vertical_layout->get_pixel_size(fontw, fonth);
1456 if(fonth > maxfont)
1457 maxfont = fonth;
1458 }
1460 return maxfont;
1461 }
1463 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1464 {
1465 return _signal_primitive_changed;
1466 }
1468 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1469 {
1470 _observer->set(get_selected());
1471 signal_primitive_changed()();
1472 _dialog._color_matrix_values->clear_store();
1473 }
1475 /* Add all filter primitives in the current to the list.
1476 Keeps the same selection if possible, otherwise selects the first element */
1477 void FilterEffectsDialog::PrimitiveList::update()
1478 {
1479 SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1480 const SPFilterPrimitive* active_prim = get_selected();
1481 bool active_found = false;
1483 _model->clear();
1485 if(f) {
1486 _dialog._primitive_box.set_sensitive(true);
1487 _dialog.update_filter_general_settings_view();
1488 for(SPObject *prim_obj = f->children;
1489 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1490 prim_obj = prim_obj->next) {
1491 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1492 if(prim) {
1493 Gtk::TreeModel::Row row = *_model->append();
1494 row[_columns.primitive] = prim;
1496 //XML Tree being used directly here while it shouldn't be.
1497 row[_columns.type_id] = FPConverter.get_id_from_key(prim->getRepr()->name());
1498 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1499 row[_columns.id] = prim->getId();
1501 if(prim == active_prim) {
1502 get_selection()->select(row);
1503 active_found = true;
1504 }
1505 }
1506 }
1508 if(!active_found && _model->children().begin())
1509 get_selection()->select(_model->children().begin());
1511 columns_autosize();
1512 }
1513 else {
1514 _dialog._primitive_box.set_sensitive(false);
1515 }
1516 }
1518 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1519 {
1520 _primitive_menu = menu;
1521 }
1523 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1524 {
1525 if(_dialog._filter_modifier.get_selected_filter()) {
1526 Gtk::TreeModel::iterator i = get_selection()->get_selected();
1527 if(i)
1528 return (*i)[_columns.primitive];
1529 }
1531 return 0;
1532 }
1534 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1535 {
1536 for(Gtk::TreeIter i = _model->children().begin();
1537 i != _model->children().end(); ++i) {
1538 if((*i)[_columns.primitive] == prim)
1539 get_selection()->select(i);
1540 }
1541 }
1543 void FilterEffectsDialog::PrimitiveList::remove_selected()
1544 {
1545 SPFilterPrimitive* prim = get_selected();
1547 if(prim) {
1548 _observer->set(0);
1550 //XML Tree being used directly here while it shouldn't be.
1551 sp_repr_unparent(prim->getRepr());
1553 DocumentUndo::done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1554 _("Remove filter primitive"));
1556 update();
1557 }
1558 }
1560 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1561 {
1562 Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1563 Glib::RefPtr<Gdk::Window> win = get_bin_window();
1564 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1566 SPFilterPrimitive* prim = get_selected();
1567 int row_count = get_model()->children().size();
1569 int fheight = CellRendererConnection::size;
1570 Gdk::Rectangle rct, vis;
1571 Gtk::TreeIter row = get_model()->children().begin();
1572 int text_start_x = 0;
1573 if(row) {
1574 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1575 get_visible_rect(vis);
1576 int vis_x, vis_y;
1577 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1579 text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter._length + 1) + 1;
1580 for(unsigned int i = 0; i < FPInputConverter._length; ++i) {
1581 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1582 const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1583 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());
1584 get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1585 get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1586 }
1587 }
1589 int row_index = 0;
1590 for(; row != get_model()->children().end(); ++row, ++row_index) {
1591 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1592 const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1594 // Check mouse state
1595 int mx, my;
1596 Gdk::ModifierType mask;
1597 get_bin_window()->get_pointer(mx, my, mask);
1599 // Outline the bottom of the connection area
1600 const int outline_x = x + fheight * (row_count - row_index);
1601 get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1603 // Side outline
1604 get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1606 std::vector<Gdk::Point> con_poly;
1607 int con_drag_y = 0;
1608 bool inside;
1609 const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1610 const int inputs = input_count(row_prim);
1612 if(SP_IS_FEMERGE(row_prim)) {
1613 for(int i = 0; i < inputs; ++i) {
1614 inside = do_connection_node(row, i, con_poly, mx, my);
1615 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1616 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1617 inside, con_poly);
1619 if(_in_drag == (i + 1))
1620 con_drag_y = con_poly[2].get_y();
1622 if(_in_drag != (i + 1) || row_prim != prim)
1623 draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1624 }
1625 }
1626 else {
1627 // Draw "in" shape
1628 inside = do_connection_node(row, 0, con_poly, mx, my);
1629 con_drag_y = con_poly[2].get_y();
1630 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1631 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1632 inside, con_poly);
1634 // Draw "in" connection
1635 if(_in_drag != 1 || row_prim != prim)
1636 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1638 if(inputs == 2) {
1639 // Draw "in2" shape
1640 inside = do_connection_node(row, 1, con_poly, mx, my);
1641 if(_in_drag == 2)
1642 con_drag_y = con_poly[2].get_y();
1643 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1644 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1645 inside, con_poly);
1646 // Draw "in2" connection
1647 if(_in_drag != 2 || row_prim != prim)
1648 draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1649 }
1650 }
1652 // Draw drag connection
1653 if(row_prim == prim && _in_drag) {
1654 get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1655 mx, con_drag_y);
1656 get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1657 }
1658 }
1660 return true;
1661 }
1663 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1664 const int text_start_x, const int x1, const int y1,
1665 const int row_count)
1666 {
1667 int src_id = 0;
1668 Gtk::TreeIter res = find_result(input, attr, src_id);
1669 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1670 Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1671 Glib::RefPtr<Gdk::GC> gc;
1673 const bool is_first = input == get_model()->children().begin();
1674 const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1675 const bool use_default = !res && !is_merge;
1677 if(res == input || (use_default && is_first)) {
1678 // Draw straight connection to a standard input
1679 // Draw a lighter line for an implicit connection to a standard input
1680 const int tw = _connection_cell.get_text_width();
1681 gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1682 gc = (use_default && is_first) ? lightgc : darkgc;
1683 get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1684 get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1685 }
1686 else {
1687 // Draw an 'L'-shaped connection to another filter primitive
1688 // If no connection is specified, draw a light connection to the previous primitive
1689 gc = use_default ? lightgc : darkgc;
1691 if(use_default) {
1692 res = input;
1693 --res;
1694 }
1696 if(res) {
1697 Gdk::Rectangle rct;
1699 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1700 const int fheight = CellRendererConnection::size;
1702 get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1703 const int row_index = find_index(res);
1704 const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1705 const int y2 = rct.get_y() + rct.get_height();
1707 // Draw a bevelled 'L'-shaped connection
1708 get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1709 get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1710 get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1711 }
1712 }
1713 }
1715 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1716 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1717 std::vector<Gdk::Point>& points,
1718 const int ix, const int iy)
1719 {
1720 Gdk::Rectangle rct;
1721 const int icnt = input_count((*row)[_columns.primitive]);
1723 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1724 const int fheight = CellRendererConnection::size;
1726 get_cell_area(_model->get_path(row), *get_column(1), rct);
1727 const float h = rct.get_height() / icnt;
1729 const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1730 const int con_w = (int)(fheight * 0.35f);
1731 const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1732 points.clear();
1733 points.push_back(Gdk::Point(x, con_y));
1734 points.push_back(Gdk::Point(x, con_y + con_w * 2));
1735 points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1737 return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1738 }
1740 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1741 const int attr, int& src_id)
1742 {
1743 SPFilterPrimitive* prim = (*start)[_columns.primitive];
1744 Gtk::TreeIter target = _model->children().end();
1745 int image = 0;
1747 if(SP_IS_FEMERGE(prim)) {
1748 int c = 0;
1749 bool found = false;
1750 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1751 if(c == attr && SP_IS_FEMERGENODE(o)) {
1752 image = SP_FEMERGENODE(o)->input;
1753 found = true;
1754 }
1755 }
1756 if(!found)
1757 return target;
1758 }
1759 else {
1760 if(attr == SP_ATTR_IN)
1761 image = prim->image_in;
1762 else if(attr == SP_ATTR_IN2) {
1763 if(SP_IS_FEBLEND(prim))
1764 image = SP_FEBLEND(prim)->in2;
1765 else if(SP_IS_FECOMPOSITE(prim))
1766 image = SP_FECOMPOSITE(prim)->in2;
1767 else if(SP_IS_FEDISPLACEMENTMAP(prim))
1768 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1769 else
1770 return target;
1771 }
1772 else
1773 return target;
1774 }
1776 if(image >= 0) {
1777 for(Gtk::TreeIter i = _model->children().begin();
1778 i != start; ++i) {
1779 if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1780 target = i;
1781 }
1782 return target;
1783 }
1784 else if(image < -1) {
1785 src_id = -(image + 2);
1786 return start;
1787 }
1789 return target;
1790 }
1792 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1793 {
1794 int i = 0;
1795 for(Gtk::TreeIter iter = _model->children().begin();
1796 iter != target; ++iter, ++i){};
1797 return i;
1798 }
1800 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1801 {
1802 Gtk::TreePath path;
1803 Gtk::TreeViewColumn* col;
1804 const int x = (int)e->x, y = (int)e->y;
1805 int cx, cy;
1807 _drag_prim = 0;
1809 if(get_path_at_pos(x, y, path, col, cx, cy)) {
1810 Gtk::TreeIter iter = _model->get_iter(path);
1811 std::vector<Gdk::Point> points;
1813 _drag_prim = (*iter)[_columns.primitive];
1814 const int icnt = input_count(_drag_prim);
1816 for(int i = 0; i < icnt; ++i) {
1817 if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1818 _in_drag = i + 1;
1819 break;
1820 }
1821 }
1823 queue_draw();
1824 }
1826 if(_in_drag) {
1827 _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1828 _autoscroll = 0;
1829 get_selection()->select(path);
1830 return true;
1831 }
1832 else
1833 return Gtk::TreeView::on_button_press_event(e);
1834 }
1836 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1837 {
1838 const int speed = 10;
1839 const int limit = 15;
1841 Gdk::Rectangle vis;
1842 get_visible_rect(vis);
1843 int vis_x, vis_y;
1844 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1845 const int top = vis_y + vis.get_height();
1847 // When autoscrolling during a connection drag, set the speed based on
1848 // where the mouse is in relation to the edges.
1849 if(e->y < vis_y)
1850 _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1851 else if(e->y < vis_y + limit)
1852 _autoscroll = -speed;
1853 else if(e->y > top)
1854 _autoscroll = (int)(speed + (e->y - top) / 5);
1855 else if(e->y > top - limit)
1856 _autoscroll = speed;
1857 else
1858 _autoscroll = 0;
1860 queue_draw();
1862 return Gtk::TreeView::on_motion_notify_event(e);
1863 }
1865 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1866 {
1867 SPFilterPrimitive *prim = get_selected(), *target;
1869 _scroll_connection.disconnect();
1871 if(_in_drag && prim) {
1872 Gtk::TreePath path;
1873 Gtk::TreeViewColumn* col;
1874 int cx, cy;
1876 if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1877 const gchar *in_val = 0;
1878 Glib::ustring result;
1879 Gtk::TreeIter target_iter = _model->get_iter(path);
1880 target = (*target_iter)[_columns.primitive];
1881 col = get_column(1);
1883 Gdk::Rectangle rct;
1884 get_cell_area(path, *col, rct);
1885 const int twidth = _connection_cell.get_text_width();
1886 const int sources_x = rct.get_width() - twidth * FPInputConverter._length;
1887 if(cx > sources_x) {
1888 int src = (cx - sources_x) / twidth;
1889 if (src < 0) {
1890 src = 0;
1891 } else if(src >= static_cast<int>(FPInputConverter._length)) {
1892 src = FPInputConverter._length - 1;
1893 }
1894 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1895 in_val = result.c_str();
1896 }
1897 else {
1898 // Ensure that the target comes before the selected primitive
1899 for(Gtk::TreeIter iter = _model->children().begin();
1900 iter != get_selection()->get_selected(); ++iter) {
1901 if(iter == target_iter) {
1902 Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1903 // Make sure the target has a result
1904 const gchar *gres = repr->attribute("result");
1905 if(!gres) {
1906 result = sp_filter_get_new_result_name(SP_FILTER(prim->parent));
1907 repr->setAttribute("result", result.c_str());
1908 in_val = result.c_str();
1909 }
1910 else
1911 in_val = gres;
1912 break;
1913 }
1914 }
1915 }
1917 if(SP_IS_FEMERGE(prim)) {
1918 int c = 1;
1919 bool handled = false;
1920 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1921 if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1922 // If input is null, delete it
1923 if(!in_val) {
1925 //XML Tree being used directly here while it shouldn't be.
1926 sp_repr_unparent(o->getRepr());
1927 DocumentUndo::done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1928 _("Remove merge node"));
1929 (*get_selection()->get_selected())[_columns.primitive] = prim;
1930 }
1931 else
1932 _dialog.set_attr(o, SP_ATTR_IN, in_val);
1933 handled = true;
1934 }
1935 }
1936 // Add new input?
1937 if(!handled && c == _in_drag && in_val) {
1938 Inkscape::XML::Document *xml_doc = prim->document->getReprDoc();
1939 Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1940 repr->setAttribute("inkscape:collect", "always");
1942 //XML Tree being used directly here while it shouldn't be.
1943 prim->getRepr()->appendChild(repr);
1944 SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1945 Inkscape::GC::release(repr);
1946 _dialog.set_attr(node, SP_ATTR_IN, in_val);
1947 (*get_selection()->get_selected())[_columns.primitive] = prim;
1948 }
1949 }
1950 else {
1951 if(_in_drag == 1)
1952 _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1953 else if(_in_drag == 2)
1954 _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1955 }
1956 }
1958 _in_drag = 0;
1959 queue_draw();
1961 _dialog.update_settings_view();
1962 }
1964 if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1965 const bool sensitive = get_selected() != NULL;
1966 _primitive_menu->items()[0].set_sensitive(sensitive);
1967 _primitive_menu->items()[1].set_sensitive(sensitive);
1968 _primitive_menu->popup(e->button, e->time);
1970 return true;
1971 }
1972 else
1973 return Gtk::TreeView::on_button_release_event(e);
1974 }
1976 // Checks all of prim's inputs, removes any that use result
1977 void check_single_connection(SPFilterPrimitive* prim, const int result)
1978 {
1979 if(prim && result >= 0) {
1981 if(prim->image_in == result)
1982 SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1984 if(SP_IS_FEBLEND(prim)) {
1985 if(SP_FEBLEND(prim)->in2 == result)
1986 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1987 }
1988 else if(SP_IS_FECOMPOSITE(prim)) {
1989 if(SP_FECOMPOSITE(prim)->in2 == result)
1990 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1991 }
1992 else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
1993 if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
1994 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1995 }
1996 }
1997 }
1999 // Remove any connections going to/from prim_iter that forward-reference other primitives
2000 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
2001 {
2002 SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
2003 bool before = true;
2005 for(Gtk::TreeIter iter = _model->children().begin();
2006 iter != _model->children().end(); ++iter) {
2007 if(iter == prim_iter)
2008 before = false;
2009 else {
2010 SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
2011 if(before)
2012 check_single_connection(cur_prim, prim->image_out);
2013 else
2014 check_single_connection(prim, cur_prim->image_out);
2015 }
2016 }
2017 }
2019 // Reorder the filter primitives to match the list order
2020 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
2021 {
2022 SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
2023 int ndx = 0;
2025 for(Gtk::TreeModel::iterator iter = _model->children().begin();
2026 iter != _model->children().end(); ++iter, ++ndx) {
2027 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2028 if(prim && prim == _drag_prim) {
2029 SP_OBJECT_REPR(prim)->setPosition(ndx);
2030 break;
2031 }
2032 }
2034 for(Gtk::TreeModel::iterator iter = _model->children().begin();
2035 iter != _model->children().end(); ++iter, ++ndx) {
2036 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
2037 if(prim && prim == _drag_prim) {
2038 sanitize_connections(iter);
2039 get_selection()->select(iter);
2040 break;
2041 }
2042 }
2044 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2046 DocumentUndo::done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2047 }
2049 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2050 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2051 {
2052 if(_autoscroll) {
2053 Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2054 double v;
2056 v = a.get_value() + _autoscroll;
2057 if(v < 0)
2058 v = 0;
2059 if(v > a.get_upper() - a.get_page_size())
2060 v = a.get_upper() - a.get_page_size();
2062 a.set_value(v);
2064 queue_draw();
2065 }
2067 return true;
2068 }
2070 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2071 {
2072 return _model->children().size();
2073 }
2075 /*** FilterEffectsDialog ***/
2077 FilterEffectsDialog::FilterEffectsDialog()
2078 : UI::Widget::Panel("", "/dialogs/filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2079 _add_primitive_type(FPConverter),
2080 _add_primitive(_("Add Effect:")),
2081 _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2082 _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2083 _settings_initialized(false),
2084 _locked(false),
2085 _attr_lock(false),
2086 _filter_modifier(*this),
2087 _primitive_list(*this)
2088 {
2089 _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2090 NR_FILTER_ENDPRIMITIVETYPE);
2091 _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2092 1);
2093 _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2094 _sizegroup->set_ignore_hidden();
2096 _add_primitive_type.remove_row(NR_FILTER_TILE);
2097 _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2099 // Initialize widget hierarchy
2100 Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2101 Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2102 Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox(/*homogeneous:*/false, /*spacing:*/4));
2103 Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2105 _getContents()->add(*hpaned);
2106 hpaned->pack1(_filter_modifier);
2107 hpaned->pack2(_primitive_box);
2108 _primitive_box.pack_start(*sw_prims);
2109 _primitive_box.pack_start(*hb_prims, false, false);
2110 _primitive_box.pack_start(*infobox,false, false);
2111 sw_prims->add(_primitive_list);
2112 infobox->pack_start(_infobox_icon, false, false);
2113 infobox->pack_start(_infobox_desc, false, false);
2114 _infobox_desc.set_line_wrap(true);
2115 _infobox_desc.set_size_request(200, -1);
2117 hb_prims->pack_start(_add_primitive, false, false);
2118 hb_prims->pack_start(_add_primitive_type, false, false);
2119 _getContents()->pack_start(_settings_tabs, false, false);
2120 _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2121 _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2123 _primitive_list.signal_primitive_changed().connect(
2124 sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2125 _filter_modifier.signal_filter_changed().connect(
2126 sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2128 _add_primitive_type.signal_changed().connect(
2129 sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2131 sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2132 sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2133 // al_settings->set_padding(0, 0, 12, 0);
2134 // fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2135 // ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2136 _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2137 _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2138 sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2140 show_all_children();
2141 init_settings_widgets();
2142 _primitive_list.update();
2143 update_primitive_infobox();
2144 }
2146 FilterEffectsDialog::~FilterEffectsDialog()
2147 {
2148 delete _settings;
2149 delete _filter_general_settings;
2150 }
2152 void FilterEffectsDialog::set_attrs_locked(const bool l)
2153 {
2154 _locked = l;
2155 }
2157 void FilterEffectsDialog::show_all_vfunc()
2158 {
2159 UI::Widget::Panel::show_all_vfunc();
2161 update_settings_view();
2162 }
2164 void FilterEffectsDialog::init_settings_widgets()
2165 {
2166 // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2167 // most of the current values are complete guesses!
2169 _empty_settings.set_sensitive(false);
2170 _settings_tab1.pack_start(_empty_settings);
2172 _no_filter_selected.set_sensitive(false);
2173 _settings_tab2.pack_start(_no_filter_selected);
2174 _settings_initialized = true;
2176 _filter_general_settings->type(0);
2177 _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"));
2178 _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"));
2180 _settings->type(NR_FILTER_BLEND);
2181 _settings->add_combo(BLEND_NORMAL, SP_ATTR_MODE, _("Mode:"), BlendModeConverter);
2183 _settings->type(NR_FILTER_COLORMATRIX);
2184 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."));
2185 _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s):"));
2186 colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2188 _settings->type(NR_FILTER_COMPONENTTRANSFER);
2189 _settings->add_notimplemented();
2190 /*
2191 //TRANSLATORS: for info on "Slope" and "Intercept", see http://id.mind.net/~zona/mmts/functionInstitute/linearFunctions/lsif.html
2192 _settings->add_combo(COMPONENTTRANSFER_TYPE_IDENTITY, SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2193 _ct_slope = _settings->add_spinslider(1, SP_ATTR_SLOPE, _("Slope"), -10, 10, 0.1, 0.01, 2);
2194 _ct_intercept = _settings->add_spinslider(0, SP_ATTR_INTERCEPT, _("Intercept"), -10, 10, 0.1, 0.01, 2);
2195 _ct_amplitude = _settings->add_spinslider(1, SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 10, 0.1, 0.01, 2);
2196 _ct_exponent = _settings->add_spinslider(1, SP_ATTR_EXPONENT, _("Exponent"), 0, 10, 0.1, 0.01, 2);
2197 _ct_offset = _settings->add_spinslider(0, SP_ATTR_OFFSET, _("Offset"), -10, 10, 0.1, 0.01, 2);*/
2199 _settings->type(NR_FILTER_COMPOSITE);
2200 _settings->add_combo(COMPOSITE_OVER, SP_ATTR_OPERATOR, _("Operator:"), CompositeOperatorConverter);
2201 _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."));
2202 _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."));
2203 _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."));
2204 _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."));
2206 _settings->type(NR_FILTER_CONVOLVEMATRIX);
2207 _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"));
2208 _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."));
2209 //TRANSLATORS: for info on "Kernel", see http://en.wikipedia.org/wiki/Kernel_(matrix)
2210 _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."));
2211 _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2212 _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."));
2213 _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."));
2214 _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."));
2215 _settings->add_checkbutton(false, SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive."));
2217 _settings->type(NR_FILTER_DIFFUSELIGHTING);
2218 _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Diffuse Color:"), _("Defines the color of the light source"));
2219 _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale:"), -5, 5, 0.01, 0.001, 3, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2220 _settings->add_spinslider(1, SP_ATTR_DIFFUSECONSTANT, _("Constant:"), 0, 5, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2221 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2222 _settings->add_lightsource();
2224 _settings->type(NR_FILTER_DISPLACEMENTMAP);
2225 _settings->add_spinslider(0, SP_ATTR_SCALE, _("Scale:"), 0, 100, 1, 0.01, 1, _("This defines the intensity of the displacement effect."));
2226 _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_XCHANNELSELECTOR, _("X displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the X direction"));
2227 _settings->add_combo(DISPLACEMENTMAP_CHANNEL_ALPHA, SP_ATTR_YCHANNELSELECTOR, _("Y displacement:"), DisplacementMapChannelConverter, _("Color component that controls the displacement in the Y direction"));
2229 _settings->type(NR_FILTER_FLOOD);
2230 _settings->add_color(/*default: black*/ 0, SP_PROP_FLOOD_COLOR, _("Flood Color:"), _("The whole filter region will be filled with this color."));
2231 _settings->add_spinslider(1, SP_PROP_FLOOD_OPACITY, _("Opacity:"), 0, 1, 0.1, 0.01, 2);
2233 _settings->type(NR_FILTER_GAUSSIANBLUR);
2234 _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation:"), 0.01, 100, 1, 0.01, 1, _("The standard deviation for the blur operation."));
2236 _settings->type(NR_FILTER_MERGE);
2237 _settings->add_no_params();
2239 _settings->type(NR_FILTER_MORPHOLOGY);
2240 _settings->add_combo(MORPHOLOGY_OPERATOR_ERODE, SP_ATTR_OPERATOR, _("Operator:"), MorphologyOperatorConverter, _("Erode: performs \"thinning\" of input image.\nDilate: performs \"fattenning\" of input image."));
2241 _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius:"), 0, 100, 1, 0.01, 1);
2243 _settings->type(NR_FILTER_IMAGE);
2244 _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image:"));
2246 _settings->type(NR_FILTER_OFFSET);
2247 _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"));
2248 _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"));
2250 _settings->type(NR_FILTER_SPECULARLIGHTING);
2251 _settings->add_color(/*default: white*/ 0xffffffff, SP_PROP_LIGHTING_COLOR, _("Specular Color:"), _("Defines the color of the light source"));
2252 _settings->add_spinslider(1, SP_ATTR_SURFACESCALE, _("Surface Scale:"), -5, 5, 0.1, 0.01, 2, _("This value amplifies the heights of the bump map defined by the input alpha channel"));
2253 _settings->add_spinslider(1, SP_ATTR_SPECULARCONSTANT, _("Constant:"), 0, 5, 0.1, 0.01, 2, _("This constant affects the Phong lighting model."));
2254 _settings->add_spinslider(1, SP_ATTR_SPECULAREXPONENT, _("Exponent:"), 1, 50, 1, 0.01, 1, _("Exponent for specular term, larger is more \"shiny\"."));
2255 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length:"), 0.01, 10, 1, 0.01, 1);
2256 _settings->add_lightsource();
2258 _settings->type(NR_FILTER_TILE);
2259 _settings->add_notimplemented();
2261 _settings->type(NR_FILTER_TURBULENCE);
2262 // _settings->add_checkbutton(false, SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2263 _settings->add_combo(TURBULENCE_TURBULENCE, SP_ATTR_TYPE, _("Type:"), TurbulenceTypeConverter, _("Indicates whether the filter primitive should perform a noise or turbulence function."));
2264 _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency:"), 0, 0.4, 0.001, 0.01, 3);
2265 _settings->add_spinslider(1, SP_ATTR_NUMOCTAVES, _("Octaves:"), 1, 10, 1, 1, 0);
2266 _settings->add_spinslider(0, SP_ATTR_SEED, _("Seed:"), 0, 1000, 1, 1, 0, _("The starting number for the pseudo random number generator."));
2267 }
2269 void FilterEffectsDialog::add_primitive()
2270 {
2271 SPFilter* filter = _filter_modifier.get_selected_filter();
2273 if(filter) {
2274 SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2276 _primitive_list.select(prim);
2278 DocumentUndo::done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2279 }
2280 }
2282 void FilterEffectsDialog::update_primitive_infobox()
2283 {
2284 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2285 if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2286 _infobox_icon.show();
2287 _infobox_desc.show();
2288 } else {
2289 _infobox_icon.hide();
2290 _infobox_desc.hide();
2291 }
2292 switch(_add_primitive_type.get_active_data()->id){
2293 case(NR_FILTER_BLEND):
2294 _infobox_icon.set_from_icon_name("feBlend-icon", Gtk::ICON_SIZE_DIALOG);
2295 _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2296 break;
2297 case(NR_FILTER_COLORMATRIX):
2298 _infobox_icon.set_from_icon_name("feColorMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2299 _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."));
2300 break;
2301 case(NR_FILTER_COMPONENTTRANSFER):
2302 _infobox_icon.set_from_icon_name("feComponentTransfer-icon", Gtk::ICON_SIZE_DIALOG);
2303 _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."));
2304 break;
2305 case(NR_FILTER_COMPOSITE):
2306 _infobox_icon.set_from_icon_name("feComposite-icon", Gtk::ICON_SIZE_DIALOG);
2307 _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."));
2308 break;
2309 case(NR_FILTER_CONVOLVEMATRIX):
2310 _infobox_icon.set_from_icon_name("feConvolveMatrix-icon", Gtk::ICON_SIZE_DIALOG);
2311 _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."));
2312 break;
2313 case(NR_FILTER_DIFFUSELIGHTING):
2314 _infobox_icon.set_from_icon_name("feDiffuseLighting-icon", Gtk::ICON_SIZE_DIALOG);
2315 _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."));
2316 break;
2317 case(NR_FILTER_DISPLACEMENTMAP):
2318 _infobox_icon.set_from_icon_name("feDisplacementMap-icon", Gtk::ICON_SIZE_DIALOG);
2319 _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."));
2320 break;
2321 case(NR_FILTER_FLOOD):
2322 _infobox_icon.set_from_icon_name("feFlood-icon", Gtk::ICON_SIZE_DIALOG);
2323 _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."));
2324 break;
2325 case(NR_FILTER_GAUSSIANBLUR):
2326 _infobox_icon.set_from_icon_name("feGaussianBlur-icon", Gtk::ICON_SIZE_DIALOG);
2327 _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."));
2328 break;
2329 case(NR_FILTER_IMAGE):
2330 _infobox_icon.set_from_icon_name("feImage-icon", Gtk::ICON_SIZE_DIALOG);
2331 _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2332 break;
2333 case(NR_FILTER_MERGE):
2334 _infobox_icon.set_from_icon_name("feMerge-icon", Gtk::ICON_SIZE_DIALOG);
2335 _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."));
2336 break;
2337 case(NR_FILTER_MORPHOLOGY):
2338 _infobox_icon.set_from_icon_name("feMorphology-icon", Gtk::ICON_SIZE_DIALOG);
2339 _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."));
2340 break;
2341 case(NR_FILTER_OFFSET):
2342 _infobox_icon.set_from_icon_name("feOffset-icon", Gtk::ICON_SIZE_DIALOG);
2343 _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."));
2344 break;
2345 case(NR_FILTER_SPECULARLIGHTING):
2346 _infobox_icon.set_from_icon_name("feSpecularLighting-icon", Gtk::ICON_SIZE_DIALOG);
2347 _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."));
2348 break;
2349 case(NR_FILTER_TILE):
2350 _infobox_icon.set_from_icon_name("feTile-icon", Gtk::ICON_SIZE_DIALOG);
2351 _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2352 break;
2353 case(NR_FILTER_TURBULENCE):
2354 _infobox_icon.set_from_icon_name("feTurbulence-icon", Gtk::ICON_SIZE_DIALOG);
2355 _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."));
2356 break;
2357 default:
2358 g_assert(false);
2359 break;
2360 }
2361 _infobox_icon.set_pixel_size(96);
2362 }
2364 void FilterEffectsDialog::duplicate_primitive()
2365 {
2366 SPFilter* filter = _filter_modifier.get_selected_filter();
2367 SPFilterPrimitive* origprim = _primitive_list.get_selected();
2369 if(filter && origprim) {
2370 Inkscape::XML::Node *repr;
2371 repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2372 SP_OBJECT_REPR(filter)->appendChild(repr);
2374 DocumentUndo::done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2376 _primitive_list.update();
2377 }
2378 }
2380 void FilterEffectsDialog::convolve_order_changed()
2381 {
2382 _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2383 _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2384 _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2385 }
2387 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2388 {
2389 set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2390 }
2392 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2393 {
2394 if(!_locked) {
2395 _attr_lock = true;
2396 SPFilter *filter = _filter_modifier.get_selected_filter();
2397 const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2398 if (filter && name && SP_OBJECT_REPR(filter)){
2399 SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2400 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2401 }
2402 _attr_lock = false;
2403 }
2404 }
2406 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2407 {
2408 set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2409 }
2411 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2412 {
2413 if(!_locked) {
2414 _attr_lock = true;
2416 SPFilter *filter = _filter_modifier.get_selected_filter();
2417 const gchar* name = (const gchar*)sp_attribute_name(attr);
2418 if(filter && name && o) {
2419 update_settings_sensitivity();
2421 SP_OBJECT_REPR(o)->setAttribute(name, val);
2422 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2424 Glib::ustring undokey = "filtereffects:";
2425 undokey += name;
2426 DocumentUndo::maybeDone(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2427 _("Set filter primitive attribute"));
2428 }
2430 _attr_lock = false;
2431 }
2432 }
2434 void FilterEffectsDialog::update_filter_general_settings_view()
2435 {
2436 if(_settings_initialized != true) return;
2438 if(!_locked) {
2439 _attr_lock = true;
2441 SPFilter* filter = _filter_modifier.get_selected_filter();
2443 if(filter) {
2444 _filter_general_settings->show_and_update(0, filter);
2445 _no_filter_selected.hide();
2446 }
2447 else {
2448 std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2449 vect[0]->hide_all();
2450 _no_filter_selected.show();
2451 }
2453 _attr_lock = false;
2454 }
2455 }
2457 void FilterEffectsDialog::update_settings_view()
2458 {
2459 update_settings_sensitivity();
2461 if(_attr_lock)
2462 return;
2464 //First Tab
2466 std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2467 for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2468 _empty_settings.show();
2470 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2471 if (prefs->getBool("/options/showfiltersinfobox/value", true)){
2472 _infobox_icon.show();
2473 _infobox_desc.show();
2474 } else {
2475 _infobox_icon.hide();
2476 _infobox_desc.hide();
2477 }
2479 SPFilterPrimitive* prim = _primitive_list.get_selected();
2481 if(prim) {
2483 //XML Tree being used directly here while it shouldn't be.
2484 _settings->show_and_update(FPConverter.get_id_from_key(prim->getRepr()->name()), prim);
2485 _empty_settings.hide();
2486 }
2488 //Second Tab
2490 std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2491 vect2[0]->hide_all();
2492 _no_filter_selected.show();
2494 SPFilter* filter = _filter_modifier.get_selected_filter();
2496 if(filter) {
2497 _filter_general_settings->show_and_update(0, filter);
2498 _no_filter_selected.hide();
2499 }
2501 }
2503 void FilterEffectsDialog::update_settings_sensitivity()
2504 {
2505 SPFilterPrimitive* prim = _primitive_list.get_selected();
2506 const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2507 _k1->set_sensitive(use_k);
2508 _k2->set_sensitive(use_k);
2509 _k3->set_sensitive(use_k);
2510 _k4->set_sensitive(use_k);
2512 // Component transfer not yet implemented
2513 /*
2514 if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2515 SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2516 const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2517 const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2519 _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2520 _ct_slope->set_sensitive(linear);
2521 _ct_intercept->set_sensitive(linear);
2522 _ct_amplitude->set_sensitive(gamma);
2523 _ct_exponent->set_sensitive(gamma);
2524 _ct_offset->set_sensitive(gamma);
2525 }
2526 */
2527 }
2529 void FilterEffectsDialog::update_color_matrix()
2530 {
2531 _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2532 }
2534 } // namespace Dialog
2535 } // namespace UI
2536 } // namespace Inkscape
2538 /*
2539 Local Variables:
2540 mode:c++
2541 c-file-style:"stroustrup"
2542 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2543 indent-tabs-mode:nil
2544 fill-column:99
2545 End:
2546 */
2547 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :