Code

Filter effects dialog:
[inkscape.git] / src / ui / dialog / filter-effects-dialog.cpp
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/paned.h>
19 #include <gtkmm/scale.h>
20 #include <gtkmm/scrolledwindow.h>
21 #include <gtkmm/spinbutton.h>
22 #include <gtkmm/stock.h>
23 #include <glibmm/i18n.h>
25 #include "application/application.h"
26 #include "application/editor.h"
27 #include "desktop.h"
28 #include "desktop-handles.h"
29 #include "dialog-manager.h"
30 #include "document.h"
31 #include "filter-chemistry.h"
32 #include "filter-effects-dialog.h"
33 #include "inkscape.h"
34 #include "selection.h"
35 #include "sp-feblend.h"
36 #include "sp-fecomposite.h"
37 #include "sp-fedisplacementmap.h"
38 #include "sp-femerge.h"
39 #include "sp-filter-primitive.h"
40 #include "sp-gaussian-blur.h"
41 #include "sp-feoffset.h"
42 #include "style.h"
43 #include "verbs.h"
44 #include "xml/node.h"
45 #include "xml/repr.h"
46 #include <sstream>
48 #include <iostream>
50 using namespace NR;
52 namespace Inkscape {
53 namespace UI {
54 namespace Dialog {
56 /* Displays/Edits the kernel matrix for feConvolveMatrix */
57 class FilterEffectsDialog::ConvolveMatrix : public Gtk::TreeView, public AttrWidget
58 {
59 public:
60     ConvolveMatrix(const SPAttributeEnum a)
61         : AttrWidget(a)
62     {
63         _model = Gtk::ListStore::create(_columns);
64         set_model(_model);
65         set_headers_visible(false);
66     }
68     Glib::ustring get_as_attribute() const
69     {
70         std::ostringstream os;
71         
72         for(Gtk::TreeIter iter = _model->children().begin();
73             iter != _model->children().end(); ++iter) {
74             for(unsigned c = 0; c < get_columns().size(); ++c) {
75                 os << (*iter)[_columns.cols[c]] << " ";
76             }
77         }
78         
79         return os.str();
80     }
81     
82     void set_from_attribute(SPObject* o)
83     {
84         update(SP_FECONVOLVEMATRIX(o));
85     }
87     sigc::signal<void>& signal_changed()
88     {
89         return _signal_changed;
90     }
92     void update_direct(FilterEffectsDialog* d)
93     {
94         update(SP_FECONVOLVEMATRIX(d->_primitive_list.get_selected()));
95     }
96 private:
97     class ConvolveMatrixColumns : public Gtk::TreeModel::ColumnRecord
98     {
99     public:
100         ConvolveMatrixColumns()
101         {
102             cols.resize(5);
103             for(unsigned i = 0; i < cols.size(); ++i)
104                 add(cols[i]);
105         }
106         std::vector<Gtk::TreeModelColumn<double> > cols;
107     };
109     void update(SPFeConvolveMatrix* conv)
110     {
111         if(conv) {
112             int cols, rows;
114             cols = (int)conv->order.getNumber();
115             if(cols > 5)
116                 cols = 5;
117             rows = conv->order.optNumber_set ? (int)conv->order.getOptNumber() : cols;
119             update(conv, cols, rows);
120         }
121     }
123     void update(SPFeConvolveMatrix* conv, const int rows, const int cols)
124     {
125         _model->clear();
127         remove_all_columns();
129         if(conv) {
130             int ndx = 0;
132             for(int i = 0; i < cols; ++i) {
133                 append_column_numeric_editable("", _columns.cols[i], "%.2f");
134                 dynamic_cast<Gtk::CellRendererText*>(get_column(i)->get_first_cell_renderer())->signal_edited().connect(
135                     sigc::mem_fun(*this, &ConvolveMatrix::rebind));
136             }
138             for(int r = 0; r < rows; ++r) {
139                 Gtk::TreeRow row = *(_model->append());
140                 for(int c = 0; c < cols; ++c, ++ndx)
141                     row[_columns.cols[c]] = ndx < (int)conv->kernelMatrix.size() ? conv->kernelMatrix[ndx] : 0;
142             }
143         }
144     }
146     void rebind(const Glib::ustring&, const Glib::ustring&)
147     {
148         _signal_changed();
149     }
151     Glib::RefPtr<Gtk::ListStore> _model;
152     ConvolveMatrixColumns _columns;
153     sigc::signal<void> _signal_changed;
154 };
156 class FilterEffectsDialog::Settings
158 public:
159     Settings(FilterEffectsDialog& d)
160         : _dialog(d)
161     {
162         _sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
163         _sizegroup->set_ignore_hidden();
165         for(int i = 0; i < NR_FILTER_ENDPRIMITIVETYPE; ++i) {
166             _dialog._settings_box.add(_groups[i]);
167         }
168     }
170     ~Settings()
171     {
172         for(int i = 0; i < NR_FILTER_ENDPRIMITIVETYPE; ++i) {
173             for(unsigned j = 0; j < _attrwidgets[i].size(); ++j)
174                 delete _attrwidgets[i][j];
175         }
176     }
178     // Show the active settings group and update all the AttrWidgets with new values
179     void show_and_update(const NR::FilterPrimitiveType t)
180     {
181         type(t);
182         _groups[t].show_all();
184         SPObject* ob = _dialog._primitive_list.get_selected();
186         _dialog.set_attrs_locked(true);
187         for(unsigned i = 0; i < _attrwidgets[_current_type].size(); ++i)
188             _attrwidgets[_current_type][i]->set_from_attribute(ob);
189         _dialog.set_attrs_locked(false);
190     }
192     void type(const NR::FilterPrimitiveType t)
193     {
194         _current_type = t;
195     }
197     void add(Gtk::ColorButton& cb, const SPAttributeEnum attr, const Glib::ustring& label)
198     {
199         //generic_add(cb, label);
200         //cb.signal_color_set().connect(
201         //    sigc::bind(sigc::mem_fun(_dialog, &FilterEffectsDialog::set_attr_color), attr, &cb));
202     }
204     // ConvolveMatrix
205     ConvolveMatrix* add(const SPAttributeEnum attr, const Glib::ustring& label)
206     {
207         ConvolveMatrix* conv = new ConvolveMatrix(attr);
208         add_widget(*conv, label);
209         _attrwidgets[_current_type].push_back(conv);
210         conv->signal_changed().connect(
211             sigc::bind(sigc::mem_fun(_dialog, &FilterEffectsDialog::set_attr_direct), attr, conv));
212         return conv;
213     }
215     // SpinSlider
216     SpinSlider* add(const SPAttributeEnum attr, const Glib::ustring& label,
217              const double lo, const double hi, const double step_inc, const double climb, const int digits)
218     {
219         SpinSlider* spinslider = new SpinSlider(lo, lo, hi, step_inc, climb, digits, attr);
220         add_widget(*spinslider, label);
221         _attrwidgets[_current_type].push_back(spinslider);
222         spinslider->signal_value_changed().connect(
223             sigc::bind(sigc::mem_fun(_dialog, &FilterEffectsDialog::set_attr_direct), attr, spinslider));
224         return spinslider;
225     }
227     // DualSpinSlider
228     DualSpinSlider* add(const SPAttributeEnum attr, const Glib::ustring& label1, const Glib::ustring& label2,
229                  const double lo, const double hi, const double step_inc, const double climb, const int digits)
230     {
231         DualSpinSlider* dss = new DualSpinSlider(lo, lo, hi, step_inc, climb, digits, attr);
232         add_widget(dss->get_spinslider1(), label1);
233         add_widget(dss->get_spinslider2(), label2);
234         _attrwidgets[_current_type].push_back(dss);
235         dss->signal_value_changed().connect(
236             sigc::bind(sigc::mem_fun(_dialog, &FilterEffectsDialog::set_attr_direct), attr, dss));
237         return dss;
238     }
240     // ComboBoxEnum
241     template<typename T> ComboBoxEnum<T>* add(const SPAttributeEnum attr,
242                                   const Glib::ustring& label,
243                                   const Util::EnumDataConverter<T>& conv)
244     {
245         ComboBoxEnum<T>* combo = new ComboBoxEnum<T>(conv, attr);
246         add_widget(*combo, label);
247         _attrwidgets[_current_type].push_back(combo);
248         combo->signal_changed().connect(
249             sigc::bind(sigc::mem_fun(_dialog, &FilterEffectsDialog::set_attr_direct), attr, combo));
250         return combo;
251     }
253     // Combine the two most recent settings widgets in to the same row
254     void combine()
255     {
256         Gtk::VBox& vb = _groups[_current_type];
257         const int size = vb.children().size();
258         if(size >= 2) {
259             Gtk::HBox* h1 = dynamic_cast<Gtk::HBox*>(vb.children()[size - 2].get_widget());
260             Gtk::HBox* h2 = dynamic_cast<Gtk::HBox*>(vb.children()[size - 1].get_widget());
261             Gtk::Widget* c1 = h1->children()[1].get_widget();
262             Gtk::Widget* c2 = h2->children()[1].get_widget();
263             h1->remove(*c1);
264             h2->remove(*c2);
265             h1->pack_start(*c1, false, false);
266             h1->pack_start(*c2, false, false);
267             vb.remove(*h2);
268         }
269     }
270 private:
271     /* Adds a new settings widget using the specified label. The label will be formatted with a colon
272        and all widgets within the setting group are aligned automatically. */
273     void add_widget(Gtk::Widget& w, const Glib::ustring& label)
274     {
275         Gtk::Label *lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
276         Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
277         hb->set_spacing(12);
278         hb->pack_start(*lbl, false, false);
279         hb->pack_start(w);
280         _groups[_current_type].pack_start(*hb);
282         _sizegroup->add_widget(*lbl);
284         hb->show();
285         lbl->show();
287         w.show();
288     }
290     Gtk::VBox _groups[NR::NR_FILTER_ENDPRIMITIVETYPE];
291     Glib::RefPtr<Gtk::SizeGroup> _sizegroup;
293     FilterEffectsDialog& _dialog;
294     std::vector<AttrWidget*> _attrwidgets[NR::NR_FILTER_ENDPRIMITIVETYPE];
295     NR::FilterPrimitiveType _current_type;
296 };
298 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
299                                           sigc::slot<void> rem)
301     Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
303     menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
304     Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
305     menu->append(*mi);
306     mi->signal_activate().connect(rem);
307     mi->show();
308     menu->accelerate(parent);
310     return menu;
313 static void try_id_change(SPObject* ob, const Glib::ustring& text)
315     // FIXME: this needs more serious error checking...
316     if(ob && !SP_ACTIVE_DOCUMENT->getObjectById(text.c_str())) {
317         SPException ex;
318         SP_EXCEPTION_INIT(&ex);
319         sp_object_setAttribute(ob, "id", text.c_str(), &ex);
320         sp_document_done(SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_FILTER_EFFECTS, _("Set object ID"));
321     }
324 /*** FilterModifier ***/
325 FilterEffectsDialog::FilterModifier::FilterModifier()
326     : _add(Gtk::Stock::ADD)
328     Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
329     pack_start(*sw);
330     pack_start(_add, false, false);
331     sw->add(_list);
333     _list.set_model(_model);
334     const int selcol = _list.append_column("", _cell_sel);
335     Gtk::TreeViewColumn* col = _list.get_column(selcol - 1);
336     if(col)
337        col->add_attribute(_cell_sel.property_sel(), _columns.sel);
338     _list.append_column(_("_Filter"), _columns.id);
340     sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
341     sw->set_shadow_type(Gtk::SHADOW_IN);
342     show_all_children();
343     _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
344     _list.signal_button_press_event().connect_notify(
345         sigc::mem_fun(*this, &FilterModifier::filter_list_button_press));
346     _list.signal_button_release_event().connect_notify(
347         sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
348     _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
349                               sigc::mem_fun(*this, &FilterModifier::remove_filter));
351     g_signal_connect(G_OBJECT(INKSCAPE), "change_selection",
352                      G_CALLBACK(&FilterModifier::on_inkscape_change_selection), this);
354     update_filters();
357 FilterEffectsDialog::FilterModifier::CellRendererSel::CellRendererSel()
358     : Glib::ObjectBase(typeid(CellRendererSel)),
359       _size(10),
360       _sel(*this, "sel", 0)
361 {}
363 Glib::PropertyProxy<int> FilterEffectsDialog::FilterModifier::CellRendererSel::property_sel()
365     return _sel.get_proxy();
368 void FilterEffectsDialog::FilterModifier::CellRendererSel::get_size_vfunc(
369     Gtk::Widget&, const Gdk::Rectangle*, int* x, int* y, int* w, int* h) const
371     if(x)
372         (*x) = 0;
373     if(y)
374         (*y) = 0;
375     if(w)
376         (*w) = _size;
377     if(h)
378         (*h) = _size;
381 void FilterEffectsDialog::FilterModifier::CellRendererSel::render_vfunc(
382     const Glib::RefPtr<Gdk::Drawable>& win, Gtk::Widget& widget, const Gdk::Rectangle& bg_area,
383     const Gdk::Rectangle& cell_area, const Gdk::Rectangle& expose_area, Gtk::CellRendererState flags)
385     const int sel = _sel.get_value();
387     if(sel > 0) {
388         const int s = _size - 2;
389         const int w = cell_area.get_width();
390         const int h = cell_area.get_height();
391         const int x = cell_area.get_x() + w / 2 - s / 2;
392         const int y = cell_area.get_y() + h / 2 - s / 2;
394         win->draw_rectangle(widget.get_style()->get_text_gc(Gtk::STATE_NORMAL), (sel == 1), x, y, s, s);
395     }
398 // When the selection changes, show the active filter(s) in the dialog
399 void FilterEffectsDialog::FilterModifier::on_inkscape_change_selection(Application *inkscape,
400                                                                        Selection *sel,
401                                                                        FilterModifier* fm)
403     if(fm && sel)
404         fm->update_selection(sel);
407 void FilterEffectsDialog::FilterModifier::update_selection(Selection *sel)
409     std::set<SPObject*> used;
411     for(GSList const *i = sel->itemList(); i != NULL; i = i->next) {
412         SPObject *obj = SP_OBJECT (i->data);
413         SPStyle *style = SP_OBJECT_STYLE (obj);
414         if(!style || !SP_IS_ITEM(obj)) continue;
416         if(style->filter.set && style->getFilter())
417             used.insert(style->getFilter());
418         else
419             used.insert(0);
420     }
422     const int size = used.size();
424     for(Gtk::TreeIter iter = _model->children().begin();
425         iter != _model->children().end(); ++iter) {
426         if(used.find((*iter)[_columns.filter]) != used.end()) {
427             // If only one filter is in use by the selection, select it
428             if(size == 1)
429                 _list.get_selection()->select(iter);
430             (*iter)[_columns.sel] = size;
431         }
432         else
433             (*iter)[_columns.sel] = 0;
434     }
437 Glib::SignalProxy0<void> FilterEffectsDialog::FilterModifier::signal_selection_changed()
439     return _list.get_selection()->signal_changed();
442 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
444     if(_list.get_selection()) {
445         Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
447         if(i)
448             return (*i)[_columns.filter];
449     }
451     return 0;
454 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
456     if(filter) {
457         for(Gtk::TreeModel::iterator i = _model->children().begin();
458             i != _model->children().end(); ++i) {
459             if((*i)[_columns.filter] == filter) {
460                 _list.get_selection()->select(i);
461                 break;
462             }
463         }
464     }
467 void FilterEffectsDialog::FilterModifier::filter_list_button_press(GdkEventButton* e)
469     // Double-click
470     if(e->type == GDK_2BUTTON_PRESS) {
471         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
472         SPDocument *doc = sp_desktop_document(desktop);
473         SPFilter* filter = get_selected_filter();
474         Inkscape::Selection *sel = sp_desktop_selection(desktop);
476         GSList const *items = sel->itemList();
478         for (GSList const *i = items; i != NULL; i = i->next) {
479             SPItem * item = SP_ITEM(i->data);
480             SPStyle *style = SP_OBJECT_STYLE(item);
481             g_assert(style != NULL);
482             
483             sp_style_set_property_url(SP_OBJECT(item), "filter", SP_OBJECT(filter), false);
484             SP_OBJECT(item)->requestDisplayUpdate((SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG ));
485         }
487         update_selection(sel);
488         sp_document_maybe_done(doc, "fillstroke:blur", SP_VERB_DIALOG_FILL_STROKE,  _("Change blur"));
489     }
492 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
494     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
495         const bool sensitive = get_selected_filter() != NULL;
496         _menu->items()[0].set_sensitive(sensitive);
497         _menu->items()[1].set_sensitive(sensitive);
498         _menu->popup(event->button, event->time);
499     }
502 void FilterEffectsDialog::FilterModifier::add_filter()
504     SPDocument* doc = sp_desktop_document(SP_ACTIVE_DESKTOP);
505     SPFilter* filter = new_filter(doc);
507     update_filters();
509     select_filter(filter);
511     sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
514 void FilterEffectsDialog::FilterModifier::remove_filter()
516     SPFilter *filter = get_selected_filter();
518     if(filter) {
519         SPDocument* doc = filter->document;
520         sp_repr_unparent(filter->repr);
522         sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
524         update_filters();
525     }
528 void FilterEffectsDialog::FilterModifier::duplicate_filter()
530     SPFilter* filter = get_selected_filter();
532     if(filter) {
533         Inkscape::XML::Node* repr = SP_OBJECT_REPR(filter), *parent = repr->parent();
534         repr = repr->duplicate(repr->document());
535         parent->appendChild(repr);
537         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
539         update_filters();
540     }
543 void FilterEffectsDialog::FilterModifier::filter_name_edited(const Glib::ustring& path, const Glib::ustring& text)
545     Gtk::TreeModel::iterator i = _model->get_iter(path);
547     if(i)
548         try_id_change((SPObject*)(*i)[_columns.filter], text);
551 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
552     : Glib::ObjectBase(typeid(CellRendererConnection)),
553       _primitive(*this, "primitive", 0)
554 {}
556 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
558     return _primitive.get_proxy();
561 int FilterEffectsDialog::CellRendererConnection::input_count(const SPFilterPrimitive* prim)
563     if(!prim)
564         return 0;
565     else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
566         return 2;
567     else if(SP_IS_FEMERGE(prim)) {
568         // Return the number of feMergeNode connections plus an extra one for adding a new input
569         int count = 1;
570         for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count);
571         return count;
572     }
573     else
574         return 1;
577 void FilterEffectsDialog::CellRendererConnection::set_text_width(const int w)
579     _text_width = w;
582 int FilterEffectsDialog::CellRendererConnection::get_text_width() const
584     return _text_width;
587 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
588     Gtk::Widget& widget, const Gdk::Rectangle* cell_area,
589     int* x_offset, int* y_offset, int* width, int* height) const
591     PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
593     if(x_offset)
594         (*x_offset) = 0;
595     if(y_offset)
596         (*y_offset) = 0;
597     if(width)
598         (*width) = size * primlist.primitive_count() + _text_width * 7;
599     if(height) {
600         // Scale the height depending on the number of inputs, unless it's
601         // the first primitive, in which case there are no connections
602         SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
603         (*height) = size * input_count(prim);
604     }
607 /*** PrimitiveList ***/
608 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
609     : _dialog(d),
610       _in_drag(0)
612     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
613     signal_expose_event().connect(sigc::mem_fun(*this, &PrimitiveList::on_expose_signal));
615     _model = Gtk::ListStore::create(_columns);
617     set_reorderable(true);
619     set_model(_model);
620     append_column(_("_Type"), _columns.type);
622     signal_selection_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
624     _connection_cell.set_text_width(init_text());
626     int cols_count = append_column(_("Connections"), _connection_cell);
627     Gtk::TreeViewColumn* col = get_column(cols_count - 1);
628     if(col)
629        col->add_attribute(_connection_cell.property_primitive(), _columns.primitive);
632 // Sets up a vertical Pango context/layout, and returns the largest
633 // width needed to render the FilterPrimitiveInput labels.
634 int FilterEffectsDialog::PrimitiveList::init_text()
636     // Set up a vertical context+layout
637     Glib::RefPtr<Pango::Context> context = create_pango_context();
638     const Pango::Matrix matrix = {0, -1, 1, 0, 0, 0};
639     context->set_matrix(matrix);
640     _vertical_layout = Pango::Layout::create(context);
642     int maxfont = 0;
643     for(int i = 0; i < FPInputConverter.end; ++i) {
644         _vertical_layout->set_text(FPInputConverter.get_label((FilterPrimitiveInput)i));
645         int fontw, fonth;
646         _vertical_layout->get_pixel_size(fontw, fonth);
647         if(fonth > maxfont)
648             maxfont = fonth;
649     }
651     return maxfont;
654 Glib::SignalProxy0<void> FilterEffectsDialog::PrimitiveList::signal_selection_changed()
656     return get_selection()->signal_changed();
659 /* Add all filter primitives in the current to the list.
660    Keeps the same selection if possible, otherwise selects the first element */
661 void FilterEffectsDialog::PrimitiveList::update()
663     SPFilter* f = _dialog._filter_modifier.get_selected_filter();
664     const SPFilterPrimitive* active_prim = get_selected();
665     bool active_found = false;
667     _model->clear();
669     if(f) {
670         _dialog._primitive_box.set_sensitive(true);
672         for(SPObject *prim_obj = f->children;
673                 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
674                 prim_obj = prim_obj->next) {
675             SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
676             if(prim) {
677                 Gtk::TreeModel::Row row = *_model->append();
678                 row[_columns.primitive] = prim;
679                 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
680                 row[_columns.type] = FPConverter.get_label(row[_columns.type_id]);
681                 row[_columns.id] = SP_OBJECT_ID(prim);
683                 if(prim == active_prim) {
684                     get_selection()->select(row);
685                     active_found = true;
686                 }
687             }
688         }
690         if(!active_found && _model->children().begin())
691             get_selection()->select(_model->children().begin());
692     }
693     else {
694         _dialog._primitive_box.set_sensitive(false);
695     }
698 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
700     _primitive_menu = menu;
703 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
705     if(_dialog._filter_modifier.get_selected_filter()) {
706         Gtk::TreeModel::iterator i = get_selection()->get_selected();
707         if(i)
708             return (*i)[_columns.primitive];
709     }
711     return 0;
714 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
716     for(Gtk::TreeIter i = _model->children().begin();
717         i != _model->children().end(); ++i) {
718         if((*i)[_columns.primitive] == prim)
719             get_selection()->select(i);
720     }
725 bool FilterEffectsDialog::PrimitiveList::on_expose_signal(GdkEventExpose* e)
727     Gdk::Rectangle clip(e->area.x, e->area.y, e->area.width, e->area.height);
728     Glib::RefPtr<Gdk::Window> win = get_bin_window();
729     Glib::RefPtr<Gdk::GC> darkgc = get_style()->get_dark_gc(Gtk::STATE_NORMAL);
731     SPFilterPrimitive* prim = get_selected();
732     int row_count = get_model()->children().size();
734     int fheight = CellRendererConnection::size;
735     Gdk::Rectangle rct, vis;
736     Gtk::TreeIter row = get_model()->children().begin();
737     int text_start_x = 0;
738     if(row) {
739         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
740         get_visible_rect(vis);
741         int vis_x, vis_y;
742         tree_to_widget_coords(vis.get_x(), vis.get_y(), vis_x, vis_y);
744         text_start_x = rct.get_x() + rct.get_width() - _connection_cell.get_text_width() * (FPInputConverter.end + 1);
745         for(int i = 0; i < FPInputConverter.end; ++i) {
746             _vertical_layout->set_text(FPInputConverter.get_label((FilterPrimitiveInput)i));
747             const int x = text_start_x + _connection_cell.get_text_width() * (i + 1);
748             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());
749             get_bin_window()->draw_layout(get_style()->get_text_gc(Gtk::STATE_NORMAL), x, vis_y, _vertical_layout);
750             get_bin_window()->draw_line(darkgc, x, vis_y, x, vis_y + vis.get_height());
751         }
752     }
754     int row_index = 0;
755     for(; row != get_model()->children().end(); ++row, ++row_index) {
756         get_cell_area(get_model()->get_path(row), *get_column(1), rct);
757         const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
759         // Check mouse state
760         int mx, my;
761         Gdk::ModifierType mask;
762         get_bin_window()->get_pointer(mx, my, mask);
764         // Outline the bottom of the connection area
765         const int outline_x = x + fheight * (row_count - row_index);
766         get_bin_window()->draw_line(darkgc, x, y + h, outline_x, y + h);
768         // Side outline
769         get_bin_window()->draw_line(darkgc, outline_x, y, outline_x, y + h);
771         std::vector<Gdk::Point> con_poly;
772         int con_drag_y;
773         bool inside;
774         const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
775         const int inputs = CellRendererConnection::input_count(row_prim);
777         if(SP_IS_FEMERGE(row_prim)) {
778             for(int i = 0; i < inputs; ++i) {
779                 inside = do_connection_node(row, i, con_poly, mx, my);
780                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
781                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
782                                                inside, con_poly);
784                 // TODO: draw connections for each of the feMergeNodes
785             }
786         }
787         else {
788             // Draw "in" shape
789             inside = do_connection_node(row, 0, con_poly, mx, my);
790             con_drag_y = con_poly[2].get_y();
791             get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
792                                            darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
793                                            inside, con_poly);
794             // Draw "in" connection
795             if(_in_drag != 1 || row_prim != prim)
796                 draw_connection(row, SP_ATTR_IN, text_start_x, outline_x, con_poly[2].get_y(), row_count);
798             if(inputs == 2) {
799                 // Draw "in2" shape
800                 inside = do_connection_node(row, 1, con_poly, mx, my);
801                 if(_in_drag == 2)
802                     con_drag_y = con_poly[2].get_y();
803                 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
804                                                darkgc : get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
805                                                inside, con_poly);
806                 // Draw "in2" connection
807                 if(_in_drag != 2 || row_prim != prim)
808                     draw_connection(row, SP_ATTR_IN2, text_start_x, outline_x, con_poly[2].get_y(), row_count);
809             }
811             // Draw drag connection
812             if(row_prim == prim && _in_drag) {
813                 get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
814                                             mx, con_drag_y);
815                 get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
816             }
817         }
818     }
820     return true;
823 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const SPAttributeEnum attr,
824                                                          const int text_start_x, const int x1, const int y1,
825                                                          const int row_count)
827     const Gtk::TreeIter res = find_result(input, attr);
828     Glib::RefPtr<Gdk::GC> gc = get_style()->get_black_gc();
829     
830     if(res == input) {
831         // Draw straight connection to a standard input
832         const int tw = _connection_cell.get_text_width();
833         const int src = 1 + (int)FPInputConverter.get_id_from_key(
834             SP_OBJECT_REPR((*res)[_columns.primitive])->attribute((const gchar*)sp_attribute_name(attr)));
835         gint end_x = text_start_x + tw * src + (int)(tw * 0.5f);
836         get_bin_window()->draw_rectangle(gc, true, end_x-2, y1-2, 5, 5);
837         get_bin_window()->draw_line(gc, x1, y1, end_x, y1);
838     }
839     else if(res != _model->children().end()) {
840         Gdk::Rectangle rct;
842         get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
843         const int fheight = CellRendererConnection::size;
845         get_cell_area(get_model()->get_path(res), *get_column(1), rct);
846         const int row_index = find_index(res);
847         const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
848         const int y2 = rct.get_y() + rct.get_height();
850         // Draw an 'L'-shaped connection to another filter primitive
851         get_bin_window()->draw_line(gc, x1, y1, x2, y1);
852         get_bin_window()->draw_line(gc, x2, y1, x2, y2);
853     }
856 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
857 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
858                                                             std::vector<Gdk::Point>& points,
859                                                             const int ix, const int iy)
861     Gdk::Rectangle rct;
862     const int input_count = CellRendererConnection::input_count((*row)[_columns.primitive]);
864     get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
865     const int fheight = CellRendererConnection::size;
867     get_cell_area(_model->get_path(row), *get_column(1), rct);
868     const float h = rct.get_height() / input_count;
870     const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
871     const int con_w = (int)(fheight * 0.35f);
872     const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
873     points.clear();
874     points.push_back(Gdk::Point(x, con_y));
875     points.push_back(Gdk::Point(x, con_y + con_w * 2));
876     points.push_back(Gdk::Point(x - con_w, con_y + con_w));
878     return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
881 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
882                                                                     const SPAttributeEnum attr)
884     SPFilterPrimitive* prim = (*start)[_columns.primitive];
885     Gtk::TreeIter target = _model->children().end();
886     int image;
888     if(attr == SP_ATTR_IN)
889         image = prim->image_in;
890     else if(attr == SP_ATTR_IN2) {
891         if(SP_IS_FEBLEND(prim))
892             image = SP_FEBLEND(prim)->in2;
893         else if(SP_IS_FECOMPOSITE(prim))
894             image = SP_FECOMPOSITE(prim)->in2;
895         /*else if(SP_IS_FEDISPLACEMENTMAP(prim))
896         image = SP_FEDISPLACEMENTMAP(prim)->in2;*/
897         else
898             return target;
899     }
900     else
901         return target;
903     if(image >= 0) {
904         for(Gtk::TreeIter i = _model->children().begin();
905             i != start; ++i) {
906             if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
907                 target = i;
908         }
909         return target;
910     }
911     else if(image < -1)
912         return start;
914     return target;
917 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
919     int i = 0;
920     for(Gtk::TreeIter iter = _model->children().begin();
921         iter != target; ++iter, ++i);
922     return i;
925 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
927     Gtk::TreePath path;
928     Gtk::TreeViewColumn* col;
929     const int x = (int)e->x, y = (int)e->y;
930     int cx, cy;
932     _drag_prim = 0;
933     
934     if(get_path_at_pos(x, y, path, col, cx, cy)) {
935         Gtk::TreeIter iter = _model->get_iter(path);
936         std::vector<Gdk::Point> points;
937         if(do_connection_node(_model->get_iter(path), 0, points, x, y))
938             _in_drag = 1;
939         else if(do_connection_node(_model->get_iter(path), 1, points, x, y))
940             _in_drag = 2;
941         
942         queue_draw();
943         _drag_prim = (*iter)[_columns.primitive];
944     }
946     if(_in_drag) {
947         get_selection()->select(path);
948         return true;
949     }
950     else
951         return Gtk::TreeView::on_button_press_event(e);
954 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
956     queue_draw();
958     return Gtk::TreeView::on_motion_notify_event(e);
961 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
963     SPFilterPrimitive *prim = get_selected(), *target;
965     if(_in_drag && prim) {
966         Gtk::TreePath path;
967         Gtk::TreeViewColumn* col;
968         int cx, cy;
969         
970         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
971             const gchar *in_val = 0;
972             Glib::ustring result;
973             Gtk::TreeIter target_iter = _model->get_iter(path);
974             target = (*target_iter)[_columns.primitive];
976             Gdk::Rectangle rct;
977             get_cell_area(path, *col, rct);
978             const int twidth = _connection_cell.get_text_width();
979             const int sources_x = rct.get_width() - twidth * FPInputConverter.end;
980             if(cx > sources_x) {
981                 int src = (cx - sources_x) / twidth;
982                 if(src < 0)
983                     src = 0;
984                 else if(src >= FPInputConverter.end)
985                     src = FPInputConverter.end - 1;
986                 result = FPInputConverter.get_key((FilterPrimitiveInput)src);
987                 in_val = result.c_str();
988             }
989             else {
990                 // Ensure that the target comes before the selected primitive
991                 for(Gtk::TreeIter iter = _model->children().begin();
992                     iter != get_selection()->get_selected(); ++iter) {
993                     if(iter == target_iter) {
994                         Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
995                         // Make sure the target has a result
996                         const gchar *gres = repr->attribute("result");
997                         if(!gres) {
998                             result = "result" + Glib::Ascii::dtostr(SP_FILTER(prim->parent)->_image_number_next);
999                             repr->setAttribute("result", result.c_str());
1000                             in_val = result.c_str();
1001                         }
1002                         else
1003                             in_val = gres;
1004                         break;
1005                     }
1006                 }
1007             }
1009             if(_in_drag == 1)
1010                 _dialog.set_attr(SP_ATTR_IN, in_val);
1011             else if(_in_drag == 2)
1012                 _dialog.set_attr(SP_ATTR_IN2, in_val);
1013         }
1015         _in_drag = 0;
1016         queue_draw();
1018         _dialog.update_settings_view();
1019     }
1021     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
1022         const bool sensitive = get_selected() != NULL;
1023         _primitive_menu->items()[0].set_sensitive(sensitive);
1024         _primitive_menu->items()[1].set_sensitive(sensitive);
1025         _primitive_menu->popup(e->button, e->time);
1027         return true;
1028     }
1029     else
1030         return Gtk::TreeView::on_button_release_event(e);
1033 // Checks all of prim's inputs, removes any that use result
1034 void check_single_connection(SPFilterPrimitive* prim, const int result)
1036     if(prim && result >= 0) {
1038         if(prim->image_in == result)
1039             SP_OBJECT_REPR(prim)->setAttribute("in", 0);
1041         if(SP_IS_FEBLEND(prim)) {
1042             if(SP_FEBLEND(prim)->in2 == result)
1043                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1044         }
1045         else if(SP_IS_FECOMPOSITE(prim)) {
1046             if(SP_FECOMPOSITE(prim)->in2 == result)
1047                 SP_OBJECT_REPR(prim)->setAttribute("in2", 0);
1048         }
1049     }
1052 // Remove any connections going to/from prim_iter that forward-reference other primitives
1053 void FilterEffectsDialog::PrimitiveList::sanitize_connections(const Gtk::TreeIter& prim_iter)
1055     SPFilterPrimitive *prim = (*prim_iter)[_columns.primitive];
1056     bool before = true;
1058     for(Gtk::TreeIter iter = _model->children().begin();
1059         iter != _model->children().end(); ++iter) {
1060         if(iter == prim_iter)
1061             before = false;
1062         else {
1063             SPFilterPrimitive* cur_prim = (*iter)[_columns.primitive];
1064             if(before)
1065                 check_single_connection(cur_prim, prim->image_out);
1066             else
1067                 check_single_connection(prim, cur_prim->image_out);
1068         }
1069     }
1072 // Reorder the filter primitives to match the list order
1073 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>&)
1075     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
1076     int ndx = 0;
1078     for(Gtk::TreeModel::iterator iter = _model->children().begin();
1079         iter != _model->children().end(); ++iter, ++ndx) {
1080         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
1081         if(prim) {
1082             SP_OBJECT_REPR(prim)->setPosition(ndx);
1083             if(_drag_prim == prim) {
1084                 sanitize_connections(iter);
1085                 get_selection()->select(iter);
1086             }
1087         }
1088     }
1090     filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
1092     sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
1095 int FilterEffectsDialog::PrimitiveList::primitive_count() const
1097     return _model->children().size();
1100 /*** FilterEffectsDialog ***/
1102 FilterEffectsDialog::FilterEffectsDialog() 
1103     : Dialog ("dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
1104       _primitive_list(*this),
1105       _add_primitive_type(FPConverter),
1106       _add_primitive(Gtk::Stock::ADD),
1107       _empty_settings(_("No primitive selected"), Gtk::ALIGN_LEFT),
1108       _locked(false)
1110     _settings = new Settings(*this);
1112     // Initialize widget hierarchy
1113     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
1114     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
1115     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
1116     Gtk::Frame* fr_settings = Gtk::manage(new Gtk::Frame(_("<b>Settings</b>")));
1117     Gtk::Alignment* al_settings = Gtk::manage(new Gtk::Alignment);
1118     get_vbox()->add(*hpaned);
1119     hpaned->pack1(_filter_modifier);
1120     hpaned->pack2(_primitive_box);
1121     _primitive_box.pack_start(*sw_prims);
1122     _primitive_box.pack_start(*hb_prims, false, false);
1123     sw_prims->add(_primitive_list);
1124     hb_prims->pack_end(_add_primitive, false, false);
1125     hb_prims->pack_end(_add_primitive_type, false, false);
1126     get_vbox()->pack_start(*fr_settings, false, false);
1127     fr_settings->add(*al_settings);
1128     al_settings->add(_settings_box);
1130     _primitive_list.signal_selection_changed().connect(
1131         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
1132     _filter_modifier.signal_selection_changed().connect(
1133         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
1135     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
1136     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
1137     al_settings->set_padding(0, 0, 12, 0);
1138     fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
1139     ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
1140     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
1141     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
1142                                                sigc::mem_fun(*this, &FilterEffectsDialog::remove_primitive)));
1143     
1144     show_all_children();
1145     init_settings_widgets();
1146     _primitive_list.update();
1147     update_settings_view();
1150 FilterEffectsDialog::~FilterEffectsDialog()
1152     delete _settings;
1155 void FilterEffectsDialog::set_attrs_locked(const bool l)
1157     _locked = l;
1160 void FilterEffectsDialog::init_settings_widgets()
1162     // TODO: Find better range/climb-rate/digits values for the SpinSliders,
1163     //       most of the current values are complete guesses!
1165     _empty_settings.set_sensitive(false);
1166     _settings_box.pack_start(_empty_settings);
1168     _settings->type(NR_FILTER_BLEND);
1169     _settings->add(SP_ATTR_MODE, _("Mode"), BlendModeConverter);
1171     _settings->type(NR_FILTER_COMPOSITE);
1172     _settings->add(SP_ATTR_OPERATOR, _("Operator"), CompositeOperatorConverter);
1173     _k1 = _settings->add(SP_ATTR_K1, _("K1"), -10, 10, 1, 0.01, 1);
1174     _k2 = _settings->add(SP_ATTR_K2, _("K2"), -10, 10, 1, 0.01, 1);
1175     _k3 = _settings->add(SP_ATTR_K3, _("K3"), -10, 10, 1, 0.01, 1);
1176     _k4 = _settings->add(SP_ATTR_K4, _("K4"), -10, 10, 1, 0.01, 1);
1178     _settings->type(NR_FILTER_CONVOLVEMATRIX);
1179     DualSpinSlider* order = _settings->add(SP_ATTR_ORDER, _("Size"), "", 1, 5, 1, 1, 0);
1180     order->remove_scale();
1181     _settings->combine();
1182     SpinSlider* tx = _settings->add(SP_ATTR_TARGETX, _("Target"), 1, 5, 1, 1, 0);
1183     tx->remove_scale();
1184     SpinSlider* ty = _settings->add(SP_ATTR_TARGETY, "", 1, 5, 1, 1, 0);
1185     ty->remove_scale();
1186     _settings->combine();
1187     ConvolveMatrix* convmat = _settings->add(SP_ATTR_KERNELMATRIX, _("Kernel"));
1188     order->signal_value_changed().connect(
1189         sigc::bind(sigc::mem_fun(*convmat, &ConvolveMatrix::update_direct), this));
1190     _settings->add(SP_ATTR_DIVISOR, _("Divisor"), 0.01, 10, 1, 0.01, 1);
1191     _settings->add(SP_ATTR_BIAS, _("Bias"), -10, 10, 1, 0.01, 1);
1192     
1193     _settings->type(NR_FILTER_GAUSSIANBLUR);
1194     _settings->add(SP_ATTR_STDDEVIATION, _("Standard Deviation X"), _("Standard Deviation Y"), 0, 100, 1, 0.01, 1);
1196     _settings->type(NR_FILTER_OFFSET);
1197     _settings->add(SP_ATTR_DX, _("Delta X"), -100, 100, 1, 0.01, 1);
1198     _settings->add(SP_ATTR_DY, _("Delta Y"), -100, 100, 1, 0.01, 1);
1200     _settings->type(NR_FILTER_SPECULARLIGHTING);
1201     //_settings->add(_specular_color, SP_PROP_LIGHTING_COLOR, _("Specular Color"));
1202     _settings->add(SP_ATTR_SURFACESCALE, _("Surface Scale"), -10, 10, 1, 0.01, 1);
1203     _settings->add(SP_ATTR_SPECULARCONSTANT, _("Constant"), 0, 100, 1, 0.01, 1);
1204     _settings->add(SP_ATTR_SPECULAREXPONENT, _("Exponent"), 1, 128, 1, 0.01, 1);
1206     _settings->type(NR_FILTER_TURBULENCE);
1207     /*std::vector<Gtk::Widget*> trb_grp;
1208     trb_grp.push_back(&_turbulence_fractalnoise);
1209     trb_grp.push_back(&_turbulence_turbulence);
1210     _settings->add(trb_grp);
1211     _turbulence.add_setting(_turbulence_numoctaves, _("Octaves"));
1212     _turbulence.add_setting(_turbulence_basefrequency, _("Base Frequency"));
1213     _turbulence.add_setting(_turbulence_seed, _("Seed"));
1214     _turbulence.add_setting(_turbulence_stitchtiles);*/
1217 void FilterEffectsDialog::add_primitive()
1219     SPFilter* filter = _filter_modifier.get_selected_filter();
1220     
1221     if(filter) {
1222         SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
1224         _primitive_list.update();
1225         _primitive_list.select(prim);
1227         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
1228     }
1231 void FilterEffectsDialog::remove_primitive()
1233     SPFilterPrimitive* prim = _primitive_list.get_selected();
1235     if(prim) {
1236         sp_repr_unparent(prim->repr);
1238         sp_document_done(sp_desktop_document(SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_FILTER_EFFECTS,
1239                          _("Remove filter primitive"));
1241         _primitive_list.update();
1242     }
1245 void FilterEffectsDialog::duplicate_primitive()
1247     SPFilter* filter = _filter_modifier.get_selected_filter();
1248     SPFilterPrimitive* origprim = _primitive_list.get_selected();
1250     if(filter && origprim) {
1251         Inkscape::XML::Node *repr;
1252         repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
1253         SP_OBJECT_REPR(filter)->appendChild(repr);
1255         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
1257         _primitive_list.update();
1258     }
1261 void FilterEffectsDialog::set_attr_color(const SPAttributeEnum attr, const Gtk::ColorButton* input)
1263     if(input->is_sensitive()) {
1264         std::ostringstream os;
1265         const Gdk::Color c = input->get_color();
1266         const int r = 255 * c.get_red() / 65535, g = 255 * c.get_green() / 65535, b = 255 * c.get_blue() / 65535;
1267         os << "rgb(" << r << "," << g << "," << b << ")";
1268         set_attr(attr, os.str().c_str());
1269     }
1272 void FilterEffectsDialog::set_attr_direct(const SPAttributeEnum attr, const AttrWidget* input)
1274     set_attr(attr, input->get_as_attribute().c_str());
1277 void FilterEffectsDialog::set_attr(const SPAttributeEnum attr, const gchar* val)
1279     if(!_locked) {
1280         SPFilter *filter = _filter_modifier.get_selected_filter();
1281         SPFilterPrimitive* prim = _primitive_list.get_selected();
1282         
1283         if(filter && prim) {
1284             update_settings_sensitivity();
1285             
1286             SP_OBJECT_REPR(prim)->setAttribute((gchar*)sp_attribute_name(attr), val);
1287             filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
1288             
1289             sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Set filter primitive attribute"));
1290         }
1291     }
1294 void FilterEffectsDialog::update_settings_view()
1296     SPFilterPrimitive* prim = _primitive_list.get_selected();
1298     // Hide all the settings
1299     _settings_box.hide_all();
1300     _settings_box.show();
1302     _settings_box.set_sensitive(false);
1303     _empty_settings.show();
1305     if(prim) {
1306         const FilterPrimitiveType tid = FPConverter.get_id_from_key(prim->repr->name());
1308         _settings->show_and_update(tid);
1310         _settings_box.set_sensitive(true);
1311         _empty_settings.hide();
1312     }
1314     update_settings_sensitivity();
1317 void FilterEffectsDialog::update_settings_sensitivity()
1319     SPFilterPrimitive* prim = _primitive_list.get_selected();
1320     const bool use_k = SP_IS_FECOMPOSITE(prim) && SP_FECOMPOSITE(prim)->composite_operator == COMPOSITE_ARITHMETIC;
1321     _k1->set_sensitive(use_k);
1322     _k2->set_sensitive(use_k);
1323     _k3->set_sensitive(use_k);
1324     _k4->set_sensitive(use_k);
1327 } // namespace Dialog
1328 } // namespace UI
1329 } // namespace Inkscape
1331 /*
1332   Local Variables:
1333   mode:c++
1334   c-file-style:"stroustrup"
1335   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1336   indent-tabs-mode:nil
1337   fill-column:99
1338   End:
1339 */
1340 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :