a97654b75774914be3031f36be45cfdfeb0b3242
1 /**
2 * \brief Filter Effects dialog
3 *
4 * Authors:
5 * Nicholas Bishop <nicholasbishop@gmail.org>
6 * Rodrigo Kumpera <kumpera@gmail.com>
7 * Felipe C. da S. Sanches <felipe.sanches@gmail.com>
8 *
9 * Copyright (C) 2007 Authors
10 *
11 * Released under GNU GPL. Read the file 'COPYING' for more information.
12 */
14 #ifdef HAVE_CONFIG_H
15 # include <config.h>
16 #endif
18 #include <gtk/gtktreeview.h>
19 #include <gtkmm/cellrenderertext.h>
20 #include <gtkmm/colorbutton.h>
21 #include <gtkmm/messagedialog.h>
22 #include <gtkmm/paned.h>
23 #include <gtkmm/scale.h>
24 #include <gtkmm/scrolledwindow.h>
25 #include <gtkmm/spinbutton.h>
26 #include <gtkmm/stock.h>
27 #include <glibmm/i18n.h>
29 #include "application/application.h"
30 #include "application/editor.h"
31 #include "desktop.h"
32 #include "desktop-handles.h"
33 #include "dialog-manager.h"
34 #include "dir-util.h"
35 #include "document.h"
36 #include "filter-chemistry.h"
37 #include "filter-effects-dialog.h"
38 #include "filter-enums.h"
39 #include "inkscape.h"
40 #include "path-prefix.h"
41 #include "prefs-utils.h"
42 #include "selection.h"
43 #include "sp-feblend.h"
44 #include "sp-fecolormatrix.h"
45 #include "sp-fecomponenttransfer.h"
46 #include "sp-fecomposite.h"
47 #include "sp-feconvolvematrix.h"
48 #include "sp-fedisplacementmap.h"
49 #include "sp-fedistantlight.h"
50 #include "sp-femerge.h"
51 #include "sp-femergenode.h"
52 #include "sp-feoffset.h"
53 #include "sp-fepointlight.h"
54 #include "sp-fespotlight.h"
55 #include "sp-filter-primitive.h"
56 #include "sp-gaussian-blur.h"
58 #include "style.h"
59 #include "svg/svg-color.h"
60 #include "ui/dialog/filedialog.h"
61 #include "verbs.h"
62 #include "xml/node.h"
63 #include "xml/node-observer.h"
64 #include "xml/repr.h"
65 #include <sstream>
67 #include "io/sys.h"
68 #include <iostream>
70 using namespace NR;
72 namespace Inkscape {
73 namespace UI {
74 namespace Dialog {
76 // Returns the number of inputs available for the filter primitive type
77 int input_count(const SPFilterPrimitive* prim)
78 {
79 if(!prim)
80 return 0;
81 else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
82 return 2;
83 else if(SP_IS_FEMERGE(prim)) {
84 // Return the number of feMergeNode connections plus an extra
85 int count = 1;
86 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count);
87 return count;
88 }
89 else
90 return 1;
91 }
93 // Very simple observer that just emits a signal if anything happens to a node
94 class FilterEffectsDialog::SignalObserver : public XML::NodeObserver
95 {
96 public:
97 SignalObserver()
98 : _oldsel(0)
99 {}
101 // Add this observer to the SPObject and remove it from any previous object
102 void set(SPObject* o)
103 {
104 if(_oldsel && _oldsel->repr)
105 _oldsel->repr->removeObserver(*this);
106 if(o && o->repr)
107 o->repr->addObserver(*this);
108 _oldsel = o;
109 }
111 void notifyChildAdded(XML::Node&, XML::Node&, XML::Node*)
112 { signal_changed()(); }
114 void notifyChildRemoved(XML::Node&, XML::Node&, XML::Node*)
115 { signal_changed()(); }
117 void notifyChildOrderChanged(XML::Node&, XML::Node&, XML::Node*, XML::Node*)
118 { signal_changed()(); }
120 void notifyContentChanged(XML::Node&, Util::ptr_shared<char>, Util::ptr_shared<char>)
121 {}
123 void notifyAttributeChanged(XML::Node&, GQuark, Util::ptr_shared<char>, Util::ptr_shared<char>)
124 { signal_changed()(); }
126 sigc::signal<void>& signal_changed()
127 {
128 return _signal_changed;
129 }
130 private:
131 sigc::signal<void> _signal_changed;
132 SPObject* _oldsel;
133 };
135 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
136 {
137 public:
138 CheckButtonAttr(const Glib::ustring& label,
139 const Glib::ustring& tv, const Glib::ustring& fv,
140 const SPAttributeEnum a)
141 : Gtk::CheckButton(label),
142 AttrWidget(a, true),//TO-DO: receive a defaultvalue parameter in the constructor
143 _true_val(tv), _false_val(fv)
144 {
145 signal_toggled().connect(signal_attr_changed().make_slot());
146 }
148 Glib::ustring get_as_attribute() const
149 {
150 return get_active() ? _true_val : _false_val;
151 }
153 void set_from_attribute(SPObject* o)
154 {
155 const gchar* val = attribute_value(o);
156 if(val) {
157 if(_true_val == val)
158 set_active(true);
159 else if(_false_val == val)
160 set_active(false);
161 } else {
162 set_active(get_default()->as_bool());
163 }
164 }
165 private:
166 const Glib::ustring _true_val, _false_val;
167 };
169 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
170 {
171 public:
172 SpinButtonAttr(double lower, double upper, double step_inc,
173 double climb_rate, int digits, const SPAttributeEnum a, double def)
174 : Gtk::SpinButton(climb_rate, digits),
175 AttrWidget(a, def)
176 {
177 set_range(lower, upper);
178 set_increments(step_inc, step_inc * 5);
180 signal_value_changed().connect(signal_attr_changed().make_slot());
181 }
183 Glib::ustring get_as_attribute() const
184 {
185 const double val = get_value();
187 if(get_digits() == 0)
188 return Glib::Ascii::dtostr((int)val);
189 else
190 return Glib::Ascii::dtostr(val);
191 }
193 void set_from_attribute(SPObject* o)
194 {
195 const gchar* val = attribute_value(o);
196 if(val){
197 set_value(Glib::Ascii::strtod(val));
198 } else {
199 set_value(get_default()->as_double());
200 }
201 }
202 };
204 // Contains an arbitrary number of spin buttons that use seperate attributes
205 class MultiSpinButton : public Gtk::HBox
206 {
207 public:
208 MultiSpinButton(double lower, double upper, double step_inc,
209 double climb_rate, int digits, std::vector<SPAttributeEnum> attrs, std::vector<double> default_values)
210 {
211 g_assert(attrs.size()==default_values.size());
212 for(unsigned i = 0; i < attrs.size(); ++i) {
213 _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i], default_values[i]));
214 pack_start(*_spins.back(), false, false);
215 }
216 }
218 ~MultiSpinButton()
219 {
220 for(unsigned i = 0; i < _spins.size(); ++i)
221 delete _spins[i];
222 }
224 std::vector<SpinButtonAttr*>& get_spinbuttons()
225 {
226 return _spins;
227 }
228 private:
229 std::vector<SpinButtonAttr*> _spins;
230 };
232 // Contains two spinbuttons that describe a NumberOptNumber
233 class DualSpinButton : public Gtk::HBox, public AttrWidget
234 {
235 public:
236 DualSpinButton(double lower, double upper, double step_inc,
237 double climb_rate, int digits, const SPAttributeEnum a)
238 : AttrWidget(a), //TO-DO: receive default num-opt-num as parameter in the constructor
239 _s1(climb_rate, digits), _s2(climb_rate, digits)
240 {
241 _s1.set_range(lower, upper);
242 _s2.set_range(lower, upper);
243 _s1.set_increments(step_inc, step_inc * 5);
244 _s2.set_increments(step_inc, step_inc * 5);
246 _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
247 _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
249 pack_start(_s1, false, false);
250 pack_start(_s2, false, false);
251 }
253 Gtk::SpinButton& get_spinbutton1()
254 {
255 return _s1;
256 }
258 Gtk::SpinButton& get_spinbutton2()
259 {
260 return _s2;
261 }
263 virtual Glib::ustring get_as_attribute() const
264 {
265 double v1 = _s1.get_value();
266 double v2 = _s2.get_value();
268 if(_s1.get_digits() == 0) {
269 v1 = (int)v1;
270 v2 = (int)v2;
271 }
273 return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
274 }
276 virtual void set_from_attribute(SPObject* o)
277 {
278 const gchar* val = attribute_value(o);
279 NumberOptNumber n;
280 if(val) {
281 n.set(val);
282 } else {
283 n.set("0 0"); //TO-DO: replace this line by the next one that is currently commented out
284 // n.set(default_value(o));
285 }
286 _s1.set_value(n.getNumber());
287 _s2.set_value(n.getOptNumber());
289 }
290 private:
291 Gtk::SpinButton _s1, _s2;
292 };
294 class ColorButton : public Gtk::ColorButton, public AttrWidget
295 {
296 public:
297 ColorButton(const SPAttributeEnum a)
298 : AttrWidget(a)
299 {
300 signal_color_set().connect(signal_attr_changed().make_slot());
302 Gdk::Color col;
303 col.set_rgb(65535, 65535, 65535);
304 set_color(col);
305 }
307 // Returns the color in 'rgb(r,g,b)' form.
308 Glib::ustring get_as_attribute() const
309 {
310 std::ostringstream os;
311 const Gdk::Color c = get_color();
312 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?
313 os << "rgb(" << r << "," << g << "," << b << ")";
314 return os.str();
315 }
318 void set_from_attribute(SPObject* o)
319 {
320 const gchar* val = attribute_value(o);
321 guint32 i = 0;
322 if(val) {
323 i = sp_svg_read_color(val, 0xFFFFFFFF);
324 } else {
325 //TO-DO: read from constructor attribute
326 //i = default_value(o);
327 }
328 const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
329 Gdk::Color col;
330 col.set_rgb(r * 256, g * 256, b * 256);
331 set_color(col);
332 }
333 };
335 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
336 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
337 {
338 public:
339 MatrixAttr(const SPAttributeEnum a)
340 : AttrWidget(a), _locked(false)
341 {
342 _model = Gtk::ListStore::create(_columns);
343 _tree.set_model(_model);
344 _tree.set_headers_visible(false);
345 _tree.show();
346 add(_tree);
347 set_shadow_type(Gtk::SHADOW_IN);
348 }
350 std::vector<double> get_values() const
351 {
352 std::vector<double> vec;
353 for(Gtk::TreeIter iter = _model->children().begin();
354 iter != _model->children().end(); ++iter) {
355 for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
356 vec.push_back((*iter)[_columns.cols[c]]);
357 }
358 return vec;
359 }
361 void set_values(const std::vector<double>& v)
362 {
363 unsigned i = 0;
364 for(Gtk::TreeIter iter = _model->children().begin();
365 iter != _model->children().end(); ++iter) {
366 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
367 if(i >= v.size())
368 return;
369 (*iter)[_columns.cols[c]] = v[i];
370 ++i;
371 }
372 }
373 }
375 Glib::ustring get_as_attribute() const
376 {
377 std::ostringstream os;
379 for(Gtk::TreeIter iter = _model->children().begin();
380 iter != _model->children().end(); ++iter) {
381 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
382 os << (*iter)[_columns.cols[c]] << " ";
383 }
384 }
386 return os.str();
387 }
389 void set_from_attribute(SPObject* o)
390 {
391 if(o) {
392 if(SP_IS_FECONVOLVEMATRIX(o)) {
393 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
394 int cols, rows;
395 cols = (int)conv->order.getNumber();
396 if(cols > 5)
397 cols = 5;
398 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
399 update(o, rows, cols);
400 }
401 else if(SP_IS_FECOLORMATRIX(o))
402 update(o, 4, 5);
403 }
404 }
405 private:
406 class MatrixColumns : public Gtk::TreeModel::ColumnRecord
407 {
408 public:
409 MatrixColumns()
410 {
411 cols.resize(5);
412 for(unsigned i = 0; i < cols.size(); ++i)
413 add(cols[i]);
414 }
415 std::vector<Gtk::TreeModelColumn<double> > cols;
416 };
418 void update(SPObject* o, const int rows, const int cols)
419 {
420 if(_locked)
421 return;
423 _model->clear();
425 _tree.remove_all_columns();
427 std::vector<gdouble>* values = NULL;
428 if(SP_IS_FECOLORMATRIX(o))
429 values = &SP_FECOLORMATRIX(o)->values;
430 else if(SP_IS_FECONVOLVEMATRIX(o))
431 values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
432 else
433 return;
435 if(o) {
436 int ndx = 0;
438 for(int i = 0; i < cols; ++i) {
439 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
440 dynamic_cast<Gtk::CellRendererText*>(
441 _tree.get_column_cell_renderer(i))->signal_edited().connect(
442 sigc::mem_fun(*this, &MatrixAttr::rebind));
443 }
445 for(int r = 0; r < rows; ++r) {
446 Gtk::TreeRow row = *(_model->append());
447 // Default to identity matrix
448 for(int c = 0; c < cols; ++c, ++ndx)
449 row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
450 }
451 }
452 }
454 void rebind(const Glib::ustring&, const Glib::ustring&)
455 {
456 _locked = true;
457 signal_attr_changed()();
458 _locked = false;
459 }
461 bool _locked;
462 Gtk::TreeView _tree;
463 Glib::RefPtr<Gtk::ListStore> _model;
464 MatrixColumns _columns;
465 };
467 // Displays a matrix or a slider for feColorMatrix
468 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
469 {
470 public:
471 ColorMatrixValues()
472 : AttrWidget(SP_ATTR_VALUES),
473 _matrix(SP_ATTR_VALUES),
474 _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
475 _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
476 _label(_("None"), Gtk::ALIGN_LEFT),
477 _use_stored(false),
478 _saturation_store(0),
479 _angle_store(0)
480 {
481 _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
482 _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
483 _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
484 signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
486 _matrix.show();
487 _saturation.show();
488 _angle.show();
489 _label.show();
490 _label.set_sensitive(false);
492 set_shadow_type(Gtk::SHADOW_NONE);
493 }
495 virtual void set_from_attribute(SPObject* o)
496 {
497 if(SP_IS_FECOLORMATRIX(o)) {
498 SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
499 remove();
500 switch(col->type) {
501 case COLORMATRIX_SATURATE:
502 add(_saturation);
503 if(_use_stored)
504 _saturation.set_value(_saturation_store);
505 else
506 _saturation.set_from_attribute(o);
507 break;
508 case COLORMATRIX_HUEROTATE:
509 add(_angle);
510 if(_use_stored)
511 _angle.set_value(_angle_store);
512 else
513 _angle.set_from_attribute(o);
514 break;
515 case COLORMATRIX_LUMINANCETOALPHA:
516 add(_label);
517 break;
518 case COLORMATRIX_MATRIX:
519 default:
520 add(_matrix);
521 if(_use_stored)
522 _matrix.set_values(_matrix_store);
523 else
524 _matrix.set_from_attribute(o);
525 break;
526 }
527 _use_stored = true;
528 }
529 }
531 virtual Glib::ustring get_as_attribute() const
532 {
533 const Widget* w = get_child();
534 if(w == &_label)
535 return "";
536 else
537 return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
538 }
540 void clear_store()
541 {
542 _use_stored = false;
543 }
544 private:
545 void update_store()
546 {
547 const Widget* w = get_child();
548 if(w == &_matrix)
549 _matrix_store = _matrix.get_values();
550 else if(w == &_saturation)
551 _saturation_store = _saturation.get_value();
552 else if(w == &_angle)
553 _angle_store = _angle.get_value();
554 }
556 MatrixAttr _matrix;
557 SpinSlider _saturation;
558 SpinSlider _angle;
559 Gtk::Label _label;
561 // Store separate values for the different color modes
562 bool _use_stored;
563 std::vector<double> _matrix_store;
564 double _saturation_store;
565 double _angle_store;
566 };
568 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
570 //Displays a chooser for feImage input
571 //It may be a filename or the id for an SVG Element
572 //described in xlink:href syntax
573 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
574 {
575 public:
576 FileOrElementChooser(const SPAttributeEnum a)
577 : AttrWidget(a)
578 {
579 pack_start(_entry, false, false);
580 pack_start(_fromFile, false, false);
581 //pack_start(_fromSVGElement, false, false);
583 _fromFile.set_label(_("Image File"));
584 _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
586 _fromSVGElement.set_label(_("Selected SVG Element"));
587 _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
589 _entry.signal_changed().connect(signal_attr_changed().make_slot());
591 show_all();
593 }
595 // Returns the element in xlink:href form.
596 Glib::ustring get_as_attribute() const
597 {
598 return _entry.get_text();
599 }
602 void set_from_attribute(SPObject* o)
603 {
604 const gchar* val = attribute_value(o);
605 if(val) {
606 _entry.set_text(val);
607 } else {
608 _entry.set_text("");
609 }
610 }
612 void set_desktop(SPDesktop* d){
613 _desktop = d;
614 }
616 private:
617 void select_svg_element(){
618 Inkscape::Selection* sel = sp_desktop_selection(_desktop);
619 if (sel->isEmpty()) return;
620 Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
621 if (!node || !node->matchAttributeName("id")) return;
623 std::ostringstream xlikhref;
624 xlikhref << "#" << node->attribute("id");
625 _entry.set_text(xlikhref.str());
626 }
628 void select_file(){
630 //# Get the current directory for finding files
631 Glib::ustring open_path;
632 char *attr = (char *)prefs_get_string_attribute("dialogs.open", "path");
633 if (attr)
634 open_path = attr;
636 //# Test if the open_path directory exists
637 if (!Inkscape::IO::file_test(open_path.c_str(),
638 (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
639 open_path = "";
641 //# If no open path, default to our home directory
642 if (open_path.size() < 1)
643 {
644 open_path = g_get_home_dir();
645 open_path.append(G_DIR_SEPARATOR_S);
646 }
648 //# Create a dialog if we don't already have one
649 if (!selectFeImageFileInstance) {
650 selectFeImageFileInstance =
651 Inkscape::UI::Dialog::FileOpenDialog::create(
652 *_desktop->getToplevel(),
653 open_path,
654 Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not justy svg*/
655 (char const *)_("Select an image to be used as feImage input"));
656 }
658 //# Show the dialog
659 bool const success = selectFeImageFileInstance->show();
660 if (!success)
661 return;
663 //# User selected something. Get name and type
664 Glib::ustring fileName = selectFeImageFileInstance->getFilename();
666 if (fileName.size() > 0) {
668 Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
670 if ( newFileName.size() > 0)
671 fileName = newFileName;
672 else
673 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
675 open_path = fileName;
676 open_path.append(G_DIR_SEPARATOR_S);
677 prefs_set_string_attribute("dialogs.open", "path", open_path.c_str());
679 _entry.set_text(fileName);
680 }
681 return;
682 }
684 Gtk::Entry _entry;
685 Gtk::Button _fromFile;
686 Gtk::Button _fromSVGElement;
687 SPDesktop* _desktop;
688 };
690 class FilterEffectsDialog::Settings
691 {
692 public:
693 typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
695 Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
696 : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
697 {
698 _groups.resize(_max_types);
699 _attrwidgets.resize(_max_types);
700 _size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
702 for(int i = 0; i < _max_types; ++i) {
703 _groups[i] = new Gtk::VBox;
704 b.pack_start(*_groups[i], false, false);
705 }
706 _current_type = 0;
707 }
709 ~Settings()
710 {
711 for(int i = 0; i < _max_types; ++i) {
712 delete _groups[i];
713 for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
714 delete _attrwidgets[i][j];
715 }
716 }
718 // Show the active settings group and update all the AttrWidgets with new values
719 void show_and_update(const int t, SPObject* ob)
720 {
721 if(t != _current_type) {
722 type(t);
723 for(unsigned i = 0; i < _groups.size(); ++i)
724 _groups[i]->hide();
725 }
726 if(t >= 0)
727 _groups[t]->show_all();
729 _dialog.set_attrs_locked(true);
730 for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
731 _attrwidgets[_current_type][i]->set_from_attribute(ob);
732 _dialog.set_attrs_locked(false);
733 }
735 int get_current_type() const
736 {
737 return _current_type;
738 }
740 void type(const int t)
741 {
742 _current_type = t;
743 }
745 void add_notimplemented()
746 {
747 Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("This SVG filter effect is not yet implemented in Inkscape.")));
749 add_widget(lbl, "");
750 }
752 // LightSource
753 LightSourceControl* add_lightsource();
755 // CheckBox
756 CheckButtonAttr* add_checkbutton(const SPAttributeEnum attr, const Glib::ustring& label,
757 const Glib::ustring& tv, const Glib::ustring& fv)
758 {
759 CheckButtonAttr* cb = new CheckButtonAttr(label, tv, fv, attr);
760 add_widget(cb, "");
761 add_attr_widget(cb);
762 return cb;
763 }
765 // ColorButton
766 ColorButton* add_color(const SPAttributeEnum attr, const Glib::ustring& label)
767 {
768 ColorButton* col = new ColorButton(attr);
769 add_widget(col, label);
770 add_attr_widget(col);
771 return col;
772 }
774 // Matrix
775 MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label)
776 {
777 MatrixAttr* conv = new MatrixAttr(attr);
778 add_widget(conv, label);
779 add_attr_widget(conv);
780 return conv;
781 }
783 // ColorMatrixValues
784 ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
785 {
786 ColorMatrixValues* cmv = new ColorMatrixValues;
787 add_widget(cmv, label);
788 add_attr_widget(cmv);
789 return cmv;
790 }
792 // SpinSlider
793 SpinSlider* add_spinslider(const SPAttributeEnum attr, const Glib::ustring& label,
794 const double lo, const double hi, const double step_inc, const double climb, const int digits)
795 {
796 SpinSlider* spinslider = new SpinSlider(lo, lo, hi, step_inc, climb, digits, attr);
797 add_widget(spinslider, label);
798 add_attr_widget(spinslider);
799 return spinslider;
800 }
802 // DualSpinSlider
803 DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
804 const double lo, const double hi, const double step_inc,
805 const double climb, const int digits)
806 {
807 DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr);
808 add_widget(dss, label);
809 add_attr_widget(dss);
810 return dss;
811 }
813 // DualSpinButton
814 DualSpinButton* add_dualspinbutton(const SPAttributeEnum attr, const Glib::ustring& label,
815 const double lo, const double hi, const double step_inc,
816 const double climb, const int digits)
817 {
818 DualSpinButton* dsb = new DualSpinButton(lo, hi, step_inc, climb, digits, attr);
819 add_widget(dsb, label);
820 add_attr_widget(dsb);
821 return dsb;
822 }
824 // MultiSpinButton
825 MultiSpinButton* add_multispinbutton(double def1, double def2, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
826 const Glib::ustring& label, const double lo, const double hi,
827 const double step_inc, const double climb, const int digits)
828 {
829 std::vector<SPAttributeEnum> attrs;
830 attrs.push_back(attr1);
831 attrs.push_back(attr2);
833 std::vector<double> default_values;
834 default_values.push_back(def1);
835 default_values.push_back(def2);
836 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values);
837 add_widget(msb, label);
838 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
839 add_attr_widget(msb->get_spinbuttons()[i]);
840 return msb;
841 }
842 MultiSpinButton* add_multispinbutton(double def1, double def2, double def3, const SPAttributeEnum attr1, const SPAttributeEnum attr2,
843 const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
844 const double hi, const double step_inc, const double climb, const int digits)
845 {
846 std::vector<SPAttributeEnum> attrs;
847 attrs.push_back(attr1);
848 attrs.push_back(attr2);
849 attrs.push_back(attr3);
851 std::vector<double> default_values;
852 default_values.push_back(def1);
853 default_values.push_back(def2);
854 default_values.push_back(def3);
856 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs, default_values);
857 add_widget(msb, label);
858 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
859 add_attr_widget(msb->get_spinbuttons()[i]);
860 return msb;
861 }
863 // FileOrElementChooser
864 FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
865 {
866 FileOrElementChooser* foech = new FileOrElementChooser(attr);
867 foech->set_desktop(_dialog.getDesktop());
868 add_widget(foech, label);
869 add_attr_widget(foech);
870 return foech;
871 }
873 // ComboBoxEnum
874 template<typename T> ComboBoxEnum<T>* add_combo(const SPAttributeEnum attr,
875 const Glib::ustring& label,
876 const Util::EnumDataConverter<T>& conv)
877 {
878 ComboBoxEnum<T>* combo = new ComboBoxEnum<T>(conv, attr);
879 add_widget(combo, label);
880 add_attr_widget(combo);
881 return combo;
882 }
883 private:
884 void add_attr_widget(AttrWidget* a)
885 {
886 _attrwidgets[_current_type].push_back(a);
887 a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
888 }
890 /* Adds a new settings widget using the specified label. The label will be formatted with a colon
891 and all widgets within the setting group are aligned automatically. */
892 void add_widget(Gtk::Widget* w, const Glib::ustring& label)
893 {
894 Gtk::Label *lbl = 0;
895 Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
896 hb->set_spacing(12);
898 if(label != "") {
899 lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
900 hb->pack_start(*lbl, false, false);
901 _size_group->add_widget(*lbl);
902 lbl->show();
903 }
905 hb->pack_start(*w);
906 _groups[_current_type]->pack_start(*hb);
907 hb->show();
908 w->show();
909 }
911 std::vector<Gtk::VBox*> _groups;
912 Glib::RefPtr<Gtk::SizeGroup> _size_group;
913 FilterEffectsDialog& _dialog;
914 SetAttrSlot _set_attr_slot;
915 std::vector<std::vector< AttrWidget*> > _attrwidgets;
916 int _current_type, _max_types;
917 };
919 // Settings for the three light source objects
920 class FilterEffectsDialog::LightSourceControl : public AttrWidget
921 {
922 public:
923 LightSourceControl(FilterEffectsDialog& d)
924 : AttrWidget(SP_ATTR_INVALID),
925 _dialog(d),
926 _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
927 _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
928 _light_source(LightSourceConverter),
929 _locked(false)
930 {
931 _light_box.pack_start(_light_label, false, false);
932 _light_box.pack_start(_light_source);
933 _light_box.show_all();
934 _light_box.set_spacing(12);
935 _dialog._sizegroup->add_widget(_light_label);
937 _box.add(_light_box);
938 _box.reorder_child(_light_box, 0);
939 _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
941 // FIXME: these range values are complete crap
943 _settings.type(LIGHT_DISTANT);
944 _settings.add_spinslider(SP_ATTR_AZIMUTH, _("Azimuth"), 0, 360, 1, 1, 0);
945 _settings.add_spinslider(SP_ATTR_ELEVATION, _("Elevation"), 0, 360, 1, 1, 0);
947 _settings.type(LIGHT_POINT);
948 _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);
950 _settings.type(LIGHT_SPOT);
951 _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);
952 _settings.add_multispinbutton(/*default x:*/ (double) 0, /*default y:*/ (double) 0, /*default z:*/ (double) 0,
953 SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
954 _("Points At"), -99999, 99999, 1, 100, 0);
955 _settings.add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0);
956 _settings.add_spinslider(SP_ATTR_LIMITINGCONEANGLE, _("Cone Angle"), 1, 100, 1, 1, 0);
957 }
959 Gtk::VBox& get_box()
960 {
961 return _box;
962 }
963 protected:
964 Glib::ustring get_as_attribute() const
965 {
966 return "";
967 }
968 void set_from_attribute(SPObject* o)
969 {
970 if(_locked)
971 return;
973 _locked = true;
975 SPObject* child = o->children;
977 if(SP_IS_FEDISTANTLIGHT(child))
978 _light_source.set_active(0);
979 else if(SP_IS_FEPOINTLIGHT(child))
980 _light_source.set_active(1);
981 else if(SP_IS_FESPOTLIGHT(child))
982 _light_source.set_active(2);
983 else
984 _light_source.set_active(-1);
986 update();
988 _locked = false;
989 }
990 private:
991 void on_source_changed()
992 {
993 if(_locked)
994 return;
996 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
997 if(prim) {
998 _locked = true;
1000 SPObject* child = prim->children;
1001 const int ls = _light_source.get_active_row_number();
1002 // Check if the light source type has changed
1003 if(!(ls == -1 && !child) &&
1004 !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
1005 !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
1006 !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
1007 if(child)
1008 sp_repr_unparent(child->repr);
1010 if(ls != -1) {
1011 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1012 Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
1013 prim->repr->appendChild(repr);
1014 }
1016 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
1017 update();
1018 }
1020 _locked = false;
1021 }
1022 }
1024 void update()
1025 {
1026 _box.hide_all();
1027 _box.show();
1028 _light_box.show_all();
1030 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1031 if(prim && prim->children)
1032 _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1033 }
1035 FilterEffectsDialog& _dialog;
1036 Gtk::VBox _box;
1037 Settings _settings;
1038 Gtk::HBox _light_box;
1039 Gtk::Label _light_label;
1040 ComboBoxEnum<LightSource> _light_source;
1041 bool _locked;
1042 };
1044 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1045 {
1046 LightSourceControl* ls = new LightSourceControl(_dialog);
1047 add_attr_widget(ls);
1048 add_widget(&ls->get_box(), "");
1049 return ls;
1050 }
1052 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1053 sigc::slot<void> rem)
1054 {
1055 Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1057 menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1058 Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1059 menu->append(*mi);
1060 mi->signal_activate().connect(rem);
1061 mi->show();
1062 menu->accelerate(parent);
1064 return menu;
1065 }
1067 /*** FilterModifier ***/
1068 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1069 : _dialog(d), _add(Gtk::Stock::NEW), _observer(new SignalObserver)
1070 {
1071 Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1072 pack_start(*sw);
1073 pack_start(_add, false, false);
1074 sw->add(_list);
1076 _model = Gtk::ListStore::create(_columns);
1077 _list.set_model(_model);
1078 _cell_toggle.set_active(true);
1079 const int selcol = _list.append_column("", _cell_toggle);
1080 Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1081 if(col)
1082 col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1083 _list.append_column_editable(_("_Filter"), _columns.label);
1084 ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1085 signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1087 sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1088 sw->set_shadow_type(Gtk::SHADOW_IN);
1089 show_all_children();
1090 _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1091 _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1092 _list.signal_button_release_event().connect_notify(
1093 sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1094 _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1095 sigc::mem_fun(*this, &FilterModifier::remove_filter));
1096 _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1097 _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1098 _menu->accelerate(*this);
1100 _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1101 _observer->signal_changed().connect(signal_filter_changed().make_slot());
1102 g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1103 G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1105 g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1106 G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1108 on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1109 update_filters();
1110 }
1112 FilterEffectsDialog::FilterModifier::~FilterModifier()
1113 {
1114 _resource_changed.disconnect();
1115 _doc_replaced.disconnect();
1116 }
1118 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1119 {
1120 me->_doc_replaced.disconnect();
1121 me->_doc_replaced = desktop->connectDocumentReplaced(
1122 sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1124 me->_resource_changed.disconnect();
1125 me->_resource_changed =
1126 sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1127 sigc::mem_fun(me, &FilterModifier::update_filters));
1129 me->_dialog.setDesktop(desktop);
1131 me->update_filters();
1132 }
1135 // When the selection changes, show the active filter(s) in the dialog
1136 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1137 Selection *sel,
1138 FilterModifier* fm)
1139 {
1140 if(fm && sel)
1141 fm->update_selection(sel);
1142 }
1144 // Update each filter's sel property based on the current object selection;
1145 // If the filter is not used by any selected object, sel = 0,
1146 // otherwise sel is set to the total number of filters in use by selected objects
1147 // If only one filter is in use, it is selected
1148 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1149 {
1150 std::set<SPObject*> used;
1152 for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1153 SPObject *obj = SP_OBJECT (i->data);
1154 SPStyle *style = SP_OBJECT_STYLE (obj);
1155 if(!style || !SP_IS_ITEM(obj)) continue;
1157 if(style->filter.set && style->getFilter())
1158 used.insert(style->getFilter());
1159 else
1160 used.insert(0);
1161 }
1163 const int size = used.size();
1165 for(Gtk::TreeIter iter = _model->children().begin();
1166 iter != _model->children().end(); ++iter) {
1167 if(used.find((*iter)[_columns.filter]) != used.end()) {
1168 // If only one filter is in use by the selection, select it
1169 if(size == 1)
1170 _list.get_selection()->select(iter);
1171 (*iter)[_columns.sel] = size;
1172 }
1173 else
1174 (*iter)[_columns.sel] = 0;
1175 }
1176 }
1178 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1179 {
1180 _observer->set(get_selected_filter());
1181 signal_filter_changed()();
1182 }
1184 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1185 {
1186 Gtk::TreeModel::iterator iter = _model->get_iter(path);
1188 if(iter) {
1189 SPFilter* filter = (*iter)[_columns.filter];
1190 filter->setLabel(text.c_str());
1191 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1192 if(iter)
1193 (*iter)[_columns.label] = text;
1194 }
1195 }
1197 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1198 {
1199 Gtk::TreeIter iter = _model->get_iter(path);
1201 if(iter) {
1202 SPDesktop *desktop = _dialog.getDesktop();
1203 SPDocument *doc = sp_desktop_document(desktop);
1204 SPFilter* filter = (*iter)[_columns.filter];
1205 Inkscape::Selection *sel = sp_desktop_selection(desktop);
1207 /* If this filter is the only one used in the selection, unset it */
1208 if((*iter)[_columns.sel] == 1)
1209 filter = 0;
1211 GSList const *items = sel->itemList();
1213 for (GSList const *i = items; i != NULL; i = i->next) {
1214 SPItem * item = SP_ITEM(i->data);
1215 SPStyle *style = SP_OBJECT_STYLE(item);
1216 g_assert(style != NULL);
1218 if(filter)
1219 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1220 else
1221 ::remove_filter(item, false);
1223 SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1224 }
1226 update_selection(sel);
1227 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Apply filter"));
1228 }
1229 }
1231 /* Add all filters in the document to the combobox.
1232 Keeps the same selection if possible, otherwise selects the first element */
1233 void FilterEffectsDialog::FilterModifier::update_filters()
1234 {
1235 SPDesktop* desktop = _dialog.getDesktop();
1236 SPDocument* document = sp_desktop_document(desktop);
1237 const GSList* filters = sp_document_get_resource_list(document, "filter");
1239 _model->clear();
1241 for(const GSList *l = filters; l; l = l->next) {
1242 Gtk::TreeModel::Row row = *_model->append();
1243 SPFilter* f = (SPFilter*)l->data;
1244 row[_columns.filter] = f;
1245 const gchar* lbl = f->label();
1246 const gchar* id = SP_OBJECT_ID(f);
1247 row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1248 }
1250 update_selection(desktop->selection);
1251 _dialog.update_filter_general_settings_view();
1252 }
1254 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1255 {
1256 if(_list.get_selection()) {
1257 Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1259 if(i)
1260 return (*i)[_columns.filter];
1261 }
1263 return 0;
1264 }
1266 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1267 {
1268 if(filter) {
1269 for(Gtk::TreeModel::iterator i = _model->children().begin();
1270 i != _model->children().end(); ++i) {
1271 if((*i)[_columns.filter] == filter) {
1272 _list.get_selection()->select(i);
1273 break;
1274 }
1275 }
1276 }
1277 }
1279 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1280 {
1281 if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1282 const bool sensitive = get_selected_filter() != NULL;
1283 _menu->items()[0].set_sensitive(sensitive);
1284 _menu->items()[1].set_sensitive(sensitive);
1285 _menu->popup(event->button, event->time);
1286 }
1287 }
1289 void FilterEffectsDialog::FilterModifier::add_filter()
1290 {
1291 SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1292 SPFilter* filter = new_filter(doc);
1294 const int count = _model->children().size();
1295 std::ostringstream os;
1296 os << "filter" << count;
1297 filter->setLabel(os.str().c_str());
1299 update_filters();
1301 select_filter(filter);
1303 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1304 }
1306 void FilterEffectsDialog::FilterModifier::remove_filter()
1307 {
1308 SPFilter *filter = get_selected_filter();
1310 if(filter) {
1311 SPDocument* doc = filter->document;
1312 sp_repr_unparent(filter->repr);
1314 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1316 update_filters();
1317 }
1318 }
1320 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1321 {
1322 SPFilter* filter = get_selected_filter();
1324 if(filter) {
1325 Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1326 repr = repr->duplicate(repr->document());
1327 parent->appendChild(repr);
1329 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1331 update_filters();
1332 }
1333 }
1335 void FilterEffectsDialog::FilterModifier::rename_filter()
1336 {
1337 _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1338 }
1340 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1341 : Glib::ObjectBase(typeid(CellRendererConnection)),
1342 _primitive(*this, "primitive", 0)
1343 {}
1345 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1346 {
1347 return _primitive.get_proxy();
1348 }
1350 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1351 {
1352 _text_width = w;
1353 }
1355 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1356 {
1357 return _text_width;
1358 }
1360 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1361 Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1362 int* x_offset, int* y_offset, int* width, int* height) const
1363 {
1364 PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1366 if(x_offset)
1367 (*x_offset) = 0;
1368 if(y_offset)
1369 (*y_offset) = 0;
1370 if(width)
1371 (*width) = size * primlist.primitive_count() + _text_width * 7;
1372 if(height) {
1373 // Scale the height depending on the number of inputs, unless it's
1374 // the first primitive, in which case there are no connections
1375 SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1376 (*height) = size * input_count(prim);
1377 }
1378 }
1380 /*** PrimitiveList ***/
1381 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1382 : _dialog(d),
1383 _in_drag(0),
1384 _observer(new SignalObserver)
1385 {
1386 d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1388 add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1389 signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1391 _model = Gtk::ListStore::create(_columns);
1393 set_reorderable(true);
1395 set_model(_model);
1396 append_column(_("_Effect"), _columns.type);
1398 _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1399 get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1400 signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1402 _connection_cell.set_text_width(init_text());
1404 int cols_count = append_column(_("Connections"), _connection_cell);
1405 Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1406 if(col)
1407 col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1408 }
1410 // Sets up a vertical Pango context/layout, and returns the largest
1411 // width needed to render the FilterPrimitiveInput labels.
1412 int FilterEffectsDialog::PrimitiveList::init_text()
1413 {
1414 // Set up a vertical context+layout
1415 Glib::RefPtr<Pango::Context> context = create_pango_context();
1416 const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1417 context->set_matrix(matrix);
1418 _vertical_layout = Pango::Layout::create(context);
1420 int maxfont = 0;
1421 for(int i = 0; i < FPInputConverter.end; ++i) {
1422 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1423 int fontw, fonth;
1424 _vertical_layout->get_pixel_size(fontw, fonth);
1425 if(fonth > maxfont)
1426 maxfont = fonth;
1427 }
1429 return maxfont;
1430 }
1432 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1433 {
1434 return _signal_primitive_changed;
1435 }
1437 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1438 {
1439 _observer->set(get_selected());
1440 signal_primitive_changed()();
1441 _dialog._color_matrix_values->clear_store();
1442 }
1444 /* Add all filter primitives in the current to the list.
1445 Keeps the same selection if possible, otherwise selects the first element */
1446 void FilterEffectsDialog::PrimitiveList::update()
1447 {
1448 SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1449 const SPFilterPrimitive* active_prim = get_selected();
1450 bool active_found = false;
1452 _model->clear();
1454 if(f) {
1455 _dialog._primitive_box.set_sensitive(true);
1456 _dialog.update_filter_general_settings_view();
1457 for(SPObject *prim_obj = f->children;
1458 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1459 prim_obj = prim_obj->next) {
1460 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1461 if(prim) {
1462 Gtk::TreeModel::Row row = *_model->append();
1463 row[_columns.primitive] = prim;
1464 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1465 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1466 row[_columns.id] = SP_OBJECT_ID(prim);
1468 if(prim == active_prim) {
1469 get_selection()->select(row);
1470 active_found = true;
1471 }
1472 }
1473 }
1475 if(!active_found && _model->children().begin())
1476 get_selection()->select(_model->children().begin());
1478 columns_autosize();
1479 }
1480 else {
1481 _dialog._primitive_box.set_sensitive(false);
1482 }
1483 }
1485 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1486 {
1487 _primitive_menu = menu;
1488 }
1490 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1491 {
1492 if(_dialog._filter_modifier.get_selected_filter()) {
1493 Gtk::TreeModel::iterator i = get_selection()->get_selected();
1494 if(i)
1495 return (*i)[_columns.primitive];
1496 }
1498 return 0;
1499 }
1501 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1502 {
1503 for(Gtk::TreeIter i = _model->children().begin();
1504 i != _model->children().end(); ++i) {
1505 if((*i)[_columns.primitive] == prim)
1506 get_selection()->select(i);
1507 }
1508 }
1510 void FilterEffectsDialog::PrimitiveList::remove_selected()
1511 {
1512 SPFilterPrimitive* prim = get_selected();
1514 if(prim) {
1515 _observer->set(0);
1517 sp_repr_unparent(prim->repr);
1519 sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1520 _("Remove filter primitive"));
1522 update();
1523 }
1524 }
1526 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1527 {
1528 Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1529 Glib::RefPtr<Gdk::Window> win = get_bin_window();
1530 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1532 SPFilterPrimitive* prim = get_selected();
1533 int row_count = get_model()->children().size();
1535 int fheight = CellRendererConnection::size;
1536 Gdk::Rectangle rct, vis;
1537 Gtk::TreeIter row = get_model()->children().begin();
1538 int text_start_x = 0;
1539 if(row) {
1540 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1541 get_visible_rect(vis);
1542 int vis_x, vis_y;
1543 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1545 text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter.end + 1) + 1;
1546 for(int i = 0; i < FPInputConverter.end; ++i) {
1547 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1548 const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1549 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());
1550 get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1551 get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1552 }
1553 }
1555 int row_index = 0;
1556 for(; row != get_model()->children().end(); ++row, ++row_index) {
1557 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1558 const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1560 // Check mouse state
1561 int mx, my;
1562 Gdk::ModifierType mask;
1563 get_bin_window()->get_pointer(mx, my, mask);
1565 // Outline the bottom of the connection area
1566 const int outline_x = x + fheight * (row_count - row_index);
1567 get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1569 // Side outline
1570 get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1572 std::vector<Gdk::Point> con_poly;
1573 int con_drag_y = 0;
1574 bool inside;
1575 const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1576 const int inputs = input_count(row_prim);
1578 if(SP_IS_FEMERGE(row_prim)) {
1579 for(int i = 0; i < inputs; ++i) {
1580 inside = do_connection_node(row, i, con_poly, mx, my);
1581 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1582 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1583 inside, con_poly);
1585 if(_in_drag == (i + 1))
1586 con_drag_y = con_poly[2].get_y();
1588 if(_in_drag != (i + 1) || row_prim != prim)
1589 draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1590 }
1591 }
1592 else {
1593 // Draw "in" shape
1594 inside = do_connection_node(row, 0, con_poly, mx, my);
1595 con_drag_y = con_poly[2].get_y();
1596 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1597 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1598 inside, con_poly);
1600 // Draw "in" connection
1601 if(_in_drag != 1 || row_prim != prim)
1602 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1604 if(inputs == 2) {
1605 // Draw "in2" shape
1606 inside = do_connection_node(row, 1, con_poly, mx, my);
1607 if(_in_drag == 2)
1608 con_drag_y = con_poly[2].get_y();
1609 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1610 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1611 inside, con_poly);
1612 // Draw "in2" connection
1613 if(_in_drag != 2 || row_prim != prim)
1614 draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1615 }
1616 }
1618 // Draw drag connection
1619 if(row_prim == prim && _in_drag) {
1620 get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1621 mx, con_drag_y);
1622 get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1623 }
1624 }
1626 return true;
1627 }
1629 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1630 const int text_start_x, const int x1, const int y1,
1631 const int row_count)
1632 {
1633 int src_id = 0;
1634 Gtk::TreeIter res = find_result(input, attr, src_id);
1635 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1636 Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1637 Glib::RefPtr<Gdk::GC> gc;
1639 const bool is_first = input == get_model()->children().begin();
1640 const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1641 const bool use_default = !res && !is_merge;
1643 if(res == input || (use_default && is_first)) {
1644 // Draw straight connection to a standard input
1645 // Draw a lighter line for an implicit connection to a standard input
1646 const int tw = _connection_cell.get_text_width();
1647 gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1648 gc = (use_default && is_first) ? lightgc : darkgc;
1649 get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1650 get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1651 }
1652 else {
1653 // Draw an 'L'-shaped connection to another filter primitive
1654 // If no connection is specified, draw a light connection to the previous primitive
1655 gc = use_default ? lightgc : darkgc;
1657 if(use_default) {
1658 res = input;
1659 --res;
1660 }
1662 if(res) {
1663 Gdk::Rectangle rct;
1665 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1666 const int fheight = CellRendererConnection::size;
1668 get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1669 const int row_index = find_index(res);
1670 const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1671 const int y2 = rct.get_y() + rct.get_height();
1673 // Draw a bevelled 'L'-shaped connection
1674 get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1675 get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1676 get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1677 }
1678 }
1679 }
1681 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1682 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1683 std::vector<Gdk::Point>& points,
1684 const int ix, const int iy)
1685 {
1686 Gdk::Rectangle rct;
1687 const int icnt = input_count((*row)[_columns.primitive]);
1689 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1690 const int fheight = CellRendererConnection::size;
1692 get_cell_area(_model->get_path(row), *get_column(1), rct);
1693 const float h = rct.get_height() / icnt;
1695 const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1696 const int con_w = (int)(fheight * 0.35f);
1697 const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1698 points.clear();
1699 points.push_back(Gdk::Point(x, con_y));
1700 points.push_back(Gdk::Point(x, con_y + con_w * 2));
1701 points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1703 return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1704 }
1706 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1707 const int attr, int& src_id)
1708 {
1709 SPFilterPrimitive* prim = (*start)[_columns.primitive];
1710 Gtk::TreeIter target = _model->children().end();
1711 int image = 0;
1713 if(SP_IS_FEMERGE(prim)) {
1714 int c = 0;
1715 bool found = false;
1716 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1717 if(c == attr && SP_IS_FEMERGENODE(o)) {
1718 image = SP_FEMERGENODE(o)->input;
1719 found = true;
1720 }
1721 }
1722 if(!found)
1723 return target;
1724 }
1725 else {
1726 if(attr == SP_ATTR_IN)
1727 image = prim->image_in;
1728 else if(attr == SP_ATTR_IN2) {
1729 if(SP_IS_FEBLEND(prim))
1730 image = SP_FEBLEND(prim)->in2;
1731 else if(SP_IS_FECOMPOSITE(prim))
1732 image = SP_FECOMPOSITE(prim)->in2;
1733 else if(SP_IS_FEDISPLACEMENTMAP(prim))
1734 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1735 else
1736 return target;
1737 }
1738 else
1739 return target;
1740 }
1742 if(image >= 0) {
1743 for(Gtk::TreeIter i = _model->children().begin();
1744 i != start; ++i) {
1745 if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1746 target = i;
1747 }
1748 return target;
1749 }
1750 else if(image < -1) {
1751 src_id = -(image + 2);
1752 return start;
1753 }
1755 return target;
1756 }
1758 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1759 {
1760 int i = 0;
1761 for(Gtk::TreeIter iter = _model->children().begin();
1762 iter != target; ++iter, ++i);
1763 return i;
1764 }
1766 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1767 {
1768 Gtk::TreePath path;
1769 Gtk::TreeViewColumn* col;
1770 const int x = (int)e->x, y = (int)e->y;
1771 int cx, cy;
1773 _drag_prim = 0;
1775 if(get_path_at_pos(x, y, path, col, cx, cy)) {
1776 Gtk::TreeIter iter = _model->get_iter(path);
1777 std::vector<Gdk::Point> points;
1779 _drag_prim = (*iter)[_columns.primitive];
1780 const int icnt = input_count(_drag_prim);
1782 for(int i = 0; i < icnt; ++i) {
1783 if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1784 _in_drag = i + 1;
1785 break;
1786 }
1787 }
1789 queue_draw();
1790 }
1792 if(_in_drag) {
1793 _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1794 _autoscroll = 0;
1795 get_selection()->select(path);
1796 return true;
1797 }
1798 else
1799 return Gtk::TreeView::on_button_press_event(e);
1800 }
1802 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1803 {
1804 const int speed = 10;
1805 const int limit = 15;
1807 Gdk::Rectangle vis;
1808 get_visible_rect(vis);
1809 int vis_x, vis_y;
1810 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1811 const int top = vis_y + vis.get_height();
1813 // When autoscrolling during a connection drag, set the speed based on
1814 // where the mouse is in relation to the edges.
1815 if(e->y < vis_y)
1816 _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1817 else if(e->y < vis_y + limit)
1818 _autoscroll = -speed;
1819 else if(e->y > top)
1820 _autoscroll = (int)(speed + (e->y - top) / 5);
1821 else if(e->y > top - limit)
1822 _autoscroll = speed;
1823 else
1824 _autoscroll = 0;
1826 queue_draw();
1828 return Gtk::TreeView::on_motion_notify_event(e);
1829 }
1831 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1832 {
1833 SPFilterPrimitive *prim = get_selected(), *target;
1835 _scroll_connection.disconnect();
1837 if(_in_drag && prim) {
1838 Gtk::TreePath path;
1839 Gtk::TreeViewColumn* col;
1840 int cx, cy;
1842 if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1843 const gchar *in_val = 0;
1844 Glib::ustring result;
1845 Gtk::TreeIter target_iter = _model->get_iter(path);
1846 target = (*target_iter)[_columns.primitive];
1847 col = get_column(1);
1849 Gdk::Rectangle rct;
1850 get_cell_area(path, *col, rct);
1851 const int twidth = _connection_cell.get_text_width();
1852 const int sources_x = rct.get_width() - twidth * FPInputConverter.end;
1853 if(cx > sources_x) {
1854 int src = (cx - sources_x) / twidth;
1855 if(src < 0)
1856 src = 0;
1857 else if(src >= FPInputConverter.end)
1858 src = FPInputConverter.end - 1;
1859 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1860 in_val = result.c_str();
1861 }
1862 else {
1863 // Ensure that the target comes before the selected primitive
1864 for(Gtk::TreeIter iter = _model->children().begin();
1865 iter != get_selection()->get_selected(); ++iter) {
1866 if(iter == target_iter) {
1867 Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1868 // Make sure the target has a result
1869 const gchar *gres = repr->attribute("result");
1870 if(!gres) {
1871 result = "result" + Glib::Ascii::dtostr(SP_FILTER(prim->parent)->_image_number_next);
1872 repr->setAttribute("result", result.c_str());
1873 in_val = result.c_str();
1874 }
1875 else
1876 in_val = gres;
1877 break;
1878 }
1879 }
1880 }
1882 if(SP_IS_FEMERGE(prim)) {
1883 int c = 1;
1884 bool handled = false;
1885 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1886 if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1887 // If input is null, delete it
1888 if(!in_val) {
1889 sp_repr_unparent(o->repr);
1890 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1891 _("Remove merge node"));
1892 (*get_selection()->get_selected())[_columns.primitive] = prim;
1893 }
1894 else
1895 _dialog.set_attr(o, SP_ATTR_IN, in_val);
1896 handled = true;
1897 }
1898 }
1899 // Add new input?
1900 if(!handled && c == _in_drag && in_val) {
1901 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1902 Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1903 repr->setAttribute("inkscape:collect", "always");
1904 prim->repr->appendChild(repr);
1905 SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1906 Inkscape::GC::release(repr);
1907 _dialog.set_attr(node, SP_ATTR_IN, in_val);
1908 (*get_selection()->get_selected())[_columns.primitive] = prim;
1909 }
1910 }
1911 else {
1912 if(_in_drag == 1)
1913 _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1914 else if(_in_drag == 2)
1915 _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1916 }
1917 }
1919 _in_drag = 0;
1920 queue_draw();
1922 _dialog.update_settings_view();
1923 }
1925 if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1926 const bool sensitive = get_selected() != NULL;
1927 _primitive_menu->items()[0].set_sensitive(sensitive);
1928 _primitive_menu->items()[1].set_sensitive(sensitive);
1929 _primitive_menu->popup(e->button, e->time);
1931 return true;
1932 }
1933 else
1934 return Gtk::TreeView::on_button_release_event(e);
1935 }
1937 // Checks all of prim's inputs, removes any that use result
1938 void check_single_connection(SPFilterPrimitive* prim, const int result)
1939 {
1940 if(prim && result >= 0) {
1942 if(prim->image_in == result)
1943 SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1945 if(SP_IS_FEBLEND(prim)) {
1946 if(SP_FEBLEND(prim)->in2 == result)
1947 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1948 }
1949 else if(SP_IS_FECOMPOSITE(prim)) {
1950 if(SP_FECOMPOSITE(prim)->in2 == result)
1951 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1952 }
1953 else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
1954 if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
1955 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1956 }
1957 }
1958 }
1960 // Remove any connections going to/from prim_iter that forward-reference other primitives
1961 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
1962 {
1963 SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
1964 bool before = true;
1966 for(Gtk::TreeIter iter = _model->children().begin();
1967 iter != _model->children().end(); ++iter) {
1968 if(iter == prim_iter)
1969 before = false;
1970 else {
1971 SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
1972 if(before)
1973 check_single_connection(cur_prim, prim->image_out);
1974 else
1975 check_single_connection(prim, cur_prim->image_out);
1976 }
1977 }
1978 }
1980 // Reorder the filter primitives to match the list order
1981 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
1982 {
1983 SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
1984 int ndx = 0;
1986 for(Gtk::TreeModel::iterator iter = _model->children().begin();
1987 iter != _model->children().end(); ++iter, ++ndx) {
1988 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
1989 if(prim && prim == _drag_prim) {
1990 SP_OBJECT_REPR(prim)->setPosition(ndx);
1991 break;
1992 }
1993 }
1995 for(Gtk::TreeModel::iterator iter = _model->children().begin();
1996 iter != _model->children().end(); ++iter, ++ndx) {
1997 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
1998 if(prim && prim == _drag_prim) {
1999 sanitize_connections(iter);
2000 get_selection()->select(iter);
2001 break;
2002 }
2003 }
2005 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2007 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
2008 }
2010 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
2011 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
2012 {
2013 if(_autoscroll) {
2014 Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
2015 double v;
2017 v = a.get_value() + _autoscroll;
2018 if(v < 0)
2019 v = 0;
2020 if(v > a.get_upper() - a.get_page_size())
2021 v = a.get_upper() - a.get_page_size();
2023 a.set_value(v);
2025 queue_draw();
2026 }
2028 return true;
2029 }
2031 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2032 {
2033 return _model->children().size();
2034 }
2036 /*** FilterEffectsDialog ***/
2038 FilterEffectsDialog::FilterEffectsDialog()
2039 : UI::Widget::Panel("", "dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2040 _filter_modifier(*this),
2041 _primitive_list(*this),
2042 _add_primitive_type(FPConverter),
2043 _add_primitive(_("Add Effect:")),
2044 _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2045 _no_filter_selected(_("No filter selected"), Gtk::ALIGN_LEFT),
2046 _locked(false),
2047 _attr_lock(false),
2048 _settings_initialized(false)
2049 {
2050 _settings = new Settings(*this, _settings_tab1, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2051 NR_FILTER_ENDPRIMITIVETYPE);
2052 _filter_general_settings = new Settings(*this, _settings_tab2, sigc::mem_fun(*this, &FilterEffectsDialog::set_filternode_attr),
2053 1);
2054 _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2055 _sizegroup->set_ignore_hidden();
2057 _add_primitive_type.remove_row(NR_FILTER_TILE);
2058 _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2060 // Initialize widget hierarchy
2061 Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2062 Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2063 Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox);
2064 Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2066 _getContents()->add(*hpaned);
2067 hpaned->pack1(_filter_modifier);
2068 hpaned->pack2(_primitive_box);
2069 _primitive_box.pack_start(*sw_prims);
2070 _primitive_box.pack_start(*infobox,false, false);
2071 _primitive_box.pack_start(*hb_prims, false, false);
2072 sw_prims->add(_primitive_list);
2073 infobox->pack_start(_infobox_icon, false, false);
2074 infobox->pack_start(_infobox_desc, false, false);
2075 _infobox_desc.set_line_wrap(true);
2077 hb_prims->pack_end(_add_primitive_type, false, false);
2078 hb_prims->pack_end(_add_primitive, false, false);
2079 _getContents()->pack_start(_settings_tabs, false, false);
2080 _settings_tabs.append_page(_settings_tab1, _("Effect parameters"));
2081 _settings_tabs.append_page(_settings_tab2, _("Filter General Settings"));
2083 _primitive_list.signal_primitive_changed().connect(
2084 sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2085 _filter_modifier.signal_filter_changed().connect(
2086 sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2088 _add_primitive_type.signal_changed().connect(
2089 sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2091 sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2092 sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2093 // al_settings->set_padding(0, 0, 12, 0);
2094 // fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2095 // ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2096 _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2097 _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2098 sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2100 show_all_children();
2101 init_settings_widgets();
2102 _primitive_list.update();
2103 update_primitive_infobox();
2104 }
2106 FilterEffectsDialog::~FilterEffectsDialog()
2107 {
2108 delete _settings;
2109 delete _filter_general_settings;
2110 }
2112 void FilterEffectsDialog::set_attrs_locked(const bool l)
2113 {
2114 _locked = l;
2115 }
2117 void FilterEffectsDialog::show_all_vfunc()
2118 {
2119 UI::Widget::Panel::show_all_vfunc();
2121 update_settings_view();
2122 }
2124 void FilterEffectsDialog::init_settings_widgets()
2125 {
2126 // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2127 // most of the current values are complete guesses!
2129 _empty_settings.set_sensitive(false);
2130 _settings_tab1.pack_start(_empty_settings);
2132 _no_filter_selected.set_sensitive(false);
2133 _settings_tab2.pack_start(_no_filter_selected);
2134 _settings_initialized = true;
2136 _filter_general_settings->type(0);
2137 _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);
2138 _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);
2140 _settings->type(NR_FILTER_BLEND);
2141 _settings->add_combo(SP_ATTR_MODE, _("Mode"), BlendModeConverter);
2143 _settings->type(NR_FILTER_COLORMATRIX);
2144 ComboBoxEnum<FilterColorMatrixType>* colmat = _settings->add_combo(SP_ATTR_TYPE, _("Type"), ColorMatrixTypeConverter);
2145 _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s)"));
2146 colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2148 _settings->type(NR_FILTER_COMPONENTTRANSFER);
2149 _settings->add_notimplemented();
2150 /*_settings->add_combo(SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2151 _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2152 _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2153 _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2154 _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2155 _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2157 _settings->type(NR_FILTER_COMPOSITE);
2158 _settings->add_combo(SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
2159 _k1 = _settings->add_spinslider(SP_ATTR_K1, _("K1"), -10, 10, 0.1, 0.01, 2);
2160 _k2 = _settings->add_spinslider(SP_ATTR_K2, _("K2"), -10, 10, 0.1, 0.01, 2);
2161 _k3 = _settings->add_spinslider(SP_ATTR_K3, _("K3"), -10, 10, 0.1, 0.01, 2);
2162 _k4 = _settings->add_spinslider(SP_ATTR_K4, _("K4"), -10, 10, 0.1, 0.01, 2);
2164 _settings->type(NR_FILTER_CONVOLVEMATRIX);
2165 _convolve_order = _settings->add_dualspinbutton(SP_ATTR_ORDER, _("Size"), 1, 5, 1, 1, 0);
2166 _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);
2167 _convolve_matrix = _settings->add_matrix(SP_ATTR_KERNELMATRIX, _("Kernel"));
2168 _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2169 _settings->add_spinslider(SP_ATTR_DIVISOR, _("Divisor"), 1, 20, 1, 0.1, 2);
2170 _settings->add_spinslider(SP_ATTR_BIAS, _("Bias"), -10, 10, 1, 0.01, 1);
2171 _settings->add_combo(SP_ATTR_EDGEMODE, _("Edge Mode"), ConvolveMatrixEdgeModeConverter);
2172 _settings->add_checkbutton(SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false");
2174 _settings->type(NR_FILTER_DIFFUSELIGHTING);
2175 _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Diffuse Color"));
2176 _settings->add_spinslider(SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1);
2177 _settings->add_spinslider(SP_ATTR_DIFFUSECONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2);
2178 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2179 _settings->add_lightsource();
2181 _settings->type(NR_FILTER_DISPLACEMENTMAP);
2182 _settings->add_spinslider(SP_ATTR_SCALE, _("Scale"), 0, 100, 1, 0.01, 1);
2183 _settings->add_combo(SP_ATTR_XCHANNELSELECTOR, _("X Channel"), DisplacementMapChannelConverter);
2184 _settings->add_combo(SP_ATTR_YCHANNELSELECTOR, _("Y Channel"), DisplacementMapChannelConverter);
2186 _settings->type(NR_FILTER_FLOOD);
2187 _settings->add_color(SP_PROP_FLOOD_COLOR, _("Flood Color"));
2188 _settings->add_spinslider(SP_PROP_FLOOD_OPACITY, _("Opacity"), 0, 1, 0.1, 0.01, 2);
2190 _settings->type(NR_FILTER_GAUSSIANBLUR);
2191 _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation"), 0.01, 100, 1, 0.01, 1);
2193 _settings->type(NR_FILTER_MORPHOLOGY);
2194 _settings->add_combo(SP_ATTR_OPERATOR, _("Operator"), MorphologyOperatorConverter);
2195 _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius"), 0, 100, 1, 0.01, 1);
2197 _settings->type(NR_FILTER_IMAGE);
2198 _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image"));
2200 _settings->type(NR_FILTER_OFFSET);
2201 _settings->add_spinslider(SP_ATTR_DX, _("Delta X"), -100, 100, 1, 0.01, 1);
2202 _settings->add_spinslider(SP_ATTR_DY, _("Delta Y"), -100, 100, 1, 0.01, 1);
2204 _settings->type(NR_FILTER_SPECULARLIGHTING);
2205 _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Specular Color"));
2206 _settings->add_spinslider(SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1);
2207 _settings->add_spinslider(SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2);
2208 _settings->add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1);
2209 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2210 _settings->add_lightsource();
2212 _settings->type(NR_FILTER_TILE);
2213 _settings->add_notimplemented();
2215 _settings->type(NR_FILTER_TURBULENCE);
2216 _settings->add_checkbutton(SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2217 _settings->add_combo(SP_ATTR_TYPE, _("Type"), TurbulenceTypeConverter);
2218 _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency"), 0, 1, 0.001, 0.01, 3);
2219 _settings->add_spinslider(SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2220 _settings->add_spinslider(SP_ATTR_SEED, _("Seed"), 0, 1000, 1, 1, 0);
2221 }
2223 void FilterEffectsDialog::add_primitive()
2224 {
2225 SPFilter* filter = _filter_modifier.get_selected_filter();
2227 if(filter) {
2228 SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2230 _primitive_list.select(prim);
2232 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2233 }
2234 }
2236 void FilterEffectsDialog::update_primitive_infobox()
2237 {
2238 if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2239 _infobox_icon.show();
2240 _infobox_desc.show();
2241 } else {
2242 _infobox_icon.hide();
2243 _infobox_desc.hide();
2244 }
2245 switch(_add_primitive_type.get_active_data()->id){
2246 case(NR::NR_FILTER_BLEND):
2247 _infobox_icon.set(g_strdup_printf("%s/feBlend-icon.png", INKSCAPE_PIXMAPDIR));
2248 _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2249 break;
2250 case(NR::NR_FILTER_COLORMATRIX):
2251 _infobox_icon.set(g_strdup_printf("%s/feColorMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2252 _infobox_desc.set_markup(_("The <b>feColorMatrix</b> filter primitive applies a matrix transformation to colour of each rendered pixel. This allows for effects like turning object to grayscale, modifying colour saturation and changing colour hue."));
2253 break;
2254 case(NR::NR_FILTER_COMPONENTTRANSFER):
2255 _infobox_icon.set(g_strdup_printf("%s/feComponentTransfer-icon.png", INKSCAPE_PIXMAPDIR));
2256 _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."));
2257 break;
2258 case(NR::NR_FILTER_COMPOSITE):
2259 _infobox_icon.set(g_strdup_printf("%s/feComposite-icon.png", INKSCAPE_PIXMAPDIR));
2260 _infobox_desc.set_markup(_("The <b>feComposite</b> filter primitive composites two images using one of the Porter-Duff blending modes or the aritmetic mode described in SVG standard. Porter-Duff blending modes are essentially logical operations between the corresponding pixel values of the images."));
2261 break;
2262 case(NR::NR_FILTER_CONVOLVEMATRIX):
2263 _infobox_icon.set(g_strdup_printf("%s/feConvolveMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2264 _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."));
2265 break;
2266 case(NR::NR_FILTER_DIFFUSELIGHTING):
2267 _infobox_icon.set(g_strdup_printf("%s/feDiffuseLighting-icon.png", INKSCAPE_PIXMAPDIR));
2268 _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."));
2269 break;
2270 case(NR::NR_FILTER_DISPLACEMENTMAP):
2271 _infobox_icon.set(g_strdup_printf("%s/feDisplacementMap-icon.png", INKSCAPE_PIXMAPDIR));
2272 _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."));
2273 break;
2274 case(NR::NR_FILTER_FLOOD):
2275 _infobox_icon.set(g_strdup_printf("%s/feFlood-icon.png", INKSCAPE_PIXMAPDIR));
2276 _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."));
2277 break;
2278 case(NR::NR_FILTER_GAUSSIANBLUR):
2279 _infobox_icon.set(g_strdup_printf("%s/feGaussianBlur-icon.png", INKSCAPE_PIXMAPDIR));
2280 _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."));
2281 break;
2282 case(NR::NR_FILTER_IMAGE):
2283 _infobox_icon.set(g_strdup_printf("%s/feImage-icon.png", INKSCAPE_PIXMAPDIR));
2284 _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2285 break;
2286 case(NR::NR_FILTER_MERGE):
2287 _infobox_icon.set(g_strdup_printf("%s/feMerge-icon.png", INKSCAPE_PIXMAPDIR));
2288 _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."));
2289 break;
2290 case(NR::NR_FILTER_MORPHOLOGY):
2291 _infobox_icon.set(g_strdup_printf("%s/feMorphology-icon.png", INKSCAPE_PIXMAPDIR));
2292 _infobox_desc.set_markup(_("The <b>feMorphology</b> filter primitive provides erode and dilate effects. For single-colour objects erode makes the object thinner and dilate makes it thicker."));
2293 break;
2294 case(NR::NR_FILTER_OFFSET):
2295 _infobox_icon.set(g_strdup_printf("%s/feOffset-icon.png", INKSCAPE_PIXMAPDIR));
2296 _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."));
2297 break;
2298 case(NR::NR_FILTER_SPECULARLIGHTING):
2299 _infobox_icon.set(g_strdup_printf("%s/feSpecularLighting-icon.png", INKSCAPE_PIXMAPDIR));
2300 _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."));
2301 break;
2302 case(NR::NR_FILTER_TILE):
2303 _infobox_icon.set(g_strdup_printf("%s/feTile-icon.png", INKSCAPE_PIXMAPDIR));
2304 _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2305 break;
2306 case(NR::NR_FILTER_TURBULENCE):
2307 _infobox_icon.set(g_strdup_printf("%s/feTurbulence-icon.png", INKSCAPE_PIXMAPDIR));
2308 _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."));
2309 break;
2310 default:
2311 g_assert(false);
2312 break;
2313 }
2314 }
2316 void FilterEffectsDialog::duplicate_primitive()
2317 {
2318 SPFilter* filter = _filter_modifier.get_selected_filter();
2319 SPFilterPrimitive* origprim = _primitive_list.get_selected();
2321 if(filter && origprim) {
2322 Inkscape::XML::Node *repr;
2323 repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2324 SP_OBJECT_REPR(filter)->appendChild(repr);
2326 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2328 _primitive_list.update();
2329 }
2330 }
2332 void FilterEffectsDialog::convolve_order_changed()
2333 {
2334 _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2335 _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2336 _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2337 }
2339 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2340 {
2341 set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2342 }
2344 void FilterEffectsDialog::set_filternode_attr(const AttrWidget* input)
2345 {
2346 if(!_locked) {
2347 _attr_lock = true;
2348 SPFilter *filter = _filter_modifier.get_selected_filter();
2349 const gchar* name = (const gchar*)sp_attribute_name(input->get_attribute());
2350 if (filter && name && SP_OBJECT_REPR(filter)){
2351 SP_OBJECT_REPR(filter)->setAttribute(name, input->get_as_attribute().c_str());
2352 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2353 }
2354 _attr_lock = false;
2355 }
2356 }
2358 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2359 {
2360 set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2361 }
2363 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2364 {
2365 if(!_locked) {
2366 _attr_lock = true;
2368 SPFilter *filter = _filter_modifier.get_selected_filter();
2369 const gchar* name = (const gchar*)sp_attribute_name(attr);
2370 if(filter && name && o) {
2371 update_settings_sensitivity();
2373 SP_OBJECT_REPR(o)->setAttribute(name, val);
2374 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2376 Glib::ustring undokey = "filtereffects:";
2377 undokey += name;
2378 sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2379 _("Set filter primitive attribute"));
2380 }
2382 _attr_lock = false;
2383 }
2384 }
2386 void FilterEffectsDialog::update_filter_general_settings_view()
2387 {
2388 if(_settings_initialized != true) return;
2390 if(!_locked) {
2391 _attr_lock = true;
2393 SPFilter* filter = _filter_modifier.get_selected_filter();
2395 if(filter) {
2396 _filter_general_settings->show_and_update(0, filter);
2397 _no_filter_selected.hide();
2398 }
2399 else {
2400 std::vector<Gtk::Widget*> vect = _settings_tab2.get_children();
2401 vect[0]->hide_all();
2402 _no_filter_selected.show();
2403 }
2405 _attr_lock = false;
2406 }
2407 }
2409 void FilterEffectsDialog::update_settings_view()
2410 {
2411 update_settings_sensitivity();
2413 if(_attr_lock)
2414 return;
2416 //First Tab
2418 std::vector<Gtk::Widget*> vect1 = _settings_tab1.get_children();
2419 for(unsigned int i=0; i<vect1.size(); i++) vect1[i]->hide_all();
2420 _empty_settings.show();
2422 if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2423 _infobox_icon.show();
2424 _infobox_desc.show();
2425 } else {
2426 _infobox_icon.hide();
2427 _infobox_desc.hide();
2428 }
2430 SPFilterPrimitive* prim = _primitive_list.get_selected();
2432 if(prim) {
2433 _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2434 _empty_settings.hide();
2435 }
2437 //Second Tab
2439 std::vector<Gtk::Widget*> vect2 = _settings_tab2.get_children();
2440 vect2[0]->hide_all();
2441 _no_filter_selected.show();
2443 SPFilter* filter = _filter_modifier.get_selected_filter();
2445 if(filter) {
2446 _filter_general_settings->show_and_update(0, filter);
2447 _no_filter_selected.hide();
2448 }
2450 }
2452 void FilterEffectsDialog::update_settings_sensitivity()
2453 {
2454 SPFilterPrimitive* prim = _primitive_list.get_selected();
2455 const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2456 _k1->set_sensitive(use_k);
2457 _k2->set_sensitive(use_k);
2458 _k3->set_sensitive(use_k);
2459 _k4->set_sensitive(use_k);
2461 // Component transfer not yet implemented
2462 /*
2463 if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2464 SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2465 const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2466 const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2468 _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2469 _ct_slope->set_sensitive(linear);
2470 _ct_intercept->set_sensitive(linear);
2471 _ct_amplitude->set_sensitive(gamma);
2472 _ct_exponent->set_sensitive(gamma);
2473 _ct_offset->set_sensitive(gamma);
2474 }
2475 */
2476 }
2478 void FilterEffectsDialog::update_color_matrix()
2479 {
2480 _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2481 }
2483 } // namespace Dialog
2484 } // namespace UI
2485 } // namespace Inkscape
2487 /*
2488 Local Variables:
2489 mode:c++
2490 c-file-style:"stroustrup"
2491 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2492 indent-tabs-mode:nil
2493 fill-column:99
2494 End:
2495 */
2496 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :