1 /**
2 * \brief Filter Effects dialog
3 *
4 * Authors:
5 * Nicholas Bishop <nicholasbishop@gmail.org>
6 *
7 * Copyright (C) 2007 Authors
8 *
9 * Released under GNU GPL. Read the file 'COPYING' for more information.
10 */
12 #ifdef HAVE_CONFIG_H
13 # include <config.h>
14 #endif
16 #include <gtk/gtktreeview.h>
17 #include <gtkmm/cellrenderertext.h>
18 #include <gtkmm/colorbutton.h>
19 #include <gtkmm/messagedialog.h>
20 #include <gtkmm/paned.h>
21 #include <gtkmm/scale.h>
22 #include <gtkmm/scrolledwindow.h>
23 #include <gtkmm/spinbutton.h>
24 #include <gtkmm/stock.h>
25 #include <glibmm/i18n.h>
27 #include "application/application.h"
28 #include "application/editor.h"
29 #include "desktop.h"
30 #include "desktop-handles.h"
31 #include "dialog-manager.h"
32 #include "dir-util.h"
33 #include "document.h"
34 #include "filter-chemistry.h"
35 #include "filter-effects-dialog.h"
36 #include "filter-enums.h"
37 #include "inkscape.h"
38 #include "path-prefix.h"
39 #include "prefs-utils.h"
40 #include "selection.h"
41 #include "sp-feblend.h"
42 #include "sp-fecolormatrix.h"
43 #include "sp-fecomponenttransfer.h"
44 #include "sp-fecomposite.h"
45 #include "sp-feconvolvematrix.h"
46 #include "sp-fedisplacementmap.h"
47 #include "sp-fedistantlight.h"
48 #include "sp-femerge.h"
49 #include "sp-femergenode.h"
50 #include "sp-feoffset.h"
51 #include "sp-fepointlight.h"
52 #include "sp-fespotlight.h"
53 #include "sp-filter-primitive.h"
54 #include "sp-gaussian-blur.h"
56 #include "style.h"
57 #include "svg/svg-color.h"
58 #include "ui/dialog/filedialog.h"
59 #include "verbs.h"
60 #include "xml/node.h"
61 #include "xml/node-observer.h"
62 #include "xml/repr.h"
63 #include <sstream>
65 #include "io/sys.h"
66 #include <iostream>
68 using namespace NR;
70 namespace Inkscape {
71 namespace UI {
72 namespace Dialog {
74 // Returns the number of inputs available for the filter primitive type
75 int input_count(const SPFilterPrimitive* prim)
76 {
77 if(!prim)
78 return 0;
79 else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
80 return 2;
81 else if(SP_IS_FEMERGE(prim)) {
82 // Return the number of feMergeNode connections plus an extra
83 int count = 1;
84 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count);
85 return count;
86 }
87 else
88 return 1;
89 }
91 // Very simple observer that just emits a signal if anything happens to a node
92 class FilterEffectsDialog::SignalObserver : public XML::NodeObserver
93 {
94 public:
95 SignalObserver()
96 : _oldsel(0)
97 {}
99 // Add this observer to the SPObject and remove it from any previous object
100 void set(SPObject* o)
101 {
102 if(_oldsel && _oldsel->repr)
103 _oldsel->repr->removeObserver(*this);
104 if(o && o->repr)
105 o->repr->addObserver(*this);
106 _oldsel = o;
107 }
109 void notifyChildAdded(XML::Node&, XML::Node&, XML::Node*)
110 { signal_changed()(); }
112 void notifyChildRemoved(XML::Node&, XML::Node&, XML::Node*)
113 { signal_changed()(); }
115 void notifyChildOrderChanged(XML::Node&, XML::Node&, XML::Node*, XML::Node*)
116 { signal_changed()(); }
118 void notifyContentChanged(XML::Node&, Util::ptr_shared<char>, Util::ptr_shared<char>)
119 {}
121 void notifyAttributeChanged(XML::Node&, GQuark, Util::ptr_shared<char>, Util::ptr_shared<char>)
122 { signal_changed()(); }
124 sigc::signal<void>& signal_changed()
125 {
126 return _signal_changed;
127 }
128 private:
129 sigc::signal<void> _signal_changed;
130 SPObject* _oldsel;
131 };
133 class CheckButtonAttr : public Gtk::CheckButton, public AttrWidget
134 {
135 public:
136 CheckButtonAttr(const Glib::ustring& label,
137 const Glib::ustring& tv, const Glib::ustring& fv,
138 const SPAttributeEnum a)
139 : Gtk::CheckButton(label),
140 AttrWidget(a),
141 _true_val(tv), _false_val(fv)
142 {
143 signal_toggled().connect(signal_attr_changed().make_slot());
144 }
146 Glib::ustring get_as_attribute() const
147 {
148 return get_active() ? _true_val : _false_val;
149 }
151 void set_from_attribute(SPObject* o)
152 {
153 const gchar* val = attribute_value(o);
154 if(val) {
155 if(_true_val == val)
156 set_active(true);
157 else if(_false_val == val)
158 set_active(false);
159 }
160 }
161 private:
162 const Glib::ustring _true_val, _false_val;
163 };
165 class SpinButtonAttr : public Gtk::SpinButton, public AttrWidget
166 {
167 public:
168 SpinButtonAttr(double lower, double upper, double step_inc,
169 double climb_rate, int digits, const SPAttributeEnum a)
170 : Gtk::SpinButton(climb_rate, digits),
171 AttrWidget(a)
172 {
173 set_range(lower, upper);
174 set_increments(step_inc, step_inc * 5);
176 signal_value_changed().connect(signal_attr_changed().make_slot());
177 }
179 Glib::ustring get_as_attribute() const
180 {
181 const double val = get_value();
183 if(get_digits() == 0)
184 return Glib::Ascii::dtostr((int)val);
185 else
186 return Glib::Ascii::dtostr(val);
187 }
189 void set_from_attribute(SPObject* o)
190 {
191 const gchar* val = attribute_value(o);
192 if(val)
193 set_value(Glib::Ascii::strtod(val));
194 }
195 };
197 // Contains an arbitrary number of spin buttons that use seperate attributes
198 class MultiSpinButton : public Gtk::HBox
199 {
200 public:
201 MultiSpinButton(double lower, double upper, double step_inc,
202 double climb_rate, int digits, std::vector<SPAttributeEnum> attrs)
203 {
204 for(unsigned i = 0; i < attrs.size(); ++i) {
205 _spins.push_back(new SpinButtonAttr(lower, upper, step_inc, climb_rate, digits, attrs[i]));
206 pack_start(*_spins.back(), false, false);
207 }
208 }
210 ~MultiSpinButton()
211 {
212 for(unsigned i = 0; i < _spins.size(); ++i)
213 delete _spins[i];
214 }
216 std::vector<SpinButtonAttr*>& get_spinbuttons()
217 {
218 return _spins;
219 }
220 private:
221 std::vector<SpinButtonAttr*> _spins;
222 };
224 // Contains two spinbuttons that describe a NumberOptNumber
225 class DualSpinButton : public Gtk::HBox, public AttrWidget
226 {
227 public:
228 DualSpinButton(double lower, double upper, double step_inc,
229 double climb_rate, int digits, const SPAttributeEnum a)
230 : AttrWidget(a),
231 _s1(climb_rate, digits), _s2(climb_rate, digits)
232 {
233 _s1.set_range(lower, upper);
234 _s2.set_range(lower, upper);
235 _s1.set_increments(step_inc, step_inc * 5);
236 _s2.set_increments(step_inc, step_inc * 5);
238 _s1.signal_value_changed().connect(signal_attr_changed().make_slot());
239 _s2.signal_value_changed().connect(signal_attr_changed().make_slot());
241 pack_start(_s1, false, false);
242 pack_start(_s2, false, false);
243 }
245 Gtk::SpinButton& get_spinbutton1()
246 {
247 return _s1;
248 }
250 Gtk::SpinButton& get_spinbutton2()
251 {
252 return _s2;
253 }
255 virtual Glib::ustring get_as_attribute() const
256 {
257 double v1 = _s1.get_value();
258 double v2 = _s2.get_value();
260 if(_s1.get_digits() == 0) {
261 v1 = (int)v1;
262 v2 = (int)v2;
263 }
265 return Glib::Ascii::dtostr(v1) + " " + Glib::Ascii::dtostr(v2);
266 }
268 virtual void set_from_attribute(SPObject* o)
269 {
270 const gchar* val = attribute_value(o);
271 if(val) {
272 NumberOptNumber n;
273 n.set(val);
274 _s1.set_value(n.getNumber());
275 _s2.set_value(n.getOptNumber());
276 }
277 }
278 private:
279 Gtk::SpinButton _s1, _s2;
280 };
282 class ColorButton : public Gtk::ColorButton, public AttrWidget
283 {
284 public:
285 ColorButton(const SPAttributeEnum a)
286 : AttrWidget(a)
287 {
288 signal_color_set().connect(signal_attr_changed().make_slot());
290 Gdk::Color col;
291 col.set_rgb(65535, 65535, 65535);
292 set_color(col);
293 }
295 // Returns the color in 'rgb(r,g,b)' form.
296 Glib::ustring get_as_attribute() const
297 {
298 std::ostringstream os;
299 const Gdk::Color c = get_color();
300 const int r = c.get_red() / 257, g = c.get_green() / 257, b = c.get_blue() / 257;
301 os << "rgb(" << r << "," << g << "," << b << ")";
302 return os.str();
303 }
306 void set_from_attribute(SPObject* o)
307 {
308 const gchar* val = attribute_value(o);
309 if(val) {
310 const guint32 i = sp_svg_read_color(val, 0xFFFFFFFF);
311 const int r = SP_RGBA32_R_U(i), g = SP_RGBA32_G_U(i), b = SP_RGBA32_B_U(i);
312 Gdk::Color col;
313 col.set_rgb(r * 257, g * 257, b * 257);
314 set_color(col);
315 }
316 }
317 };
319 /* Displays/Edits the matrix for feConvolveMatrix or feColorMatrix */
320 class FilterEffectsDialog::MatrixAttr : public Gtk::Frame, public AttrWidget
321 {
322 public:
323 MatrixAttr(const SPAttributeEnum a)
324 : AttrWidget(a), _locked(false)
325 {
326 _model = Gtk::ListStore::create(_columns);
327 _tree.set_model(_model);
328 _tree.set_headers_visible(false);
329 _tree.show();
330 add(_tree);
331 set_shadow_type(Gtk::SHADOW_IN);
332 }
334 std::vector<double> get_values() const
335 {
336 std::vector<double> vec;
337 for(Gtk::TreeIter iter = _model->children().begin();
338 iter != _model->children().end(); ++iter) {
339 for(unsigned c = 0; c < _tree.get_columns().size(); ++c)
340 vec.push_back((*iter)[_columns.cols[c]]);
341 }
342 return vec;
343 }
345 void set_values(const std::vector<double>& v)
346 {
347 unsigned i = 0;
348 for(Gtk::TreeIter iter = _model->children().begin();
349 iter != _model->children().end(); ++iter) {
350 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
351 if(i >= v.size())
352 return;
353 (*iter)[_columns.cols[c]] = v[i];
354 ++i;
355 }
356 }
357 }
359 Glib::ustring get_as_attribute() const
360 {
361 std::ostringstream os;
363 for(Gtk::TreeIter iter = _model->children().begin();
364 iter != _model->children().end(); ++iter) {
365 for(unsigned c = 0; c < _tree.get_columns().size(); ++c) {
366 os << (*iter)[_columns.cols[c]] << " ";
367 }
368 }
370 return os.str();
371 }
373 void set_from_attribute(SPObject* o)
374 {
375 if(o) {
376 if(SP_IS_FECONVOLVEMATRIX(o)) {
377 SPFeConvolveMatrix* conv = SP_FECONVOLVEMATRIX(o);
378 int cols, rows;
379 cols = (int)conv->order.getNumber();
380 if(cols > 5)
381 cols = 5;
382 rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
383 update(o, rows, cols);
384 }
385 else if(SP_IS_FECOLORMATRIX(o))
386 update(o, 4, 5);
387 }
388 }
389 private:
390 class MatrixColumns : public Gtk::TreeModel::ColumnRecord
391 {
392 public:
393 MatrixColumns()
394 {
395 cols.resize(5);
396 for(unsigned i = 0; i < cols.size(); ++i)
397 add(cols[i]);
398 }
399 std::vector<Gtk::TreeModelColumn<double> > cols;
400 };
402 void update(SPObject* o, const int rows, const int cols)
403 {
404 if(_locked)
405 return;
407 _model->clear();
409 _tree.remove_all_columns();
411 std::vector<gdouble>* values = NULL;
412 if(SP_IS_FECOLORMATRIX(o))
413 values = &SP_FECOLORMATRIX(o)->values;
414 else if(SP_IS_FECONVOLVEMATRIX(o))
415 values = &SP_FECONVOLVEMATRIX(o)->kernelMatrix;
416 else
417 return;
419 if(o) {
420 int ndx = 0;
422 for(int i = 0; i < cols; ++i) {
423 _tree.append_column_numeric_editable("", _columns.cols[i], "%.2f");
424 dynamic_cast<Gtk::CellRendererText*>(
425 _tree.get_column_cell_renderer(i))->signal_edited().connect(
426 sigc::mem_fun(*this, &MatrixAttr::rebind));
427 }
429 for(int r = 0; r < rows; ++r) {
430 Gtk::TreeRow row = *(_model->append());
431 // Default to identity matrix
432 for(int c = 0; c < cols; ++c, ++ndx)
433 row[_columns.cols[c]] = ndx < (int)values->size() ? (*values)[ndx] : (r == c ? 1 : 0);
434 }
435 }
436 }
438 void rebind(const Glib::ustring&, const Glib::ustring&)
439 {
440 _locked = true;
441 signal_attr_changed()();
442 _locked = false;
443 }
445 bool _locked;
446 Gtk::TreeView _tree;
447 Glib::RefPtr<Gtk::ListStore> _model;
448 MatrixColumns _columns;
449 };
451 // Displays a matrix or a slider for feColorMatrix
452 class FilterEffectsDialog::ColorMatrixValues : public Gtk::Frame, public AttrWidget
453 {
454 public:
455 ColorMatrixValues()
456 : AttrWidget(SP_ATTR_VALUES),
457 _matrix(SP_ATTR_VALUES),
458 _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES),
459 _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES),
460 _label(_("None"), Gtk::ALIGN_LEFT),
461 _use_stored(false),
462 _saturation_store(0),
463 _angle_store(0)
464 {
465 _matrix.signal_attr_changed().connect(signal_attr_changed().make_slot());
466 _saturation.signal_attr_changed().connect(signal_attr_changed().make_slot());
467 _angle.signal_attr_changed().connect(signal_attr_changed().make_slot());
468 signal_attr_changed().connect(sigc::mem_fun(*this, &ColorMatrixValues::update_store));
470 _matrix.show();
471 _saturation.show();
472 _angle.show();
473 _label.show();
474 _label.set_sensitive(false);
476 set_shadow_type(Gtk::SHADOW_NONE);
477 }
479 virtual void set_from_attribute(SPObject* o)
480 {
481 if(SP_IS_FECOLORMATRIX(o)) {
482 SPFeColorMatrix* col = SP_FECOLORMATRIX(o);
483 remove();
484 switch(col->type) {
485 case COLORMATRIX_SATURATE:
486 add(_saturation);
487 if(_use_stored)
488 _saturation.set_value(_saturation_store);
489 else
490 _saturation.set_from_attribute(o);
491 break;
492 case COLORMATRIX_HUEROTATE:
493 add(_angle);
494 if(_use_stored)
495 _angle.set_value(_angle_store);
496 else
497 _angle.set_from_attribute(o);
498 break;
499 case COLORMATRIX_LUMINANCETOALPHA:
500 add(_label);
501 break;
502 case COLORMATRIX_MATRIX:
503 default:
504 add(_matrix);
505 if(_use_stored)
506 _matrix.set_values(_matrix_store);
507 else
508 _matrix.set_from_attribute(o);
509 break;
510 }
511 _use_stored = true;
512 }
513 }
515 virtual Glib::ustring get_as_attribute() const
516 {
517 const Widget* w = get_child();
518 if(w == &_label)
519 return "";
520 else
521 return dynamic_cast<const AttrWidget*>(w)->get_as_attribute();
522 }
524 void clear_store()
525 {
526 _use_stored = false;
527 }
528 private:
529 void update_store()
530 {
531 const Widget* w = get_child();
532 if(w == &_matrix)
533 _matrix_store = _matrix.get_values();
534 else if(w == &_saturation)
535 _saturation_store = _saturation.get_value();
536 else if(w == &_angle)
537 _angle_store = _angle.get_value();
538 }
540 MatrixAttr _matrix;
541 SpinSlider _saturation;
542 SpinSlider _angle;
543 Gtk::Label _label;
545 // Store separate values for the different color modes
546 bool _use_stored;
547 std::vector<double> _matrix_store;
548 double _saturation_store;
549 double _angle_store;
550 };
552 static Inkscape::UI::Dialog::FileOpenDialog * selectFeImageFileInstance = NULL;
554 //Displays a chooser for feImage input
555 //It may be a filename or the id for an SVG Element
556 //described in xlink:href syntax
557 class FileOrElementChooser : public Gtk::HBox, public AttrWidget
558 {
559 public:
560 FileOrElementChooser(const SPAttributeEnum a)
561 : AttrWidget(a)
562 {
563 pack_start(_entry, false, false);
564 pack_start(_fromFile, false, false);
565 //pack_start(_fromSVGElement, false, false);
567 _fromFile.set_label(_("Image File"));
568 _fromFile.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_file));
570 _fromSVGElement.set_label(_("Selected SVG Element"));
571 _fromSVGElement.signal_clicked().connect(sigc::mem_fun(*this, &FileOrElementChooser::select_svg_element));
573 _entry.signal_changed().connect(signal_attr_changed().make_slot());
575 show_all();
577 }
579 // Returns the element in xlink:href form.
580 Glib::ustring get_as_attribute() const
581 {
582 return _entry.get_text();
583 }
586 void set_from_attribute(SPObject* o)
587 {
588 const gchar* val = attribute_value(o);
589 if(val) {
590 _entry.set_text(val);
591 }
592 }
594 void set_desktop(SPDesktop* d){
595 _desktop = d;
596 }
598 private:
599 void select_svg_element(){
600 Inkscape::Selection* sel = sp_desktop_selection(_desktop);
601 if (sel->isEmpty()) return;
602 Inkscape::XML::Node* node = (Inkscape::XML::Node*) g_slist_nth_data((GSList *)sel->reprList(), 0);
603 if (!node || !node->matchAttributeName("id")) return;
605 std::ostringstream xlikhref;
606 xlikhref << "#(" << node->attribute("id") << ")";
607 _entry.set_text(xlikhref.str());
608 }
610 void select_file(){
612 //# Get the current directory for finding files
613 Glib::ustring open_path;
614 char *attr = (char *)prefs_get_string_attribute("dialogs.open", "path");
615 if (attr)
616 open_path = attr;
618 //# Test if the open_path directory exists
619 if (!Inkscape::IO::file_test(open_path.c_str(),
620 (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
621 open_path = "";
623 //# If no open path, default to our home directory
624 if (open_path.size() < 1)
625 {
626 open_path = g_get_home_dir();
627 open_path.append(G_DIR_SEPARATOR_S);
628 }
630 //# Create a dialog if we don't already have one
631 if (!selectFeImageFileInstance) {
632 selectFeImageFileInstance =
633 Inkscape::UI::Dialog::FileOpenDialog::create(
634 *_desktop->getToplevel(),
635 open_path,
636 Inkscape::UI::Dialog::SVG_TYPES,/*TODO: any image, not justy svg*/
637 (char const *)_("Select an image to be used as feImage input"));
638 }
640 //# Show the dialog
641 bool const success = selectFeImageFileInstance->show();
642 if (!success)
643 return;
645 //# User selected something. Get name and type
646 Glib::ustring fileName = selectFeImageFileInstance->getFilename();
648 if (fileName.size() > 0) {
650 Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
652 if ( newFileName.size() > 0)
653 fileName = newFileName;
654 else
655 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
657 open_path = fileName;
658 open_path.append(G_DIR_SEPARATOR_S);
659 prefs_set_string_attribute("dialogs.open", "path", open_path.c_str());
661 _entry.set_text(fileName);
662 }
663 return;
664 }
666 Gtk::Entry _entry;
667 Gtk::Button _fromFile;
668 Gtk::Button _fromSVGElement;
669 SPDesktop* _desktop;
670 };
672 class FilterEffectsDialog::Settings
673 {
674 public:
675 typedef sigc::slot<void, const AttrWidget*> SetAttrSlot;
677 Settings(FilterEffectsDialog& d, Gtk::Box& b, SetAttrSlot slot, const int maxtypes)
678 : _dialog(d), _set_attr_slot(slot), _current_type(-1), _max_types(maxtypes)
679 {
680 _groups.resize(_max_types);
681 _attrwidgets.resize(_max_types);
683 for(int i = 0; i < _max_types; ++i) {
684 _groups[i] = new Gtk::VBox;
685 b.add(*_groups[i]);
686 }
687 }
689 ~Settings()
690 {
691 for(int i = 0; i < _max_types; ++i) {
692 delete _groups[i];
693 for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
694 delete _attrwidgets[i][j];
695 }
696 }
698 // Show the active settings group and update all the AttrWidgets with new values
699 void show_and_update(const int t, SPObject* ob)
700 {
701 if(t != _current_type) {
702 type(t);
703 for(unsigned i = 0; i < _groups.size(); ++i)
704 _groups[i]->hide();
705 }
706 if(t >= 0)
707 _groups[t]->show_all();
709 _dialog.set_attrs_locked(true);
710 for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
711 _attrwidgets[_current_type][i]->set_from_attribute(ob);
712 _dialog.set_attrs_locked(false);
713 }
715 int get_current_type() const
716 {
717 return _current_type;
718 }
720 void type(const int t)
721 {
722 _current_type = t;
723 }
725 void add_notimplemented()
726 {
727 Gtk::Label* lbl = Gtk::manage(new Gtk::Label("This SVG filter effect is not yet implemented in Inkscape."));
729 add_widget(lbl, "");
730 }
732 // LightSource
733 LightSourceControl* add_lightsource();
735 // CheckBox
736 CheckButtonAttr* add_checkbutton(const SPAttributeEnum attr, const Glib::ustring& label,
737 const Glib::ustring& tv, const Glib::ustring& fv)
738 {
739 CheckButtonAttr* cb = new CheckButtonAttr(label, tv, fv, attr);
740 add_widget(cb, "");
741 add_attr_widget(cb);
742 return cb;
743 }
745 // ColorButton
746 ColorButton* add_color(const SPAttributeEnum attr, const Glib::ustring& label)
747 {
748 ColorButton* col = new ColorButton(attr);
749 add_widget(col, label);
750 add_attr_widget(col);
751 return col;
752 }
754 // Matrix
755 MatrixAttr* add_matrix(const SPAttributeEnum attr, const Glib::ustring& label)
756 {
757 MatrixAttr* conv = new MatrixAttr(attr);
758 add_widget(conv, label);
759 add_attr_widget(conv);
760 return conv;
761 }
763 // ColorMatrixValues
764 ColorMatrixValues* add_colormatrixvalues(const Glib::ustring& label)
765 {
766 ColorMatrixValues* cmv = new ColorMatrixValues;
767 add_widget(cmv, label);
768 add_attr_widget(cmv);
769 return cmv;
770 }
772 // SpinSlider
773 SpinSlider* add_spinslider(const SPAttributeEnum attr, const Glib::ustring& label,
774 const double lo, const double hi, const double step_inc, const double climb, const int digits)
775 {
776 SpinSlider* spinslider = new SpinSlider(lo, lo, hi, step_inc, climb, digits, attr);
777 add_widget(spinslider, label);
778 add_attr_widget(spinslider);
779 return spinslider;
780 }
782 // DualSpinSlider
783 DualSpinSlider* add_dualspinslider(const SPAttributeEnum attr, const Glib::ustring& label,
784 const double lo, const double hi, const double step_inc,
785 const double climb, const int digits)
786 {
787 DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr);
788 add_widget(dss, label);
789 add_attr_widget(dss);
790 return dss;
791 }
793 // DualSpinButton
794 DualSpinButton* add_dualspinbutton(const SPAttributeEnum attr, const Glib::ustring& label,
795 const double lo, const double hi, const double step_inc,
796 const double climb, const int digits)
797 {
798 DualSpinButton* dsb = new DualSpinButton(lo, hi, step_inc, climb, digits, attr);
799 add_widget(dsb, label);
800 add_attr_widget(dsb);
801 return dsb;
802 }
804 // MultiSpinButton
805 MultiSpinButton* add_multispinbutton(const SPAttributeEnum attr1, const SPAttributeEnum attr2,
806 const Glib::ustring& label, const double lo, const double hi,
807 const double step_inc, const double climb, const int digits)
808 {
809 std::vector<SPAttributeEnum> attrs;
810 attrs.push_back(attr1);
811 attrs.push_back(attr2);
812 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs);
813 add_widget(msb, label);
814 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
815 add_attr_widget(msb->get_spinbuttons()[i]);
816 return msb;
817 }
818 MultiSpinButton* add_multispinbutton(const SPAttributeEnum attr1, const SPAttributeEnum attr2,
819 const SPAttributeEnum attr3, const Glib::ustring& label, const double lo,
820 const double hi, const double step_inc, const double climb, const int digits)
821 {
822 std::vector<SPAttributeEnum> attrs;
823 attrs.push_back(attr1);
824 attrs.push_back(attr2);
825 attrs.push_back(attr3);
826 MultiSpinButton* msb = new MultiSpinButton(lo, hi, step_inc, climb, digits, attrs);
827 add_widget(msb, label);
828 for(unsigned i = 0; i < msb->get_spinbuttons().size(); ++i)
829 add_attr_widget(msb->get_spinbuttons()[i]);
830 return msb;
831 }
833 // FileOrElementChooser
834 FileOrElementChooser* add_fileorelement(const SPAttributeEnum attr, const Glib::ustring& label)
835 {
836 FileOrElementChooser* foech = new FileOrElementChooser(attr);
837 foech->set_desktop(_dialog.getDesktop());
838 add_widget(foech, label);
839 add_attr_widget(foech);
840 return foech;
841 }
843 // ComboBoxEnum
844 template<typename T> ComboBoxEnum<T>* add_combo(const SPAttributeEnum attr,
845 const Glib::ustring& label,
846 const Util::EnumDataConverter<T>& conv)
847 {
848 ComboBoxEnum<T>* combo = new ComboBoxEnum<T>(conv, attr);
849 add_widget(combo, label);
850 add_attr_widget(combo);
851 return combo;
852 }
853 private:
854 void add_attr_widget(AttrWidget* a)
855 {
856 _attrwidgets[_current_type].push_back(a);
857 a->signal_attr_changed().connect(sigc::bind(_set_attr_slot, a));
858 }
860 /* Adds a new settings widget using the specified label. The label will be formatted with a colon
861 and all widgets within the setting group are aligned automatically. */
862 void add_widget(Gtk::Widget* w, const Glib::ustring& label)
863 {
864 Gtk::Label *lbl = 0;
865 Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
866 hb->set_spacing(12);
868 if(label != "") {
869 lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
870 hb->pack_start(*lbl, false, false);
871 _dialog._sizegroup->add_widget(*lbl);
872 lbl->show();
873 }
875 hb->pack_start(*w);
876 _groups[_current_type]->pack_start(*hb);
877 hb->show();
878 w->show();
879 }
881 std::vector<Gtk::VBox*> _groups;
883 FilterEffectsDialog& _dialog;
884 SetAttrSlot _set_attr_slot;
885 std::vector<std::vector<AttrWidget*> > _attrwidgets;
886 int _current_type, _max_types;
887 };
889 // Settings for the three light source objects
890 class FilterEffectsDialog::LightSourceControl : public AttrWidget
891 {
892 public:
893 LightSourceControl(FilterEffectsDialog& d)
894 : AttrWidget(SP_ATTR_INVALID),
895 _dialog(d),
896 _settings(d, _box, sigc::mem_fun(_dialog, &FilterEffectsDialog::set_child_attr_direct), LIGHT_ENDSOURCE),
897 _light_label(_("Light Source:"), Gtk::ALIGN_LEFT),
898 _light_source(LightSourceConverter),
899 _locked(false)
900 {
901 _light_box.pack_start(_light_label, false, false);
902 _light_box.pack_start(_light_source);
903 _light_box.show_all();
904 _light_box.set_spacing(12);
905 _dialog._sizegroup->add_widget(_light_label);
907 _box.add(_light_box);
908 _box.reorder_child(_light_box, 0);
909 _light_source.signal_changed().connect(sigc::mem_fun(*this, &LightSourceControl::on_source_changed));
911 // FIXME: these range values are complete crap
913 _settings.type(LIGHT_DISTANT);
914 _settings.add_spinslider(SP_ATTR_AZIMUTH, _("Azimuth"), 0, 360, 1, 1, 0);
915 _settings.add_spinslider(SP_ATTR_ELEVATION, _("Elevation"), 0, 360, 1, 1, 0);
917 _settings.type(LIGHT_POINT);
918 _settings.add_multispinbutton(SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0);
920 _settings.type(LIGHT_SPOT);
921 _settings.add_multispinbutton(SP_ATTR_X, SP_ATTR_Y, SP_ATTR_Z, _("Location"), -99999, 99999, 1, 100, 0);
922 _settings.add_multispinbutton(SP_ATTR_POINTSATX, SP_ATTR_POINTSATY, SP_ATTR_POINTSATZ,
923 _("Points At"), -99999, 99999, 1, 100, 0);
924 _settings.add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Specular Exponent"), 1, 100, 1, 1, 0);
925 _settings.add_spinslider(SP_ATTR_LIMITINGCONEANGLE, _("Cone Angle"), 1, 100, 1, 1, 0);
926 }
928 Gtk::VBox& get_box()
929 {
930 return _box;
931 }
932 protected:
933 Glib::ustring get_as_attribute() const
934 {
935 return "";
936 }
937 void set_from_attribute(SPObject* o)
938 {
939 if(_locked)
940 return;
942 _locked = true;
944 SPObject* child = o->children;
946 if(SP_IS_FEDISTANTLIGHT(child))
947 _light_source.set_active(0);
948 else if(SP_IS_FEPOINTLIGHT(child))
949 _light_source.set_active(1);
950 else if(SP_IS_FESPOTLIGHT(child))
951 _light_source.set_active(2);
952 else
953 _light_source.set_active(-1);
955 update();
957 _locked = false;
958 }
959 private:
960 void on_source_changed()
961 {
962 if(_locked)
963 return;
965 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
966 if(prim) {
967 _locked = true;
969 SPObject* child = prim->children;
970 const int ls = _light_source.get_active_row_number();
971 // Check if the light source type has changed
972 if(!(ls == -1 && !child) &&
973 !(ls == 0 && SP_IS_FEDISTANTLIGHT(child)) &&
974 !(ls == 1 && SP_IS_FEPOINTLIGHT(child)) &&
975 !(ls == 2 && SP_IS_FESPOTLIGHT(child))) {
976 if(child)
977 sp_repr_unparent(child->repr);
979 if(ls != -1) {
980 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
981 Inkscape::XML::Node *repr = xml_doc->createElement(_light_source.get_active_data()->key.c_str());
982 prim->repr->appendChild(repr);
983 }
985 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("New light source"));
986 update();
987 }
989 _locked = false;
990 }
991 }
993 void update()
994 {
995 _box.hide_all();
996 _box.show();
997 _light_box.show_all();
999 SPFilterPrimitive* prim = _dialog._primitive_list.get_selected();
1000 if(prim && prim->children)
1001 _settings.show_and_update(_light_source.get_active_data()->id, prim->children);
1002 }
1004 FilterEffectsDialog& _dialog;
1005 Gtk::VBox _box;
1006 Settings _settings;
1007 Gtk::HBox _light_box;
1008 Gtk::Label _light_label;
1009 ComboBoxEnum<LightSource> _light_source;
1010 bool _locked;
1011 };
1013 FilterEffectsDialog::LightSourceControl* FilterEffectsDialog::Settings::add_lightsource()
1014 {
1015 LightSourceControl* ls = new LightSourceControl(_dialog);
1016 add_attr_widget(ls);
1017 add_widget(&ls->get_box(), "");
1018 return ls;
1019 }
1021 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
1022 sigc::slot<void> rem)
1023 {
1024 Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
1026 menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
1027 Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
1028 menu->append(*mi);
1029 mi->signal_activate().connect(rem);
1030 mi->show();
1031 menu->accelerate(parent);
1033 return menu;
1034 }
1036 /*** FilterModifier ***/
1037 FilterEffectsDialog::FilterModifier::FilterModifier(FilterEffectsDialog& d)
1038 : _dialog(d), _add(Gtk::Stock::NEW), _observer(new SignalObserver)
1039 {
1040 Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
1041 pack_start(*sw);
1042 pack_start(_add, false, false);
1043 sw->add(_list);
1045 _model = Gtk::ListStore::create(_columns);
1046 _list.set_model(_model);
1047 _cell_toggle.set_active(true);
1048 const int selcol = _list.append_column("", _cell_toggle);
1049 Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
1050 if(col)
1051 col->add_attribute(_cell_toggle.property_active(), _columns.sel);
1052 _list.append_column_editable(_("_Filter"), _columns.label);
1053 ((Gtk::CellRendererText*)_list.get_column(1)->get_first_cell_renderer())->
1054 signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::on_name_edited));
1056 sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1057 sw->set_shadow_type(Gtk::SHADOW_IN);
1058 show_all_children();
1059 _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
1060 _cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &FilterModifier::on_selection_toggled));
1061 _list.signal_button_release_event().connect_notify(
1062 sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
1063 _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
1064 sigc::mem_fun(*this, &FilterModifier::remove_filter));
1065 _menu->items().push_back(Gtk::Menu_Helpers::MenuElem(
1066 _("R_ename"), sigc::mem_fun(*this, &FilterModifier::rename_filter)));
1067 _menu->accelerate(*this);
1069 _list.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FilterModifier::on_filter_selection_changed));
1070 _observer->signal_changed().connect(signal_filter_changed().make_slot());
1071 g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
1072 G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
1074 g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop",
1075 G_CALLBACK(&FilterModifier::on_activate_desktop), this);
1077 on_activate_desktop(INKSCAPE, d.getDesktop(), this);
1078 update_filters();
1079 }
1081 FilterEffectsDialog::FilterModifier::~FilterModifier()
1082 {
1083 _resource_changed.disconnect();
1084 _doc_replaced.disconnect();
1085 }
1087 void FilterEffectsDialog::FilterModifier::on_activate_desktop(Application*, SPDesktop* desktop, FilterModifier* me)
1088 {
1089 me->_doc_replaced.disconnect();
1090 me->_doc_replaced = desktop->connectDocumentReplaced(
1091 sigc::mem_fun(me, &FilterModifier::on_document_replaced));
1093 me->_resource_changed.disconnect();
1094 me->_resource_changed =
1095 sp_document_resources_changed_connect(sp_desktop_document(desktop), "filter",
1096 sigc::mem_fun(me, &FilterModifier::update_filters));
1098 me->_dialog.setDesktop(desktop);
1100 me->update_filters();
1101 }
1104 // When the selection changes, show the active filter(s) in the dialog
1105 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application */*inkscape*/,
1106 Selection *sel,
1107 FilterModifier* fm)
1108 {
1109 if(fm && sel)
1110 fm->update_selection(sel);
1111 }
1113 // Update each filter's sel property based on the current object selection;
1114 // If the filter is not used by any selected object, sel = 0,
1115 // otherwise sel is set to the total number of filters in use by selected objects
1116 // If only one filter is in use, it is selected
1117 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
1118 {
1119 std::set<SPObject*> used;
1121 for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
1122 SPObject *obj = SP_OBJECT (i->data);
1123 SPStyle *style = SP_OBJECT_STYLE (obj);
1124 if(!style || !SP_IS_ITEM(obj)) continue;
1126 if(style->filter.set && style->getFilter())
1127 used.insert(style->getFilter());
1128 else
1129 used.insert(0);
1130 }
1132 const int size = used.size();
1134 for(Gtk::TreeIter iter = _model->children().begin();
1135 iter != _model->children().end(); ++iter) {
1136 if(used.find((*iter)[_columns.filter]) != used.end()) {
1137 // If only one filter is in use by the selection, select it
1138 if(size == 1)
1139 _list.get_selection()->select(iter);
1140 (*iter)[_columns.sel] = size;
1141 }
1142 else
1143 (*iter)[_columns.sel] = 0;
1144 }
1145 }
1147 void FilterEffectsDialog::FilterModifier::on_filter_selection_changed()
1148 {
1149 _observer->set(get_selected_filter());
1150 signal_filter_changed()();
1151 }
1153 void FilterEffectsDialog::FilterModifier::on_name_edited(const Glib::ustring& path, const Glib::ustring& text)
1154 {
1155 Gtk::TreeModel::iterator iter = _model->get_iter(path);
1157 if(iter) {
1158 SPFilter* filter = (*iter)[_columns.filter];
1159 filter->setLabel(text.c_str());
1160 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Rename filter"));
1161 if(iter)
1162 (*iter)[_columns.label] = text;
1163 }
1164 }
1166 void FilterEffectsDialog::FilterModifier::on_selection_toggled(const Glib::ustring& path)
1167 {
1168 Gtk::TreeIter iter = _model->get_iter(path);
1170 if(iter) {
1171 SPDesktop *desktop = _dialog.getDesktop();
1172 SPDocument *doc = sp_desktop_document(desktop);
1173 SPFilter* filter = (*iter)[_columns.filter];
1174 Inkscape::Selection *sel = sp_desktop_selection(desktop);
1176 /* If this filter is the only one used in the selection, unset it */
1177 if((*iter)[_columns.sel] == 1)
1178 filter = 0;
1180 GSList const *items = sel->itemList();
1182 for (GSList const *i = items; i != NULL; i = i->next) {
1183 SPItem * item = SP_ITEM(i->data);
1184 SPStyle *style = SP_OBJECT_STYLE(item);
1185 g_assert(style != NULL);
1187 if(filter)
1188 sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
1189 else
1190 ::remove_filter(item, false);
1192 SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
1193 }
1195 update_selection(sel);
1196 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Apply filter"));
1197 }
1198 }
1200 /* Add all filters in the document to the combobox.
1201 Keeps the same selection if possible, otherwise selects the first element */
1202 void FilterEffectsDialog::FilterModifier::update_filters()
1203 {
1204 SPDesktop* desktop = _dialog.getDesktop();
1205 SPDocument* document = sp_desktop_document(desktop);
1206 const GSList* filters = sp_document_get_resource_list(document, "filter");
1208 _model->clear();
1210 for(const GSList *l = filters; l; l = l->next) {
1211 Gtk::TreeModel::Row row = *_model->append();
1212 SPFilter* f = (SPFilter*)l->data;
1213 row[_columns.filter] = f;
1214 const gchar* lbl = f->label();
1215 const gchar* id = SP_OBJECT_ID(f);
1216 row[_columns.label] = lbl ? lbl : (id ? id : "filter");
1217 }
1219 update_selection(desktop->selection);
1220 }
1222 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
1223 {
1224 if(_list.get_selection()) {
1225 Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
1227 if(i)
1228 return (*i)[_columns.filter];
1229 }
1231 return 0;
1232 }
1234 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
1235 {
1236 if(filter) {
1237 for(Gtk::TreeModel::iterator i = _model->children().begin();
1238 i != _model->children().end(); ++i) {
1239 if((*i)[_columns.filter] == filter) {
1240 _list.get_selection()->select(i);
1241 break;
1242 }
1243 }
1244 }
1245 }
1247 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
1248 {
1249 if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
1250 const bool sensitive = get_selected_filter() != NULL;
1251 _menu->items()[0].set_sensitive(sensitive);
1252 _menu->items()[1].set_sensitive(sensitive);
1253 _menu->popup(event->button, event->time);
1254 }
1255 }
1257 void FilterEffectsDialog::FilterModifier::add_filter()
1258 {
1259 SPDocument* doc = sp_desktop_document(_dialog.getDesktop());
1260 SPFilter* filter = new_filter(doc);
1262 const int count = _model->children().size();
1263 std::ostringstream os;
1264 os << "filter" << count;
1265 filter->setLabel(os.str().c_str());
1267 update_filters();
1269 select_filter(filter);
1271 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
1272 }
1274 void FilterEffectsDialog::FilterModifier::remove_filter()
1275 {
1276 SPFilter *filter = get_selected_filter();
1278 if(filter) {
1279 SPDocument* doc = filter->document;
1280 sp_repr_unparent(filter->repr);
1282 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
1284 update_filters();
1285 }
1286 }
1288 void FilterEffectsDialog::FilterModifier::duplicate_filter()
1289 {
1290 SPFilter* filter = get_selected_filter();
1292 if(filter) {
1293 Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
1294 repr = repr->duplicate(repr->document());
1295 parent->appendChild(repr);
1297 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
1299 update_filters();
1300 }
1301 }
1303 void FilterEffectsDialog::FilterModifier::rename_filter()
1304 {
1305 _list.set_cursor(_model->get_path(_list.get_selection()->get_selected()), *_list.get_column(1), true);
1306 }
1308 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
1309 : Glib::ObjectBase(typeid(CellRendererConnection)),
1310 _primitive(*this, "primitive", 0)
1311 {}
1313 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
1314 {
1315 return _primitive.get_proxy();
1316 }
1318 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
1319 {
1320 _text_width = w;
1321 }
1323 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
1324 {
1325 return _text_width;
1326 }
1328 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
1329 Gtk::Widget& widget, const Gdk::Rectangle* /*cell_area*/,
1330 int* x_offset, int* y_offset, int* width, int* height) const
1331 {
1332 PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
1334 if(x_offset)
1335 (*x_offset) = 0;
1336 if(y_offset)
1337 (*y_offset) = 0;
1338 if(width)
1339 (*width) = size * primlist.primitive_count() + _text_width * 7;
1340 if(height) {
1341 // Scale the height depending on the number of inputs, unless it's
1342 // the first primitive, in which case there are no connections
1343 SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
1344 (*height) = size * input_count(prim);
1345 }
1346 }
1348 /*** PrimitiveList ***/
1349 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
1350 : _dialog(d),
1351 _in_drag(0),
1352 _observer(new SignalObserver)
1353 {
1354 d.signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1356 add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1357 signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
1359 _model = Gtk::ListStore::create(_columns);
1361 set_reorderable(true);
1363 set_model(_model);
1364 append_column(_("_Effect"), _columns.type);
1366 _observer->signal_changed().connect(signal_primitive_changed().make_slot());
1367 get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PrimitiveList::on_primitive_selection_changed));
1368 signal_primitive_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
1370 _connection_cell.set_text_width(init_text());
1372 int cols_count = append_column(_("Connections"), _connection_cell);
1373 Gtk::TreeViewColumn* col = get_column(cols_count - 1);
1374 if(col)
1375 col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
1376 }
1378 // Sets up a vertical Pango context/layout, and returns the largest
1379 // width needed to render the FilterPrimitiveInput labels.
1380 int FilterEffectsDialog::PrimitiveList::init_text()
1381 {
1382 // Set up a vertical context+layout
1383 Glib::RefPtr<Pango::Context> context = create_pango_context();
1384 const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
1385 context->set_matrix(matrix);
1386 _vertical_layout = Pango::Layout::create(context);
1388 int maxfont = 0;
1389 for(int i = 0; i < FPInputConverter.end; ++i) {
1390 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1391 int fontw, fonth;
1392 _vertical_layout->get_pixel_size(fontw, fonth);
1393 if(fonth > maxfont)
1394 maxfont = fonth;
1395 }
1397 return maxfont;
1398 }
1400 sigc::signal<void>& FilterEffectsDialog::PrimitiveList::signal_primitive_changed()
1401 {
1402 return _signal_primitive_changed;
1403 }
1405 void FilterEffectsDialog::PrimitiveList::on_primitive_selection_changed()
1406 {
1407 _observer->set(get_selected());
1408 signal_primitive_changed()();
1409 _dialog._color_matrix_values->clear_store();
1410 }
1412 /* Add all filter primitives in the current to the list.
1413 Keeps the same selection if possible, otherwise selects the first element */
1414 void FilterEffectsDialog::PrimitiveList::update()
1415 {
1416 SPFilter* f = _dialog._filter_modifier.get_selected_filter();
1417 const SPFilterPrimitive* active_prim = get_selected();
1418 bool active_found = false;
1420 _model->clear();
1422 if(f) {
1423 _dialog._primitive_box.set_sensitive(true);
1425 for(SPObject *prim_obj = f->children;
1426 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
1427 prim_obj = prim_obj->next) {
1428 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
1429 if(prim) {
1430 Gtk::TreeModel::Row row = *_model->append();
1431 row[_columns.primitive] = prim;
1432 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
1433 row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
1434 row[_columns.id] = SP_OBJECT_ID(prim);
1436 if(prim == active_prim) {
1437 get_selection()->select(row);
1438 active_found = true;
1439 }
1440 }
1441 }
1443 if(!active_found && _model->children().begin())
1444 get_selection()->select(_model->children().begin());
1446 columns_autosize();
1447 }
1448 else {
1449 _dialog._primitive_box.set_sensitive(false);
1450 }
1451 }
1453 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
1454 {
1455 _primitive_menu = menu;
1456 }
1458 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
1459 {
1460 if(_dialog._filter_modifier.get_selected_filter()) {
1461 Gtk::TreeModel::iterator i = get_selection()->get_selected();
1462 if(i)
1463 return (*i)[_columns.primitive];
1464 }
1466 return 0;
1467 }
1469 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
1470 {
1471 for(Gtk::TreeIter i = _model->children().begin();
1472 i != _model->children().end(); ++i) {
1473 if((*i)[_columns.primitive] == prim)
1474 get_selection()->select(i);
1475 }
1476 }
1478 void FilterEffectsDialog::PrimitiveList::remove_selected()
1479 {
1480 SPFilterPrimitive* prim = get_selected();
1482 if(prim) {
1483 _observer->set(0);
1485 sp_repr_unparent(prim->repr);
1487 sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_FILTER_EFFECTS,
1488 _("Remove filter primitive"));
1490 update();
1491 }
1492 }
1494 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
1495 {
1496 Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
1497 Glib::RefPtr<Gdk::Window> win = get_bin_window();
1498 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1500 SPFilterPrimitive* prim = get_selected();
1501 int row_count = get_model()->children().size();
1503 int fheight = CellRendererConnection::size;
1504 Gdk::Rectangle rct, vis;
1505 Gtk::TreeIter row = get_model()->children().begin();
1506 int text_start_x = 0;
1507 if(row) {
1508 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1509 get_visible_rect(vis);
1510 int vis_x, vis_y;
1511 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1513 text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter.end + 1) + 1;
1514 for(int i = 0; i < FPInputConverter.end; ++i) {
1515 _vertical_layout->set_text(_(FPInputConverter.get_label((FilterPrimitiveInput)i).c_str()));
1516 const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
1517 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());
1518 get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x + 1, vis_y, _vertical_layout);
1519 get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
1520 }
1521 }
1523 int row_index = 0;
1524 for(; row != get_model()->children().end(); ++row, ++row_index) {
1525 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
1526 const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
1528 // Check mouse state
1529 int mx, my;
1530 Gdk::ModifierType mask;
1531 get_bin_window()->get_pointer(mx, my, mask);
1533 // Outline the bottom of the connection area
1534 const int outline_x = x + fheight * (row_count - row_index);
1535 get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
1537 // Side outline
1538 get_bin_window()->draw_line(darkgc, outline_x, y - 1, outline_x, y + h);
1540 std::vector<Gdk::Point> con_poly;
1541 int con_drag_y;
1542 bool inside;
1543 const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
1544 const int inputs = input_count(row_prim);
1546 if(SP_IS_FEMERGE(row_prim)) {
1547 for(int i = 0; i < inputs; ++i) {
1548 inside = do_connection_node(row, i, con_poly, mx, my);
1549 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1550 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1551 inside, con_poly);
1553 if(_in_drag == (i + 1))
1554 con_drag_y = con_poly[2].get_y();
1556 if(_in_drag != (i + 1) || row_prim != prim)
1557 draw_connection(row, i, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1558 }
1559 }
1560 else {
1561 // Draw "in" shape
1562 inside = do_connection_node(row, 0, con_poly, mx, my);
1563 con_drag_y = con_poly[2].get_y();
1564 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1565 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1566 inside, con_poly);
1568 // Draw "in" connection
1569 if(_in_drag != 1 || row_prim != prim)
1570 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1572 if(inputs == 2) {
1573 // Draw "in2" shape
1574 inside = do_connection_node(row, 1, con_poly, mx, my);
1575 if(_in_drag == 2)
1576 con_drag_y = con_poly[2].get_y();
1577 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
1578 darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
1579 inside, con_poly);
1580 // Draw "in2" connection
1581 if(_in_drag != 2 || row_prim != prim)
1582 draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
1583 }
1584 }
1586 // Draw drag connection
1587 if(row_prim == prim && _in_drag) {
1588 get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
1589 mx, con_drag_y);
1590 get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
1591 }
1592 }
1594 return true;
1595 }
1597 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int attr,
1598 const int text_start_x, const int x1, const int y1,
1599 const int row_count)
1600 {
1601 int src_id = 0;
1602 Gtk::TreeIter res = find_result(input, attr, src_id);
1603 Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_black_gc();
1604 Glib::RefPtr<Gdk::GC> lightgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
1605 Glib::RefPtr<Gdk::GC> gc;
1607 const bool is_first = input == get_model()->children().begin();
1608 const bool is_merge = SP_IS_FEMERGE((SPFilterPrimitive*)(*input)[_columns.primitive]);
1609 const bool use_default = !res && !is_merge;
1611 if(res == input || (use_default && is_first)) {
1612 // Draw straight connection to a standard input
1613 // Draw a lighter line for an implicit connection to a standard input
1614 const int tw = _connection_cell.get_text_width();
1615 gint end_x = text_start_x + tw * (src_id + 1) + (int)(tw * 0.5f) + 1;
1616 gc = (use_default && is_first) ? lightgc : darkgc;
1617 get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
1618 get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
1619 }
1620 else {
1621 // Draw an 'L'-shaped connection to another filter primitive
1622 // If no connection is specified, draw a light connection to the previous primitive
1623 gc = use_default ? lightgc : darkgc;
1625 if(use_default) {
1626 res = input;
1627 --res;
1628 }
1630 if(res) {
1631 Gdk::Rectangle rct;
1633 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1634 const int fheight = CellRendererConnection::size;
1636 get_cell_area(get_model()->get_path(res), *get_column(1), rct);
1637 const int row_index = find_index(res);
1638 const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
1639 const int y2 = rct.get_y() + rct.get_height();
1641 // Draw a bevelled 'L'-shaped connection
1642 get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2-fheight/4, y1);
1643 get_bin_window()->draw_line(get_style()->get_black_gc(), x2-fheight/4, y1, x2, y1-fheight/4);
1644 get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1-fheight/4, x2, y2);
1645 }
1646 }
1647 }
1649 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
1650 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
1651 std::vector<Gdk::Point>& points,
1652 const int ix, const int iy)
1653 {
1654 Gdk::Rectangle rct;
1655 const int icnt = input_count((*row)[_columns.primitive]);
1657 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
1658 const int fheight = CellRendererConnection::size;
1660 get_cell_area(_model->get_path(row), *get_column(1), rct);
1661 const float h = rct.get_height() / icnt;
1663 const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
1664 const int con_w = (int)(fheight * 0.35f);
1665 const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
1666 points.clear();
1667 points.push_back(Gdk::Point(x, con_y));
1668 points.push_back(Gdk::Point(x, con_y + con_w * 2));
1669 points.push_back(Gdk::Point(x - con_w, con_y + con_w));
1671 return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
1672 }
1674 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
1675 const int attr, int& src_id)
1676 {
1677 SPFilterPrimitive* prim = (*start)[_columns.primitive];
1678 Gtk::TreeIter target = _model->children().end();
1679 int image;
1681 if(SP_IS_FEMERGE(prim)) {
1682 int c = 0;
1683 bool found = false;
1684 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++c) {
1685 if(c == attr && SP_IS_FEMERGENODE(o)) {
1686 image = SP_FEMERGENODE(o)->input;
1687 found = true;
1688 }
1689 }
1690 if(!found)
1691 return target;
1692 }
1693 else {
1694 if(attr == SP_ATTR_IN)
1695 image = prim->image_in;
1696 else if(attr == SP_ATTR_IN2) {
1697 if(SP_IS_FEBLEND(prim))
1698 image = SP_FEBLEND(prim)->in2;
1699 else if(SP_IS_FECOMPOSITE(prim))
1700 image = SP_FECOMPOSITE(prim)->in2;
1701 else if(SP_IS_FEDISPLACEMENTMAP(prim))
1702 image = SP_FEDISPLACEMENTMAP(prim)->in2;
1703 else
1704 return target;
1705 }
1706 else
1707 return target;
1708 }
1710 if(image >= 0) {
1711 for(Gtk::TreeIter i = _model->children().begin();
1712 i != start; ++i) {
1713 if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
1714 target = i;
1715 }
1716 return target;
1717 }
1718 else if(image < -1) {
1719 src_id = -(image + 2);
1720 return start;
1721 }
1723 return target;
1724 }
1726 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
1727 {
1728 int i = 0;
1729 for(Gtk::TreeIter iter = _model->children().begin();
1730 iter != target; ++iter, ++i);
1731 return i;
1732 }
1734 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
1735 {
1736 Gtk::TreePath path;
1737 Gtk::TreeViewColumn* col;
1738 const int x = (int)e->x, y = (int)e->y;
1739 int cx, cy;
1741 _drag_prim = 0;
1743 if(get_path_at_pos(x, y, path, col, cx, cy)) {
1744 Gtk::TreeIter iter = _model->get_iter(path);
1745 std::vector<Gdk::Point> points;
1747 _drag_prim = (*iter)[_columns.primitive];
1748 const int icnt = input_count(_drag_prim);
1750 for(int i = 0; i < icnt; ++i) {
1751 if(do_connection_node(_model->get_iter(path), i, points, x, y)) {
1752 _in_drag = i + 1;
1753 break;
1754 }
1755 }
1757 queue_draw();
1758 }
1760 if(_in_drag) {
1761 _scroll_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &PrimitiveList::on_scroll_timeout), 150);
1762 _autoscroll = 0;
1763 get_selection()->select(path);
1764 return true;
1765 }
1766 else
1767 return Gtk::TreeView::on_button_press_event(e);
1768 }
1770 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
1771 {
1772 const int speed = 10;
1773 const int limit = 15;
1775 Gdk::Rectangle vis;
1776 get_visible_rect(vis);
1777 int vis_x, vis_y;
1778 tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
1779 const int top = vis_y + vis.get_height();
1781 // When autoscrolling during a connection drag, set the speed based on
1782 // where the mouse is in relation to the edges.
1783 if(e->y < vis_y)
1784 _autoscroll = -(int)(speed + (vis_y - e->y) / 5);
1785 else if(e->y < vis_y + limit)
1786 _autoscroll = -speed;
1787 else if(e->y > top)
1788 _autoscroll = (int)(speed + (e->y - top) / 5);
1789 else if(e->y > top - limit)
1790 _autoscroll = speed;
1791 else
1792 _autoscroll = 0;
1794 queue_draw();
1796 return Gtk::TreeView::on_motion_notify_event(e);
1797 }
1799 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
1800 {
1801 SPFilterPrimitive *prim = get_selected(), *target;
1803 _scroll_connection.disconnect();
1805 if(_in_drag && prim) {
1806 Gtk::TreePath path;
1807 Gtk::TreeViewColumn* col;
1808 int cx, cy;
1810 if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
1811 const gchar *in_val = 0;
1812 Glib::ustring result;
1813 Gtk::TreeIter target_iter = _model->get_iter(path);
1814 target = (*target_iter)[_columns.primitive];
1815 col = get_column(1);
1817 Gdk::Rectangle rct;
1818 get_cell_area(path, *col, rct);
1819 const int twidth = _connection_cell.get_text_width();
1820 const int sources_x = rct.get_width() - twidth * FPInputConverter.end;
1821 if(cx > sources_x) {
1822 int src = (cx - sources_x) / twidth;
1823 if(src < 0)
1824 src = 0;
1825 else if(src >= FPInputConverter.end)
1826 src = FPInputConverter.end - 1;
1827 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
1828 in_val = result.c_str();
1829 }
1830 else {
1831 // Ensure that the target comes before the selected primitive
1832 for(Gtk::TreeIter iter = _model->children().begin();
1833 iter != get_selection()->get_selected(); ++iter) {
1834 if(iter == target_iter) {
1835 Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
1836 // Make sure the target has a result
1837 const gchar *gres = repr->attribute("result");
1838 if(!gres) {
1839 result = "result" + Glib::Ascii::dtostr(SP_FILTER(prim->parent)->_image_number_next);
1840 repr->setAttribute("result", result.c_str());
1841 in_val = result.c_str();
1842 }
1843 else
1844 in_val = gres;
1845 break;
1846 }
1847 }
1848 }
1850 if(SP_IS_FEMERGE(prim)) {
1851 int c = 1;
1852 bool handled = false;
1853 for(SPObject* o = prim->firstChild(); o && !handled; o = o->next, ++c) {
1854 if(c == _in_drag && SP_IS_FEMERGENODE(o)) {
1855 // If input is null, delete it
1856 if(!in_val) {
1857 sp_repr_unparent(o->repr);
1858 sp_document_done(prim->document, SP_VERB_DIALOG_FILTER_EFFECTS,
1859 _("Remove merge node"));
1860 (*get_selection()->get_selected())[_columns.primitive] = prim;
1861 }
1862 else
1863 _dialog.set_attr(o, SP_ATTR_IN, in_val);
1864 handled = true;
1865 }
1866 }
1867 // Add new input?
1868 if(!handled && c == _in_drag && in_val) {
1869 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(prim->document);
1870 Inkscape::XML::Node *repr = xml_doc->createElement("svg:feMergeNode");
1871 repr->setAttribute("inkscape:collect", "always");
1872 prim->repr->appendChild(repr);
1873 SPFeMergeNode *node = SP_FEMERGENODE(prim->document->getObjectByRepr(repr));
1874 Inkscape::GC::release(repr);
1875 _dialog.set_attr(node, SP_ATTR_IN, in_val);
1876 (*get_selection()->get_selected())[_columns.primitive] = prim;
1877 }
1878 }
1879 else {
1880 if(_in_drag == 1)
1881 _dialog.set_attr(prim, SP_ATTR_IN, in_val);
1882 else if(_in_drag == 2)
1883 _dialog.set_attr(prim, SP_ATTR_IN2, in_val);
1884 }
1885 }
1887 _in_drag = 0;
1888 queue_draw();
1890 _dialog.update_settings_view();
1891 }
1893 if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1894 const bool sensitive = get_selected() != NULL;
1895 _primitive_menu->items()[0].set_sensitive(sensitive);
1896 _primitive_menu->items()[1].set_sensitive(sensitive);
1897 _primitive_menu->popup(e->button, e->time);
1899 return true;
1900 }
1901 else
1902 return Gtk::TreeView::on_button_release_event(e);
1903 }
1905 // Checks all of prim's inputs, removes any that use result
1906 void check_single_connection(SPFilterPrimitive* prim, const int result)
1907 {
1908 if(prim && result >= 0) {
1910 if(prim->image_in == result)
1911 SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1913 if(SP_IS_FEBLEND(prim)) {
1914 if(SP_FEBLEND(prim)->in2 == result)
1915 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1916 }
1917 else if(SP_IS_FECOMPOSITE(prim)) {
1918 if(SP_FECOMPOSITE(prim)->in2 == result)
1919 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1920 }
1921 else if(SP_IS_FEDISPLACEMENTMAP(prim)) {
1922 if(SP_FEDISPLACEMENTMAP(prim)->in2 == result)
1923 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1924 }
1925 }
1926 }
1928 // Remove any connections going to/from prim_iter that forward-reference other primitives
1929 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
1930 {
1931 SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
1932 bool before = true;
1934 for(Gtk::TreeIter iter = _model->children().begin();
1935 iter != _model->children().end(); ++iter) {
1936 if(iter == prim_iter)
1937 before = false;
1938 else {
1939 SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
1940 if(before)
1941 check_single_connection(cur_prim, prim->image_out);
1942 else
1943 check_single_connection(prim, cur_prim->image_out);
1944 }
1945 }
1946 }
1948 // Reorder the filter primitives to match the list order
1949 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>& /*dc*/)
1950 {
1951 SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
1952 int ndx = 0;
1954 for(Gtk::TreeModel::iterator iter = _model->children().begin();
1955 iter != _model->children().end(); ++iter, ++ndx) {
1956 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
1957 if(prim && prim == _drag_prim) {
1958 SP_OBJECT_REPR(prim)->setPosition(ndx);
1959 break;
1960 }
1961 }
1963 for(Gtk::TreeModel::iterator iter = _model->children().begin();
1964 iter != _model->children().end(); ++iter, ++ndx) {
1965 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
1966 if(prim && prim == _drag_prim) {
1967 sanitize_connections(iter);
1968 get_selection()->select(iter);
1969 break;
1970 }
1971 }
1973 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
1975 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
1976 }
1978 // If a connection is dragged towards the top or bottom of the list, the list should scroll to follow.
1979 bool FilterEffectsDialog::PrimitiveList::on_scroll_timeout()
1980 {
1981 if(_autoscroll) {
1982 Gtk::Adjustment& a = *dynamic_cast<Gtk::ScrolledWindow*>(get_parent())->get_vadjustment();
1983 double v;
1985 v = a.get_value() + _autoscroll;
1986 if(v < 0)
1987 v = 0;
1988 if(v > a.get_upper() - a.get_page_size())
1989 v = a.get_upper() - a.get_page_size();
1991 a.set_value(v);
1993 queue_draw();
1994 }
1996 return true;
1997 }
1999 int FilterEffectsDialog::PrimitiveList::primitive_count() const
2000 {
2001 return _model->children().size();
2002 }
2004 /*** FilterEffectsDialog ***/
2006 FilterEffectsDialog::FilterEffectsDialog()
2007 : UI::Widget::Panel("", "dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
2008 _filter_modifier(*this),
2009 _primitive_list(*this),
2010 _add_primitive_type(FPConverter),
2011 _add_primitive(_("Add Effect:")),
2012 _empty_settings(_("No effect selected"), Gtk::ALIGN_LEFT),
2013 _locked(false),
2014 _attr_lock(false)
2015 {
2016 _settings = new Settings(*this, _settings_box, sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_direct),
2017 NR_FILTER_ENDPRIMITIVETYPE);
2018 _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
2019 _sizegroup->set_ignore_hidden();
2021 _add_primitive_type.remove_row(NR_FILTER_TILE);
2022 _add_primitive_type.remove_row(NR_FILTER_COMPONENTTRANSFER);
2024 // Initialize widget hierarchy
2025 Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
2026 Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
2027 Gtk::HBox* infobox = Gtk::manage(new Gtk::HBox);
2028 Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
2029 Gtk::Frame* fr_settings = Gtk::manage(new Gtk::Frame(_("<b>Effect parameters</b>")));
2030 Gtk::Alignment* al_settings = Gtk::manage(new Gtk::Alignment);
2031 _getContents()->add(*hpaned);
2032 hpaned->pack1(_filter_modifier);
2033 hpaned->pack2(_primitive_box);
2034 _primitive_box.pack_start(*sw_prims);
2035 _primitive_box.pack_start(*infobox,false, false);
2036 _primitive_box.pack_start(*hb_prims, false, false);
2037 sw_prims->add(_primitive_list);
2038 infobox->pack_start(_infobox_icon, false, false);
2039 infobox->pack_end(_infobox_desc, false, false);
2040 _infobox_desc.set_line_wrap(true);
2042 hb_prims->pack_end(_add_primitive_type, false, false);
2043 hb_prims->pack_end(_add_primitive, false, false);
2044 _getContents()->pack_start(*fr_settings, false, false);
2045 fr_settings->add(*al_settings);
2046 al_settings->add(_settings_box);
2048 _primitive_list.signal_primitive_changed().connect(
2049 sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
2050 _filter_modifier.signal_filter_changed().connect(
2051 sigc::mem_fun(_primitive_list, &PrimitiveList::update));
2053 _add_primitive_type.signal_changed().connect(
2054 sigc::mem_fun(*this, &FilterEffectsDialog::update_primitive_infobox));
2056 sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2057 sw_prims->set_shadow_type(Gtk::SHADOW_IN);
2058 al_settings->set_padding(0, 0, 12, 0);
2059 fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
2060 ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
2061 _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
2062 _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
2063 sigc::mem_fun(_primitive_list, &PrimitiveList::remove_selected)));
2065 show_all_children();
2066 init_settings_widgets();
2067 _primitive_list.update();
2068 update_primitive_infobox();
2069 }
2071 FilterEffectsDialog::~FilterEffectsDialog()
2072 {
2073 delete _settings;
2074 }
2076 void FilterEffectsDialog::set_attrs_locked(const bool l)
2077 {
2078 _locked = l;
2079 }
2081 void FilterEffectsDialog::show_all_vfunc()
2082 {
2083 UI::Widget::Panel::show_all_vfunc();
2085 update_settings_view();
2086 }
2088 void FilterEffectsDialog::init_settings_widgets()
2089 {
2090 // TODO: Find better range/climb-rate/digits values for the SpinSliders,
2091 // most of the current values are complete guesses!
2093 _empty_settings.set_sensitive(false);
2094 _settings_box.pack_start(_empty_settings);
2096 _settings->type(NR_FILTER_BLEND);
2097 _settings->add_combo(SP_ATTR_MODE, _("Mode"), BlendModeConverter);
2099 _settings->type(NR_FILTER_COLORMATRIX);
2100 ComboBoxEnum<FilterColorMatrixType>* colmat = _settings->add_combo(SP_ATTR_TYPE, _("Type"), ColorMatrixTypeConverter);
2101 _color_matrix_values = _settings->add_colormatrixvalues(_("Value(s)"));
2102 colmat->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::update_color_matrix));
2104 _settings->type(NR_FILTER_COMPONENTTRANSFER);
2105 _settings->add_notimplemented();
2106 /*_settings->add_combo(SP_ATTR_TYPE, _("Type"), ComponentTransferTypeConverter);
2107 _ct_slope = _settings->add_spinslider(SP_ATTR_SLOPE, _("Slope"), -100, 100, 1, 0.01, 1);
2108 _ct_intercept = _settings->add_spinslider(SP_ATTR_INTERCEPT, _("Intercept"), -100, 100, 1, 0.01, 1);
2109 _ct_amplitude = _settings->add_spinslider(SP_ATTR_AMPLITUDE, _("Amplitude"), 0, 100, 1, 0.01, 1);
2110 _ct_exponent = _settings->add_spinslider(SP_ATTR_EXPONENT, _("Exponent"), 0, 100, 1, 0.01, 1);
2111 _ct_offset = _settings->add_spinslider(SP_ATTR_OFFSET, _("Offset"), -100, 100, 1, 0.01, 1);*/
2113 _settings->type(NR_FILTER_COMPOSITE);
2114 _settings->add_combo(SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
2115 _k1 = _settings->add_spinslider(SP_ATTR_K1, _("K1"), -10, 10, 0.1, 0.01, 2);
2116 _k2 = _settings->add_spinslider(SP_ATTR_K2, _("K2"), -10, 10, 0.1, 0.01, 2);
2117 _k3 = _settings->add_spinslider(SP_ATTR_K3, _("K3"), -10, 10, 0.1, 0.01, 2);
2118 _k4 = _settings->add_spinslider(SP_ATTR_K4, _("K4"), -10, 10, 0.1, 0.01, 2);
2120 _settings->type(NR_FILTER_CONVOLVEMATRIX);
2121 _convolve_order = _settings->add_dualspinbutton(SP_ATTR_ORDER, _("Size"), 1, 5, 1, 1, 0);
2122 _convolve_target = _settings->add_multispinbutton(SP_ATTR_TARGETX, SP_ATTR_TARGETY, _("Target"), 0, 4, 1, 1, 0);
2123 _convolve_matrix = _settings->add_matrix(SP_ATTR_KERNELMATRIX, _("Kernel"));
2124 _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed));
2125 _settings->add_spinslider(SP_ATTR_DIVISOR, _("Divisor"), 1, 20, 1, 0.1, 2);
2126 _settings->add_spinslider(SP_ATTR_BIAS, _("Bias"), -10, 10, 1, 0.01, 1);
2127 _settings->add_combo(SP_ATTR_EDGEMODE, _("Edge Mode"), ConvolveMatrixEdgeModeConverter);
2128 _settings->add_checkbutton(SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false");
2130 _settings->type(NR_FILTER_DIFFUSELIGHTING);
2131 _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Diffuse Color"));
2132 _settings->add_spinslider(SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1);
2133 _settings->add_spinslider(SP_ATTR_DIFFUSECONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2);
2134 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2135 _settings->add_lightsource();
2137 _settings->type(NR_FILTER_DISPLACEMENTMAP);
2138 _settings->add_spinslider(SP_ATTR_SCALE, _("Scale"), 0, 100, 1, 0.01, 1);
2139 _settings->add_combo(SP_ATTR_XCHANNELSELECTOR, _("X Channel"), DisplacementMapChannelConverter);
2140 _settings->add_combo(SP_ATTR_YCHANNELSELECTOR, _("Y Channel"), DisplacementMapChannelConverter);
2142 _settings->type(NR_FILTER_FLOOD);
2143 _settings->add_color(SP_PROP_FLOOD_COLOR, _("Flood Color"));
2144 _settings->add_spinslider(SP_PROP_FLOOD_OPACITY, _("Opacity"), 0, 1, 0.1, 0.01, 2);
2146 _settings->type(NR_FILTER_GAUSSIANBLUR);
2147 _settings->add_dualspinslider(SP_ATTR_STDDEVIATION, _("Standard Deviation"), 0.01, 100, 1, 0.01, 1);
2149 _settings->type(NR_FILTER_MORPHOLOGY);
2150 _settings->add_combo(SP_ATTR_OPERATOR, _("Operator"), MorphologyOperatorConverter);
2151 _settings->add_dualspinslider(SP_ATTR_RADIUS, _("Radius"), 0, 100, 1, 0.01, 1);
2153 _settings->type(NR_FILTER_IMAGE);
2154 _settings->add_fileorelement(SP_ATTR_XLINK_HREF, _("Source of Image"));
2155 _settings->add_multispinbutton(SP_ATTR_X, SP_ATTR_Y, _("Coordinates"), -10000, 10000, 1, 1, 0);
2156 _settings->add_multispinbutton(SP_ATTR_WIDTH, SP_ATTR_HEIGHT, _("Dimensions"), 0, 10000, 1, 1, 0);
2158 _settings->type(NR_FILTER_OFFSET);
2159 _settings->add_spinslider(SP_ATTR_DX, _("Delta X"), -100, 100, 1, 0.01, 1);
2160 _settings->add_spinslider(SP_ATTR_DY, _("Delta Y"), -100, 100, 1, 0.01, 1);
2162 _settings->type(NR_FILTER_SPECULARLIGHTING);
2163 _settings->add_color(SP_PROP_LIGHTING_COLOR, _("Specular Color"));
2164 _settings->add_spinslider(SP_ATTR_SURFACESCALE, _("Surface Scale"), -1000, 1000, 1, 0.01, 1);
2165 _settings->add_spinslider(SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 0.1, 0.01, 2);
2166 _settings->add_spinslider(SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1);
2167 _settings->add_dualspinslider(SP_ATTR_KERNELUNITLENGTH, _("Kernel Unit Length"), 0.01, 10, 1, 0.01, 1);
2168 _settings->add_lightsource();
2170 _settings->type(NR_FILTER_TILE);
2171 _settings->add_notimplemented();
2173 _settings->type(NR_FILTER_TURBULENCE);
2174 _settings->add_checkbutton(SP_ATTR_STITCHTILES, _("Stitch Tiles"), "stitch", "noStitch");
2175 _settings->add_combo(SP_ATTR_TYPE, _("Type"), TurbulenceTypeConverter);
2176 _settings->add_dualspinslider(SP_ATTR_BASEFREQUENCY, _("Base Frequency"), 0, 1, 0.001, 0.01, 3);
2177 _settings->add_spinslider(SP_ATTR_NUMOCTAVES, _("Octaves"), 1, 10, 1, 1, 0);
2178 _settings->add_spinslider(SP_ATTR_SEED, _("Seed"), 0, 1000, 1, 1, 0);
2179 }
2181 void FilterEffectsDialog::add_primitive()
2182 {
2183 SPFilter* filter = _filter_modifier.get_selected_filter();
2185 if(filter) {
2186 SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
2188 _primitive_list.select(prim);
2190 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
2191 }
2192 }
2194 void FilterEffectsDialog::update_primitive_infobox()
2195 {
2196 if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2197 _infobox_icon.show();
2198 _infobox_desc.show();
2199 } else {
2200 _infobox_icon.hide();
2201 _infobox_desc.hide();
2202 }
2203 switch(_add_primitive_type.get_active_data()->id){
2204 case(NR::NR_FILTER_BLEND):
2205 _infobox_icon.set(g_strdup_printf("%s/feBlend-icon.png", INKSCAPE_PIXMAPDIR));
2206 _infobox_desc.set_markup(_("The <b>feBlend</b> filter primitive provides 4 image blending modes: screen, multiply, darken and lighten."));
2207 break;
2208 case(NR::NR_FILTER_COLORMATRIX):
2209 _infobox_icon.set(g_strdup_printf("%s/feColorMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2210 _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."));
2211 break;
2212 case(NR::NR_FILTER_COMPONENTTRANSFER):
2213 _infobox_icon.set(g_strdup_printf("%s/feComponentTransfer-icon.png", INKSCAPE_PIXMAPDIR));
2214 _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."));
2215 break;
2216 case(NR::NR_FILTER_COMPOSITE):
2217 _infobox_icon.set(g_strdup_printf("%s/feComposite-icon.png", INKSCAPE_PIXMAPDIR));
2218 _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."));
2219 break;
2220 case(NR::NR_FILTER_CONVOLVEMATRIX):
2221 _infobox_icon.set(g_strdup_printf("%s/feConvolveMatrix-icon.png", INKSCAPE_PIXMAPDIR));
2222 _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."));
2223 break;
2224 case(NR::NR_FILTER_DIFFUSELIGHTING):
2225 _infobox_icon.set(g_strdup_printf("%s/feDiffuseLighting-icon.png", INKSCAPE_PIXMAPDIR));
2226 _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."));
2227 break;
2228 case(NR::NR_FILTER_DISPLACEMENTMAP):
2229 _infobox_icon.set(g_strdup_printf("%s/feDisplacementMap-icon.png", INKSCAPE_PIXMAPDIR));
2230 _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."));
2231 break;
2232 case(NR::NR_FILTER_FLOOD):
2233 _infobox_icon.set(g_strdup_printf("%s/feFlood-icon.png", INKSCAPE_PIXMAPDIR));
2234 _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."));
2235 break;
2236 case(NR::NR_FILTER_GAUSSIANBLUR):
2237 _infobox_icon.set(g_strdup_printf("%s/feGaussianBlur-icon.png", INKSCAPE_PIXMAPDIR));
2238 _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."));
2239 break;
2240 case(NR::NR_FILTER_IMAGE):
2241 _infobox_icon.set(g_strdup_printf("%s/feImage-icon.png", INKSCAPE_PIXMAPDIR));
2242 _infobox_desc.set_markup(_("The <b>feImage</b> filter primitive fills the region with an external image or another part of the document."));
2243 break;
2244 case(NR::NR_FILTER_MERGE):
2245 _infobox_icon.set(g_strdup_printf("%s/feMerge-icon.png", INKSCAPE_PIXMAPDIR));
2246 _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."));
2247 break;
2248 case(NR::NR_FILTER_MORPHOLOGY):
2249 _infobox_icon.set(g_strdup_printf("%s/feMorphology-icon.png", INKSCAPE_PIXMAPDIR));
2250 _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."));
2251 break;
2252 case(NR::NR_FILTER_OFFSET):
2253 _infobox_icon.set(g_strdup_printf("%s/feOffset-icon.png", INKSCAPE_PIXMAPDIR));
2254 _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."));
2255 break;
2256 case(NR::NR_FILTER_SPECULARLIGHTING):
2257 _infobox_icon.set(g_strdup_printf("%s/feSpecularLighting-icon.png", INKSCAPE_PIXMAPDIR));
2258 _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."));
2259 break;
2260 case(NR::NR_FILTER_TILE):
2261 _infobox_icon.set(g_strdup_printf("%s/feTile-icon.png", INKSCAPE_PIXMAPDIR));
2262 _infobox_desc.set_markup(_("The <b>feTile</b> filter primitive tiles a region with its input graphic"));
2263 break;
2264 case(NR::NR_FILTER_TURBULENCE):
2265 _infobox_icon.set(g_strdup_printf("%s/feTurbulence-icon.png", INKSCAPE_PIXMAPDIR));
2266 _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."));
2267 break;
2268 }
2269 }
2271 void FilterEffectsDialog::duplicate_primitive()
2272 {
2273 SPFilter* filter = _filter_modifier.get_selected_filter();
2274 SPFilterPrimitive* origprim = _primitive_list.get_selected();
2276 if(filter && origprim) {
2277 Inkscape::XML::Node *repr;
2278 repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
2279 SP_OBJECT_REPR(filter)->appendChild(repr);
2281 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
2283 _primitive_list.update();
2284 }
2285 }
2287 void FilterEffectsDialog::convolve_order_changed()
2288 {
2289 _convolve_matrix->set_from_attribute(SP_OBJECT(_primitive_list.get_selected()));
2290 _convolve_target->get_spinbuttons()[0]->get_adjustment()->set_upper(_convolve_order->get_spinbutton1().get_value() - 1);
2291 _convolve_target->get_spinbuttons()[1]->get_adjustment()->set_upper(_convolve_order->get_spinbutton2().get_value() - 1);
2292 }
2294 void FilterEffectsDialog::set_attr_direct(const AttrWidget* input)
2295 {
2296 set_attr(_primitive_list.get_selected(), input->get_attribute(), input->get_as_attribute().c_str());
2297 }
2299 void FilterEffectsDialog::set_child_attr_direct(const AttrWidget* input)
2300 {
2301 set_attr(_primitive_list.get_selected()->children, input->get_attribute(), input->get_as_attribute().c_str());
2302 }
2304 void FilterEffectsDialog::set_attr(SPObject* o, const SPAttributeEnum attr, const gchar* val)
2305 {
2306 if(!_locked) {
2307 _attr_lock = true;
2309 SPFilter *filter = _filter_modifier.get_selected_filter();
2310 const gchar* name = (const gchar*)sp_attribute_name(attr);
2311 if(filter && name && o) {
2312 update_settings_sensitivity();
2314 SP_OBJECT_REPR(o)->setAttribute(name, val);
2315 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
2317 Glib::ustring undokey = "filtereffects:";
2318 undokey += name;
2319 sp_document_maybe_done(filter->document, undokey.c_str(), SP_VERB_DIALOG_FILTER_EFFECTS,
2320 _("Set filter primitive attribute"));
2321 }
2323 _attr_lock = false;
2324 }
2325 }
2327 void FilterEffectsDialog::update_settings_view()
2328 {
2329 update_settings_sensitivity();
2331 if(_attr_lock)
2332 return;
2334 _settings_box.hide_all();
2335 _settings_box.show();
2336 _empty_settings.show();
2338 if (prefs_get_int_attribute ("options.showfiltersinfobox", "value", 1)){
2339 _infobox_icon.show();
2340 _infobox_desc.show();
2341 } else {
2342 _infobox_icon.hide();
2343 _infobox_desc.hide();
2344 }
2346 SPFilterPrimitive* prim = _primitive_list.get_selected();
2348 if(prim) {
2349 _settings->show_and_update(FPConverter.get_id_from_key(prim->repr->name()), prim);
2350 _empty_settings.hide();
2351 }
2352 }
2354 void FilterEffectsDialog::update_settings_sensitivity()
2355 {
2356 SPFilterPrimitive* prim = _primitive_list.get_selected();
2357 const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
2358 _k1->set_sensitive(use_k);
2359 _k2->set_sensitive(use_k);
2360 _k3->set_sensitive(use_k);
2361 _k4->set_sensitive(use_k);
2363 // Component transfer not yet implemented
2364 /*
2365 if(SP_IS_FECOMPONENTTRANSFER(prim)) {
2366 SPFeComponentTransfer* ct = SP_FECOMPONENTTRANSFER(prim);
2367 const bool linear = ct->type == COMPONENTTRANSFER_TYPE_LINEAR;
2368 const bool gamma = ct->type == COMPONENTTRANSFER_TYPE_GAMMA;
2370 _ct_table->set_sensitive(ct->type == COMPONENTTRANSFER_TYPE_TABLE || ct->type == COMPONENTTRANSFER_TYPE_DISCRETE);
2371 _ct_slope->set_sensitive(linear);
2372 _ct_intercept->set_sensitive(linear);
2373 _ct_amplitude->set_sensitive(gamma);
2374 _ct_exponent->set_sensitive(gamma);
2375 _ct_offset->set_sensitive(gamma);
2376 }
2377 */
2378 }
2380 void FilterEffectsDialog::update_color_matrix()
2381 {
2382 _color_matrix_values->set_from_attribute(_primitive_list.get_selected());
2383 }
2385 } // namespace Dialog
2386 } // namespace UI
2387 } // namespace Inkscape
2389 /*
2390 Local Variables:
2391 mode:c++
2392 c-file-style:"stroustrup"
2393 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2394 indent-tabs-mode:nil
2395 fill-column:99
2396 End:
2397 */
2398 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :